merge autoland to mozilla-central. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Thu, 07 Sep 2017 23:50:27 +0200
changeset 429001 64bf417d1bdf9bb8b562d73cc0742b1ec60a8d0e
parent 428913 3c96d611ebd67fc219d22bcb476a72412c76f6c7 (current diff)
parent 429000 bf29e38baf5d25933128d6e564ae732a422a2837 (diff)
child 429056 b4c1ad9565ee9d00d96501c4a83083daf25c1413
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge autoland to mozilla-central. r=merge a=merge MozReview-Commit-ID: 6Nq2hl5g0a5
dom/indexedDB/test/bfcache_iframe1.html
dom/indexedDB/test/bfcache_iframe2.html
dom/media/webspeech/synth/test/file_bfcache_frame.html
dom/media/webspeech/synth/test/file_bfcache_frame2.html
dom/workers/test/WorkerDebugger_frozen_iframe1.html
dom/workers/test/WorkerDebugger_frozen_iframe2.html
dom/workers/test/suspend_iframe.html
mobile/android/app/src/photon/res/drawable-hdpi/open_in_browser.png
mobile/android/app/src/photon/res/drawable-hdpi/settings_notifications.png
mobile/android/app/src/photon/res/drawable-xhdpi/open_in_browser.png
mobile/android/app/src/photon/res/drawable-xhdpi/settings_notifications.png
mobile/android/app/src/photon/res/drawable-xxhdpi/open_in_browser.png
mobile/android/app/src/photon/res/drawable-xxhdpi/settings_notifications.png
mobile/android/app/src/photon/res/drawable-xxxhdpi/open_in_browser.png
mobile/android/app/src/photon/res/drawable-xxxhdpi/settings_notifications.png
mobile/android/base/java/org/mozilla/gecko/BootReceiver.java
mobile/android/base/java/org/mozilla/gecko/feeds/ContentNotificationsDelegate.java
mobile/android/base/java/org/mozilla/gecko/feeds/FeedAlarmReceiver.java
mobile/android/base/java/org/mozilla/gecko/feeds/FeedFetcher.java
mobile/android/base/java/org/mozilla/gecko/feeds/FeedService.java
mobile/android/base/java/org/mozilla/gecko/feeds/action/CheckForUpdatesAction.java
mobile/android/base/java/org/mozilla/gecko/feeds/action/EnrollSubscriptionsAction.java
mobile/android/base/java/org/mozilla/gecko/feeds/action/FeedAction.java
mobile/android/base/java/org/mozilla/gecko/feeds/action/SetupAlarmsAction.java
mobile/android/base/java/org/mozilla/gecko/feeds/action/SubscribeToFeedAction.java
mobile/android/base/java/org/mozilla/gecko/feeds/action/WithdrawSubscriptionsAction.java
mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSite.java
mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSiteBlogger.java
mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSiteMedium.java
mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSiteTumblr.java
mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSiteWordpress.java
mobile/android/base/java/org/mozilla/gecko/feeds/parser/Feed.java
mobile/android/base/java/org/mozilla/gecko/feeds/parser/Item.java
mobile/android/base/java/org/mozilla/gecko/feeds/parser/SimpleFeedParser.java
mobile/android/base/java/org/mozilla/gecko/feeds/subscriptions/FeedSubscription.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/feeds/knownsites/TestKnownSiteBlogger.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/feeds/knownsites/TestKnownSiteMedium.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/feeds/knownsites/TestKnownSiteTumblr.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/feeds/parser/TestSimpleFeedParser.java
uriloader/exthandler/win/nsMIMEInfoWin.cpp
xpcom/base/nsVersionComparator.cpp
xpcom/string/string-template-def-char.h
xpcom/string/string-template-def-unichar.h
xpcom/string/string-template-undef.h
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1558,17 +1558,18 @@ pref("browser.tabs.remote.desktopbehavio
 // We leave it here set to false to reset users' defaults and allow
 // us to change everybody to true in the future, when desired.
 pref("browser.tabs.remote.autostart.1", false);
 pref("browser.tabs.remote.autostart.2", true);
 #endif
 
 // For speculatively warming up tabs to improve perceived
 // performance while using the async tab switcher.
-pref("browser.tabs.remote.warmup.enabled", true);
+// Disabled until bug 1397426 is fixed.
+pref("browser.tabs.remote.warmup.enabled", false);
 pref("browser.tabs.remote.warmup.maxTabs", 3);
 pref("browser.tabs.remote.warmup.unloadDelayMs", 2000);
 
 // For the about:tabcrashed page
 pref("browser.tabs.crashReporting.sendReport", true);
 pref("browser.tabs.crashReporting.includeURL", false);
 pref("browser.tabs.crashReporting.requestEmail", false);
 pref("browser.tabs.crashReporting.emailMe", false);
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -1157,34 +1157,53 @@ var PlacesToolbarHelper = {
     let viewElt = this._viewElt;
     if (!viewElt || viewElt._placesView)
       return;
 
     // CustomizableUI.addListener is idempotent, so we can safely
     // call this multiple times.
     CustomizableUI.addListener(this);
 
+    if (!this._isObservingToolbars) {
+      this._isObservingToolbars = true;
+      window.addEventListener("toolbarvisibilitychange", this);
+    }
+
     // If the bookmarks toolbar item is:
     // - not in a toolbar, or;
     // - the toolbar is collapsed, or;
     // - the toolbar is hidden some other way:
     // don't initialize.  Also, there is no need to initialize the toolbar if
     // customizing, because that will happen when the customization is done.
     let toolbar = this._getParentToolbar(viewElt);
     if (!toolbar || toolbar.collapsed || this._isCustomizing ||
-        getComputedStyle(toolbar, "").display == "none")
+        getComputedStyle(toolbar, "").display == "none") {
       return;
+    }
 
     new PlacesToolbar(this._place);
     if (forceToolbarOverflowCheck) {
       viewElt._placesView.updateOverflowStatus();
     }
   },
 
+  handleEvent(event) {
+    switch (event.type) {
+      case "toolbarvisibilitychange":
+        if (event.target == this._viewElt.parentNode.parentNode)
+          this._resetView();
+        break;
+    }
+  },
+
   uninit: function PTH_uninit() {
+    if (this._isObservingToolbars) {
+      delete this._isObservingToolbars;
+      window.removeEventListener("toolbarvisibilitychange", this);
+    }
     CustomizableUI.removeListener(this);
   },
 
   customizeStart: function PTH_customizeStart() {
     try {
       let viewElt = this._viewElt;
       if (viewElt && viewElt._placesView)
         viewElt._placesView.uninit();
@@ -1468,22 +1487,23 @@ var RecentBookmarksMenuUI = {
   /**
    * nsINavBookmarkObserver methods.
    */
 
   /*
    * Handles onItemRemoved notifications from the bookmarks service.
    */
   onItemRemoved(itemId, parentId, index, itemType, uri, guid) {
+    if (!this.visible) {
+      return;
+    }
     // Update the menu when a bookmark has been removed.
     // The native menubar on Mac doesn't support live update, so this is
     // unlikely to be called there.
-    if (this._recentGuids.size == 0 ||
-        (guid && this._recentGuids.has(guid))) {
-
+    if (guid && this._recentGuids.has(guid)) {
       if (this._itemRemovedTimer) {
         clearTimeout(this._itemRemovedTimer);
       }
 
       this._itemRemovedTimer = setTimeout(() => {
         this._clearExistingItems();
         this._insertRecentMenuItems();
       }, this.ITEM_REMOVED_TIMEOUT);
@@ -1530,17 +1550,16 @@ var LibraryUI = {
     animatableBox.style.setProperty("--library-button-height", libraryBounds.height + "px");
     animatableBox.style.setProperty("--library-icon-x", iconBounds.x + "px");
     if (navBar.hasAttribute("brighttext")) {
       animatableBox.setAttribute("brighttext", "true");
     } else {
       animatableBox.removeAttribute("brighttext");
     }
     animatableBox.removeAttribute("fade");
-    gNavToolbox.setAttribute("animate", animation);
     libraryButton.setAttribute("animate", animation);
     animatableBox.setAttribute("animate", animation);
     if (!this._libraryButtonAnimationEndListeners[animation]) {
       this._libraryButtonAnimationEndListeners[animation] = event => {
         this._libraryButtonAnimationEndListener(event, animation);
       }
     }
     animatableBox.addEventListener("animationend", this._libraryButtonAnimationEndListeners[animation]);
@@ -1553,17 +1572,16 @@ var LibraryUI = {
       animatableBox.setAttribute("fade", "true");
     } else if (aEvent.animationName == `library-${animation}-fade`) {
       animatableBox.removeEventListener("animationend", LibraryUI._libraryButtonAnimationEndListeners[animation]);
       animatableBox.removeAttribute("animate");
       animatableBox.removeAttribute("fade");
       let libraryButton = document.getElementById("library-button");
       // Put the 'fill' back in the normal icon.
       libraryButton.removeAttribute("animate");
-      gNavToolbox.removeAttribute("animate");
     }
   },
 };
 
 /**
  * Handles the bookmarks menu-button in the toolbar.
  */
 
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -174,16 +174,32 @@ tabbrowser {
   pointer-events: none; /* avoid blocking dragover events on scroll buttons */
 }
 
 .tabbrowser-tab[tabdrop-samewindow],
 .tabbrowser-tabs[movingtab] > .tabbrowser-tab[fadein]:not([selected]) {
   transition: transform 200ms var(--animation-easing-function);
 }
 
+#TabsToolbar[movingtab] > .tabbrowser-tabs {
+  padding-bottom: 15px;
+}
+
+#TabsToolbar[movingtab] + #nav-bar {
+  margin-top: -15px;
+  pointer-events: none;
+}
+
+/* Allow dropping a tab on buttons with associated drop actions. */
+#TabsToolbar[movingtab] + #nav-bar > #nav-bar-customization-target > #home-button,
+#TabsToolbar[movingtab] + #nav-bar > #nav-bar-customization-target > #downloads-button,
+#TabsToolbar[movingtab] + #nav-bar > #nav-bar-customization-target > #bookmarks-menu-button {
+  pointer-events: auto;
+}
+
 .new-tab-popup,
 #alltabs-popup {
   -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-alltabs-popup");
 }
 
 toolbar[printpreview="true"] {
   -moz-binding: url("chrome://global/content/printPreviewBindings.xml#printpreviewtoolbar");
 }
@@ -662,17 +678,17 @@ html|input.urlbar-input[textoverflow]:no
   transition: none;
 }
 
 #DateTimePickerPanel[active="true"] {
   -moz-binding: url("chrome://global/content/bindings/datetimepopup.xml#datetime-popup");
 }
 
 #urlbar[pageproxystate=invalid] > #page-action-buttons > .urlbar-page-action,
-#identity-box.chromeUI ~ #page-action-buttons > .urlbar-page-action,
+#identity-box.chromeUI ~ #page-action-buttons > .urlbar-page-action:not(#star-button-box),
 #urlbar[usertyping] > .urlbar-textbox-container > .urlbar-history-dropmarker,
 .urlbar-go-button[pageproxystate="valid"],
 .urlbar-go-button:not([parentfocused="true"]),
 #urlbar[pageproxystate="invalid"] > #identity-box > #blocked-permissions-container,
 #urlbar[pageproxystate="invalid"] > #identity-box > #notification-popup-box,
 #urlbar[pageproxystate="invalid"] > #identity-box > #identity-icon-labels {
   display: none;
 }
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -5526,17 +5526,16 @@ function setToolbarVisibility(toolbar, i
     detail: {
       visible: isVisible
     },
     bubbles: true
   };
   let event = new CustomEvent("toolbarvisibilitychange", eventParams);
   toolbar.dispatchEvent(event);
 
-  PlacesToolbarHelper.init();
   BookmarkingUI.onToolbarVisibilityChange();
 }
 
 function updateToggleControlLabel(control) {
   if (!control.hasAttribute("label-checked")) {
     return;
   }
 
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -909,27 +909,26 @@
                 <image id="pageActionButton"
                        class="urlbar-icon urlbar-page-action"
                        role="button"
                        tooltiptext="&pageActionButton.tooltip;"
                        onmousedown="BrowserPageActions.mainButtonClicked(event);"/>
                 <hbox id="star-button-box"
                       hidden="true"
                       class="urlbar-icon-wrapper urlbar-page-action"
-                      role="button"
                       context="pageActionPanelContextMenu"
                       oncontextmenu="BrowserPageActions.onContextMenu(event);"
                       onclick="BrowserPageActions.bookmark.onUrlbarNodeClicked(event);">
                   <image id="star-button"
                          class="urlbar-icon"
                          role="button"
                          observes="bookmarkThisPageBroadcaster"/>
                   <hbox id="star-button-animatable-box">
                     <image id="star-button-animatable-image"
-                           role="button"
+                           role="presentation"
                            observes="bookmarkThisPageBroadcaster"/>
                   </hbox>
                 </hbox>
               </hbox>
             </textbox>
         </toolbaritem>
 
         <toolbarspring cui-areatype="toolbar" class="chromeclass-toolbar-additional"/>
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -6690,16 +6690,17 @@
 
       <method name="_animateTabMove">
         <parameter name="event"/>
         <body><![CDATA[
           let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
 
           if (this.getAttribute("movingtab") != "true") {
             this.setAttribute("movingtab", "true");
+            this.parentNode.setAttribute("movingtab", "true");
             this.selectedItem = draggedTab;
           }
 
           if (!("animLastScreenX" in draggedTab._dragData))
             draggedTab._dragData.animLastScreenX = draggedTab._dragData.screenX;
 
           let screenX = event.screenX;
           if (screenX == draggedTab._dragData.animLastScreenX)
@@ -6793,16 +6794,17 @@
         <body><![CDATA[
           if (this.getAttribute("movingtab") != "true")
             return;
 
           for (let tab of this.tabbrowser.visibleTabs)
             tab.style.transform = "";
 
           this.removeAttribute("movingtab");
+          this.parentNode.removeAttribute("movingtab");
 
           this._handleTabSelect();
         ]]></body>
       </method>
 
       <method name="handleEvent">
         <parameter name="aEvent"/>
         <body><![CDATA[
--- a/browser/components/customizableui/test/browser_876926_customize_mode_wrapping.js
+++ b/browser/components/customizableui/test/browser_876926_customize_mode_wrapping.js
@@ -10,36 +10,53 @@ const kPanel = CustomizableUI.AREA_FIXED
 const kToolbar = CustomizableUI.AREA_NAVBAR;
 const kVisiblePalette = "customization-palette";
 const kPlaceholderClass = "panel-customization-placeholder";
 
 function checkWrapper(id) {
   is(document.querySelectorAll("#wrapper-" + id).length, 1, "There should be exactly 1 wrapper for " + id + " in the customizing window.");
 }
 
+async function ensureVisibleIfInPalette(node) {
+    if (node.parentNode.parentNode == gNavToolbox.palette) {
+      node.scrollIntoView();
+      window.QueryInterface(Ci.nsIInterfaceRequestor);
+      let dwu = window.getInterface(Ci.nsIDOMWindowUtils);
+      await BrowserTestUtils.waitForCondition(() => {
+        let nodeBounds = dwu.getBoundsWithoutFlushing(node);
+        let paletteBounds = dwu.getBoundsWithoutFlushing(gNavToolbox.palette);
+        return nodeBounds.top >= paletteBounds.top && nodeBounds.bottom <= paletteBounds.bottom;
+      });
+    }
+}
+
 var move = {
-  "drag": function(id, target) {
+  "drag": async function(id, target) {
     let targetNode = document.getElementById(target);
     if (targetNode.customizationTarget) {
       targetNode = targetNode.customizationTarget;
     }
-    simulateItemDrag(document.getElementById(id), targetNode);
+    let nodeToMove = document.getElementById(id);
+    await ensureVisibleIfInPalette(nodeToMove);
+    simulateItemDrag(nodeToMove, targetNode);
   },
-  "dragToItem": function(id, target) {
+  "dragToItem": async function(id, target) {
     let targetNode = document.getElementById(target);
     if (targetNode.customizationTarget) {
       targetNode = targetNode.customizationTarget;
     }
     let items = targetNode.querySelectorAll("toolbarpaletteitem:not(." + kPlaceholderClass + ")");
     if (target == kPanel) {
       targetNode = items[items.length - 1];
     } else {
       targetNode = items[0];
     }
-    simulateItemDrag(document.getElementById(id), targetNode);
+    let nodeToMove = document.getElementById(id);
+    await ensureVisibleIfInPalette(nodeToMove);
+    simulateItemDrag(nodeToMove, targetNode);
   },
   "API": function(id, target) {
     if (target == kVisiblePalette) {
       return CustomizableUI.removeWidgetFromArea(id);
     }
     return CustomizableUI.addWidgetToArea(id, target, null);
   }
 };
@@ -85,33 +102,33 @@ function isLastVisibleInToolbar(containe
 function isFirst(containerId, defaultPlacements, id) {
   assertAreaPlacements(containerId, [id].concat(defaultPlacements));
   is(document.getElementById(containerId).customizationTarget.firstChild.firstChild.id, id,
      "Widget " + id + " should be in " + containerId + " in customizing window.");
   is(otherWin.document.getElementById(containerId).customizationTarget.firstChild.id, id,
      "Widget " + id + " should be in " + containerId + " in other window.");
 }
 
-function checkToolbar(id, method) {
+async function checkToolbar(id, method) {
   // Place at start of the toolbar:
   let toolbarPlacements = getAreaWidgetIds(kToolbar);
-  move[method](id, kToolbar);
+  await move[method](id, kToolbar);
   if (method == "dragToItem") {
     isFirst(kToolbar, toolbarPlacements, id);
   } else if (method == "drag") {
     isLastVisibleInToolbar(kToolbar, toolbarPlacements, id);
   } else {
     isLast(kToolbar, toolbarPlacements, id);
   }
   checkWrapper(id);
 }
 
-function checkPanel(id, method) {
+async function checkPanel(id, method) {
   let panelPlacements = getAreaWidgetIds(kPanel);
-  move[method](id, kPanel);
+  await move[method](id, kPanel);
   let children = document.getElementById(kPanel).querySelectorAll("toolbarpaletteitem:not(." + kPlaceholderClass + ")");
   let otherChildren = otherWin.document.getElementById(kPanel).children;
   let newPlacements = panelPlacements.concat([id]);
   // Relative position of the new item from the end:
   let position = -1;
   // For the drag to item case, we drag to the last item, making the dragged item the
   // penultimate item. We can't well use the first item because the panel has complicated
   // rules about rearranging wide items (which, by default, the first two items are).
@@ -123,19 +140,19 @@ function checkPanel(id, method) {
   assertAreaPlacements(kPanel, newPlacements);
   is(children[children.length + position].firstChild.id, id,
      "Widget " + id + " should be in " + kPanel + " in customizing window.");
   is(otherChildren[otherChildren.length + position].id, id,
      "Widget " + id + " should be in " + kPanel + " in other window.");
   checkWrapper(id);
 }
 
-function checkPalette(id, method) {
+async function checkPalette(id, method) {
   // Move back to palette:
-  move[method](id, kVisiblePalette);
+  await move[method](id, kVisiblePalette);
   ok(CustomizableUI.inDefaultState, "Should end in default state");
   let visibleChildren = gCustomizeMode.visiblePalette.children;
   let expectedChild = method == "dragToItem" ? visibleChildren[0] : visibleChildren[visibleChildren.length - 1];
   is(expectedChild.firstChild.id, id, "Widget " + id + " was moved using " + method + " and should now be wrapped in palette in customizing window.");
   if (id == kXULWidgetId) {
     ok(otherWin.gNavToolbox.palette.querySelector("#" + id), "Widget " + id + " should be in invisible palette in other window.");
   }
   checkWrapper(id);
@@ -165,29 +182,29 @@ add_task(async function MoveWidgetsInTwo
   // Create the XUL button to use in the test in both windows.
   createXULButtonForWindow(window);
   createXULButtonForWindow(otherWin);
   ok(CustomizableUI.inDefaultState, "Should start in default state");
 
   for (let widgetId of [kXULWidgetId, kAPIWidgetId]) {
     for (let method of ["API", "drag", "dragToItem"]) {
       info("Moving widget " + widgetId + " using " + method);
-      checkToolbar(widgetId, method);
+      await checkToolbar(widgetId, method);
       // We add an item to the panel because otherwise we can't test dragging
       // to items that are already there. We remove it because
       // 'checkPalette' checks that we leave the browser in the default state.
       CustomizableUI.addWidgetToArea("cui-mode-wrapping-some-panel-item", CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
-      checkPanel(widgetId, method);
+      await checkPanel(widgetId, method);
       CustomizableUI.removeWidgetFromArea("cui-mode-wrapping-some-panel-item");
-      checkPalette(widgetId, method);
+      await checkPalette(widgetId, method);
       CustomizableUI.addWidgetToArea("cui-mode-wrapping-some-panel-item", CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
-      checkPanel(widgetId, method);
-      checkToolbar(widgetId, method);
+      await checkPanel(widgetId, method);
+      await checkToolbar(widgetId, method);
       CustomizableUI.removeWidgetFromArea("cui-mode-wrapping-some-panel-item");
-      checkPalette(widgetId, method);
+      await checkPalette(widgetId, method);
     }
   }
   await promiseWindowClosed(otherWin);
   otherWin = null;
   await endCustomizing();
   removeXULButtonForWindow(window);
 });
 
--- a/browser/components/extensions/test/browser/browser_ext_chrome_settings_overrides_home.js
+++ b/browser/components/extensions/test/browser/browser_ext_chrome_settings_overrides_home.js
@@ -1,29 +1,36 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 
 "use strict";
 
-XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
-                                  "resource://gre/modules/Preferences.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+                                  "resource://gre/modules/Services.jsm");
 
 // Named this way so they correspond to the extensions
 const HOME_URI_2 = "http://example.com/";
 const HOME_URI_3 = "http://example.org/";
 const HOME_URI_4 = "http://example.net/";
 
 const CONTROLLABLE = "controllable_by_this_extension";
 const CONTROLLED_BY_THIS = "controlled_by_this_extension";
 const CONTROLLED_BY_OTHER = "controlled_by_other_extensions";
 
+const HOMEPAGE_URL_PREF = "browser.startup.homepage";
+
+const getHomePageURL = () => {
+  return Services.prefs.getComplexValue(
+    HOMEPAGE_URL_PREF, Ci.nsIPrefLocalizedString).data;
+};
+
 add_task(async function test_multiple_extensions_overriding_home_page() {
-  let defaultHomePage = Preferences.get("browser.startup.homepage");
+  let defaultHomePage = getHomePageURL();
 
   function background() {
     browser.test.onMessage.addListener(async msg => {
       switch (msg) {
         case "checkHomepage":
           let homepage = await browser.browserSettings.homepageOverride.get({});
           browser.test.sendMessage("homepage", homepage);
           break;
@@ -68,28 +75,28 @@ add_task(async function test_multiple_ex
     is(homepage.value, expectedValue,
        `homepageOverride setting returns the expected value: ${expectedValue}.`);
     is(homepage.levelOfControl, expectedLevelOfControl,
        `homepageOverride setting returns the expected levelOfControl: ${expectedLevelOfControl}.`);
   }
 
   await ext1.startup();
 
-  is(Preferences.get("browser.startup.homepage"), defaultHomePage,
+  is(getHomePageURL(), defaultHomePage,
      "Home url should be the default");
-  await checkHomepageOverride(ext1, null, CONTROLLABLE);
+  await checkHomepageOverride(ext1, getHomePageURL(), CONTROLLABLE);
 
   // Because we are expecting the pref to change when we start or unload, we
   // need to wait on a pref change.  This is because the pref management is
   // async and can happen after the startup/unload is finished.
-  let prefPromise = promisePrefChangeObserved("browser.startup.homepage");
+  let prefPromise = promisePrefChangeObserved(HOMEPAGE_URL_PREF);
   await ext2.startup();
   await prefPromise;
 
-  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_2),
+  ok(getHomePageURL().endsWith(HOME_URI_2),
      "Home url should be overridden by the second extension.");
 
   await checkHomepageOverride(ext1, HOME_URI_2, CONTROLLED_BY_OTHER);
 
   // Verify that calling set and clear do nothing.
   ext2.sendMessage("trySet");
   await ext2.awaitMessage("homepageSet");
   await checkHomepageOverride(ext1, HOME_URI_2, CONTROLLED_BY_OTHER);
@@ -98,107 +105,107 @@ add_task(async function test_multiple_ex
   await ext2.awaitMessage("homepageCleared");
   await checkHomepageOverride(ext1, HOME_URI_2, CONTROLLED_BY_OTHER);
 
   // Because we are unloading an earlier extension, browser.startup.homepage won't change
   await ext1.unload();
 
   await checkHomepageOverride(ext2, HOME_URI_2, CONTROLLED_BY_THIS);
 
-  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_2),
+  ok(getHomePageURL().endsWith(HOME_URI_2),
      "Home url should be overridden by the second extension.");
 
-  prefPromise = promisePrefChangeObserved("browser.startup.homepage");
+  prefPromise = promisePrefChangeObserved(HOMEPAGE_URL_PREF);
   await ext3.startup();
   await prefPromise;
 
-  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_3),
+  ok(getHomePageURL().endsWith(HOME_URI_3),
      "Home url should be overridden by the third extension.");
 
   await checkHomepageOverride(ext3, HOME_URI_3, CONTROLLED_BY_THIS);
 
   // Because we are unloading an earlier extension, browser.startup.homepage won't change
   await ext2.unload();
 
-  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_3),
+  ok(getHomePageURL().endsWith(HOME_URI_3),
      "Home url should be overridden by the third extension.");
 
   await checkHomepageOverride(ext3, HOME_URI_3, CONTROLLED_BY_THIS);
 
-  prefPromise = promisePrefChangeObserved("browser.startup.homepage");
+  prefPromise = promisePrefChangeObserved(HOMEPAGE_URL_PREF);
   await ext4.startup();
   await prefPromise;
 
-  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_4),
+  ok(getHomePageURL().endsWith(HOME_URI_4),
      "Home url should be overridden by the third extension.");
 
   await checkHomepageOverride(ext3, HOME_URI_4, CONTROLLED_BY_OTHER);
 
-  prefPromise = promisePrefChangeObserved("browser.startup.homepage");
+  prefPromise = promisePrefChangeObserved(HOMEPAGE_URL_PREF);
   await ext4.unload();
   await prefPromise;
 
-  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_3),
+  ok(getHomePageURL().endsWith(HOME_URI_3),
      "Home url should be overridden by the third extension.");
 
   await checkHomepageOverride(ext3, HOME_URI_3, CONTROLLED_BY_THIS);
 
-  prefPromise = promisePrefChangeObserved("browser.startup.homepage");
+  prefPromise = promisePrefChangeObserved(HOMEPAGE_URL_PREF);
   await ext3.unload();
   await prefPromise;
 
-  is(Preferences.get("browser.startup.homepage"), defaultHomePage,
+  is(getHomePageURL(), defaultHomePage,
      "Home url should be reset to default");
 
   await ext5.startup();
-  await checkHomepageOverride(ext5, null, CONTROLLABLE);
+  await checkHomepageOverride(ext5, defaultHomePage, CONTROLLABLE);
   await ext5.unload();
 });
 
 const HOME_URI_1 = "http://example.com/";
 const USER_URI = "http://example.edu/";
 
 add_task(async function test_extension_setting_home_page_back() {
-  let defaultHomePage = Preferences.get("browser.startup.homepage");
+  let defaultHomePage = getHomePageURL();
 
-  Preferences.set("browser.startup.homepage", USER_URI);
+  Services.prefs.setStringPref(HOMEPAGE_URL_PREF, USER_URI);
 
-  is(Preferences.get("browser.startup.homepage"), USER_URI,
+  is(getHomePageURL(), USER_URI,
      "Home url should be the user set value");
 
   let ext1 = ExtensionTestUtils.loadExtension({
     manifest: {"chrome_settings_overrides": {homepage: HOME_URI_1}},
     useAddonManager: "temporary",
   });
 
   // Because we are expecting the pref to change when we start or unload, we
   // need to wait on a pref change.  This is because the pref management is
   // async and can happen after the startup/unload is finished.
-  let prefPromise = promisePrefChangeObserved("browser.startup.homepage");
+  let prefPromise = promisePrefChangeObserved(HOMEPAGE_URL_PREF);
   await ext1.startup();
   await prefPromise;
 
-  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_1),
+  ok(getHomePageURL().endsWith(HOME_URI_1),
      "Home url should be overridden by the second extension.");
 
-  prefPromise = promisePrefChangeObserved("browser.startup.homepage");
+  prefPromise = promisePrefChangeObserved(HOMEPAGE_URL_PREF);
   await ext1.unload();
   await prefPromise;
 
-  is(Preferences.get("browser.startup.homepage"), USER_URI,
+  is(getHomePageURL(), USER_URI,
      "Home url should be the user set value");
 
-  Preferences.reset("browser.startup.homepage");
+  Services.prefs.clearUserPref(HOMEPAGE_URL_PREF);
 
-  is(Preferences.get("browser.startup.homepage"), defaultHomePage,
+  is(getHomePageURL(), defaultHomePage,
      "Home url should be the default");
 });
 
 add_task(async function test_disable() {
-  let defaultHomePage = Preferences.get("browser.startup.homepage");
+  let defaultHomePage = getHomePageURL();
 
   const ID = "id@tests.mozilla.org";
 
   let ext1 = ExtensionTestUtils.loadExtension({
     manifest: {
       applications: {
         gecko: {
           id: ID,
@@ -206,56 +213,56 @@ add_task(async function test_disable() {
       },
       "chrome_settings_overrides": {
         homepage: HOME_URI_1,
       },
     },
     useAddonManager: "temporary",
   });
 
-  let prefPromise = promisePrefChangeObserved("browser.startup.homepage");
+  let prefPromise = promisePrefChangeObserved(HOMEPAGE_URL_PREF);
   await ext1.startup();
   await prefPromise;
 
-  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_1),
+  ok(getHomePageURL().endsWith(HOME_URI_1),
      "Home url should be overridden by the extension.");
 
   let addon = await AddonManager.getAddonByID(ID);
   is(addon.id, ID);
 
-  prefPromise = promisePrefChangeObserved("browser.startup.homepage");
+  prefPromise = promisePrefChangeObserved(HOMEPAGE_URL_PREF);
   addon.userDisabled = true;
   await prefPromise;
 
-  is(Preferences.get("browser.startup.homepage"), defaultHomePage,
+  is(getHomePageURL(), defaultHomePage,
      "Home url should be the default");
 
-  prefPromise = promisePrefChangeObserved("browser.startup.homepage");
+  prefPromise = promisePrefChangeObserved(HOMEPAGE_URL_PREF);
   addon.userDisabled = false;
   await prefPromise;
 
-  ok(Preferences.get("browser.startup.homepage").endsWith(HOME_URI_1),
+  ok(getHomePageURL().endsWith(HOME_URI_1),
      "Home url should be overridden by the extension.");
 
-  prefPromise = promisePrefChangeObserved("browser.startup.homepage");
+  prefPromise = promisePrefChangeObserved(HOMEPAGE_URL_PREF);
   await ext1.unload();
   await prefPromise;
 
-  is(Preferences.get("browser.startup.homepage"), defaultHomePage,
+  is(getHomePageURL(), defaultHomePage,
      "Home url should be the default");
 });
 
 add_task(async function test_local() {
   let ext1 = ExtensionTestUtils.loadExtension({
     manifest: {"chrome_settings_overrides": {"homepage": "home.html"}},
     useAddonManager: "temporary",
   });
 
-  let prefPromise = promisePrefChangeObserved("browser.startup.homepage");
+  let prefPromise = promisePrefChangeObserved(HOMEPAGE_URL_PREF);
   await ext1.startup();
   await prefPromise;
 
-  let homepage = Preferences.get("browser.startup.homepage");
+  let homepage = getHomePageURL();
   ok((homepage.startsWith("moz-extension") && homepage.endsWith("home.html")),
      "Home url should be relative to extension.");
 
   await ext1.unload();
 });
--- a/browser/components/extensions/test/xpcshell/test_ext_url_overrides_newtab.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_url_overrides_newtab.js
@@ -31,16 +31,18 @@ AddonTestUtils.overrideCertDB();
 createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
 
 function awaitEvent(eventName) {
   return new Promise(resolve => {
     Management.once(eventName, (e, ...args) => resolve(...args));
   });
 }
 
+const DEFAULT_NEW_TAB_URL = aboutNewTabService.newTabURL;
+
 add_task(async function test_multiple_extensions_overriding_newtab_page() {
   const NEWTAB_URI_2 = "webext-newtab-1.html";
   const NEWTAB_URI_3 = "webext-newtab-2.html";
   const EXT_2_ID = "ext2@tests.mozilla.org";
   const EXT_3_ID = "ext3@tests.mozilla.org";
 
   const CONTROLLABLE = "controllable_by_this_extension";
   const CONTROLLED_BY_THIS = "controlled_by_this_extension";
@@ -64,23 +66,18 @@ add_task(async function test_multiple_ex
       }
     });
   }
 
   async function checkNewTabPageOverride(ext, expectedValue, expectedLevelOfControl) {
     ext.sendMessage("checkNewTabPage");
     let newTabPage = await ext.awaitMessage("newTabPage");
 
-    if (expectedValue) {
-      ok(newTabPage.value.endsWith(expectedValue),
-         `newTabPageOverride setting returns the expected value ending with: ${expectedValue}.`);
-    } else {
-      equal(newTabPage.value, expectedValue,
-            `newTabPageOverride setting returns the expected value: ${expectedValue}.`);
-    }
+    ok(newTabPage.value.endsWith(expectedValue),
+       `newTabPageOverride setting returns the expected value ending with: ${expectedValue}.`);
     equal(newTabPage.levelOfControl, expectedLevelOfControl,
           `newTabPageOverride setting returns the expected levelOfControl: ${expectedLevelOfControl}.`);
   }
 
   let extObj = {
     manifest: {
       "chrome_url_overrides": {},
       permissions: ["browserSettings"],
@@ -94,106 +91,106 @@ add_task(async function test_multiple_ex
   extObj.manifest.chrome_url_overrides = {newtab: NEWTAB_URI_2};
   extObj.manifest.applications = {gecko: {id: EXT_2_ID}};
   let ext2 = ExtensionTestUtils.loadExtension(extObj);
 
   extObj.manifest.chrome_url_overrides = {newtab: NEWTAB_URI_3};
   extObj.manifest.applications.gecko.id =  EXT_3_ID;
   let ext3 = ExtensionTestUtils.loadExtension(extObj);
 
-  equal(aboutNewTabService.newTabURL, "about:newtab",
-     "Default newtab url is about:newtab");
+  equal(aboutNewTabService.newTabURL, DEFAULT_NEW_TAB_URL,
+     "newTabURL is set to the default.");
 
   await promiseStartupManager();
 
   await ext1.startup();
-  equal(aboutNewTabService.newTabURL, "about:newtab",
-       "Default newtab url is still about:newtab");
+  equal(aboutNewTabService.newTabURL, DEFAULT_NEW_TAB_URL,
+       "newTabURL is still set to the default.");
 
-  await checkNewTabPageOverride(ext1, null, CONTROLLABLE);
+  await checkNewTabPageOverride(ext1, aboutNewTabService.newTabURL, CONTROLLABLE);
 
   await ext2.startup();
   ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_2),
-     "Newtab url is overriden by the second extension.");
+     "newTabURL is overriden by the second extension.");
   await checkNewTabPageOverride(ext1, NEWTAB_URI_2, CONTROLLED_BY_OTHER);
 
   // Verify that calling set and clear do nothing.
   ext2.sendMessage("trySet");
   await ext2.awaitMessage("newTabPageSet");
   await checkNewTabPageOverride(ext1, NEWTAB_URI_2, CONTROLLED_BY_OTHER);
 
   ext2.sendMessage("tryClear");
   await ext2.awaitMessage("newTabPageCleared");
   await checkNewTabPageOverride(ext1, NEWTAB_URI_2, CONTROLLED_BY_OTHER);
 
   // Disable the second extension.
   let addon = await AddonManager.getAddonByID(EXT_2_ID);
   let disabledPromise = awaitEvent("shutdown");
   addon.userDisabled = true;
   await disabledPromise;
-  equal(aboutNewTabService.newTabURL, "about:newtab",
-        "Newtab url is about:newtab after second extension is disabled.");
-  await checkNewTabPageOverride(ext1, null, CONTROLLABLE);
+  equal(aboutNewTabService.newTabURL, DEFAULT_NEW_TAB_URL,
+        "newTabURL url is reset to the default after second extension is disabled.");
+  await checkNewTabPageOverride(ext1, aboutNewTabService.newTabURL, CONTROLLABLE);
 
   // Re-enable the second extension.
   let enabledPromise = awaitEvent("ready");
   addon.userDisabled = false;
   await enabledPromise;
   ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_2),
-     "Newtab url is overriden by the second extension.");
+     "newTabURL is overriden by the second extension.");
   await checkNewTabPageOverride(ext2, NEWTAB_URI_2, CONTROLLED_BY_THIS);
 
   await ext1.unload();
   ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_2),
-     "Newtab url is still overriden by the second extension.");
+     "newTabURL is still overriden by the second extension.");
   await checkNewTabPageOverride(ext2, NEWTAB_URI_2, CONTROLLED_BY_THIS);
 
   await ext3.startup();
   ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_3),
-   "Newtab url is overriden by the third extension.");
+   "newTabURL is overriden by the third extension.");
   await checkNewTabPageOverride(ext2, NEWTAB_URI_3, CONTROLLED_BY_OTHER);
 
   // Disable the second extension.
   disabledPromise = awaitEvent("shutdown");
   addon.userDisabled = true;
   await disabledPromise;
   ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_3),
-   "Newtab url is still overriden by the third extension.");
+   "newTabURL is still overriden by the third extension.");
   await checkNewTabPageOverride(ext3, NEWTAB_URI_3, CONTROLLED_BY_THIS);
 
   // Re-enable the second extension.
   enabledPromise = awaitEvent("ready");
   addon.userDisabled = false;
   await enabledPromise;
   ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_3),
-   "Newtab url is still overriden by the third extension.");
+   "newTabURL is still overriden by the third extension.");
   await checkNewTabPageOverride(ext3, NEWTAB_URI_3, CONTROLLED_BY_THIS);
 
   await ext3.unload();
   ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_2),
-     "Newtab url reverts to being overriden by the second extension.");
+     "newTabURL reverts to being overriden by the second extension.");
   await checkNewTabPageOverride(ext2, NEWTAB_URI_2, CONTROLLED_BY_THIS);
 
   await ext2.unload();
-  equal(aboutNewTabService.newTabURL, "about:newtab",
-     "Newtab url is reset to about:newtab");
+  equal(aboutNewTabService.newTabURL, DEFAULT_NEW_TAB_URL,
+     "newTabURL url is reset to the default.");
 
   await promiseShutdownManager();
 });
 
 // Tests that we handle the upgrade/downgrade process correctly
 // when an extension is installed temporarily on top of a permanently
 // installed one.
 add_task(async function test_temporary_installation() {
   const ID = "newtab@tests.mozilla.org";
   const PAGE1 = "page1.html";
   const PAGE2 = "page2.html";
 
-  equal(aboutNewTabService.newTabURL, "about:newtab",
-        "Default newtab url is about:newtab");
+  equal(aboutNewTabService.newTabURL, DEFAULT_NEW_TAB_URL,
+        "newTabURL is set to the default.");
 
   await promiseStartupManager();
 
   let permanent = ExtensionTestUtils.loadExtension({
     manifest: {
       applications: {
         gecko: {id: ID},
       },
@@ -201,38 +198,38 @@ add_task(async function test_temporary_i
         newtab: PAGE1,
       },
     },
     useAddonManager: "permanent",
   });
 
   await permanent.startup();
   ok(aboutNewTabService.newTabURL.endsWith(PAGE1),
-     "newtab url is overridden by permanent extension");
+     "newTabURL is overridden by permanent extension.");
 
   let temporary = ExtensionTestUtils.loadExtension({
     manifest: {
       applications: {
         gecko: {id: ID},
       },
       chrome_url_overrides: {
         newtab: PAGE2,
       },
     },
     useAddonManager: "temporary",
   });
 
   await temporary.startup();
   ok(aboutNewTabService.newTabURL.endsWith(PAGE2),
-     "newtab url is overridden by temporary extension");
+     "newTabURL is overridden by temporary extension.");
 
   await promiseRestartManager();
   await permanent.awaitStartup();
 
   ok(aboutNewTabService.newTabURL.endsWith(PAGE1),
-     "newtab url is back to the value set by permanent extension");
+     "newTabURL is back to the value set by permanent extension.");
 
   await permanent.unload();
 
-  equal(aboutNewTabService.newTabURL, "about:newtab",
-        "newtab url is back to default about:newtab");
+  equal(aboutNewTabService.newTabURL, DEFAULT_NEW_TAB_URL,
+        "newTabURL is set back to the default.");
   await promiseShutdownManager();
 });
--- a/browser/components/places/PlacesUIUtils.jsm
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -18,16 +18,18 @@ Cu.import("resource://gre/modules/Places
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                   "resource://gre/modules/PluralForm.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
                                   "resource:///modules/RecentWindow.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
+                                  "resource://gre/modules/PromiseUtils.jsm");
 
 // PlacesUtils exposes multiple symbols, so we can't use defineLazyModuleGetter.
 Cu.import("resource://gre/modules/PlacesUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesTransactions",
                                   "resource://gre/modules/PlacesTransactions.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Weave",
@@ -630,31 +632,55 @@ this.PlacesUIUtils = {
    *        Describes the item to be edited/added in the dialog.
    *        See documentation at the top of bookmarkProperties.js
    * @param aWindow
    *        Owner window for the new dialog.
    *
    * @see documentation at the top of bookmarkProperties.js
    * @return true if any transaction has been performed, false otherwise.
    */
-  showBookmarkDialog:
-  function PUIU_showBookmarkDialog(aInfo, aParentWindow) {
+  showBookmarkDialog(aInfo, aParentWindow) {
     // Preserve size attributes differently based on the fact the dialog has
     // a folder picker or not, since it needs more horizontal space than the
     // other controls.
     let hasFolderPicker = !("hiddenRows" in aInfo) ||
                           !aInfo.hiddenRows.includes("folderPicker");
     // Use a different chrome url to persist different sizes.
     let dialogURL = hasFolderPicker ?
                     "chrome://browser/content/places/bookmarkProperties2.xul" :
                     "chrome://browser/content/places/bookmarkProperties.xul";
 
     let features = "centerscreen,chrome,modal,resizable=yes";
+
+    let topUndoEntry;
+    let batchBlockingDeferred;
+
+    if (this.useAsyncTransactions) {
+      // Set the transaction manager into batching mode.
+      topUndoEntry = PlacesTransactions.topUndoEntry;
+      batchBlockingDeferred = PromiseUtils.defer();
+      PlacesTransactions.batch(async () => {
+        await batchBlockingDeferred.promise;
+      });
+    }
+
     aParentWindow.openDialog(dialogURL, "", features, aInfo);
-    return ("performed" in aInfo && aInfo.performed);
+
+    let performed = ("performed" in aInfo && aInfo.performed);
+
+    if (this.useAsyncTransactions) {
+      batchBlockingDeferred.resolve();
+
+      if (!performed &&
+          topUndoEntry != PlacesTransactions.topUndoEntry) {
+        PlacesTransactions.undo().catch(Components.utils.reportError);
+      }
+    }
+
+    return performed;
   },
 
   _getTopBrowserWin: function PUIU__getTopBrowserWin() {
     return RecentWindow.getMostRecentBrowserWindow();
   },
 
   /**
    * set and fetch a favicon. Can only be used from the parent process.
--- a/browser/components/places/content/bookmarkProperties.js
+++ b/browser/components/places/content/bookmarkProperties.js
@@ -57,18 +57,16 @@
  * been performed by the dialog.
  */
 
 /* import-globals-from editBookmarkOverlay.js */
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
-                                  "resource://gre/modules/PromiseUtils.jsm");
 
 const BOOKMARK_ITEM = 0;
 const BOOKMARK_FOLDER = 1;
 const LIVEMARK_CONTAINER = 2;
 
 const ACTION_EDIT = 0;
 const ACTION_ADD = 1;
 
@@ -376,42 +374,30 @@ var BookmarkPropertiesPanel = {
 
   // Hack for implementing batched-Undo around the editBookmarkOverlay
   // instant-apply code. For all the details see the comment above beginBatch
   // in browser-places.js
   _batchBlockingDeferred: null,
   _beginBatch() {
     if (this._batching)
       return;
-    if (PlacesUIUtils.useAsyncTransactions) {
-      this._topUndoEntry = PlacesTransactions.topUndoEntry;
-      this._batchBlockingDeferred = PromiseUtils.defer();
-      PlacesTransactions.batch(async () => {
-        await this._batchBlockingDeferred.promise;
-      });
-    } else {
+    if (!PlacesUIUtils.useAsyncTransactions) {
       PlacesUtils.transactionManager.beginBatch(null);
     }
     this._batching = true;
   },
 
   _endBatch() {
     if (!this._batching)
-      return false;
+      return;
 
-    if (PlacesUIUtils.useAsyncTransactions) {
-      this._batchBlockingDeferred.resolve();
-      this._batchBlockingDeferred = null;
-    } else {
+    if (!PlacesUIUtils.useAsyncTransactions) {
       PlacesUtils.transactionManager.endBatch(false);
     }
     this._batching = false;
-    let changed = this._topUndoEntry != PlacesTransactions.topUndoEntry;
-    delete this._topUndoEntry;
-    return changed;
   },
 
   // nsISupports
   QueryInterface: function BPP_QueryInterface(aIID) {
     if (aIID.equals(Ci.nsIDOMEventListener) ||
         aIID.equals(Ci.nsISupports))
       return this;
 
@@ -445,22 +431,18 @@ var BookmarkPropertiesPanel = {
     window.arguments[0].performed = true;
   },
 
   onDialogCancel() {
     // The order here is important! We have to uninit the panel first, otherwise
     // changes done as part of Undo may change the panel contents and by
     // that force it to commit more transactions.
     gEditItemOverlay.uninitPanel(true);
-    let changed = this._endBatch();
-    if (PlacesUIUtils.useAsyncTransactions) {
-      if (changed) {
-        PlacesTransactions.undo().catch(Components.utils.reportError);
-      }
-    } else {
+    this._endBatch();
+    if (!PlacesUIUtils.useAsyncTransactions) {
       PlacesUtils.transactionManager.undoTransaction();
     }
     window.arguments[0].performed = false;
   },
 
   /**
    * This method checks to see if the input fields are in a valid state.
    *
--- a/browser/components/places/content/browserPlacesViews.js
+++ b/browser/components/places/content/browserPlacesViews.js
@@ -112,22 +112,24 @@ PlacesViewBase.prototype = {
     return val;
   },
 
   /**
    * Gets the DOM node used for the given places node.
    *
    * @param aPlacesNode
    *        a places result node.
+   * @param aAllowMissing
+   *        whether the node may be missing
    * @throws if there is no DOM node set for aPlacesNode.
    */
   _getDOMNodeForPlacesNode:
-  function PVB__getDOMNodeForPlacesNode(aPlacesNode) {
+  function PVB__getDOMNodeForPlacesNode(aPlacesNode, aAllowMissing = false) {
     let node = this._domNodes.get(aPlacesNode, null);
-    if (!node) {
+    if (!node && !aAllowMissing) {
       throw new Error("No DOM node set for aPlacesNode.\nnode.type: " +
                       aPlacesNode.type + ". node.parent: " + aPlacesNode);
     }
     return node;
   },
 
   get controller() {
     return this._controller;
@@ -278,21 +280,22 @@ PlacesViewBase.prototype = {
       return;
     }
 
     this._cleanPopup(aPopup);
 
     let cc = resultNode.childCount;
     if (cc > 0) {
       this._setEmptyPopupStatus(aPopup, false);
-
+      let fragment = document.createDocumentFragment();
       for (let i = 0; i < cc; ++i) {
         let child = resultNode.getChild(i);
-        this._insertNewItemToPopup(child, aPopup, null);
+        this._insertNewItemToPopup(child, fragment);
       }
+      aPopup.insertBefore(fragment, aPopup._endMarker);
     } else {
       this._setEmptyPopupStatus(aPopup, true);
     }
     aPopup._built = true;
   },
 
   _removeChild: function PVB__removeChild(aChild) {
     // If document.popupNode pointed to this child, null it out,
@@ -399,26 +402,25 @@ PlacesViewBase.prototype = {
     element._placesNode = aPlacesNode;
     if (!this._domNodes.has(aPlacesNode))
       this._domNodes.set(aPlacesNode, element);
 
     return element;
   },
 
   _insertNewItemToPopup:
-  function PVB__insertNewItemToPopup(aNewChild, aPopup, aBefore) {
+  function PVB__insertNewItemToPopup(aNewChild, aInsertionNode, aBefore = null) {
     let element = this._createDOMNodeForPlacesNode(aNewChild);
-    let before = aBefore || aPopup._endMarker;
 
     if (element.localName == "menuitem" || element.localName == "menu") {
       if (typeof this.options.extraClasses.entry == "string")
         element.classList.add(this.options.extraClasses.entry);
     }
 
-    aPopup.insertBefore(element, before);
+    aInsertionNode.insertBefore(element, aBefore);
     return element;
   },
 
   _setLivemarkSiteURIMenuItem:
   function PVB__setLivemarkSiteURIMenuItem(aPopup) {
     let livemarkInfo = this.controller.getCachedLivemarkInfo(aPopup._placesNode);
     let siteUrl = livemarkInfo && livemarkInfo.siteURI ?
                   livemarkInfo.siteURI.spec : null;
@@ -630,17 +632,17 @@ PlacesViewBase.prototype = {
   function PVB_nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) {
     let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
     if (!parentElt._built)
       return;
 
     let index = Array.prototype.indexOf.call(parentElt.childNodes, parentElt._startMarker) +
                 aIndex + 1;
     this._insertNewItemToPopup(aPlacesNode, parentElt,
-                               parentElt.childNodes[index]);
+                               parentElt.childNodes[index] || parentElt._endMarker);
     this._setEmptyPopupStatus(parentElt, false);
   },
 
   nodeMoved:
   function PBV_nodeMoved(aPlacesNode,
                          aOldParentPlacesNode, aOldIndex,
                          aNewParentPlacesNode, aNewIndex) {
     // Note: the current implementation of moveItem does not actually
@@ -1031,44 +1033,62 @@ PlacesToolbar.prototype = {
     if (this._overFolder.elt)
       this._clearOverFolder();
 
     this._openedMenuButton = null;
     while (this._rootElt.hasChildNodes()) {
       this._rootElt.firstChild.remove();
     }
 
+    let fragment = document.createDocumentFragment();
     let cc = this._resultNode.childCount;
-    for (let i = 0; i < cc; ++i) {
-      this._insertNewItem(this._resultNode.getChild(i), null);
+    if (cc > 0) {
+      // There could be a lot of nodes, but we only want to build the ones that
+      // are likely to be shown, not all of them. Then we'll lazily create the
+      // missing nodes when needed.
+      // We don't want to cause reflows at every node insertion to calculate
+      // a precise size, thus we guess a size from the first node.
+      let button = this._insertNewItem(this._resultNode.getChild(0),
+                                       this._rootElt);
+      requestAnimationFrame(() => {
+        // May have been destroyed in the meanwhile.
+        if (!this._resultNode || !this._rootElt)
+          return;
+        // We assume a button with just the icon will be more or less a square,
+        // then compensate the measurement error by considering a larger screen
+        // width. Moreover the window could be bigger than the screen.
+        let size = button.clientHeight;
+        let limit = Math.min(cc, parseInt((window.screen.width * 1.5) / size));
+        for (let i = 1; i < limit; ++i) {
+          this._insertNewItem(this._resultNode.getChild(i), fragment);
+        }
+        this._rootElt.appendChild(fragment);
+      });
     }
 
     if (this._chevronPopup.hasAttribute("type")) {
       // Chevron has already been initialized, but since we are forcing
       // a rebuild of the toolbar, it has to be rebuilt.
       // Otherwise, it will be initialized when the toolbar overflows.
       this._chevronPopup.place = this.place;
     }
   },
 
   _insertNewItem:
-  function PT__insertNewItem(aChild, aBefore) {
+  function PT__insertNewItem(aChild, aInsertionNode, aBefore = null) {
     this._domNodes.delete(aChild);
 
     let type = aChild.type;
     let button;
     if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
       button = document.createElement("toolbarseparator");
     } else {
       button = document.createElement("toolbarbutton");
       button.className = "bookmark-item";
       button.setAttribute("label", aChild.title || "");
-      let icon = aChild.icon;
-      if (icon)
-        button.setAttribute("image", icon);
 
       if (PlacesUtils.containerTypes.includes(type)) {
         button.setAttribute("type", "menu");
         button.setAttribute("container", "true");
 
         if (PlacesUtils.nodeIsQuery(aChild)) {
           button.setAttribute("query", "true");
           if (PlacesUtils.nodeIsTagQuery(aChild))
@@ -1094,27 +1114,30 @@ PlacesToolbar.prototype = {
       }
     }
 
     button._placesNode = aChild;
     if (!this._domNodes.has(aChild))
       this._domNodes.set(aChild, button);
 
     if (aBefore)
-      this._rootElt.insertBefore(button, aBefore);
+      aInsertionNode.insertBefore(button, aBefore);
     else
-      this._rootElt.appendChild(button);
+      aInsertionNode.appendChild(button);
+    return button;
   },
 
   _updateChevronPopupNodesVisibility:
   function PT__updateChevronPopupNodesVisibility() {
-    for (let i = 0, node = this._chevronPopup._startMarker.nextSibling;
-         node != this._chevronPopup._endMarker;
-         i++, node = node.nextSibling) {
-      node.hidden = this._rootElt.childNodes[i].style.visibility != "hidden";
+    // Note the toolbar by default builds less nodes than the chevron popup.
+    for (let toolbarNode = this._rootElt.firstChild,
+         node = this._chevronPopup._startMarker.nextSibling;
+         toolbarNode && node;
+         toolbarNode = toolbarNode.nextSibling, node = node.nextSibling) {
+      node.hidden = toolbarNode.style.visibility != "hidden";
     }
   },
 
   _onChevronPopupShowing:
   function PT__onChevronPopupShowing(aEvent) {
     // Handle popupshowing only for the chevron popup, not for nested ones.
     if (aEvent.target != this._chevronPopup)
       return;
@@ -1129,17 +1152,19 @@ PlacesToolbar.prototype = {
     switch (aEvent.type) {
       case "unload":
         this.uninit();
         break;
       case "resize":
         // This handler updates nodes visibility in both the toolbar
         // and the chevron popup when a window resize does not change
         // the overflow status of the toolbar.
-        this.updateChevron();
+        if (aEvent.target == aEvent.currentTarget) {
+          this.updateChevron();
+        }
         break;
       case "overflow":
         if (!this._isOverflowStateEventRelevant(aEvent))
           return;
         this._onOverflow();
         break;
       case "underflow":
         if (!this._isOverflowStateEventRelevant(aEvent))
@@ -1225,83 +1250,149 @@ PlacesToolbar.prototype = {
       this._updateChevronTimer.cancel();
 
     this._updateChevronTimer = this._setTimer(100);
   },
 
   _updateChevronTimerCallback: function PT__updateChevronTimerCallback() {
     let scrollRect = this._rootElt.getBoundingClientRect();
     let childOverflowed = false;
-    for (let i = 0; i < this._rootElt.childNodes.length; i++) {
-      let child = this._rootElt.childNodes[i];
+    for (let child of this._rootElt.childNodes) {
       // Once a child overflows, all the next ones will.
       if (!childOverflowed) {
         let childRect = child.getBoundingClientRect();
         childOverflowed = this.isRTL ? (childRect.left < scrollRect.left)
                                      : (childRect.right > scrollRect.right);
-
       }
-      child.style.visibility = childOverflowed ? "hidden" : "visible";
+
+      if (childOverflowed) {
+        child.removeAttribute("image");
+        child.style.visibility = "hidden";
+      } else {
+        let icon = child._placesNode.icon;
+        if (icon)
+          child.setAttribute("image", icon);
+        child.style.visibility = "visible";
+      }
     }
 
     // We rebuild the chevron on popupShowing, so if it is open
     // we must update it.
     if (this._chevron.open)
       this._updateChevronPopupNodesVisibility();
+    let event = new CustomEvent("BookmarksToolbarVisibilityUpdated", {bubbles: true});
+    this._viewElt.dispatchEvent(event);
   },
 
   nodeInserted:
   function PT_nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) {
     let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
-    if (parentElt == this._rootElt) {
+    if (parentElt == this._rootElt) { // Node is on the toolbar.
       let children = this._rootElt.childNodes;
-      this._insertNewItem(aPlacesNode,
-        aIndex < children.length ? children[aIndex] : null);
-      this.updateChevron();
+      // Nothing to do if it's a never-visible node, but note it's possible
+      // we are appending.
+      if (aIndex > children.length)
+        return;
+
+      // Note that childCount is already accounting for the node being added,
+      // thus we must subtract one node from it.
+      if (this._resultNode.childCount - 1 > children.length) {
+        if (aIndex == children.length) {
+          // If we didn't build all the nodes and new node is being appended,
+          // we can skip it as well.
+          return;
+        }
+        // Keep the number of built nodes consistent.
+        this._rootElt.removeChild(this._rootElt.lastChild);
+      }
+
+      let button = this._insertNewItem(aPlacesNode, this._rootElt,
+                                       children[aIndex] || null);
+      let prevSiblingOverflowed = aIndex > 0 && aIndex <= children.length &&
+                                  children[aIndex - 1].style.visibility == "hidden";
+      if (prevSiblingOverflowed) {
+        button.style.visibility = "hidden";
+      } else {
+        let icon = aPlacesNode.icon;
+        if (icon)
+          button.setAttribute("image", icon);
+        this.updateChevron();
+      }
       return;
     }
 
     PlacesViewBase.prototype.nodeInserted.apply(this, arguments);
   },
 
   nodeRemoved:
   function PT_nodeRemoved(aParentPlacesNode, aPlacesNode, aIndex) {
     let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
-    let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+    if (parentElt == this._rootElt) { // Node is on the toolbar.
+      let elt = this._getDOMNodeForPlacesNode(aPlacesNode, true);
+      // Nothing to do if it's a never-visible node.
+      if (!elt)
+        return;
+
+      // Here we need the <menu>.
+      if (elt.localName == "menupopup")
+        elt = elt.parentNode;
 
-    // Here we need the <menu>.
-    if (elt.localName == "menupopup")
-      elt = elt.parentNode;
-
-    if (parentElt == this._rootElt) {
+      let overflowed = elt.style.visibility == "hidden";
       this._removeChild(elt);
-      this.updateChevron();
+      if (this._resultNode.childCount > this._rootElt.childNodes.length) {
+        // A new node should be built to keep a coherent number of children.
+        this._insertNewItem(this._resultNode.getChild(this._rootElt.childNodes.length),
+                            this._rootElt);
+      }
+      if (!overflowed)
+        this.updateChevron();
       return;
     }
 
     PlacesViewBase.prototype.nodeRemoved.apply(this, arguments);
   },
 
   nodeMoved:
   function PT_nodeMoved(aPlacesNode,
                         aOldParentPlacesNode, aOldIndex,
                         aNewParentPlacesNode, aNewIndex) {
     let parentElt = this._getDOMNodeForPlacesNode(aNewParentPlacesNode);
-    if (parentElt == this._rootElt) {
-      // Container is on the toolbar.
+    if (parentElt == this._rootElt) { // Node is on the toolbar.
+      // Do nothing if the node will never be visible.
+      let lastBuiltIndex = this._rootElt.childNodes.length - 1;
+      if (aOldIndex > lastBuiltIndex && aNewIndex > lastBuiltIndex + 1)
+        return;
 
-      // Move the element.
-      let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+      let elt = this._getDOMNodeForPlacesNode(aPlacesNode, true);
+      if (elt) {
+        // Here we need the <menu>.
+        if (elt.localName == "menupopup")
+          elt = elt.parentNode;
+        this._removeChild(elt);
+      }
 
-      // Here we need the <menu>.
-      if (elt.localName == "menupopup")
-        elt = elt.parentNode;
+      if (aNewIndex > lastBuiltIndex + 1) {
+        if (this._resultNode.childCount > this._rootElt.childNodes.length) {
+          // If the element was built and becomes non built, another node should
+          // be built to keep a coherent number of children.
+          this._insertNewItem(this._resultNode.getChild(this._rootElt.childNodes.length),
+                              this._rootElt);
+        }
+        return;
+      }
 
-      this._removeChild(elt);
-      this._rootElt.insertBefore(elt, this._rootElt.childNodes[aNewIndex]);
+      if (!elt) {
+        // The node has not been inserted yet, so we must create it.
+        elt = this._insertNewItem(aPlacesNode, this._rootElt, this._rootElt.childNodes[aNewIndex]);
+        let icon = aPlacesNode.icon;
+        if (icon)
+          elt.setAttribute("image", icon);
+      } else {
+        this._rootElt.insertBefore(elt, this._rootElt.childNodes[aNewIndex]);
+      }
 
       // The chevron view may get nodeMoved after the toolbar.  In such a case,
       // we should ensure (by manually swapping menuitems) that the actual nodes
       // are in the final position before updateChevron tries to updates their
       // visibility, or the chevron may go out of sync.
       // Luckily updateChevron runs on a timer, so, by the time it updates
       // nodes, the menu has already handled the notification.
 
@@ -1309,27 +1400,26 @@ PlacesToolbar.prototype = {
       return;
     }
 
     PlacesViewBase.prototype.nodeMoved.apply(this, arguments);
   },
 
   nodeAnnotationChanged:
   function PT_nodeAnnotationChanged(aPlacesNode, aAnno) {
-    let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
-    if (elt == this._rootElt)
+    let elt = this._getDOMNodeForPlacesNode(aPlacesNode, true);
+    // Nothing to do if it's a never-visible node.
+    if (!elt || elt == this._rootElt)
       return;
 
     // We're notified for the menupopup, not the containing toolbarbutton.
     if (elt.localName == "menupopup")
       elt = elt.parentNode;
 
-    if (elt.parentNode == this._rootElt) {
-      // Node is on the toolbar.
-
+    if (elt.parentNode == this._rootElt) { // Node is on the toolbar.
       // All livemarks have a feedURI, so use it as our indicator.
       if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
         elt.setAttribute("livemark", true);
 
         PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
           .then(aLivemark => {
             this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
             this.invalidateContainer(aPlacesNode);
@@ -1337,37 +1427,40 @@ PlacesToolbar.prototype = {
       }
     } else {
       // Node is in a submenu.
       PlacesViewBase.prototype.nodeAnnotationChanged.apply(this, arguments);
     }
   },
 
   nodeTitleChanged: function PT_nodeTitleChanged(aPlacesNode, aNewTitle) {
-    let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+    let elt = this._getDOMNodeForPlacesNode(aPlacesNode, true);
 
-    // There's no UI representation for the root node, thus there's
-    // nothing to be done when the title changes.
-    if (elt == this._rootElt)
+    // Nothing to do if it's a never-visible node.
+    if (!elt || elt == this._rootElt)
       return;
 
     PlacesViewBase.prototype.nodeTitleChanged.apply(this, arguments);
 
     // Here we need the <menu>.
     if (elt.localName == "menupopup")
       elt = elt.parentNode;
 
-    if (elt.parentNode == this._rootElt) {
-      // Node is on the toolbar
-      this.updateChevron();
+    if (elt.parentNode == this._rootElt) { // Node is on the toolbar.
+      if (elt.style.visibility != "hidden")
+        this.updateChevron();
     }
   },
 
   invalidateContainer: function PT_invalidateContainer(aPlacesNode) {
-    let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+    let elt = this._getDOMNodeForPlacesNode(aPlacesNode, true);
+    // Nothing to do if it's a never-visible node.
+    if (!elt)
+      return;
+
     if (elt == this._rootElt) {
       // Container is the toolbar itself.
       this._rebuild();
       return;
     }
 
     PlacesViewBase.prototype.invalidateContainer.apply(this, arguments);
   },
@@ -1509,17 +1602,21 @@ PlacesToolbar.prototype = {
     let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
     timer.initWithCallback(this, aTime, timer.TYPE_ONE_SHOT);
     return timer;
   },
 
   notify: function PT_notify(aTimer) {
     if (aTimer == this._updateChevronTimer) {
       this._updateChevronTimer = null;
-      this._updateChevronTimerCallback();
+      // TODO: This should use promiseLayoutFlushed("layout"), so that
+      // _updateChevronTimerCallback can use getBoundsWithoutFlush. But for
+      // yet unknown reasons doing that causes intermittent failures, apparently
+      // due the flush not happening in a meaningful time.
+      window.requestAnimationFrame(this._updateChevronTimerCallback.bind(this));
     } else if (aTimer == this._ibTimer) {
       // * Timer to turn off indicator bar.
       this._dropIndicator.collapsed = true;
       this._ibTimer = null;
     } else if (aTimer == this._overFolder.openTimer) {
       // * Timer to open a menubutton that's being dragged over.
       // Set the autoopen attribute on the folder's menupopup so that
       // the menu will automatically close when the mouse drags off of it.
@@ -1872,17 +1969,17 @@ PlacesPanelMenuView.prototype = {
     return PlacesViewBase.prototype.QueryInterface.apply(this, arguments);
   },
 
   uninit: function PAMV_uninit() {
     PlacesViewBase.prototype.uninit.apply(this, arguments);
   },
 
   _insertNewItem:
-  function PAMV__insertNewItem(aChild, aBefore) {
+  function PAMV__insertNewItem(aChild, aInsertionNode, aBefore = null) {
     this._domNodes.delete(aChild);
 
     let type = aChild.type;
     let button;
     if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
       button = document.createElement("toolbarseparator");
       button.setAttribute("class", "small-separator");
     } else {
@@ -1914,27 +2011,28 @@ PlacesPanelMenuView.prototype = {
                             PlacesUIUtils.guessUrlSchemeForUI(aChild.uri));
       }
     }
 
     button._placesNode = aChild;
     if (!this._domNodes.has(aChild))
       this._domNodes.set(aChild, button);
 
-    this._rootElt.insertBefore(button, aBefore);
+    aInsertionNode.insertBefore(button, aBefore);
+    return button;
   },
 
   nodeInserted:
   function PAMV_nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) {
     let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
     if (parentElt != this._rootElt)
       return;
 
     let children = this._rootElt.childNodes;
-    this._insertNewItem(aPlacesNode,
+    this._insertNewItem(aPlacesNode, this._rootElt,
       aIndex < children.length ? children[aIndex] : null);
   },
 
   nodeRemoved:
   function PAMV_nodeRemoved(aParentPlacesNode, aPlacesNode, aIndex) {
     let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
     if (parentElt != this._rootElt)
       return;
@@ -1993,19 +2091,21 @@ PlacesPanelMenuView.prototype = {
     if (elt != this._rootElt)
       return;
 
     // Container is the toolbar itself.
     while (this._rootElt.hasChildNodes()) {
       this._rootElt.firstChild.remove();
     }
 
+    let fragment = document.createDocumentFragment();
     for (let i = 0; i < this._resultNode.childCount; ++i) {
-      this._insertNewItem(this._resultNode.getChild(i), null);
+      this._insertNewItem(this._resultNode.getChild(i), fragment);
     }
+    this._rootElt.appendChild(fragment);
   }
 };
 
 this.PlacesPanelview = class extends PlacesViewBase {
   constructor(container, panelview, place, options = {}) {
     options.rootElt = container;
     options.viewElt = panelview;
     super(place, options);
--- a/browser/components/places/tests/browser/browser.ini
+++ b/browser/components/places/tests/browser/browser.ini
@@ -53,16 +53,17 @@ subsuite = clipboard
 [browser_library_views_liveupdate.js]
 [browser_markPageAsFollowedLink.js]
 [browser_paste_into_tags.js]
 subsuite = clipboard
 [browser_sidebarpanels_click.js]
 skip-if = true # temporarily disabled for breaking the treeview - bug 658744
 [browser_sort_in_library.js]
 [browser_toolbar_drop_text.js]
+[browser_toolbar_overflow.js]
 [browser_toolbarbutton_menu_context.js]
 [browser_views_iconsupdate.js]
 support-files =
   favicon-normal16.png
 [browser_views_liveupdate.js]
 [browser_bookmark_all_tabs.js]
 support-files =
   bookmark_dummy_1.html
--- a/browser/components/places/tests/browser/browser_bookmarkProperties_cancel.js
+++ b/browser/components/places/tests/browser/browser_bookmarkProperties_cancel.js
@@ -63,24 +63,24 @@ add_task(async function test_cancel_with
       },
       async function test(dialogWin) {
         let acceptButton = dialogWin.document.documentElement.getButton("accept");
         await BrowserTestUtils.waitForCondition(() => !acceptButton.disabled,
           "The accept button should be enabled");
       }
     );
 
-    Assert.ok(PlacesTransactions.undo.notCalled, "undo should not have been called");
-
     // Check the bookmark is still removed.
     Assert.ok(!(await PlacesUtils.bookmarks.fetch(bookmarks[0].guid)),
       "The originally removed bookmark should not exist.");
 
     Assert.ok(await PlacesUtils.bookmarks.fetch(bookmarks[1].guid),
       "The second bookmark should still exist");
+
+    Assert.ok(PlacesTransactions.undo.notCalled, "undo should not have been called");
   });
 });
 
 add_task(async function test_cancel_with_changes() {
   if (!PlacesUIUtils.useAsyncTransactions) {
     Assert.ok(true, "Skipping test as async transactions are turned off");
     return;
   }
@@ -107,12 +107,12 @@ add_task(async function test_cancel_with
         // The dialog is instant apply.
         await promiseTitleChangeNotification;
 
         // Ensure that the addition really is finished before we hit cancel.
         await PlacesTestUtils.promiseAsyncUpdates();
       }
     );
 
-    Assert.ok(PlacesTransactions.undo.calledOnce,
+    await BrowserTestUtils.waitForCondition(() => PlacesTransactions.undo.calledOnce,
       "undo should have been called once.");
   });
 });
new file mode 100644
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_toolbar_overflow.js
@@ -0,0 +1,182 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests the bookmarks toolbar overflow.
+ */
+
+var gToolbar = document.getElementById("PersonalToolbar");
+var gToolbarContent = document.getElementById("PlacesToolbarItems");
+var gChevron = document.getElementById("PlacesChevron");
+
+const BOOKMARKS_COUNT = 250;
+
+add_task(async function setup() {
+  await PlacesUtils.bookmarks.eraseEverything();
+
+  // Add bookmarks.
+  await PlacesUtils.bookmarks.insertTree({
+    guid: PlacesUtils.bookmarks.toolbarGuid,
+    children: Array(BOOKMARKS_COUNT).fill("")
+                                    .map((_, i) => ({ url: `http://test.places.${i}/`}))
+  });
+
+  // Uncollapse the personal toolbar if needed.
+  let wasCollapsed = gToolbar.collapsed;
+  Assert.ok(wasCollapsed, "The toolbar is collapsed by default");
+  if (wasCollapsed) {
+    let promiseReady = BrowserTestUtils.waitForEvent(gToolbar, "BookmarksToolbarVisibilityUpdated");
+    await promiseSetToolbarVisibility(gToolbar, true);
+    await promiseReady;
+  }
+  registerCleanupFunction(async () => {
+    if (wasCollapsed) {
+      await promiseSetToolbarVisibility(gToolbar, false);
+    }
+    await PlacesUtils.bookmarks.eraseEverything();
+  });
+});
+
+add_task(async function() {
+  // Check that the overflow chevron is visible.
+  Assert.ok(!gChevron.collapsed, "The overflow chevron should be visible");
+  Assert.ok(gToolbarContent.childNodes.length < BOOKMARKS_COUNT,
+            "Not all the nodes should be built by default");
+  let visibleNodes = [];
+  for (let node of gToolbarContent.childNodes) {
+    if (node.style.visibility == "visible")
+      visibleNodes.push(node);
+  }
+  Assert.ok(visibleNodes.length < gToolbarContent.childNodes.length,
+            "The number of visible nodes should be smaller than the number of built nodes");
+
+  await test_index("Node at the last visible index", visibleNodes.length - 1, "visible");
+  await test_index("Node at the first invisible index", visibleNodes.length, "hidden");
+  await test_index("First non-built node", gToolbarContent.childNodes.length, undefined);
+  await test_index("Later non-built node", gToolbarContent.childNodes.length + 1, undefined);
+
+  await test_move_index("Move node from last visible to first hidden",
+                        visibleNodes.length - 1, visibleNodes.length,
+                        "visible", "hidden");
+  await test_move_index("Move node from fist visible to last built",
+                        0, gToolbarContent.childNodes.length - 1,
+                        "visible", "hidden");
+  await test_move_index("Move node from fist visible to first non built",
+                        0, gToolbarContent.childNodes.length,
+                        "visible", undefined);
+});
+
+async function test_index(desc, index, expected) {
+  info(desc);
+  let children = gToolbarContent.childNodes;
+  let originalLen = children.length;
+  let nodeExisted = children.length > index;
+  let previousNodeIsVisible = nodeExisted &&
+                              children[index - 1].style.visibility == "visible";
+  let promiseUpdateVisibility = expected == "visible" || previousNodeIsVisible
+    ? BrowserTestUtils.waitForEvent(gToolbar, "BookmarksToolbarVisibilityUpdated")
+    : Promise.resolve();
+  let bm = await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+    url: "http://test.places.added/",
+    index
+  });
+  Assert.equal(bm.index, index, "Sanity check the bookmark index");
+  await promiseUpdateVisibility;
+
+  if (!expected) {
+    Assert.ok(children.length <= index,
+              "The new node should not have been added");
+  } else {
+    Assert.equal(children[index]._placesNode.bookmarkGuid, bm.guid,
+                 "Found the added bookmark at the expected position");
+    Assert.equal(children[index].style.visibility, expected,
+                 `The bookmark node should be ${expected}`);
+  }
+  Assert.equal(children.length, originalLen,
+               "Number of built nodes should stay the same");
+
+  info("Remove the node");
+  promiseUpdateVisibility = expected == "visible"
+    ? BrowserTestUtils.waitForEvent(gToolbar, "BookmarksToolbarVisibilityUpdated")
+    : Promise.resolve();
+  await PlacesUtils.bookmarks.remove(bm);
+  await promiseUpdateVisibility;
+
+  if (expected && nodeExisted) {
+    Assert.equal(children[index]._placesNode.uri,
+                `http://test.places.${index}/`,
+                "Found the previous bookmark at the expected position");
+    Assert.equal(children[index].style.visibility, expected,
+                 `The bookmark node should be ${expected}`);
+  }
+  Assert.equal(children.length, originalLen,
+               "Number of built nodes should stay the same");
+}
+
+async function test_move_index(desc, fromIndex, toIndex, original, expected) {
+  info(desc);
+  let children = gToolbarContent.childNodes;
+  let originalLen = children.length;
+  let movedGuid = children[fromIndex]._placesNode.bookmarkGuid;
+  let existingGuid = children[toIndex] ?
+    children[toIndex]._placesNode.bookmarkGuid : null;
+  let existingIndex = fromIndex < toIndex ? toIndex - 1 : toIndex + 1;
+
+  Assert.equal(children[fromIndex].style.visibility, original,
+               `The bookmark node should be ${original}`);
+  let promiseUpdateVisibility = original == "visible" || expected == "visible"
+    ? BrowserTestUtils.waitForEvent(gToolbar, "BookmarksToolbarVisibilityUpdated")
+    : Promise.resolve();
+  await PlacesUtils.bookmarks.update({
+    guid: movedGuid,
+    index: toIndex
+  });
+  await promiseUpdateVisibility;
+
+  if (!expected) {
+    Assert.ok(children.length <= toIndex,
+              "Node in the new position is not expected");
+    Assert.ok(children[originalLen - 1],
+              "We should keep number of built nodes consistent");
+  } else {
+    Assert.equal(children[toIndex]._placesNode.bookmarkGuid, movedGuid,
+                 "Found the moved bookmark at the expected position");
+    Assert.equal(children[toIndex].style.visibility, expected,
+                 `The destination bookmark node should be ${expected}`);
+  }
+  Assert.equal(children[fromIndex].style.visibility, original,
+               `The origin bookmark node should be ${original}`);
+  if (existingGuid) {
+    Assert.equal(children[existingIndex]._placesNode.bookmarkGuid, existingGuid,
+                 "Found the pushed away bookmark at the expected position");
+  }
+  Assert.equal(children.length, originalLen,
+               "Number of built nodes should stay the same");
+
+  info("Moving back the node");
+  promiseUpdateVisibility = original == "visible" || expected == "visible"
+    ? BrowserTestUtils.waitForEvent(gToolbar, "BookmarksToolbarVisibilityUpdated")
+    : Promise.resolve();
+  await PlacesUtils.bookmarks.update({
+    guid: movedGuid,
+    index: fromIndex
+  });
+  await promiseUpdateVisibility;
+
+  Assert.equal(children[fromIndex]._placesNode.bookmarkGuid, movedGuid,
+               "Found the moved bookmark at the expected position");
+  if (expected) {
+    Assert.equal(children[toIndex].style.visibility, expected,
+                 `The bookmark node should be ${expected}`);
+  }
+  Assert.equal(children[fromIndex].style.visibility, original,
+               `The bookmark node should be ${original}`);
+  if (existingGuid) {
+    Assert.equal(children[toIndex]._placesNode.bookmarkGuid, existingGuid,
+                 "Found the pushed away bookmark at the expected position");
+  }
+  Assert.equal(children.length, originalLen,
+               "Number of built nodes should stay the same");
+}
--- a/browser/components/places/tests/chrome/test_RecentBookmarksMenuUI.xul
+++ b/browser/components/places/tests/chrome/test_RecentBookmarksMenuUI.xul
@@ -230,16 +230,47 @@
       is(document.getElementById("fakeRecentBookmarks").hidden, true,
          "The title item should be hidden");
       is(document.getElementById("fakeNextSeparator").hidden, false,
          "Next separator should not be hidden");
 
       simulateHideMenu();
     });
 
+    add_task(async function test_remove_with_recently_bookmarked_hidden() {
+      RecentBookmarksMenuUI.init(document.getElementById("fakeRecentBookmarks"));
+      RecentBookmarksMenuUI.visible = false;
+
+      is(bmMenu.children.length, 3,
+         "There should only be the original 3 items in the menu");
+
+      let bmToRemove = await PlacesUtils.bookmarks.fetch({url: `${BASE_URL}bookmark_dummy_6.html`});
+
+      sandbox.stub(RecentBookmarksMenuUI, "_clearExistingItems");
+      sandbox.stub(RecentBookmarksMenuUI, "_insertRecentMenuItems");
+
+      const clock = sandbox.useFakeTimers();
+
+      await PlacesUtils.bookmarks.remove(bmToRemove);
+
+      // Move the clock past the timeout to ensure any update happen.
+      clock.tick(RecentBookmarksMenuUI.ITEM_REMOVED_TIMEOUT + 1);
+      clock.restore();
+
+      is(bmMenu.children.length, 3,
+         "There should only be the original 3 items in the menu");
+      is(RecentBookmarksMenuUI._clearExistingItems.notCalled, true,
+         "Should not have cleared the existing items when recently bookmarked are hidden.");
+      is(RecentBookmarksMenuUI._insertRecentMenuItems.notCalled, true,
+         "Should not have inserted new menu items when recently bookmarked are hidden.");
+
+      sandbox.restore();
+      simulateHideMenu();
+    });
+
     add_task(async function test_show_recently_bookmarked() {
       RecentBookmarksMenuUI.init(document.getElementById("fakeRecentBookmarks"));
 
       is(bmMenu.children.length, 3,
          "There should only be the original 3 items in the menu");
 
       RecentBookmarksMenuUI.visible = true;
 
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -153,17 +153,16 @@ const RESTORE_TAB_CONTENT_REASON = {
 };
 
 Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm", this);
 Cu.import("resource://gre/modules/Services.jsm", this);
 Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
 Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", this);
 Cu.import("resource://gre/modules/Timer.jsm", this);
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
-Cu.import("resource://gre/modules/debug.js", this);
 Cu.import("resource://gre/modules/osfile.jsm", this);
 
 XPCOMUtils.defineLazyServiceGetters(this, {
   gSessionStartup: ["@mozilla.org/browser/sessionstartup;1", "nsISessionStartup"],
   gScreenManager: ["@mozilla.org/gfx/screenmanager;1", "nsIScreenManager"],
   Telemetry: ["@mozilla.org/base/telemetry;1", "nsITelemetry"],
 });
 
@@ -2153,19 +2152,16 @@ var SessionStoreInternal = {
    * Handler for the event that is fired when a <xul:browser> crashes.
    *
    * @param aWindow
    *        The window that the crashed browser belongs to.
    * @param aBrowser
    *        The <xul:browser> that is now in the crashed state.
    */
   onBrowserCrashed(aBrowser) {
-    NS_ASSERT(aBrowser.isRemoteBrowser,
-              "Only remote browsers should be able to crash");
-
     this.enterCrashedState(aBrowser);
     // The browser crashed so we might never receive flush responses.
     // Resolve all pending flush requests for the crashed browser.
     TabStateFlusher.resolveAll(aBrowser);
   },
 
   /**
    * Called when a browser is showing or is about to show the tab
@@ -2891,17 +2887,22 @@ var SessionStoreInternal = {
    *
    * This method might be called multiple times before it has finished
    * flushing the browser tab. If that occurs, the loadArguments from
    * the most recent call to navigateAndRestore will be used once the
    * flush has finished.
    */
   navigateAndRestore(tab, loadArguments, historyIndex) {
     let window = tab.ownerGlobal;
-    NS_ASSERT(window.__SSi, "tab's window must be tracked");
+
+    if (!window.__SSi) {
+      Cu.reportError("Tab's window must be tracked.");
+      return;
+    }
+
     let browser = tab.linkedBrowser;
 
     // Were we already waiting for a flush from a previous call to
     // navigateAndRestore on this tab?
     let alreadyRestoring =
       this._remotenessChangingBrowsers.has(browser.permanentKey);
 
     // Stash the most recent loadArguments in this WeakMap so that
@@ -3639,21 +3640,24 @@ var SessionStoreInternal = {
       if (t != selectedIndex) {
         this.restoreTab(aTabs[t], aTabData[t]);
       }
     }
   },
 
   // Restores the given tab state for a given tab.
   restoreTab(tab, tabData, options = {}) {
-    NS_ASSERT(!tab.linkedBrowser.__SS_restoreState,
-              "must reset tab before calling restoreTab()");
+    let browser = tab.linkedBrowser;
+
+    if (browser.__SS_restoreState) {
+      Cu.reportError("Must reset tab before calling restoreTab.");
+      return;
+    }
 
     let loadArguments = options.loadArguments;
-    let browser = tab.linkedBrowser;
     let window = tab.ownerGlobal;
     let tabbrowser = window.gBrowser;
     let forceOnDemand = options.forceOnDemand;
 
     let willRestoreImmediately = options.restoreImmediately ||
                                  tabbrowser.selectedBrowser == browser;
 
     let isBrowserInserted = browser.isConnected;
@@ -4677,24 +4681,26 @@ var SessionStoreInternal = {
   /**
    * Reset the restoring state for a particular tab. This will be called when
    * removing a tab or when a tab needs to be reset (it's being overwritten).
    *
    * @param aTab
    *        The tab that will be "reset"
    */
   _resetLocalTabRestoringState(aTab) {
-    NS_ASSERT(aTab.linkedBrowser.__SS_restoreState,
-              "given tab is not restoring");
-
     let browser = aTab.linkedBrowser;
 
     // Keep the tab's previous state for later in this method
     let previousState = browser.__SS_restoreState;
 
+    if (!previousState) {
+      Cu.reportError("Given tab is not restoring.");
+      return;
+    }
+
     // The browser is no longer in any sort of restoring state.
     delete browser.__SS_restoreState;
 
     aTab.removeAttribute("pending");
 
     if (previousState == TAB_STATE_RESTORING) {
       if (this._tabsRestoringCount)
         this._tabsRestoringCount--;
@@ -4702,20 +4708,23 @@ var SessionStoreInternal = {
       // Make sure that the tab is removed from the list of tabs to restore.
       // Again, this is normally done in restoreTabContent, but that isn't being called
       // for this tab.
       TabRestoreQueue.remove(aTab);
     }
   },
 
   _resetTabRestoringState(tab) {
-    NS_ASSERT(tab.linkedBrowser.__SS_restoreState,
-              "given tab is not restoring");
-
     let browser = tab.linkedBrowser;
+
+    if (!browser.__SS_restoreState) {
+      Cu.reportError("Given tab is not restoring.");
+      return;
+    }
+
     browser.messageManager.sendAsyncMessage("SessionStore:resetRestore", {});
     this._resetLocalTabRestoringState(tab);
   },
 
   /**
    * Each fresh tab starts out with epoch=0. This function can be used to
    * start a next epoch by incrementing the current value. It will enables us
    * to ignore stale messages sent from previous epochs. The function returns
--- a/browser/extensions/formautofill/FormAutofillHandler.jsm
+++ b/browser/extensions/formautofill/FormAutofillHandler.jsm
@@ -554,17 +554,17 @@ FormAutofillHandler.prototype = {
         data[type].record[detail.fieldName] = value;
 
         if (detail.state == "AUTO_FILLED") {
           data[type].untouchedFields.push(detail.fieldName);
         }
       });
     });
 
-    this.normalizeAddress(data.address);
+    this._normalizeAddress(data.address);
 
     if (data.address &&
         Object.values(data.address.record).filter(v => v).length <
         FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD) {
       log.debug("No address record saving since there are only",
                      Object.keys(data.address.record).length,
                      "usable fields");
       delete data.address;
@@ -574,21 +574,34 @@ FormAutofillHandler.prototype = {
         !FormAutofillUtils.isCCNumber(data.creditCard.record["cc-number"]))) {
       log.debug("No credit card record saving since card number is invalid");
       delete data.creditCard;
     }
 
     return data;
   },
 
-  normalizeAddress(address) {
+  _normalizeAddress(address) {
     if (!address) {
       return;
     }
 
+    // Normalize Country
+    if (address.record.country) {
+      let detail = this.getFieldDetailByName("country");
+      // Try identifying country field aggressively if it doesn't come from
+      // @autocomplete.
+      if (detail._reason != "autocomplete") {
+        let countryCode = FormAutofillUtils.identifyCountryCode(address.record.country);
+        if (countryCode) {
+          address.record.country = countryCode;
+        }
+      }
+    }
+
     // Normalize Tel
     FormAutofillUtils.compressTel(address.record);
     if (address.record.tel) {
       let allTelComponentsAreUntouched = Object.keys(address.record)
         .filter(field => FormAutofillUtils.getCategoryFromFieldName(field) == "tel")
         .every(field => address.untouchedFields.includes(field));
       if (allTelComponentsAreUntouched) {
         // No need to verify it if none of related fields are modified after autofilling.
--- a/browser/extensions/formautofill/test/unit/test_createRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_createRecords.js
@@ -18,16 +18,56 @@ const TESTCASES = [
       "given-name": "John",
       "family-name": "Doe",
     },
     expectedRecord: {
       address: undefined,
     },
   },
   {
+    description: "\"country\" using @autocomplete shouldn't be identified aggressively",
+    document: `<form>
+                <input id="given-name" autocomplete="given-name">
+                <input id="family-name" autocomplete="family-name">
+                <input id="country" autocomplete="country">
+               </form>`,
+    formValue: {
+      "given-name": "John",
+      "family-name": "Doe",
+      country: "United States",
+    },
+    expectedRecord: {
+      address: {
+        "given-name": "John",
+        "family-name": "Doe",
+        country: "United States",
+      },
+    },
+  },
+  {
+    description: "\"country\" using heuristics should be identified aggressively",
+    document: `<form>
+                <input id="given-name" autocomplete="given-name">
+                <input id="family-name" autocomplete="family-name">
+                <input id="country" name="country">
+               </form>`,
+    formValue: {
+      "given-name": "John",
+      "family-name": "Doe",
+      country: "United States",
+    },
+    expectedRecord: {
+      address: {
+        "given-name": "John",
+        "family-name": "Doe",
+        country: "US",
+      },
+    },
+  },
+  {
     description: "\"tel\" related fields should be concatenated",
     document: `<form>
                 <input id="given-name" autocomplete="given-name">
                 <input id="family-name" autocomplete="family-name">
                 <input id="tel-country-code" autocomplete="tel-country-code">
                 <input id="tel-national" autocomplete="tel-national">
                </form>`,
     formValue: {
--- a/browser/extensions/pocket/bootstrap.js
+++ b/browser/extensions/pocket/bootstrap.js
@@ -114,17 +114,17 @@ var PocketPageAction = {
           wrapper.setAttribute("context", "pageActionPanelContextMenu");
           wrapper.addEventListener("contextmenu", event => {
             window.BrowserPageActions.onContextMenu(event);
           });
           let animatableBox = doc.createElement("hbox");
           animatableBox.id = "pocket-animatable-box";
           let animatableImage = doc.createElement("image");
           animatableImage.id = "pocket-animatable-image";
-          animatableImage.setAttribute("role", "button");
+          animatableImage.setAttribute("role", "presentation");
           let tooltip =
             gPocketBundle.GetStringFromName("pocket-button.tooltiptext");
           animatableImage.setAttribute("tooltiptext", tooltip);
           let pocketButton = doc.createElement("image");
           pocketButton.id = "pocket-button";
           pocketButton.classList.add("urlbar-icon");
           pocketButton.setAttribute("role", "button");
           pocketButton.setAttribute("tooltiptext", tooltip);
--- a/browser/extensions/pocket/skin/shared/pocket.css
+++ b/browser/extensions/pocket/skin/shared/pocket.css
@@ -54,16 +54,26 @@
 }
 
 #pocket-button-box[animate="true"] > #pocket-animatable-box {
   /* .urlbar-icon has width 28px. Each frame is 20px wide. Set margin-inline-start
      to be half the difference, 4px. */
   margin-inline-start: 4px;
 }
 
+:root[uidensity=compact] #pocket-button-box[animate="true"] > #pocket-animatable-box {
+  /* .urlbar-icon has width 24px in this case */
+  margin-inline-start: 2px;
+}
+
+:root[uidensity=touch] #star-button-box[animationsenabled] > #star-button[starred][animate] + #star-button-animatable-box {
+  /* .urlbar-icon has width 30px in this case */
+  margin-inline-start: 5px;
+}
+
 #pocket-button-box[animate="true"] > #pocket-animatable-box > #pocket-animatable-image,
 #pocket-button > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
   height: var(--toolbarbutton-height); /* Height must be equal to height of toolbarbutton padding-box */
 }
 
 #pocket-button-box[animate="true"],
 #pocket-button[open="true"][animationsenabled][cui-areatype="toolbar"]:not([overflowedItem="true"]) {
   position: relative;
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -582,17 +582,21 @@ html|span.ac-emphasize-text-url {
   color: -moz-FieldText;
 }
 
 #sidebar-header {
   border-bottom: 1px solid ThreeDShadow;
 }
 
 .sidebar-splitter {
-  border-color: ThreeDShadow;
+  -moz-appearance: none;
+  width: 6px;
+  background-color: -moz-dialog;
+  border: 1px ThreeDShadow;
+  border-style: none solid;
 }
 
 .browserContainer > findbar {
   background-color: -moz-dialog;
   color: -moz-DialogText;
   text-shadow: none;
 }
 
--- a/browser/themes/shared/browser.inc.css
+++ b/browser/themes/shared/browser.inc.css
@@ -51,19 +51,19 @@
   -moz-outline-radius: 2px;
 }
 
 #navigator-toolbox > toolbar[customizing]:not(#toolbar-menubar):not(#TabsToolbar):not(#nav-bar):empty {
   /* Avoid the toolbar having no height when there's no items in it */
   min-height: 22px;
 }
 
-/* Library animation */
+/* Required for Library animation */
 
-#navigator-toolbox[animate] {
+#navigator-toolbox {
   position: relative;
 }
 
 #library-animatable-box {
   display: none;
 }
 
 #library-animatable-box[animate] {
--- a/browser/themes/shared/sidebar.inc.css
+++ b/browser/themes/shared/sidebar.inc.css
@@ -6,34 +6,38 @@
 
 .sidebar-header,
 #sidebar-header {
   font-size: 1.333em;
   font-weight: lighter;
   padding: 8px;
 }
 
+%ifndef MOZ_WIDGET_GTK
+% We don't let the splitter overlap the sidebar on Linux since the sidebar's
+% scrollbar is too narrow on Linux.
 .sidebar-splitter {
   -moz-appearance: none;
   border: 0 solid;
   border-inline-end-width: 1px;
   min-width: 1px;
-  width: 8px;
+  width: 4px;
   background-image: none !important;
   background-color: transparent;
-  margin-inline-start: -8px;
+  margin-inline-start: -4px;
   position: relative;
 }
 
 #sidebar-box[positionend] + .sidebar-splitter {
   border-inline-end-width: 0;
   border-inline-start-width: 1px;
   margin-inline-start: 0;
-  margin-inline-end: -8px;
+  margin-inline-end: -4px;
 }
+%endif
 
 #sidebar-throbber[loading="true"] {
   list-style-image: url("chrome://global/skin/icons/loading.png");
 }
 
 @media (min-resolution: 2dppx) {
   .sidebar-throbber[loading="true"],
   #sidebar-throbber[loading="true"] {
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -21,16 +21,17 @@
   --tab-min-height: 41px;
 }
 
 :root:-moz-lwtheme {
   --tab-line-color: var(--lwt-accent-color);
 }
 
 #tabbrowser-tabs,
+#tabbrowser-tabs > .tabbrowser-arrowscrollbox,
 .tabbrowser-tabs[positionpinnedtabs] > .tabbrowser-tab[pinned] {
   min-height: var(--tab-min-height);
 }
 
 .tab-stack {
   min-height: inherit;
 }
 
@@ -224,19 +225,24 @@
 .tab-sharing-icon-overlay[sharing="screen"] {
   list-style-image: url("chrome://browser/skin/notification-icons/screen.svg");
 }
 
 .tab-icon-overlay {
   width: 16px;
   height: 16px;
   margin-top: -8px;
+  margin-inline-start: -15px;
+  margin-inline-end: -1px;
+  position: relative;
+}
+
+.tab-icon-overlay[pinned] {
   margin-inline-start: -6px;
   margin-inline-end: -10px;
-  position: relative;
 }
 
 .tab-icon-overlay[crashed] {
   list-style-image: url("chrome://browser/skin/tabbrowser/crashed.svg");
 }
 
 .tab-icon-overlay[soundplaying],
 .tab-icon-overlay[muted]:not([crashed]),
--- a/browser/themes/shared/toolbarbutton-icons.inc.css
+++ b/browser/themes/shared/toolbarbutton-icons.inc.css
@@ -8,16 +8,17 @@ toolbar[brighttext] {
 
 .toolbarbutton-animatable-box,
 .toolbarbutton-1 {
   -moz-context-properties: fill;
   fill: #4c4c4c;
 }
 
 .toolbarbutton-animatable-box[brighttext],
+toolbar[brighttext] .toolbarbutton-animatable-box,
 toolbar[brighttext] .toolbarbutton-1 {
   fill: #fff;
 }
 
 #back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
 #forward-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
 #reload-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
 #nav-bar-overflow-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
--- a/browser/themes/shared/urlbar-searchbar.inc.css
+++ b/browser/themes/shared/urlbar-searchbar.inc.css
@@ -197,22 +197,16 @@
   width: 26px;
 }
 
 .urlbar-go-button:-moz-locale-dir(ltr),
 .search-go-button:-moz-locale-dir(ltr) {
   transform: scaleX(-1);
 }
 
-.urlbar-go-button:hover,
-.search-go-button:hover {
-  fill: #058b00;
-  fill-opacity: 1;
-}
-
 .urlbar-history-dropmarker {
   -moz-appearance: none;
   list-style-image: url(chrome://global/skin/icons/arrow-dropdown-16.svg);
   transition: opacity 0.15s ease;
 }
 
 #urlbar[switchingtabs] > .urlbar-textbox-container > .urlbar-history-dropmarker {
   transition: none;
@@ -271,16 +265,26 @@
   /* Set the height to equal the height of each frame of the SVG. Must use
      min- and max- width and height due to bug 1379332. */
   min-width: 33px;
   max-width: 33px;
   min-height: 33px;
   max-height: 33px;
 }
 
+:root[uidensity=compact] #star-button-box[animationsenabled] > #star-button[starred][animate] + #star-button-animatable-box {
+  /* .urlbar-icon has width 24px in this case */
+  margin-inline-start: -4.5px;
+}
+
+:root[uidensity=touch] #star-button-box[animationsenabled] > #star-button[starred][animate] + #star-button-animatable-box {
+  /* .urlbar-icon has width 30px in this case */
+  margin-inline-start: -1.5px;
+}
+
 #star-button-box[animationsenabled] > #star-button[starred][animate] + #star-button-animatable-box > #star-button-animatable-image {
   background-image: url("chrome://browser/skin/bookmark-animation.svg");
   background-size: auto;
   list-style-image: none;
   height: var(--toolbarbutton-height);
   min-height: 33px; /* Minimum height must be equal to the height of the SVG sprite */
   animation-name: bookmark-animation;
   animation-fill-mode: forwards;
--- a/build/valgrind/cross-architecture.sup
+++ b/build/valgrind/cross-architecture.sup
@@ -10,17 +10,17 @@
    ...
    fun:_ZL9SaveToEnvPKc
    ...
 }
 {
    PR_SetEnv requires its argument to be leaked, but does not appear on stacks. (See bug 793549.)
    Memcheck:Leak
    ...
-   fun:_ZL13SaveWordToEnvPKcRK10nsACString
+   fun:_ZL13SaveWordToEnvPKcRK12nsTSubstringIcE
    ...
 }
 {
    PR_SetEnv requires its argument to be leaked, but does not appear on stacks. (See bug 944133.)
    Memcheck:Leak
    ...
    fun:_ZN13CrashReporter14SetRestartArgsEiPPc
    ...
--- a/devtools/client/debugger/test/mochitest/browser_dbg_pause-warning.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_pause-warning.js
@@ -28,19 +28,17 @@ function test() {
   });
 }
 
 function testPause() {
   gDebugger.gThreadClient.addOneTimeListener("paused", () => {
     ok(gDebugger.gThreadClient.paused,
       "threadClient.paused has been updated to true.");
 
-    gToolbox.once("inspector-selected").then(inspector => {
-      inspector.once("inspector-updated").then(testNotificationIsUp1);
-    });
+    gToolbox.once("inspector-selected").then(testNotificationIsUp1);
     gToolbox.selectTool("inspector");
   });
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
     gDebugger.document.getElementById("resume"),
     gDebugger);
 
   // Evaluate a script to fully pause the debugger
--- a/devtools/client/dom/content/components/dom-tree.js
+++ b/devtools/client/dom/content/components/dom-tree.js
@@ -25,20 +25,21 @@ const PropTypes = React.PropTypes;
 
 /**
  * Renders DOM panel tree.
  */
 var DomTree = React.createClass({
   displayName: "DomTree",
 
   propTypes: {
-    object: PropTypes.any,
+    dispatch: PropTypes.func.isRequired,
     filter: PropTypes.string,
-    dispatch: PropTypes.func.isRequired,
     grips: PropTypes.object,
+    object: PropTypes.any,
+    openLink: PropTypes.func,
   },
 
   /**
    * Filter DOM properties. Return true if the object
    * should be visible in the tree.
    */
   onFilter: function (object) {
     if (!this.props.filter) {
@@ -47,39 +48,47 @@ var DomTree = React.createClass({
 
     return (object.name && object.name.indexOf(this.props.filter) > -1);
   },
 
   /**
    * Render DOM panel content
    */
   render: function () {
+    let {
+      dispatch,
+      grips,
+      object,
+      openLink,
+    } = this.props;
+
     let columns = [{
       "id": "value"
     }];
 
     // This is the integration point with Reps. The DomTree is using
     // Reps to render all values. The code also specifies default rep
     // used for data types that don't have its own specific template.
     let renderValue = props => {
       return Rep(Object.assign({}, props, {
         defaultRep: Grip,
         cropLimit: 50,
       }));
     };
 
     return (
       TreeView({
-        object: this.props.object,
-        provider: new GripProvider(this.props.grips, this.props.dispatch),
+        columns,
         decorator: new DomDecorator(),
         mode: MODE.SHORT,
-        columns: columns,
-        renderValue: renderValue,
-        onFilter: this.onFilter
+        object,
+        onFilter: this.onFilter,
+        openLink,
+        provider: new GripProvider(grips, dispatch),
+        renderValue,
       })
     );
   }
 });
 
 const mapStateToProps = (state) => {
   return {
     grips: state.grips,
--- a/devtools/client/dom/content/components/main-frame.js
+++ b/devtools/client/dom/content/components/main-frame.js
@@ -1,54 +1,62 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ /* globals DomProvider */
+
 "use strict";
 
 // React & Redux
 const React = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
 // DOM Panel
 const DomTree = React.createFactory(require("./dom-tree"));
 const MainToolbar = React.createFactory(require("./main-toolbar"));
 
 // Shortcuts
 const { div } = React.DOM;
 const PropTypes = React.PropTypes;
 
 /**
- * Renders basic layout of the DOM panel. The DOM panel cotent consists
+ * Renders basic layout of the DOM panel. The DOM panel content consists
  * from two main parts: toolbar and tree.
  */
 var MainFrame = React.createClass({
   displayName: "MainFrame",
 
   propTypes: {
-    object: PropTypes.any,
+    dispatch: PropTypes.func.isRequired,
     filter: PropTypes.string,
-    dispatch: PropTypes.func.isRequired,
+    object: PropTypes.any,
   },
 
   /**
    * Render DOM panel content
    */
   render: function () {
+    let {
+      filter,
+      object,
+    } = this.props;
+
     return (
       div({className: "mainFrame"},
         MainToolbar({
           dispatch: this.props.dispatch,
           object: this.props.object
         }),
         div({className: "treeTableBox"},
           DomTree({
-            object: this.props.object,
-            filter: this.props.filter,
+            filter,
+            object,
+            openLink: url => DomProvider.openLink(url),
           })
         )
       )
     );
   }
 });
 
 // Transform state into props
--- a/devtools/client/dom/content/dom-view.js
+++ b/devtools/client/dom/content/dom-view.js
@@ -56,10 +56,10 @@ DomView.prototype = {
 
     if (typeof this[method] == "function") {
       this[method](data.args);
     }
   },
 };
 
 // Construct DOM panel view object and expose it to tests.
-// Tests can access it throught: |panel.panelWin.view|
+// Tests can access it through: |panel.panelWin.view|
 window.view = new DomView(store);
--- a/devtools/client/dom/dom-panel.js
+++ b/devtools/client/dom/dom-panel.js
@@ -62,18 +62,20 @@ DomPanel.prototype = {
 
   initialize: function () {
     this.panelWin.addEventListener("devtools/content/message",
       this.onContentMessage, true);
 
     this.target.on("navigate", this.onTabNavigated);
     this._toolbox.on("select", this.onPanelVisibilityChange);
 
+    // Export provider object with useful API for DOM panel.
     let provider = {
-      getPrototypeAndProperties: this.getPrototypeAndProperties.bind(this)
+      getPrototypeAndProperties: this.getPrototypeAndProperties.bind(this),
+      openLink: this.openLink.bind(this),
     };
 
     exportIntoContentScope(this.panelWin, provider, "DomProvider");
 
     this.shouldRefresh = true;
   },
 
   destroy: Task.async(function* () {
@@ -111,17 +113,17 @@ DomPanel.prototype = {
 
     this.getRootGrip().then(rootGrip => {
       this.postContentMessage("initialize", rootGrip);
     });
   },
 
   /**
    * Make sure the panel is refreshed when the page is reloaded.
-   * The panel is refreshed immediatelly if it's currently selected
+   * The panel is refreshed immediately if it's currently selected
    * or lazily  when the user actually selects it.
    */
   onTabNavigated: function () {
     this.shouldRefresh = true;
     this.refresh();
   },
 
   /**
@@ -172,16 +174,23 @@ DomPanel.prototype = {
       }
     });
 
     this.pendingRequests.set(grip.actor, deferred.promise);
 
     return deferred.promise;
   },
 
+  openLink: function (url) {
+    let parentDoc = this._toolbox.doc;
+    let iframe = parentDoc.getElementById("this._toolbox");
+    let top = iframe.ownerDocument.defaultView.top;
+    top.openUILinkIn(url, "tab");
+  },
+
   getRootGrip: function () {
     let deferred = defer();
 
     // Attach Console. It might involve RDP communication, so wait
     // asynchronously for the result
     this.target.activeConsole.evaluateJSAsync("window", res => {
       deferred.resolve(res.result);
     });
--- a/devtools/client/inspector/inspector-commands.js
+++ b/devtools/client/inspector/inspector-commands.js
@@ -2,17 +2,18 @@
  * 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 strict";
 
 const l10n = require("gcli/l10n");
 const {gDevTools} = require("devtools/client/framework/devtools");
 /* eslint-disable mozilla/reject-some-requires */
-const {EyeDropper, HighlighterEnvironment} = require("devtools/server/actors/highlighters");
+const {HighlighterEnvironment} = require("devtools/server/actors/highlighters");
+const {EyeDropper} = require("devtools/server/actors/highlighters/eye-dropper");
 /* eslint-enable mozilla/reject-some-requires */
 const Telemetry = require("devtools/client/shared/telemetry");
 
 const windowEyeDroppers = new WeakMap();
 
 exports.items = [{
   item: "command",
   runAt: "client",
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -3,47 +3,50 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* global window, BrowserLoader */
 
 "use strict";
 
-var Services = require("Services");
-var promise = require("promise");
-var EventEmitter = require("devtools/shared/old-event-emitter");
+const Services = require("Services");
+const promise = require("promise");
+const EventEmitter = require("devtools/shared/old-event-emitter");
 const {executeSoon} = require("devtools/shared/DevToolsUtils");
-var KeyShortcuts = require("devtools/client/shared/key-shortcuts");
-var {Task} = require("devtools/shared/task");
-const {initCssProperties} = require("devtools/shared/fronts/css-properties");
-const nodeConstants = require("devtools/shared/dom-node-constants");
+const {Task} = require("devtools/shared/task");
+
+// constructor
 const Telemetry = require("devtools/client/shared/telemetry");
-
-const Menu = require("devtools/client/framework/menu");
-const MenuItem = require("devtools/client/framework/menu-item");
-
-const {HTMLBreadcrumbs} = require("devtools/client/inspector/breadcrumbs");
-const ExtensionSidebar = require("devtools/client/inspector/extensions/extension-sidebar");
-const GridInspector = require("devtools/client/inspector/grids/grid-inspector");
-const {InspectorSearch} = require("devtools/client/inspector/inspector-search");
 const HighlightersOverlay = require("devtools/client/inspector/shared/highlighters-overlay");
 const ReflowTracker = require("devtools/client/inspector/shared/reflow-tracker");
-const {ToolSidebar} = require("devtools/client/inspector/toolsidebar");
-const MarkupView = require("devtools/client/inspector/markup/markup");
-const {CommandUtils} = require("devtools/client/shared/developer-toolbar");
-const {ViewHelpers} = require("devtools/client/shared/widgets/view-helpers");
-const clipboardHelper = require("devtools/shared/platform/clipboard");
+const Store = require("devtools/client/inspector/store");
 
-const Store = require("devtools/client/inspector/store");
+loader.lazyRequireGetter(this, "initCssProperties", "devtools/shared/fronts/css-properties", true);
+loader.lazyRequireGetter(this, "HTMLBreadcrumbs", "devtools/client/inspector/breadcrumbs", true);
+loader.lazyRequireGetter(this, "KeyShortcuts", "devtools/client/shared/key-shortcuts");
+loader.lazyRequireGetter(this, "GridInspector", "devtools/client/inspector/grids/grid-inspector");
+loader.lazyRequireGetter(this, "InspectorSearch", "devtools/client/inspector/inspector-search", true);
+loader.lazyRequireGetter(this, "ToolSidebar", "devtools/client/inspector/toolsidebar", true);
+loader.lazyRequireGetter(this, "MarkupView", "devtools/client/inspector/markup/markup");
+
+loader.lazyRequireGetter(this, "nodeConstants", "devtools/shared/dom-node-constants");
+loader.lazyRequireGetter(this, "Menu", "devtools/client/framework/menu");
+loader.lazyRequireGetter(this, "MenuItem", "devtools/client/framework/menu-item");
+loader.lazyRequireGetter(this, "ExtensionSidebar", "devtools/client/inspector/extensions/extension-sidebar");
+loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
+loader.lazyRequireGetter(this, "ViewHelpers", "devtools/client/shared/widgets/view-helpers", true);
+loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clipboard");
 
 const {LocalizationHelper, localizeMarkup} = require("devtools/shared/l10n");
 const INSPECTOR_L10N =
       new LocalizationHelper("devtools/client/locales/inspector.properties");
-const TOOLBOX_L10N = new LocalizationHelper("devtools/client/locales/toolbox.properties");
+loader.lazyGetter(this, "TOOLBOX_L10N", function () {
+  return new LocalizationHelper("devtools/client/locales/toolbox.properties");
+});
 
 // Sidebar dimensions
 const INITIAL_SIDEBAR_SIZE = 350;
 
 // If the toolbox width is smaller than given amount of pixels,
 // the sidebar automatically switches from 'landscape' to 'portrait' mode.
 const PORTRAIT_MODE_WIDTH = 700;
 
@@ -230,17 +233,17 @@ Inspector.prototype = {
         }).catch(console.error),
         this._target.actorHasMethod("inspector", "resolveRelativeURL").then(value => {
           this._supportsResolveRelativeURL = value;
         }).catch(console.error),
       ]);
     });
   },
 
-  _deferredOpen: function (defaultSelection) {
+  _deferredOpen: async function (defaultSelection) {
     this.breadcrumbs = new HTMLBreadcrumbs(this);
 
     this.walker.on("new-root", this.onNewRoot);
 
     this.selection.on("new-node-front", this.onNewSelection);
     this.selection.on("detached-front", this.onDetached);
 
     if (this.target.isLocalTab) {
@@ -270,37 +273,35 @@ Inspector.prototype = {
       this.target.on("thread-resumed", this.updateDebuggerPausedWarning);
       this._toolbox.on("select", this.updateDebuggerPausedWarning);
       this.updateDebuggerPausedWarning();
     }
 
     this._initMarkup();
     this.isReady = false;
 
-    return new Promise(resolve => {
-      this.once("markuploaded", () => {
-        this.isReady = true;
+    this.setupSearchBox();
+    this.setupSidebar();
+    this.setupExtensionSidebars();
 
-        // All the components are initialized. Let's select a node.
-        if (defaultSelection) {
-          this.selection.setNodeFront(defaultSelection, "inspector-open");
-          this.markup.expandNode(this.selection.nodeFront);
-        }
+    await this.once("markuploaded");
+    this.isReady = true;
 
-        // And setup the toolbar only now because it may depend on the document.
-        this.setupToolbar();
+    // All the components are initialized. Let's select a node.
+    if (defaultSelection) {
+      let onAllPanelsUpdated = this.once("inspector-updated");
+      this.selection.setNodeFront(defaultSelection, "inspector-open");
+      await onAllPanelsUpdated;
+      await this.markup.expandNode(this.selection.nodeFront);
+    }
 
-        this.emit("ready");
-        resolve(this);
-      });
-
-      this.setupSearchBox();
-      this.setupSidebar();
-      this.setupExtensionSidebars();
-    });
+    // And setup the toolbar only now because it may depend on the document.
+    await this.setupToolbar();
+    this.emit("ready");
+    return this;
   },
 
   _onBeforeNavigate: function () {
     this._defaultNode = null;
     this.selection.setNodeFront(null);
     this._destroyMarkup();
     this.isDirty = false;
     this._pendingSelection = null;
--- a/devtools/client/inspector/inspector.xhtml
+++ b/devtools/client/inspector/inspector.xhtml
@@ -28,17 +28,17 @@
   <script type="application/javascript"
           src="chrome://devtools/content/shared/theme-switching.js"></script>
   <script type="text/javascript">
     /* eslint-disable */
     var isInChrome = window.location.href.includes("chrome:");
     if (isInChrome) {
       var exports = {};
       var Cu = Components.utils;
-      var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+      var { require, loader } = Cu.import("resource://devtools/shared/Loader.jsm", {});
       var { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
     }
   </script>
 
   <!-- in content, inspector.js is mapped to the dynamically generated webpack bundle -->
   <script type="application/javascript" src="inspector.js" defer="true"></script>
 </head>
 <body class="theme-body" role="application">
--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -1150,17 +1150,17 @@ MarkupView.prototype = {
     });
   },
 
   /**
    * Expand the node's children.
    */
   expandNode: function (node) {
     let container = this.getContainer(node);
-    this._expandContainer(container);
+    return this._expandContainer(container);
   },
 
   /**
    * Expand the entire tree beneath a container.
    *
    * @param  {MarkupContainer} container
    *         The container to expand.
    */
@@ -1643,17 +1643,17 @@ MarkupView.prototype = {
         if (!this._containers) {
           return promise.reject("markup view destroyed");
         }
         this._queuedChildUpdates.delete(container);
 
         // If children are dirty, we got a change notification for this node
         // while the request was in progress, we need to do it again.
         if (container.childrenDirty) {
-          return this._updateChildren(container, {expand: centered});
+          return this._updateChildren(container, {expand: centered || expand});
         }
 
         let fragment = this.doc.createDocumentFragment();
 
         for (let child of children.nodes) {
           let childContainer = this.importNode(child, flash);
           fragment.appendChild(childContainer.elt);
         }
--- a/devtools/client/inspector/test/browser_inspector_switch-to-inspector-on-pick.js
+++ b/devtools/client/inspector/test/browser_inspector_switch-to-inspector-on-pick.js
@@ -28,12 +28,9 @@ function openToolbox(tab) {
 function* startPickerAndAssertSwitchToInspector(toolbox) {
   info("Clicking element picker button.");
   let pickButton = toolbox.doc.querySelector("#command-button-pick");
   pickButton.click();
 
   info("Waiting for inspector to be selected.");
   yield toolbox.once("inspector-selected");
   is(toolbox.currentToolId, "inspector", "Switched to the inspector");
-
-  info("Waiting for inspector to update.");
-  yield toolbox.getCurrentPanel().once("inspector-updated");
 }
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -298,21 +298,17 @@ pref("devtools.netmonitor.persistlog", f
 // any timestamps.
 pref("devtools.webconsole.timestampMessages", false);
 
 // Web Console automatic multiline mode: |true| if you want incomplete statements
 // to automatically trigger multiline editing (equivalent to shift + enter).
 pref("devtools.webconsole.autoMultiline", true);
 
 // Enable the new webconsole frontend
-#if defined(NIGHTLY_BUILD) || defined(MOZ_DEV_EDITION)
 pref("devtools.webconsole.new-frontend-enabled", true);
-#else
-pref("devtools.webconsole.new-frontend-enabled", false);
-#endif
 
 // Enable client-side mapping service for source maps
 pref("devtools.source-map.client-service.enabled", true);
 
 // The number of lines that are displayed in the web console.
 pref("devtools.hud.loglimit", 10000);
 
 // The number of lines that are displayed in the old web console for the Net,
--- a/devtools/client/shared/components/tree/tree-cell.js
+++ b/devtools/client/shared/components/tree/tree-cell.js
@@ -62,17 +62,17 @@ define(function (require, exports, modul
         classNames = [classNames];
       }
 
       return classNames;
     },
 
     updateInputEnabled: function (evt) {
       this.setState(Object.assign({}, this.state, {
-        inputEnabled: evt.target.nodeName !== "input",
+        inputEnabled: evt.target.nodeName.toLowerCase() !== "input",
       }));
     },
 
     render: function () {
       let {
         member,
         id,
         value,
--- a/devtools/client/styleeditor/StyleEditorUI.jsm
+++ b/devtools/client/styleeditor/StyleEditorUI.jsm
@@ -66,31 +66,42 @@ function StyleEditorUI(debuggee, target,
   this._panelDoc = panelDoc;
   this._cssProperties = cssProperties;
   this._window = this._panelDoc.defaultView;
   this._root = this._panelDoc.getElementById("style-editor-chrome");
 
   this.editors = [];
   this.selectedEditor = null;
   this.savedLocations = {};
+  this._seenSheets = new Map();
+
+  // Don't add any style sheets that might arrive via events, until
+  // the call to initialize.  Style sheets can arrive from the server
+  // at any time, for example if a new style sheet was added, or if
+  // the style sheet actor was just created and is walking the style
+  // sheets for the first time.  In any case, in |initialize| we're
+  // going to fetch the list of sheets anyway.
+  this._suppressAdd = true;
 
   this._onOptionsPopupShowing = this._onOptionsPopupShowing.bind(this);
   this._onOptionsPopupHiding = this._onOptionsPopupHiding.bind(this);
-  this._onStyleSheetCreated = this._onStyleSheetCreated.bind(this);
   this._onNewDocument = this._onNewDocument.bind(this);
   this._onMediaPrefChanged = this._onMediaPrefChanged.bind(this);
   this._updateMediaList = this._updateMediaList.bind(this);
   this._clear = this._clear.bind(this);
   this._onError = this._onError.bind(this);
   this._updateOpenLinkItem = this._updateOpenLinkItem.bind(this);
   this._openLinkNewTab = this._openLinkNewTab.bind(this);
+  this._addStyleSheet = this._addStyleSheet.bind(this);
 
   this._prefObserver = new PrefObserver("devtools.styleeditor.");
   this._prefObserver.on(PREF_ORIG_SOURCES, this._onNewDocument);
   this._prefObserver.on(PREF_MEDIA_SIDEBAR, this._onMediaPrefChanged);
+
+  this._debuggee.on("stylesheet-added", this._addStyleSheet);
 }
 this.StyleEditorUI = StyleEditorUI;
 
 StyleEditorUI.prototype = {
   /**
    * Get whether any of the editors have unsaved changes.
    *
    * @return boolean
@@ -159,17 +170,17 @@ StyleEditorUI.prototype = {
    * Build the initial UI and wire buttons with event handlers.
    */
   createUI: function () {
     let viewRoot = this._root.parentNode.querySelector(".splitview-root");
 
     this._view = new SplitView(viewRoot);
 
     wire(this._view.rootElement, ".style-editor-newButton", () =>{
-      this._debuggee.addStyleSheet(null).then(this._onStyleSheetCreated);
+      this._debuggee.addStyleSheet(null);
     });
 
     wire(this._view.rootElement, ".style-editor-importButton", ()=> {
       this._importFromFile(this._mockImportFile || null, this._window);
     });
 
     this._optionsButton = this._panelDoc.getElementById("style-editor-options");
     this._panelDoc.addEventListener("contextmenu", () => {
@@ -227,29 +238,31 @@ StyleEditorUI.prototype = {
    * Refresh editors to reflect the stylesheets in the document.
    *
    * @param {string} event
    *        Event name
    * @param {StyleSheet} styleSheet
    *        StyleSheet object for new sheet
    */
   _onNewDocument: function () {
+    this._suppressAdd = true;
     this._debuggee.getStyleSheets().then((styleSheets) => {
       return this._resetStyleSheetList(styleSheets);
     }).catch(console.error);
   },
 
   /**
    * Add editors for all the given stylesheets to the UI.
    *
    * @param  {array} styleSheets
    *         Array of StyleSheetFront
    */
   _resetStyleSheetList: Task.async(function* (styleSheets) {
     this._clear();
+    this._suppressAdd = false;
 
     for (let sheet of styleSheets) {
       try {
         yield this._addStyleSheet(sheet);
       } catch (e) {
         this.emit("error", { key: LOAD_ERROR });
       }
     }
@@ -282,66 +295,91 @@ StyleEditorUI.prototype = {
         this.savedLocations[identifier] = editor.savedFile;
       }
     }
 
     this._clearStyleSheetEditors();
     this._view.removeAll();
 
     this.selectedEditor = null;
+    // Here the keys are style sheet actors, and the values are
+    // promises that resolve to the sheet's editor.  See |_addStyleSheet|.
+    this._seenSheets = new Map();
+    this._suppressAdd = true;
 
     this._root.classList.add("loading");
   },
 
   /**
    * Add an editor for this stylesheet. Add editors for its original sources
    * instead (e.g. Sass sources), if applicable.
    *
    * @param  {StyleSheetFront} styleSheet
    *         Style sheet to add to style editor
+   * @param {Boolean} isNew
+   *        True if this style sheet was created by a call to the
+   *        style sheets actor's @see addStyleSheet method.
+   * @return {Promise}
+   *         A promise that resolves to the style sheet's editor when the style sheet has
+   *         been fully loaded.  If the style sheet has a source map, and source mapping
+   *         is enabled, then the promise resolves to null.
    */
-  _addStyleSheet: Task.async(function* (styleSheet) {
-    let editor = yield this._addStyleSheetEditor(styleSheet);
-
-    if (!Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) {
-      return;
+  _addStyleSheet: function (styleSheet, isNew) {
+    if (this._suppressAdd) {
+      return null;
     }
 
-    let sources = yield styleSheet.getOriginalSources();
-    if (sources && sources.length) {
-      let parentEditorName = editor.friendlyName;
-      this._removeStyleSheetEditor(editor);
+    if (!this._seenSheets.has(styleSheet)) {
+      let promise = (async () => {
+        let editor = await this._addStyleSheetEditor(styleSheet, isNew);
+
+        if (!Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) {
+          return editor;
+        }
+
+        let sources = await styleSheet.getOriginalSources();
+        // A single generated sheet might map to multiple original
+        // sheets, so make editors for each of them.
+        if (sources && sources.length) {
+          let parentEditorName = editor.friendlyName;
+          this._removeStyleSheetEditor(editor);
+          editor = null;
 
-      for (let source of sources) {
-        // set so the first sheet will be selected, even if it's a source
-        source.styleSheetIndex = styleSheet.styleSheetIndex;
-        source.relatedStyleSheet = styleSheet;
-        source.relatedEditorName = parentEditorName;
-        yield this._addStyleSheetEditor(source);
-      }
+          for (let source of sources) {
+            // set so the first sheet will be selected, even if it's a source
+            source.styleSheetIndex = styleSheet.styleSheetIndex;
+            source.relatedStyleSheet = styleSheet;
+            source.relatedEditorName = parentEditorName;
+            await this._addStyleSheetEditor(source);
+          }
+        }
+
+        return editor;
+      })();
+      this._seenSheets.set(styleSheet, promise);
     }
-  }),
+    return this._seenSheets.get(styleSheet);
+  },
 
   /**
    * Add a new editor to the UI for a source.
    *
    * @param {StyleSheet}  styleSheet
    *        Object representing stylesheet
-   * @param {nsIfile}  file
-   *         Optional file object that sheet was imported from
    * @param {Boolean} isNew
    *         Optional if stylesheet is a new sheet created by user
    * @return {Promise} that is resolved with the created StyleSheetEditor when
    *                   the editor is fully initialized or rejected on error.
    */
-  _addStyleSheetEditor: Task.async(function* (styleSheet, file, isNew) {
+  _addStyleSheetEditor: Task.async(function* (styleSheet, isNew) {
     // recall location of saved file for this sheet after page reload
+    let file = null;
     let identifier = this.getStyleSheetIdentifier(styleSheet);
     let savedFile = this.savedLocations[identifier];
-    if (savedFile && !file) {
+    if (savedFile) {
       file = savedFile;
     }
 
     let editor = new StyleSheetEditor(styleSheet, this._window, file, isNew,
                                       this._walker, this._highlighter);
 
     editor.on("property-change", this._summaryChange.bind(this, editor));
     editor.on("media-rules-changed", this._updateMediaList.bind(this, editor));
@@ -382,34 +420,34 @@ StyleEditorUI.prototype = {
         if (!Components.isSuccessCode(status)) {
           this.emit("error", { key: LOAD_ERROR });
           return;
         }
         let source =
             NetUtil.readInputStreamToString(stream, stream.available());
         stream.close();
 
+        this._suppressAdd = true;
         this._debuggee.addStyleSheet(source).then((styleSheet) => {
-          this._onStyleSheetCreated(styleSheet, selectedFile);
+          this._suppressAdd = false;
+          this._addStyleSheet(styleSheet, true).then(editor => {
+            if (editor) {
+              editor.savedFile = selectedFile;
+            }
+            // Just for testing purposes.
+            this.emit("test:editor-updated", editor);
+          });
         });
       });
     };
 
     showFilePicker(file, false, parentWindow, onFileSelected);
   },
 
   /**
-   * When a new or imported stylesheet has been added to the document.
-   * Add an editor for it.
-   */
-  _onStyleSheetCreated: function (styleSheet, file) {
-    this._addStyleSheetEditor(styleSheet, file, true);
-  },
-
-  /**
    * Forward any error from a stylesheet.
    *
    * @param  {string} event
    *         Event name
    * @param  {data} data
    *         The event data
    */
   _onError: function (event, data) {
@@ -1005,22 +1043,27 @@ StyleEditorUI.prototype = {
   destroy: function () {
     if (this._highlighter) {
       this._highlighter.finalize();
       this._highlighter = null;
     }
 
     this._clearStyleSheetEditors();
 
+    this._seenSheets = null;
+    this._suppressAdd = false;
+
     let sidebar = this._panelDoc.querySelector(".splitview-controller");
     let sidebarWidth = sidebar.getAttribute("width");
     Services.prefs.setIntPref(PREF_NAV_WIDTH, sidebarWidth);
 
     this._optionsMenu.removeEventListener("popupshowing",
                                           this._onOptionsPopupShowing);
     this._optionsMenu.removeEventListener("popuphiding",
                                           this._onOptionsPopupHiding);
 
     this._prefObserver.off(PREF_ORIG_SOURCES, this._onNewDocument);
     this._prefObserver.off(PREF_MEDIA_SIDEBAR, this._onMediaPrefChanged);
     this._prefObserver.destroy();
+
+    this._debuggee.off("stylesheet-added", this._addStyleSheet);
   }
 };
--- a/devtools/client/styleeditor/test/browser.ini
+++ b/devtools/client/styleeditor/test/browser.ini
@@ -56,16 +56,17 @@ support-files =
   !/devtools/client/framework/test/shared-head.js
   !/devtools/client/inspector/shared/test/head.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/responsive.html/test/browser/devices.json
   !/devtools/client/shared/test/test-actor-registry.js
   !/devtools/client/shared/test/test-actor.js
 
+[browser_styleeditor_add_stylesheet.js]
 [browser_styleeditor_autocomplete.js]
 [browser_styleeditor_autocomplete-disabled.js]
 [browser_styleeditor_bom.js]
 [browser_styleeditor_bug_740541_iframes.js]
 [browser_styleeditor_bug_851132_middle_click.js]
 [browser_styleeditor_bug_870339.js]
 [browser_styleeditor_cmd_edit.js]
 [browser_styleeditor_enabled.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/styleeditor/test/browser_styleeditor_add_stylesheet.js
@@ -0,0 +1,37 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that a newly-added style sheet shows up in the style editor.
+
+const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html";
+
+add_task(function* () {
+  let { ui } = yield openStyleEditorForURL(TESTCASE_URI);
+
+  is(ui.editors.length, 2, "Two sheets present after load.");
+
+  // We have to wait for the length to change, because we might still
+  // be seeing events from the initial open.
+  let added = new Promise(resolve => {
+    let handler = () => {
+      if (ui.editors.length === 3) {
+        ui.off("editor-added", handler);
+        resolve();
+      }
+    };
+    ui.on("editor-added", handler);
+  });
+
+  info("Adding a style sheet");
+  yield ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
+    let document = content.document;
+    const style = document.createElement("style");
+    style.appendChild(document.createTextNode("div { background: #f06; }"));
+    document.head.appendChild(style);
+  });
+  yield added;
+
+  is(ui.editors.length, 3, "Three sheets present after new style sheet");
+});
--- a/devtools/client/styleeditor/test/browser_styleeditor_import.js
+++ b/devtools/client/styleeditor/test/browser_styleeditor_import.js
@@ -13,17 +13,17 @@ Components.utils.import("resource://gre/
 var FileUtils = tempScope.FileUtils;
 
 const FILENAME = "styleeditor-import-test.css";
 const SOURCE = "body{background:red;}";
 
 add_task(function* () {
   let { panel, ui } = yield openStyleEditorForURL(TESTCASE_URI);
 
-  let added = ui.once("editor-added");
+  let added = ui.once("test:editor-updated");
   importSheet(ui, panel.panelWindow);
 
   info("Waiting for editor to be added for the imported sheet.");
   let editor = yield added;
 
   is(editor.savedFile.leafName, FILENAME,
      "imported stylesheet will be saved directly into the same file");
   is(editor.friendlyName, FILENAME,
--- a/devtools/install.rdf
+++ b/devtools/install.rdf
@@ -4,25 +4,26 @@
 # 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/.
 -->
 
 <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
      xmlns:em="http://www.mozilla.org/2004/em-rdf#">
   <Description about="urn:mozilla:install-manifest"
                em:id="devtools@mozilla.org"
-               em:name="Developer Tools (local version)"
-               em:description="Add-on to load DevTools from local sources and easily reload them with Ctrl+Alt+r shortcut"
+               em:name="Developer Tools"
+               em:description="System-addon for Firefox DevTools"
                em:version="44.0a1"
                em:type="2"
                em:creator="Mozilla">
 
     <em:bootstrap>true</em:bootstrap>
     <em:multiprocessCompatible>true</em:multiprocessCompatible>
+    <!-- Valid for all toolkit applications -->
     <em:targetApplication>
       <Description>
-        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
-        <em:minVersion>44.0a1</em:minVersion>
+        <em:id>toolkit@mozilla.org</em:id>
+        <em:minVersion>57.0a1</em:minVersion>
         <em:maxVersion>*</em:maxVersion>
       </Description>
     </em:targetApplication>
   </Description>
 </RDF>
--- a/devtools/server/actors/highlighters.js
+++ b/devtools/server/actors/highlighters.js
@@ -5,20 +5,22 @@
 "use strict";
 
 const { Ci, Cu } = require("chrome");
 
 const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
 const EventEmitter = require("devtools/shared/old-event-emitter");
 const protocol = require("devtools/shared/protocol");
 const Services = require("Services");
-const { isWindowIncluded } = require("devtools/shared/layout/utils");
 const { highlighterSpec, customHighlighterSpec } = require("devtools/shared/specs/highlighters");
-const { isXUL } = require("./highlighters/utils/markup");
-const { SimpleOutlineHighlighter } = require("./highlighters/simple-outline");
+
+loader.lazyRequireGetter(this, "isWindowIncluded", "devtools/shared/layout/utils", true);
+loader.lazyRequireGetter(this, "isXUL", "devtools/server/actors/highlighters/utils/markup", true);
+loader.lazyRequireGetter(this, "SimpleOutlineHighlighter", "devtools/server/actors/highlighters/simple-outline", true);
+loader.lazyRequireGetter(this, "BoxModelHighlighter", "devtools/server/actors/highlighters/box-model", true);
 
 const HIGHLIGHTER_PICKED_TIMER = 1000;
 const IS_OSX = Services.appinfo.OS === "Darwin";
 
 /**
  * The registration mechanism for highlighters provide a quick way to
  * have modular highlighters, instead of a hard coded list.
  * It allow us to split highlighers in sub modules, and add them dynamically
@@ -36,26 +38,22 @@ const highlighterTypes = new Map();
 const isTypeRegistered = (typeName) => highlighterTypes.has(typeName);
 exports.isTypeRegistered = isTypeRegistered;
 
 /**
  * Registers a given constructor as highlighter, for the `typeName` given.
  * If no `typeName` is provided, the `typeName` property on the constructor's prototype
  * is used, if one is found, otherwise the name of the constructor function is used.
  */
-const register = (constructor, typeName) => {
-  if (!typeName) {
-    typeName = constructor.prototype.typeName || constructor.name;
-  }
-
+const register = (typeName, modulePath) => {
   if (highlighterTypes.has(typeName)) {
     throw Error(`${typeName} is already registered.`);
   }
 
-  highlighterTypes.set(typeName, constructor);
+  highlighterTypes.set(typeName, modulePath);
 };
 exports.register = register;
 
 /**
  * The Highlighter is the server-side entry points for any tool that wishes to
  * highlight elements in some way in the content document.
  *
  * A little bit of vocabulary:
@@ -440,28 +438,29 @@ exports.CustomHighlighterActor = protoco
    * The typename must be one of HIGHLIGHTER_CLASSES and the class must
    * implement constructor(tabActor), show(node), hide(), destroy()
    */
   initialize: function (inspector, typeName) {
     protocol.Actor.prototype.initialize.call(this, null);
 
     this._inspector = inspector;
 
-    let constructor = highlighterTypes.get(typeName);
-    if (!constructor) {
+    let modulePath = highlighterTypes.get(typeName);
+    if (!modulePath) {
       let list = [...highlighterTypes.keys()];
 
       throw new Error(`${typeName} isn't a valid highlighter class (${list})`);
     }
 
     // The assumption is that all custom highlighters need the canvasframe
     // container to append their elements, so if this is a XUL window, bail out.
     if (!isXUL(this._inspector.tabActor.window)) {
       this._highlighterEnv = new HighlighterEnvironment();
       this._highlighterEnv.initFromTabActor(inspector.tabActor);
+      let constructor = require("./highlighters/" + modulePath)[typeName];
       this._highlighter = new constructor(this._highlighterEnv);
       if (this._highlighter.on) {
         this._highlighter.on("highlighter-event", this._onHighlighterEvent.bind(this));
       }
     } else {
       throw new Error("Custom " + typeName +
         "highlighter cannot be created in a XUL window");
     }
@@ -700,47 +699,18 @@ HighlighterEnvironment.prototype = {
       }
     }
 
     this._tabActor = null;
     this._win = null;
   }
 };
 
-const { BoxModelHighlighter } = require("./highlighters/box-model");
-register(BoxModelHighlighter);
-exports.BoxModelHighlighter = BoxModelHighlighter;
-
-const { CssGridHighlighter } = require("./highlighters/css-grid");
-register(CssGridHighlighter);
-exports.CssGridHighlighter = CssGridHighlighter;
-
-const { CssTransformHighlighter } = require("./highlighters/css-transform");
-register(CssTransformHighlighter);
-exports.CssTransformHighlighter = CssTransformHighlighter;
-
-const { SelectorHighlighter } = require("./highlighters/selector");
-register(SelectorHighlighter);
-exports.SelectorHighlighter = SelectorHighlighter;
-
-const { GeometryEditorHighlighter } = require("./highlighters/geometry-editor");
-register(GeometryEditorHighlighter);
-exports.GeometryEditorHighlighter = GeometryEditorHighlighter;
-
-const { RulersHighlighter } = require("./highlighters/rulers");
-register(RulersHighlighter);
-exports.RulersHighlighter = RulersHighlighter;
-
-const { MeasuringToolHighlighter } = require("./highlighters/measuring-tool");
-register(MeasuringToolHighlighter);
-exports.MeasuringToolHighlighter = MeasuringToolHighlighter;
-
-const { EyeDropper } = require("./highlighters/eye-dropper");
-register(EyeDropper);
-exports.EyeDropper = EyeDropper;
-
-const { PausedDebuggerOverlay } = require("./highlighters/paused-debugger");
-register(PausedDebuggerOverlay);
-exports.PausedDebuggerOverlay = PausedDebuggerOverlay;
-
-const { ShapesHighlighter } = require("./highlighters/shapes");
-register(ShapesHighlighter);
-exports.ShapesHighlighter = ShapesHighlighter;
+register("BoxModelHighlighter", "box-model");
+register("CssGridHighlighter", "css-grid");
+register("CssTransformHighlighter", "css-transform");
+register("SelectorHighlighter", "selector");
+register("GeometryEditorHighlighter", "geometry-editor");
+register("RulersHighlighter", "rulers");
+register("MeasuringToolHighlighter", "measuring-tool");
+register("EyeDropper", "eye-dropper");
+register("PausedDebuggerOverlay", "paused-debugger");
+register("ShapesHighlighter", "shapes");
--- a/devtools/server/actors/inspector.js
+++ b/devtools/server/actors/inspector.js
@@ -48,46 +48,58 @@
  * So to be able to answer "all the children of a given node that we have
  * seen on the client side", we guarantee that every time we've seen a node,
  * we connect it up through its parents.
  */
 
 const {Cc, Ci, Cu} = require("chrome");
 const Services = require("Services");
 const protocol = require("devtools/shared/protocol");
-const {LayoutActor} = require("devtools/server/actors/layout");
 const {LongStringActor} = require("devtools/server/actors/string");
 const promise = require("promise");
 const defer = require("devtools/shared/defer");
 const {Task} = require("devtools/shared/task");
 const EventEmitter = require("devtools/shared/event-emitter");
-const {WalkerSearch} = require("devtools/server/actors/utils/walker-search");
-const {PageStyleActor, getFontPreviewData} = require("devtools/server/actors/styles");
-const {
-  HighlighterActor,
-  CustomHighlighterActor,
-  isTypeRegistered,
-  HighlighterEnvironment
-} = require("devtools/server/actors/highlighters");
-const {EyeDropper} = require("devtools/server/actors/highlighters/eye-dropper");
-const {
-  isAnonymous,
-  isNativeAnonymous,
-  isXBLAnonymous,
-  isShadowAnonymous,
-  getFrameElement,
-  loadSheet
-} = require("devtools/shared/layout/utils");
-const {getLayoutChangesObserver, releaseLayoutChangesObserver} = require("devtools/server/actors/reflow");
-const nodeFilterConstants = require("devtools/shared/dom-node-filter-constants");
-const {colorUtils} = require("devtools/shared/css/color");
-
-const {EventParsers} = require("devtools/server/event-parsers");
+
 const {nodeSpec, nodeListSpec, walkerSpec, inspectorSpec} = require("devtools/shared/specs/inspector");
 
+loader.lazyRequireGetter(this, "DevToolsUtils", "devtools/shared/DevToolsUtils");
+loader.lazyRequireGetter(this, "AsyncUtils", "devtools/shared/async-utils");
+loader.lazyRequireGetter(this, "CssLogic", "devtools/server/css-logic", true);
+loader.lazyRequireGetter(this, "findCssSelector", "devtools/shared/inspector/css-logic", true);
+loader.lazyRequireGetter(this, "getCssPath", "devtools/shared/inspector/css-logic", true);
+loader.lazyRequireGetter(this, "getXPath", "devtools/shared/inspector/css-logic", true);
+loader.lazyRequireGetter(this, "colorUtils", "devtools/shared/css/color", true);
+loader.lazyRequireGetter(this, "EyeDropper", "devtools/server/actors/highlighters/eye-dropper", true);
+loader.lazyRequireGetter(this, "WalkerSearch", "devtools/server/actors/utils/walker-search", true);
+loader.lazyRequireGetter(this, "PageStyleActor", "devtools/server/actors/styles", true);
+loader.lazyRequireGetter(this, "getFontPreviewData", "devtools/server/actors/styles", true);
+loader.lazyRequireGetter(this, "flags", "devtools/shared/flags");
+loader.lazyRequireGetter(this, "LayoutActor", "devtools/server/actors/layout", true);
+loader.lazyRequireGetter(this, "HighlighterActor", "devtools/server/actors/highlighters", true);
+loader.lazyRequireGetter(this, "CustomHighlighterActor", "devtools/server/actors/highlighters", true);
+loader.lazyRequireGetter(this, "isTypeRegistered", "devtools/server/actors/highlighters", true);
+loader.lazyRequireGetter(this, "HighlighterEnvironment", "devtools/server/actors/highlighters", true);
+loader.lazyRequireGetter(this, "EventParsers", "devtools/server/event-parsers", true);
+loader.lazyRequireGetter(this, "isAnonymous", "devtools/shared/layout/utils", true);
+loader.lazyRequireGetter(this, "isNativeAnonymous", "devtools/shared/layout/utils", true);
+loader.lazyRequireGetter(this, "isXBLAnonymous", "devtools/shared/layout/utils", true);
+loader.lazyRequireGetter(this, "isShadowAnonymous", "devtools/shared/layout/utils", true);
+loader.lazyRequireGetter(this, "getFrameElement", "devtools/shared/layout/utils", true);
+loader.lazyRequireGetter(this, "loadSheet", "devtools/shared/layout/utils", true);
+loader.lazyRequireGetter(this, "getLayoutChangesObserver", "devtools/server/actors/reflow", true);
+loader.lazyRequireGetter(this, "releaseLayoutChangesObserver", "devtools/server/actors/reflow", true);
+loader.lazyRequireGetter(this, "nodeFilterConstants", "devtools/shared/dom-node-filter-constants");
+
+loader.lazyServiceGetter(this, "DOMParser",
+  "@mozilla.org/xmlextras/domparser;1", "nsIDOMParser");
+
+loader.lazyServiceGetter(this, "eventListenerService",
+  "@mozilla.org/eventlistenerservice;1", "nsIEventListenerService");
+
 const FONT_FAMILY_PREVIEW_TEXT = "The quick brown fox jumps over the lazy dog";
 const FONT_FAMILY_PREVIEW_TEXT_SIZE = 20;
 const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
 const HIDDEN_CLASS = "__fx-devtools-hide-shortcut__";
 const SVG_NS = "http://www.w3.org/2000/svg";
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const IMAGE_FETCHING_TIMEOUT = 500;
@@ -135,38 +147,16 @@ var HELPER_SHEET = "data:text/css;charse
   }
 
   :-moz-devtools-highlighted {
     outline: 2px dashed #F06!important;
     outline-offset: -2px !important;
   }
 `);
 
-const flags = require("devtools/shared/flags");
-
-loader.lazyRequireGetter(this, "DevToolsUtils",
-                         "devtools/shared/DevToolsUtils");
-
-loader.lazyRequireGetter(this, "AsyncUtils", "devtools/shared/async-utils");
-
-loader.lazyGetter(this, "DOMParser", function () {
-  return Cc["@mozilla.org/xmlextras/domparser;1"]
-           .createInstance(Ci.nsIDOMParser);
-});
-
-loader.lazyGetter(this, "eventListenerService", function () {
-  return Cc["@mozilla.org/eventlistenerservice;1"]
-           .getService(Ci.nsIEventListenerService);
-});
-
-loader.lazyRequireGetter(this, "CssLogic", "devtools/server/css-logic", true);
-loader.lazyRequireGetter(this, "findCssSelector", "devtools/shared/inspector/css-logic", true);
-loader.lazyRequireGetter(this, "getCssPath", "devtools/shared/inspector/css-logic", true);
-loader.lazyRequireGetter(this, "getXPath", "devtools/shared/inspector/css-logic", true);
-
 /**
  * We only send nodeValue up to a certain size by default.  This stuff
  * controls that size.
  */
 exports.DEFAULT_VALUE_SUMMARY_LENGTH = 50;
 var gValueSummaryLength = exports.DEFAULT_VALUE_SUMMARY_LENGTH;
 
 exports.getValueSummaryLength = function () {
--- a/devtools/server/actors/styles.js
+++ b/devtools/server/actors/styles.js
@@ -1,30 +1,38 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const {Cc, Ci} = require("chrome");
+const {Ci} = require("chrome");
 const promise = require("promise");
 const protocol = require("devtools/shared/protocol");
 const {LongStringActor} = require("devtools/server/actors/string");
-const {getDefinedGeometryProperties} = require("devtools/server/actors/highlighters/geometry-editor");
-const {parseNamedDeclarations} = require("devtools/shared/css/parsing-utils");
-const {isCssPropertyKnown} = require("devtools/server/actors/css-properties");
 const {Task} = require("devtools/shared/task");
 
 // This will also add the "stylesheet" actor type for protocol.js to recognize
-const {UPDATE_PRESERVING_RULES, UPDATE_GENERAL} = require("devtools/server/actors/stylesheets");
+
 const {pageStyleSpec, styleRuleSpec, ELEMENT_STYLE} = require("devtools/shared/specs/styles");
 
-loader.lazyGetter(this, "CssLogic", () => require("devtools/server/css-logic").CssLogic);
-loader.lazyGetter(this, "SharedCssLogic", () => require("devtools/shared/inspector/css-logic"));
-loader.lazyGetter(this, "DOMUtils", () => Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils));
+loader.lazyRequireGetter(this, "CssLogic", "devtools/server/css-logic", true);
+loader.lazyRequireGetter(this, "SharedCssLogic", "devtools/shared/inspector/css-logic");
+loader.lazyRequireGetter(this, "getDefinedGeometryProperties",
+  "devtools/server/actors/highlighters/geometry-editor", true);
+loader.lazyRequireGetter(this, "isCssPropertyKnown",
+  "devtools/server/actors/css-properties", true);
+loader.lazyRequireGetter(this, "parseNamedDeclarations",
+  "devtools/shared/css/parsing-utils", true);
+loader.lazyRequireGetter(this, "UPDATE_PRESERVING_RULES",
+  "devtools/server/actors/stylesheets", true);
+loader.lazyRequireGetter(this, "UPDATE_GENERAL",
+  "devtools/server/actors/stylesheets", true);
+
+loader.lazyServiceGetter(this, "DOMUtils", "@mozilla.org/inspector/dom-utils;1", "inIDOMUtils");
 
 loader.lazyGetter(this, "PSEUDO_ELEMENTS", () => {
   return DOMUtils.getCSSPseudoElementNames();
 });
 
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 const FONT_PREVIEW_TEXT = "Abc";
 const FONT_PREVIEW_FONT_SIZE = 40;
--- a/devtools/server/actors/stylesheets.js
+++ b/devtools/server/actors/stylesheets.js
@@ -1,35 +1,35 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const {Cc, Ci} = require("chrome");
+const {Ci} = require("chrome");
 const Services = require("Services");
-const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
 const promise = require("promise");
 const defer = require("devtools/shared/defer");
 const {Task} = require("devtools/shared/task");
 const protocol = require("devtools/shared/protocol");
 const {LongStringActor} = require("devtools/server/actors/string");
 const {fetch} = require("devtools/shared/DevToolsUtils");
-const {listenOnce} = require("devtools/shared/async-utils");
 const {originalSourceSpec, mediaRuleSpec, styleSheetSpec,
        styleSheetsSpec} = require("devtools/shared/specs/stylesheets");
 const {SourceMapConsumer} = require("source-map");
 const {
   addPseudoClassLock, removePseudoClassLock } = require("devtools/server/actors/highlighters/utils/markup");
 
-loader.lazyGetter(this, "CssLogic", () => require("devtools/shared/inspector/css-logic"));
+loader.lazyRequireGetter(this, "CssLogic", "devtools/shared/inspector/css-logic");
+loader.lazyRequireGetter(this, "addPseudoClassLock",
+  "devtools/server/actors/highlighters/utils/markup", true);
+loader.lazyRequireGetter(this, "removePseudoClassLock",
+  "devtools/server/actors/highlighters/utils/markup", true);
 
-XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
-  return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
-});
+loader.lazyServiceGetter(this, "DOMUtils", "@mozilla.org/inspector/dom-utils;1", "inIDOMUtils");
 
 var TRANSITION_PSEUDO_CLASS = ":-moz-styleeditor-transitioning";
 var TRANSITION_DURATION_MS = 500;
 var TRANSITION_BUFFER_MS = 1000;
 var TRANSITION_RULE_SELECTOR =
 `:root${TRANSITION_PSEUDO_CLASS}, :root${TRANSITION_PSEUDO_CLASS} *`;
 var TRANSITION_RULE = `${TRANSITION_RULE_SELECTOR} {
   transition-duration: ${TRANSITION_DURATION_MS}ms !important;
@@ -243,17 +243,17 @@ var StyleSheetActor = protocol.ActorClas
           break;
         }
       }
     }
     return this._styleSheetIndex;
   },
 
   destroy: function () {
-    if (this._transitionTimeout) {
+    if (this._transitionTimeout && this.window) {
       this.window.clearTimeout(this._transitionTimeout);
       removePseudoClassLock(
                    this.document.documentElement, TRANSITION_PSEUDO_CLASS);
     }
   },
 
   initialize: function (styleSheet, parentActor, window) {
     protocol.Actor.prototype.initialize.call(this, null);
@@ -791,97 +791,149 @@ var StyleSheetsActor = protocol.ActorCla
   form: function () {
     return { actor: this.actorID };
   },
 
   initialize: function (conn, tabActor) {
     protocol.Actor.prototype.initialize.call(this, null);
 
     this.parentActor = tabActor;
+
+    this._onNewStyleSheetActor = this._onNewStyleSheetActor.bind(this);
+    this._onSheetAdded = this._onSheetAdded.bind(this);
+    this._onWindowReady = this._onWindowReady.bind(this);
+
+    this.parentActor.on("stylesheet-added", this._onNewStyleSheetActor);
+    this.parentActor.on("window-ready", this._onWindowReady);
+
+    // We listen for StyleSheetApplicableStateChanged rather than
+    // StyleSheetAdded, because the latter will be sent before the
+    // rules are ready.  Using the former (with a check to ensure that
+    // the sheet is enabled) ensures that the sheet is ready before we
+    // try to make an actor for it.
+    this.parentActor.chromeEventHandler
+      .addEventListener("StyleSheetApplicableStateChanged", this._onSheetAdded, true);
+
+    // This is used when creating a new style sheet, so that we can
+    // pass the correct flag when emitting our stylesheet-added event.
+    // See addStyleSheet and _onNewStyleSheetActor for more details.
+    this._nextStyleSheetIsNew = false;
+  },
+
+  destroy: function () {
+    for (let win of this.parentActor.windows) {
+      // This flag only exists for devtools, so we are free to clear
+      // it when we're done.
+      win.document.styleSheetChangeEventsEnabled = false;
+    }
+
+    this.parentActor.off("stylesheet-added", this._onNewStyleSheetActor);
+    this.parentActor.off("window-ready", this._onWindowReady);
+
+    this.parentActor.chromeEventHandler.removeEventListener("StyleSheetAdded",
+                                                            this._onSheetAdded, true);
+
+    protocol.Actor.prototype.destroy.call(this);
+  },
+
+  /**
+   * Event handler that is called when a the tab actor emits window-ready.
+   *
+   * @param {Event} evt
+   *        The triggering event.
+   */
+  _onWindowReady: function (evt) {
+    this._addStyleSheets(evt.window);
+  },
+
+  /**
+   * Event handler that is called when a the tab actor emits stylesheet-added.
+   *
+   * @param {StyleSheetActor} actor
+   *        The new style sheet actor.
+   */
+  _onNewStyleSheetActor: function (actor) {
+    // Forward it to the client side.
+    this.emit("stylesheet-added", actor, this._nextStyleSheetIsNew);
+    this._nextStyleSheetIsNew = false;
   },
 
   /**
    * Protocol method for getting a list of StyleSheetActors representing
    * all the style sheets in this document.
    */
   getStyleSheets: Task.async(function* () {
-    // Iframe document can change during load (bug 1171919). Track their windows
-    // instead.
-    let windows = [this.window];
     let actors = [];
 
-    for (let win of windows) {
+    for (let win of this.parentActor.windows) {
       let sheets = yield this._addStyleSheets(win);
       actors = actors.concat(sheets);
-
-      // Recursively handle style sheets of the documents in iframes.
-      for (let iframe of win.document.querySelectorAll("iframe, browser, frame")) {
-        if (iframe.contentDocument && iframe.contentWindow) {
-          // Sometimes, iframes don't have any document, like the
-          // one that are over deeply nested (bug 285395)
-          windows.push(iframe.contentWindow);
-        }
-      }
     }
     return actors;
   }),
 
   /**
    * Check if we should be showing this stylesheet.
    *
-   * @param {Document} doc
-   *        Document for which we're checking
    * @param {DOMCSSStyleSheet} sheet
    *        Stylesheet we're interested in
    *
    * @return boolean
    *         Whether the stylesheet should be listed.
    */
-  _shouldListSheet: function (doc, sheet) {
+  _shouldListSheet: function (sheet) {
     // Special case about:PreferenceStyleSheet, as it is generated on the
     // fly and the URI is not registered with the about: handler.
     // https://bugzilla.mozilla.org/show_bug.cgi?id=935803#c37
     if (sheet.href && sheet.href.toLowerCase() == "about:preferencestylesheet") {
       return false;
     }
 
     return true;
   },
 
   /**
+   * Event handler that is called when a new style sheet is added to
+   * a document.  In particular,  StyleSheetApplicableStateChanged is
+   * listened for, because StyleSheetAdded is sent too early, before
+   * the rules are ready.
+   *
+   * @param {Event} evt
+   *        The triggering event.
+   */
+  _onSheetAdded: function (evt) {
+    let sheet = evt.stylesheet;
+    if (this._shouldListSheet(sheet) && !this._haveAncestorWithSameURL(sheet)) {
+      this.parentActor.createStyleSheetActor(sheet);
+    }
+  },
+
+  /**
    * Add all the stylesheets for the document in this window to the map and
    * create an actor for each one if not already created.
    *
    * @param {Window} win
    *        Window for which to add stylesheets
    *
    * @return {Promise}
    *         Promise that resolves to an array of StyleSheetActors
    */
   _addStyleSheets: function (win) {
     return Task.spawn(function* () {
       let doc = win.document;
-      // readyState can be uninitialized if an iframe has just been created but
-      // it has not started to load yet.
-      if (doc.readyState === "loading" || doc.readyState === "uninitialized") {
-        // Wait for the document to load first.
-        yield listenOnce(win, "DOMContentLoaded", true);
-
-        // Make sure we have the actual document for this window. If the
-        // readyState was initially uninitialized, the initial dummy document
-        // was replaced with the actual document (bug 1171919).
-        doc = win.document;
-      }
+      // We have to set this flag in order to get the
+      // StyleSheetApplicableStateChanged events.  See Document.webidl.
+      doc.styleSheetChangeEventsEnabled = true;
 
       let isChrome = Services.scriptSecurityManager.isSystemPrincipal(doc.nodePrincipal);
       let styleSheets = isChrome ? DOMUtils.getAllStyleSheets(doc) : doc.styleSheets;
       let actors = [];
       for (let i = 0; i < styleSheets.length; i++) {
         let sheet = styleSheets[i];
-        if (!this._shouldListSheet(doc, sheet)) {
+        if (!this._shouldListSheet(sheet)) {
           continue;
         }
 
         let actor = this.parentActor.createStyleSheetActor(sheet);
         actors.push(actor);
 
         // Get all sheets, including imported ones
         let imports = yield this._getImported(doc, actor);
@@ -911,17 +963,17 @@ var StyleSheetsActor = protocol.ActorCla
         if (rule.type == Ci.nsIDOMCSSRule.IMPORT_RULE) {
           // With the Gecko style system, the associated styleSheet may be null
           // if it has already been seen because an import cycle for the same
           // URL.  With Stylo, the styleSheet will exist (which is correct per
           // the latest CSSOM spec), so we also need to check ancestors for the
           // same URL to avoid cycles.
           let sheet = rule.styleSheet;
           if (!sheet || this._haveAncestorWithSameURL(sheet) ||
-              !this._shouldListSheet(doc, sheet)) {
+              !this._shouldListSheet(sheet)) {
             continue;
           }
           let actor = this.parentActor.createStyleSheetActor(rule.styleSheet);
           imported.push(actor);
 
           // recurse imports in this stylesheet as well
           let children = yield this._getImported(doc, actor);
           imported = imported.concat(children);
@@ -957,16 +1009,23 @@ var StyleSheetsActor = protocol.ActorCla
    * Return an actor for it.
    *
    * @param  {object} request
    *         Debugging protocol request object, with 'text property'
    * @return {object}
    *         Object with 'styelSheet' property for form on new actor.
    */
   addStyleSheet: function (text) {
+    // This is a bit convoluted.  The style sheet actor may be created
+    // by a notification from platform.  In this case, we can't easily
+    // pass the "new" flag through to createStyleSheetActor, so we set
+    // a flag locally and check it before sending an event to the
+    // client.  See |_onNewStyleSheetActor|.
+    this._nextStyleSheetIsNew = true;
+
     let parent = this.document.documentElement;
     let style = this.document.createElementNS("http://www.w3.org/1999/xhtml", "style");
     style.setAttribute("type", "text/css");
 
     if (text) {
       style.appendChild(this.document.createTextNode(text));
     }
     parent.appendChild(style);
--- a/devtools/shared/fronts/css-properties.js
+++ b/devtools/shared/fronts/css-properties.js
@@ -1,20 +1,22 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 loader.lazyRequireGetter(this, "CSS_PROPERTIES_DB",
   "devtools/shared/css/properties-db", true);
 
+loader.lazyRequireGetter(this, "cssColors",
+  "devtools/shared/css/color-db", true);
+
 const { FrontClassWithSpec, Front } = require("devtools/shared/protocol");
 const { cssPropertiesSpec } = require("devtools/shared/specs/css-properties");
 const { Task } = require("devtools/shared/task");
-const { cssColors } = require("devtools/shared/css/color-db");
 
 /**
  * Build up a regular expression that matches a CSS variable token. This is an
  * ident token that starts with two dashes "--".
  *
  * https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
  */
 var NON_ASCII = "[^\\x00-\\x7F]";
--- a/devtools/shared/fronts/inspector.js
+++ b/devtools/shared/fronts/inspector.js
@@ -18,17 +18,18 @@ const {
   inspectorSpec,
   nodeSpec,
   nodeListSpec,
   walkerSpec
 } = require("devtools/shared/specs/inspector");
 const promise = require("promise");
 const defer = require("devtools/shared/defer");
 const { Task } = require("devtools/shared/task");
-const nodeConstants = require("devtools/shared/dom-node-constants.js");
+loader.lazyRequireGetter(this, "nodeConstants",
+  "devtools/shared/dom-node-constants");
 loader.lazyRequireGetter(this, "CommandUtils",
   "devtools/client/shared/developer-toolbar", true);
 
 const HIDDEN_CLASS = "__fx-devtools-hide-shortcut__";
 
 /**
  * Convenience API for building a list of attribute modifications
  * for the `modifyAttributes` request.
--- a/devtools/shared/fronts/styles.js
+++ b/devtools/shared/fronts/styles.js
@@ -11,17 +11,19 @@ const {
   preEvent
 } = require("devtools/shared/protocol");
 const {
   pageStyleSpec,
   styleRuleSpec
 } = require("devtools/shared/specs/styles");
 const promise = require("promise");
 const { Task } = require("devtools/shared/task");
-const { RuleRewriter } = require("devtools/shared/css/parsing-utils");
+
+loader.lazyRequireGetter(this, "RuleRewriter",
+  "devtools/shared/css/parsing-utils", true);
 
 /**
  * PageStyleFront, the front object for the PageStyleActor
  */
 const PageStyleFront = FrontClassWithSpec(pageStyleSpec, {
   initialize: function (conn, form, ctx, detail) {
     Front.prototype.initialize.call(this, conn, form, ctx, detail);
     this.inspector = this.parent();
--- a/devtools/shared/fronts/stylesheets.js
+++ b/devtools/shared/fronts/stylesheets.js
@@ -1,27 +1,28 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { Front, FrontClassWithSpec } = require("devtools/shared/protocol");
 const {
-  getIndentationFromPrefs,
-  getIndentationFromString
-} = require("devtools/shared/indentation");
-const {
   originalSourceSpec,
   mediaRuleSpec,
   styleSheetSpec,
   styleSheetsSpec
 } = require("devtools/shared/specs/stylesheets");
 const promise = require("promise");
 const { Task } = require("devtools/shared/task");
 
+loader.lazyRequireGetter(this, "getIndentationFromPrefs",
+  "devtools/shared/indentation", true);
+loader.lazyRequireGetter(this, "getIndentationFromString",
+  "devtools/shared/indentation", true);
+
 /**
  * The client-side counterpart for an OriginalSourceActor.
  */
 const OriginalSourceFront = FrontClassWithSpec(originalSourceSpec, {
   initialize: function (client, form) {
     Front.prototype.initialize.call(this, client, form);
 
     this.isOriginalSource = true;
--- a/devtools/shared/fronts/webaudio.js
+++ b/devtools/shared/fronts/webaudio.js
@@ -6,17 +6,18 @@
 const {
   audionodeSpec,
   webAudioSpec,
   AUTOMATION_METHODS,
   NODE_CREATION_METHODS,
   NODE_ROUTING_METHODS,
 } = require("devtools/shared/specs/webaudio");
 const protocol = require("devtools/shared/protocol");
-const AUDIO_NODE_DEFINITION = require("devtools/server/actors/utils/audionodes.json");
+loader.lazyRequireGetter(this, "AUDIO_NODE_DEFINITION",
+  "devtools/server/actors/utils/audionodes.json");
 
 /**
  * The corresponding Front object for the AudioNodeActor.
  *
  * @attribute {String} type
  *            The type of audio node, like "OscillatorNode", "MediaElementAudioSourceNode"
  * @attribute {Boolean} source
  *            Boolean indicating if the node is a source node, like BufferSourceNode,
--- a/devtools/shared/gcli/commands/highlight.js
+++ b/devtools/shared/gcli/commands/highlight.js
@@ -1,20 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const l10n = require("gcli/l10n");
 require("devtools/server/actors/inspector");
-const {
-  BoxModelHighlighter,
-  HighlighterEnvironment
-} = require("devtools/server/actors/highlighters");
+const {HighlighterEnvironment}  = require("devtools/server/actors/highlighters");
+const {BoxModelHighlighter} = require("devtools/server/actors/highlighters/box-model");
 
 const {PluralForm} = require("devtools/shared/plural-form");
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper("devtools/shared/locales/gclicommands.properties");
 
 // How many maximum nodes can be highlighted in parallel
 const MAX_HIGHLIGHTED_ELEMENTS = 100;
 
--- a/devtools/shared/gcli/commands/measure.js
+++ b/devtools/shared/gcli/commands/measure.js
@@ -6,18 +6,20 @@
 
 const EventEmitter = require("devtools/shared/event-emitter");
 
 loader.lazyRequireGetter(this, "CommandState",
   "devtools/shared/gcli/command-state", true);
 
 const l10n = require("gcli/l10n");
 require("devtools/server/actors/inspector");
-const { MeasuringToolHighlighter, HighlighterEnvironment } =
+const { HighlighterEnvironment } =
   require("devtools/server/actors/highlighters");
+const { MeasuringToolHighlighter } =
+  require("devtools/server/actors/highlighters/measuring-tool");
 
 const highlighters = new WeakMap();
 
 exports.items = [
   // The client measure command is used to maintain the toolbar button state
   // only and redirects to the server command to actually toggle the measuring
   // tool (see `measure_server` below).
   {
--- a/devtools/shared/gcli/commands/rulers.js
+++ b/devtools/shared/gcli/commands/rulers.js
@@ -6,18 +6,20 @@
 
 const EventEmitter = require("devtools/shared/event-emitter");
 
 loader.lazyRequireGetter(this, "CommandState",
   "devtools/shared/gcli/command-state", true);
 
 const l10n = require("gcli/l10n");
 require("devtools/server/actors/inspector");
-const { RulersHighlighter, HighlighterEnvironment } =
+const { HighlighterEnvironment } =
   require("devtools/server/actors/highlighters");
+const { RulersHighlighter } =
+  require("devtools/server/actors/highlighters/rulers");
 
 const highlighters = new WeakMap();
 
 exports.items = [
   // The client rulers command is used to maintain the toolbar button state only
   // and redirects to the server command to actually toggle the rulers (see
   // rulers_server below).
   {
--- a/devtools/shared/specs/stylesheets.js
+++ b/devtools/shared/specs/stylesheets.js
@@ -100,16 +100,24 @@ const styleSheetSpec = generateActorSpec
   }
 });
 
 exports.styleSheetSpec = styleSheetSpec;
 
 const styleSheetsSpec = generateActorSpec({
   typeName: "stylesheets",
 
+  events: {
+    "stylesheet-added": {
+      type: "stylesheetAdded",
+      sheet: Arg(0, "stylesheet"),
+      isNew: Arg(1, "boolean")
+    },
+  },
+
   methods: {
     getStyleSheets: {
       request: {},
       response: { styleSheets: RetVal("array:stylesheet") }
     },
     addStyleSheet: {
       request: { text: Arg(0, "string") },
       response: { styleSheet: RetVal("stylesheet") }
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -2889,17 +2889,19 @@ EventStateManager::PostHandleKeyboardEve
           return;
         }
       }
     }
     // The widget expects a reply for every keyboard event. If the event wasn't
     // dispatched to a content process (non-e10s or no content process
     // running), we need to short-circuit here. Otherwise, we need to wait for
     // the content process to handle the event.
-    aKeyboardEvent->mWidget->PostHandleKeyEvent(aKeyboardEvent);
+    if (aKeyboardEvent->mWidget) {
+      aKeyboardEvent->mWidget->PostHandleKeyEvent(aKeyboardEvent);
+    }
     if (aKeyboardEvent->DefaultPrevented()) {
       aStatus = nsEventStatus_eConsumeNoDefault;
       return;
     }
   }
 
   // XXX Currently, our automated tests don't support mKeyNameIndex.
   //     Therefore, we still need to handle this with keyCode.
new file mode 100644
--- /dev/null
+++ b/dom/events/crashtests/1397711.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<script>
+  let code = "x".charCodeAt(0);
+  let e = new KeyboardEvent("keypress", {
+    keyCode: code,
+    charCode: code,
+    bubbles: true
+  });
+  let utils = SpecialPowers.getDOMWindowUtils(window);
+  utils.dispatchDOMEventViaPresShell(document.documentElement, e, false);
+</script>
--- a/dom/events/crashtests/crashtests.list
+++ b/dom/events/crashtests/crashtests.list
@@ -11,8 +11,9 @@ load 1035654-1.html
 load 1035654-2.html
 needs-focus load 1072137-1.html
 load 1143972-1.html
 load 1190036-1.html
 load eventctor-nulldictionary.html
 load eventctor-nullstorage.html
 load recursive-DOMNodeInserted.html
 load recursive-onload.html
+load 1397711.html
--- a/dom/ipc/ProcessHangMonitor.h
+++ b/dom/ipc/ProcessHangMonitor.h
@@ -6,21 +6,21 @@
 
 #ifndef mozilla_ProcessHangMonitor_h
 #define mozilla_ProcessHangMonitor_h
 
 #include "mozilla/AlreadyAddRefed.h"
 #include "mozilla/Atomics.h"
 #include "nsCOMPtr.h"
 #include "nsIObserver.h"
+#include "nsStringFwd.h"
 
 class nsIRunnable;
 class nsITabChild;
 class nsIThread;
-class nsString;
 
 namespace mozilla {
 
 namespace dom {
 class ContentParent;
 class TabParent;
 } // namespace dom
 
--- a/dom/presentation/Presentation.cpp
+++ b/dom/presentation/Presentation.cpp
@@ -58,38 +58,50 @@ Presentation::WrapObject(JSContext* aCx,
                          JS::Handle<JSObject*> aGivenProto)
 {
   return PresentationBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
 Presentation::SetDefaultRequest(PresentationRequest* aRequest)
 {
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    return;
+  }
+
   nsCOMPtr<nsIDocument> doc = mWindow ? mWindow->GetExtantDoc() : nullptr;
   if (NS_WARN_IF(!doc)) {
     return;
   }
 
   if (doc->GetSandboxFlags() & SANDBOXED_PRESENTATION) {
     return;
   }
 
   mDefaultRequest = aRequest;
 }
 
 already_AddRefed<PresentationRequest>
 Presentation::GetDefaultRequest() const
 {
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    return nullptr;
+  }
+
   RefPtr<PresentationRequest> request = mDefaultRequest;
   return request.forget();
 }
 
 already_AddRefed<PresentationReceiver>
 Presentation::GetReceiver()
 {
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    return nullptr;
+  }
+
   // return the same receiver if already created
   if (mReceiver) {
     RefPtr<PresentationReceiver> receiver = mReceiver;
     return receiver.forget();
   }
 
   if (!HasReceiverSupport() || !IsInPresentedContent()) {
     return nullptr;
--- a/dom/presentation/PresentationAvailability.cpp
+++ b/dom/presentation/PresentationAvailability.cpp
@@ -4,16 +4,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/. */
 
 #include "PresentationAvailability.h"
 
 #include "mozilla/dom/PresentationAvailabilityBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/Unused.h"
+#include "nsContentUtils.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIPresentationDeviceManager.h"
 #include "nsIPresentationService.h"
 #include "nsServiceManagerUtils.h"
 #include "PresentationLog.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
@@ -152,16 +153,20 @@ void
 PresentationAvailability::EnqueuePromise(RefPtr<Promise>& aPromise)
 {
   mPromises.AppendElement(aPromise);
 }
 
 bool
 PresentationAvailability::Value() const
 {
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    return false;
+  }
+
   return mIsAvailable;
 }
 
 NS_IMETHODIMP
 PresentationAvailability::NotifyAvailableChange(const nsTArray<nsString>& aAvailabilityUrls,
                                                 bool aIsAvailable)
 {
   bool available = false;
@@ -186,22 +191,31 @@ PresentationAvailability::UpdateAvailabi
   bool isChanged = (aIsAvailable != mIsAvailable);
 
   mIsAvailable = aIsAvailable;
 
   if (!mPromises.IsEmpty()) {
     // Use the first availability change notification to resolve promise.
     do {
       nsTArray<RefPtr<Promise>> promises = Move(mPromises);
+
+      if (nsContentUtils::ShouldResistFingerprinting()) {
+        continue;
+      }
+
       for (auto& promise : promises) {
         promise->MaybeResolve(this);
       }
       // more promises may have been added to mPromises, at least in theory
     } while (!mPromises.IsEmpty());
 
     return;
   }
 
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    return;
+  }
+
   if (isChanged) {
     Unused <<
       NS_WARN_IF(NS_FAILED(DispatchTrustedEvent(NS_LITERAL_STRING("change"))));
   }
 }
--- a/dom/presentation/PresentationConnection.cpp
+++ b/dom/presentation/PresentationConnection.cpp
@@ -152,47 +152,73 @@ PresentationConnection::WrapObject(JSCon
                                    JS::Handle<JSObject*> aGivenProto)
 {
   return PresentationConnectionBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
 PresentationConnection::GetId(nsAString& aId) const
 {
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    aId = EmptyString();
+    return;
+  }
+
   aId = mId;
 }
 
 void
 PresentationConnection::GetUrl(nsAString& aUrl) const
 {
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    aUrl = EmptyString();
+    return;
+  }
+
   aUrl = mUrl;
 }
 
 PresentationConnectionState
 PresentationConnection::State() const
 {
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    return PresentationConnectionState::Terminated;
+  }
+
   return mState;
 }
 
 PresentationConnectionBinaryType
 PresentationConnection::BinaryType() const
 {
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    return PresentationConnectionBinaryType::Blob;
+  }
+
   return mBinaryType;
 }
 
 void
 PresentationConnection::SetBinaryType(PresentationConnectionBinaryType aType)
 {
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    return;
+  }
+
   mBinaryType = aType;
 }
 
 void
 PresentationConnection::Send(const nsAString& aData,
                              ErrorResult& aRv)
 {
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    return;
+  }
+
   // Sending is not allowed if the session is not connected.
   if (NS_WARN_IF(mState != PresentationConnectionState::Connected)) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
@@ -212,16 +238,20 @@ PresentationConnection::Send(const nsASt
       NS_LITERAL_STRING("\""));
   }
 }
 
 void
 PresentationConnection::Send(Blob& aData,
                              ErrorResult& aRv)
 {
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    return;
+  }
+
   if (NS_WARN_IF(mState != PresentationConnectionState::Connected)) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
   if(NS_WARN_IF(!service)) {
@@ -236,16 +266,20 @@ PresentationConnection::Send(Blob& aData
       NS_LITERAL_STRING("Unable to send binary message for Blob message."));
   }
 }
 
 void
 PresentationConnection::Send(const ArrayBuffer& aData,
                              ErrorResult& aRv)
 {
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    return;
+  }
+
   if (NS_WARN_IF(mState != PresentationConnectionState::Connected)) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
   if(NS_WARN_IF(!service)) {
@@ -268,16 +302,20 @@ PresentationConnection::Send(const Array
       NS_LITERAL_STRING("Unable to send binary message for ArrayBuffer message."));
   }
 }
 
 void
 PresentationConnection::Send(const ArrayBufferView& aData,
                              ErrorResult& aRv)
 {
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    return;
+  }
+
   if (NS_WARN_IF(mState != PresentationConnectionState::Connected)) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
   if(NS_WARN_IF(!service)) {
@@ -299,16 +337,20 @@ PresentationConnection::Send(const Array
     AsyncCloseConnectionWithErrorMsg(
       NS_LITERAL_STRING("Unable to send binary message for ArrayBufferView message."));
   }
 }
 
 void
 PresentationConnection::Close(ErrorResult& aRv)
 {
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    return;
+  }
+
   // It only works when the state is CONNECTED or CONNECTING.
   if (NS_WARN_IF(mState != PresentationConnectionState::Connected &&
                  mState != PresentationConnectionState::Connecting)) {
     return;
   }
 
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
@@ -321,16 +363,20 @@ PresentationConnection::Close(ErrorResul
     service->CloseSession(mId,
                           mRole,
                           nsIPresentationService::CLOSED_REASON_CLOSED)));
 }
 
 void
 PresentationConnection::Terminate(ErrorResult& aRv)
 {
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    return;
+  }
+
   // It only works when the state is CONNECTED.
   if (NS_WARN_IF(mState != PresentationConnectionState::Connected)) {
     return;
   }
 
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
   if(NS_WARN_IF(!service)) {
@@ -407,16 +453,20 @@ PresentationConnection::NotifyStateChang
 
 nsresult
 PresentationConnection::ProcessStateChanged(nsresult aReason)
 {
   switch (mState) {
     case PresentationConnectionState::Connecting:
       return NS_OK;
     case PresentationConnectionState::Connected: {
+      if (nsContentUtils::ShouldResistFingerprinting()) {
+        return NS_OK;
+      }
+
       RefPtr<AsyncEventDispatcher> asyncDispatcher =
         new AsyncEventDispatcher(this, NS_LITERAL_STRING("connect"), false);
       return asyncDispatcher->PostDOMEvent();
     }
     case PresentationConnectionState::Closed: {
       PresentationConnectionClosedReason reason =
         PresentationConnectionClosedReason::Closed;
 
@@ -436,20 +486,22 @@ PresentationConnection::ProcessStateChan
       }
 
       Unused <<
         NS_WARN_IF(NS_FAILED(DispatchConnectionCloseEvent(reason, errorMsg)));
 
       return RemoveFromLoadGroup();
     }
     case PresentationConnectionState::Terminated: {
-      // Ensure onterminate event is fired.
-      RefPtr<AsyncEventDispatcher> asyncDispatcher =
-        new AsyncEventDispatcher(this, NS_LITERAL_STRING("terminate"), false);
-      Unused << NS_WARN_IF(NS_FAILED(asyncDispatcher->PostDOMEvent()));
+      if (!nsContentUtils::ShouldResistFingerprinting()) {
+        // Ensure onterminate event is fired.
+        RefPtr<AsyncEventDispatcher> asyncDispatcher =
+          new AsyncEventDispatcher(this, NS_LITERAL_STRING("terminate"), false);
+        Unused << NS_WARN_IF(NS_FAILED(asyncDispatcher->PostDOMEvent()));
+      }
 
       nsCOMPtr<nsIPresentationService> service =
         do_GetService(PRESENTATION_SERVICE_CONTRACTID);
       if (NS_WARN_IF(!service)) {
         return NS_ERROR_NOT_AVAILABLE;
       }
 
       nsresult rv = service->UnregisterSessionListener(mId, mRole);
@@ -490,16 +542,20 @@ PresentationConnection::NotifyMessage(co
   }
 
   return NS_OK;
 }
 
 nsresult
 PresentationConnection::DoReceiveMessage(const nsACString& aData, bool aIsBinary)
 {
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    return NS_OK;
+  }
+
   // Transform the data.
   AutoJSAPI jsapi;
   if (!jsapi.Init(GetOwner())) {
     return NS_ERROR_FAILURE;
   }
   JSContext* cx = jsapi.cx();
   JS::Rooted<JS::Value> jsData(cx);
 
@@ -535,16 +591,20 @@ PresentationConnection::DoReceiveMessage
 }
 
 nsresult
 PresentationConnection::DispatchConnectionCloseEvent(
   PresentationConnectionClosedReason aReason,
   const nsAString& aMessage,
   bool aDispatchNow)
 {
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    return NS_OK;
+  }
+
   if (mState != PresentationConnectionState::Closed) {
     MOZ_ASSERT(false, "The connection state should be closed.");
     return NS_ERROR_FAILURE;
   }
 
   PresentationConnectionCloseEventInit init;
   init.mReason = aReason;
   init.mMessage = aMessage;
--- a/dom/presentation/PresentationConnectionList.cpp
+++ b/dom/presentation/PresentationConnectionList.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "PresentationConnectionList.h"
 
+#include "nsContentUtils.h"
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/dom/PresentationConnectionAvailableEvent.h"
 #include "mozilla/dom/PresentationConnectionListBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "PresentationConnection.h"
 
 namespace mozilla {
 namespace dom {
@@ -40,23 +41,32 @@ PresentationConnectionList::WrapObject(J
 {
   return PresentationConnectionListBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
 PresentationConnectionList::GetConnections(
   nsTArray<RefPtr<PresentationConnection>>& aConnections) const
 {
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    aConnections.Clear();
+    return;
+  }
+
   aConnections = mConnections;
 }
 
 nsresult
 PresentationConnectionList::DispatchConnectionAvailableEvent(
   PresentationConnection* aConnection)
 {
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    return NS_OK;
+  }
+
   PresentationConnectionAvailableEventInit init;
   init.mConnection = aConnection;
 
   RefPtr<PresentationConnectionAvailableEvent> event =
     PresentationConnectionAvailableEvent::Constructor(
       this,
       NS_LITERAL_STRING("connectionavailable"),
       init);
@@ -99,17 +109,19 @@ PresentationConnectionList::NotifyStateC
     FindConnectionById(aSessionId) != mConnections.NoIndex ? true : false;
 
   PresentationConnectionListBinding::ClearCachedConnectionsValue(this);
   switch (aConnection->State()) {
     case PresentationConnectionState::Connected:
       if (!connectionFound) {
         mConnections.AppendElement(aConnection);
         if (mGetConnectionListPromise) {
-          mGetConnectionListPromise->MaybeResolve(this);
+          if (!nsContentUtils::ShouldResistFingerprinting()) {
+            mGetConnectionListPromise->MaybeResolve(this);
+          }
           mGetConnectionListPromise = nullptr;
           return;
         }
       }
       DispatchConnectionAvailableEvent(aConnection);
       break;
     case PresentationConnectionState::Terminated:
       if (connectionFound) {
--- a/dom/presentation/PresentationReceiver.cpp
+++ b/dom/presentation/PresentationReceiver.cpp
@@ -140,16 +140,19 @@ PresentationReceiver::GetConnectionList(
       [self]() -> void { self->CreateConnectionList(); }));
     if (NS_FAILED(rv)) {
       aRv.Throw(rv);
       return nullptr;
     }
   }
 
   RefPtr<Promise> promise = mGetConnectionListPromise;
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+  }
   return promise.forget();
 }
 
 void
 PresentationReceiver::CreateConnectionList()
 {
   MOZ_ASSERT(mGetConnectionListPromise);
 
--- a/dom/presentation/PresentationRequest.cpp
+++ b/dom/presentation/PresentationRequest.cpp
@@ -167,16 +167,21 @@ PresentationRequest::StartWithDevice(con
     return nullptr;
   }
 
   RefPtr<Promise> promise = Promise::Create(global, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+    return promise.forget();
+  }
+
   if (IsProhibitMixedSecurityContexts(doc) &&
       !IsAllURLAuthenticated()) {
     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     return promise.forget();
   }
 
   if (doc->GetSandboxFlags() & SANDBOXED_PRESENTATION) {
     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
@@ -266,16 +271,21 @@ PresentationRequest::Reconnect(const nsA
     return nullptr;
   }
 
   RefPtr<Promise> promise = Promise::Create(global, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+    return promise.forget();
+  }
+
   if (IsProhibitMixedSecurityContexts(doc) &&
       !IsAllURLAuthenticated()) {
     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     return promise.forget();
   }
 
   if (doc->GetSandboxFlags() & SANDBOXED_PRESENTATION) {
     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
@@ -379,16 +389,21 @@ PresentationRequest::GetAvailability(Err
     return nullptr;
   }
 
   RefPtr<Promise> promise = Promise::Create(global, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+    return promise.forget();
+  }
+
   if (IsProhibitMixedSecurityContexts(doc) &&
       !IsAllURLAuthenticated()) {
     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     return promise.forget();
   }
 
   if (doc->GetSandboxFlags() & SANDBOXED_PRESENTATION) {
     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
@@ -438,16 +453,20 @@ PresentationRequest::FindOrCreatePresent
     aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return;
   }
 }
 
 nsresult
 PresentationRequest::DispatchConnectionAvailableEvent(PresentationConnection* aConnection)
 {
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    return NS_OK;
+  }
+
   PresentationConnectionAvailableEventInit init;
   init.mConnection = aConnection;
 
   RefPtr<PresentationConnectionAvailableEvent> event =
     PresentationConnectionAvailableEvent::Constructor(this,
                                                       NS_LITERAL_STRING("connectionavailable"),
                                                       init);
   if (NS_WARN_IF(!event)) {
--- a/dom/presentation/PresentationSessionInfo.cpp
+++ b/dom/presentation/PresentationSessionInfo.cpp
@@ -640,16 +640,20 @@ PresentationControllingInfo::Shutdown(ns
     Unused << NS_WARN_IF(NS_FAILED(mServerSocket->Close()));
     mServerSocket = nullptr;
   }
 }
 
 nsresult
 PresentationControllingInfo::GetAddress()
 {
+  if (nsContentUtils::ShouldResistFingerprinting()) {
+    return NS_ERROR_FAILURE;
+  }
+
 #if defined(MOZ_WIDGET_ANDROID)
   RefPtr<PresentationNetworkHelper> networkHelper =
     new PresentationNetworkHelper(this,
                                   &PresentationControllingInfo::OnGetAddress);
   nsresult rv = networkHelper->GetWifiIPAddress();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_fingerprinting_resistance_receiver.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script>
+function testReceiver() {
+  alert(!!navigator.presentation.receiver);
+}
+
+testReceiver();
+window.addEventListener('hashchange', testReceiver);
+</script>
--- a/dom/presentation/tests/mochitest/mochitest.ini
+++ b/dom/presentation/tests/mochitest/mochitest.ini
@@ -71,8 +71,11 @@ skip-if = (e10s || toolkit == 'android')
 skip-if = toolkit == 'android'
 [test_presentation_sandboxed_presentation.html]
 skip-if = true # bug 1315867
 [test_presentation_reconnect.html]
 [test_presentation_mixed_security_contexts.html]
 skip-if = toolkit == 'android' # bug 1304934
 [test_presentation_availability.html]
 support-files = test_presentation_availability_iframe.html
+[test_presentation_fingerprinting_resistance.html]
+skip-if = (e10s || toolkit == 'android')
+support-files = file_presentation_fingerprinting_resistance_receiver.html
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_fingerprinting_resistance.html
@@ -0,0 +1,145 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+/* global SimpleTest SpecialPowers */
+
+const gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript1UA.js'));
+const kReceiverFile = 'file_presentation_fingerprinting_resistance_receiver.html';
+const kReceiverUrl = SimpleTest.getTestFileURL(kReceiverFile);
+
+let runTests = async () => {
+  await setup();
+  let request = await createRequest();
+  let iframe = await testRequestAndReceiver(request);
+  await enableResistFingerprinting();
+  await testRequestResistFingerprinting(request);
+  await testReceiverResistFingerprinting(iframe);
+  teardown();
+};
+
+let setup = async () => {
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      ['dom.presentation.enabled', true],
+      ['dom.presentation.controller.enabled', true],
+      ['dom.presentation.receiver.enabled', true],
+      ['dom.presentation.test.enabled', true],
+      ['dom.presentation.test.stage', 0],
+      ['dom.mozBrowserFramesEnabled', true],
+      ['network.disable.ipc.security', true]
+    ]
+  });
+
+  gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+    gScript.removeMessageListener('device-prompt', devicePromptHandler);
+    gScript.sendAsyncMessage('trigger-device-prompt-select');
+  });
+
+  gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+    gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
+    gScript.sendAsyncMessage('trigger-control-channel-open');
+  });
+
+  gScript.addMessageListener('promise-setup-ready', function promiseSetupReadyHandler() {
+    gScript.removeMessageListener('promise-setup-ready', promiseSetupReadyHandler);
+    gScript.sendAsyncMessage('trigger-on-session-request', kReceiverUrl);
+  });
+};
+
+let createRequest = () => new Promise((resolve, reject) => {
+  let request = new PresentationRequest(kReceiverFile);
+  request.getAvailability().then((availability) => {
+    SimpleTest.ok(availability, 'PresentationRequest.getAvailability');
+    availability.onchange = () => {
+      availability.onchange = null;
+      resolve(request);
+    };
+    gScript.sendAsyncMessage('trigger-device-add');
+  }).catch((error) => {
+    SimpleTest.ok(false, 'PresentationRequest.getAvailability: ' + error);
+    teardown();
+    reject(error);
+  });
+});
+
+let testRequestAndReceiver = (request) => new Promise((resolve, reject) => {
+  gScript.addMessageListener('sender-launch', function senderLaunchHandler(url) {
+    // SimpleTest.is(url, kReceiverUrl, 'sender-launch');
+    gScript.removeMessageListener('sender-launch', senderLaunchHandler);
+
+    let iframe = document.createElement('iframe');
+    iframe.setAttribute('src', kReceiverUrl);
+    iframe.setAttribute('mozbrowser', 'true');
+    iframe.setAttribute('mozpresentation', kReceiverUrl);
+    iframe.setAttribute('remote', 'false');
+    iframe.addEventListener('mozbrowsershowmodalprompt', (event) => {
+      SimpleTest.is(event.detail.message, 'true', 'navigator.presentation.receiver');
+      resolve(iframe);
+    }, {once: true});
+
+    let promise = new Promise((resolve) => {
+      document.body.appendChild(iframe);
+      resolve(iframe);
+    });
+
+    let obs = SpecialPowers.Cc["@mozilla.org/observer-service;1"].getService(SpecialPowers.Ci.nsIObserverService);
+    obs.notifyObservers(promise, 'setup-request-promise');
+  });
+
+  request.start().then((connection) => {
+    SimpleTest.ok(connection, 'PresentationRequest.start');
+  }).catch((error) => {
+    SimpleTest.ok(false, 'PresentationRequest.start: ' + error);
+    teardown();
+    reject(error);
+  });
+});
+
+let enableResistFingerprinting = () => {
+  const kPref = 'privacy.resistFingerprinting';
+  SimpleTest.info(kPref + ' = true');
+  return SpecialPowers.pushPrefEnv({
+    set: [
+      [kPref, true]
+    ]
+  });
+};
+
+let testRequestResistFingerprinting = (request) => {
+  return request.getAvailability()
+      .then(() => SimpleTest.ok(false, 'PresentationRequest.getAvailability'))
+      .catch((error) => SimpleTest.is(error.name, 'SecurityError', 'PresentationRequest.getAvailability'))
+      .then(() => request.start())
+      .then(() => SimpleTest.ok(false, 'PresentationRequest.start'))
+      .catch((error) => SimpleTest.is(error.name, 'SecurityError', 'PresentationRequest.start'))
+      .then(() => request.reconnect(kReceiverUrl))
+      .then(() => SimpleTest.ok(false, 'PresentationRequest.reconnect'))
+      .catch((error) => SimpleTest.is(error.name, 'SecurityError', 'PresentationRequest.reconnect'));
+};
+
+let testReceiverResistFingerprinting = (iframe) => new Promise((resolve) => {
+  iframe.addEventListener('mozbrowsershowmodalprompt', (event) => {
+    SimpleTest.is(event.detail.message, 'false', 'navigator.presentation.receiver');
+    resolve();
+  }, {once: true});
+  iframe.setAttribute('src', kReceiverUrl + '#privacy.resistFingerprinting');
+});
+
+let teardown = () => {
+  gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+    gScript.destroy();
+    SimpleTest.finish();
+  });
+
+  gScript.sendAsyncMessage('teardown');
+};
+
+SimpleTest.waitForExplicitFinish();
+document.addEventListener('DOMContentLoaded', () => {
+  SpecialPowers.pushPermissions([
+    {type: 'presentation-device-manage', allow: false, context: document},
+    {type: 'browser', allow: true, context: document},
+  ], runTests);
+});
+</script>
--- a/js/src/devtools/rootAnalysis/analyzeHeapWrites.js
+++ b/js/src/devtools/rootAnalysis/analyzeHeapWrites.js
@@ -440,27 +440,27 @@ function ignoreContents(entry)
             /nsTArray_Impl.*?::AppendElement/,
             /nsTArray_Impl.*?::RemoveElementsAt/,
             /nsTArray_Impl.*?::ReplaceElementsAt/,
             /nsTArray_Impl.*?::InsertElementsAt/,
             /nsTArray_Impl.*?::SetCapacity/,
             /nsTArray_base.*?::EnsureCapacity/,
             /nsTArray_base.*?::ShiftData/,
             /AutoTArray.*?::Init/,
-            /nsAC?String::SetCapacity/,
-            /nsAC?String::SetLength/,
-            /nsAC?String::Assign/,
-            /nsAC?String::Append/,
-            /nsAC?String::Replace/,
-            /nsAC?String::Trim/,
-            /nsAC?String::Truncate/,
-            /nsAString::StripTaggedASCII/,
-            /nsAC?String::operator=/,
-            /nsAutoString::nsAutoString/,
-            /nsFixedCString::nsFixedCString/,
+            /nsTSubstring<T>::SetCapacity/,
+            /nsTSubstring<T>::SetLength/,
+            /nsTSubstring<T>::Assign/,
+            /nsTSubstring<T>::Append/,
+            /nsTSubstring<T>::Replace/,
+            /nsTSubstring<T>::Trim/,
+            /nsTSubstring<T>::Truncate/,
+            /nsTSubstring<T>::StripTaggedASCII/,
+            /nsTSubstring<T>::operator=/,
+            /nsTAutoStringN<T, N>::nsTAutoStringN/,
+            /nsTFixedString<T>::nsTFixedString/,
 
             // Similar for some other data structures
             /nsCOMArray_base::SetCapacity/,
             /nsCOMArray_base::Clear/,
             /nsCOMArray_base::AppendElement/,
 
             // UniquePtr is similar.
             /mozilla::UniquePtr/,
--- a/layout/base/crashtests/crashtests.list
+++ b/layout/base/crashtests/crashtests.list
@@ -36,17 +36,17 @@ load 243519-1.html
 load 244490-1.html
 load 254367-1.html
 load 263359-1.html
 load 265027-1.html
 load 265736-1.html
 load 265736-2.html
 load 265899-1.html
 load 265973-1.html
-asserts-if(!stylo,6-12) load 265986-1.html # Bug 512405 , bug 718883
+asserts(6-12) load 265986-1.html # Bug 512405 , bug 718883
 load 265999-1.html
 load 266222-1.html
 asserts(1-7) load 266360-1.html # bug 576358
 load 266445-1.html
 asserts(2) load 266445-2.html
 load 268157-1.html
 load 269566-1.html
 load 272647-1.html
--- a/layout/generic/crashtests/crashtests.list
+++ b/layout/generic/crashtests/crashtests.list
@@ -136,17 +136,17 @@ load 387058-1.html
 load 387058-2.html
 load 387088-1.html
 load 387209-1.html
 load 387213-1.html
 load 387215-1.xhtml
 load 387219-1.xhtml
 load 387233-1.html
 load 387233-2.html
-asserts-if(stylo,2) load 387282-1.html
+load 387282-1.html
 load 388175-1.html
 load 388367-1.html
 load 388709-1.html
 load 389635-1.html
 load 390050-1.html
 load 390050-2.html
 load 390050-3.html
 load 390762-1.html
@@ -394,18 +394,18 @@ load 533379-1.html
 load 533379-2.html
 load 534082-1.html
 load 534366-1.html
 load 534366-2.html
 load 536692-1.xhtml
 load 537645.xhtml
 load 541277-1.html
 load 541277-2.html
-asserts-if(stylo,2) load 541714-1.html # bug 634161
-asserts-if(stylo,3) load 541714-2.html # bug 634161
+load 541714-1.html
+load 541714-2.html
 load 542136-1.html
 load 545571-1.html
 load 547338.xul
 load 547843-1.xhtml
 load 551635-1.html
 load 553504-1.xhtml
 load 564368-1.xhtml
 load 564968.xhtml
@@ -553,17 +553,17 @@ load 885009-1.html
 load 893496-1.html
 load 893523.html
 asserts(0-3) load 898871.html # bug 479160 - mostly OSX, sometimes Windows
 asserts(0-3) load 914501.html # bug 1144852 - all platforms
 load 914891.html
 load 915475.xhtml
 load 927558.html
 load 943509-1.html
-asserts-if(stylo,2-8) load 944909-1.html
+load 944909-1.html
 load 946167-1.html
 load 947158.html
 load 949932.html
 asserts-if(Android,0-1) load 964078.html # bug 989718
 load 970710.html
 load 973701-1.xhtml
 load 973701-2.xhtml
 load 986899.html
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1575,17 +1575,17 @@ fails-if(Android) random-if(layersGPUAcc
 skip-if(Android) == 580160-1.html 580160-1-ref.html # bug 920927 for Android; issues without the test-plugin
 fuzzy-if(asyncPan&&!layersGPUAccelerated,255,141) HTTP(..) == 580863-1.html 580863-1-ref.html
 fails-if(Android) random-if(layersGPUAccelerated) fuzzy-if(skiaContent,1,6436) == 581317-1.html 581317-1-ref.html
 == 581579-1.html 581579-1-ref.html
 == 582037-1a.html 582037-1-ref.html
 == 582037-1b.html 582037-1-ref.html
 fuzzy-if(Android,3,256) == 582037-2a.html 582037-2-ref.html
 fuzzy-if(Android,3,256) == 582037-2b.html 582037-2-ref.html
-asserts(1-2) asserts-if(styloVsGecko,3) == 582146-1.html about:blank
+asserts(1-2) asserts-if(styloVsGecko,3-4) == 582146-1.html about:blank
 == 582476-1.svg 582476-1-ref.svg
 == 584400-dash-length.svg 584400-dash-length-ref.svg
 == 584699-1.html 584699-1-ref.html
 fuzzy-if(Android,2,48) == 585598-2.xhtml 585598-2-ref.xhtml
 == 586400-1.html 586400-1-ref.html
 fuzzy-if(d2d,52,1051) == 586683-1.html 586683-1-ref.html
 == 589615-1a.xhtml 589615-1-ref.html
 == 589615-1b.html 589615-1-ref.html
--- a/layout/style/ServoBindings.toml
+++ b/layout/style/ServoBindings.toml
@@ -338,17 +338,18 @@ mapped-generic-types = [
     { generic = false, gecko = "mozilla::ServoCustomPropertiesMap", servo = "Option<::servo_arc::Arc<::custom_properties::CustomPropertiesMap>>" },
     { generic = false, gecko = "mozilla::ServoRuleNode", servo = "Option<::rule_tree::StrongRuleNode>" },
     { generic = false, gecko = "mozilla::ServoVisitedStyle", servo = "Option<::servo_arc::RawOffsetArc<::properties::ComputedValues>>" },
     { generic = false, gecko = "mozilla::ServoComputedValueFlags", servo = "::properties::computed_value_flags::ComputedValueFlags" },
     { generic = true, gecko = "mozilla::ServoRawOffsetArc", servo = "::servo_arc::RawOffsetArc" },
     { generic = false, gecko = "ServoStyleContextStrong", servo = "::gecko_bindings::sugar::ownership::Strong<::properties::ComputedValues>" },
 ]
 fixups = [
-    { pat = "root::nsString", rep = "::nsstring::nsStringRepr" },
+    { pat = "\\broot::nsString\\b", rep = "::nsstring::nsStringRepr" },
+    { pat = "\\broot::nsTString<u16>", rep = "::nsstring::nsStringRepr" },
 ]
 
 [bindings]
 headers = ["mozilla/ServoBindings.h"]
 hide-types = [
     "nsACString_internal",
     "nsAString_internal",
     "ServoStyleContextBorrowed",
@@ -536,11 +537,13 @@ servo-borrow-types = [
     "RawGeckoPropertyValuePairList",
     "RawGeckoComputedKeyframeValuesList",
     "RawGeckoFontFaceRuleList",
     "RawGeckoServoStyleRuleList",
     "RawGeckoServoAnimationValueList",
     "RawGeckoStyleChildrenIterator",
 ]
 fixups = [
+    # Remap the templated string type to the helper type
+    { pat = "\\bnsTString<u16>", rep = "nsString" },
     # hack for gecko-owned string
-    { pat = "<nsString", rep = "<nsStringRepr" },
+    { pat = "\\b<nsString\\b", rep = "<nsStringRepr" },
 ]
--- a/layout/style/ServoCSSRuleList.cpp
+++ b/layout/style/ServoCSSRuleList.cpp
@@ -218,17 +218,17 @@ ServoCSSRuleList::DeleteRule(uint32_t aI
       DropRule(already_AddRefed<css::Rule>(CastToPtr(rule)));
     }
     mRules.RemoveElementAt(aIndex);
   }
   return rv;
 }
 
 uint16_t
-ServoCSSRuleList::GetRuleType(uint32_t aIndex) const
+ServoCSSRuleList::GetDOMCSSRuleType(uint32_t aIndex) const
 {
   uintptr_t rule = mRules[aIndex];
   if (rule <= kMaxRuleType) {
     return rule;
   }
   return CastToPtr(rule)->Type();
 }
 
--- a/layout/style/ServoCSSRuleList.h
+++ b/layout/style/ServoCSSRuleList.h
@@ -44,17 +44,17 @@ public:
   uint32_t Length() final { return mRules.Length(); }
 
   void DropReference();
 
   css::Rule* GetRule(uint32_t aIndex);
   nsresult InsertRule(const nsAString& aRule, uint32_t aIndex);
   nsresult DeleteRule(uint32_t aIndex);
 
-  uint16_t GetRuleType(uint32_t aIndex) const;
+  uint16_t GetDOMCSSRuleType(uint32_t aIndex) const;
 
 private:
   virtual ~ServoCSSRuleList();
 
   // XXX Is it possible to have an address lower than or equal to 255?
   //     Is it possible to have more than 255 CSS rule types?
   static const uintptr_t kMaxRuleType = UINT8_MAX;
 
--- a/layout/style/ServoStyleSheet.cpp
+++ b/layout/style/ServoStyleSheet.cpp
@@ -407,17 +407,17 @@ ServoStyleSheet::InsertRuleInternal(cons
   GetCssRulesInternal();
 
   mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, true);
   aRv = mRuleList->InsertRule(aRule, aIndex);
   if (aRv.Failed()) {
     return 0;
   }
   if (mDocument) {
-    if (mRuleList->GetRuleType(aIndex) != css::Rule::IMPORT_RULE ||
+    if (mRuleList->GetDOMCSSRuleType(aIndex) != nsIDOMCSSRule::IMPORT_RULE ||
         !RuleHasPendingChildSheet(mRuleList->GetRule(aIndex))) {
       // XXX We may not want to get the rule when stylesheet change event
       // is not enabled.
       mDocument->StyleRuleAdded(this, mRuleList->GetRule(aIndex));
     }
   }
   return aIndex;
 }
new file mode 100644
--- /dev/null
+++ b/layout/style/crashtests/1397363-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+a = document.documentElement.animate([{ "clip": "rect(632vw,0,103.9vmax,5ex)" }],
+                                     { duration:96.2272536276,
+                                       iterations:Number.POSITIVE_INFINITY,
+                                       iterationComposite:"accumulate" });
+a.playbackRate = 3000;
+</script>
+</head>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/style/crashtests/1397439-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<math>
+<mstyle scriptlevel=101>
+<mstyle scriptlevel=-204>
+</math>
+
--- a/layout/style/crashtests/crashtests.list
+++ b/layout/style/crashtests/crashtests.list
@@ -207,8 +207,10 @@ load 1391577.html
 load 1393189.html
 load 1393580.html
 load 1389645.html
 load 1390726.html
 load 1393791.html
 load 1384232.html
 load 1395725.html
 load 1396041.html
+load 1397363-1.html
+load 1397439-1.html
--- a/layout/style/test/chrome/test_stylesheet_clone_import_rule.html
+++ b/layout/style/test/chrome/test_stylesheet_clone_import_rule.html
@@ -17,82 +17,110 @@
   const Cu = SpecialPowers.Components.utils;
   const { ContentTaskUtils } = Cu.import("resource://testing-common/ContentTaskUtils.jsm", {});
 
   document.styleSheetChangeEventsEnabled = true;
 
   let theOnlyDiv = document.getElementById("theOnlyDiv");
   let stylesheet = document.getElementById("theOnlyLink").sheet;
 
-  runTest();
+  runTest().catch(function(reason) {
+    ok(false, "Failed with reason: " + reason);
+  }).then(function() {
+    SimpleTest.finish();
+  });
 
   function cssRulesToString(cssRules) {
     return Array.from(cssRules).map(rule => rule.cssText).join('');
   }
 
   async function runTest() {
 
     // Test that the div is initially red (from base.css)
     is(getComputedStyle(theOnlyDiv).color, "rgb(0, 128, 0)", "div begins as green.");
 
     // Create a Promise to watch for some number of events.
-    let generateCountingListenerCallback = function(numberOfEventsToWaitFor) {
+    let generateCountingListenerCallback = function(numberOfEventsToWaitFor, arrayToFill) {
       let eventsReceived = 0;
       return function(event) {
+        info("Event processor: called with event " + event.rule.cssText);
+        arrayToFill.push(event);
         return (++eventsReceived >= numberOfEventsToWaitFor);
       }
     };
+    let events = [];
     let gotAllOurStyleRuleAddedEvents = ContentTaskUtils.waitForEvent(document,
-      "StyleRuleAdded", true, generateCountingListenerCallback(2));
+      "StyleRuleAdded", true, generateCountingListenerCallback(2, events));
 
+    // Insert some import rules.
     stylesheet.insertRule('@import url("import_useless2.css")', 0);
     stylesheet.insertRule('@import url("import_useless2.css")', 1);
 
     // Wait for the StyleRuleAdded events to be fired. This function returns the last event,
     // but we don't care what it contains -- only that the correct number of events fired.
     await gotAllOurStyleRuleAddedEvents;
+    is(events.length, 2, "Got the expected number of StyleRuleAdded events.");
 
-    // Get the imported sheets and confirm they are non-null and have rules.
-    let importSheet1 = stylesheet.cssRules[0].styleSheet;
-    let importSheet2 = stylesheet.cssRules[1].styleSheet;
+    // Do some sanity checking of our import rules.
+    let primaryRules = stylesheet.cssRules;
+    for (let i = 0; i < 2; ++i) {
+      // Check that the main stylesheet rule is the same rule we saw in the event listener.
+      is(primaryRules[i], events[i].rule, "Rule " + i + " matches the event listener.");
 
-    ok(importSheet1 && importSheet2, "Imported sheets exist.");
-    if (!(importSheet1 && importSheet2)) {
-      SimpleTest.finish();
-      return;
+      // Get the imported sheet and confirm it is non-null.
+      let importSheet = primaryRules[i].styleSheet;
+      ok(importSheet, "Imported sheet " + i + " exists.");
+      if (!importSheet) {
+        info("Sheet " + i + " doesn't exist -- aborting.");
+        return;
+      }
+
+      // Confirm the import sheet has rules.
+      info("Retrieving rules for sheet " + i);
+      let rules;
+
+      try {
+        rules = importSheet.cssRules;
+      } catch (e) {
+        throw "Exception thrown when retrieving rules for sheet " + i + ": " + e.message;
+      }
+      ok(rules, "Imported sheet " + i + " has rules.");
+      if (!rules) {
+        info("Rules for sheet " + i + " don't exist -- aborting.");
+        return;
+      }
     }
 
-    ok(importSheet1.cssRules && importSheet2.cssRules, "Imported sheets have rules.");
-    if (!(importSheet1.cssRules && importSheet2.cssRules)) {
-      SimpleTest.finish();
-      return;
-    }
+    // Make some helper variables for the comparison tests.
+    let importSheet1 = primaryRules[0].styleSheet;
+    let rules1 = importSheet1.cssRules;
+
+    let importSheet2 = primaryRules[1].styleSheet;
+    let rules2 = importSheet2.cssRules;
 
     // Confirm that these two sheets are meaningfully the same.
-    is(cssRulesToString(importSheet1.cssRules), cssRulesToString(importSheet2.cssRules), "Cloned sheet rules are equivalent.");
+    is(cssRulesToString(rules1), cssRulesToString(rules2), "Cloned sheet rules are equivalent.");
 
     // Add a color-changing rule to the first stylesheet.
     importSheet1.insertRule('div { color: blue; }');
+    rules1 = importSheet1.cssRules;
+
     // And make sure that it has an effect.
     is(getComputedStyle(theOnlyDiv).color, "rgb(0, 0, 255)", "div becomes blue.");
 
     // Make sure that the two sheets have different rules now.
-    isnot(cssRulesToString(importSheet1.cssRules), cssRulesToString(importSheet2.cssRules), "Cloned sheet rules are no longer equivalent.");
+    isnot(cssRulesToString(rules1), cssRulesToString(rules2), "Cloned sheet rules are no longer equivalent.");
 
     // Add a color-changing rule to the second stylesheet (that will mask the first).
     importSheet2.insertRule('div { color: red; }');
     // And make sure that it has an effect.
     is(getComputedStyle(theOnlyDiv).color, "rgb(255, 0, 0)", "div becomes red.");
 
     // Delete the second sheet by removing the import rule, and make sure the color changes back.
     stylesheet.deleteRule(1);
     is(getComputedStyle(theOnlyDiv).color, "rgb(0, 0, 255)", "div goes back to blue.");
 
     // Delete the first sheet by removing the import rule, and make sure the color changes back.
     stylesheet.deleteRule(0);
     is(getComputedStyle(theOnlyDiv).color, "rgb(0, 128, 0)", "div goes back to green.");
-
-    SimpleTest.finish();
   }
-
-
 </script>
 </html>
--- a/layout/svg/crashtests/crashtests.list
+++ b/layout/svg/crashtests/crashtests.list
@@ -100,17 +100,17 @@ load 515288-1.html
 load 522394-1.svg
 load 522394-2.svg
 load 522394-3.svg
 load 566216-1.svg
 load 587336-1.html
 load 590291-1.svg
 load 601999-1.html
 load 605626-1.svg
-asserts-if(!stylo,2) load 606914.xhtml # bug 606914, bug 718883
+asserts(2) load 606914.xhtml # bug 606914, bug 718883
 load 610594-1.html
 load 610954-1.html
 load 612662-1.svg
 load 612662-2.svg
 load 612736-1.svg
 load 612736-2.svg
 load 614367-1.svg
 load 620034-1.html
--- a/media/gmp-clearkey/0.1/VideoDecoder.cpp
+++ b/media/gmp-clearkey/0.1/VideoDecoder.cpp
@@ -33,22 +33,17 @@ VideoDecoder::VideoDecoder(Host_8 *aHost
 {
   CK_LOGD("VideoDecoder created");
 
   // We drop the ref in DecodingComplete().
   AddRef();
 
   mDecoder = new WMFH264Decoder();
 
-  // MinGW Does not support std::thread (See #1349912)
-#ifdef __MINGW32__
-  uint32_t cores = 1u;
-#else
   uint32_t cores = std::max(1u, std::thread::hardware_concurrency());
-#endif
 
   HRESULT hr = mDecoder->Init(cores);
 }
 
 VideoDecoder::~VideoDecoder()
 {
   CK_LOGD("VideoDecoder destroyed");
 }
--- a/mobile/android/app/src/main/res/layout/activity_stream_overridable_page_icon_layout.xml
+++ b/mobile/android/app/src/main/res/layout/activity_stream_overridable_page_icon_layout.xml
@@ -1,26 +1,21 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <merge xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:gecko="http://schemas.android.com/tools">
 
-    <!-- The default visibilities are set in code.
-
-         centerInside will center smaller favicons and draw a colored border around them. -->
+    <!-- The default visibilities are set in code. -->
     <org.mozilla.gecko.widget.FaviconView
             android:id="@+id/favicon_view"
+            style="@style/ActivityStreamFaviconView"
             android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            gecko:enableRoundCorners="false"
-            gecko:overrideScaleType="false"
-            android:scaleType="centerInside"
-            />
+            android:layout_height="match_parent"/>
 
     <ImageView
             android:id="@+id/image_view"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             />
 
 </merge>
\ No newline at end of file
--- a/mobile/android/app/src/main/res/layout/activity_stream_topsites_card.xml
+++ b/mobile/android/app/src/main/res/layout/activity_stream_topsites_card.xml
@@ -1,26 +1,22 @@
 <?xml version="1.0" encoding="utf-8"?>
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:gecko="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:layout_marginBottom="@dimen/activity_stream_base_margin">
 
-    <!-- centerInside will center smaller favicons and draw a colored border around them. -->
     <org.mozilla.gecko.widget.FaviconView
         android:id="@+id/favicon"
+        style="@style/ActivityStreamFaviconView"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        gecko:enableRoundCorners="false"
-        tools:background="@drawable/favicon_globe"
-        android:scaleType="centerInside"
-        gecko:overrideScaleType="false" />
+        tools:background="@drawable/favicon_globe"/>
 
     <!-- scrollHorizontally=false allows drags on the TextView to scroll the ViewPager.
          See https://stackoverflow.com/a/18171834/2219998 -->
     <TextView
         android:id="@+id/title"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:background="#66000000"
--- a/mobile/android/app/src/main/res/values/ids.xml
+++ b/mobile/android/app/src/main/res/values/ids.xml
@@ -12,15 +12,14 @@
     <item type="id" name="menu_items"/>
     <item type="id" name="menu_margin"/>
     <item type="id" name="recycler_view_click_support" />
     <item type="id" name="range_list"/>
     <item type="id" name="pref_header_general"/>
     <item type="id" name="pref_header_privacy"/>
     <item type="id" name="pref_header_search"/>
     <item type="id" name="updateServicePermissionNotification" />
-    <item type="id" name="websiteContentNotification" />
     <item type="id" name="foregroundNotification" />
     <item type="id" name="actionbar"/>
     <item type="id" name="action_button"/>
     <item type="id" name="page_progress"/>
     <item type="id" name="mediaControlNotification"/>
 </resources>
--- a/mobile/android/app/src/main/res/xml/preferences_notifications.xml
+++ b/mobile/android/app/src/main/res/xml/preferences_notifications.xml
@@ -1,16 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
-    <SwitchPreference android:key="android.not_a_preference.notifications.content"
-        android:title="@string/pref_content_notifications"
-        android:summary="@string/pref_content_notifications_summary"
-        android:defaultValue="true" />
-    <org.mozilla.gecko.preferences.AlignRightLinkPreference
-        android:key="android.not_a_preference.notifications.content.learn_more"
-        android:title="@string/pref_learn_more"
-        android:persistent="false"
-        url="https://support.mozilla.org/kb/notifications-firefox-android?utm_source=inproduct&amp;utm_medium=notifications&amp;utm_campaign=mobileandroid" />
     <SwitchPreference android:key="android.not_a_preference.notifications.whats_new"
         android:title="@string/pref_whats_new_notification"
         android:summary="@string/pref_whats_new_notification_summary"
         android:defaultValue="true" />
 </PreferenceScreen>
\ No newline at end of file
deleted file mode 100644
index ead438be77ca435324336a9c3d9e3595a557995d..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 43cf696b7a850a7792b6abfa4eb940850f35baef..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 8aa658426c682423fc3bcce7fe870bc62036f0c8..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index df6b69c73d7ef92b130ca5050f79df1c5b9f5c7c..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 3dd83fb119c29b5560003198d77cf849bfd5421a..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index ecdc0a7221bf19068ffba427b489451b60371816..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 2424cf2d43076f93a0ea3c216a2e199423c7df23..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 846f22bbe119f7cf4c64f91e97bd70ffb86b6160..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/mobile/android/app/src/photon/res/values/styles.xml
+++ b/mobile/android/app/src/photon/res/values/styles.xml
@@ -1,14 +1,17 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
-<resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
+<resources
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:tools="http://schemas.android.com/tools"
+        xmlns:gecko="http://schemas.android.com/apk/res-auto">
 
     <!--
         Base application styles. This could be overridden in other res/values-XXX/themes.xml.
     -->
     <style name="Widget"/>
 
     <style name="Widget.BaseButton" parent="android:style/Widget.Button"/>
 
@@ -819,16 +822,25 @@
         <item name="android:listDivider">@drawable/as_contextmenu_divider</item>
     </style>
 
     <style name="ActivityStreamButton" parent="Widget.AppCompat.Button.Colored">
         <item name="colorButtonNormal">@color/link_blue</item>
         <item name="android:textColor">@android:color/white</item>
     </style>
 
+    <!-- centerInside will downscale larger icons to fit or center smaller favicons and allow us to draw a colored
+         border around them. When changing these values, consider favicons downloaded from the internet and
+         those provided by suggested sites, including suggested sites from distributions. -->
+    <style name="ActivityStreamFaviconView">
+        <item name="enableRoundCorners">false</item>
+        <item name="overrideScaleType">false</item>
+        <item name="android:scaleType">centerInside</item>
+    </style>
+
     <!-- URL bar - Site identity -->
     <style name="UrlBar.SiteIdentity" parent="UrlBar.V15.SiteIdentity"/>
     <style name="UrlBar.Base.SiteIdentity" parent="UrlBar.Button">
         <item name="android:layout_width">@dimen/browser_toolbar_site_security_width</item>
         <item name="android:layout_height">@dimen/browser_toolbar_url_height</item>
         <item name="android:scaleType">center</item>
     </style>
     <style name="UrlBar.V15.SiteIdentity" parent="UrlBar.Base.SiteIdentity">
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -381,40 +381,23 @@
             android:name="org.mozilla.gecko.notifications.NotificationService">
         </service>
 
         <service
             android:exported="false"
             android:name="org.mozilla.gecko.dlc.DownloadContentService">
         </service>
 
-        <service
-            android:exported="false"
-            android:name="org.mozilla.gecko.feeds.FeedService">
-        </service>
-
         <!-- DON'T EXPORT THIS, please! An attacker could delete arbitrary files. -->
         <service
             android:exported="false"
             android:name="org.mozilla.gecko.cleanup.FileCleanupService">
         </service>
 
         <receiver
-            android:name="org.mozilla.gecko.feeds.FeedAlarmReceiver"
-            android:exported="false" />
-
-        <receiver
-            android:name="org.mozilla.gecko.BootReceiver"
-            android:exported="false">
-            <intent-filter>
-                <action android:name="android.intent.action.BOOT_COMPLETED"></action>
-            </intent-filter>
-        </receiver>
-
-        <receiver
             android:name="org.mozilla.gecko.PackageReplacedReceiver"
             android:exported="false">
             <intent-filter>
                 <action android:name="android.intent.action.MY_PACKAGE_REPLACED"></action>
             </intent-filter>
         </receiver>
 
         <service
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/BootReceiver.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-import org.mozilla.gecko.feeds.FeedService;
-
-/**
- * This broadcast receiver receives ACTION_BOOT_COMPLETED broadcasts and starts components that should
- * run after the device has booted.
- */
-public class BootReceiver extends BroadcastReceiver {
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        if (intent == null || !intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
-            return; // This is not the broadcast you are looking for.
-        }
-
-        FeedService.setup(context);
-    }
-}
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -2,128 +2,23 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import android.Manifest;
 import android.annotation.TargetApi;
-import android.app.DownloadManager;
-import android.content.ContentProviderClient;
-import android.os.Environment;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.UiThread;
-
-import org.mozilla.gecko.activitystream.ActivityStream;
-import org.mozilla.gecko.adjust.AdjustBrowserAppDelegate;
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
-import org.mozilla.gecko.Tabs.TabEvents;
-import org.mozilla.gecko.animation.PropertyAnimator;
-import org.mozilla.gecko.bookmarks.BookmarkEditFragment;
-import org.mozilla.gecko.bookmarks.BookmarkUtils;
-import org.mozilla.gecko.bookmarks.EditBookmarkTask;
-import org.mozilla.gecko.cleanup.FileCleanupController;
-import org.mozilla.gecko.dawn.DawnHelper;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.SuggestedSites;
-import org.mozilla.gecko.delegates.BrowserAppDelegate;
-import org.mozilla.gecko.delegates.OfflineTabStatusDelegate;
-import org.mozilla.gecko.delegates.ScreenshotDelegate;
-import org.mozilla.gecko.distribution.Distribution;
-import org.mozilla.gecko.distribution.DistributionStoreCallback;
-import org.mozilla.gecko.dlc.DownloadContentService;
-import org.mozilla.gecko.icons.IconsHelper;
-import org.mozilla.gecko.icons.decoders.IconDirectoryEntry;
-import org.mozilla.gecko.icons.decoders.FaviconDecoder;
-import org.mozilla.gecko.icons.decoders.LoadFaviconResult;
-import org.mozilla.gecko.feeds.ContentNotificationsDelegate;
-import org.mozilla.gecko.feeds.FeedService;
-import org.mozilla.gecko.firstrun.FirstrunAnimationContainer;
-import org.mozilla.gecko.gfx.DynamicToolbarAnimator;
-import org.mozilla.gecko.gfx.DynamicToolbarAnimator.PinReason;
-import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
-import org.mozilla.gecko.home.BrowserSearch;
-import org.mozilla.gecko.home.HomeBanner;
-import org.mozilla.gecko.home.HomeConfig;
-import org.mozilla.gecko.home.HomeConfig.PanelType;
-import org.mozilla.gecko.home.HomeConfigPrefsBackend;
-import org.mozilla.gecko.home.HomeFragment;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenInBackgroundListener;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.home.HomePanelsManager;
-import org.mozilla.gecko.home.HomeScreen;
-import org.mozilla.gecko.home.SearchEngine;
-import org.mozilla.gecko.icons.Icons;
-import org.mozilla.gecko.media.VideoPlayer;
-import org.mozilla.gecko.menu.GeckoMenu;
-import org.mozilla.gecko.menu.GeckoMenuItem;
-import org.mozilla.gecko.mma.MmaDelegate;
-import org.mozilla.gecko.mozglue.GeckoLoader;
-import org.mozilla.gecko.mozglue.SafeIntent;
-import org.mozilla.gecko.notifications.NotificationHelper;
-import org.mozilla.gecko.overlays.ui.ShareDialog;
-import org.mozilla.gecko.permissions.Permissions;
-import org.mozilla.gecko.preferences.ClearOnShutdownPref;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.promotion.AddToHomeScreenPromotion;
-import org.mozilla.gecko.delegates.BookmarkStateChangeDelegate;
-import org.mozilla.gecko.promotion.ReaderViewBookmarkPromotion;
-import org.mozilla.gecko.prompts.Prompt;
-import org.mozilla.gecko.reader.SavedReaderViewHelper;
-import org.mozilla.gecko.reader.ReaderModeUtils;
-import org.mozilla.gecko.reader.ReadingListHelper;
-import org.mozilla.gecko.restrictions.Restrictable;
-import org.mozilla.gecko.restrictions.Restrictions;
-import org.mozilla.gecko.search.SearchEngineManager;
-import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
-import org.mozilla.gecko.tabqueue.TabQueueHelper;
-import org.mozilla.gecko.tabqueue.TabQueuePrompt;
-import org.mozilla.gecko.tabs.TabHistoryController;
-import org.mozilla.gecko.tabs.TabHistoryController.OnShowTabHistory;
-import org.mozilla.gecko.tabs.TabHistoryFragment;
-import org.mozilla.gecko.tabs.TabHistoryPage;
-import org.mozilla.gecko.tabs.TabsPanel;
-import org.mozilla.gecko.telemetry.TelemetryUploadService;
-import org.mozilla.gecko.telemetry.TelemetryCorePingDelegate;
-import org.mozilla.gecko.telemetry.measurements.SearchCountMeasurements;
-import org.mozilla.gecko.toolbar.AutocompleteHandler;
-import org.mozilla.gecko.toolbar.BrowserToolbar;
-import org.mozilla.gecko.toolbar.BrowserToolbar.TabEditingState;
-import org.mozilla.gecko.trackingprotection.TrackingProtectionPrompt;
-import org.mozilla.gecko.updater.PostUpdateHandler;
-import org.mozilla.gecko.updater.UpdateServiceHelper;
-import org.mozilla.gecko.util.ActivityUtils;
-import org.mozilla.gecko.util.Clipboard;
-import org.mozilla.gecko.util.ContextUtils;
-import org.mozilla.gecko.util.DrawableUtil;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.GamepadUtils;
-import org.mozilla.gecko.util.GeckoBundle;
-import org.mozilla.gecko.util.HardwareUtils;
-import org.mozilla.gecko.util.IntentUtils;
-import org.mozilla.gecko.util.MenuUtils;
-import org.mozilla.gecko.util.PrefUtils;
-import org.mozilla.gecko.util.StringUtils;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.util.WindowUtil;
-import org.mozilla.gecko.widget.ActionModePresenter;
-import org.mozilla.gecko.widget.AnchoredPopup;
-import org.mozilla.gecko.widget.AnimatedProgressBar;
-import org.mozilla.gecko.widget.GeckoActionProvider;
-
 import android.app.Activity;
 import android.app.AlertDialog;
+import android.app.DownloadManager;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
@@ -134,17 +29,21 @@ import android.graphics.drawable.BitmapD
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.nfc.NdefMessage;
 import android.nfc.NdefRecord;
 import android.nfc.NfcAdapter;
 import android.nfc.NfcEvent;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.StrictMode;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.UiThread;
 import android.support.design.widget.Snackbar;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.NotificationCompat;
 import android.support.v4.content.res.ResourcesCompat;
 import android.support.v4.view.MenuItemCompat;
 import android.text.TextUtils;
 import android.util.AttributeSet;
@@ -162,27 +61,125 @@ import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.ViewStub;
 import android.view.ViewTreeObserver;
 import android.view.Window;
 import android.view.animation.Interpolator;
 import android.widget.Button;
 import android.widget.ListView;
 import android.widget.ViewFlipper;
+
+import org.mozilla.gecko.AppConstants.Versions;
+import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
+import org.mozilla.gecko.Tabs.TabEvents;
+import org.mozilla.gecko.activitystream.ActivityStream;
+import org.mozilla.gecko.adjust.AdjustBrowserAppDelegate;
+import org.mozilla.gecko.animation.PropertyAnimator;
+import org.mozilla.gecko.annotation.RobocopTarget;
+import org.mozilla.gecko.bookmarks.BookmarkEditFragment;
+import org.mozilla.gecko.bookmarks.BookmarkUtils;
+import org.mozilla.gecko.bookmarks.EditBookmarkTask;
+import org.mozilla.gecko.cleanup.FileCleanupController;
+import org.mozilla.gecko.dawn.DawnHelper;
+import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.db.BrowserDB;
+import org.mozilla.gecko.db.SuggestedSites;
+import org.mozilla.gecko.delegates.BookmarkStateChangeDelegate;
+import org.mozilla.gecko.delegates.BrowserAppDelegate;
+import org.mozilla.gecko.delegates.OfflineTabStatusDelegate;
+import org.mozilla.gecko.delegates.ScreenshotDelegate;
+import org.mozilla.gecko.distribution.Distribution;
+import org.mozilla.gecko.distribution.DistributionStoreCallback;
+import org.mozilla.gecko.dlc.DownloadContentService;
+import org.mozilla.gecko.firstrun.FirstrunAnimationContainer;
+import org.mozilla.gecko.gfx.DynamicToolbarAnimator;
+import org.mozilla.gecko.gfx.DynamicToolbarAnimator.PinReason;
+import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
+import org.mozilla.gecko.home.BrowserSearch;
+import org.mozilla.gecko.home.HomeBanner;
+import org.mozilla.gecko.home.HomeConfig;
+import org.mozilla.gecko.home.HomeConfig.PanelType;
+import org.mozilla.gecko.home.HomeConfigPrefsBackend;
+import org.mozilla.gecko.home.HomeFragment;
+import org.mozilla.gecko.home.HomePager.OnUrlOpenInBackgroundListener;
+import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
+import org.mozilla.gecko.home.HomePanelsManager;
+import org.mozilla.gecko.home.HomeScreen;
+import org.mozilla.gecko.home.SearchEngine;
+import org.mozilla.gecko.icons.Icons;
+import org.mozilla.gecko.icons.IconsHelper;
+import org.mozilla.gecko.icons.decoders.FaviconDecoder;
+import org.mozilla.gecko.icons.decoders.IconDirectoryEntry;
+import org.mozilla.gecko.icons.decoders.LoadFaviconResult;
+import org.mozilla.gecko.media.VideoPlayer;
+import org.mozilla.gecko.menu.GeckoMenu;
+import org.mozilla.gecko.menu.GeckoMenuItem;
+import org.mozilla.gecko.mma.MmaDelegate;
+import org.mozilla.gecko.mozglue.GeckoLoader;
+import org.mozilla.gecko.mozglue.SafeIntent;
+import org.mozilla.gecko.notifications.NotificationHelper;
+import org.mozilla.gecko.overlays.ui.ShareDialog;
+import org.mozilla.gecko.permissions.Permissions;
+import org.mozilla.gecko.preferences.ClearOnShutdownPref;
+import org.mozilla.gecko.preferences.GeckoPreferences;
+import org.mozilla.gecko.promotion.AddToHomeScreenPromotion;
+import org.mozilla.gecko.promotion.ReaderViewBookmarkPromotion;
+import org.mozilla.gecko.prompts.Prompt;
+import org.mozilla.gecko.reader.ReaderModeUtils;
+import org.mozilla.gecko.reader.ReadingListHelper;
+import org.mozilla.gecko.reader.SavedReaderViewHelper;
+import org.mozilla.gecko.restrictions.Restrictable;
+import org.mozilla.gecko.restrictions.Restrictions;
+import org.mozilla.gecko.search.SearchEngineManager;
 import org.mozilla.gecko.switchboard.AsyncConfigLoader;
 import org.mozilla.gecko.switchboard.SwitchBoard;
+import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
+import org.mozilla.gecko.tabqueue.TabQueueHelper;
+import org.mozilla.gecko.tabqueue.TabQueuePrompt;
+import org.mozilla.gecko.tabs.TabHistoryController;
+import org.mozilla.gecko.tabs.TabHistoryController.OnShowTabHistory;
+import org.mozilla.gecko.tabs.TabHistoryFragment;
+import org.mozilla.gecko.tabs.TabHistoryPage;
+import org.mozilla.gecko.tabs.TabsPanel;
+import org.mozilla.gecko.telemetry.TelemetryCorePingDelegate;
+import org.mozilla.gecko.telemetry.TelemetryUploadService;
+import org.mozilla.gecko.telemetry.measurements.SearchCountMeasurements;
+import org.mozilla.gecko.toolbar.AutocompleteHandler;
+import org.mozilla.gecko.toolbar.BrowserToolbar;
+import org.mozilla.gecko.toolbar.BrowserToolbar.TabEditingState;
+import org.mozilla.gecko.trackingprotection.TrackingProtectionPrompt;
+import org.mozilla.gecko.updater.PostUpdateHandler;
+import org.mozilla.gecko.updater.UpdateServiceHelper;
+import org.mozilla.gecko.util.ActivityUtils;
+import org.mozilla.gecko.util.Clipboard;
+import org.mozilla.gecko.util.ContextUtils;
+import org.mozilla.gecko.util.DrawableUtil;
+import org.mozilla.gecko.util.EventCallback;
+import org.mozilla.gecko.util.GamepadUtils;
+import org.mozilla.gecko.util.GeckoBundle;
+import org.mozilla.gecko.util.HardwareUtils;
+import org.mozilla.gecko.util.IntentUtils;
+import org.mozilla.gecko.util.MenuUtils;
+import org.mozilla.gecko.util.PrefUtils;
+import org.mozilla.gecko.util.StringUtils;
+import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.util.WindowUtil;
+import org.mozilla.gecko.widget.ActionModePresenter;
+import org.mozilla.gecko.widget.AnchoredPopup;
+import org.mozilla.gecko.widget.AnimatedProgressBar;
+import org.mozilla.gecko.widget.GeckoActionProvider;
 import org.mozilla.gecko.widget.SplashScreen;
 
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.lang.reflect.Method;
 import java.net.URLEncoder;
+import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.regex.Pattern;
 
 import static org.mozilla.gecko.mma.MmaDelegate.NEW_TAB;
@@ -333,17 +330,16 @@ public class BrowserApp extends GeckoApp
 
     private final TelemetryCorePingDelegate mTelemetryCorePingDelegate = new TelemetryCorePingDelegate();
 
     private final List<BrowserAppDelegate> delegates = Collections.unmodifiableList(Arrays.asList(
             new AddToHomeScreenPromotion(),
             new ScreenshotDelegate(),
             new BookmarkStateChangeDelegate(),
             new ReaderViewBookmarkPromotion(),
-            new ContentNotificationsDelegate(),
             new PostUpdateHandler(),
             mTelemetryCorePingDelegate,
             new OfflineTabStatusDelegate(),
             new AdjustBrowserAppDelegate(mTelemetryCorePingDelegate)
     ));
 
     @NonNull
     private SearchEngineManager mSearchEngineManager; // Contains reference to Context - DO NOT LEAK!
@@ -1814,17 +1810,16 @@ public class BrowserApp extends GeckoApp
 
                 if (AppConstants.MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE &&
                         !IntentUtils.getIsInAutomationFromEnvironment(new SafeIntent(getIntent()))) {
                     // TODO: Better scheduling of DLC actions (Bug 1257492)
                     DownloadContentService.startSync(this);
                     DownloadContentService.startVerification(this);
                 }
 
-                FeedService.setup(this);
                 break;
 
             case "Accessibility:Enabled":
                 mDynamicToolbar.setAccessibilityEnabled(message.getBoolean("enabled"));
                 break;
 
             case "Menu:Open":
                 if (mBrowserToolbar.isEditing()) {
--- a/mobile/android/base/java/org/mozilla/gecko/Experiments.java
+++ b/mobile/android/base/java/org/mozilla/gecko/Experiments.java
@@ -20,21 +20,16 @@ import java.util.List;
  * https://github.com/mozilla-services/switchboard-experiments
  */
 public class Experiments {
     private static final String LOGTAG = "GeckoExperiments";
 
     // Show a system notification linking to a "What's New" page on app update.
     public static final String WHATSNEW_NOTIFICATION = "whatsnew-notification";
 
-    // Subscribe to known, bookmarked sites and show a notification if new content is available.
-    public static final String CONTENT_NOTIFICATIONS_12HRS = "content-notifications-12hrs";
-    public static final String CONTENT_NOTIFICATIONS_8AM = "content-notifications-8am";
-    public static final String CONTENT_NOTIFICATIONS_5PM = "content-notifications-5pm";
-
     // Onboarding: "Features and Story". These experiments are determined
     // on the client, they are not part of the server config.
     public static final String ONBOARDING3_A = "onboarding3-a"; // Control: No first run
     public static final String ONBOARDING3_B = "onboarding3-b"; // 4 static Feature + 1 dynamic slides
     public static final String ONBOARDING3_C = "onboarding3-c"; // Differentiating features slides
 
     // Synchronizing the catalog of downloadable content from Kinto
     public static final String DOWNLOAD_CONTENT_CATALOG_SYNC = "download-content-catalog-sync";
--- a/mobile/android/base/java/org/mozilla/gecko/db/LocalUrlAnnotations.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/LocalUrlAnnotations.java
@@ -5,22 +5,19 @@
 package org.mozilla.gecko.db;
 
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.net.Uri;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
-import android.util.Log;
 
-import org.json.JSONException;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.db.BrowserContract.UrlAnnotations.Key;
-import org.mozilla.gecko.feeds.subscriptions.FeedSubscription;
 
 public class LocalUrlAnnotations implements UrlAnnotations {
     private static final String LOGTAG = "LocalUrlAnnotations";
 
     private Uri urlAnnotationsTableWithProfile;
 
     public LocalUrlAnnotations(final String profile) {
         urlAnnotationsTableWithProfile = DBUtils.appendProfile(profile, BrowserContract.UrlAnnotations.CONTENT_URI);
@@ -92,75 +89,22 @@ public class LocalUrlAnnotations impleme
     public Cursor getWebsitesWithFeedUrl(ContentResolver cr) {
         return cr.query(urlAnnotationsTableWithProfile,
                 new String[] { BrowserContract.UrlAnnotations.URL },
                 BrowserContract.UrlAnnotations.KEY + " = ?",
                 new String[] { Key.FEED.getDbValue() },
                 null);
     }
 
-    /**
-     * Returns true if there's a subscription for this feed URL. False otherwise.
-     */
-    @Override
-    public boolean hasFeedSubscription(ContentResolver cr, String feedUrl) {
-        return hasResultsForSelection(cr,
-                BrowserContract.UrlAnnotations.URL + " = ? AND " + BrowserContract.UrlAnnotations.KEY + " = ?",
-                new String[]{feedUrl, Key.FEED_SUBSCRIPTION.getDbValue()});
-    }
-
-    /**
-     * Insert the given feed subscription (Mapping from feed URL to the subscription object).
-     */
-    @Override
-    public void insertFeedSubscription(ContentResolver cr, FeedSubscription subscription) {
-        try {
-            insertAnnotation(cr, subscription.getFeedUrl(), Key.FEED_SUBSCRIPTION, subscription.toJSON().toString());
-        } catch (JSONException e) {
-            Log.w(LOGTAG, "Could not serialize subscription");
-        }
-    }
-
-    /**
-     * Update the feed subscription with new values.
-     */
-    @Override
-    public void updateFeedSubscription(ContentResolver cr, FeedSubscription subscription) {
-        try {
-            updateAnnotation(cr, subscription.getFeedUrl(), Key.FEED_SUBSCRIPTION, subscription.toJSON().toString());
-        } catch (JSONException e) {
-            Log.w(LOGTAG, "Could not serialize subscription");
-        }
-    }
-
-    /**
-     * Delete the subscription for the feed URL.
-     */
-    @Override
-    public void deleteFeedSubscription(ContentResolver cr, FeedSubscription subscription) {
-        deleteAnnotation(cr, subscription.getFeedUrl(), Key.FEED_SUBSCRIPTION);
-    }
-
     private int deleteAnnotation(final ContentResolver cr, final String url, final Key key) {
         return cr.delete(urlAnnotationsTableWithProfile,
                 BrowserContract.UrlAnnotations.KEY + " = ? AND " + BrowserContract.UrlAnnotations.URL + " = ?",
                 new String[] { key.getDbValue(), url  });
     }
 
-    private int updateAnnotation(final ContentResolver cr, final String url, final Key key, final String value) {
-        ContentValues values = new ContentValues();
-        values.put(BrowserContract.UrlAnnotations.VALUE, value);
-        values.put(BrowserContract.UrlAnnotations.DATE_MODIFIED, System.currentTimeMillis());
-
-        return cr.update(urlAnnotationsTableWithProfile,
-                values,
-                BrowserContract.UrlAnnotations.KEY + " = ? AND " + BrowserContract.UrlAnnotations.URL + " = ?",
-                new String[]{key.getDbValue(), url});
-    }
-
     private void insertAnnotation(final ContentResolver cr, final String url, final Key key, final String value) {
         insertAnnotation(cr, url, key.getDbValue(), value);
     }
 
     @RobocopTarget
     @Override
     public void insertAnnotation(final ContentResolver cr, final String url, final String key, final String value) {
         final long creationTime = System.currentTimeMillis();
--- a/mobile/android/base/java/org/mozilla/gecko/db/UrlAnnotations.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/UrlAnnotations.java
@@ -1,33 +1,29 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.db;
 
 import android.content.ContentResolver;
 import android.database.Cursor;
+
 import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.feeds.subscriptions.FeedSubscription;
 
 public interface UrlAnnotations {
     @RobocopTarget void insertAnnotation(ContentResolver cr, String url, String key, String value);
 
     Cursor getScreenshots(ContentResolver cr);
     void insertScreenshot(ContentResolver cr, String pageUrl, String screenshotPath);
 
     Cursor getFeedSubscriptions(ContentResolver cr);
     Cursor getWebsitesWithFeedUrl(ContentResolver cr);
     void deleteFeedUrl(ContentResolver cr, String websiteUrl);
     boolean hasWebsiteForFeedUrl(ContentResolver cr, String feedUrl);
-    void deleteFeedSubscription(ContentResolver cr, FeedSubscription subscription);
-    void updateFeedSubscription(ContentResolver cr, FeedSubscription subscription);
-    boolean hasFeedSubscription(ContentResolver cr, String feedUrl);
-    void insertFeedSubscription(ContentResolver cr, FeedSubscription subscription);
     boolean hasFeedUrlForWebsite(ContentResolver cr, String websiteUrl);
     void insertFeedUrl(ContentResolver cr, String originUrl, String feedUrl);
 
     void insertReaderViewUrl(ContentResolver cr, String pageURL);
     void deleteReaderViewUrl(ContentResolver cr, String pageURL);
 
     /**
      * Did the user ever interact with this URL in regards to home screen shortcuts?
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/ContentNotificationsDelegate.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v4.app.NotificationManagerCompat;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.BrowserApp;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.delegates.BrowserAppDelegate;
-import org.mozilla.gecko.mozglue.SafeIntent;
-
-import java.util.List;
-
-/**
- * BrowserAppDelegate implementation that takes care of handling intents from content notifications.
- */
-public class ContentNotificationsDelegate extends BrowserAppDelegate {
-    // The application is opened from a content notification
-    public static final String ACTION_CONTENT_NOTIFICATION = AppConstants.ANDROID_PACKAGE_NAME + ".action.CONTENT_NOTIFICATION";
-
-    public static final String EXTRA_READ_BUTTON = "read_button";
-    public static final String EXTRA_URLS = "urls";
-
-    private static final String TELEMETRY_EXTRA_CONTENT_UPDATE = "content_update";
-    private static final String TELEMETRY_EXTRA_READ_NOW_BUTTON = TELEMETRY_EXTRA_CONTENT_UPDATE + "_read_now";
-
-    @Override
-    public void onCreate(BrowserApp browserApp, Bundle savedInstanceState) {
-        if (savedInstanceState != null) {
-            // This activity is getting restored: We do not want to handle the URLs in the Intent again. The browser
-            // will take care of restoring the tabs we already created.
-            return;
-        }
-
-
-        final Intent unsafeIntent = browserApp.getIntent();
-
-        // Nothing to do.
-        if (unsafeIntent == null) {
-            return;
-        }
-
-        final SafeIntent intent = new SafeIntent(unsafeIntent);
-
-        if (ACTION_CONTENT_NOTIFICATION.equals(intent.getAction())) {
-            openURLsFromIntent(browserApp, intent);
-        }
-    }
-
-    @Override
-    public void onNewIntent(BrowserApp browserApp, @NonNull final SafeIntent intent) {
-        if (ACTION_CONTENT_NOTIFICATION.equals(intent.getAction())) {
-            openURLsFromIntent(browserApp, intent);
-        }
-    }
-
-    private void openURLsFromIntent(BrowserApp browserApp, @NonNull final SafeIntent intent) {
-        final List<String> urls = intent.getStringArrayListExtra(EXTRA_URLS);
-        if (urls != null) {
-            browserApp.openUrls(urls);
-        }
-
-        Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(browserApp));
-
-        Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, TELEMETRY_EXTRA_CONTENT_UPDATE);
-
-        if (intent.getBooleanExtra(EXTRA_READ_BUTTON, false)) {
-            // "READ NOW" button in notification was clicked
-            Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.NOTIFICATION, TELEMETRY_EXTRA_READ_NOW_BUTTON);
-
-            // Android's "auto cancel" won't remove the notification when an action button is pressed. So we do it ourselves here.
-            NotificationManagerCompat.from(browserApp).cancel(R.id.websiteContentNotification);
-        } else {
-            // Notification was clicked
-            Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.NOTIFICATION, TELEMETRY_EXTRA_CONTENT_UPDATE);
-        }
-
-        Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(browserApp));
-    }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/FeedAlarmReceiver.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds;
-
-import android.content.Context;
-import android.content.Intent;
-import android.support.v4.content.WakefulBroadcastReceiver;
-import android.util.Log;
-
-/**
- * Broadcast receiver that will receive broadcasts from the AlarmManager and start the FeedService
- * with the given action.
- */
-public class FeedAlarmReceiver extends WakefulBroadcastReceiver {
-    private static final String LOGTAG = "FeedCheckAction";
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        final String action = intent.getAction();
-
-        Log.d(LOGTAG, "Received alarm with action: " + action);
-
-        final Intent serviceIntent = new Intent(context, FeedService.class);
-        serviceIntent.setAction(action);
-
-        startWakefulService(context, serviceIntent);
-    }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/FeedFetcher.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import org.mozilla.gecko.feeds.parser.Feed;
-import org.mozilla.gecko.feeds.parser.SimpleFeedParser;
-import org.mozilla.gecko.util.IOUtils;
-import org.mozilla.gecko.util.ProxySelector;
-
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.net.URI;
-import java.net.URISyntaxException;
-
-import ch.boye.httpclientandroidlib.util.TextUtils;
-
-/**
- * Helper class for fetching and parsing a feed.
- */
-public class FeedFetcher {
-    private static final int CONNECT_TIMEOUT = 15000;
-    private static final int READ_TIMEOUT = 15000;
-
-    public static class FeedResponse {
-        public final Feed feed;
-        public final String etag;
-        public final String lastModified;
-
-        public FeedResponse(Feed feed, String etag, String lastModified) {
-            this.feed = feed;
-            this.etag = etag;
-            this.lastModified = lastModified;
-        }
-    }
-
-    /**
-     * Fetch and parse a feed from the given URL. Will return null if fetching or parsing failed.
-     */
-    public static FeedResponse fetchAndParseFeed(String url) {
-        return fetchAndParseFeedIfModified(url, null, null);
-    }
-
-    /**
-     * Fetch and parse a feed from the given URL using the given ETag and "Last modified" value.
-     *
-     * Will return null if fetching or parsing failed. Will also return null if the feed has not
-     * changed (ETag / Last-Modified-Since).
-     *
-     * @param eTag The ETag from the last fetch or null if no ETag is available (will always fetch feed)
-     * @param lastModified The "Last modified" header from the last time the feed has been fetch or
-     *                     null if no value is available (will always fetch feed)
-     * @return A FeedResponse or null if no feed could be fetched (error or no new version available)
-     */
-    @Nullable
-    public static FeedResponse fetchAndParseFeedIfModified(@NonNull String url, @Nullable String eTag, @Nullable String lastModified) {
-        HttpURLConnection connection = null;
-        InputStream stream = null;
-
-        try {
-            connection = (HttpURLConnection) ProxySelector.openConnectionWithProxy(new URI(url));
-            connection.setInstanceFollowRedirects(true);
-            connection.setConnectTimeout(CONNECT_TIMEOUT);
-            connection.setReadTimeout(READ_TIMEOUT);
-
-            if (!TextUtils.isEmpty(eTag)) {
-                connection.setRequestProperty("If-None-Match", eTag);
-            }
-
-            if (!TextUtils.isEmpty(lastModified)) {
-                connection.setRequestProperty("If-Modified-Since", lastModified);
-            }
-
-            final int statusCode = connection.getResponseCode();
-
-            if (statusCode != HttpURLConnection.HTTP_OK) {
-                return null;
-            }
-
-            String responseEtag = connection.getHeaderField("ETag");
-            if (!TextUtils.isEmpty(responseEtag) && responseEtag.startsWith("W/")) {
-                // Weak ETag, get actual ETag value
-                responseEtag = responseEtag.substring(2);
-            }
-
-            final String updatedLastModified = connection.getHeaderField("Last-Modified");
-
-            stream = new BufferedInputStream(connection.getInputStream());
-
-            final SimpleFeedParser parser = new SimpleFeedParser();
-            final Feed feed = parser.parse(stream);
-
-            return new FeedResponse(feed, responseEtag, updatedLastModified);
-        } catch (IOException e) {
-            return null;
-        } catch (SimpleFeedParser.ParserException e) {
-            return null;
-        } catch (URISyntaxException e) {
-            return null;
-        } finally {
-            if (connection != null) {
-                connection.disconnect();
-            }
-            IOUtils.safeStreamClose(stream);
-        }
-    }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/FeedService.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds;
-
-import android.app.IntentService;
-import android.content.Context;
-import android.content.Intent;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.support.annotation.Nullable;
-import android.support.v4.net.ConnectivityManagerCompat;
-import android.util.Log;
-
-import org.mozilla.gecko.switchboard.SwitchBoard;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.feeds.action.FeedAction;
-import org.mozilla.gecko.feeds.action.CheckForUpdatesAction;
-import org.mozilla.gecko.feeds.action.EnrollSubscriptionsAction;
-import org.mozilla.gecko.feeds.action.SetupAlarmsAction;
-import org.mozilla.gecko.feeds.action.SubscribeToFeedAction;
-import org.mozilla.gecko.feeds.action.WithdrawSubscriptionsAction;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.Experiments;
-
-/**
- * Background service for subscribing to and checking website feeds to notify the user about updates.
- */
-public class FeedService extends IntentService {
-    private static final String LOGTAG = "GeckoFeedService";
-
-    public static final String ACTION_SETUP = AppConstants.ANDROID_PACKAGE_NAME + ".FEEDS.SETUP";
-    public static final String ACTION_SUBSCRIBE = AppConstants.ANDROID_PACKAGE_NAME + ".FEEDS.SUBSCRIBE";
-    public static final String ACTION_CHECK = AppConstants.ANDROID_PACKAGE_NAME + ".FEEDS.CHECK";
-    public static final String ACTION_ENROLL = AppConstants.ANDROID_PACKAGE_NAME + ".FEEDS.ENROLL";
-    public static final String ACTION_WITHDRAW = AppConstants.ANDROID_PACKAGE_NAME + ".FEEDS.WITHDRAW";
-
-    public static void setup(Context context) {
-        Intent intent = new Intent(context, FeedService.class);
-        intent.setAction(ACTION_SETUP);
-        context.startService(intent);
-    }
-
-    public static void subscribe(Context context, String feedUrl) {
-        Intent intent = new Intent(context, FeedService.class);
-        intent.setAction(ACTION_SUBSCRIBE);
-        intent.putExtra(SubscribeToFeedAction.EXTRA_FEED_URL, feedUrl);
-        context.startService(intent);
-    }
-
-    public FeedService() {
-        super(LOGTAG);
-    }
-
-    private BrowserDB browserDB;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-
-        browserDB = BrowserDB.from(this);
-    }
-
-    @Override
-    protected void onHandleIntent(Intent intent) {
-        try {
-            if (intent == null) {
-                return;
-            }
-
-            Log.d(LOGTAG, "Service started with action: " + intent.getAction());
-
-            if (!isInExperiment(this)) {
-                Log.d(LOGTAG, "Not in content notifications experiment. Skipping.");
-                return;
-            }
-
-            FeedAction action = createActionForIntent(intent);
-            if (action == null) {
-                Log.d(LOGTAG, "No action to process");
-                return;
-            }
-
-            if (action.requiresPreferenceEnabled() && !isPreferenceEnabled()) {
-                Log.d(LOGTAG, "Preference is disabled. Skipping.");
-                return;
-            }
-
-            if (action.requiresNetwork() && !isConnectedToUnmeteredNetwork()) {
-                // For now just skip if we are not connected or the network is metered. We do not want
-                // to use precious mobile traffic.
-                Log.d(LOGTAG, "Not connected to a network or network is metered. Skipping.");
-                return;
-            }
-
-            action.perform(browserDB, intent);
-        } finally {
-            FeedAlarmReceiver.completeWakefulIntent(intent);
-        }
-
-        Log.d(LOGTAG, "Done.");
-    }
-
-    @Nullable
-    private FeedAction createActionForIntent(Intent intent) {
-        final Context context = getApplicationContext();
-
-        switch (intent.getAction()) {
-            case ACTION_SETUP:
-                return new SetupAlarmsAction(context);
-
-            case ACTION_SUBSCRIBE:
-                return new SubscribeToFeedAction(context);
-
-            case ACTION_CHECK:
-                return new CheckForUpdatesAction(context);
-
-            case ACTION_ENROLL:
-                return new EnrollSubscriptionsAction(context);
-
-            case ACTION_WITHDRAW:
-                return new WithdrawSubscriptionsAction(context);
-
-            default:
-                throw new AssertionError("Unknown action: " + intent.getAction());
-        }
-    }
-
-    private boolean isConnectedToUnmeteredNetwork() {
-        ConnectivityManager manager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
-        NetworkInfo networkInfo = manager.getActiveNetworkInfo();
-        if (networkInfo == null || !networkInfo.isConnected()) {
-            return false;
-        }
-
-        return !ConnectivityManagerCompat.isActiveNetworkMetered(manager);
-    }
-
-    public static boolean isInExperiment(Context context) {
-        return SwitchBoard.isInExperiment(context, Experiments.CONTENT_NOTIFICATIONS_12HRS) ||
-               SwitchBoard.isInExperiment(context, Experiments.CONTENT_NOTIFICATIONS_5PM) ||
-               SwitchBoard.isInExperiment(context, Experiments.CONTENT_NOTIFICATIONS_8AM);
-    }
-
-    public static String getEnabledExperiment(Context context) {
-        String experiment = null;
-
-        if (SwitchBoard.isInExperiment(context, Experiments.CONTENT_NOTIFICATIONS_12HRS)) {
-            experiment = Experiments.CONTENT_NOTIFICATIONS_12HRS;
-        } else if (SwitchBoard.isInExperiment(context, Experiments.CONTENT_NOTIFICATIONS_8AM)) {
-            experiment = Experiments.CONTENT_NOTIFICATIONS_8AM;
-        } else if (SwitchBoard.isInExperiment(context, Experiments.CONTENT_NOTIFICATIONS_5PM)) {
-            experiment = Experiments.CONTENT_NOTIFICATIONS_5PM;
-        }
-
-        return experiment;
-    }
-
-    private boolean isPreferenceEnabled() {
-        return GeckoSharedPrefs.forApp(this).getBoolean(GeckoPreferences.PREFS_NOTIFICATIONS_CONTENT, true);
-    }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/action/CheckForUpdatesAction.java
+++ /dev/null
@@ -1,281 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.action;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.NotificationManagerCompat;
-import android.support.v4.content.ContextCompat;
-import android.text.format.DateFormat;
-
-import org.json.JSONException;
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.BrowserApp;
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.UrlAnnotations;
-import org.mozilla.gecko.feeds.ContentNotificationsDelegate;
-import org.mozilla.gecko.feeds.FeedFetcher;
-import org.mozilla.gecko.feeds.FeedService;
-import org.mozilla.gecko.feeds.parser.Feed;
-import org.mozilla.gecko.feeds.subscriptions.FeedSubscription;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.util.StringUtils;
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-/**
- * CheckForUpdatesAction: Check if feeds we subscribed to have new content available.
- */
-public class CheckForUpdatesAction extends FeedAction {
-    /**
-     * This extra will be added to Intents fired by the notification.
-     */
-    public static final String EXTRA_CONTENT_NOTIFICATION = "content-notification";
-
-    private final Context context;
-
-    public CheckForUpdatesAction(Context context) {
-        this.context = context;
-    }
-
-    @Override
-    public void perform(BrowserDB browserDB, Intent intent) {
-        final UrlAnnotations urlAnnotations = browserDB.getUrlAnnotations();
-        final ContentResolver resolver = context.getContentResolver();
-        final List<Feed> updatedFeeds = new ArrayList<>();
-
-        log("Checking feeds for updates..");
-
-        Cursor cursor = urlAnnotations.getFeedSubscriptions(resolver);
-        if (cursor == null) {
-            return;
-        }
-
-        try {
-            while (cursor.moveToNext()) {
-                FeedSubscription subscription = FeedSubscription.fromCursor(cursor);
-
-                FeedFetcher.FeedResponse response = checkFeedForUpdates(subscription);
-                if (response != null) {
-                    final Feed feed = response.feed;
-
-                    if (!hasBeenVisited(browserDB, feed.getLastItem().getURL())) {
-                        // Only notify about this update if the last item hasn't been visited yet.
-                        updatedFeeds.add(feed);
-                    } else {
-                        Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(context));
-                        Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL,
-                                TelemetryContract.Method.SERVICE,
-                                "content_update");
-                        Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(context));
-                    }
-
-                    urlAnnotations.updateFeedSubscription(resolver, subscription);
-                }
-            }
-        } catch (JSONException e) {
-            log("Could not deserialize subscription", e);
-        } finally {
-            cursor.close();
-        }
-
-        showNotification(updatedFeeds);
-    }
-
-    private FeedFetcher.FeedResponse checkFeedForUpdates(FeedSubscription subscription) {
-        log("Checking feed: " + subscription.getFeedTitle());
-
-        FeedFetcher.FeedResponse response = fetchFeed(subscription);
-        if (response == null) {
-            return null;
-        }
-
-        if (subscription.hasBeenUpdated(response)) {
-            log("* Feed has changed. New item: " + response.feed.getLastItem().getTitle());
-
-            subscription.update(response);
-
-            return response;
-
-        }
-
-        return null;
-    }
-
-    /**
-     * Returns true if this URL has been visited before.
-     *
-     * We do an exact match. So this can fail if the feed uses a different URL and redirects to
-     * content. But it's better than no checks at all.
-     */
-    private boolean hasBeenVisited(final BrowserDB browserDB, final String url) {
-        final Cursor cursor = browserDB.getHistoryForURL(context.getContentResolver(), url);
-        if (cursor == null) {
-            return false;
-        }
-
-        try {
-            if (cursor.moveToFirst()) {
-                return cursor.getInt(cursor.getColumnIndex(BrowserContract.History.VISITS)) > 0;
-            }
-        } finally {
-            cursor.close();
-        }
-
-        return false;
-    }
-
-    private void showNotification(List<Feed> updatedFeeds) {
-        final int feedCount = updatedFeeds.size();
-        if (feedCount == 0) {
-            return;
-        }
-
-        if (feedCount == 1) {
-            showNotificationForSingleUpdate(updatedFeeds.get(0));
-        } else {
-            showNotificationForMultipleUpdates(updatedFeeds);
-        }
-
-        Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(context));
-        Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.NOTIFICATION, "content_update");
-        Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(context));
-    }
-
-    private void showNotificationForSingleUpdate(Feed feed) {
-        final String date = DateFormat.getMediumDateFormat(context).format(new Date(feed.getLastItem().getTimestamp()));
-
-        final NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle()
-                .bigText(feed.getLastItem().getTitle())
-                .setBigContentTitle(feed.getTitle())
-                .setSummaryText(context.getString(R.string.content_notification_updated_on, date));
-
-        final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, createOpenIntent(feed), PendingIntent.FLAG_UPDATE_CURRENT);
-
-        final Notification notification = new NotificationCompat.Builder(context)
-                .setSmallIcon(R.drawable.ic_status_logo)
-                .setContentTitle(feed.getTitle())
-                .setContentText(feed.getLastItem().getTitle())
-                .setStyle(style)
-                .setColor(ContextCompat.getColor(context, R.color.fennec_ui_accent))
-                .setContentIntent(pendingIntent)
-                .setAutoCancel(true)
-                .addAction(createOpenAction(feed))
-                .addAction(createNotificationSettingsAction())
-                .build();
-
-        NotificationManagerCompat.from(context).notify(R.id.websiteContentNotification, notification);
-    }
-
-    private void showNotificationForMultipleUpdates(List<Feed> feeds) {
-        final NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
-        for (Feed feed : feeds) {
-            inboxStyle.addLine(StringUtils.stripScheme(feed.getLastItem().getURL(), StringUtils.UrlFlags.STRIP_HTTPS));
-        }
-        inboxStyle.setSummaryText(context.getString(R.string.content_notification_summary));
-
-        final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, createOpenIntent(feeds), PendingIntent.FLAG_UPDATE_CURRENT);
-
-        Notification notification = new NotificationCompat.Builder(context)
-                .setSmallIcon(R.drawable.ic_status_logo)
-                .setContentTitle(context.getString(R.string.content_notification_title_plural, feeds.size()))
-                .setContentText(context.getString(R.string.content_notification_summary))
-                .setStyle(inboxStyle)
-                .setColor(ContextCompat.getColor(context, R.color.fennec_ui_accent))
-                .setContentIntent(pendingIntent)
-                .setAutoCancel(true)
-                .addAction(createOpenAction(feeds))
-                .setNumber(feeds.size())
-                .addAction(createNotificationSettingsAction())
-                .build();
-
-        NotificationManagerCompat.from(context).notify(R.id.websiteContentNotification, notification);
-    }
-
-    private Intent createOpenIntent(Feed feed) {
-        final List<Feed> feeds = new ArrayList<>();
-        feeds.add(feed);
-
-        return createOpenIntent(feeds);
-    }
-
-    private Intent createOpenIntent(List<Feed> feeds) {
-        final ArrayList<String> urls = new ArrayList<>();
-        for (Feed feed : feeds) {
-            urls.add(feed.getLastItem().getURL());
-        }
-
-        final Intent intent = new Intent(context, BrowserApp.class);
-        intent.setAction(ContentNotificationsDelegate.ACTION_CONTENT_NOTIFICATION);
-        intent.putStringArrayListExtra(ContentNotificationsDelegate.EXTRA_URLS, urls);
-
-        return intent;
-    }
-
-    private NotificationCompat.Action createOpenAction(Feed feed) {
-        final List<Feed> feeds = new ArrayList<>();
-        feeds.add(feed);
-
-        return createOpenAction(feeds);
-    }
-
-    private NotificationCompat.Action createOpenAction(List<Feed> feeds) {
-        Intent intent = createOpenIntent(feeds);
-        intent.putExtra(ContentNotificationsDelegate.EXTRA_READ_BUTTON, true);
-
-        PendingIntent pendingIntent = PendingIntent.getActivity(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
-
-        return new NotificationCompat.Action(
-                R.drawable.open_in_browser,
-                context.getString(R.string.content_notification_action_read_now),
-                pendingIntent);
-    }
-
-    private NotificationCompat.Action createNotificationSettingsAction() {
-        final Intent intent = new Intent(GeckoApp.ACTION_LAUNCH_SETTINGS);
-        intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
-        intent.putExtra(EXTRA_CONTENT_NOTIFICATION, true);
-
-        GeckoPreferences.setResourceToOpen(intent, "preferences_notifications");
-
-        PendingIntent settingsIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
-
-        return new NotificationCompat.Action(
-                R.drawable.settings_notifications,
-                context.getString(R.string.content_notification_action_settings),
-                settingsIntent);
-    }
-
-    private FeedFetcher.FeedResponse fetchFeed(FeedSubscription subscription) {
-        return FeedFetcher.fetchAndParseFeedIfModified(
-                subscription.getFeedUrl(),
-                subscription.getETag(),
-                subscription.getLastModified()
-        );
-    }
-
-    @Override
-    public boolean requiresNetwork() {
-        return true;
-    }
-
-    @Override
-    public boolean requiresPreferenceEnabled() {
-        return true;
-    }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/action/EnrollSubscriptionsAction.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.action;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.text.TextUtils;
-
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.UrlAnnotations;
-import org.mozilla.gecko.feeds.FeedService;
-import org.mozilla.gecko.feeds.knownsites.KnownSiteBlogger;
-import org.mozilla.gecko.feeds.knownsites.KnownSite;
-import org.mozilla.gecko.feeds.knownsites.KnownSiteMedium;
-import org.mozilla.gecko.feeds.knownsites.KnownSiteTumblr;
-import org.mozilla.gecko.feeds.knownsites.KnownSiteWordpress;
-
-/**
- * EnrollSubscriptionsAction: Search for bookmarks of known sites we can subscribe to.
- */
-public class EnrollSubscriptionsAction extends FeedAction {
-    private static final String LOGTAG = "FeedEnrollAction";
-
-    private static final KnownSite[] knownSites = {
-        new KnownSiteMedium(),
-        new KnownSiteBlogger(),
-        new KnownSiteWordpress(),
-        new KnownSiteTumblr(),
-    };
-
-    private Context context;
-
-    public EnrollSubscriptionsAction(Context context) {
-        this.context = context;
-    }
-
-    @Override
-    public void perform(BrowserDB db, Intent intent) {
-        log("Searching for bookmarks to enroll in updates");
-
-        final ContentResolver contentResolver = context.getContentResolver();
-
-        for (KnownSite knownSite : knownSites) {
-            searchFor(db, contentResolver, knownSite);
-        }
-    }
-
-    @Override
-    public boolean requiresNetwork() {
-        return false;
-    }
-
-    @Override
-    public boolean requiresPreferenceEnabled() {
-        return true;
-    }
-
-    private void searchFor(BrowserDB db, ContentResolver contentResolver, KnownSite knownSite) {
-        final UrlAnnotations urlAnnotations = db.getUrlAnnotations();
-
-        final Cursor cursor = db.getBookmarksForPartialUrl(contentResolver, knownSite.getURLSearchString());
-        if (cursor == null) {
-            log("Nothing found (" + knownSite.getClass().getSimpleName() + ")");
-            return;
-        }
-
-        try {
-            log("Found " + cursor.getCount() + " websites");
-
-            while (cursor.moveToNext()) {
-
-                final String url = cursor.getString(cursor.getColumnIndex(BrowserContract.Bookmarks.URL));
-
-                log(" URL: " + url);
-
-                String feedUrl = knownSite.getFeedFromURL(url);
-                if (TextUtils.isEmpty(feedUrl)) {
-                    log("Could not determine feed for URL: " + url);
-                    return;
-                }
-
-                if (!urlAnnotations.hasFeedUrlForWebsite(contentResolver, url)) {
-                    urlAnnotations.insertFeedUrl(contentResolver, url, feedUrl);
-                }
-
-                if (!urlAnnotations.hasFeedSubscription(contentResolver, feedUrl)) {
-                    FeedService.subscribe(context, feedUrl);
-                }
-            }
-        } finally {
-            cursor.close();
-        }
-    }
-
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/action/FeedAction.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.action;
-
-import android.content.Intent;
-import android.util.Log;
-
-import org.mozilla.gecko.db.BrowserDB;
-
-/**
- * Interface for actions run by FeedService.
- */
-public abstract class FeedAction {
-    public static final boolean DEBUG_LOG = false;
-
-    /**
-     * Perform this action.
-     *
-     * @param browserDB database instance to perform the action.
-     * @param intent used to start the service.
-     */
-    public abstract void perform(BrowserDB browserDB, Intent intent);
-
-    /**
-     * Does this action require an active network connection?
-     */
-    public abstract boolean requiresNetwork();
-
-    /**
-     * Should this action only run if the preference is enabled?
-     */
-    public abstract boolean requiresPreferenceEnabled();
-
-    /**
-     * This method will swallow all log messages to avoid logging potential personal information.
-     *
-     * For debugging purposes set {@code DEBUG_LOG} to true.
-     */
-    public void log(String message) {
-        if (DEBUG_LOG) {
-            Log.d("Gecko" + getClass().getSimpleName(), message);
-        }
-    }
-
-    /**
-     * This method will swallow all log messages to avoid logging potential personal information.
-     *
-     * For debugging purposes set {@code DEBUG_LOG} to true.
-     */
-    public void log(String message, Throwable throwable) {
-        if (DEBUG_LOG) {
-            Log.d("Gecko" + getClass().getSimpleName(), message, throwable);
-        }
-    }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/action/SetupAlarmsAction.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.action;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.os.SystemClock;
-
-import org.mozilla.gecko.switchboard.SwitchBoard;
-
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.feeds.FeedAlarmReceiver;
-import org.mozilla.gecko.feeds.FeedService;
-import org.mozilla.gecko.Experiments;
-
-import java.text.DateFormat;
-import java.util.Calendar;
-
-/**
- * SetupAlarmsAction: Set up alarms to run various actions every now and then.
- */
-public class SetupAlarmsAction extends FeedAction {
-    private static final String LOGTAG = "FeedSetupAction";
-
-    private Context context;
-
-    public SetupAlarmsAction(Context context) {
-        this.context = context;
-    }
-
-    @Override
-    public void perform(BrowserDB browserDB, Intent intent) {
-        final AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
-
-        cancelPreviousAlarms(alarmManager);
-        scheduleAlarms(alarmManager);
-    }
-
-    @Override
-    public boolean requiresNetwork() {
-        return false;
-    }
-
-    @Override
-    public boolean requiresPreferenceEnabled() {
-        return false;
-    }
-
-    private void cancelPreviousAlarms(AlarmManager alarmManager) {
-        final PendingIntent withdrawIntent = getWithdrawPendingIntent();
-        alarmManager.cancel(withdrawIntent);
-
-        final PendingIntent enrollIntent = getEnrollPendingIntent();
-        alarmManager.cancel(enrollIntent);
-
-        final PendingIntent checkIntent = getCheckPendingIntent();
-        alarmManager.cancel(checkIntent);
-
-        log("Cancelled previous alarms");
-    }
-
-    private void scheduleAlarms(AlarmManager alarmManager) {
-        alarmManager.setInexactRepeating(
-                AlarmManager.ELAPSED_REALTIME,
-                SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_FIFTEEN_MINUTES,
-                AlarmManager.INTERVAL_DAY,
-                getWithdrawPendingIntent());
-
-        alarmManager.setInexactRepeating(
-                AlarmManager.ELAPSED_REALTIME,
-                SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_HALF_HOUR,
-                AlarmManager.INTERVAL_DAY,
-                getEnrollPendingIntent()
-        );
-
-        if (SwitchBoard.isInExperiment(context, Experiments.CONTENT_NOTIFICATIONS_12HRS)) {
-            scheduleUpdateCheckEvery12Hours(alarmManager);
-        }
-
-        if (SwitchBoard.isInExperiment(context, Experiments.CONTENT_NOTIFICATIONS_8AM)) {
-            scheduleUpdateAtFullHour(alarmManager, 8);
-        }
-
-        if (SwitchBoard.isInExperiment(context, Experiments.CONTENT_NOTIFICATIONS_5PM)) {
-            scheduleUpdateAtFullHour(alarmManager, 17);
-        }
-
-
-        log("Scheduled alarms");
-    }
-
-    private void scheduleUpdateCheckEvery12Hours(AlarmManager alarmManager) {
-        alarmManager.setInexactRepeating(
-                AlarmManager.ELAPSED_REALTIME,
-                SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_HOUR,
-                AlarmManager.INTERVAL_HALF_DAY,
-                getCheckPendingIntent()
-        );
-    }
-
-    private void scheduleUpdateAtFullHour(AlarmManager alarmManager, int hourOfDay) {
-        final Calendar calendar = Calendar.getInstance();
-
-        if (calendar.get(Calendar.HOUR_OF_DAY) >= hourOfDay) {
-            // This time has already passed today. Try again tomorrow.
-            calendar.add(Calendar.DAY_OF_MONTH, 1);
-        }
-
-        calendar.set(Calendar.HOUR_OF_DAY, hourOfDay);
-        calendar.set(Calendar.MINUTE, 0);
-        calendar.set(Calendar.SECOND, 0);
-        calendar.set(Calendar.MILLISECOND, 0);
-
-        alarmManager.setInexactRepeating(
-                AlarmManager.RTC,
-                calendar.getTimeInMillis(),
-                AlarmManager.INTERVAL_DAY,
-                getCheckPendingIntent()
-        );
-
-        log("Scheduled update alarm at " + DateFormat.getDateTimeInstance().format(calendar.getTime()));
-    }
-
-    private PendingIntent getWithdrawPendingIntent() {
-        Intent intent = new Intent(context, FeedAlarmReceiver.class);
-        intent.setAction(FeedService.ACTION_WITHDRAW);
-        return PendingIntent.getBroadcast(context, 0, intent, 0);
-    }
-
-    private PendingIntent getEnrollPendingIntent() {
-        Intent intent = new Intent(context, FeedAlarmReceiver.class);
-        intent.setAction(FeedService.ACTION_ENROLL);
-        return PendingIntent.getBroadcast(context, 0, intent, 0);
-    }
-
-    private PendingIntent getCheckPendingIntent() {
-        Intent intent = new Intent(context, FeedAlarmReceiver.class);
-        intent.setAction(FeedService.ACTION_CHECK);
-        return PendingIntent.getBroadcast(context, 0, intent, 0);
-    }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/action/SubscribeToFeedAction.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.action;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.UrlAnnotations;
-import org.mozilla.gecko.feeds.FeedFetcher;
-import org.mozilla.gecko.feeds.FeedService;
-import org.mozilla.gecko.feeds.subscriptions.FeedSubscription;
-
-/**
- * SubscribeToFeedAction: Try to fetch a feed and create a subscription if successful.
- */
-public class SubscribeToFeedAction extends FeedAction {
-    private static final String LOGTAG = "FeedSubscribeAction";
-
-    public static final String EXTRA_FEED_URL = "feed_url";
-
-    private Context context;
-
-    public SubscribeToFeedAction(Context context) {
-        this.context = context;
-    }
-
-    @Override
-    public void perform(BrowserDB browserDB, Intent intent) {
-        final UrlAnnotations urlAnnotations = browserDB.getUrlAnnotations();
-
-        final Bundle extras = intent.getExtras();
-        final String feedUrl = extras.getString(EXTRA_FEED_URL);
-
-        if (urlAnnotations.hasFeedSubscription(context.getContentResolver(), feedUrl)) {
-            log("Already subscribed to " + feedUrl + ". Skipping.");
-            return;
-        }
-
-        log("Subscribing to feed: " + feedUrl);
-
-        subscribe(urlAnnotations, feedUrl);
-    }
-
-    @Override
-    public boolean requiresNetwork() {
-        return true;
-    }
-
-    @Override
-    public boolean requiresPreferenceEnabled() {
-        return true;
-    }
-
-    private void subscribe(UrlAnnotations urlAnnotations, String feedUrl) {
-        FeedFetcher.FeedResponse response = FeedFetcher.fetchAndParseFeed(feedUrl);
-        if (response == null) {
-            log(String.format("Could not fetch feed (%s). Not subscribing for now.", feedUrl));
-            return;
-        }
-
-        log("Subscribing to feed: " + response.feed.getTitle());
-        log("          Last item: " + response.feed.getLastItem().getTitle());
-
-        final FeedSubscription subscription = FeedSubscription.create(feedUrl, response);
-
-        urlAnnotations.insertFeedSubscription(context.getContentResolver(), subscription);
-
-        Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(context));
-        Telemetry.sendUIEvent(TelemetryContract.Event.SAVE, TelemetryContract.Method.SERVICE, "content_update");
-        Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(context));
-    }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/action/WithdrawSubscriptionsAction.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.action;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-
-import org.json.JSONException;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.UrlAnnotations;
-import org.mozilla.gecko.feeds.FeedService;
-import org.mozilla.gecko.feeds.subscriptions.FeedSubscription;
-
-/**
- * WithdrawSubscriptionsAction: Look for feeds to unsubscribe from.
- */
-public class WithdrawSubscriptionsAction extends FeedAction {
-    private static final String LOGTAG = "FeedWithdrawAction";
-
-    private Context context;
-
-    public WithdrawSubscriptionsAction(Context context) {
-        this.context = context;
-    }
-
-    @Override
-    public void perform(BrowserDB browserDB, Intent intent) {
-        log("Searching for subscriptions to remove..");
-
-        final UrlAnnotations urlAnnotations = browserDB.getUrlAnnotations();
-        final ContentResolver resolver = context.getContentResolver();
-
-        removeFeedsOfUnknownUrls(browserDB, urlAnnotations, resolver);
-        removeSubscriptionsOfRemovedFeeds(urlAnnotations, resolver);
-    }
-
-    /**
-     * Search for website URLs with a feed assigned. Remove entry if website URL is not known anymore:
-     * For now this means the website is not bookmarked.
-     */
-    private void removeFeedsOfUnknownUrls(BrowserDB browserDB, UrlAnnotations urlAnnotations, ContentResolver resolver) {
-        Cursor cursor = urlAnnotations.getWebsitesWithFeedUrl(resolver);
-        if (cursor == null) {
-            return;
-        }
-
-        try {
-            while (cursor.moveToNext()) {
-                final String url = cursor.getString(cursor.getColumnIndex(BrowserContract.UrlAnnotations.URL));
-
-                if (!browserDB.isBookmark(resolver, url)) {
-                    log("Removing feed for unknown URL: " + url);
-
-                    urlAnnotations.deleteFeedUrl(resolver, url);
-                }
-            }
-        } finally {
-            cursor.close();
-        }
-    }
-
-    /**
-     * Remove subscriptions of feed URLs that are not assigned to a website URL (anymore).
-     */
-    private void removeSubscriptionsOfRemovedFeeds(UrlAnnotations urlAnnotations, ContentResolver resolver) {
-        Cursor cursor = urlAnnotations.getFeedSubscriptions(resolver);
-        if (cursor == null) {
-            return;
-        }
-
-        try {
-            while (cursor.moveToNext()) {
-                final FeedSubscription subscription = FeedSubscription.fromCursor(cursor);
-
-                if (!urlAnnotations.hasWebsiteForFeedUrl(resolver, subscription.getFeedUrl())) {
-                    log("Removing subscription for feed: " + subscription.getFeedUrl());
-
-                    urlAnnotations.deleteFeedSubscription(resolver, subscription);
-
-                    Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(context));
-                    Telemetry.sendUIEvent(TelemetryContract.Event.UNSAVE, TelemetryContract.Method.SERVICE, "content_update");
-                    Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(context));
-                }
-            }
-        } catch (JSONException e) {
-            log("Could not deserialize subscription", e);
-        } finally {
-            cursor.close();
-        }
-    }
-
-    @Override
-    public boolean requiresNetwork() {
-        return false;
-    }
-
-    @Override
-    public boolean requiresPreferenceEnabled() {
-        return true;
-    }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSite.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.knownsites;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-/**
- * A site we know and for which we can guess the feed URL from an arbitrary URL.
- */
-public interface KnownSite {
-    /**
-     * Get a search string to find URLs of this site in our database. This search string is usually
-     * a partial domain / URL.
-     *
-     * For example we could return "medium.com" to find all URLs that contain this string. This could
-     * obviously find URLs that are not actually medium.com sites. This is acceptable as long as
-     * getFeedFromURL() can handle these inputs and either returns a feed for valid URLs or null for
-     * other matches that are not related to this site.
-     */
-    @NonNull String getURLSearchString();
-
-    /**
-     * Get the Feed URL for this URL. For a known site we can "guess" the feed URL from an URL
-     * pointing to any page. The input URL will be a result from the database found with the value
-     * returned by getURLSearchString().
-     *
-     * Example:
-     * - Input:  https://medium.com/@antlam/ux-thoughts-for-2016-1fc1d6e515e8
-     * - Output: https://medium.com/feed/@antlam
-     *
-     * @return the url representing a feed, or null if a feed could not be determined.
-     */
-    @Nullable String getFeedFromURL(String url);
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSiteBlogger.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.knownsites;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Blogger.com
- */
-public class KnownSiteBlogger implements KnownSite {
-    @Override
-    public String getURLSearchString() {
-        return ".blogspot.com";
-    }
-
-    @Override
-    public String getFeedFromURL(String url) {
-        Pattern pattern = Pattern.compile("https?://(www\\.)?(.*?)\\.blogspot\\.com(/.*)?");
-        Matcher matcher = pattern.matcher(url);
-        if (matcher.matches()) {
-            return String.format("https://%s.blogspot.com/feeds/posts/default", matcher.group(2));
-        }
-        return null;
-    }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSiteMedium.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.knownsites;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Medium.com
- */
-public class KnownSiteMedium implements KnownSite {
-    @Override
-    public String getURLSearchString() {
-        return "://medium.com/";
-    }
-
-    @Override
-    public String getFeedFromURL(String url) {
-        Pattern pattern = Pattern.compile("https?://medium.com/([^/]+)(/.*)?");
-        Matcher matcher = pattern.matcher(url);
-        if (matcher.matches()) {
-            return String.format("https://medium.com/feed/%s", matcher.group(1));
-        }
-        return null;
-    }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSiteTumblr.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.knownsites;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Tumblr.com
- */
-public class KnownSiteTumblr implements KnownSite {
-    @Override
-    public String getURLSearchString() {
-        return ".tumblr.com";
-    }
-
-    @Override
-    public String getFeedFromURL(String url) {
-        final Pattern pattern = Pattern.compile("https?://(.*?).tumblr.com(/.*)?");
-        final Matcher matcher = pattern.matcher(url);
-        if (matcher.matches()) {
-            final String username = matcher.group(1);
-            if (username.equals("www")) {
-                return null;
-            }
-            return "http://" + username + ".tumblr.com/rss";
-        }
-        return null;
-    }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSiteWordpress.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package org.mozilla.gecko.feeds.knownsites;
-
-import android.support.annotation.NonNull;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Wordpress.com
- */
-public class KnownSiteWordpress implements KnownSite {
-    @Override
-    public String getURLSearchString() {
-        return ".wordpress.com";
-    }
-
-    @Override
-    public String getFeedFromURL(String url) {
-        Pattern pattern = Pattern.compile("https?://(.*?).wordpress.com(/.*)?");
-        Matcher matcher = pattern.matcher(url);
-        if (matcher.matches()) {
-            return "https://" + matcher.group(1) + ".wordpress.com/feed/";
-        }
-        return null;
-    }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/parser/Feed.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.parser;
-
-import ch.boye.httpclientandroidlib.util.TextUtils;
-
-public class Feed {
-    private String title;
-    private String websiteURL;
-    private String feedURL;
-    private Item lastItem;
-
-    public static Feed create(String title, String websiteURL, String feedURL, Item lastItem) {
-        Feed feed = new Feed();
-
-        feed.setTitle(title);
-        feed.setWebsiteURL(websiteURL);
-        feed.setFeedURL(feedURL);
-        feed.setLastItem(lastItem);
-
-        return feed;
-    }
-
-    /* package-private */ Feed() {}
-
-    /* package-private */ void setTitle(String title) {
-        this.title = title;
-    }
-
-    /* package-private */ void setWebsiteURL(String websiteURL) {
-        this.websiteURL = websiteURL;
-    }
-
-    /* package-private */ void setFeedURL(String feedURL) {
-        this.feedURL = feedURL;
-    }
-
-    /* package-private */ void setLastItem(Item lastItem) {
-        this.lastItem = lastItem;
-    }
-
-    /**
-     * Is this feed object sufficiently complete so that we can use it?
-     */
-    /* package-private */ boolean isSufficientlyComplete() {
-        return !TextUtils.isEmpty(title) &&
-                lastItem != null &&
-                !TextUtils.isEmpty(lastItem.getURL()) &&
-                !TextUtils.isEmpty(lastItem.getTitle());
-    }
-
-    public String getTitle() {
-        return title;
-    }
-
-    public String getWebsiteURL() {
-        return websiteURL;
-    }
-
-    public String getFeedURL() {
-        return feedURL;
-    }
-
-    public Item getLastItem() {
-        return lastItem;
-    }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/parser/Item.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.parser;
-
-public class Item {
-    private String title;
-    private String url;
-    private long timestamp;
-
-    public static Item create(String title, String url, long timestamp) {
-        Item item = new Item();
-
-        item.setTitle(title);
-        item.setURL(url);
-        item.setTimestamp(timestamp);
-
-        return item;
-    }
-
-    /* package-private */ void setTitle(String title) {
-        this.title = title;
-    }
-
-    /* package-private */ void setURL(String url) {
-        this.url = url;
-    }
-
-    /* package-private */ void setTimestamp(long timestamp) {
-        this.timestamp = timestamp;
-    }
-
-    public String getTitle() {
-        return title;
-    }
-
-    public String getURL() {
-        return url;
-    }
-
-    /**
-     * @return the number of milliseconds since Jan. 1, 1970, midnight GMT.
-     */
-    public long getTimestamp() {
-        return timestamp;
-    }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/parser/SimpleFeedParser.java
+++ /dev/null
@@ -1,367 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.parser;
-
-import android.util.Log;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlPullParserFactory;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-
-import ch.boye.httpclientandroidlib.util.TextUtils;
-
-/**
- * A super simple feed parser written for implementing "content notifications". This XML Pull Parser
- * can read ATOM and RSS feeds and returns an object describing the feed and the latest entry.
- */
-public class SimpleFeedParser {
-    /**
-     * Generic exception that's thrown by the parser whenever a stream cannot be parsed.
-     */
-    public static class ParserException extends Exception {
-        private static final long serialVersionUID = -6119538440219805603L;
-
-        public ParserException(Throwable cause) {
-            super(cause);
-        }
-
-        public ParserException(String message) {
-            super(message);
-        }
-    }
-
-    private static final String LOGTAG = "Gecko/FeedParser";
-
-    private static final String TAG_RSS = "rss";
-    private static final String TAG_FEED = "feed";
-    private static final String TAG_RDF = "RDF";
-    private static final String TAG_TITLE = "title";
-    private static final String TAG_ITEM = "item";
-    private static final String TAG_LINK = "link";
-    private static final String TAG_ENTRY = "entry";
-    private static final String TAG_PUBDATE = "pubDate";
-    private static final String TAG_UPDATED = "updated";
-    private static final String TAG_DATE = "date";
-    private static final String TAG_SOURCE = "source";
-    private static final String TAG_IMAGE = "image";
-    private static final String TAG_CONTENT = "content";
-
-    private class ParserState {
-        public Feed feed;
-        public Item currentItem;
-        public boolean isRSS;
-        public boolean isATOM;
-        public boolean inSource;
-        public boolean inImage;
-        public boolean inContent;
-    }
-
-    public Feed parse(InputStream in) throws ParserException, IOException {
-        final ParserState state = new ParserState();
-
-        try {
-            final XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
-            factory.setNamespaceAware(true);
-
-            XmlPullParser parser = factory.newPullParser();
-            parser.setInput(in, null);
-
-            int eventType = parser.getEventType();
-
-            while (eventType != XmlPullParser.END_DOCUMENT) {
-                switch (eventType) {
-                    case XmlPullParser.START_DOCUMENT:
-                        handleStartDocument(state);
-                        break;
-
-                    case XmlPullParser.START_TAG:
-                        handleStartTag(parser, state);
-                        break;
-
-                    case XmlPullParser.END_TAG:
-                        handleEndTag(parser, state);
-                        break;
-                }
-
-                eventType = parser.next();
-            }
-        } catch (XmlPullParserException e) {
-            throw new ParserException(e);
-        }
-
-        if (!state.feed.isSufficientlyComplete()) {
-            throw new ParserException("Feed is not sufficiently complete");
-        }
-
-        return state.feed;
-    }
-
-    private void handleStartDocument(ParserState state) {
-        state.feed = new Feed();
-    }
-
-    private void handleStartTag(XmlPullParser parser, ParserState state) throws IOException, XmlPullParserException {
-        switch (parser.getName()) {
-            case TAG_RSS:
-                state.isRSS = true;
-                break;
-
-            case TAG_FEED:
-                state.isATOM = true;
-                break;
-
-            case TAG_RDF:
-                // This is a RSS 1.0 feed
-                state.isRSS = true;
-                break;
-
-            case TAG_ITEM:
-            case TAG_ENTRY:
-                state.currentItem = new Item();
-                break;
-
-            case TAG_TITLE:
-                handleTitleStartTag(parser, state);
-                break;
-
-            case TAG_LINK:
-                handleLinkStartTag(parser, state);
-                break;
-
-            case TAG_PUBDATE:
-                handlePubDateStartTag(parser, state);
-                break;
-
-            case TAG_UPDATED:
-                handleUpdatedStartTag(parser, state);
-                break;
-
-            case TAG_DATE:
-                handleDateStartTag(parser, state);
-                break;
-
-            case TAG_SOURCE:
-                state.inSource = true;
-                break;
-
-            case TAG_IMAGE:
-                state.inImage = true;
-                break;
-
-            case TAG_CONTENT:
-                state.inContent = true;
-                break;
-        }
-    }
-
-    private void handleEndTag(XmlPullParser parser, ParserState state) {
-        switch (parser.getName()) {
-            case TAG_ITEM:
-            case TAG_ENTRY:
-                handleItemOrEntryREndTag(state);
-                break;
-
-            case TAG_SOURCE:
-                state.inSource = false;
-                break;
-
-            case TAG_IMAGE:
-                state.inImage = false;
-                break;
-
-            case TAG_CONTENT:
-                state.inContent = false;
-                break;
-        }
-    }
-
-    private void handleTitleStartTag(XmlPullParser parser, ParserState state) throws IOException, XmlPullParserException {
-        if (state.inSource || state.inImage || state.inContent) {
-            // We do not care about titles in <source>, <image> or <media> tags.
-            return;
-        }
-
-        String title = getTextUntilEndTag(parser, TAG_TITLE);
-
-        title = title.replaceAll("[\r\n]", " ");
-        title = title.replaceAll("  +", " ");
-
-        if (state.currentItem != null) {
-            state.currentItem.setTitle(title);
-        } else {
-            state.feed.setTitle(title);
-        }
-    }
-
-    private void handleLinkStartTag(XmlPullParser parser, ParserState state) throws IOException, XmlPullParserException {
-        if (state.inSource || state.inImage) {
-            // We do not care about links in <source> or <image> tags.
-            return;
-        }
-
-        Map<String, String> attributes = fetchAttributes(parser);
-
-        if (attributes.size() > 0) {
-            String rel = attributes.get("rel");
-
-            if (state.currentItem == null && "self".equals(rel)) {
-                state.feed.setFeedURL(attributes.get("href"));
-                return;
-            }
-
-            if (rel == null || "alternate".equals(rel)) {
-                String type = attributes.get("type");
-                if (type == null || type.equals("text/html")) {
-                    String link = attributes.get("href");
-                    if (TextUtils.isEmpty(link)) {
-                        return;
-                    }
-
-                    if (state.currentItem != null) {
-                        state.currentItem.setURL(link);
-                    } else {
-                        state.feed.setWebsiteURL(link);
-                    }
-
-                    return;
-                }
-            }
-        }
-
-        if (state.isRSS) {
-            String link = getTextUntilEndTag(parser, TAG_LINK);
-            if (TextUtils.isEmpty(link)) {
-                return;
-            }
-
-            if (state.currentItem != null) {
-                state.currentItem.setURL(link);
-            } else {
-                state.feed.setWebsiteURL(link);
-            }
-        }
-    }
-
-    private void handleItemOrEntryREndTag(ParserState state) {
-        if (state.feed.getLastItem() == null || state.feed.getLastItem().getTimestamp() < state.currentItem.getTimestamp()) {
-            // Only set this item as "last item" if we do not have an item yet or this item is newer.
-            state.feed.setLastItem(state.currentItem);
-        }
-
-        state.currentItem = null;
-    }
-
-    private void handlePubDateStartTag(XmlPullParser parser, ParserState state) throws IOException, XmlPullParserException {
-        if (state.currentItem == null) {
-            return;
-        }
-
-        String pubDate = getTextUntilEndTag(parser, TAG_PUBDATE);
-        if (TextUtils.isEmpty(pubDate)) {
-            return;
-        }
-
-        // RFC-822
-        SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
-
-        updateCurrentItemTimestamp(state, pubDate, format);
-    }
-
-    private void handleUpdatedStartTag(XmlPullParser parser, ParserState state) throws IOException, XmlPullParserException {
-        if (state.inSource) {
-            // We do not care about stuff in <source> tags.
-            return;
-        }
-
-        if (state.currentItem == null) {
-            // We are only interested in <updated> values of feed items.
-            return;
-        }
-
-        String updated = getTextUntilEndTag(parser, TAG_UPDATED);
-        if (TextUtils.isEmpty(updated)) {
-            return;
-        }
-
-        SimpleDateFormat[] formats = new SimpleDateFormat[] {
-            new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US),
-            new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US)
-        };
-
-        // Fix timezones SimpleDateFormat can't parse:
-        // 2016-01-26T18:56:54Z -> 2016-01-26T18:56:54+0000 (Timezone: Z -> +0000)
-        updated = updated.replaceFirst("Z$", "+0000");
-        // 2016-01-26T18:56:54+01:00 -> 2016-01-26T18:56:54+0100 (Timezone: +01:00 -> +0100)
-        updated = updated.replaceFirst("([0-9]{2})([\\+\\-])([0-9]{2}):([0-9]{2})$", "$1$2$3$4");
-
-        updateCurrentItemTimestamp(state, updated, formats);
-    }
-
-    private void handleDateStartTag(XmlPullParser parser, ParserState state) throws IOException, XmlPullParserException {
-        if (state.currentItem == null) {
-            // We are only interested in <updated> values of feed items.
-            return;
-        }
-
-        String text = getTextUntilEndTag(parser, TAG_DATE);
-        if (TextUtils.isEmpty(text)) {
-            return;
-        }
-
-        // Fix timezones SimpleDateFormat can't parse:
-        // 2016-01-26T18:56:54+00:00 -> 2016-01-26T18:56:54+0000
-        text = text.replaceFirst("([0-9]{2})([\\+\\-])([0-9]{2}):([0-9]{2})$", "$1$2$3$4");
-
-        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US);
-
-        updateCurrentItemTimestamp(state, text, format);
-    }
-
-    private void updateCurrentItemTimestamp(ParserState state, String text, SimpleDateFormat... formats) {
-        for (SimpleDateFormat format : formats) {
-            try {
-                Date date = format.parse(text);
-                state.currentItem.setTimestamp(date.getTime());
-                return;
-            } catch (ParseException e) {
-                Log.w(LOGTAG, "Could not parse 'updated': " + text);
-            }
-        }
-    }
-
-    private Map<String, String> fetchAttributes(XmlPullParser parser) {
-        Map<String, String> attributes = new HashMap<>();
-
-        for (int i = 0; i < parser.getAttributeCount(); i++) {
-            attributes.put(parser.getAttributeName(i), parser.getAttributeValue(i));
-        }
-
-        return attributes;
-    }
-
-    private String getTextUntilEndTag(XmlPullParser parser, String tag) throws IOException, XmlPullParserException {
-        StringBuilder builder = new StringBuilder();
-
-        while (parser.next() != XmlPullParser.END_DOCUMENT) {
-            if (parser.getEventType() == XmlPullParser.TEXT) {
-                builder.append(parser.getText());
-            } else if (parser.getEventType() == XmlPullParser.END_TAG && tag.equals(parser.getName())) {
-                break;
-            }
-        }
-
-        return builder.toString().trim();
-    }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/subscriptions/FeedSubscription.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.subscriptions;
-
-import android.database.Cursor;
-import android.text.TextUtils;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.feeds.FeedFetcher;
-import org.mozilla.gecko.feeds.parser.Item;
-
-/**
- * An object describing a subscription and containing some meta data about the last time we fetched
- * the feed.
- */
-public class FeedSubscription {
-    private static final String JSON_KEY_FEED_TITLE = "feed_title";
-    private static final String JSON_KEY_LAST_ITEM_TITLE = "last_item_title";
-    private static final String JSON_KEY_LAST_ITEM_URL = "last_item_url";
-    private static final String JSON_KEY_LAST_ITEM_TIMESTAMP = "last_item_timestamp";
-    private static final String JSON_KEY_ETAG = "etag";
-    private static final String JSON_KEY_LAST_MODIFIED = "last_modified";
-
-    private String feedUrl;
-    private String feedTitle;
-    private String lastItemTitle;
-    private String lastItemUrl;
-    private long lastItemTimestamp;
-    private String etag;
-    private String lastModified;
-
-    public static FeedSubscription create(String feedUrl, FeedFetcher.FeedResponse response) {
-        FeedSubscription subscription = new FeedSubscription();
-        subscription.feedUrl = feedUrl;
-
-        subscription.update(response);
-
-        return subscription;
-    }
-
-    public static FeedSubscription fromCursor(Cursor cursor) throws JSONException {
-        final FeedSubscription subscription = new FeedSubscription();
-        subscription.feedUrl = cursor.getString(cursor.getColumnIndex(BrowserContract.UrlAnnotations.URL));
-
-        final String value = cursor.getString(cursor.getColumnIndex(BrowserContract.UrlAnnotations.VALUE));
-        subscription.fromJSON(new JSONObject(value));
-
-        return subscription;
-    }
-
-    private void fromJSON(JSONObject object) throws JSONException {
-        feedTitle = object.getString(JSON_KEY_FEED_TITLE);
-        lastItemTitle = object.getString(JSON_KEY_LAST_ITEM_TITLE);
-        lastItemUrl = object.getString(JSON_KEY_LAST_ITEM_URL);
-        lastItemTimestamp = object.getLong(JSON_KEY_LAST_ITEM_TIMESTAMP);
-        etag = object.optString(JSON_KEY_ETAG);
-        lastModified = object.optString(JSON_KEY_LAST_MODIFIED);
-    }
-
-    public void update(FeedFetcher.FeedResponse response) {
-        feedTitle = response.feed.getTitle();
-        lastItemTitle = response.feed.getLastItem().getTitle();
-        lastItemUrl = response.feed.getLastItem().getURL();
-        lastItemTimestamp = response.feed.getLastItem().getTimestamp();
-        etag = response.etag;
-        lastModified = response.lastModified;
-    }
-
-    /**
-     * Guesstimate if this response is a newer representation of the feed.
-     */
-    public boolean hasBeenUpdated(FeedFetcher.FeedResponse response) {
-        final Item responseItem = response.feed.getLastItem();
-
-        if (responseItem.getTimestamp() > lastItemTimestamp) {
-            // The timestamp is from a newer date so we expect that this item is a new item. But this
-            // could also mean that the timestamp of an already existing item has been updated. We
-            // accept that and assume that the content will have changed too in this case.
-            return true;
-        }
-
-        if (responseItem.getTimestamp() == lastItemTimestamp && responseItem.getTimestamp() != 0) {
-            // We have a timestamp that is not zero and this item has still the timestamp: It's very
-            // likely that we are looking at the same item. We assume this is not new content.
-            return false;
-        }
-
-        if (!responseItem.getURL().equals(lastItemUrl)) {
-            // The URL changed: It is very likely that this is a new item. At least it has been updated
-            // in a way that we just treat it as new content here.
-            return true;
-        }
-
-        return false;
-    }
-
-    public String getFeedUrl() {
-        return feedUrl;
-    }
-
-    public String getFeedTitle() {
-        return feedTitle;
-    }
-
-    public String getETag() {
-        return etag;
-    }
-
-    public String getLastModified() {
-        return lastModified;
-    }
-
-    public JSONObject toJSON() throws JSONException {
-        JSONObject object = new JSONObject();
-
-        object.put(JSON_KEY_FEED_TITLE, feedTitle);
-        object.put(JSON_KEY_LAST_ITEM_TITLE, lastItemTitle);
-        object.put(JSON_KEY_LAST_ITEM_URL, lastItemUrl);
-        object.put(JSON_KEY_LAST_ITEM_TIMESTAMP, lastItemTimestamp);
-        object.put(JSON_KEY_ETAG, etag);
-        object.put(JSON_KEY_LAST_MODIFIED, lastModified);
-
-        return object;
-    }
-}
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferenceFragment.java
+++ b/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferenceFragment.java
@@ -121,17 +121,16 @@ public class GeckoPreferenceFragment ext
             return getString(R.string.pref_category_privacy_short);
         }
 
         // We can launch this category from the the magnifying glass in the quick search bar.
         if (res == R.xml.preferences_search) {
             return getString(R.string.pref_category_search);
         }
 
-        // Launched as action from content notifications.
         if (res == R.xml.preferences_notifications) {
             return getString(R.string.pref_category_notifications);
         }
 
         return null;
     }
 
     /**
@@ -153,17 +152,16 @@ public class GeckoPreferenceFragment ext
             return R.id.pref_header_privacy;
         }
 
         // We can launch this category from the the magnifying glass in the quick search bar.
         if (res == R.xml.preferences_search) {
             return R.id.pref_header_search;
         }
 
-        // Launched as action from content notifications.
         if (res == R.xml.preferences_notifications) {
             return R.id.pref_header_notifications;
         }
 
         return -1;
     }
 
     private void updateTitle() {
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
+++ b/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
@@ -1,72 +1,29 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.preferences;
 
-import org.json.JSONArray;
-import org.mozilla.gecko.AboutPages;
-import org.mozilla.gecko.AdjustConstants;
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.BrowserApp;
-import org.mozilla.gecko.BrowserLocaleManager;
-import org.mozilla.gecko.DataReportingNotification;
-import org.mozilla.gecko.DynamicToolbar;
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.Experiments;
-import org.mozilla.gecko.GeckoApplication;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.LocaleManager;
-import org.mozilla.gecko.Locales;
-import org.mozilla.gecko.PrefsHelper;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.SnackbarBuilder;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.TelemetryContract.Method;
-import org.mozilla.gecko.activitystream.ActivityStream;
-import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
-import org.mozilla.gecko.feeds.FeedService;
-import org.mozilla.gecko.feeds.action.CheckForUpdatesAction;
-import org.mozilla.gecko.mma.MmaDelegate;
-import org.mozilla.gecko.permissions.Permissions;
-import org.mozilla.gecko.restrictions.Restrictable;
-import org.mozilla.gecko.restrictions.Restrictions;
-import org.mozilla.gecko.tabqueue.TabQueueHelper;
-import org.mozilla.gecko.tabqueue.TabQueuePrompt;
-import org.mozilla.gecko.updater.UpdateService;
-import org.mozilla.gecko.updater.UpdateServiceHelper;
-import org.mozilla.gecko.util.BundleEventListener;
-import org.mozilla.gecko.util.ContextUtils;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.GeckoBundle;
-import org.mozilla.gecko.util.HardwareUtils;
-import org.mozilla.gecko.util.InputOptionsUtils;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.util.ViewUtil;
-
+import android.Manifest;
 import android.annotation.TargetApi;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.Fragment;
 import android.app.FragmentManager;
 import android.app.NotificationManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.content.res.Configuration;
-import android.Manifest;
-import android.os.Build;
 import android.os.Bundle;
 import android.preference.CheckBoxPreference;
 import android.preference.EditTextPreference;
 import android.preference.ListPreference;
 import android.preference.Preference;
 import android.preference.Preference.OnPreferenceChangeListener;
 import android.preference.Preference.OnPreferenceClickListener;
 import android.preference.PreferenceActivity;
@@ -85,17 +42,53 @@ import android.util.Log;
 import android.view.MenuItem;
 import android.view.View;
 import android.widget.AdapterView;
 import android.widget.EditText;
 import android.widget.LinearLayout;
 import android.widget.ListAdapter;
 import android.widget.ListView;
 
-import org.mozilla.gecko.switchboard.SwitchBoard;
+import org.json.JSONArray;
+import org.mozilla.gecko.AboutPages;
+import org.mozilla.gecko.AdjustConstants;
+import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.BrowserApp;
+import org.mozilla.gecko.BrowserLocaleManager;
+import org.mozilla.gecko.DataReportingNotification;
+import org.mozilla.gecko.DynamicToolbar;
+import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.GeckoApplication;
+import org.mozilla.gecko.GeckoProfile;
+import org.mozilla.gecko.GeckoSharedPrefs;
+import org.mozilla.gecko.LocaleManager;
+import org.mozilla.gecko.Locales;
+import org.mozilla.gecko.PrefsHelper;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.SnackbarBuilder;
+import org.mozilla.gecko.Telemetry;
+import org.mozilla.gecko.TelemetryContract;
+import org.mozilla.gecko.TelemetryContract.Method;
+import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
+import org.mozilla.gecko.mma.MmaDelegate;
+import org.mozilla.gecko.permissions.Permissions;
+import org.mozilla.gecko.restrictions.Restrictable;
+import org.mozilla.gecko.restrictions.Restrictions;
+import org.mozilla.gecko.tabqueue.TabQueueHelper;
+import org.mozilla.gecko.tabqueue.TabQueuePrompt;
+import org.mozilla.gecko.updater.UpdateService;
+import org.mozilla.gecko.updater.UpdateServiceHelper;
+import org.mozilla.gecko.util.BundleEventListener;
+import org.mozilla.gecko.util.ContextUtils;
+import org.mozilla.gecko.util.EventCallback;
+import org.mozilla.gecko.util.GeckoBundle;
+import org.mozilla.gecko.util.HardwareUtils;
+import org.mozilla.gecko.util.InputOptionsUtils;
+import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.util.ViewUtil;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -150,18 +143,16 @@ public class GeckoPreferences
     private static final String PREFS_CLEAR_PRIVATE_DATA_EXIT = NON_PREF_PREFIX + "history.clear_on_exit";
     private static final String PREFS_SCREEN_ADVANCED = NON_PREF_PREFIX + "advanced_screen";
     public static final String PREFS_HOMEPAGE = NON_PREF_PREFIX + "homepage";
     public static final String PREFS_HOMEPAGE_FOR_EVERY_NEW_TAB = NON_PREF_PREFIX + "newtab.load_homepage";
     public static final String PREFS_HOMEPAGE_PARTNER_COPY = GeckoPreferences.PREFS_HOMEPAGE + ".partner";
     public static final String PREFS_HISTORY_SAVED_SEARCH = NON_PREF_PREFIX + "search.search_history.enabled";
     private static final String PREFS_FAQ_LINK = NON_PREF_PREFIX + "faq.link";
     private static final String PREFS_FEEDBACK_LINK = NON_PREF_PREFIX + "feedback.link";
-    public static final String PREFS_NOTIFICATIONS_CONTENT = NON_PREF_PREFIX + "notifications.content";
-    public static final String PREFS_NOTIFICATIONS_CONTENT_LEARN_MORE = NON_PREF_PREFIX + "notifications.content.learn_more";
     public static final String PREFS_NOTIFICATIONS_WHATS_NEW = NON_PREF_PREFIX + "notifications.whats_new";
     public static final String PREFS_APP_UPDATE_LAST_BUILD_ID = "app.update.last_build_id";
     public static final String PREFS_READ_PARTNER_CUSTOMIZATIONS_PROVIDER = NON_PREF_PREFIX + "distribution.read_partner_customizations_provider";
     public static final String PREFS_READ_PARTNER_BOOKMARKS_PROVIDER = NON_PREF_PREFIX + "distribution.read_partner_bookmarks_provider";
     public static final String PREFS_CUSTOM_TABS = NON_PREF_PREFIX + "customtabs";
     public static final String PREFS_PWA = NON_PREF_PREFIX + "pwa";
     public static final String PREFS_CATEGORY_EXPERIMENTAL_FEATURES = NON_PREF_PREFIX + "category_experimental";
     public static final String PREFS_COMPACT_TABS = NON_PREF_PREFIX + "compact_tabs";
@@ -410,23 +401,16 @@ public class GeckoPreferences
         // capture EXTRA_SHOW_FRAGMENT_TITLE from the intent and store the title ID.
 
         // If launched from notification, explicitly cancel the notification.
         if (intentExtras != null && intentExtras.containsKey(DataReportingNotification.ALERT_NAME_DATAREPORTING_NOTIFICATION)) {
             Telemetry.sendUIEvent(TelemetryContract.Event.LAUNCH, Method.NOTIFICATION, "settings-data-choices");
             NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
             notificationManager.cancel(DataReportingNotification.ALERT_NAME_DATAREPORTING_NOTIFICATION.hashCode());
         }
-
-        // Launched from "Notifications settings" action button in a notification.
-        if (intentExtras != null && intentExtras.containsKey(CheckForUpdatesAction.EXTRA_CONTENT_NOTIFICATION)) {
-            Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(this));
-            Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, Method.BUTTON, "notification-settings");
-            Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(this));
-        }
     }
 
     /**
      * Set intent to display top-level settings fragment,
      * and show the correct title.
      */
     private void setupTopLevelFragmentIntent() {
         Intent intent = getIntent();
@@ -844,23 +828,16 @@ public class GeckoPreferences
                     final String url = getResources().getString(R.string.feedback_link, AppConstants.MOZ_APP_VERSION, AppConstants.MOZ_UPDATE_CHANNEL);
                     ((LinkPreference) pref).setUrl(url);
                 } else if (PREFS_DYNAMIC_TOOLBAR.equals(key)) {
                     if (DynamicToolbar.isForceDisabled()) {
                         preferences.removePreference(pref);
                         i--;
                         continue;
                     }
-                } else if (PREFS_NOTIFICATIONS_CONTENT.equals(key) ||
-                        PREFS_NOTIFICATIONS_CONTENT_LEARN_MORE.equals(key)) {
-                    if (!FeedService.isInExperiment(this)) {
-                        preferences.removePreference(pref);
-                        i--;
-                        continue;
-                    }
                 } else if (PREFS_CUSTOM_TABS.equals(key) && !AppConstants.MOZ_ANDROID_CUSTOM_TABS) {
                     preferences.removePreference(pref);
                     i--;
                     continue;
                 } else if (PREFS_PWA.equals(key) && !AppConstants.MOZ_ANDROID_PWA) {
                     preferences.removePreference(pref);
                     i--;
                     continue;
@@ -1174,18 +1151,16 @@ public class GeckoPreferences
                 return true;
             }
         } else if (PREFS_TAB_QUEUE.equals(prefName)) {
             if ((Boolean) newValue && !TabQueueHelper.canDrawOverlays(this)) {
                 Intent promptIntent = new Intent(this, TabQueuePrompt.class);
                 startActivityForResult(promptIntent, REQUEST_CODE_TAB_QUEUE);
                 return false;
             }
-        } else if (PREFS_NOTIFICATIONS_CONTENT.equals(prefName)) {
-            FeedService.setup(this);
         } else if (HANDLERS.containsKey(prefName)) {
             PrefHandler handler = HANDLERS.get(prefName);
             handler.onChange(this, preference, newValue);
         } else if (PREFS_SEARCH_SUGGESTIONS_ENABLED.equals(prefName)) {
             // Tell Gecko to transmit the current search engine data again, so
             // BrowserSearch is notified immediately about the new enabled state.
             EventDispatcher.getInstance().dispatch("SearchEngines:GetVisible", null);
         }
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -201,18 +201,16 @@
 <!ENTITY pref_search_hint2 "TIP: Add any website to your list of search providers by long-pressing on its search field and then touching the &formatI; icon.">
 <!ENTITY pref_category_advanced "Advanced">
 <!-- Localization note (pref_category_advanced_summary3): “data saver” in this
      context means consuming less data, e.g. by not loading images, not
      “storing data”. -->
 <!ENTITY pref_category_advanced_summary3 "Restore tabs, data saver, developer tools">
 <!ENTITY pref_category_notifications "Notifications">
 <!ENTITY pref_category_notifications_summary "New features, website updates">
-<!ENTITY pref_content_notifications "Website updates">
-<!ENTITY pref_content_notifications_summary2 "Discover new content from supported sites">
 <!ENTITY pref_developer_remotedebugging_usb "Remote debugging via USB">
 <!ENTITY pref_developer_remotedebugging_wifi "Remote debugging via Wi-Fi">
 <!ENTITY pref_developer_remotedebugging_wifi_disabled_summary "Wi-Fi debugging requires your device to have a QR code reader app installed.">
 <!ENTITY pref_remember_signons2 "Remember logins">
 <!ENTITY pref_manage_logins "Manage logins">
 
 <!ENTITY pref_category_home "Home">
 <!ENTITY pref_category_home_summary "Customize your homepage">
@@ -330,33 +328,16 @@
 <!-- Localization note (tab_queue_notification_text_singular2) : This is the
      text of a notification; we expect only one tab queued. -->
 <!ENTITY tab_queue_notification_text_singular2 "1 tab waiting">
 
 <!-- Localization note (tab_queue_notification_settings): This notification text is shown if a tab
      has been queued but we are missing the system permission to show an overlay. -->
 <!ENTITY tab_queue_notification_settings "To \&quot;Open multiple links\&quot;, please enable the \'Draw over other apps\' permission for &brandShortName;">
 
-<!ENTITY content_notification_summary "&brandShortName;">
-<!-- Localization note (content_notification_title_plural): &formatD; will be replaced with the number of websites that
-     have been updated (new content is available). The number of websites is always more than one (>= 2). For a single
-     update the website title is used instead of this string.
-     We can't use Android plural forms, sadly. See Bug #753859. -->
-<!ENTITY content_notification_title_plural "&formatD; websites updated">
-<!-- Localization note (content_notification_action_settings2): This label will be shown as an action in a content notification.
-     Clicking the action will jump to the notification settings of the app. -->
-<!ENTITY content_notification_action_settings2 "Settings">
-<!-- Localization note(content_notification_action_read_now): This label will be shown as an action in a content notification.
-     Clicking the action will open all new content in the browser. -->
-<!ENTITY content_notification_action_read_now "Read now">
-<!-- Localization note (content_notification_updated_on): &formatS; will be replaced with a medium sized version of the
-     date, depending on locale. For en_US this is for example: Feb 24, 2016. For more details see the Android developer
-     documentation for DateFormat.getMediumDateFormat(). -->
-<!ENTITY content_notification_updated_on "Updated on &formatS;">
-
 <!ENTITY pref_char_encoding "Character encoding">
 <!ENTITY pref_char_encoding_on "Show menu">
 <!ENTITY pref_char_encoding_off "Don\'t show menu">
 <!ENTITY pref_clear_private_data2 "Clear private data">
 <!-- Localization note (pref_clear_private_data_now_tablet): This action to clear private data is only shown on tablets.
      The action is shown below a header saying "Clear private data"; See pref_clear_private_data -->
 <!ENTITY pref_clear_private_data_now_tablet "Clear now">
 <!ENTITY pref_clear_on_exit_title3 "Clear private data on exit">
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -548,17 +548,16 @@ gbjar.sources += ['java/org/mozilla/geck
     'ANRReporter.java',
     'bookmarks/BookmarkEditFragment.java',
     'bookmarks/BookmarkUtils.java',
     'bookmarks/CreateFolderCallback.java',
     'bookmarks/CreateFolderFragment.java',
     'bookmarks/EditBookmarkTask.java',
     'bookmarks/SelectFolderCallback.java',
     'bookmarks/SelectFolderFragment.java',
-    'BootReceiver.java',
     'BrowserApp.java',
     'BrowserLocaleManager.java',
     'cleanup/FileCleanupController.java',
     'cleanup/FileCleanupService.java',
     'CustomEditText.java',
     'customtabs/ActionBarPresenter.java',
     'customtabs/CustomTabsActivity.java',
     'customtabs/CustomTabsSecurityPopup.java',
@@ -624,35 +623,16 @@ gbjar.sources += ['java/org/mozilla/geck
     'dlc/VerifyAction.java',
     'DoorHangerPopup.java',
     'DownloadsIntegration.java',
     'drawable/DrawableWrapper.java',
     'drawable/ShiftDrawable.java',
     'DynamicToolbar.java',
     'EditBookmarkDialog.java',
     'Experiments.java',
-    'feeds/action/CheckForUpdatesAction.java',
-    'feeds/action/EnrollSubscriptionsAction.java',
-    'feeds/action/FeedAction.java',
-    'feeds/action/SetupAlarmsAction.java',
-    'feeds/action/SubscribeToFeedAction.java',
-    'feeds/action/WithdrawSubscriptionsAction.java',
-    'feeds/ContentNotificationsDelegate.java',
-    'feeds/FeedAlarmReceiver.java',
-    'feeds/FeedFetcher.java',
-    'feeds/FeedService.java',
-    'feeds/knownsites/KnownSite.java',
-    'feeds/knownsites/KnownSiteBlogger.java',
-    'feeds/knownsites/KnownSiteMedium.java',
-    'feeds/knownsites/KnownSiteTumblr.java',
-    'feeds/knownsites/KnownSiteWordpress.java',
-    'feeds/parser/Feed.java',
-    'feeds/parser/Item.java',
-    'feeds/parser/SimpleFeedParser.java',
-    'feeds/subscriptions/FeedSubscription.java',
     'FilePicker.java',
     'FilePickerResultHandler.java',
     'FindInPageBar.java',
     'firstrun/DataPanel.java',
     'firstrun/FirstrunAnimationContainer.java',
     'firstrun/FirstrunPager.java',
     'firstrun/FirstrunPagerConfig.java',
     'firstrun/FirstrunPanel.java',
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -164,18 +164,16 @@
   <string name="pref_category_advanced">&pref_category_advanced;</string>
   <string name="pref_category_advanced_summary">&pref_category_advanced_summary3;</string>
   <string name="pref_developer_remotedebugging_usb">&pref_developer_remotedebugging_usb;</string>
   <string name="pref_developer_remotedebugging_wifi">&pref_developer_remotedebugging_wifi;</string>
   <string name="pref_developer_remotedebugging_wifi_disabled_summary">&pref_developer_remotedebugging_wifi_disabled_summary;</string>
 
   <string name="pref_category_notifications">&pref_category_notifications;</string>
   <string name="pref_category_notifications_summary">&pref_category_notifications_summary;</string>
-  <string name="pref_content_notifications">&pref_content_notifications;</string>
-  <string name="pref_content_notifications_summary">&pref_content_notifications_summary2;</string>
 
   <string name="pref_category_home">&pref_category_home;</string>
   <string name="pref_category_home_summary">&pref_category_home_summary;</string>
   <string name="pref_category_home_panels">&pref_category_home_panels;</string>
   <string name="pref_home_updates_wifi">&pref_home_updates_wifi;</string>
   <string name="pref_category_home_add_ons">&pref_category_home_add_ons;</string>
   <string name="pref_home_updates">&pref_home_updates2;</string>
   <string name="pref_home_updates_enabled">&pref_home_updates_enabled;</string>
@@ -299,22 +297,16 @@
   <string name="tab_queue_prompt_settings_button">&tab_queue_prompt_settings_button;</string>
   <string name="tab_queue_toast_message">&tab_queue_toast_message3;</string>
   <string name="tab_queue_toast_action">&tab_queue_toast_action;</string>
   <string name="tab_queue_notification_text_singular">&tab_queue_notification_text_singular2;</string>
   <string name="tab_queue_notification_text_plural">&tab_queue_notification_text_plural2;</string>
   <string name="tab_queue_notification_title">&tab_queue_notification_title;</string>
   <string name="tab_queue_notification_settings">&tab_queue_notification_settings;</string>
 
-  <string name="content_notification_summary">&content_notification_summary;</string>
-  <string name="content_notification_title_plural">&content_notification_title_plural;</string>
-  <string name="content_notification_action_settings">&content_notification_action_settings2;</string>
-  <string name="content_notification_action_read_now">&content_notification_action_read_now;</string>
-  <string name="content_notification_updated_on">&content_notification_updated_on;</string>
-
   <string name="pref_default_browser">&pref_default_browser;</string>
   <string name="pref_default_browser_mozilla_support_tablet">&pref_default_browser_mozilla_support_tablet;</string>
 
   <string name="pref_about_firefox">&pref_about_firefox;</string>
 
   <string name="pref_vendor_faqs">&pref_vendor_faqs;</string>
   <!-- https://support.mozilla.org/1/mobile/%VERSION%/%OS%/%LOCALE%/faq -->
   <string name="faq_link">https://support.mozilla.org/1/mobile/&formatS1;/&formatS2;/&formatS3;/faq</string>
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHlsPlayer.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHlsPlayer.java
@@ -678,16 +678,21 @@ public class GeckoHlsPlayer implements B
                                                   16, 0, getDuration(),
                                                   fmt.sampleMimeType, csd);
         return aInfo;
     }
 
     // Called on HLSDemuxer's TaskQueue
     @Override
     public synchronized boolean seek(long positionUs) {
+        if (mPlayer == null) {
+            Log.d(LOGTAG, "Seek operation won't be performed as no player exists!");
+            return false;
+        }
+
         // Need to temporarily resume Exoplayer to download the chunks for getting the demuxed
         // keyframe sample when HTMLMediaElement is paused. Suspend Exoplayer when collecting enough
         // samples in onLoadingChanged.
         if (mExoplayerSuspended) {
             resumeExoplayer();
         }
         // positionUs : microseconds.
         // NOTE : 1) It's not possible to seek media by tracktype via ExoPlayer Interface.
deleted file mode 100644
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/feeds/knownsites/TestKnownSiteBlogger.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.knownsites;
-
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.gecko.background.testhelpers.TestRunner;
-import org.mozilla.gecko.helpers.AssertUtil;
-
-@RunWith(TestRunner.class)
-public class TestKnownSiteBlogger {
-    /**
-     * Test that the search string is a substring of some known URLs.
-     */
-    @Test
-    public void testURLSearchString() {
-        final KnownSite blogger = new KnownSiteBlogger();
-        final String searchString = blogger.getURLSearchString();
-
-        AssertUtil.assertContains(
-                "http://mykzilla.blogspot.com/",
-                searchString);
-
-        AssertUtil.assertContains(
-                "http://example.blogspot.com",
-                searchString);
-
-        AssertUtil.assertContains(
-                "https://mykzilla.blogspot.com/2015/06/introducing-pluotsorbet.html",
-                searchString);
-
-        AssertUtil.assertContains(
-                "http://android-developers.blogspot.com/2016/02/android-support-library-232.html",
-                searchString);
-
-        AssertUtil.assertContainsNot(
-                "http://www.mozilla.org",
-                searchString);
-    }
-
-    /**
-     * Test that we get a feed URL for valid Blogger URLs.
-     */
-    @Test
-    public void testGettingFeedFromURL() {
-        final KnownSite blogger = new KnownSiteBlogger();
-
-        Assert.assertEquals(
-                "https://mykzilla.blogspot.com/feeds/posts/default",
-                blogger.getFeedFromURL("http://mykzilla.blogspot.com/"));
-
-        Assert.assertEquals(
-                "https://example.blogspot.com/feeds/posts/default",
-                blogger.getFeedFromURL("http://example.blogspot.com"));
-
-        Assert.assertEquals(
-                "https://mykzilla.blogspot.com/feeds/posts/default",
-                blogger.getFeedFromURL("https://mykzilla.blogspot.com/2015/06/introducing-pluotsorbet.html"));
-
-        Assert.assertEquals(
-                "https://android-developers.blogspot.com/feeds/posts/default",
-                blogger.getFeedFromURL("http://android-developers.blogspot.com/2016/02/android-support-library-232.html"));
-
-        Assert.assertEquals(
-                "https://example.blogspot.com/feeds/posts/default",
-                blogger.getFeedFromURL("http://example.blogspot.com/2016/03/i-moved-to-example.blogspot.com"));
-
-        Assert.assertNull(blogger.getFeedFromURL("http://www.mozilla.org"));
-    }
-}
deleted file mode 100644
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/feeds/knownsites/TestKnownSiteMedium.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.knownsites;
-
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.gecko.background.testhelpers.TestRunner;
-import org.mozilla.gecko.helpers.AssertUtil;
-
-@RunWith(TestRunner.class)
-public class TestKnownSiteMedium {
-    /**
-     * Test that the search string is a substring of some known URLs.
-     */
-    @Test
-    public void testURLSearchString() {
-        final KnownSite medium = new KnownSiteMedium();
-        final String searchString = medium.getURLSearchString();
-
-        AssertUtil.assertContains(
-                "https://medium.com/@Antlam/",
-                searchString);
-
-        AssertUtil.assertContains(
-                "https://medium.com/google-developers",
-                searchString);
-
-        AssertUtil.assertContains(
-                "http://medium.com/@brandonshin/how-slackbot-forced-us-to-workout-7b4741a2de73",
-                searchString
-        );
-
-        AssertUtil.assertContainsNot(
-                "http://www.mozilla.org",
-                searchString);
-    }
-
-    /**
-     * Test that we get a feed URL for valid Medium URLs.
-     */
-    @Test
-    public void testGettingFeedFromURL() {
-        final KnownSite medium = new KnownSiteMedium();
-
-        Assert.assertEquals(
-                "https://medium.com/feed/@Antlam",
-                medium.getFeedFromURL("https://medium.com/@Antlam/")
-        );
-
-        Assert.assertEquals(
-                "https://medium.com/feed/google-developers",
-                medium.getFeedFromURL("https://medium.com/google-developers")
-        );
-
-        Assert.assertEquals(
-                "https://medium.com/feed/@brandonshin",
-                medium.getFeedFromURL("http://medium.com/@brandonshin/how-slackbot-forced-us-to-workout-7b4741a2de73")
-        );
-
-        Assert.assertNull(medium.getFeedFromURL("http://www.mozilla.org"));
-    }
-}
deleted file mode 100644
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/feeds/knownsites/TestKnownSiteTumblr.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.knownsites;
-
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.gecko.background.testhelpers.TestRunner;
-import org.mozilla.gecko.helpers.AssertUtil;
-
-@RunWith(TestRunner.class)
-public class TestKnownSiteTumblr {
-    /**
-     * Test that the search string is a substring of some known URLs.
-     */
-    @Test
-    public void testURLSearchString() {
-        final KnownSite tumblr = new KnownSiteTumblr();
-        final String searchString = tumblr.getURLSearchString();
-
-        AssertUtil.assertContains(
-                "http://contentnotifications.tumblr.com/",
-                searchString);
-
-        AssertUtil.assertContains(
-                "https://contentnotifications.tumblr.com",
-                searchString);
-
-        AssertUtil.assertContains(
-                "http://contentnotifications.tumblr.com/post/142684202402/content-notification-firefox-for-android-480",
-                searchString);
-
-        AssertUtil.assertContainsNot(
-                "http://www.mozilla.org",
-                searchString);
-    }
-
-    /**
-     * Test that we get a feed URL for valid Medium URLs.
-     */
-    @Test
-    public void testGettingFeedFromURL() {
-        final KnownSite tumblr = new KnownSiteTumblr();
-
-        Assert.assertEquals(
-                "http://contentnotifications.tumblr.com/rss",
-                tumblr.getFeedFromURL("http://contentnotifications.tumblr.com/")
-        );
-
-        Assert.assertEquals(
-                "http://staff.tumblr.com/rss",
-                tumblr.getFeedFromURL("https://staff.tumblr.com/post/141928246566/replies-are-back-and-the-sun-is-shining-on-the")
-        );
-
-        Assert.assertNull(tumblr.getFeedFromURL("https://www.tumblr.com"));
-
-        Assert.assertNull(tumblr.getFeedFromURL("http://www.mozilla.org"));
-    }
-}
deleted file mode 100644
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/feeds/parser/TestSimpleFeedParser.java
+++ /dev/null
@@ -1,323 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.parser;
-
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mozilla.gecko.background.testhelpers.TestRunner;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedReader;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.io.UnsupportedEncodingException;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.text.SimpleDateFormat;
-import java.util.Locale;
-
-@RunWith(TestRunner.class)
-public class TestSimpleFeedParser {
-    /**
-     * Parse and verify the RSS example from Wikipedia:
-     * https://en.wikipedia.org/wiki/RSS#Example
-     */
-    @Test
-    public void testRSSExample() throws Exception {
-        InputStream stream = openFeed("feed_rss_wikipedia.xml");
-
-        SimpleFeedParser parser = new SimpleFeedParser();
-        Feed feed = parser.parse(stream);
-
-        Assert.assertNotNull(feed);
-        Assert.assertEquals("RSS Title", feed.getTitle());
-        Assert.assertEquals("http://www.example.com/main.html", feed.getWebsiteURL());
-        Assert.assertNull(feed.getFeedURL());
-        Assert.assertTrue(feed.isSufficientlyComplete());
-
-        Item item = feed.getLastItem();
-
-        Assert.assertNotNull(item);
-        Assert.assertEquals("Example entry", item.getTitle());
-        Assert.assertEquals("http://www.example.com/blog/post/1", item.getURL());
-        Assert.assertEquals(1252254000000L, item.getTimestamp());
-    }
-
-    /**
-     * Parse and verify the ATOM example from Wikipedia:
-     * https://en.wikipedia.org/wiki/Atom_%28standard%29#Example_of_an_Atom_1.0_feed
-     */
-    @Test
-    public void testATOMExample() throws Exception {
-        InputStream stream = openFeed("feed_atom_wikipedia.xml");
-
-        SimpleFeedParser parser = new SimpleFeedParser();
-        Feed feed = parser.parse(stream);
-
-        Assert.assertNotNull(feed);
-        Assert.assertEquals("Example Feed", feed.getTitle());
-        Assert.assertEquals("http://example.org/", feed.getWebsiteURL());
-        Assert.assertEquals("http://example.org/feed/", feed.getFeedURL());
-        Assert.assertTrue(feed.isSufficientlyComplete());
-
-        Item item = feed.getLastItem();
-
-        Assert.assertNotNull(item);
-        Assert.assertEquals("Atom-Powered Robots Run Amok", item.getTitle());
-        Assert.assertEquals("http://example.org/2003/12/13/atom03.html", item.getURL());
-        Assert.assertEquals(1071340202000L, item.getTimestamp());
-    }
-
-    /**
-     * Parse and verify a snapshot of a Medium feed.
-     */
-    @Test
-    public void testMediumFeed() throws Exception {
-        InputStream stream = openFeed("feed_rss_medium.xml");
-
-        SimpleFeedParser parser = new SimpleFeedParser();
-        Feed feed = parser.parse(stream);
-
-        Assert.assertNotNull(feed);
-        Assert.assertEquals("Anthony Lam on Medium", feed.getTitle());
-        Assert.assertEquals("https://medium.com/@antlam?source=rss-59f49b9e4b19------2", feed.getWebsiteURL());
-        Assert.assertEquals("https://medium.com/feed/@antlam", feed.getFeedURL());
-        Assert.assertTrue(feed.isSufficientlyComplete());
-
-        Item item = feed.getLastItem();
-
-        Assert.assertNotNull(item);
-        Assert.assertEquals("UX thoughts for 2016", item.getTitle());
-        Assert.assertEquals("https://medium.com/@antlam/ux-thoughts-for-2016-1fc1d6e515e8?source=rss-59f49b9e4b19------2", item.getURL());
-        Assert.assertEquals(1452537838000L, item.getTimestamp());
-    }
-
-    /**
-     * Parse and verify a snapshot of planet.mozilla.org ATOM feed.
-     */
-    @Test
-    public void testPlanetMozillaATOMFeed() throws Exception {
-        InputStream stream = openFeed("feed_atom_planetmozilla.xml");
-
-        SimpleFeedParser parser = new SimpleFeedParser();
-        Feed feed = parser.parse(stream);
-
-        Assert.assertNotNull(feed);
-        Assert.assertEquals("Planet Mozilla", feed.getTitle());
-        Assert.assertEquals("http://planet.mozilla.org/", feed.getWebsiteURL());
-        Assert.assertEquals("http://planet.mozilla.org/atom.xml", feed.getFeedURL());
-        Assert.assertTrue(feed.isSufficientlyComplete());
-
-        Item item = feed.getLastItem();
-
-        Assert.assertNotNull(item);
-        Assert.assertEquals("Firefox 45.0 Beta 3 Testday, February 5th", item.getTitle());
-        Assert.assertEquals("https://quality.mozilla.org/2016/01/firefox-45-0-beta-3-testday-february-5th/", item.getURL());
-        Assert.assertEquals(1453819255000L, item.getTimestamp());
-    }
-
-    /**
-     * Parse and verify a snapshot of planet.mozilla.org RSS 2.0 feed.
-     */
-    @Test
-    public void testPlanetMozillaRSS20Feed() throws Exception {
-        InputStream stream = openFeed("feed_rss20_planetmozilla.xml");
-
-        SimpleFeedParser parser = new SimpleFeedParser();
-        Feed feed = parser.parse(stream);
-
-        Assert.assertNotNull(feed);
-        Assert.assertEquals("Planet Mozilla", feed.getTitle());
-        Assert.assertEquals("http://planet.mozilla.org/", feed.getWebsiteURL());
-        Assert.assertEquals("http://planet.mozilla.org/rss20.xml", feed.getFeedURL());
-        Assert.assertTrue(feed.isSufficientlyComplete());
-
-        Item item = feed.getLastItem();
-
-        Assert.assertNotNull(item);
-        Assert.assertEquals("Aaron Klotz: Announcing Mozdbgext", item.getTitle());
-        Assert.assertEquals("http://dblohm7.ca/blog/2016/01/26/announcing-mozdbgext/", item.getURL());
-        Assert.assertEquals(1453837500000L, item.getTimestamp());
-    }
-
-    /**
-     * Parse and verify a snapshot of planet.mozilla.org RSS 1.0 feed.
-     */
-    @Test
-    public void testPlanetMozillaRSS10Feed() throws Exception {
-        InputStream stream = openFeed("feed_rss10_planetmozilla.xml");
-
-        SimpleFeedParser parser = new SimpleFeedParser();
-        Feed feed = parser.parse(stream);
-
-        Assert.assertNotNull(feed);
-        Assert.assertEquals("Planet Mozilla", feed.getTitle());
-        Assert.assertEquals("http://planet.mozilla.org/", feed.getWebsiteURL());
-        Assert.assertEquals("http://planet.mozilla.org/rss10.xml", feed.getFeedURL());
-        Assert.assertTrue(feed.isSufficientlyComplete());
-
-        Item item = feed.getLastItem();
-
-        Assert.assertNotNull(item);
-        Assert.assertEquals("Aaron Klotz: Announcing Mozdbgext", item.getTitle());
-        Assert.assertEquals("http://dblohm7.ca/blog/2016/01/26/announcing-mozdbgext/", item.getURL());
-        Assert.assertEquals(1453837500000L, item.getTimestamp());
-    }
-
-    /**
-     * Parse an verify a snapshot of a feedburner ATOM feed.
-     */
-    @Test
-    public void testFeedburnerAtomFeed() throws Exception {
-        InputStream stream = openFeed("feed_atom_feedburner.xml");
-
-        SimpleFeedParser parser = new SimpleFeedParser();
-        Feed feed = parser.parse(stream);
-
-        Assert.assertNotNull(feed);
-        Assert.assertEquals("Android Zeitgeist", feed.getTitle());
-        Assert.assertEquals("http://www.androidzeitgeist.com/", feed.getWebsiteURL());
-        Assert.assertEquals("http://feeds.feedburner.com/AndroidZeitgeist", feed.getFeedURL());
-        Assert.assertTrue(feed.isSufficientlyComplete());
-
-        Item item = feed.getLastItem();
-
-        Assert.assertNotNull(item);
-        Assert.assertEquals("Support for restricted profiles in Firefox 42", item.getTitle());
-        Assert.assertEquals("http://feedproxy.google.com/~r/AndroidZeitgeist/~3/xaSicfGuwOU/support-restricted-profiles-firefox.html", item.getURL());
-        Assert.assertEquals(1442511968239L, item.getTimestamp());
-    }
-
-    /**
-     * Parse and verify a snapshot of a Tumblr RSS feed.
-     */
-    @Test
-    public void testTumblrRssFeed() throws Exception {
-        InputStream stream = openFeed("feed_rss_tumblr.xml");
-
-        SimpleFeedParser parser = new SimpleFeedParser();
-        Feed feed = parser.parse(stream);
-
-        Assert.assertNotNull(feed);
-        Assert.assertEquals("Tumblr Staff", feed.getTitle());
-        Assert.assertEquals("http://staff.tumblr.com/", feed.getWebsiteURL());
-        Assert.assertNull(feed.getFeedURL());
-        Assert.assertTrue(feed.isSufficientlyComplete());
-
-        Item item = feed.getLastItem();
-
-        Assert.assertNotNull(item);
-        Assert.assertEquals("hardyboyscovers: Can Nancy Drew see things through and solve...", item.getTitle());
-        Assert.assertEquals("http://staff.tumblr.com/post/138124026275", item.getURL());
-        Assert.assertEquals(1453861812000L, item.getTimestamp());
-    }
-
-    /**
-     * Parse and verify a snapshot of a Spiegel (German news magazine) RSS feed.
-     */
-    @Test
-    public void testSpiegelRssFeed() throws Exception {
-        InputStream stream = openFeed("feed_rss_spon.xml");
-
-        SimpleFeedParser parser = new SimpleFeedParser();
-        Feed feed = parser.parse(stream);
-
-        Assert.assertNotNull(feed);
-        Assert.assertEquals("SPIEGEL ONLINE - Schlagzeilen", feed.getTitle());
-        Assert.assertEquals("http://www.spiegel.de", feed.getWebsiteURL());
-        Assert.assertNull(feed.getFeedURL());
-        Assert.assertTrue(feed.isSufficientlyComplete());
-
-        Item item = feed.getLastItem();
-
-        Assert.assertNotNull(item);
-        Assert.assertEquals("Angebliche Vergewaltigung einer 13-Jährigen: Steinmeier kanzelt russischen Minister Lawrow ab", item.getTitle());
-        Assert.assertEquals("http://www.spiegel.de/politik/ausland/steinmeier-kanzelt-lawrow-ab-aerger-um-angebliche-vergewaltigung-a-1074292.html#ref=rss", item.getURL());
-        Assert.assertEquals(1453914976000L, item.getTimestamp());
-    }
-
-    /**
-     * Parse and verify a snapshot of a Heise (German tech news) RSS feed.
-     */
-    @Test
-    public void testHeiseRssFeed() throws Exception {
-        InputStream stream = openFeed("feed_rss_heise.xml");
-
-        SimpleFeedParser parser = new SimpleFeedParser();
-        Feed feed = parser.parse(stream);
-
-        Assert.assertNotNull(feed);
-        Assert.assertEquals("heise online News", feed.getTitle());
-        Assert.assertEquals("http://www.heise.de/newsticker/", feed.getWebsiteURL());
-        Assert.assertNull(feed.getFeedURL());
-        Assert.assertTrue(feed.isSufficientlyComplete());
-
-        Item item = feed.getLastItem();
-
-        Assert.assertNotNull(item);
-        Assert.assertEquals("Google: “Dramatische Verbesserungen” für Chrome in iOS", item.getTitle());
-        Assert.assertEquals("http://www.heise.de/newsticker/meldung/Google-Dramatische-Verbesserungen-fuer-Chrome-in-iOS-3085808.html?wt_mc=rss.ho.beitrag.atom", item.getURL());
-        Assert.assertEquals(1453915920000L, item.getTimestamp());
-    }
-
-    @Test
-    public void testWordpressFeed() throws Exception {
-        InputStream stream = openFeed("feed_rss_wordpress.xml");
-
-        SimpleFeedParser parser = new SimpleFeedParser();
-        Feed feed = parser.parse(stream);
-
-        Assert.assertNotNull(feed);
-        Assert.assertEquals("justasimpletest2016", feed.getTitle());
-        Assert.assertEquals("https://justasimpletest2016.wordpress.com", feed.getWebsiteURL());
-        Assert.assertEquals("https://justasimpletest2016.wordpress.com/feed/", feed.getFeedURL());
-        Assert.assertTrue(feed.isSufficientlyComplete());
-
-        Item item = feed.getLastItem();
-
-        Assert.assertNotNull(item);
-        Assert.assertEquals("Hello World!", item.getTitle());
-        Assert.assertEquals("https://justasimpletest2016.wordpress.com/2016/02/26/hello-world/", item.getURL());
-        Assert.assertEquals(1456524466000L, item.getTimestamp());
-    }
-
-    /**
-     * Parse and test a snapshot of mykzilla.blogspot.com
-     */
-    @Test
-    public void testBloggerFeed() throws Exception {
-        InputStream stream = openFeed("feed_atom_blogger.xml");
-
-        SimpleFeedParser parser = new SimpleFeedParser();
-        Feed feed = parser.parse(stream);
-
-        Assert.assertNotNull(feed);
-        Assert.assertEquals("mykzilla", feed.getTitle());
-        Assert.assertEquals("http://mykzilla.blogspot.com/", feed.getWebsiteURL());
-        Assert.assertEquals("http://www.blogger.com/feeds/18929277/posts/default", feed.getFeedURL());
-        Assert.assertTrue(feed.isSufficientlyComplete());
-
-        Item item = feed.getLastItem();
-
-        Assert.assertNotNull(item);
-        Assert.assertEquals("URL Has Been Changed", item.getTitle());
-        Assert.assertEquals("http://mykzilla.blogspot.com/2016/01/url-has-been-changed.html", item.getURL());
-        Assert.assertEquals(1452531451366L, item.getTimestamp());
-    }
-
-    private InputStream openFeed(String fileName) throws URISyntaxException, FileNotFoundException, UnsupportedEncodingException {
-        URL url = getClass().getResource("/" + fileName);
-        if (url == null) {
-            throw new FileNotFoundException(fileName);
-        }
-
-        return new BufferedInputStream(new FileInputStream(url.getPath()));
-    }
-}
--- a/mobile/locales/en-US/chrome/region.properties
+++ b/mobile/locales/en-US/chrome/region.properties
@@ -54,43 +54,43 @@ browser.contentHandlers.types.0.uri=http
 browser.suggestedsites.list.0=facebook
 browser.suggestedsites.list.1=youtube
 browser.suggestedsites.list.2=amazon
 browser.suggestedsites.list.3=wikipedia
 browser.suggestedsites.list.4=twitter
 
 browser.suggestedsites.facebook.title=Facebook
 browser.suggestedsites.facebook.url=https://m.facebook.com/
-browser.suggestedsites.facebook.bgcolor=#385185
+browser.suggestedsites.facebook.bgcolor=#3B5998
 
 browser.suggestedsites.youtube.title=YouTube
 browser.suggestedsites.youtube.url=https://m.youtube.com/
-browser.suggestedsites.youtube.bgcolor=#cd201f
+browser.suggestedsites.youtube.bgcolor=#DB2532
 
 browser.suggestedsites.amazon.title=Amazon
 browser.suggestedsites.amazon.url=https://www.amazon.com/
-browser.suggestedsites.amazon.bgcolor=#000000
+browser.suggestedsites.amazon.bgcolor=#FFFFFF
 
 browser.suggestedsites.wikipedia.title=Wikipedia
 browser.suggestedsites.wikipedia.url=https://www.wikipedia.org/
 browser.suggestedsites.wikipedia.bgcolor=#000000
 
 browser.suggestedsites.twitter.title=Twitter
 browser.suggestedsites.twitter.url=https://mobile.twitter.com/
-browser.suggestedsites.twitter.bgcolor=#55acee
+browser.suggestedsites.twitter.bgcolor=#1DA1F2
 
 browser.suggestedsites.restricted.list.0=restricted_fxsupport
 browser.suggestedsites.restricted.list.1=webmaker
 browser.suggestedsites.restricted.list.2=restricted_mozilla
 
 browser.suggestedsites.restricted_fxsupport.title=Firefox Help and Support for restricted profiles on Android tablets
 browser.suggestedsites.restricted_fxsupport.url=https://support.mozilla.org/kb/controlledaccess
-browser.suggestedsites.restricted_fxsupport.bgcolor=#f37c00
+browser.suggestedsites.restricted_fxsupport.bgcolor=#FF9400
 
 browser.suggestedsites.webmaker.title=Learn the Web: Mozilla Webmaker
 browser.suggestedsites.webmaker.url=https://webmaker.org/
-browser.suggestedsites.webmaker.bgcolor=#f37c00
+browser.suggestedsites.webmaker.bgcolor=#5B6C86
 
 # LOCALIZATION NOTE: browser.suggestedsites.restricted_mozilla.url must be different from browser.suggestedsites.mozilla.url
 browser.suggestedsites.restricted_mozilla.title=The Mozilla Project
 browser.suggestedsites.restricted_mozilla.url=https://www.mozilla.org
-browser.suggestedsites.restricted_mozilla.bgcolor=#ce4e41
+browser.suggestedsites.restricted_mozilla.bgcolor=#000000
 browser.suggestedsites.restricted_mozilla.trackingid=632
--- a/python/mozlint/mozlint/__init__.py
+++ b/python/mozlint/mozlint/__init__.py
@@ -1,7 +1,9 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 # flake8: noqa
 
+from __future__ import absolute_import
+
 from .roller import LintRoller
 from .result import ResultContainer
--- a/python/mozlint/mozlint/cli.py
+++ b/python/mozlint/mozlint/cli.py
@@ -1,13 +1,13 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-from __future__ import print_function, unicode_literals
+from __future__ import absolute_import, print_function, unicode_literals
 
 import os
 import sys
 from argparse import REMAINDER, ArgumentParser
 
 from mozlint.formatters import all_formatters
 
 SEARCH_PATHS = []
--- a/python/mozlint/mozlint/editor.py
+++ b/python/mozlint/mozlint/editor.py
@@ -1,13 +1,13 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-from __future__ import unicode_literals, print_function
+from __future__ import absolute_import, unicode_literals, print_function
 
 import os
 import subprocess
 import tempfile
 
 from mozlint import formatters
 
 
--- a/python/mozlint/mozlint/errors.py
+++ b/python/mozlint/mozlint/errors.py
@@ -1,12 +1,14 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+from __future__ import absolute_import
+
 import os
 
 
 class LintException(Exception):
     pass
 
 
 class LinterNotFound(LintException):
--- a/python/mozlint/mozlint/formatters/__init__.py
+++ b/python/mozlint/mozlint/formatters/__init__.py
@@ -1,12 +1,14 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+from __future__ import absolute_import
+
 import json
 
 from ..result import ResultEncoder
 from .compact import CompactFormatter
 from .stylish import StylishFormatter
 from .treeherder import TreeherderFormatter
 
 
--- a/python/mozlint/mozlint/formatters/compact.py
+++ b/python/mozlint/mozlint/formatters/compact.py
@@ -1,13 +1,13 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-from __future__ import unicode_literals
+from __future__ import absolute_import, unicode_literals
 
 from ..result import ResultContainer
 
 
 class CompactFormatter(object):
     """Formatter for compact output.
 
     This formatter prints one error per line, mimicking the
--- a/python/mozlint/mozlint/formatters/stylish.py
+++ b/python/mozlint/mozlint/formatters/stylish.py
@@ -1,13 +1,13 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-from __future__ import unicode_literals
+from __future__ import absolute_import, unicode_literals
 
 from ..result import ResultContainer
 
 try:
     import blessings
 except ImportError:
     blessings = None
 
--- a/python/mozlint/mozlint/formatters/treeherder.py
+++ b/python/mozlint/mozlint/formatters/treeherder.py
@@ -1,13 +1,13 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-from __future__ import unicode_literals
+from __future__ import absolute_import, unicode_literals
 
 from ..result import ResultContainer
 
 
 class TreeherderFormatter(object):
     """Formatter for treeherder friendly output.
 
     This formatter looks ugly, but prints output such that
--- a/python/mozlint/mozlint/parser.py
+++ b/python/mozlint/mozlint/parser.py
@@ -1,12 +1,14 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+from __future__ import absolute_import
+
 import os
 
 import yaml
 
 from .types import supported_types
 from .errors import LinterNotFound, LinterParseError
 
 
--- a/python/mozlint/mozlint/result.py
+++ b/python/mozlint/mozlint/result.py
@@ -1,12 +1,14 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+from __future__ import absolute_import
+
 from json import dumps, JSONEncoder
 
 
 class ResultContainer(object):
     """Represents a single lint error and its related metadata.
 
     :param linter: name of the linter that flagged this error
     :param path: path to the file containing the error
--- a/python/mozlint/mozlint/roller.py
+++ b/python/mozlint/mozlint/roller.py
@@ -1,13 +1,13 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-from __future__ import unicode_literals
+from __future__ import absolute_import, print_function, unicode_literals
 
 import os
 import signal
 import sys
 import traceback
 from collections import defaultdict
 from concurrent.futures import ProcessPoolExecutor
 from multiprocessing import cpu_count
--- a/python/mozlint/mozlint/types.py
+++ b/python/mozlint/mozlint/types.py
@@ -1,13 +1,13 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-from __future__ import unicode_literals
+from __future__ import absolute_import, unicode_literals
 
 import re
 import sys
 from abc import ABCMeta, abstractmethod
 
 from mozlog import get_default_logger, commandline, structuredlog
 from mozlog.reader import LogHandler
 
--- a/python/mozlint/mozlint/vcs.py
+++ b/python/mozlint/mozlint/vcs.py
@@ -1,12 +1,14 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+from __future__ import absolute_import, print_function
+
 import os
 import subprocess
 
 
 class VCSHelper(object):
     """A base VCS helper that always returns an empty list
     for the case when no version control was found.
     """
--- a/python/mozlint/setup.py
+++ b/python/mozlint/setup.py
@@ -1,12 +1,14 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+from __future__ import absolute_import
+
 from setuptools import setup
 
 VERSION = 0.1
 DEPS = ["mozlog>=3.4"]
 
 setup(
     name='mozlint',
     description='Framework for registering and running micro lints',
--- a/python/mozlint/test/conftest.py
+++ b/python/mozlint/test/conftest.py
@@ -1,12 +1,14 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+from __future__ import absolute_import
+
 import os
 import sys
 
 import pytest
 
 from mozlint import LintRoller
 
 
--- a/python/mozlint/test/files/foobar.py
+++ b/python/mozlint/test/files/foobar.py
@@ -1,2 +1,5 @@
 # Oh no.. we called this variable foobar, bad!
+
+from __future__ import absolute_import
+
 foobar = "a string"
--- a/python/mozlint/test/linters/external.py
+++ b/python/mozlint/test/linters/external.py
@@ -1,12 +1,14 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+from __future__ import absolute_import
+
 from mozlint import result
 from mozlint.errors import LintException
 
 
 def badreturncode(files, config, **lintargs):
     return 1
 
 
--- a/python/mozlint/test/test_cli.py
+++ b/python/mozlint/test/test_cli.py
@@ -1,12 +1,14 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+from __future__ import absolute_import
+
 import os
 from distutils.spawn import find_executable
 
 import mozunit
 import pytest
 
 from mozlint import cli
 
--- a/python/mozlint/test/test_formatters.py
+++ b/python/mozlint/test/test_formatters.py
@@ -1,13 +1,13 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-from __future__ import unicode_literals
+from __future__ import absolute_import, unicode_literals
 
 import json
 from collections import defaultdict
 
 import mozunit
 import pytest
 
 from mozlint import ResultContainer
--- a/python/mozlint/test/test_parser.py
+++ b/python/mozlint/test/test_parser.py
@@ -1,12 +1,14 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+from __future__ import absolute_import
+
 import os
 
 import mozunit
 import pytest
 
 from mozlint.parser import Parser
 from mozlint.errors import (
     LinterNotFound,
--- a/python/mozlint/test/test_roller.py
+++ b/python/mozlint/test/test_roller.py
@@ -1,12 +1,14 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+from __future__ import absolute_import
+
 import os
 import sys
 
 import mozunit
 import pytest
 
 from mozlint import ResultContainer
 from mozlint.errors import LintersNotConfigured, LintException
--- a/python/mozlint/test/test_types.py
+++ b/python/mozlint/test/test_types.py
@@ -1,12 +1,14 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+from __future__ import absolute_import
+
 import os
 
 import mozunit
 import pytest
 
 from mozlint.result import ResultContainer
 
 
--- a/python/mozlint/test/test_vcs.py
+++ b/python/mozlint/test/test_vcs.py
@@ -1,12 +1,14 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+from __future__ import absolute_import
+
 import os
 import subprocess
 
 import mozunit
 import pytest
 
 from mozlint.vcs import VCSHelper, vcs_class
 
--- a/security/manager/ssl/nsNSSCertificateDB.cpp
+++ b/security/manager/ssl/nsNSSCertificateDB.cpp
@@ -1340,16 +1340,21 @@ nsNSSCertificateDB::SetCertTrustFromStri
 NS_IMETHODIMP
 nsNSSCertificateDB::GetCerts(nsIX509CertList **_retval)
 {
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown()) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
+  nsresult rv = BlockUntilLoadableRootsLoaded();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
   nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext();
   nsCOMPtr<nsIX509CertList> nssCertList;
   UniqueCERTCertList certList(PK11_ListCerts(PK11CertListUnique, ctx));
 
   // nsNSSCertList 1) adopts certList, and 2) handles the nullptr case fine.
   // (returns an empty list)
   nssCertList = new nsNSSCertList(Move(certList), locker);
 
--- a/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
+++ b/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
@@ -88,17 +88,17 @@ CacheDirAndAutoClear(nsIProperties* aDir
     return;
   }
 
   *cacheVar = MakeUnique<nsString>();
   ClearOnShutdown(cacheVar);
   MOZ_ALWAYS_SUCCEEDS(dirToCache->GetPath(**cacheVar));
 
   // Convert network share path to format for sandbox policy.
-  if (Substring(**cac