Merge mozilla-central to beta. a=merge, l10n=me on a CLOSED TREE
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 21 Sep 2017 09:38:52 -0400
changeset 431719 06d4034a8a035cfefeec3b96164d5c66198666cd
parent 431556 efff4f307675a2226d4ef5867d64b6499bde39e3 (current diff)
parent 431718 f7e9777221a34f9f23c2e4933307eb38b621b679 (diff)
child 431720 62642f3648221bd0e7a7acaa7e90d47f77353501
push id7785
push userryanvm@gmail.com
push dateThu, 21 Sep 2017 13:39:55 +0000
treeherdermozilla-beta@06d4034a8a03 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone57.0
Merge mozilla-central to beta. a=merge, l10n=me on a CLOSED TREE
browser/themes/linux/privatebrowsing-mask.png
browser/themes/osx/privatebrowsing-mask-short.png
browser/themes/osx/privatebrowsing-mask-short@2x.png
browser/themes/osx/privatebrowsing-mask.png
browser/themes/osx/privatebrowsing-mask@2x.png
browser/themes/windows/privatebrowsing-mask-tabstrip-win7.png
browser/themes/windows/privatebrowsing-mask-tabstrip.png
browser/themes/windows/privatebrowsing-mask-titlebar-win7-tall.png
browser/themes/windows/privatebrowsing-mask-titlebar-win7.png
browser/themes/windows/privatebrowsing-mask-titlebar.png
mobile/android/services/src/main/java/org/mozilla/gecko/sync/DelayedWorkTracker.java
servo/components/style/cache.rs
testing/talos/talos/tests/perf-reftest/.eslintrc.json
testing/talos/talos/tests/perf-reftest/bloom-basic-2.html
testing/talos/talos/tests/perf-reftest/coalesce-1.html
testing/talos/talos/tests/perf-reftest/coalesce-ref.html
testing/talos/talos/tests/perf-reftest/perf_reftest.manifest
toolkit/components/extensions/schemas/native_host_manifest.json
toolkit/components/extensions/test/xpcshell/test_native_messaging.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -668,16 +668,20 @@ pref("network.protocol-handler.expose.ne
 pref("network.protocol-handler.expose.snews", false);
 pref("network.protocol-handler.expose.nntp", false);
 
 pref("accessibility.typeaheadfind", false);
 pref("accessibility.typeaheadfind.timeout", 5000);
 pref("accessibility.typeaheadfind.linksonly", false);
 pref("accessibility.typeaheadfind.flashBar", 1);
 
+// Accessibility indicator preferences such as support URL, enabled flag.
+pref("accessibility.support.url", "https://support.mozilla.org/%LOCALE%/kb/accessibility-services");
+pref("accessibility.indicator.enabled", true);
+
 pref("plugins.click_to_play", true);
 pref("plugins.testmode", false);
 
 // Should plugins that are hidden show the infobar UI?
 pref("plugins.show_infobar", false);
 
 // Should dismissing the hidden plugin infobar suppress it permanently?
 pref("plugins.remember_infobar_dismissal", true);
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -295,17 +295,18 @@ toolbarpaletteitem {
 %else
 /* On non-OSX, these should be start-aligned */
 #titlebar-buttonbox-container {
   -moz-box-align: start;
 }
 %endif
 
 %if !defined(MOZ_WIDGET_GTK)
-#TabsToolbar > .private-browsing-indicator {
+#TabsToolbar > .private-browsing-indicator,
+#TabsToolbar > .accessibility-indicator {
   -moz-box-ordinal-group: 1000;
 }
 %endif
 
 %ifdef XP_WIN
 #main-window[sizemode="maximized"] #titlebar-buttonbox {
   -moz-appearance: -moz-window-button-box-maximized;
 }
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1357,16 +1357,17 @@ var gBrowserInit = {
       gURLBar.setAttribute("enablehistory", "false");
     }
 
     // Misc. inits.
     TabletModeUpdater.init();
     CombinedStopReload.init();
     gPrivateBrowsingUI.init();
     BrowserPageActions.init();
+    gAccessibilityServiceIndicator.init();
 
     if (window.matchMedia("(-moz-os-version: windows-win8)").matches &&
         window.matchMedia("(-moz-windows-default-theme)").matches) {
       let windowFrameColor = new Color(...Cu.import("resource:///modules/Windows8WindowFrameColor.jsm", {})
                                             .Windows8WindowFrameColor.get());
       // Default to black for foreground text.
       if (!windowFrameColor.isContrastRatioAcceptable(new Color(0, 0, 0))) {
         document.documentElement.setAttribute("darkwindowframe", "true");
@@ -1856,16 +1857,18 @@ var gBrowserInit = {
     TrackingProtection.uninit();
 
     CaptivePortalWatcher.uninit();
 
     SidebarUI.uninit();
 
     DownloadsButton.uninit();
 
+    gAccessibilityServiceIndicator.uninit();
+
     // Now either cancel delayedStartup, or clean up the services initialized from
     // it.
     if (this._boundDelayedStartup) {
       this._cancelDelayedStartup();
     } else {
       if (Win7Features)
         Win7Features.onCloseWindow();
 
@@ -5425,17 +5428,18 @@ function onViewToolbarsPopupShowing(aEve
   // triggerNode can be a nested child element of a toolbaritem.
   let toolbarItem = popup.triggerNode;
 
   if (toolbarItem && toolbarItem.localName == "toolbarpaletteitem") {
     toolbarItem = toolbarItem.firstChild;
   } else if (toolbarItem && toolbarItem.localName != "toolbar") {
     while (toolbarItem && toolbarItem.parentNode) {
       let parent = toolbarItem.parentNode;
-      if ((parent.classList && parent.classList.contains("customization-target")) ||
+      if (parent.nodeType !== Node.ELEMENT_NODE ||
+          (parent.classList && parent.classList.contains("customization-target")) ||
           parent.getAttribute("overflowfortoolbar") || // Needs to work in the overflow list as well.
           parent.localName == "toolbarpaletteitem" ||
           parent.localName == "toolbar")
         break;
       toolbarItem = parent;
     }
   } else {
     toolbarItem = null;
@@ -8034,16 +8038,73 @@ function getTabModalPromptBox(aWindow) {
   return null;
 }
 
 /* DEPRECATED */
 function getBrowser() {
   return gBrowser;
 }
 
+const gAccessibilityServiceIndicator = {
+  init() {
+    // Pref to enable accessibility service indicator.
+    gPrefService.addObserver("accessibility.indicator.enabled", this);
+    // Accessibility service init/shutdown event.
+    Services.obs.addObserver(this, "a11y-init-or-shutdown");
+    this.update(Services.appinfo.accessibilityEnabled);
+  },
+
+  update(accessibilityEnabled = false) {
+    if (this.enabled && accessibilityEnabled) {
+      this._active = true;
+      document.documentElement.setAttribute("accessibilitymode", "true");
+      [...document.querySelectorAll(".accessibility-indicator")].forEach(
+        indicator => ["click", "keypress"].forEach(type =>
+          indicator.addEventListener(type, this)));
+      TabsInTitlebar.updateAppearance(true);
+    } else if (this._active) {
+      this._active = false;
+      document.documentElement.removeAttribute("accessibilitymode");
+      [...document.querySelectorAll(".accessibility-indicator")].forEach(
+        indicator => ["click", "keypress"].forEach(type =>
+          indicator.removeEventListener(type, this)));
+      TabsInTitlebar.updateAppearance(true);
+    }
+  },
+
+  observe(subject, topic, data) {
+    if (topic == "nsPref:changed" && data === "accessibility.indicator.enabled") {
+      this.update(Services.appinfo.accessibilityEnabled);
+    } else if (topic === "a11y-init-or-shutdown") {
+      // When "a11y-init-or-shutdown" event is fired, "1" indicates that
+      // accessibility service is started and "0" that it is shut down.
+      this.update(data === "1");
+    }
+  },
+
+  get enabled() {
+    return gPrefService.getBoolPref("accessibility.indicator.enabled");
+  },
+
+  handleEvent({ key, type }) {
+    if ((type === "keypress" && [" ", "Enter"].includes(key)) ||
+         type === "click") {
+      let a11yServicesSupportURL =
+        Services.urlFormatter.formatURLPref("accessibility.support.url");
+      gBrowser.selectedTab = gBrowser.addTab(a11yServicesSupportURL);
+    }
+  },
+
+  uninit() {
+    gPrefService.removeObserver("accessibility.indicator.enabled", this);
+    Services.obs.removeObserver(this, "a11y-init-or-shutdown");
+    this.update();
+  }
+};
+
 var gPrivateBrowsingUI = {
   init: function PBUI_init() {
     // Do nothing for normal windows
     if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
       return;
     }
 
     // Disable the Clear Recent History... menu item when in PB mode
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -568,30 +568,30 @@
   <box id="appMenu-viewCache" hidden="true"/>
 
 #ifdef CAN_DRAW_IN_TITLEBAR
 <vbox id="titlebar">
   <hbox id="titlebar-content">
     <spacer id="titlebar-spacer" flex="1"/>
     <hbox id="titlebar-buttonbox-container">
 #ifdef XP_WIN
-      <hbox id="private-browsing-indicator-titlebar">
-        <hbox class="private-browsing-indicator"/>
-      </hbox>
+      <button class="accessibility-indicator" tooltiptext="&accessibilityIndicator.tooltip;" aria-live="polite"/>
+      <hbox class="private-browsing-indicator"/>
 #endif
       <hbox id="titlebar-buttonbox">
         <toolbarbutton class="titlebar-button" id="titlebar-min" oncommand="window.minimize();"/>
         <toolbarbutton class="titlebar-button" id="titlebar-max" oncommand="onTitlebarMaxClick();"/>
         <toolbarbutton class="titlebar-button" id="titlebar-close" command="cmd_closeWindow"/>
       </hbox>
     </hbox>
 #ifdef XP_MACOSX
     <!-- OS X does not natively support RTL for its titlebar items, so we prevent this secondary
          buttonbox from reversing order in RTL by forcing an LTR direction. -->
     <hbox id="titlebar-secondary-buttonbox" dir="ltr">
+      <button class="accessibility-indicator" tooltiptext="&accessibilityIndicator.tooltip;" aria-live="polite"/>
       <hbox class="private-browsing-indicator"/>
       <hbox id="titlebar-fullscreen-button"/>
     </hbox>
 #endif
   </hbox>
 </vbox>
 #endif
 
@@ -628,18 +628,22 @@
              customizable="true"
              mode="icons"
              iconsize="small"
              aria-label="&tabsToolbar.label;"
              context="toolbar-context-menu"
              collapsed="true">
 
 #if defined(MOZ_WIDGET_GTK)
-      <hbox id="private-browsing-indicator"
+      <hbox class="private-browsing-indicator"
             skipintoolbarset="true"/>
+      <button class="accessibility-indicator"
+              tooltiptext="&accessibilityIndicator.tooltip;"
+              aria-live="polite"
+              skipintoolbarset="true"/>
 #endif
 
       <tabs id="tabbrowser-tabs"
             class="tabbrowser-tabs"
             tabbrowser="content"
             flex="1"
             setfocus="false"
             tooltip="tabbrowser-tab-tooltip"
@@ -677,16 +681,18 @@
                 label="&newUserContext.label;">
             <menupopup id="alltabs_containersMenuTab" />
           </menu>
           <menuseparator id="alltabs-popup-separator-2"/>
         </menupopup>
       </toolbarbutton>
 
 #if !defined(MOZ_WIDGET_GTK)
+      <button class="accessibility-indicator" tooltiptext="&accessibilityIndicator.tooltip;"
+              aria-live="polite" skipintoolbarset="true"/>
       <hbox class="private-browsing-indicator" skipintoolbarset="true"/>
 #endif
 #ifdef CAN_DRAW_IN_TITLEBAR
       <hbox class="titlebar-placeholder" type="caption-buttons"
             id="titlebar-placeholder-on-TabsToolbar-for-captions-buttons" persist="width"
 #ifndef XP_MACOSX
             ordinal="1000"
 #endif
--- a/browser/base/content/test/about/browser.ini
+++ b/browser/base/content/test/about/browser.ini
@@ -13,17 +13,17 @@ support-files =
 [browser_aboutCertError.js]
 [browser_aboutHealthReport.js]
 skip-if = os == "linux" # Bug 924307
 [browser_aboutHome_imitate.js]
 [browser_aboutHome_input.js]
 skip-if = os == "win" && debug && !e10s # Bug 1399648
 [browser_aboutHome_search_POST.js]
 [browser_aboutHome_search_composing.js]
-skip-if = os == "linux" && !debug && bits == 32 # Bug 1399648
+skip-if = !debug && (os == "mac" || (os == "linux" && bits == 32)) # Bug 1400491, bug 1399648
 [browser_aboutHome_search_searchbar.js]
 [browser_aboutHome_search_suggestion.js]
 skip-if = os == "mac" || (os == "linux" && !debug) # Bug 1399648
 [browser_aboutHome_search_telemetry.js]
 [browser_aboutHome_snippets.js]
 [browser_aboutHome_wrapsCorrectly.js]
 skip-if = os == "linux" && !debug # Bug 1395602
 [browser_aboutNetError.js]
--- a/browser/base/content/test/performance/browser_urlbar_keyed_search_reflows.js
+++ b/browser/base/content/test/performance/browser_urlbar_keyed_search_reflows.js
@@ -20,16 +20,17 @@ const EXPECTED_REFLOWS_FIRST_OPEN = [
   {
     stack: [
       "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
       "handleOverUnderflow@chrome://global/content/bindings/autocomplete.xml",
       "_onChanged@chrome://global/content/bindings/autocomplete.xml",
       "_appendCurrentResult/<@chrome://global/content/bindings/autocomplete.xml",
     ],
     times: 18, // This number should only ever go down - never up.
+    minTimes: 12,
   },
 
   {
     stack: [
       "_rebuild@chrome://browser/content/search/search.xml",
       "set_popup@chrome://browser/content/search/search.xml",
       "enableOneOffSearches@chrome://browser/content/urlbarBindings.xml",
       "_enableOrDisableOneOffSearches@chrome://browser/content/urlbarBindings.xml",
--- a/browser/base/content/test/performance/browser_urlbar_search_reflows.js
+++ b/browser/base/content/test/performance/browser_urlbar_search_reflows.js
@@ -18,19 +18,16 @@ requestLongerTimeout(5);
 const EXPECTED_REFLOWS_FIRST_OPEN = [
   // Bug 1357054
   {
     stack: [
       "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
       "handleOverUnderflow@chrome://global/content/bindings/autocomplete.xml",
       "_onChanged@chrome://global/content/bindings/autocomplete.xml",
       "_appendCurrentResult/<@chrome://global/content/bindings/autocomplete.xml",
-      "setTimeout handler*_appendCurrentResult@chrome://global/content/bindings/autocomplete.xml",
-      "_invalidate@chrome://global/content/bindings/autocomplete.xml",
-      "invalidate@chrome://global/content/bindings/autocomplete.xml"
     ],
     times: 6, // This number should only ever go down - never up.
     minTimes: 0, // Sometimes this is not hit.
   },
 
   {
     stack: [
       "_rebuild@chrome://browser/content/search/search.xml",
--- a/browser/base/content/test/tabs/browser.ini
+++ b/browser/base/content/test/tabs/browser.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 support-files =
   dummy_page.html
   test_bug1358314.html
 
 [browser_abandonment_telemetry.js]
+[browser_accessibility_indicator.js]
 [browser_allow_process_switches_despite_related_browser.js]
 [browser_contextmenu_openlink_after_tabnavigated.js]
 [browser_isLocalAboutURI.js]
 [browser_tabCloseProbes.js]
 [browser_tabSpinnerProbe.js]
 skip-if = !e10s # Tab spinner is e10s only.
 [browser_tabSwitchPrintPreview.js]
 skip-if = os == 'mac'
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabs/browser_accessibility_indicator.js
@@ -0,0 +1,124 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const A11Y_INDICATOR_ENABLED_PREF = "accessibility.indicator.enabled";
+
+/**
+ * Test various pref and UI properties based on whether the accessibility
+ * indicator is enabled and the accessibility service is initialized.
+ * @param  {Object}  win      browser window to check the indicator in.
+ * @param  {Boolean} enabled  pref flag for accessibility indicator.
+ * @param  {Boolean} active   whether accessibility service is started or not.
+ */
+function testIndicatorState(win, enabled, active) {
+  is(Services.prefs.getBoolPref(A11Y_INDICATOR_ENABLED_PREF), enabled,
+    `Indicator is ${enabled ? "enabled" : "disabled"}.`);
+  is(Services.appinfo.accessibilityEnabled, active,
+    `Accessibility service is ${active ? "enabled" : "disabled"}.`);
+
+  let visible = enabled && active;
+  is(win.document.documentElement.hasAttribute("accessibilitymode"), visible,
+    `accessibilitymode flag is ${visible ? "set" : "unset"}.`);
+
+  // Browser UI has 2 indicators in markup for OSX and Windows but only 1 is
+  // shown depending on whether the titlebar is enabled.
+  let expectedVisibleCount = visible ? 1 : 0;
+  let visibleCount = 0;
+  [...win.document.querySelectorAll(".accessibility-indicator")].forEach(indicator =>
+    win.getComputedStyle(indicator).getPropertyValue("display") !== "none" &&
+    visibleCount++);
+  is(expectedVisibleCount, visibleCount,
+    `Indicator is ${visible ? "visible" : "invisible"}.`);
+}
+
+/**
+ * Instantiate accessibility service and wait for event associated with its
+ * startup, if necessary.
+ */
+async function initAccessibilityService() {
+  let accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+    Ci.nsIAccessibilityService);
+
+  if (!Services.appinfo.accessibilityEnabled) {
+    await new Promise(resolve => {
+      let observe = (subject, topic, data) => {
+        Services.obs.removeObserver(observe, "a11y-init-or-shutdown");
+        // "1" indicates that the accessibility service is initialized.
+        data === "1" && resolve();
+      };
+      Services.obs.addObserver(observe, "a11y-init-or-shutdown");
+    })
+  }
+
+  return accService;
+}
+
+/**
+ * If accessibility service is not yet disabled, wait for the event associated
+ * with its shutdown.
+ */
+async function shutdownAccessibilityService() {
+  if (Services.appinfo.accessibilityEnabled) {
+    await new Promise(resolve => {
+      let observe = (subject, topic, data) => {
+        Services.obs.removeObserver(observe, "a11y-init-or-shutdown");
+        // "1" indicates that the accessibility service is shutdown.
+        data === "0" && resolve();
+      };
+      Services.obs.addObserver(observe, "a11y-init-or-shutdown");
+    })
+  }
+}
+
+/**
+ * Force garbage collection.
+ */
+function forceGC() {
+  SpecialPowers.gc();
+  SpecialPowers.forceShrinkingGC();
+  SpecialPowers.forceCC();
+}
+
+add_task(async function test_accessibility_indicator() {
+  info("Test default accessibility indicator state.");
+  let newWin = await BrowserTestUtils.openNewBrowserWindow();
+  testIndicatorState(window, true, false);
+  testIndicatorState(newWin, true, false);
+
+  info("Enable accessibility and ensure the indicator is shown in all windows.");
+  let accService = await initAccessibilityService(); // eslint-disable-line no-unused-vars
+  testIndicatorState(window, true, true);
+  testIndicatorState(newWin, true, true);
+
+  info("Open a new window and ensure the indicator is shown there by default.");
+  let dynamicWin = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+  testIndicatorState(dynamicWin, true, true);
+  await BrowserTestUtils.closeWindow(dynamicWin);
+
+  info("Preff off accessibility indicator.");
+  Services.prefs.setBoolPref(A11Y_INDICATOR_ENABLED_PREF, false);
+  testIndicatorState(window, false, true);
+  testIndicatorState(newWin, false, true);
+  dynamicWin = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+  testIndicatorState(dynamicWin, false, true);
+
+  info("Preff on accessibility indicator.");
+  Services.prefs.setBoolPref(A11Y_INDICATOR_ENABLED_PREF, true);
+  testIndicatorState(window, true, true);
+  testIndicatorState(newWin, true, true);
+  testIndicatorState(dynamicWin, true, true);
+
+  info("Disable accessibility and ensure the indicator is hidden in all windows.");
+  accService = undefined;
+  forceGC();
+  await shutdownAccessibilityService();
+  testIndicatorState(window, true, false);
+  testIndicatorState(newWin, true, false);
+  testIndicatorState(dynamicWin, true, false);
+
+  Services.prefs.clearUserPref(A11Y_INDICATOR_ENABLED_PREF);
+  await BrowserTestUtils.closeWindow(newWin);
+  await BrowserTestUtils.closeWindow(dynamicWin);
+});
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -621,18 +621,22 @@
                        class="subviewbutton"
                        label="&savePageCmd.label;"
                        key="key_savePage"
                        command="Browser:SavePage"
                        />
         <toolbarbutton id="appMenu-print-button"
                        class="subviewbutton subviewbutton-iconic"
                        label="&printCmd.label;"
+#ifdef XP_MACOSX
                        key="printKb"
                        command="cmd_print"
+#else
+                       command="cmd_printPreview"
+#endif
                        />
         <toolbarseparator/>
         <toolbarbutton id="appMenu-find-button"
                        class="subviewbutton subviewbutton-iconic"
                        label="&findOnCmd.label;"
                        key="key_find"
                        command="cmd_find"/>
         <toolbarbutton id="appMenu-more-button"
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -1636,18 +1636,17 @@ var gPrivacyPane = {
       case 0: // access allowed
         checkbox.checked = false;
         break;
     }
   },
 
   _initA11yString() {
     let a11yLearnMoreLink =
-      Services.urlFormatter.formatURLPref("app.support.baseURL") +
-      "accessibility";
+      Services.urlFormatter.formatURLPref("accessibility.support.url");
     document.getElementById("a11yLearnMoreLink")
       .setAttribute("href", a11yLearnMoreLink);
   },
 
   updateA11yPrefs(checked) {
     Services.prefs.setIntPref("accessibility.force_disabled", checked ? 1 : 0);
   }
 };
--- a/browser/extensions/activity-stream/common/Actions.jsm
+++ b/browser/extensions/activity-stream/common/Actions.jsm
@@ -67,16 +67,18 @@ for (const type of [
   "SNIPPETS_DATA",
   "SNIPPETS_RESET",
   "SYSTEM_TICK",
   "TELEMETRY_IMPRESSION_STATS",
   "TELEMETRY_PERFORMANCE_EVENT",
   "TELEMETRY_UNDESIRED_EVENT",
   "TELEMETRY_USER_EVENT",
   "TOP_SITES_ADD",
+  "TOP_SITES_CANCEL_EDIT",
+  "TOP_SITES_EDIT",
   "TOP_SITES_PIN",
   "TOP_SITES_UNPIN",
   "TOP_SITES_UPDATED",
   "UNINIT"
 ]) {
   actionTypes[type] = type;
 }
 
--- a/browser/extensions/activity-stream/common/PerfService.jsm
+++ b/browser/extensions/activity-stream/common/PerfService.jsm
@@ -62,25 +62,34 @@ this._PerfService = function _PerfServic
     return this._perf.getEntriesByName(name, type);
   },
 
   /**
    * The timeOrigin property from the appropriate performance object.
    * Used to ensure that timestamps from the add-on code and the content code
    * are comparable.
    *
+   * @note If this is called from a context without a window
+   * (eg a JSM in chrome), it will return the timeOrigin of the XUL hidden
+   * window, which appears to be the first created window (and thus
+   * timeOrigin) in the browser.  Note also, however, there is also a private
+   * hidden window, presumably for private browsing, which appears to be
+   * created dynamically later.  Exactly how/when that shows up needs to be
+   * investigated.
+   *
    * @return {Number} A double of milliseconds with a precision of 0.5us.
    */
   get timeOrigin() {
     return this._perf.timeOrigin;
   },
 
   /**
    * Returns the "absolute" version of performance.now(), i.e. one that
-   * based on the timeOrigin of the XUL hiddenwindow.
+   * should ([bug 1401406](https://bugzilla.mozilla.org/show_bug.cgi?id=1401406)
+   * be comparable across both chrome and content.
    *
    * @return {Number}
    */
   absNow: function absNow() {
     return this.timeOrigin + this._perf.now();
   },
 
   /**
--- a/browser/extensions/activity-stream/common/Reducers.jsm
+++ b/browser/extensions/activity-stream/common/Reducers.jsm
@@ -24,17 +24,23 @@ const INITIAL_STATE = {
     // The version of the system-addon
     version: null
   },
   Snippets: {initialized: false},
   TopSites: {
     // Have we received real data from history yet?
     initialized: false,
     // The history (and possibly default) links
-    rows: []
+    rows: [],
+    // Used in content only to dispatch action from
+    // context menu to TopSitesEdit.
+    editForm: {
+      visible: false,
+      site: null
+    }
   },
   Prefs: {
     initialized: false,
     values: {}
   },
   Dialog: {
     visible: false,
     data: {}
@@ -99,16 +105,20 @@ function TopSites(prevState = INITIAL_ST
   let hasMatch;
   let newRows;
   switch (action.type) {
     case at.TOP_SITES_UPDATED:
       if (!action.data) {
         return prevState;
       }
       return Object.assign({}, prevState, {initialized: true, rows: action.data});
+    case at.TOP_SITES_EDIT:
+      return Object.assign({}, prevState, {editForm: {visible: true, site: action.data}});
+    case at.TOP_SITES_CANCEL_EDIT:
+      return Object.assign({}, prevState, {editForm: {visible: false}});
     case at.SCREENSHOT_UPDATED:
       newRows = prevState.rows.map(row => {
         if (row && row.url === action.data.url) {
           hasMatch = true;
           return Object.assign({}, row, {screenshot: action.data.screenshot});
         }
         return row;
       });
--- a/browser/extensions/activity-stream/data/content/activity-stream-initial-state.js
+++ b/browser/extensions/activity-stream/data/content/activity-stream-initial-state.js
@@ -1,13 +1,17 @@
 // Note - this is a generated file.
   window.gActivityStreamPrerenderedState = {
   "TopSites": {
     "initialized": false,
-    "rows": []
+    "rows": [],
+    "editForm": {
+      "visible": false,
+      "site": null
+    }
   },
   "App": {
     "initialized": false,
     "locale": "en-PRERENDER",
     "strings": {
       "newtab_page_title": " ",
       "default_label_loading": " ",
       "header_top_sites": " ",
--- a/browser/extensions/activity-stream/data/content/activity-stream.bundle.js
+++ b/browser/extensions/activity-stream/data/content/activity-stream.bundle.js
@@ -89,17 +89,17 @@ const globalImportContext = typeof Windo
 
 
 // Create an object that avoids accidental differing key/value pairs:
 // {
 //   INIT: "INIT",
 //   UNINIT: "UNINIT"
 // }
 const actionTypes = {};
-for (const type of ["BLOCK_URL", "BOOKMARK_URL", "DELETE_BOOKMARK_BY_ID", "DELETE_HISTORY_URL", "DELETE_HISTORY_URL_CONFIRM", "DIALOG_CANCEL", "DIALOG_OPEN", "INIT", "LOCALE_UPDATED", "MIGRATION_CANCEL", "MIGRATION_COMPLETED", "MIGRATION_START", "NEW_TAB_INIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_REHYDRATED", "NEW_TAB_STATE_REQUEST", "NEW_TAB_UNLOAD", "OPEN_LINK", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_CHANGED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINK_BLOCKED", "PLACES_LINK_DELETED", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_REGISTER", "SECTION_UPDATE", "SECTION_UPDATE_CARD", "SET_PREF", "SHOW_FIREFOX_ACCOUNTS", "SNIPPETS_DATA", "SNIPPETS_RESET", "SYSTEM_TICK", "TELEMETRY_IMPRESSION_STATS", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_ADD", "TOP_SITES_PIN", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "UNINIT"]) {
+for (const type of ["BLOCK_URL", "BOOKMARK_URL", "DELETE_BOOKMARK_BY_ID", "DELETE_HISTORY_URL", "DELETE_HISTORY_URL_CONFIRM", "DIALOG_CANCEL", "DIALOG_OPEN", "INIT", "LOCALE_UPDATED", "MIGRATION_CANCEL", "MIGRATION_COMPLETED", "MIGRATION_START", "NEW_TAB_INIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_REHYDRATED", "NEW_TAB_STATE_REQUEST", "NEW_TAB_UNLOAD", "OPEN_LINK", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_CHANGED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINK_BLOCKED", "PLACES_LINK_DELETED", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_REGISTER", "SECTION_UPDATE", "SECTION_UPDATE_CARD", "SET_PREF", "SHOW_FIREFOX_ACCOUNTS", "SNIPPETS_DATA", "SNIPPETS_RESET", "SYSTEM_TICK", "TELEMETRY_IMPRESSION_STATS", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_ADD", "TOP_SITES_CANCEL_EDIT", "TOP_SITES_EDIT", "TOP_SITES_PIN", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "UNINIT"]) {
   actionTypes[type] = type;
 }
 
 // Helper function for creating routed actions between content and main
 // Not intended to be used by consumers
 function _RouteMessage(action, options) {
   const meta = action.meta ? Object.assign({}, action.meta) : {};
   if (!options || !options.from || !options.to) {
@@ -328,17 +328,17 @@ module.exports = g;
 
 
 /***/ }),
 /* 5 */
 /***/ (function(module, exports) {
 
 module.exports = {
   TOP_SITES_SOURCE: "TOP_SITES",
-  TOP_SITES_CONTEXT_MENU_OPTIONS: ["CheckPinTopSite", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl", "DeleteUrl"],
+  TOP_SITES_CONTEXT_MENU_OPTIONS: ["CheckPinTopSite", "EditTopSite", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl", "DeleteUrl"],
   // minimum size necessary to show a rich icon instead of a screenshot
   MIN_RICH_FAVICON_SIZE: 96,
   // minimum size necessary to show any icon in the top left corner with a screenshot
   MIN_CORNER_FAVICON_SIZE: 16
 };
 
 /***/ }),
 /* 6 */
@@ -371,17 +371,23 @@ const INITIAL_STATE = {
     // The version of the system-addon
     version: null
   },
   Snippets: { initialized: false },
   TopSites: {
     // Have we received real data from history yet?
     initialized: false,
     // The history (and possibly default) links
-    rows: []
+    rows: [],
+    // Used in content only to dispatch action from
+    // context menu to TopSitesEdit.
+    editForm: {
+      visible: false,
+      site: null
+    }
   },
   Prefs: {
     initialized: false,
     values: {}
   },
   Dialog: {
     visible: false,
     data: {}
@@ -449,16 +455,20 @@ function TopSites(prevState = INITIAL_ST
   let hasMatch;
   let newRows;
   switch (action.type) {
     case at.TOP_SITES_UPDATED:
       if (!action.data) {
         return prevState;
       }
       return Object.assign({}, prevState, { initialized: true, rows: action.data });
+    case at.TOP_SITES_EDIT:
+      return Object.assign({}, prevState, { editForm: { visible: true, site: action.data } });
+    case at.TOP_SITES_CANCEL_EDIT:
+      return Object.assign({}, prevState, { editForm: { visible: false } });
     case at.SCREENSHOT_UPDATED:
       newRows = prevState.rows.map(row => {
         if (row && row.url === action.data.url) {
           hasMatch = true;
           return Object.assign({}, row, { screenshot: action.data.screenshot });
         }
         return row;
       });
@@ -726,25 +736,34 @@ var _PerfService = function _PerfService
     return this._perf.getEntriesByName(name, type);
   },
 
   /**
    * The timeOrigin property from the appropriate performance object.
    * Used to ensure that timestamps from the add-on code and the content code
    * are comparable.
    *
+   * @note If this is called from a context without a window
+   * (eg a JSM in chrome), it will return the timeOrigin of the XUL hidden
+   * window, which appears to be the first created window (and thus
+   * timeOrigin) in the browser.  Note also, however, there is also a private
+   * hidden window, presumably for private browsing, which appears to be
+   * created dynamically later.  Exactly how/when that shows up needs to be
+   * investigated.
+   *
    * @return {Number} A double of milliseconds with a precision of 0.5us.
    */
   get timeOrigin() {
     return this._perf.timeOrigin;
   },
 
   /**
    * Returns the "absolute" version of performance.now(), i.e. one that
-   * based on the timeOrigin of the XUL hiddenwindow.
+   * should ([bug 1401406](https://bugzilla.mozilla.org/show_bug.cgi?id=1401406)
+   * be comparable across both chrome and content.
    *
    * @return {Number}
    */
   absNow: function absNow() {
     return this.timeOrigin + this._perf.now();
   },
 
   /**
@@ -1417,16 +1436,17 @@ class TopSitesEdit extends React.PureCom
     }));
   }
   onModalOverlayClick() {
     this.setState({ showEditModal: false, showAddForm: false, showEditForm: false });
     this.props.dispatch(ac.UserEvent({
       source: TOP_SITES_SOURCE,
       event: "TOP_SITES_EDIT_CLOSE"
     }));
+    this.props.dispatch({ type: at.TOP_SITES_CANCEL_EDIT });
   }
   onShowMoreLessClick() {
     const prefIsSetToDefault = this.props.TopSitesCount === TOP_SITES_DEFAULT_LENGTH;
     this.props.dispatch(ac.SendToMain({
       type: at.SET_PREF,
       data: { name: "topSitesCount", value: prefIsSetToDefault ? TOP_SITES_SHOWMORE_LENGTH : TOP_SITES_DEFAULT_LENGTH }
     }));
     this.props.dispatch(ac.UserEvent({
@@ -1438,27 +1458,34 @@ class TopSitesEdit extends React.PureCom
     this.setState({ showAddForm: true });
     this.props.dispatch(ac.UserEvent({
       source: TOP_SITES_SOURCE,
       event: "TOP_SITES_ADD_FORM_OPEN"
     }));
   }
   onFormClose() {
     this.setState({ showAddForm: false, showEditForm: false });
+    this.props.dispatch({ type: at.TOP_SITES_CANCEL_EDIT });
   }
   onEdit(index) {
     this.setState({ showEditForm: true, editIndex: index });
     this.props.dispatch(ac.UserEvent({
       source: TOP_SITES_SOURCE,
       event: "TOP_SITES_EDIT_FORM_OPEN"
     }));
   }
   render() {
     const realTopSites = this.props.TopSites.rows.slice(0, this.props.TopSitesCount);
     const placeholderCount = this.props.TopSitesCount - realTopSites.length;
+    const showEditForm = this.props.TopSites.editForm && this.props.TopSites.editForm.visible || this.state.showEditModal && this.state.showEditForm;
+    let editIndex = this.state.editIndex;
+    if (showEditForm && this.props.TopSites.editForm.visible) {
+      const targetURL = this.props.TopSites.editForm.site.url;
+      editIndex = this.props.TopSites.rows.findIndex(s => s.url === targetURL);
+    }
     return React.createElement(
       "div",
       { className: "edit-topsites-wrapper" },
       React.createElement(
         "div",
         { className: "edit-topsites-button" },
         React.createElement(
           "button",
@@ -1525,27 +1552,27 @@ class TopSitesEdit extends React.PureCom
         { className: "edit-topsites" },
         React.createElement("div", { className: "modal-overlay", onClick: this.onModalOverlayClick }),
         React.createElement(
           "div",
           { className: "modal" },
           React.createElement(TopSiteForm, { onClose: this.onFormClose, dispatch: this.props.dispatch, intl: this.props.intl })
         )
       ),
-      this.state.showEditModal && this.state.showEditForm && React.createElement(
+      showEditForm && React.createElement(
         "div",
         { className: "edit-topsites" },
         React.createElement("div", { className: "modal-overlay", onClick: this.onModalOverlayClick }),
         React.createElement(
           "div",
           { className: "modal" },
           React.createElement(TopSiteForm, {
-            label: this.props.TopSites.rows[this.state.editIndex].label || this.props.TopSites.rows[this.state.editIndex].hostname,
-            url: this.props.TopSites.rows[this.state.editIndex].url,
-            index: this.state.editIndex,
+            label: this.props.TopSites.rows[editIndex].label || this.props.TopSites.rows[editIndex].hostname,
+            url: this.props.TopSites.rows[editIndex].url,
+            index: editIndex,
             editMode: true,
             onClose: this.onFormClose,
             dispatch: this.props.dispatch,
             intl: this.props.intl })
         )
       )
     );
   }
@@ -1927,16 +1954,24 @@ module.exports = {
     }),
     impression: ac.ImpressionStats({
       source: eventSource,
       pocket: 0,
       incognito: true,
       tiles: [{ id: site.guid, pos: index }]
     }),
     userEvent: "SAVE_TO_POCKET"
+  }),
+  EditTopSite: site => ({
+    id: "edit_topsites_button_text",
+    icon: "edit",
+    action: {
+      type: at.TOP_SITES_EDIT,
+      data: { url: site.url, label: site.label }
+    }
   })
 };
 
 module.exports.CheckBookmark = site => site.bookmarkGuid ? module.exports.RemoveBookmark(site) : module.exports.AddBookmark(site);
 module.exports.CheckPinTopSite = (site, index) => site.isPinned ? module.exports.UnpinTopSite(site) : module.exports.PinTopSite(site, index);
 
 /***/ }),
 /* 19 */
--- a/browser/extensions/activity-stream/data/content/activity-stream.css
+++ b/browser/extensions/activity-stream/data/content/activity-stream.css
@@ -161,16 +161,19 @@ a {
       padding: 0;
       text-decoration: underline; }
     .actions button.done {
       background: #0060DF;
       border: solid 1px #0060DF;
       color: #FFF;
       margin-inline-start: auto; }
 
+#snippets-container {
+  z-index: 1; }
+
 .outer-wrapper {
   display: flex;
   padding: 40px 32px 32px;
   height: 100%;
   flex-grow: 1; }
   .outer-wrapper.fixed-to-top {
     height: auto; }
 
@@ -279,18 +282,17 @@ main {
       background-clip: padding-box;
       border: 1px solid #B1B1B3;
       border-radius: 100%;
       box-shadow: 0 2px rgba(12, 12, 13, 0.1);
       fill: rgba(12, 12, 13, 0.8);
       transform: scale(0.25);
       opacity: 0;
       transition-property: transform, opacity;
-      transition-duration: 200ms;
-      z-index: 399; }
+      transition-duration: 200ms; }
       .top-sites-list .top-site-outer .context-menu-button:focus, .top-sites-list .top-site-outer .context-menu-button:active {
         transform: scale(1);
         opacity: 1; }
     .top-sites-list .top-site-outer:hover .tile, .top-sites-list .top-site-outer:focus .tile, .top-sites-list .top-site-outer.active .tile {
       box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1), 0 0 0 5px #D7D7DB;
       transition: box-shadow 150ms; }
     .top-sites-list .top-site-outer:hover .context-menu-button, .top-sites-list .top-site-outer:focus .context-menu-button, .top-sites-list .top-site-outer.active .context-menu-button {
       transform: scale(1);
@@ -336,17 +338,16 @@ main {
       background-color: #F9F9FA; }
     .top-sites-list .top-site-outer .rich-icon {
       top: 0;
       offset-inline-start: 0;
       height: 100%;
       width: 100%;
       background-size: 96px; }
     .top-sites-list .top-site-outer .default-icon {
-      z-index: 1;
       bottom: -6px;
       height: 42px;
       offset-inline-end: -6px;
       width: 42px;
       background-size: 32px;
       display: flex;
       align-items: center;
       justify-content: center;
@@ -967,18 +968,17 @@ main {
     background-clip: padding-box;
     border: 1px solid #B1B1B3;
     border-radius: 100%;
     box-shadow: 0 2px rgba(12, 12, 13, 0.1);
     fill: rgba(12, 12, 13, 0.8);
     transform: scale(0.25);
     opacity: 0;
     transition-property: transform, opacity;
-    transition-duration: 200ms;
-    z-index: 399; }
+    transition-duration: 200ms; }
     .card-outer .context-menu-button:focus, .card-outer .context-menu-button:active {
       transform: scale(1);
       opacity: 1; }
   .card-outer.placeholder {
     background: transparent; }
     .card-outer.placeholder .card {
       box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); }
   .card-outer .card {
--- a/browser/extensions/activity-stream/data/locales.json
+++ b/browser/extensions/activity-stream/data/locales.json
@@ -6111,16 +6111,17 @@
     "settings_pane_topsites_body": "เข้าถึงเว็บไซต์ที่คุณเยี่ยมชมมากที่สุด",
     "settings_pane_topsites_options_showmore": "แสดงสองแถว",
     "settings_pane_bookmarks_header": "ที่คั่นหน้าเมื่อเร็ว ๆ นี้",
     "settings_pane_bookmarks_body": "ที่คั่นหน้าที่สร้างใหม่ของคุณในตำแหน่งที่ตั้งเดียวที่สะดวก",
     "settings_pane_visit_again_header": "เยี่ยมชมอีกครั้ง",
     "settings_pane_highlights_header": "รายการเด่น",
     "settings_pane_highlights_options_bookmarks": "ที่คั่นหน้า",
     "settings_pane_highlights_options_visited": "ไซต์ที่เยี่ยมชมแล้ว",
+    "settings_pane_snippets_header": "ส่วนย่อย",
     "settings_pane_done_button": "เสร็จสิ้น",
     "edit_topsites_button_text": "แก้ไข",
     "edit_topsites_button_label": "ปรับแต่งส่วนไซต์เด่นของคุณ",
     "edit_topsites_showmore_button": "แสดงเพิ่มเติม",
     "edit_topsites_showless_button": "แสดงน้อยลง",
     "edit_topsites_done_button": "เสร็จสิ้น",
     "edit_topsites_pin_button": "ปักหมุดไซต์นี้",
     "edit_topsites_unpin_button": "ถอนหมุดไซต์นี้",
--- a/browser/extensions/activity-stream/install.rdf.in
+++ b/browser/extensions/activity-stream/install.rdf.in
@@ -3,17 +3,17 @@
 #filter substitution
 
 <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>activity-stream@mozilla.org</em:id>
     <em:type>2</em:type>
     <em:bootstrap>true</em:bootstrap>
     <em:unpack>false</em:unpack>
-    <em:version>2017.09.20.0232-e27564ef</em:version>
+    <em:version>2017.09.20.1184-2d88ef77</em:version>
     <em:name>Activity Stream</em:name>
     <em:description>A rich visual history feed and a reimagined home page make it easier than ever to find exactly what you're looking for in Firefox.</em:description>
     <em:multiprocessCompatible>true</em:multiprocessCompatible>
 
     <em:targetApplication>
       <Description>
         <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
         <em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
--- a/browser/extensions/activity-stream/lib/ActivityStream.jsm
+++ b/browser/extensions/activity-stream/lib/ActivityStream.jsm
@@ -228,26 +228,36 @@ this.ActivityStream = class ActivityStre
   constructor(options = {}) {
     this.initialized = false;
     this.options = options;
     this.store = new Store();
     this.feeds = FEEDS_CONFIG;
     this._defaultPrefs = new DefaultPrefs(PREFS_CONFIG);
   }
   init() {
-    this._updateDynamicPrefs();
-    this._defaultPrefs.init();
+    try {
+      this._updateDynamicPrefs();
+      this._defaultPrefs.init();
+
+      // Hook up the store and let all feeds and pages initialize
+      this.store.init(this.feeds, ac.BroadcastToContent({
+        type: at.INIT,
+        data: {version: this.options.version}
+      }), {type: at.UNINIT});
 
-    // Hook up the store and let all feeds and pages initialize
-    this.store.init(this.feeds, ac.BroadcastToContent({
-      type: at.INIT,
-      data: {version: this.options.version}
-    }), {type: at.UNINIT});
-
-    this.initialized = true;
+      this.initialized = true;
+    } catch (e) {
+      // TelemetryFeed could be unavailable if the telemetry is disabled, or
+      // the telemetry feed is not yet initialized.
+      const telemetryFeed = this.store.feeds.get("feeds.telemetry");
+      if (telemetryFeed) {
+        telemetryFeed.handleUndesiredEvent({data: {event: "ADDON_INIT_FAILED"}});
+      }
+      throw e;
+    }
   }
   uninit() {
     if (this.geo === "") {
       Services.prefs.removeObserver(GEO_PREF, this);
     }
 
     this.store.uninit();
     this.initialized = false;
--- a/browser/extensions/activity-stream/lib/HighlightsFeed.jsm
+++ b/browser/extensions/activity-stream/lib/HighlightsFeed.jsm
@@ -10,31 +10,43 @@ const {actionTypes: at} = Cu.import("res
 
 const {shortURL} = Cu.import("resource://activity-stream/lib/ShortURL.jsm", {});
 const {SectionsManager} = Cu.import("resource://activity-stream/lib/SectionsManager.jsm", {});
 const {TOP_SITES_SHOWMORE_LENGTH} = Cu.import("resource://activity-stream/common/Reducers.jsm", {});
 const {Dedupe} = Cu.import("resource://activity-stream/common/Dedupe.jsm", {});
 
 XPCOMUtils.defineLazyModuleGetter(this, "filterAdult",
   "resource://activity-stream/lib/FilterAdult.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LinksCache",
+  "resource://activity-stream/lib/LinksCache.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
   "resource://gre/modules/NewTabUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Screenshots",
   "resource://activity-stream/lib/Screenshots.jsm");
 
 const HIGHLIGHTS_MAX_LENGTH = 9;
 const HIGHLIGHTS_UPDATE_TIME = 15 * 60 * 1000; // 15 minutes
 const MANY_EXTRA_LENGTH = HIGHLIGHTS_MAX_LENGTH * 5 + TOP_SITES_SHOWMORE_LENGTH;
 const SECTION_ID = "highlights";
 
 this.HighlightsFeed = class HighlightsFeed {
   constructor() {
     this.highlightsLastUpdated = 0;
-    this.highlights = [];
+    this.highlightsLength = 0;
     this.dedupe = new Dedupe(this._dedupeKey);
+    this.linksCache = new LinksCache(NewTabUtils.activityStreamLinks,
+      "getHighlights", (oldLink, newLink) => {
+        // Migrate any pending images or images to the new link
+        for (const property of ["__fetchingScreenshot", "image"]) {
+          const oldValue = oldLink[property];
+          if (oldValue) {
+            newLink[property] = oldValue;
+          }
+        }
+      });
   }
 
   _dedupeKey(site) {
     return site && site.url;
   }
 
   init() {
     SectionsManager.onceInitialized(this.postInit.bind(this));
@@ -45,109 +57,105 @@ this.HighlightsFeed = class HighlightsFe
     this.fetchHighlights(true);
   }
 
   uninit() {
     SectionsManager.disableSection(SECTION_ID);
   }
 
   async fetchHighlights(broadcast = false) {
+    // We broadcast when we want to force an update, so get fresh links
+    if (broadcast) {
+      this.linksCache.expire();
+    }
+
     // We need TopSites to have been initialised for deduping
     if (!this.store.getState().TopSites.initialized) {
       await new Promise(resolve => {
         const unsubscribe = this.store.subscribe(() => {
           if (this.store.getState().TopSites.initialized) {
             unsubscribe();
             resolve();
           }
         });
       });
     }
 
     // Request more than the expected length to allow for items being removed by
     // deduping against Top Sites or multiple history from the same domain, etc.
-    const manyPages = await NewTabUtils.activityStreamLinks.getHighlights({numItems: MANY_EXTRA_LENGTH});
+    const manyPages = await this.linksCache.request({numItems: MANY_EXTRA_LENGTH});
 
     // Remove adult highlights if we need to
     const checkedAdult = this.store.getState().Prefs.values.filterAdult ?
       filterAdult(manyPages) : manyPages;
 
     // Remove any Highlights that are in Top Sites already
     const [, deduped] = this.dedupe.group(this.store.getState().TopSites.rows, checkedAdult);
 
-    // Store existing images in case we need to reuse them
-    const currentImages = {};
-    for (const site of this.highlights) {
-      if (site && site.image) {
-        currentImages[site.url] = site.image;
-      }
-    }
-
     // Keep all "bookmark"s and at most one (most recent) "history" per host
-    this.highlights = [];
+    const highlights = [];
     const hosts = new Set();
     for (const page of deduped) {
       const hostname = shortURL(page);
       // Skip this history page if we already something from the same host
       if (page.type === "history" && hosts.has(hostname)) {
         continue;
       }
 
       // If we already have the image for the card, use that immediately. Else
       // asynchronously fetch the image.
-      const image = currentImages[page.url];
-      if (!image) {
-        this.fetchImage(page.url, page.preview_image_url);
+      if (!page.image) {
+        this.fetchImage(page);
       }
 
       // We want the page, so update various fields for UI
       Object.assign(page, {
-        image,
         hasImage: true, // We always have an image - fall back to a screenshot
         hostname,
         type: page.bookmarkGuid ? "bookmark" : page.type
       });
 
       // Add the "bookmark" or not-skipped "history"
-      this.highlights.push(page);
+      highlights.push(page);
       hosts.add(hostname);
 
+      // Remove any internal properties
+      delete page.__fetchingScreenshot;
+      delete page.__updateCache;
+
       // Skip the rest if we have enough items
-      if (this.highlights.length === HIGHLIGHTS_MAX_LENGTH) {
+      if (highlights.length === HIGHLIGHTS_MAX_LENGTH) {
         break;
       }
     }
 
-    SectionsManager.updateSection(SECTION_ID, {rows: this.highlights}, this.highlightsLastUpdated === 0 || broadcast);
+    SectionsManager.updateSection(SECTION_ID, {rows: highlights}, broadcast);
     this.highlightsLastUpdated = Date.now();
+    this.highlightsLength = highlights.length;
   }
 
   /**
    * Fetch an image for a given highlight and update the card with it. If no
-   * image is available then fallback to fetching a screenshot. Update the card
-   * in `this.highlights` so that the image is cached for the next refresh.
+   * image is available then fallback to fetching a screenshot.
    */
-  async fetchImage(url, imageUrl) {
-    const image = await Screenshots.getScreenshotForURL(imageUrl || url);
-    SectionsManager.updateSectionCard(SECTION_ID, url, {image}, true);
-    if (image) {
-      const highlight = this.highlights.find(site => site.url === url);
-      if (highlight) {
-        highlight.image = image;
-      }
-    }
+  async fetchImage(page) {
+    // Request a screenshot if we don't already have one pending
+    const {preview_image_url: imageUrl, url} = page;
+    Screenshots.maybeGetAndSetScreenshot(page, imageUrl || url, "image", image => {
+      SectionsManager.updateSectionCard(SECTION_ID, url, {image}, true);
+    });
   }
 
   onAction(action) {
     switch (action.type) {
       case at.INIT:
         this.init();
         break;
       case at.NEW_TAB_LOAD:
-        if (this.highlights.length < HIGHLIGHTS_MAX_LENGTH) {
+        if (this.highlightsLength < HIGHLIGHTS_MAX_LENGTH) {
           // If we haven't filled the highlights grid yet, fetch again.
           this.fetchHighlights(true);
         } else if (Date.now() - this.highlightsLastUpdated >= HIGHLIGHTS_UPDATE_TIME) {
           // If the last time we refreshed the data is greater than 15 minutes, fetch again.
           this.fetchHighlights(false);
         }
         break;
       case at.MIGRATION_COMPLETED:
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/lib/LinksCache.jsm
@@ -0,0 +1,114 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = ["LinksCache"];
+
+const EXPIRATION_TIME = 5 * 60 * 1000; // 5 minutes
+
+/**
+ * Cache link results from a provided object property and refresh after some
+ * amount of time has passed. Allows for migrating data from previously cached
+ * links to the new links with the same url.
+ */
+this.LinksCache = class LinksCache {
+
+  /**
+   * Create a links cache for a given object property.
+   *
+   * @param {object} linkObject Object containing the link property
+   * @param {string} linkProperty Name of property on object to access
+   * @param {function} migrator Optional callback receiving the old and new link
+   *                            to allow custom migrating data from old to new.
+   * @param {function} shouldRefresh Optional callback receiving the old and new
+   *                                 options to refresh even when not expired.
+   */
+  constructor(linkObject, linkProperty, migrator = () => {}, shouldRefresh = () => {}) {
+    this.clear();
+    // Allow getting links from both methods and array properties
+    this.linkGetter = options => {
+      const ret = linkObject[linkProperty];
+      return typeof ret === "function" ? ret.call(linkObject, options) : ret;
+    };
+    this.migrator = migrator;
+    this.shouldRefresh = shouldRefresh;
+  }
+
+  /**
+   * Clear the cached data.
+   */
+  clear() {
+    this.cache = Promise.resolve([]);
+    this.lastOptions = {};
+    this.expire();
+  }
+
+  /**
+   * Force the next request to update the cache.
+   */
+  expire() {
+    delete this.lastUpdate;
+  }
+
+  /**
+   * Request data and update the cache if necessary.
+   *
+   * @param {object} options Optional data to pass to the underlying method.
+   * @returns {promise(array)} Links array with objects that can be modified.
+   */
+  async request(options = {}) {
+    // Update the cache if the data has been expired
+    const now = Date.now();
+    if (this.lastUpdate === undefined ||
+        now > this.lastUpdate + EXPIRATION_TIME ||
+        // Allow custom rules around refreshing based on options
+        this.shouldRefresh(this.lastOptions, options)) {
+      // Update request state early so concurrent requests can refer to it
+      this.lastOptions = options;
+      this.lastUpdate = now;
+
+      // Save a promise before awaits, so other requests wait for correct data
+      this.cache = new Promise(async resolve => {
+        // Allow fast lookup of old links by url that might need to migrate
+        const toMigrate = new Map();
+        for (const oldLink of await this.cache) {
+          if (oldLink) {
+            toMigrate.set(oldLink.url, oldLink);
+          }
+        }
+
+        // Make a shallow copy of each resulting link to allow direct edits
+        const copied = (await this.linkGetter(options)).map(link => link &&
+          Object.assign({}, link));
+
+        // Migrate data to the new link if we have an old link
+        for (const newLink of copied) {
+          if (newLink) {
+            const oldLink = toMigrate.get(newLink.url);
+            if (oldLink) {
+              this.migrator(oldLink, newLink);
+            }
+
+            // Add a method that can be copied to cloned links that will update
+            // the original cached link's property with the current one
+            newLink.__updateCache = function(prop) {
+              const val = this[prop];
+              if (val === undefined) {
+                delete newLink[prop];
+              } else {
+                newLink[prop] = val;
+              }
+            };
+          }
+        }
+
+        // Update cache with the copied links migrated
+        resolve(copied);
+      });
+    }
+
+    // Return the promise of the links array
+    return this.cache;
+  }
+};
--- a/browser/extensions/activity-stream/lib/Screenshots.jsm
+++ b/browser/extensions/activity-stream/lib/Screenshots.jsm
@@ -56,10 +56,45 @@ this.Screenshots = {
       const bytes = await file.read();
       const encodedData = btoa(this._bytesToString(bytes));
       file.close();
       screenshot = `data:${contentType};base64,${encodedData}`;
     } catch (err) {
       Cu.reportError(`getScreenshot error: ${err}`);
     }
     return screenshot;
+  },
+
+  /**
+   * Conditionally get a screenshot for a link if there's no existing pending
+   * screenshot. Updates the link object's desired property with the result.
+   *
+   * @param link {object} Link object to update
+   * @param url {string} Url to get a screenshot of
+   * @param property {string} Name of property on object to set
+   @ @param onScreenshot {function} Callback for when the screenshot loads
+   */
+  async maybeGetAndSetScreenshot(link, url, property, onScreenshot) {
+    // Make a link copy so we can stash internal properties to cache
+    const updateCache = link.__updateCache ? link.__updateCache.bind(link) :
+      () => {};
+
+    // Request a screenshot if we don't already have one pending
+    if (!link.__fetchingScreenshot) {
+      link.__fetchingScreenshot = this.getScreenshotForURL(url);
+      updateCache("__fetchingScreenshot");
+
+      // Trigger this callback only when first requesting
+      link.__fetchingScreenshot.then(onScreenshot).catch();
+    }
+
+    // Clean up now that we got the screenshot
+    const screenshot = await link.__fetchingScreenshot;
+    delete link.__fetchingScreenshot;
+    updateCache("__fetchingScreenshot");
+
+    // Update the link so the screenshot is in the cache
+    if (screenshot) {
+      link[property] = screenshot;
+      updateCache(property);
+    }
   }
 };
--- a/browser/extensions/activity-stream/lib/SectionsManager.jsm
+++ b/browser/extensions/activity-stream/lib/SectionsManager.jsm
@@ -1,17 +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 {utils: Cu} = Components;
+Cu.import("resource://gre/modules/EventEmitter.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 const {actionCreators: ac, actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
-Cu.import("resource://gre/modules/EventEmitter.jsm");
+const {Dedupe} = Cu.import("resource://activity-stream/common/Dedupe.jsm", {});
 
 /*
  * Generators for built in sections, keyed by the pref name for their feed.
  * Built in sections may depend on options stored as serialised JSON in the pref
  * `${feed_pref_name}.options`.
  */
 const BUILT_IN_SECTIONS = {
   "feeds.section.topstories": options => ({
@@ -31,17 +32,18 @@ const BUILT_IN_SECTIONS = {
       body: {id: options.provider_description || "pocket_feedback_body"},
       link: {href: options.info_link, id: "section_info_privacy_notice"}
     },
     emptyState: {
       message: {id: "topstories_empty_state", values: {provider: options.provider_name}},
       icon: "check"
     },
     shouldSendImpressionStats: true,
-    order: 0
+    order: 0,
+    dedupeFrom: ["highlights"]
   }),
   "feeds.section.highlights": options => ({
     id: "highlights",
     pref: {
       titleString: {id: "settings_pane_highlights_header"},
       descString: {id: "settings_pane_highlights_body2"}
     },
     shouldHidePref:  false,
@@ -68,16 +70,18 @@ const SectionsManager = {
     for (const feedPrefName of Object.keys(BUILT_IN_SECTIONS)) {
       const optionsPrefName = `${feedPrefName}.options`;
       this.addBuiltInSection(feedPrefName, prefs[optionsPrefName]);
     }
 
     Object.keys(this.CONTEXT_MENU_PREFS).forEach(k =>
       Services.prefs.addObserver(this.CONTEXT_MENU_PREFS[k], this));
 
+    this.dedupe = new Dedupe(site => site && site.url);
+
     this.initialized = true;
     this.emit(this.INIT);
   },
   observe(subject, topic, data) {
     switch (topic) {
       case "nsPref:changed":
         for (const pref of Object.keys(this.CONTEXT_MENU_PREFS)) {
           if (data === this.CONTEXT_MENU_PREFS[pref]) {
@@ -118,20 +122,42 @@ const SectionsManager = {
   },
   updateSections() {
     this.sections.forEach((section, id) => this.updateSection(id, section, true));
   },
   updateSection(id, options, shouldBroadcast) {
     this.updateSectionContextMenuOptions(options);
 
     if (this.sections.has(id)) {
-      this.sections.set(id, Object.assign(this.sections.get(id), options));
-      this.emit(this.UPDATE_SECTION, id, options, shouldBroadcast);
+      const dedupedOptions = this.dedupeRows(id, options);
+      this.sections.set(id, Object.assign(this.sections.get(id), dedupedOptions));
+      this.emit(this.UPDATE_SECTION, id, dedupedOptions, shouldBroadcast);
+
+      // Update any sections that dedupe from the updated section
+      this.sections.forEach(section => {
+        if (section.dedupeFrom && section.dedupeFrom.includes(id)) {
+          this.updateSection(section.id, section, shouldBroadcast);
+        }
+      });
     }
   },
+  dedupeRows(id, options) {
+    const newOptions = Object.assign({}, options);
+    const dedupeFrom = this.sections.get(id).dedupeFrom;
+    if (dedupeFrom && dedupeFrom.length > 0 && options.rows) {
+      for (const sectionId of dedupeFrom) {
+        const section = this.sections.get(sectionId);
+        if (section && section.rows) {
+          const [, newRows] = this.dedupe.group(section.rows, options.rows);
+          newOptions.rows = newRows;
+        }
+      }
+    }
+    return newOptions;
+  },
 
   /**
    * Sets the section's context menu options. These are all available context menu
    * options minus the ones that are tied to a pref (see CONTEXT_MENU_PREFS) set
    * to false.
    *
    * @param options section options
    */
--- a/browser/extensions/activity-stream/lib/Store.jsm
+++ b/browser/extensions/activity-stream/lib/Store.jsm
@@ -100,30 +100,38 @@ this.Store = class Store {
         this.uninitFeed(name, this._uninitAction);
       }
     }
   }
 
   /**
    * init - Initializes the ActivityStreamMessageChannel channel, and adds feeds.
    *
+   * Note that it intentionally initializes the TelemetryFeed first so that the
+   * addon is able to report the init errors from other feeds.
+   *
    * @param  {Map} feedFactories A Map of feeds with the name of the pref for
    *                                the feed as the key and a function that
    *                                constructs an instance of the feed.
    * @param {Action} initAction An optional action that will be dispatched
    *                            to feeds when they're created.
    * @param {Action} uninitAction An optional action for when feeds uninit.
    */
   init(feedFactories, initAction, uninitAction) {
     this._feedFactories = feedFactories;
     this._initAction = initAction;
     this._uninitAction = uninitAction;
 
+    const telemetryKey = "feeds.telemetry";
+    if (feedFactories.has(telemetryKey) && this._prefs.get(telemetryKey)) {
+      this.initFeed(telemetryKey);
+    }
+
     for (const pref of feedFactories.keys()) {
-      if (this._prefs.get(pref)) {
+      if (pref !== telemetryKey && this._prefs.get(pref)) {
         this.initFeed(pref);
       }
     }
 
     this._prefs.observeBranch(this);
     this._messageChannel.createChannel();
 
     // Dispatch an initial action after all enabled feeds are ready
--- a/browser/extensions/activity-stream/lib/TelemetryFeed.jsm
+++ b/browser/extensions/activity-stream/lib/TelemetryFeed.jsm
@@ -99,16 +99,17 @@ this.TelemetryFeed = class TelemetryFeed
     this._impressionStatsLastReset = Date.now();
     this._impressionStats = {
       clicked: new PersistentGuidSet(this._prefs, PREF_IMPRESSION_STATS_CLICKED),
       blocked: new PersistentGuidSet(this._prefs, PREF_IMPRESSION_STATS_BLOCKED),
       pocketed: new PersistentGuidSet(this._prefs, PREF_IMPRESSION_STATS_POCKETED)
     };
 
     this.telemetryEnabled = this._prefs.get(TELEMETRY_PREF);
+    this._aboutHomeSeen = false;
     this._onTelemetryPrefChange = this._onTelemetryPrefChange.bind(this);
     this._prefs.observe(TELEMETRY_PREF, this._onTelemetryPrefChange);
   }
 
   init() {
     Services.obs.addObserver(this.browserOpenNewtabStart, "browser-open-newtab-start");
   }
 
@@ -188,24 +189,69 @@ this.TelemetryFeed = class TelemetryFeed
   /**
    * addSession - Start tracking a new session
    *
    * @param  {string} id the portID of the open session
    * @param  {string} the URL being loaded for this session (optional)
    * @return {obj}    Session object
    */
   addSession(id, url) {
+    // XXX refactor to use setLoadTriggerInfo or saveSessionPerfData
+
+    // "unexpected" will be overwritten when appropriate
+    let load_trigger_type = "unexpected";
+    let load_trigger_ts;
+
+    if (!this._aboutHomeSeen && url === "about:home") {
+      this._aboutHomeSeen = true;
+
+      // XXX note that this will be incorrectly set in the following cases:
+      // session_restore following by clicking on the toolbar button,
+      // or someone who has changed their default home page preference to
+      // something else and later clicks the toolbar.  It will also be
+      // incorrectly unset if someone changes their "Home Page" preference to
+      // about:newtab.
+      //
+      // That said, the ratio of these mistakes to correct cases should
+      // be very small, and these issues should follow away as we implement
+      // the remaining load_trigger_type values for about:home in issue 3556.
+      //
+      // XXX file a bug to implement remaining about:home cases so this
+      // problem will go away and link to it here.
+      load_trigger_type = "first_window_opened";
+
+      // The real perceived trigger of first_window_opened is the OS-level
+      // clicking of the icon.  We use perfService.timeOrigin because it's the
+      // earliest number on this time scale that's easy to get.; We could
+      // actually use 0, but maybe that could be before the browser started?
+      // [bug 1401406](https://bugzilla.mozilla.org/show_bug.cgi?id=1401406)
+      // getting sorted out may help clarify. Even better, presumably, would be
+      // to use the process creation time for the main process, which is
+      // available, but somewhat harder to get. However, these are all more or
+      // less proxies for the same thing, so it's not clear how much the better
+      // numbers really matter, since we (activity stream) only control a
+      // relatively small amount of the code that's executing between the
+      // OS-click and when the first <browser> element starts loading.  That
+      // said, it's conceivable that it could help us catch regressions in the
+      // number of cycles early chrome code takes to execute, but it's likely
+      // that there are more direct ways to measure that.
+      load_trigger_ts = perfService.timeOrigin;
+    }
+
     const session = {
       session_id: String(gUUIDGenerator.generateUUID()),
       // "unknown" will be overwritten when appropriate
       page: url ? url : "unknown",
-      // "unexpected" will be overwritten when appropriate
-      perf: {load_trigger_type: "unexpected"}
+      perf: {load_trigger_type}
     };
 
+    if (load_trigger_ts) {
+      session.perf.load_trigger_ts = load_trigger_ts;
+    }
+
     this.sessions.set(id, session);
     return session;
   }
 
   /**
    * endSession - Stop tracking a session
    *
    * @param  {string} portID the portID of the session that just closed
@@ -339,16 +385,24 @@ this.TelemetryFeed = class TelemetryFeed
 
     // If it is an impression ping, just send it out. For the click, block, and
     // save to pocket pings, it only sends the first ping for the same article.
     if (!guidSet || guidSet.save(payload.tiles[index].id)) {
       this.sendEvent(this.createImpressionStats(action));
     }
   }
 
+  handleUserEvent(action) {
+    this.sendEvent(this.createUserEvent(action));
+  }
+
+  handleUndesiredEvent(action) {
+    this.sendEvent(this.createUndesiredEvent(action));
+  }
+
   resetImpressionStats() {
     for (const key of Object.keys(this._impressionStats)) {
       this._impressionStats[key].clear();
     }
     this._impressionStatsLastReset = Date.now();
   }
 
   onAction(action) {
@@ -369,20 +423,20 @@ this.TelemetryFeed = class TelemetryFeed
         if (Date.now() - this._impressionStatsLastReset >= IMPRESSION_STATS_RESET_TIME) {
           this.resetImpressionStats();
         }
         break;
       case at.TELEMETRY_IMPRESSION_STATS:
         this.handleImpressionStats(action);
         break;
       case at.TELEMETRY_UNDESIRED_EVENT:
-        this.sendEvent(this.createUndesiredEvent(action));
+        this.handleUndesiredEvent(action);
         break;
       case at.TELEMETRY_USER_EVENT:
-        this.sendEvent(this.createUserEvent(action));
+        this.handleUserEvent(action);
         break;
       case at.TELEMETRY_PERFORMANCE_EVENT:
         this.sendEvent(this.createPerformanceEvent(action));
         break;
       case at.UNINIT:
         this.uninit();
         break;
     }
@@ -401,22 +455,28 @@ this.TelemetryFeed = class TelemetryFeed
    * @param {String} port  The session with which this is associated
    * @param {Object} data  The perf data to be
    */
   saveSessionPerfData(port, data) {
     // XXX should use try/catch and send a bad state indicator if this
     // get blows up.
     let session = this.sessions.get(port);
 
-    // Partial workaround for #3118; avoids the worst incorrect associations of
-    // times with browsers, by associating the load trigger with the visibility
-    // event as the user is most likely associating the trigger to the tab just
-    // shown. This helps avoid associateing with a preloaded browser as those
-    // don't get the event until shown. Better fix for more cases forthcoming.
-    if (data.visibility_event_rcvd_ts) {
+    // XXX Partial workaround for #3118; avoids the worst incorrect associations
+    // of times with browsers, by associating the load trigger with the
+    // visibility event as the user is most likely associating the trigger to
+    // the tab just shown. This helps avoid associating with a preloaded
+    // browser as those don't get the event until shown. Better fix for more
+    // cases forthcoming.
+    //
+    // XXX the about:home check (and the corresponding test) should go away
+    // once the load_trigger stuff in addSession is refactored into
+    // setLoadTriggerInfo.
+    //
+    if (data.visibility_event_rcvd_ts && session.page !== "about:home") {
       this.setLoadTriggerInfo(port);
     }
 
     Object.assign(session.perf, data);
   }
 
   uninit() {
     Services.obs.removeObserver(this.browserOpenNewtabStart,
--- a/browser/extensions/activity-stream/lib/TopSitesFeed.jsm
+++ b/browser/extensions/activity-stream/lib/TopSitesFeed.jsm
@@ -9,32 +9,40 @@ Cu.import("resource://gre/modules/XPCOMU
 const {actionCreators: ac, actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
 const {TippyTopProvider} = Cu.import("resource://activity-stream/lib/TippyTopProvider.jsm", {});
 const {insertPinned, TOP_SITES_SHOWMORE_LENGTH} = Cu.import("resource://activity-stream/common/Reducers.jsm", {});
 const {Dedupe} = Cu.import("resource://activity-stream/common/Dedupe.jsm", {});
 const {shortURL} = Cu.import("resource://activity-stream/lib/ShortURL.jsm", {});
 
 XPCOMUtils.defineLazyModuleGetter(this, "filterAdult",
   "resource://activity-stream/lib/FilterAdult.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LinksCache",
+  "resource://activity-stream/lib/LinksCache.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
   "resource://gre/modules/NewTabUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Screenshots",
   "resource://activity-stream/lib/Screenshots.jsm");
 
 const UPDATE_TIME = 15 * 60 * 1000; // 15 minutes
 const DEFAULT_SITES_PREF = "default.sites";
 const DEFAULT_TOP_SITES = [];
-const FRECENCY_THRESHOLD = 100; // 1 visit (skip first-run/one-time pages)
+const FRECENCY_THRESHOLD = 100 + 1; // 1 visit (skip first-run/one-time pages)
 const MIN_FAVICON_SIZE = 96;
 
 this.TopSitesFeed = class TopSitesFeed {
   constructor() {
     this.lastUpdated = 0;
     this._tippyTopProvider = new TippyTopProvider();
     this.dedupe = new Dedupe(this._dedupeKey);
+    this.frecentCache = new LinksCache(NewTabUtils.activityStreamLinks,
+      "getTopSites", this.getLinkMigrator(), (oldOptions, newOptions) =>
+        // Refresh if no old options or requesting more items
+        !(oldOptions.numItems >= newOptions.numItems));
+    this.pinnedCache = new LinksCache(NewTabUtils.pinnedLinks,
+      "links", this.getLinkMigrator(["favicon", "faviconSize"]));
   }
   _dedupeKey(site) {
     return site && site.hostname;
   }
   refreshDefaults(sites) {
     // Clear out the array of any previous defaults
     DEFAULT_TOP_SITES.length = 0;
 
@@ -45,125 +53,150 @@ this.TopSitesFeed = class TopSitesFeed {
           isDefault: true,
           url
         };
         site.hostname = shortURL(site);
         DEFAULT_TOP_SITES.push(site);
       }
     }
   }
-  async getScreenshot(url) {
-    let screenshot = await Screenshots.getScreenshotForURL(url);
-    const action = {type: at.SCREENSHOT_UPDATED, data: {url, screenshot}};
-    this.store.dispatch(ac.BroadcastToContent(action));
+
+  /**
+   * Make a cached link data migrator by copying over screenshots and others.
+   *
+   * @param others {array} Optional extra properties to copy
+   */
+  getLinkMigrator(others = []) {
+    const properties = ["__fetchingScreenshot", "screenshot", ...others];
+    return (oldLink, newLink) => {
+      for (const property of properties) {
+        const oldValue = oldLink[property];
+        if (oldValue) {
+          newLink[property] = oldValue;
+        }
+      }
+    };
   }
   async getLinksWithDefaults(action) {
     // Get at least SHOWMORE amount so toggling between 1 and 2 rows has sites
     const numItems = Math.max(this.store.getState().Prefs.values.topSitesCount,
       TOP_SITES_SHOWMORE_LENGTH);
-    let frecent = await NewTabUtils.activityStreamLinks.getTopSites({numItems});
-    const notBlockedDefaultSites = DEFAULT_TOP_SITES.filter(site => !NewTabUtils.blockedLinks.isBlocked({url: site.url}));
-    const defaultUrls = notBlockedDefaultSites.map(site => site.url);
-    let pinned = this._getPinnedWithData(frecent);
-    pinned = pinned.map(site => site && Object.assign({}, site, {
-      isDefault: defaultUrls.indexOf(site.url) !== -1,
-      hostname: shortURL(site)
-    }));
+    const frecent = (await this.frecentCache.request({
+      numItems,
+      topsiteFrecency: FRECENCY_THRESHOLD
+    })).map(link => Object.assign({}, link, {hostname: shortURL(link)}));
+
+    // Remove any defaults that have been blocked
+    const notBlockedDefaultSites = DEFAULT_TOP_SITES.filter(link =>
+      !NewTabUtils.blockedLinks.isBlocked({url: link.url}));
+
+    // Get pinned links augmented with desired properties
+    const plainPinned = await this.pinnedCache.request();
+    const pinned = await Promise.all(plainPinned.map(async link => {
+      if (!link) {
+        return link;
+      }
 
-    if (!frecent) {
-      frecent = [];
-    } else {
-      // Get the best history links that pass the frecency threshold
-      frecent = frecent.filter(link => link && link.type !== "affiliate" &&
-        link.frecency > FRECENCY_THRESHOLD).map(site => {
-          site.hostname = shortURL(site);
-          return site;
-        });
-    }
+      // Copy all properties from a frecent link and add more
+      const finder = other => other.url === link.url;
+      const copy = Object.assign({}, frecent.find(finder) || {}, link, {
+        hostname: shortURL(link),
+        isDefault: !!notBlockedDefaultSites.find(finder)
+      });
+
+      // Add in favicons if we don't already have it
+      if (!copy.favicon) {
+        try {
+          NewTabUtils.activityStreamProvider._faviconBytesToDataURI(await
+            NewTabUtils.activityStreamProvider._addFavicons([copy]));
+          copy.__updateCache("favicon");
+          copy.__updateCache("faviconSize");
+        } catch (e) {
+          // Some issue with favicon, so just continue without one
+        }
+      }
+
+      return copy;
+    }));
 
     // Remove any duplicates from frecent and default sites
     const [, dedupedFrecent, dedupedDefaults] = this.dedupe.group(
       pinned, frecent, notBlockedDefaultSites);
     const dedupedUnpinned = [...dedupedFrecent, ...dedupedDefaults];
 
     // Remove adult sites if we need to
     const checkedAdult = this.store.getState().Prefs.values.filterAdult ?
       filterAdult(dedupedUnpinned) : dedupedUnpinned;
 
     // Insert the original pinned sites into the deduped frecent and defaults
-    return insertPinned(checkedAdult, pinned).slice(0, numItems);
+    const withPinned = insertPinned(checkedAdult, pinned).slice(0, numItems);
+
+    // Now, get a tippy top icon, a rich icon, or screenshot for every item
+    for (const link of withPinned) {
+      if (link) {
+        this._fetchIcon(link);
+
+        // Remove any internal properties
+        delete link.__fetchingScreenshot;
+        delete link.__updateCache;
+      }
+    }
+
+    return withPinned;
   }
+
+  /**
+   * Refresh the top sites data for content
+   *
+   * @param target Optional port/channel to receive the update. If not provided,
+   *               the update will be broadcasted.
+   */
   async refresh(target = null) {
     if (!this._tippyTopProvider.initialized) {
       await this._tippyTopProvider.init();
     }
 
     const links = await this.getLinksWithDefaults();
-
-    // First, cache existing screenshots in case we need to reuse them
-    const currentScreenshots = {};
-    for (const link of this.store.getState().TopSites.rows) {
-      if (link && link.screenshot) {
-        currentScreenshots[link.url] = link.screenshot;
-      }
-    }
-
-    // Now, get a tippy top icon, a rich icon, or screenshot for every item
-    for (let link of links) {
-      if (!link) { continue; }
-      this._fetchIcon(link, currentScreenshots);
-    }
     const newAction = {type: at.TOP_SITES_UPDATED, data: links};
-
     if (target) {
       // Send an update to content so the preloaded tab can get the updated content
       this.store.dispatch(ac.SendToContent(newAction, target));
     } else {
       // Broadcast an update to all open content pages
       this.store.dispatch(ac.BroadcastToContent(newAction));
     }
     this.lastUpdated = Date.now();
   }
-  _fetchIcon(link, screenshotCache = {}) {
+
+  /**
+   * Get an image for the link preferring tippy top, rich favicon, screenshots.
+   */
+  async _fetchIcon(link) {
     // Check for tippy top icon or a rich icon.
     this._tippyTopProvider.processSite(link);
-    if (!link.tippyTopIcon && (!link.favicon || link.faviconSize < MIN_FAVICON_SIZE)) {
-      // If no tippy top, then we get a screenshot.
-      if (screenshotCache[link.url]) {
-        link.screenshot = screenshotCache[link.url];
-      } else {
-        this.getScreenshot(link.url);
-      }
+    if (!link.tippyTopIcon &&
+        (!link.favicon || link.faviconSize < MIN_FAVICON_SIZE) &&
+        !link.screenshot) {
+      const {url} = link;
+      Screenshots.maybeGetAndSetScreenshot(link, url, "screenshot", screenshot => {
+        this.store.dispatch(ac.BroadcastToContent({
+          data: {screenshot, url},
+          type: at.SCREENSHOT_UPDATED
+        }));
+      });
     }
   }
-  _getPinnedWithData(links) {
-    // Augment the pinned links with any other extra data we have for them already in the store.
-    // Alternatively you can pass in some links that you know have data you want the pinned links
-    // to also have. This is useful for start up to make sure pinned links have favicons
-    // (See github ticket #3428 fore more details)
-    const originalLinks = links || this.store.getState().TopSites.rows;
-    const pinned = NewTabUtils.pinnedLinks.links;
-    return pinned.map(pinnedLink => {
-      if (pinnedLink) {
-        const hostname = shortURL(pinnedLink);
-        const originalLink = originalLinks.find(link => link && link.url === pinnedLink.url);
-        // If it's a new link then it won't have an icon, so fetch one
-        if (!originalLink) {
-          this._fetchIcon(pinnedLink);
-        }
-        return Object.assign(originalLink || {hostname}, pinnedLink);
-      }
-      return pinnedLink;
-    });
-  }
 
   /**
    * Inform others that top sites data has been updated due to pinned changes.
    */
   _broadcastPinnedSitesUpdated() {
+    // Pinned data changed, so make sure we get latest
+    this.pinnedCache.expire();
+
     // Refresh to update pinned sites with screenshots, trigger deduping, etc.
     this.refresh();
   }
 
   /**
    * Pin a site at a specific position saving only the desired keys.
    */
   _pinSiteAt({label, url}, index) {
@@ -228,16 +261,17 @@ this.TopSitesFeed = class TopSitesFeed {
           this.refresh(action.meta.fromTarget);
         }
         break;
       // All these actions mean we need new top sites
       case at.MIGRATION_COMPLETED:
       case at.PLACES_HISTORY_CLEARED:
       case at.PLACES_LINK_DELETED:
       case at.PLACES_LINK_BLOCKED:
+        this.frecentCache.expire();
         this.refresh();
         break;
       case at.PREF_CHANGED:
         if (action.data.name === DEFAULT_SITES_PREF) {
           this.refreshDefaults(action.data.value);
         }
         break;
       case at.PREFS_INITIAL_VALUES:
--- a/browser/extensions/activity-stream/test/schemas/pings.js
+++ b/browser/extensions/activity-stream/test/schemas/pings.js
@@ -96,17 +96,18 @@ const SessionPing = Joi.object().keys(Ob
     // observer event doesn't fire
     load_trigger_ts: Joi.number().positive()
       .notes(["server counter", "server counter alert"]),
 
     // What was the perceived trigger of the load action?
     //
     // Not required at least for the error cases where the observer event
     // doesn't fire
-    load_trigger_type: Joi.valid(["menu_plus_or_keyboard", "unexpected"])
+    load_trigger_type: Joi.valid(["first_window_opened",
+      "menu_plus_or_keyboard", "unexpected"])
       .notes(["server counter", "server counter alert"]).required(),
 
     // When did the topsites element finish painting?  Note that, at least for
     // the first tab to be loaded, and maybe some others, this will be before
     // topsites has yet to receive screenshots updates from the add-on code,
     // and is therefore just showing placeholder screenshots.
     topsites_first_painted_ts: Joi.number().positive()
       .notes(["server counter", "server counter alert"]),
--- a/browser/extensions/activity-stream/test/unit/common/Reducers.test.js
+++ b/browser/extensions/activity-stream/test/unit/common/Reducers.test.js
@@ -51,16 +51,29 @@ describe("Reducers", () => {
       const newRows = [{url: "foo.com"}, {url: "bar.com"}];
       const nextState = TopSites(undefined, {type: at.TOP_SITES_UPDATED, data: newRows});
       assert.equal(nextState.rows, newRows);
     });
     it("should not update state for empty action.data on TOP_SITES_UPDATED", () => {
       const nextState = TopSites(undefined, {type: at.TOP_SITES_UPDATED});
       assert.equal(nextState, INITIAL_STATE.TopSites);
     });
+    it("should set editForm.visible to true on TOP_SITES_EDIT", () => {
+      const nextState = TopSites(undefined, {type: at.TOP_SITES_EDIT});
+      assert.isTrue(nextState.editForm.visible);
+    });
+    it("should set editForm.site to action.data on TOP_SITES_EDIT", () => {
+      const data = {url: "foo", label: "label"};
+      const nextState = TopSites(undefined, {type: at.TOP_SITES_EDIT, data});
+      assert.equal(nextState.editForm.site, data);
+    });
+    it("should set editForm.visible to false on TOP_SITES_CANCEL_EDIT", () => {
+      const nextState = TopSites(undefined, {type: at.TOP_SITES_CANCEL_EDIT});
+      assert.isFalse(nextState.editForm.visible);
+    });
     it("should add screenshots for SCREENSHOT_UPDATED", () => {
       const oldState = {rows: [{url: "foo.com"}, {url: "bar.com"}]};
       const action = {type: at.SCREENSHOT_UPDATED, data: {url: "bar.com", screenshot: "data:123"}};
       const nextState = TopSites(oldState, action);
       assert.deepEqual(nextState.rows, [{url: "foo.com"}, {url: "bar.com", screenshot: "data:123"}]);
     });
     it("should not modify rows if nothing matches the url for SCREENSHOT_UPDATED", () => {
       const oldState = {rows: [{url: "foo.com"}, {url: "bar.com"}]};
@@ -470,16 +483,23 @@ describe("Reducers", () => {
       assert.notProperty(result[2], "isPinned");
       assert.notProperty(result[2], "pinIndex");
     });
     it("should handle a link present in both the links and pinned list", () => {
       const pinned = [links[7]];
       const result = insertPinned(links, pinned);
       assert.equal(links.length, result.length);
     });
+    it("should not modify the original data", () => {
+      const pinned = [{url: "http://example.com"}];
+
+      insertPinned(links, pinned);
+
+      assert.equal(typeof pinned[0].isPinned, "undefined");
+    });
   });
   describe("Snippets", () => {
     it("should return INITIAL_STATE by default", () => {
       assert.equal(Snippets(undefined, {type: "some_action"}), INITIAL_STATE.Snippets);
     });
     it("should set initialized to true on a SNIPPETS_DATA action", () => {
       const state = Snippets(undefined, {type: at.SNIPPETS_DATA, data: {}});
       assert.isTrue(state.initialized);
--- a/browser/extensions/activity-stream/test/unit/lib/ActivityStream.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/ActivityStream.test.js
@@ -227,9 +227,22 @@ describe("ActivityStream", () => {
       sandbox.stub(global.Services.locale, "getRequestedLocale").returns("en-US");
 
       as._updateDynamicPrefs();
       clock.tick(1);
 
       assert.isTrue(PREFS_CONFIG.get("feeds.section.topstories").value);
     });
   });
+  describe("telemetry reporting on init failure", () => {
+    it("should send a ping on init error", () => {
+      as = new ActivityStream();
+      const telemetry = {handleUndesiredEvent: sandbox.spy()};
+      sandbox.stub(as.store, "init").throws();
+      sandbox.stub(as.store.feeds, "get").returns(telemetry);
+      try {
+        as.init();
+      } catch (e) {
+      }
+      assert.calledOnce(telemetry.handleUndesiredEvent);
+    });
+  });
 });
--- a/browser/extensions/activity-stream/test/unit/lib/HighlightsFeed.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/HighlightsFeed.test.js
@@ -1,10 +1,11 @@
 "use strict";
 const injector = require("inject!lib/HighlightsFeed.jsm");
+const {Screenshots} = require("lib/Screenshots.jsm");
 const {GlobalOverrider} = require("test/unit/utils");
 const {actionTypes: at} = require("common/Actions.jsm");
 const {Dedupe} = require("common/Dedupe.jsm");
 
 const FAKE_LINKS = new Array(9).fill(null).map((v, i) => ({url: `http://www.site${i}.com`}));
 const FAKE_IMAGE = "data123";
 
 describe("Highlights Feed", () => {
@@ -17,29 +18,37 @@ describe("Highlights Feed", () => {
   let links;
   let clock;
   let fakeScreenshot;
   let fakeNewTabUtils;
   let filterAdultStub;
   let sectionsManagerStub;
   let shortURLStub;
 
+  const fetchHighlights = async() => {
+    await feed.fetchHighlights();
+    return sectionsManagerStub.updateSection.firstCall.args[1].rows;
+  };
+
   beforeEach(() => {
     globals = new GlobalOverrider();
     sandbox = globals.sandbox;
     fakeNewTabUtils = {activityStreamLinks: {getHighlights: sandbox.spy(() => Promise.resolve(links))}};
     sectionsManagerStub = {
       onceInitialized: sinon.stub().callsFake(callback => callback()),
       enableSection: sinon.spy(),
       disableSection: sinon.spy(),
       updateSection: sinon.spy(),
       updateSectionCard: sinon.spy(),
       sections: new Map([["highlights", {}]])
     };
-    fakeScreenshot = {getScreenshotForURL: sandbox.spy(() => Promise.resolve(FAKE_IMAGE))};
+    fakeScreenshot = {
+      getScreenshotForURL: sandbox.spy(() => Promise.resolve(FAKE_IMAGE)),
+      maybeGetAndSetScreenshot: Screenshots.maybeGetAndSetScreenshot
+    };
     filterAdultStub = sinon.stub().returns([]);
     shortURLStub = sinon.stub().callsFake(site => site.url.match(/\/([^/]+)/)[1]);
     globals.set("NewTabUtils", fakeNewTabUtils);
     ({HighlightsFeed, HIGHLIGHTS_UPDATE_TIME, SECTION_ID} = injector({
       "lib/FilterAdult.jsm": {filterAdult: filterAdultStub},
       "lib/ShortURL.jsm": {shortURL: shortURLStub},
       "lib/SectionsManager.jsm": {SectionsManager: sectionsManagerStub},
       "lib/Screenshots.jsm": {Screenshots: fakeScreenshot},
@@ -82,136 +91,168 @@ describe("Highlights Feed", () => {
     it("should fetch highlights on postInit", () => {
       feed.fetchHighlights = sinon.spy();
       feed.postInit();
       assert.calledOnce(feed.fetchHighlights);
     });
   });
   describe("#fetchHighlights", () => {
     it("should wait for TopSites to be initialised", async () => {
-      feed.store.getState = () => ({TopSites: {initialized: false}});
+      feed.store.state.TopSites.initialized = false;
       // Initially TopSites is uninitialised and fetchHighlights should wait
-      feed.fetchHighlights();
+      const firstFetch = feed.fetchHighlights();
+
       assert.calledOnce(feed.store.subscribe);
       assert.notCalled(fakeNewTabUtils.activityStreamLinks.getHighlights);
 
       // Initialisation causes the subscribe callback to be called and
       // fetchHighlights should continue
-      feed.store.getState = () => ({TopSites: {initialized: true}});
+      feed.store.state.TopSites.initialized = true;
       const subscribeCallback = feed.store.subscribe.firstCall.args[0];
       await subscribeCallback();
+      await firstFetch;
       assert.calledOnce(fakeNewTabUtils.activityStreamLinks.getHighlights);
 
       // If TopSites is initialised in the first place it shouldn't wait
+      feed.linksCache.expire();
       feed.store.subscribe.reset();
       fakeNewTabUtils.activityStreamLinks.getHighlights.reset();
-      feed.fetchHighlights();
+      await feed.fetchHighlights();
       assert.notCalled(feed.store.subscribe);
       assert.calledOnce(fakeNewTabUtils.activityStreamLinks.getHighlights);
     });
     it("should add hostname and hasImage to each link", async () => {
       links = [{url: "https://mozilla.org"}];
-      await feed.fetchHighlights();
-      assert.equal(feed.highlights[0].hostname, "mozilla.org");
-      assert.equal(feed.highlights[0].hasImage, true);
+
+      const highlights = await fetchHighlights();
+
+      assert.equal(highlights[0].hostname, "mozilla.org");
+      assert.equal(highlights[0].hasImage, true);
     });
     it("should add an existing image if it exists to the link without calling fetchImage", async () => {
       links = [{url: "https://mozilla.org", image: FAKE_IMAGE}];
-      feed.highlights = links;
       sinon.spy(feed, "fetchImage");
-      await feed.fetchHighlights();
-      assert.equal(feed.highlights[0].image, FAKE_IMAGE);
+
+      const highlights = await fetchHighlights();
+
+      assert.equal(highlights[0].image, FAKE_IMAGE);
       assert.notCalled(feed.fetchImage);
     });
     it("should call fetchImage with the correct arguments for new links", async () => {
       links = [{url: "https://mozilla.org", preview_image_url: "https://mozilla.org/preview.jog"}];
-      feed.highlights = [];
       sinon.spy(feed, "fetchImage");
+
       await feed.fetchHighlights();
+
       assert.calledOnce(feed.fetchImage);
-      assert.calledWith(feed.fetchImage, links[0].url, links[0].preview_image_url);
+      const arg = feed.fetchImage.firstCall.args[0];
+      assert.propertyVal(arg, "url", links[0].url);
+      assert.propertyVal(arg, "preview_image_url", links[0].preview_image_url);
     });
     it("should not include any links already in Top Sites", async () => {
       links = [
         {url: "https://mozilla.org"},
         {url: "http://www.topsite0.com"},
         {url: "http://www.topsite1.com"},
         {url: "http://www.topsite2.com"}
       ];
-      await feed.fetchHighlights();
-      assert.equal(feed.highlights.length, 1);
-      assert.deepEqual(feed.highlights[0], links[0]);
+
+      const highlights = await fetchHighlights();
+
+      assert.equal(highlights.length, 1);
+      assert.equal(highlights[0].url, links[0].url);
     });
     it("should not include history of same hostname as a bookmark", async () => {
       links = [
         {url: "https://site.com/bookmark", type: "bookmark"},
         {url: "https://site.com/history", type: "history"}
       ];
 
-      await feed.fetchHighlights();
+      const highlights = await fetchHighlights();
 
-      assert.equal(feed.highlights.length, 1);
-      assert.deepEqual(feed.highlights[0], links[0]);
+      assert.equal(highlights.length, 1);
+      assert.equal(highlights[0].url, links[0].url);
     });
     it("should take the first history of a hostname", async () => {
       links = [
         {url: "https://site.com/first", type: "history"},
         {url: "https://site.com/second", type: "history"},
         {url: "https://other", type: "history"}
       ];
 
-      await feed.fetchHighlights();
+      const highlights = await fetchHighlights();
 
-      assert.equal(feed.highlights.length, 2);
-      assert.deepEqual(feed.highlights[0], links[0]);
-      assert.deepEqual(feed.highlights[1], links[2]);
+      assert.equal(highlights.length, 2);
+      assert.equal(highlights[0].url, links[0].url);
+      assert.equal(highlights[1].url, links[2].url);
     });
     it("should set type to bookmark if there is a bookmarkGuid", async () => {
       links = [{url: "https://mozilla.org", type: "history", bookmarkGuid: "1234567890"}];
-      await feed.fetchHighlights();
-      assert.equal(feed.highlights[0].type, "bookmark");
+
+      const highlights = await fetchHighlights();
+
+      assert.equal(highlights[0].type, "bookmark");
     });
     it("should not filter out adult pages when pref is false", async() => {
       await feed.fetchHighlights();
 
       assert.notCalled(filterAdultStub);
     });
     it("should filter out adult pages when pref is true", async() => {
       feed.store.state.Prefs.values.filterAdult = true;
 
-      await feed.fetchHighlights();
+      const highlights = await fetchHighlights();
 
       // The stub filters out everything
       assert.calledOnce(filterAdultStub);
-      assert.equal(feed.highlights.length, 0);
+      assert.equal(highlights.length, 0);
+    });
+    it("should not expose internal link properties", async() => {
+      const highlights = await fetchHighlights();
+
+      const internal = Object.keys(highlights[0]).filter(key => key.startsWith("__"));
+      assert.equal(internal.join(""), "");
     });
   });
   describe("#fetchImage", () => {
     const FAKE_URL = "https://mozilla.org";
     const FAKE_IMAGE_URL = "https://mozilla.org/preview.jpg";
     it("should capture the image, if available", async () => {
-      await feed.fetchImage(FAKE_URL, FAKE_IMAGE_URL);
+      await feed.fetchImage({
+        preview_image_url: FAKE_IMAGE_URL,
+        url: FAKE_URL
+      });
+
       assert.calledOnce(fakeScreenshot.getScreenshotForURL);
       assert.calledWith(fakeScreenshot.getScreenshotForURL, FAKE_IMAGE_URL);
     });
     it("should fall back to capturing a screenshot", async () => {
-      await feed.fetchImage(FAKE_URL, undefined);
+      await feed.fetchImage({url: FAKE_URL});
+
       assert.calledOnce(fakeScreenshot.getScreenshotForURL);
       assert.calledWith(fakeScreenshot.getScreenshotForURL, FAKE_URL);
     });
     it("should call SectionsManager.updateSectionCard with the right arguments", async () => {
-      await feed.fetchImage(FAKE_URL, FAKE_IMAGE_URL);
+      await feed.fetchImage({
+        preview_image_url: FAKE_IMAGE_URL,
+        url: FAKE_URL
+      });
+
       assert.calledOnce(sectionsManagerStub.updateSectionCard);
       assert.calledWith(sectionsManagerStub.updateSectionCard, "highlights", FAKE_URL, {image: FAKE_IMAGE}, true);
     });
-    it("should update the card in feed.highlights with the image", async () => {
-      feed.highlights = [{url: FAKE_URL}];
-      await feed.fetchImage(FAKE_URL, FAKE_IMAGE_URL);
-      const highlight = feed.highlights.find(({url}) => url === FAKE_URL);
-      assert.propertyVal(highlight, "image", FAKE_IMAGE);
+    it("should update the card with the image", async () => {
+      const card = {
+        preview_image_url: FAKE_IMAGE_URL,
+        url: FAKE_URL
+      };
+
+      await feed.fetchImage(card);
+
+      assert.propertyVal(card, "image", FAKE_IMAGE);
     });
   });
   describe("#uninit", () => {
     it("should disable its section", () => {
       feed.onAction({type: at.UNINIT});
       assert.calledOnce(sectionsManagerStub.disableSection);
       assert.calledWith(sectionsManagerStub.disableSection, SECTION_ID);
     });
--- a/browser/extensions/activity-stream/test/unit/lib/SectionsManager.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/SectionsManager.test.js
@@ -141,25 +141,27 @@ describe("SectionsManager", () => {
       SectionsManager.removeSection(FAKE_ID);
       const spy = sinon.spy();
       SectionsManager.on(SectionsManager.UPDATE_SECTION, spy);
       SectionsManager.updateSection(FAKE_ID, {rows: FAKE_ROWS}, true);
       assert.notCalled(spy);
     });
     it("should update all sections", () => {
       SectionsManager.sections.clear();
+      const updateSectionOrig = SectionsManager.updateSection;
       SectionsManager.updateSection = sinon.spy();
 
       SectionsManager.addSection("ID1", {title: "FAKE_TITLE_1"});
       SectionsManager.addSection("ID2", {title: "FAKE_TITLE_2"});
       SectionsManager.updateSections();
 
       assert.calledTwice(SectionsManager.updateSection);
       assert.calledWith(SectionsManager.updateSection, "ID1", {title: "FAKE_TITLE_1"}, true);
       assert.calledWith(SectionsManager.updateSection, "ID2", {title: "FAKE_TITLE_2"}, true);
+      SectionsManager.updateSection = updateSectionOrig;
     });
     it("context menu pref change should update sections", () => {
       let observer;
       const services = {prefs: {getBoolPref: sinon.spy(), addObserver: (pref, o) => (observer = o), removeObserver: sinon.spy()}};
       globals.set("Services", services);
 
       SectionsManager.updateSections = sinon.spy();
       SectionsManager.CONTEXT_MENU_PREFS = {"MENU_ITEM": "MENU_ITEM_PREF"};
@@ -198,16 +200,37 @@ describe("SectionsManager", () => {
     it("should do nothing if the section doesn't exist", () => {
       SectionsManager.removeSection(FAKE_ID);
       const spy = sinon.spy();
       SectionsManager.on(SectionsManager.UPDATE_SECTION_CARD, spy);
       SectionsManager.updateSectionCard(FAKE_ID, FAKE_URL, FAKE_CARD_OPTIONS, true);
       assert.notCalled(spy);
     });
   });
+  describe("#dedupe", () => {
+    it("should dedupe stories from highlights", () => {
+      SectionsManager.init();
+      // Add some rows to highlights
+      SectionsManager.updateSection("highlights", {rows: [{url: "https://highlight.com/abc"}, {url: "https://shared.com/def"}]});
+      // Add some rows to top stories
+      SectionsManager.updateSection("topstories", {rows: [{url: "https://topstory.com/ghi"}, {url: "https://shared.com/def"}]});
+      // Verify deduping
+      assert.deepEqual(SectionsManager.sections.get("topstories").rows, [{url: "https://topstory.com/ghi"}]);
+    });
+    it("should dedupe stories from highlights when updating highlights", () => {
+      SectionsManager.init();
+      // Add some rows to top stories
+      SectionsManager.updateSection("topstories", {rows: [{url: "https://topstory.com/ghi"}, {url: "https://shared.com/def"}]});
+      assert.deepEqual(SectionsManager.sections.get("topstories").rows, [{url: "https://topstory.com/ghi"}, {url: "https://shared.com/def"}]);
+      // Add some rows to highlights
+      SectionsManager.updateSection("highlights", {rows: [{url: "https://highlight.com/abc"}, {url: "https://shared.com/def"}]});
+      // Verify deduping
+      assert.deepEqual(SectionsManager.sections.get("topstories").rows, [{url: "https://topstory.com/ghi"}]);
+    });
+  });
 });
 
 describe("SectionsFeed", () => {
   let feed;
 
   beforeEach(() => {
     SectionsManager.sections.clear();
     SectionsManager.initialized = false;
--- a/browser/extensions/activity-stream/test/unit/lib/Store.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/Store.test.js
@@ -158,16 +158,27 @@ describe("Store", () => {
       sinon.stub(store, "dispatch");
       const action = {type: "FOO"};
 
       store.init(new Map(), action);
 
       assert.calledOnce(store.dispatch);
       assert.calledWith(store.dispatch, action);
     });
+    it("should initialize the telemtry feed first", () => {
+      store._prefs.set("feeds.foo", true);
+      store._prefs.set("feeds.telemetry", true);
+      const telemetrySpy = sandbox.stub().returns({});
+      const fooSpy = sandbox.stub().returns({});
+      // Intentionally put the telemetry feed as the second item.
+      const feedFactories = new Map([["feeds.foo", fooSpy],
+                                     ["feeds.telemetry", telemetrySpy]]);
+      store.init(feedFactories);
+      assert.ok(telemetrySpy.calledBefore(fooSpy));
+    });
   });
   describe("#uninit", () => {
     it("should emit an uninit event if provided on init", () => {
       sinon.stub(store, "dispatch");
       const action = {type: "BAR"};
       store.init(new Map(), null, action);
 
       store.uninit();
--- a/browser/extensions/activity-stream/test/unit/lib/TelemetryFeed.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/TelemetryFeed.test.js
@@ -24,16 +24,17 @@ describe("TelemetryFeed", () => {
   };
   let instance;
   let clock;
   class PingCentre {sendPing() {} uninit() {}}
   class PerfService {
     getMostRecentAbsMarkStartByName() { return 1234; }
     mark() {}
     absNow() { return 123; }
+    get timeOrigin() { return 123456; }
   }
   const perfService = new PerfService();
   const {
     TelemetryFeed,
     USER_PREFS_ENCODING,
     IMPRESSION_STATS_RESET_TIME,
     TELEMETRY_PREF,
     PREF_IMPRESSION_STATS_CLICKED,
@@ -109,17 +110,48 @@ describe("TelemetryFeed", () => {
 
       assert.propertyVal(session, "page", "unknown");
     });
     it("should set the perf type when lacking timestamp", () => {
       const session = instance.addSession("foo");
 
       assert.propertyVal(session.perf, "load_trigger_type", "unexpected");
     });
+    it("should set load_trigger_type to first_window_opened on the first about:home seen", () => {
+      const session = instance.addSession("foo", "about:home");
+
+      assert.propertyVal(session.perf, "load_trigger_type",
+        "first_window_opened");
+    });
+    it("should not set load_trigger_type to first_window_opened on the second about:home seen", () => {
+      instance.addSession("foo", "about:home");
+
+      const session2 = instance.addSession("foo", "about:home");
+
+      assert.propertyNotVal(session2.perf, "load_trigger_type",
+        "first_window_opened");
+    });
+    it("should set load_trigger_ts to the value of perfService.timeOrigin", () => {
+      const session = instance.addSession("foo", "about:home");
+
+      assert.propertyVal(session.perf, "load_trigger_ts",
+        123456);
+    });
+    it("should a valid session ping on the first about:home seen", () => {
+      // Add a session
+      const portID = "foo";
+      const session = instance.addSession(portID, "about:home");
+
+      // Create a ping referencing the session
+      const ping = instance.createSessionEndEvent(session);
+      console.log("ping: ", JSON.stringify(ping));
+      assert.validate(ping, SessionPing);
+    });
   });
+
   describe("#browserOpenNewtabStart", () => {
     it("should call perfService.mark with browser-open-newtab-start", () => {
       sandbox.stub(perfService, "mark");
 
       instance.browserOpenNewtabStart();
 
       assert.calledOnce(perfService.mark);
       assert.calledWithExactly(perfService.mark, "browser-open-newtab-start");
@@ -413,16 +445,26 @@ describe("TelemetryFeed", () => {
     it("shouldn't call setLoadTriggerInfo if data has no visibility_event_rcvd_ts", () => {
       sandbox.stub(instance, "setLoadTriggerInfo");
       instance.addSession("port123");
 
       instance.saveSessionPerfData("port123", {monkeys_ts: 444455});
 
       assert.notCalled(instance.setLoadTriggerInfo);
     });
+
+    it("should not call setLoadTriggerInfo when url is about:home", () => {
+      sandbox.stub(instance, "setLoadTriggerInfo");
+      instance.addSession("port123", "about:home");
+      const data = {visibility_event_rcvd_ts: 444455};
+
+      instance.saveSessionPerfData("port123", data);
+
+      assert.notCalled(instance.setLoadTriggerInfo);
+    });
   });
 
   describe("#uninit", () => {
     it("should call .pingCentre.uninit", () => {
       const stub = sandbox.stub(instance.pingCentre, "uninit");
       instance.uninit();
       assert.calledOnce(stub);
     });
--- a/browser/extensions/activity-stream/test/unit/lib/TopSitesFeed.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/TopSitesFeed.test.js
@@ -1,16 +1,19 @@
 "use strict";
 const injector = require("inject!lib/TopSitesFeed.jsm");
+const {Screenshots} = require("lib/Screenshots.jsm");
 const {UPDATE_TIME} = require("lib/TopSitesFeed.jsm");
 const {FakePrefs, GlobalOverrider} = require("test/unit/utils");
 const action = {meta: {fromTarget: {}}};
 const {actionTypes: at} = require("common/Actions.jsm");
 const {insertPinned, TOP_SITES_SHOWMORE_LENGTH} = require("common/Reducers.jsm");
 
+const FAKE_FAVICON = "data987";
+const FAKE_FAVICON_SIZE = 128;
 const FAKE_FRECENCY = 200;
 const FAKE_LINKS = new Array(TOP_SITES_SHOWMORE_LENGTH).fill(null).map((v, i) => ({
   frecency: FAKE_FRECENCY,
   url: `http://www.site${i}.com`
 }));
 const FAKE_SCREENSHOT = "data123";
 
 function FakeTippyTopProvider() {}
@@ -36,24 +39,35 @@ describe("Top Sites Feed", () => {
     globals = new GlobalOverrider();
     sandbox = globals.sandbox;
     fakeNewTabUtils = {
       blockedLinks: {
         links: [],
         isBlocked: () => false
       },
       activityStreamLinks: {getTopSites: sandbox.spy(() => Promise.resolve(links))},
+      activityStreamProvider: {
+        _addFavicons: sandbox.spy(l => Promise.resolve(l.map(link => {
+          link.favicon = FAKE_FAVICON;
+          link.faviconSize = FAKE_FAVICON_SIZE;
+          return link;
+        }))),
+        _faviconBytesToDataURI: sandbox.spy()
+      },
       pinnedLinks: {
         links: [],
         isPinned: () => false,
         pin: sandbox.spy(),
         unpin: sandbox.spy()
       }
     };
-    fakeScreenshot = {getScreenshotForURL: sandbox.spy(() => Promise.resolve(FAKE_SCREENSHOT))};
+    fakeScreenshot = {
+      getScreenshotForURL: sandbox.spy(() => Promise.resolve(FAKE_SCREENSHOT)),
+      maybeGetAndSetScreenshot: Screenshots.maybeGetAndSetScreenshot
+    };
     filterAdultStub = sinon.stub().returns([]);
     shortURLStub = sinon.stub().callsFake(site => site.url);
     const fakeDedupe = function() {};
     globals.set("NewTabUtils", fakeNewTabUtils);
     FakePrefs.prototype.prefs["default.sites"] = "https://foo.com/";
     ({TopSitesFeed, DEFAULT_TOP_SITES} = injector({
       "lib/ActivityStreamPrefs.jsm": {Prefs: FakePrefs},
       "common/Dedupe.jsm": {Dedupe: fakeDedupe},
@@ -115,114 +129,153 @@ describe("Top Sites Feed", () => {
       assert.equal(DEFAULT_TOP_SITES.length, 0);
     });
   });
   describe("#getLinksWithDefaults", () => {
     beforeEach(() => {
       feed.refreshDefaults("https://foo.com");
     });
 
-    it("should get the links from NewTabUtils", async () => {
-      const result = await feed.getLinksWithDefaults();
-      const reference = links.map(site => Object.assign({}, site, {hostname: shortURLStub(site)}));
+    describe("general", () => {
+      beforeEach(() => {
+        sandbox.stub(fakeScreenshot, "maybeGetAndSetScreenshot");
+      });
+      it("should get the links from NewTabUtils", async () => {
+        const result = await feed.getLinksWithDefaults();
+        const reference = links.map(site => Object.assign({}, site, {hostname: shortURLStub(site)}));
 
-      assert.deepEqual(result, reference);
-      assert.calledOnce(global.NewTabUtils.activityStreamLinks.getTopSites);
-    });
-    it("should filter out low frecency links", async () => {
-      links = [
-        {frecency: FAKE_FRECENCY, url: "https://enough/visited"},
-        {frecency: 100, url: "https://visited/once"},
-        {frecency: 0, url: "https://unvisited/page"}
-      ];
+        assert.deepEqual(result, reference);
+        assert.calledOnce(global.NewTabUtils.activityStreamLinks.getTopSites);
+      });
+      it("should not filter out adult sites when pref is false", async() => {
+        await feed.getLinksWithDefaults();
+
+        assert.notCalled(filterAdultStub);
+      });
+      it("should filter out non-pinned adult sites when pref is true", async() => {
+        feed.store.state.Prefs.values.filterAdult = true;
+        fakeNewTabUtils.pinnedLinks.links = [{url: "https://foo.com/"}];
 
-      const result = await feed.getLinksWithDefaults();
+        const result = await feed.getLinksWithDefaults();
 
-      assert.equal(result[0].url, links[0].url);
-      assert.notEqual(result[1].url, links[1].url);
-      assert.notEqual(result[1].url, links[2].url);
-    });
-    it("should not filter out adult sites when pref is false", async() => {
-      await feed.getLinksWithDefaults();
-
-      assert.notCalled(filterAdultStub);
-    });
-    it("should filter out non-pinned adult sites when pref is true", async() => {
-      feed.store.state.Prefs.values.filterAdult = true;
-      fakeNewTabUtils.pinnedLinks.links = [{url: "https://foo.com/"}];
-
-      const result = await feed.getLinksWithDefaults();
+        // The stub filters out everything
+        assert.calledOnce(filterAdultStub);
+        assert.equal(result.length, 1);
+        assert.equal(result[0].url, fakeNewTabUtils.pinnedLinks.links[0].url);
+      });
+      it("should filter out the defaults that have been blocked", async () => {
+        // make sure we only have one top site, and we block the only default site we have to show
+        const url = "www.myonlytopsite.com";
+        const topsite = {
+          frecency: FAKE_FRECENCY,
+          hostname: shortURLStub({url}),
+          url
+        };
+        const blockedDefaultSite = {url: "https://foo.com"};
+        fakeNewTabUtils.activityStreamLinks.getTopSites = () => [topsite];
+        fakeNewTabUtils.blockedLinks.isBlocked = site => (site.url === blockedDefaultSite.url);
+        const result = await feed.getLinksWithDefaults();
 
-      // The stub filters out everything
-      assert.calledOnce(filterAdultStub);
-      assert.equal(result.length, 1);
-      assert.equal(result[0].url, fakeNewTabUtils.pinnedLinks.links[0].url);
-    });
-    it("should filter out the defaults that have been blocked", async () => {
-      // make sure we only have one top site, and we block the only default site we have to show
-      const url = "www.myonlytopsite.com";
-      const topsite = {
-        frecency: FAKE_FRECENCY,
-        hostname: shortURLStub({url}),
-        url
-      };
-      const blockedDefaultSite = {url: "https://foo.com"};
-      fakeNewTabUtils.activityStreamLinks.getTopSites = () => [topsite];
-      fakeNewTabUtils.blockedLinks.isBlocked = site => (site.url === blockedDefaultSite.url);
-      const result = await feed.getLinksWithDefaults();
+        // what we should be left with is just the top site we added, and not the default site we blocked
+        assert.lengthOf(result, 1);
+        assert.deepEqual(result[0], topsite);
+        assert.notInclude(result, blockedDefaultSite);
+      });
+      it("should call dedupe on the links", async () => {
+        const stub = sinon.stub(feed.dedupe, "group").callsFake((...id) => id);
+
+        await feed.getLinksWithDefaults();
 
-      // what we should be left with is just the top site we added, and not the default site we blocked
-      assert.lengthOf(result, 1);
-      assert.deepEqual(result[0], topsite);
-      assert.notInclude(result, blockedDefaultSite);
-    });
-    it("should call dedupe on the links", async () => {
-      const stub = sinon.stub(feed.dedupe, "group").callsFake((...id) => id);
+        assert.calledOnce(stub);
+      });
+      it("should dedupe the links by hostname", async () => {
+        const site = {url: "foo", hostname: "bar"};
+        const result = feed._dedupeKey(site);
 
-      await feed.getLinksWithDefaults();
+        assert.equal(result, site.hostname);
+      });
+      it("should add defaults if there are are not enough links", async () => {
+        links = [{frecency: FAKE_FRECENCY, url: "foo.com"}];
+
+        const result = await feed.getLinksWithDefaults();
+        const reference = [...links, ...DEFAULT_TOP_SITES].map(s => Object.assign({}, s, {hostname: shortURLStub(s)}));
 
-      assert.calledOnce(stub);
-    });
-    it("should dedupe the links by hostname", async () => {
-      const site = {url: "foo", hostname: "bar"};
-      const result = feed._dedupeKey(site);
-
-      assert.equal(result, site.hostname);
-    });
-    it("should add defaults if there are are not enough links", async () => {
-      links = [{frecency: FAKE_FRECENCY, url: "foo.com"}];
-
-      const result = await feed.getLinksWithDefaults();
-      const reference = [...links, ...DEFAULT_TOP_SITES].map(s => Object.assign({}, s, {hostname: shortURLStub(s)}));
+        assert.deepEqual(result, reference);
+      });
+      it("should only add defaults up to TOP_SITES_SHOWMORE_LENGTH", async () => {
+        links = [];
+        for (let i = 0; i < TOP_SITES_SHOWMORE_LENGTH - 1; i++) {
+          links.push({frecency: FAKE_FRECENCY, url: `foo${i}.com`});
+        }
+        const result = await feed.getLinksWithDefaults();
+        const reference = [...links, DEFAULT_TOP_SITES[0]].map(s => Object.assign({}, s, {hostname: shortURLStub(s)}));
 
-      assert.deepEqual(result, reference);
-    });
-    it("should only add defaults up to TOP_SITES_SHOWMORE_LENGTH", async () => {
-      links = [];
-      for (let i = 0; i < TOP_SITES_SHOWMORE_LENGTH - 1; i++) {
-        links.push({frecency: FAKE_FRECENCY, url: `foo${i}.com`});
-      }
-      const result = await feed.getLinksWithDefaults();
-      const reference = [...links, DEFAULT_TOP_SITES[0]].map(s => Object.assign({}, s, {hostname: shortURLStub(s)}));
+        assert.lengthOf(result, TOP_SITES_SHOWMORE_LENGTH);
+        assert.deepEqual(result, reference);
+      });
+      it("should not throw if NewTabUtils returns null", () => {
+        links = null;
+        assert.doesNotThrow(() => {
+          feed.getLinksWithDefaults(action);
+        });
+      });
+      it("should get more if the user has asked for more", async () => {
+        feed.store.state.Prefs.values.topSitesCount = TOP_SITES_SHOWMORE_LENGTH + 1;
 
-      assert.lengthOf(result, TOP_SITES_SHOWMORE_LENGTH);
-      assert.deepEqual(result, reference);
-    });
-    it("should not throw if NewTabUtils returns null", () => {
-      links = null;
-      assert.doesNotThrow(() => {
-        feed.getLinksWithDefaults(action);
+        const result = await feed.getLinksWithDefaults();
+
+        assert.propertyVal(result, "length", feed.store.state.Prefs.values.topSitesCount);
       });
     });
-    it("should get more if the user has asked for more", async () => {
-      feed.store.state.Prefs.values.topSitesCount = TOP_SITES_SHOWMORE_LENGTH + 1;
+    describe("caching", () => {
+      it("should reuse the cache on subsequent calls", async () => {
+        await feed.getLinksWithDefaults();
+        await feed.getLinksWithDefaults();
+
+        assert.calledOnce(global.NewTabUtils.activityStreamLinks.getTopSites);
+      });
+      it("should ignore the cache when requesting more", async () => {
+        await feed.getLinksWithDefaults();
+        feed.store.state.Prefs.values.topSitesCount *= 3;
+
+        await feed.getLinksWithDefaults();
+
+        assert.calledTwice(global.NewTabUtils.activityStreamLinks.getTopSites);
+      });
+      it("should migrate frecent screenshot data without getting screenshots again", async () => {
+        // Don't add favicons so we fall back to screenshots
+        fakeNewTabUtils.activityStreamProvider._addFavicons = sandbox.stub();
+        await feed.getLinksWithDefaults();
+        const {callCount} = fakeScreenshot.getScreenshotForURL;
+        feed.frecentCache.expire();
 
-      const result = await feed.getLinksWithDefaults();
+        const result = await feed.getLinksWithDefaults();
+
+        assert.calledTwice(global.NewTabUtils.activityStreamLinks.getTopSites);
+        assert.callCount(fakeScreenshot.getScreenshotForURL, callCount);
+        assert.propertyVal(result[0], "screenshot", FAKE_SCREENSHOT);
+      });
+      it("should migrate pinned favicon data without getting favicons again", async () => {
+        fakeNewTabUtils.pinnedLinks.links = [{url: "https://foo.com/"}];
+        await feed.getLinksWithDefaults();
+        const {callCount} = fakeNewTabUtils.activityStreamProvider._addFavicons;
+        feed.pinnedCache.expire();
 
-      assert.propertyVal(result, "length", feed.store.state.Prefs.values.topSitesCount);
+        const result = await feed.getLinksWithDefaults();
+
+        assert.callCount(fakeNewTabUtils.activityStreamProvider._addFavicons, callCount);
+        assert.propertyVal(result[0], "favicon", FAKE_FAVICON);
+        assert.propertyVal(result[0], "faviconSize", FAKE_FAVICON_SIZE);
+      });
+      it("should not expose internal link properties", async() => {
+        const result = await feed.getLinksWithDefaults();
+
+        const internal = Object.keys(result[0]).filter(key => key.startsWith("__"));
+        assert.equal(internal.join(""), "");
+      });
     });
     describe("deduping", () => {
       beforeEach(() => {
         ({TopSitesFeed, DEFAULT_TOP_SITES} = injector({
           "lib/ActivityStreamPrefs.jsm": {Prefs: FakePrefs},
           "common/Reducers.jsm": {insertPinned, TOP_SITES_SHOWMORE_LENGTH},
           "lib/Screenshots.jsm": {Screenshots: fakeScreenshot}
         }));
@@ -273,102 +326,104 @@ describe("Top Sites Feed", () => {
         }
       });
       it("should check against null entries", async () => {
         fakeNewTabUtils.pinnedLinks.links = [null];
 
         await feed.getLinksWithDefaults();
       });
     });
+    it("should call _fetchIcon for each link", async () => {
+      sinon.spy(feed, "_fetchIcon");
+
+      const results = await feed.getLinksWithDefaults(action);
+
+      assert.callCount(feed._fetchIcon, results.length);
+      results.forEach(link => {
+        assert.calledWith(feed._fetchIcon, link);
+      });
+    });
   });
   describe("#refresh", () => {
+    beforeEach(() => {
+      sandbox.stub(feed, "_fetchIcon");
+    });
     it("should initialise _tippyTopProvider if it's not already initialised", async () => {
       feed._tippyTopProvider.initialized = false;
       await feed.refresh(action);
       assert.ok(feed._tippyTopProvider.initialized);
     });
     it("should dispatch an action with the links returned", async () => {
-      sandbox.stub(feed, "getScreenshot");
       await feed.refresh(action);
       const reference = links.map(site => Object.assign({}, site, {hostname: shortURLStub(site)}));
 
       assert.calledOnce(feed.store.dispatch);
       assert.propertyVal(feed.store.dispatch.firstCall.args[0], "type", at.TOP_SITES_UPDATED);
       assert.deepEqual(feed.store.dispatch.firstCall.args[0].data, reference);
     });
-    it("should call _fetchIcon for each link and pass in existing screenshots", async () => {
-      feed.store.state.TopSites.rows = [{url: FAKE_LINKS[0].url, screenshot: "foo.jpg"}];
-      const expectedScreenshotCache = {};
-      expectedScreenshotCache[FAKE_LINKS[0].url] = "foo.jpg";
-      sinon.spy(feed, "_fetchIcon");
-      await feed.refresh(action);
-      const results = feed.store.dispatch.firstCall.args[0].data;
-      assert.callCount(feed._fetchIcon, results.length);
-      results.forEach(link => {
-        assert.calledWith(feed._fetchIcon, link, expectedScreenshotCache);
-      });
-    });
     it("should handle empty slots in the resulting top sites array", async () => {
       links = [FAKE_LINKS[0]];
       fakeNewTabUtils.pinnedLinks.links = [null, null, FAKE_LINKS[1], null, null, null, null, null, FAKE_LINKS[2]];
-      sandbox.stub(feed, "getScreenshot");
       await feed.refresh(action);
       assert.calledOnce(feed.store.dispatch);
     });
   });
   describe("#_fetchIcon", () => {
-    it("should reuse screenshots for existing links, and call feed.getScreenshot for others", () => {
-      sandbox.stub(feed, "getScreenshot");
-      const screenshotCache = {};
-      screenshotCache[FAKE_LINKS[0].url] = "foo.jpg";
-      screenshotCache[FAKE_LINKS[1].url] = "bar.png";
+    it("should reuse screenshot on the link", () => {
+      const link = {screenshot: "reuse.png"};
+
+      feed._fetchIcon(link);
+
+      assert.notCalled(fakeScreenshot.getScreenshotForURL);
+      assert.propertyVal(link, "screenshot", "reuse.png");
+    });
+    it("should reuse existing fetching screenshot on the link", async () => {
+      const link = {__fetchingScreenshot: Promise.resolve("fetching.png")};
+
+      await feed._fetchIcon(link);
 
-      feed._fetchIcon(FAKE_LINKS[0], screenshotCache);
-      assert.notCalled(feed.getScreenshot);
-      assert.propertyVal(FAKE_LINKS[0], "screenshot", "foo.jpg");
+      assert.notCalled(fakeScreenshot.getScreenshotForURL);
+      assert.propertyVal(link, "screenshot", "fetching.png");
+    });
+    it("should get a screenshot if the link is missing it", () => {
+      feed._fetchIcon(FAKE_LINKS[0]);
 
-      feed._fetchIcon(FAKE_LINKS[1], screenshotCache);
-      assert.notCalled(feed.getScreenshot);
-      assert.propertyVal(FAKE_LINKS[1], "screenshot", "bar.png");
+      assert.calledOnce(fakeScreenshot.getScreenshotForURL);
+      assert.calledWith(fakeScreenshot.getScreenshotForURL, FAKE_LINKS[0].url);
+    });
+    it("should update the link's cache if it can be updated", () => {
+      const link = {__updateCache: sandbox.stub()};
 
-      feed._fetchIcon(FAKE_LINKS[2], screenshotCache);
-      assert.calledOnce(feed.getScreenshot);
-      assert.calledWith(feed.getScreenshot, FAKE_LINKS[2].url);
+      feed._fetchIcon(link);
+
+      assert.calledOnce(link.__updateCache);
+      assert.calledWith(link.__updateCache, "__fetchingScreenshot");
     });
     it("should skip getting a screenshot if there is a tippy top icon", () => {
-      sandbox.stub(feed, "getScreenshot");
       feed._tippyTopProvider.processSite = site => {
         site.tippyTopIcon = "icon.png";
         site.backgroundColor = "#fff";
         return site;
       };
       const link = {url: "example.com"};
       feed._fetchIcon(link);
       assert.propertyVal(link, "tippyTopIcon", "icon.png");
       assert.notProperty(link, "screenshot");
-      assert.notCalled(feed.getScreenshot);
+      assert.notCalled(fakeScreenshot.getScreenshotForURL);
     });
     it("should skip getting a screenshot if there is an icon of size greater than 96x96 and no tippy top", () => {
-      sandbox.stub(feed, "getScreenshot");
       const link = {
         url: "foo.com",
         favicon: "data:foo",
         faviconSize: 196
       };
       feed._fetchIcon(link);
       assert.notProperty(link, "tippyTopIcon");
       assert.notProperty(link, "screenshot");
-      assert.notCalled(feed.getScreenshot);
-    });
-  });
-  describe("getScreenshot", () => {
-    it("should call Screenshots.getScreenshotForURL with the right url", async () => {
-      const url = "foo.com";
-      await feed.getScreenshot(url);
-      assert.calledWith(fakeScreenshot.getScreenshotForURL, url);
+      assert.notCalled(fakeScreenshot.getScreenshotForURL);
     });
   });
   describe("#onAction", () => {
     const newTabAction = {type: at.NEW_TAB_LOAD, meta: {fromTarget: "target"}};
     it("should not call refresh if there are enough sites on NEW_TAB_LOAD", () => {
       feed.lastUpdated = Date.now();
       sinon.stub(feed, "refresh");
       feed.onAction(newTabAction);
@@ -392,27 +447,16 @@ describe("Top Sites Feed", () => {
       const pinAction = {
         type: at.TOP_SITES_PIN,
         data: {site: {url: "foo.com"}, index: 7}
       };
       feed.onAction(pinAction);
       assert.calledOnce(fakeNewTabUtils.pinnedLinks.pin);
       assert.calledWith(fakeNewTabUtils.pinnedLinks.pin, pinAction.data.site, pinAction.data.index);
     });
-    it("should compare against links if available, instead of getting from store", () => {
-      const frecentSite = {url: "foo.com", faviconSize: 32, favicon: "favicon.png"};
-      const pinnedSite1 = {url: "bar.com"};
-      const pinnedSite2 = {url: "foo.com"};
-      fakeNewTabUtils.pinnedLinks.links = [pinnedSite1, pinnedSite2];
-      feed.store = {getState() { return {TopSites: {rows: sinon.spy()}}; }};
-      let result = feed._getPinnedWithData([frecentSite]);
-      assert.include(result[0], pinnedSite1);
-      assert.include(result[1], Object.assign({}, frecentSite, pinnedSite2));
-      assert.notCalled(feed.store.getState().TopSites.rows);
-    });
     it("should trigger refresh on TOP_SITES_PIN", () => {
       sinon.stub(feed, "refresh");
       const pinExistingAction = {type: at.TOP_SITES_PIN, data: {site: FAKE_LINKS[4], index: 4}};
 
       feed.onAction(pinExistingAction);
 
       assert.calledOnce(feed.refresh);
     });
@@ -436,17 +480,17 @@ describe("Top Sites Feed", () => {
     });
     it("should call refresh without a target if we clear history with PLACES_HISTORY_CLEARED", () => {
       sandbox.stub(feed, "refresh");
       feed.onAction({type: at.PLACES_HISTORY_CLEARED});
       assert.calledOnce(feed.refresh);
       assert.equal(feed.refresh.firstCall.args[0], null);
     });
     it("should still dispatch an action even if there's no target provided", async () => {
-      sandbox.stub(feed, "getScreenshot");
+      sandbox.stub(feed, "_fetchIcon");
       await feed.refresh();
       assert.calledOnce(feed.store.dispatch);
       assert.propertyVal(feed.store.dispatch.firstCall.args[0], "type", at.TOP_SITES_UPDATED);
     });
     it("should call refresh on INIT action", async () => {
       sinon.stub(feed, "refresh");
       await feed.onAction({type: at.INIT});
       assert.calledOnce(feed.refresh);
--- a/browser/extensions/formautofill/FormAutofillHeuristics.jsm
+++ b/browser/extensions/formautofill/FormAutofillHeuristics.jsm
@@ -471,33 +471,16 @@ this.FormAutofillHeuristics = {
       "cc-name",
       "cc-number",
       "cc-exp-month",
       "cc-exp-year",
       "cc-exp",
     ];
     let regexps = isAutoCompleteOff ? FIELDNAMES_IGNORING_AUTOCOMPLETE_OFF : Object.keys(this.RULES);
 
-    if (!FormAutofillUtils.isAutofillCreditCardsAvailable) {
-      if (isAutoCompleteOff) {
-        if (!this._regexpListOf_CcUnavailable_AcOff) {
-          this._regexpListOf_CcUnavailable_AcOff = regexps.filter(name => !FormAutofillUtils.isCreditCardField(name));
-        }
-        regexps = this._regexpListOf_CcUnavailable_AcOff;
-      } else {
-        if (!this._regexpListOf_CcUnavailable_AcOn) {
-          this._regexpListOf_CcUnavailable_AcOn = regexps.filter(name => !FormAutofillUtils.isCreditCardField(name));
-        }
-        regexps = this._regexpListOf_CcUnavailable_AcOn;
-      }
-    }
-    if (regexps.length == 0) {
-      return null;
-    }
-
     let labelStrings;
     let getElementStrings = {};
     getElementStrings[Symbol.iterator] = function* () {
       yield element.id;
       yield element.name;
       if (!labelStrings) {
         labelStrings = [];
         let labels = LabelUtils.findLabelElements(element);
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,5 +1,5 @@
 This is the PDF.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 1.9.562
+Current extension version is: 1.9.583
 
-Taken from upstream commit: 31a34335
+Taken from upstream commit: d7b37ae7
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -1454,17 +1454,17 @@ exports.unreachable = unreachable;
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-exports.DOMSVGFactory = exports.DOMCMapReaderFactory = exports.DOMCanvasFactory = exports.DEFAULT_LINK_REL = exports.getDefaultSetting = exports.LinkTarget = exports.getFilenameFromUrl = exports.isValidUrl = exports.isExternalLinkTargetSet = exports.addLinkAttributes = exports.RenderingCancelledException = exports.CustomStyle = undefined;
+exports.SimpleXMLParser = exports.DOMSVGFactory = exports.DOMCMapReaderFactory = exports.DOMCanvasFactory = exports.DEFAULT_LINK_REL = exports.getDefaultSetting = exports.LinkTarget = exports.getFilenameFromUrl = exports.isValidUrl = exports.isExternalLinkTargetSet = exports.addLinkAttributes = exports.RenderingCancelledException = exports.CustomStyle = undefined;
 
 var _util = __w_pdfjs_require__(0);
 
 var _global_scope = __w_pdfjs_require__(2);
 
 var _global_scope2 = _interopRequireDefault(_global_scope);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -1557,16 +1557,117 @@ class DOMSVGFactory {
     svg.setAttribute('viewBox', '0 0 ' + width + ' ' + height);
     return svg;
   }
   createElement(type) {
     (0, _util.assert)(typeof type === 'string', 'Invalid SVG element type');
     return document.createElementNS(SVG_NS, type);
   }
 }
+class SimpleDOMNode {
+  constructor(nodeName, nodeValue) {
+    this.nodeName = nodeName;
+    this.nodeValue = nodeValue;
+    Object.defineProperty(this, 'parentNode', {
+      value: null,
+      writable: true
+    });
+  }
+  get firstChild() {
+    return this.childNodes[0];
+  }
+  get nextSibling() {
+    let index = this.parentNode.childNodes.indexOf(this);
+    return this.parentNode.childNodes[index + 1];
+  }
+  get textContent() {
+    if (!this.childNodes) {
+      return this.nodeValue || '';
+    }
+    return this.childNodes.map(function (child) {
+      return child.textContent;
+    }).join('');
+  }
+  hasChildNodes() {
+    return this.childNodes && this.childNodes.length > 0;
+  }
+}
+class SimpleXMLParser {
+  parseFromString(data) {
+    let nodes = [];
+    data = data.replace(/<\?[\s\S]*?\?>|<!--[\s\S]*?-->/g, '').trim();
+    data = data.replace(/<!DOCTYPE[^>\[]+(\[[^\]]+)?[^>]+>/g, '').trim();
+    data = data.replace(/>([^<][\s\S]*?)</g, (all, text) => {
+      let length = nodes.length;
+      let node = new SimpleDOMNode('#text', this._decodeXML(text));
+      nodes.push(node);
+      if (node.textContent.trim().length === 0) {
+        return '><';
+      }
+      return '>' + length + ',<';
+    });
+    data = data.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, function (all, text) {
+      let length = nodes.length;
+      let node = new SimpleDOMNode('#text', text);
+      nodes.push(node);
+      return length + ',';
+    });
+    let regex = /<([\w\:]+)((?:[\s\w:=]|'[^']*'|"[^"]*")*)(?:\/>|>([\d,]*)<\/[^>]+>)/g;
+    let lastLength;
+    do {
+      lastLength = nodes.length;
+      data = data.replace(regex, function (all, name, attrs, data) {
+        let length = nodes.length;
+        let node = new SimpleDOMNode(name);
+        let children = [];
+        if (data) {
+          data = data.split(',');
+          data.pop();
+          data.forEach(function (child) {
+            let childNode = nodes[+child];
+            childNode.parentNode = node;
+            children.push(childNode);
+          });
+        }
+        node.childNodes = children;
+        nodes.push(node);
+        return length + ',';
+      });
+    } while (lastLength < nodes.length);
+    return { documentElement: nodes.pop() };
+  }
+  _decodeXML(text) {
+    if (text.indexOf('&') < 0) {
+      return text;
+    }
+    return text.replace(/&(#(x[0-9a-f]+|\d+)|\w+);/gi, function (all, entityName, number) {
+      if (number) {
+        if (number[0] === 'x') {
+          number = parseInt(number.substring(1), 16);
+        } else {
+          number = +number;
+        }
+        return String.fromCharCode(number);
+      }
+      switch (entityName) {
+        case 'amp':
+          return '&';
+        case 'lt':
+          return '<';
+        case 'gt':
+          return '>';
+        case 'quot':
+          return '\"';
+        case 'apos':
+          return '\'';
+      }
+      return '&' + entityName + ';';
+    });
+  }
+}
 var CustomStyle = function CustomStyleClosure() {
   var prefixes = ['ms', 'Moz', 'Webkit', 'O'];
   var _cache = Object.create(null);
   function CustomStyle() {}
   CustomStyle.getProp = function get(propName, element) {
     if (arguments.length === 1 && typeof _cache[propName] === 'string') {
       return _cache[propName];
     }
@@ -1718,16 +1819,17 @@ exports.isExternalLinkTargetSet = isExte
 exports.isValidUrl = isValidUrl;
 exports.getFilenameFromUrl = getFilenameFromUrl;
 exports.LinkTarget = LinkTarget;
 exports.getDefaultSetting = getDefaultSetting;
 exports.DEFAULT_LINK_REL = DEFAULT_LINK_REL;
 exports.DOMCanvasFactory = DOMCanvasFactory;
 exports.DOMCMapReaderFactory = DOMCMapReaderFactory;
 exports.DOMSVGFactory = DOMSVGFactory;
+exports.SimpleXMLParser = SimpleXMLParser;
 
 /***/ }),
 /* 2 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
@@ -1906,17 +2008,18 @@ function _fetchDocument(worker, source, 
       length: source.length
     },
     maxImageSize: (0, _dom_utils.getDefaultSetting)('maxImageSize'),
     disableFontFace: (0, _dom_utils.getDefaultSetting)('disableFontFace'),
     disableCreateObjectURL: (0, _dom_utils.getDefaultSetting)('disableCreateObjectURL'),
     postMessageTransfers: (0, _dom_utils.getDefaultSetting)('postMessageTransfers') && !isPostMessageTransfersDisabled,
     docBaseUrl: source.docBaseUrl,
     nativeImageDecoderSupport: source.nativeImageDecoderSupport,
-    ignoreErrors: source.ignoreErrors
+    ignoreErrors: source.ignoreErrors,
+    isEvalSupported: (0, _dom_utils.getDefaultSetting)('isEvalSupported')
   }).then(function (workerId) {
     if (worker.destroyed) {
       throw new Error('Worker was destroyed');
     }
     return workerId;
   });
 }
 var PDFDocumentLoadingTask = function PDFDocumentLoadingTaskClosure() {
@@ -3201,18 +3304,18 @@ var _UnsupportedManager = function Unsup
       for (var i = 0, ii = listeners.length; i < ii; i++) {
         listeners[i](featureId);
       }
     }
   };
 }();
 var version, build;
 {
-  exports.version = version = '1.9.562';
-  exports.build = build = '31a34335';
+  exports.version = version = '1.9.583';
+  exports.build = build = 'd7b37ae7';
 }
 exports.getDocument = getDocument;
 exports.LoopbackPort = LoopbackPort;
 exports.PDFDataRangeTransport = PDFDataRangeTransport;
 exports.PDFWorker = PDFWorker;
 exports.PDFDocumentProxy = PDFDocumentProxy;
 exports.PDFPageProxy = PDFPageProxy;
 exports.setPDFNetworkStreamClass = setPDFNetworkStreamClass;
@@ -3592,84 +3695,89 @@ exports.WebGLUtils = WebGLUtils;
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-function fixMetadata(meta) {
-  return meta.replace(/>\\376\\377([^<]+)/g, function (all, codes) {
-    var bytes = codes.replace(/\\([0-3])([0-7])([0-7])/g, function (code, d1, d2, d3) {
-      return String.fromCharCode(d1 * 64 + d2 * 8 + d3 * 1);
+exports.Metadata = undefined;
+
+var _util = __w_pdfjs_require__(0);
+
+var _dom_utils = __w_pdfjs_require__(1);
+
+class Metadata {
+  constructor(data) {
+    (0, _util.assert)(typeof data === 'string', 'Metadata: input is not a string');
+    data = this._repair(data);
+    let parser = new _dom_utils.SimpleXMLParser();
+    data = parser.parseFromString(data);
+    this._metadata = Object.create(null);
+    this._parse(data);
+  }
+  _repair(data) {
+    return data.replace(/>\\376\\377([^<]+)/g, function (all, codes) {
+      let bytes = codes.replace(/\\([0-3])([0-7])([0-7])/g, function (code, d1, d2, d3) {
+        return String.fromCharCode(d1 * 64 + d2 * 8 + d3 * 1);
+      });
+      let chars = '';
+      for (let i = 0, ii = bytes.length; i < ii; i += 2) {
+        let code = bytes.charCodeAt(i) * 256 + bytes.charCodeAt(i + 1);
+        if (code >= 32 && code < 127 && code !== 60 && code !== 62 && code !== 38) {
+          chars += String.fromCharCode(code);
+        } else {
+          chars += '&#x' + (0x10000 + code).toString(16).substring(1) + ';';
+        }
+      }
+      return '>' + chars;
     });
-    var chars = '';
-    for (var i = 0; i < bytes.length; i += 2) {
-      var code = bytes.charCodeAt(i) * 256 + bytes.charCodeAt(i + 1);
-      chars += code >= 32 && code < 127 && code !== 60 && code !== 62 && code !== 38 ? String.fromCharCode(code) : '&#x' + (0x10000 + code).toString(16).substring(1) + ';';
-    }
-    return '>' + chars;
-  });
-}
-function Metadata(meta) {
-  if (typeof meta === 'string') {
-    meta = fixMetadata(meta);
-    var parser = new DOMParser();
-    meta = parser.parseFromString(meta, 'application/xml');
-  } else if (!(meta instanceof Document)) {
-    throw new Error('Metadata: Invalid metadata object');
-  }
-  this.metaDocument = meta;
-  this.metadata = Object.create(null);
-  this.parse();
-}
-Metadata.prototype = {
-  parse: function Metadata_parse() {
-    var doc = this.metaDocument;
-    var rdf = doc.documentElement;
+  }
+  _parse(domDocument) {
+    let rdf = domDocument.documentElement;
     if (rdf.nodeName.toLowerCase() !== 'rdf:rdf') {
       rdf = rdf.firstChild;
       while (rdf && rdf.nodeName.toLowerCase() !== 'rdf:rdf') {
         rdf = rdf.nextSibling;
       }
     }
-    var nodeName = rdf ? rdf.nodeName.toLowerCase() : null;
+    let nodeName = rdf ? rdf.nodeName.toLowerCase() : null;
     if (!rdf || nodeName !== 'rdf:rdf' || !rdf.hasChildNodes()) {
       return;
     }
-    var children = rdf.childNodes,
-        desc,
-        entry,
-        name,
-        i,
-        ii,
-        length,
-        iLength;
-    for (i = 0, length = children.length; i < length; i++) {
-      desc = children[i];
+    let children = rdf.childNodes;
+    for (let i = 0, ii = children.length; i < ii; i++) {
+      let desc = children[i];
       if (desc.nodeName.toLowerCase() !== 'rdf:description') {
         continue;
       }
-      for (ii = 0, iLength = desc.childNodes.length; ii < iLength; ii++) {
-        if (desc.childNodes[ii].nodeName.toLowerCase() !== '#text') {
-          entry = desc.childNodes[ii];
-          name = entry.nodeName.toLowerCase();
-          this.metadata[name] = entry.textContent.trim();
-        }
-      }
-    }
-  },
-  get: function Metadata_get(name) {
-    return this.metadata[name] || null;
-  },
-  has: function Metadata_has(name) {
-    return typeof this.metadata[name] !== 'undefined';
-  }
-};
+      for (let j = 0, jj = desc.childNodes.length; j < jj; j++) {
+        if (desc.childNodes[j].nodeName.toLowerCase() !== '#text') {
+          let entry = desc.childNodes[j];
+          let name = entry.nodeName.toLowerCase();
+          this._metadata[name] = entry.textContent.trim();
+        }
+      }
+    }
+  }
+  get(name) {
+    return this._metadata[name] || null;
+  }
+  getAll() {
+    return this._metadata;
+  }
+  has(name) {
+    return typeof this._metadata[name] !== 'undefined';
+  }
+  get metadata() {
+    (0, _util.deprecated)('`metadata` getter; use `getAll()` instead.');
+    return this.getAll();
+  }
+}
 exports.Metadata = Metadata;
 
 /***/ }),
 /* 6 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
@@ -3719,16 +3827,18 @@ class AnnotationElementFactory {
       case _util.AnnotationType.HIGHLIGHT:
         return new HighlightAnnotationElement(parameters);
       case _util.AnnotationType.UNDERLINE:
         return new UnderlineAnnotationElement(parameters);
       case _util.AnnotationType.SQUIGGLY:
         return new SquigglyAnnotationElement(parameters);
       case _util.AnnotationType.STRIKEOUT:
         return new StrikeOutAnnotationElement(parameters);
+      case _util.AnnotationType.STAMP:
+        return new StampAnnotationElement(parameters);
       case _util.AnnotationType.FILEATTACHMENT:
         return new FileAttachmentAnnotationElement(parameters);
       default:
         return new AnnotationElement(parameters);
     }
   }
 }
 class AnnotationElement {
@@ -4242,16 +4352,29 @@ class StrikeOutAnnotationElement extends
   render() {
     this.container.className = 'strikeoutAnnotation';
     if (!this.data.hasPopup) {
       this._createPopup(this.container, null, this.data);
     }
     return this.container;
   }
 }
+class StampAnnotationElement extends AnnotationElement {
+  constructor(parameters) {
+    let isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+    super(parameters, isRenderable, true);
+  }
+  render() {
+    this.container.className = 'stampAnnotation';
+    if (!this.data.hasPopup) {
+      this._createPopup(this.container, null, this.data);
+    }
+    return this.container;
+  }
+}
 class FileAttachmentAnnotationElement extends AnnotationElement {
   constructor(parameters) {
     super(parameters, true);
     let file = this.data.file;
     this.filename = (0, _dom_utils.getFilenameFromUrl)(file.filename);
     this.content = file.content;
     this.linkService.onFileAttachmentAnnotation({
       id: (0, _util.stringToPDFString)(file.filename),
@@ -4877,18 +5000,18 @@ exports.SVGGraphics = SVGGraphics;
 
 /***/ }),
 /* 9 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
-var pdfjsVersion = '1.9.562';
-var pdfjsBuild = '31a34335';
+var pdfjsVersion = '1.9.583';
+var pdfjsBuild = 'd7b37ae7';
 var pdfjsSharedUtil = __w_pdfjs_require__(0);
 var pdfjsDisplayGlobal = __w_pdfjs_require__(13);
 var pdfjsDisplayAPI = __w_pdfjs_require__(3);
 var pdfjsDisplayTextLayer = __w_pdfjs_require__(7);
 var pdfjsDisplayAnnotationLayer = __w_pdfjs_require__(6);
 var pdfjsDisplayDOMUtils = __w_pdfjs_require__(1);
 var pdfjsDisplaySVG = __w_pdfjs_require__(8);
 ;
@@ -8004,18 +8127,18 @@ var _svg = __w_pdfjs_require__(8);
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 var isWorker = typeof window === 'undefined';
 if (!_global_scope2.default.PDFJS) {
   _global_scope2.default.PDFJS = {};
 }
 var PDFJS = _global_scope2.default.PDFJS;
 {
-  PDFJS.version = '1.9.562';
-  PDFJS.build = '31a34335';
+  PDFJS.version = '1.9.583';
+  PDFJS.build = 'd7b37ae7';
 }
 PDFJS.pdfBug = false;
 if (PDFJS.verbosity !== undefined) {
   (0, _util.setVerbosityLevel)(PDFJS.verbosity);
 }
 delete PDFJS.verbosity;
 Object.defineProperty(PDFJS, 'verbosity', {
   get() {
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -2239,16 +2239,26 @@ var PredictorStream = function Predictor
     } else if (bits === 8) {
       for (i = 0; i < colors; ++i) {
         buffer[pos++] = rawBytes[i];
       }
       for (; i < rowBytes; ++i) {
         buffer[pos] = buffer[pos - colors] + rawBytes[i];
         pos++;
       }
+    } else if (bits === 16) {
+      var bytesPerPixel = colors * 2;
+      for (i = 0; i < bytesPerPixel; ++i) {
+        buffer[pos++] = rawBytes[i];
+      }
+      for (; i < rowBytes; i += 2) {
+        var sum = ((rawBytes[i] & 0xFF) << 8) + (rawBytes[i + 1] & 0xFF) + ((buffer[pos - bytesPerPixel] & 0xFF) << 8) + (buffer[pos - bytesPerPixel + 1] & 0xFF);
+        buffer[pos++] = sum >> 8 & 0xFF;
+        buffer[pos++] = sum & 0xFF;
+      }
     } else {
       var compArray = new Uint8Array(colors + 1);
       var bitMask = (1 << bits) - 1;
       var j = 0,
           k = bufferLength;
       var columns = this.columns;
       for (i = 0; i < columns; ++i) {
         for (var kk = 0; kk < colors; ++kk) {
@@ -2735,17 +2745,19 @@ var CCITTFaxStream = function CCITTFaxSt
   var whiteTable1 = [[-1, -1], [12, ccittEOL], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [11, 1792], [11, 1792], [12, 1984], [12, 2048], [12, 2112], [12, 2176], [12, 2240], [12, 2304], [11, 1856], [11, 1856], [11, 1920], [11, 1920], [12, 2368], [12, 2432], [12, 2496], [12, 2560]];
   var whiteTable2 = [[-1, -1], [-1, -1], [-1, -1], [-1, -1], [8, 29], [8, 29], [8, 30], [8, 30], [8, 45], [8, 45], [8, 46], [8, 46], [7, 22], [7, 22], [7, 22], [7, 22], [7, 23], [7, 23], [7, 23], [7, 23], [8, 47], [8, 47], [8, 48], [8, 48], [6, 13], [6, 13], [6, 13], [6, 13], [6, 13], [6, 13], [6, 13], [6, 13], [7, 20], [7, 20], [7, 20], [7, 20], [8, 33], [8, 33], [8, 34], [8, 34], [8, 35], [8, 35], [8, 36], [8, 36], [8, 37], [8, 37], [8, 38], [8, 38], [7, 19], [7, 19], [7, 19], [7, 19], [8, 31], [8, 31], [8, 32], [8, 32], [6, 1], [6, 1], [6, 1], [6, 1], [6, 1], [6, 1], [6, 1], [6, 1], [6, 12], [6, 12], [6, 12], [6, 12], [6, 12], [6, 12], [6, 12], [6, 12], [8, 53], [8, 53], [8, 54], [8, 54], [7, 26], [7, 26], [7, 26], [7, 26], [8, 39], [8, 39], [8, 40], [8, 40], [8, 41], [8, 41], [8, 42], [8, 42], [8, 43], [8, 43], [8, 44], [8, 44], [7, 21], [7, 21], [7, 21], [7, 21], [7, 28], [7, 28], [7, 28], [7, 28], [8, 61], [8, 61], [8, 62], [8, 62], [8, 63], [8, 63], [8, 0], [8, 0], [8, 320], [8, 320], [8, 384], [8, 384], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [7, 27], [7, 27], [7, 27], [7, 27], [8, 59], [8, 59], [8, 60], [8, 60], [9, 1472], [9, 1536], [9, 1600], [9, 1728], [7, 18], [7, 18], [7, 18], [7, 18], [7, 24], [7, 24], [7, 24], [7, 24], [8, 49], [8, 49], [8, 50], [8, 50], [8, 51], [8, 51], [8, 52], [8, 52], [7, 25], [7, 25], [7, 25], [7, 25], [8, 55], [8, 55], [8, 56], [8, 56], [8, 57], [8, 57], [8, 58], [8, 58], [6, 192], [6, 192], [6, 192], [6, 192], [6, 192], [6, 192], [6, 192], [6, 192], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [8, 448], [8, 448], [8, 512], [8, 512], [9, 704], [9, 768], [8, 640], [8, 640], [8, 576], [8, 576], [9, 832], [9, 896], [9, 960], [9, 1024], [9, 1088], [9, 1152], [9, 1216], [9, 1280], [9, 1344], [9, 1408], [7, 256], [7, 256], [7, 256], [7, 256], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [6, 16], [6, 16], [6, 16], [6, 16], [6, 16], [6, 16], [6, 16], [6, 16], [6, 17], [6, 17], [6, 17], [6, 17], [6, 17], [6, 17], [6, 17], [6, 17], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [6, 14], [6, 14], [6, 14], [6, 14], [6, 14], [6, 14], [6, 14], [6, 14], [6, 15], [6, 15], [6, 15], [6, 15], [6, 15], [6, 15], [6, 15], [6, 15], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7]];
   var blackTable1 = [[-1, -1], [-1, -1], [12, ccittEOL], [12, ccittEOL], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [11, 1792], [11, 1792], [11, 1792], [11, 1792], [12, 1984], [12, 1984], [12, 2048], [12, 2048], [12, 2112], [12, 2112], [12, 2176], [12, 2176], [12, 2240], [12, 2240], [12, 2304], [12, 2304], [11, 1856], [11, 1856], [11, 1856], [11, 1856], [11, 1920], [11, 1920], [11, 1920], [11, 1920], [12, 2368], [12, 2368], [12, 2432], [12, 2432], [12, 2496], [12, 2496], [12, 2560], [12, 2560], [10, 18], [10, 18], [10, 18], [10, 18], [10, 18], [10, 18], [10, 18], [10, 18], [12, 52], [12, 52], [13, 640], [13, 704], [13, 768], [13, 832], [12, 55], [12, 55], [12, 56], [12, 56], [13, 1280], [13, 1344], [13, 1408], [13, 1472], [12, 59], [12, 59], [12, 60], [12, 60], [13, 1536], [13, 1600], [11, 24], [11, 24], [11, 24], [11, 24], [11, 25], [11, 25], [11, 25], [11, 25], [13, 1664], [13, 1728], [12, 320], [12, 320], [12, 384], [12, 384], [12, 448], [12, 448], [13, 512], [13, 576], [12, 53], [12, 53], [12, 54], [12, 54], [13, 896], [13, 960], [13, 1024], [13, 1088], [13, 1152], [13, 1216], [10, 64], [10, 64], [10, 64], [10, 64], [10, 64], [10, 64], [10, 64], [10, 64]];
   var blackTable2 = [[8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [11, 23], [11, 23], [12, 50], [12, 51], [12, 44], [12, 45], [12, 46], [12, 47], [12, 57], [12, 58], [12, 61], [12, 256], [10, 16], [10, 16], [10, 16], [10, 16], [10, 17], [10, 17], [10, 17], [10, 17], [12, 48], [12, 49], [12, 62], [12, 63], [12, 30], [12, 31], [12, 32], [12, 33], [12, 40], [12, 41], [11, 22], [11, 22], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [9, 15], [9, 15], [9, 15], [9, 15], [9, 15], [9, 15], [9, 15], [9, 15], [12, 128], [12, 192], [12, 26], [12, 27], [12, 28], [12, 29], [11, 19], [11, 19], [11, 20], [11, 20], [12, 34], [12, 35], [12, 36], [12, 37], [12, 38], [12, 39], [11, 21], [11, 21], [12, 42], [12, 43], [10, 0], [10, 0], [10, 0], [10, 0], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12]];
   var blackTable3 = [[-1, -1], [-1, -1], [-1, -1], [-1, -1], [6, 9], [6, 8], [5, 7], [5, 7], [4, 6], [4, 6], [4, 6], [4, 6], [4, 5], [4, 5], [4, 5], [4, 5], [3, 1], [3, 1], [3, 1], [3, 1], [3, 1], [3, 1], [3, 1], [3, 1], [3, 4], [3, 4], [3, 4], [3, 4], [3, 4], [3, 4], [3, 4], [3, 4], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2]];
   function CCITTFaxStream(str, maybeLength, params) {
     this.str = str;
     this.dict = str.dict;
-    params = params || _primitives.Dict.empty;
+    if (!(0, _primitives.isDict)(params)) {
+      params = _primitives.Dict.empty;
+    }
     this.encoding = params.get('K') || 0;
     this.eoline = params.get('EndOfLine') || false;
     this.byteAlign = params.get('EncodedByteAlign') || false;
     this.columns = params.get('Columns') || 1728;
     this.rows = params.get('Rows') || 0;
     var eoblock = params.get('EndOfBlock');
     if (eoblock === null || eoblock === undefined) {
       eoblock = true;
@@ -2756,16 +2768,17 @@ var CCITTFaxStream = function CCITTFaxSt
     this.refLine = new Uint32Array(this.columns + 2);
     this.codingLine[0] = this.columns;
     this.codingPos = 0;
     this.row = 0;
     this.nextLine2D = this.encoding < 0;
     this.inputBits = 0;
     this.inputBuf = 0;
     this.outputBits = 0;
+    this.rowsDone = false;
     var code1;
     while ((code1 = this.lookBits(12)) === 0) {
       this.eatBits(1);
     }
     if (code1 === 1) {
       this.eatBits(12);
     }
     if (this.encoding > 0) {
@@ -2825,16 +2838,19 @@ var CCITTFaxStream = function CCITTFaxSt
     this.codingPos = codingPos;
   };
   CCITTFaxStream.prototype.lookChar = function CCITTFaxStream_lookChar() {
     var refLine = this.refLine;
     var codingLine = this.codingLine;
     var columns = this.columns;
     var refPos, blackPixels, bits, i;
     if (this.outputBits === 0) {
+      if (this.rowsDone) {
+        this.eof = true;
+      }
       if (this.eof) {
         return null;
       }
       this.err = false;
       var code1, code2, code3;
       if (this.nextLine2D) {
         for (i = 0; codingLine[i] < columns; ++i) {
           refLine[i] = codingLine[i];
@@ -2990,17 +3006,17 @@ var CCITTFaxStream = function CCITTFaxSt
           blackPixels ^= 1;
         }
       }
       var gotEOL = false;
       if (this.byteAlign) {
         this.inputBits &= ~7;
       }
       if (!this.eoblock && this.row === this.rows - 1) {
-        this.eof = true;
+        this.rowsDone = true;
       } else {
         code1 = this.lookBits(12);
         if (this.eoline) {
           while (code1 !== ccittEOF && code1 !== 1) {
             this.eatBits(1);
             code1 = this.lookBits(12);
           }
         } else {
@@ -3011,17 +3027,17 @@ var CCITTFaxStream = function CCITTFaxSt
         }
         if (code1 === 1) {
           this.eatBits(12);
           gotEOL = true;
         } else if (code1 === ccittEOF) {
           this.eof = true;
         }
       }
-      if (!this.eof && this.encoding > 0) {
+      if (!this.eof && this.encoding > 0 && !this.rowsDone) {
         this.nextLine2D = !this.lookBits(1);
         this.eatBits(1);
       }
       if (this.eoblock && gotEOL && this.byteAlign) {
         code1 = this.lookBits(12);
         if (code1 === 1) {
           this.eatBits(12);
           if (this.encoding > 0) {
@@ -4791,17 +4807,17 @@ var Parser = function ParserClosure() {
       stream.dict = dict;
       return stream;
     },
     filter: function Parser_filter(stream, dict, length) {
       var filter = dict.get('Filter', 'F');
       var params = dict.get('DecodeParms', 'DP');
       if ((0, _primitives.isName)(filter)) {
         if (Array.isArray(params)) {
-          params = this.xref.fetchIfRef(params[0]);
+          (0, _util.warn)('/DecodeParms should not contain an Array, ' + 'when /Filter contains a Name.');
         }
         return this.makeFilter(stream, filter.name, length, params);
       }
       var maybeLength = length;
       if (Array.isArray(filter)) {
         var filterArray = filter;
         var paramsArray = params;
         for (var i = 0, ii = filterArray.length; i < ii; ++i) {
@@ -5328,22 +5344,31 @@ Object.defineProperty(exports, "__esModu
 exports.PostScriptCompiler = exports.PostScriptEvaluator = exports.PDFFunction = exports.isPDFFunction = undefined;
 
 var _util = __w_pdfjs_require__(0);
 
 var _primitives = __w_pdfjs_require__(1);
 
 var _ps_parser = __w_pdfjs_require__(26);
 
+let IsEvalSupportedCached = {
+  get value() {
+    return (0, _util.shadow)(this, 'value', (0, _util.isEvalSupported)());
+  }
+};
 var PDFFunction = function PDFFunctionClosure() {
   var CONSTRUCT_SAMPLED = 0;
   var CONSTRUCT_INTERPOLATED = 2;
   var CONSTRUCT_STICHED = 3;
   var CONSTRUCT_POSTSCRIPT = 4;
+  let isEvalSupported = true;
   return {
+    setIsEvalSupported(support = true) {
+      isEvalSupported = support !== false;
+    },
     getSampleArray: function PDFFunction_getSampleArray(size, outputSize, bps, str) {
       var i, ii;
       var length = 1;
       for (i = 0, ii = size.length; i < ii; i++) {
         length *= size[i];
       }
       length *= outputSize;
       var array = new Array(length);
@@ -5605,19 +5630,21 @@ var PDFFunction = function PDFFunctionCl
       var parser = new _ps_parser.PostScriptParser(lexer);
       var code = parser.parse();
       return [CONSTRUCT_POSTSCRIPT, domain, range, code];
     },
     constructPostScriptFromIR: function PDFFunction_constructPostScriptFromIR(IR) {
       var domain = IR[1];
       var range = IR[2];
       var code = IR[3];
-      var compiled = new PostScriptCompiler().compile(code, domain, range);
-      if (compiled) {
-        return new Function('src', 'srcOffset', 'dest', 'destOffset', compiled);
+      if (isEvalSupported && IsEvalSupportedCached.value) {
+        let compiled = new PostScriptCompiler().compile(code, domain, range);
+        if (compiled) {
+          return new Function('src', 'srcOffset', 'dest', 'destOffset', compiled);
+        }
       }
       (0, _util.info)('Unable to compile PS function');
       var numOutputs = range.length >> 1;
       var numInputs = domain.length >> 1;
       var evaluator = new PostScriptEvaluator(code);
       var cache = Object.create(null);
       var MAX_CACHE_SIZE = 2048 * 4;
       var cache_available = MAX_CACHE_SIZE;
@@ -16700,17 +16727,18 @@ var _murmurhash = __w_pdfjs_require__(35
 var _image = __w_pdfjs_require__(36);
 
 var PartialEvaluator = function PartialEvaluatorClosure() {
   const DefaultPartialEvaluatorOptions = {
     forceDataSchema: false,
     maxImageSize: -1,
     disableFontFace: false,
     nativeImageDecoderSupport: _util.NativeImageDecoding.DECODE,
-    ignoreErrors: false
+    ignoreErrors: false,
+    isEvalSupported: true
   };
   function NativeImageDecoder(xref, resources, handler, forceDataSchema) {
     this.xref = xref;
     this.resources = resources;
     this.handler = handler;
     this.forceDataSchema = forceDataSchema;
   }
   NativeImageDecoder.prototype = {
@@ -18265,17 +18293,17 @@ var PartialEvaluator = function PartialE
     extractWidths: function PartialEvaluator_extractWidths(dict, descriptor, properties) {
       var xref = this.xref;
       var glyphsWidths = [];
       var defaultWidth = 0;
       var glyphsVMetrics = [];
       var defaultVMetrics;
       var i, ii, j, jj, start, code, widths;
       if (properties.composite) {
-        defaultWidth = dict.get('DW') || 1000;
+        defaultWidth = dict.has('DW') ? dict.get('DW') : 1000;
         widths = dict.get('W');
         if (widths) {
           for (i = 0, ii = widths.length; i < ii; i++) {
             start = xref.fetchIfRef(widths[i++]);
             code = xref.fetchIfRef(widths[i]);
             if (Array.isArray(code)) {
               for (j = 0, jj = code.length; j < jj; j++) {
                 glyphsWidths[start++] = xref.fetchIfRef(code[j]);
@@ -23697,18 +23725,18 @@ exports.getUnicodeForGlyph = getUnicodeF
 
 /***/ }),
 /* 17 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
-var pdfjsVersion = '1.9.562';
-var pdfjsBuild = '31a34335';
+var pdfjsVersion = '1.9.583';
+var pdfjsBuild = 'd7b37ae7';
 var pdfjsCoreWorker = __w_pdfjs_require__(18);
 exports.WorkerMessageHandler = pdfjsCoreWorker.WorkerMessageHandler;
 
 /***/ }),
 /* 18 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
@@ -24083,17 +24111,18 @@ var WorkerMessageHandler = {
         }, onFailure);
       }
       ensureNotTerminated();
       var evaluatorOptions = {
         forceDataSchema: data.disableCreateObjectURL,
         maxImageSize: data.maxImageSize === undefined ? -1 : data.maxImageSize,
         disableFontFace: data.disableFontFace,
         nativeImageDecoderSupport: data.nativeImageDecoderSupport,
-        ignoreErrors: data.ignoreErrors
+        ignoreErrors: data.ignoreErrors,
+        isEvalSupported: data.isEvalSupported
       };
       getPdfManager(data, evaluatorOptions).then(function (newPdfManager) {
         if (terminated) {
           newPdfManager.terminate();
           throw new Error('Worker was terminated');
         }
         pdfManager = newPdfManager;
         handler.send('PDFManagerReady', null);
@@ -27536,16 +27565,18 @@ var _stream = __w_pdfjs_require__(2);
 var _evaluator = __w_pdfjs_require__(13);
 
 var _annotation = __w_pdfjs_require__(37);
 
 var _crypto = __w_pdfjs_require__(12);
 
 var _parser = __w_pdfjs_require__(5);
 
+var _function = __w_pdfjs_require__(6);
+
 var Page = function PageClosure() {
   var DEFAULT_USER_UNIT = 1.0;
   var LETTER_SIZE_MEDIABOX = [0, 0, 612, 792];
   function isAnnotationRenderable(annotation, intent) {
     return intent === 'display' && annotation.viewable || intent === 'print' && annotation.printable;
   }
   function Page(pdfManager, xref, pageIndex, pageDict, ref, fontCache, builtInCMapCache) {
     this.pdfManager = pdfManager;
@@ -27943,16 +27974,18 @@ var PDFDocument = function PDFDocumentCl
     setup: function PDFDocument_setup(recoveryMode) {
       this.xref.parse(recoveryMode);
       var pageFactory = {
         createPage: (pageIndex, dict, ref, fontCache, builtInCMapCache) => {
           return new Page(this.pdfManager, this.xref, pageIndex, dict, ref, fontCache, builtInCMapCache);
         }
       };
       this.catalog = new _obj.Catalog(this.pdfManager, this.xref, pageFactory);
+      let evaluatorOptions = this.pdfManager.evaluatorOptions;
+      _function.PDFFunction.setIsEvalSupported(evaluatorOptions.isEvalSupported);
     },
     get numPages() {
       var linearization = this.linearization;
       var num = linearization ? linearization.numPages : this.catalog.numPages;
       return (0, _util.shadow)(this, 'numPages', num);
     },
     get documentInfo() {
       var docInfo = {
@@ -39517,16 +39550,18 @@ class AnnotationFactory {
       case 'Highlight':
         return new HighlightAnnotation(parameters);
       case 'Underline':
         return new UnderlineAnnotation(parameters);
       case 'Squiggly':
         return new SquigglyAnnotation(parameters);
       case 'StrikeOut':
         return new StrikeOutAnnotation(parameters);
+      case 'Stamp':
+        return new StampAnnotation(parameters);
       case 'FileAttachment':
         return new FileAttachmentAnnotation(parameters);
       default:
         if (!subtype) {
           (0, _util.warn)('Annotation is missing the required /Subtype.');
         } else {
           (0, _util.warn)('Unimplemented annotation type "' + subtype + '", ' + 'falling back to base annotation.');
         }
@@ -40043,16 +40078,23 @@ class SquigglyAnnotation extends Annotat
 }
 class StrikeOutAnnotation extends Annotation {
   constructor(parameters) {
     super(parameters);
     this.data.annotationType = _util.AnnotationType.STRIKEOUT;
     this._preparePopup(parameters.dict);
   }
 }
+class StampAnnotation extends Annotation {
+  constructor(parameters) {
+    super(parameters);
+    this.data.annotationType = _util.AnnotationType.STAMP;
+    this._preparePopup(parameters.dict);
+  }
+}
 class FileAttachmentAnnotation extends Annotation {
   constructor(parameters) {
     super(parameters);
     let file = new _obj.FileSpec(parameters.dict.get('FS'), parameters.xref);
     this.data.annotationType = _util.AnnotationType.FILEATTACHMENT;
     this.data.file = file.serializable;
     this._preparePopup(parameters.dict);
   }
--- a/browser/extensions/pdfjs/content/web/viewer.css
+++ b/browser/extensions/pdfjs/content/web/viewer.css
@@ -244,16 +244,17 @@
 
 .annotationLayer .highlightAnnotation,
 .annotationLayer .underlineAnnotation,
 .annotationLayer .squigglyAnnotation,
 .annotationLayer .strikeoutAnnotation,
 .annotationLayer .lineAnnotation svg line,
 .annotationLayer .squareAnnotation svg rect,
 .annotationLayer .circleAnnotation svg ellipse,
+.annotationLayer .stampAnnotation,
 .annotationLayer .fileAttachmentAnnotation {
   cursor: pointer;
 }
 
 .pdfViewer .canvasWrapper {
   overflow: hidden;
 }
 
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -2001,20 +2001,17 @@ function webViewerHashchange(evt) {
     PDFViewerApplication.pdfLinkService.setHash(hash);
   }
 }
 let webViewerFileInputChange;
 ;
 function webViewerPresentationMode() {
   PDFViewerApplication.requestPresentationMode();
 }
-function webViewerOpenFile() {
-  let openFileInputName = PDFViewerApplication.appConfig.openFileInputName;
-  document.getElementById(openFileInputName).click();
-}
+function webViewerOpenFile() {}
 function webViewerPrint() {
   window.print();
 }
 function webViewerDownload() {
   PDFViewerApplication.download();
 }
 function webViewerFirstPage() {
   if (PDFViewerApplication.pdfDocument) {
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -977,8 +977,14 @@ you can use these alternative items. Oth
 <!ENTITY pageActionButton.tooltip "Page actions">
 <!ENTITY pageAction.addToUrlbar.label "Add to Address Bar">
 <!ENTITY pageAction.removeFromUrlbar.label "Remove from Address Bar">
 
 <!ENTITY pageAction.sendTabToDevice.label "Send Tab to Device">
 <!ENTITY sendToDevice.syncNotReady.label "Syncing Devices…">
 
 <!ENTITY libraryButton.tooltip "View history, saved bookmarks, and more">
+
+<!-- LOCALIZATION NOTE: (accessibilityIndicator.tooltip): This is used to
+     display a tooltip for accessibility indicator in toolbar/tabbar. It is also
+     used as a textual label for the indicator used by assistive technology
+     users. -->
+<!ENTITY accessibilityIndicator.tooltip "Accessibility Features Enabled">
--- a/browser/modules/PingCentre.jsm
+++ b/browser/modules/PingCentre.jsm
@@ -109,22 +109,27 @@ class PingCentre {
   async sendPing(data) {
     let experiments = TelemetryEnvironment.getActiveExperiments();
     let experimentsString = this._createExperimentsString(experiments);
     if (!this.enabled) {
       return Promise.resolve();
     }
 
     let clientID = data.client_id || await this.telemetryClientId;
+    let locale = data.locale || Services.locale.getAppLocalesAsLangTags().pop();
     const payload = Object.assign({
+      locale,
       topic: this._topic,
       client_id: clientID,
-      shield_id: experimentsString,
+      version: AppConstants.MOZ_APP_VERSION,
       release_channel: AppConstants.MOZ_UPDATE_CHANNEL
     }, data);
+    if (experimentsString) {
+      payload.shield_id = experimentsString;
+    }
 
     if (this.logging) {
       // performance related pings cause a lot of logging, so we mute them
       if (data.action !== "activity_stream_performance") {
         Services.console.logStringMessage(`TELEMETRY PING: ${JSON.stringify(payload)}\n`);
       }
     }
 
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -712,22 +712,16 @@ html|span.ac-emphasize-text-url {
 }
 
 /* Customization mode */
 
 %include ../shared/customizableui/customizeMode.inc.css
 
 /* End customization mode */
 
-
-#main-window[privatebrowsingmode=temporary] #private-browsing-indicator {
-  background: url("chrome://browser/skin/privatebrowsing-mask.png") center no-repeat;
-  width: 40px;
-}
-
 %include ../shared/UITour.inc.css
 
 #UITourHighlight {
   /* Below are some fixes for people without an X compositor on Linux.
      This is why we can't have nice things: */
   /* Animations don't repaint properly without an X compositor. */
   animation-name: none !important;
   /* Opacity rounds to 0 or 1 on Linux without an X compositor, making the
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -17,17 +17,16 @@ browser.jar:
   skin/classic/browser/menuPanel-exit@2x.png
   skin/classic/browser/menuPanel-help.png
   skin/classic/browser/menuPanel-help@2x.png
   skin/classic/browser/monitor.png
   skin/classic/browser/monitor_16-10.png
 * skin/classic/browser/pageInfo.css
   skin/classic/browser/pageInfo.png
   skin/classic/browser/page-livemarks.png
-  skin/classic/browser/privatebrowsing-mask.png
   skin/classic/browser/searchbar.css
   skin/classic/browser/setDesktopBackground.css
   skin/classic/browser/slowStartup-16.png
   skin/classic/browser/webRTC-indicator.css  (../shared/webRTC-indicator.css)
 * skin/classic/browser/controlcenter/panel.css        (controlcenter/panel.css)
 * skin/classic/browser/customizableui/panelUI.css (customizableui/panelUI.css)
 * skin/classic/browser/downloads/allDownloadsViewOverlay.css   (downloads/allDownloadsViewOverlay.css)
 * skin/classic/browser/downloads/downloads.css        (downloads/downloads.css)
deleted file mode 100644
index 9eaf3aec7e71bd4d7f3e3d91b47fa3717a17a947..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -49,38 +49,45 @@
 
 #main-window {
   -moz-appearance: none;
   background-color: #eeeeee;
 }
 
 /** Begin titlebar **/
 
+#titlebar-content {
+  /* Ensure the the content part of the titlebar does not shrink. */
+  min-height: inherit;
+}
+
 #titlebar-buttonbox > .titlebar-button {
   display: none;
 }
 
 /* Making the toolbox position:relative (browser.inc.css) occludes titlebar indicators
  * if the toolbox has a background. Fix this by positioning the relevant elements, too: */
 #titlebar-secondary-buttonbox {
   position: relative;
   z-index: 1;
 }
 
+#titlebar-buttonbox-container {
+  -moz-box-align: center;
+}
+
 /* These would be margin-inline-start/end if it wasn't for the fact that OS X
  * doesn't reverse the order of the items in the titlebar in RTL mode. */
 .titlebar-placeholder[type="caption-buttons"],
 #titlebar-buttonbox {
   margin-left: 7px;
 }
 
-.titlebar-placeholder[type="fullscreen-button"],
-#titlebar-secondary-buttonbox {
-  margin-right: 7px;
-  margin-left: 7px;
+.titlebar-placeholder[type="fullscreen-button"] {
+  margin-right: 4px;
 }
 
 #main-window:not(:-moz-lwtheme) > #titlebar {
   -moz-appearance: -moz-window-titlebar;
 }
 
 #main-window:not([tabsintitlebar]) > #titlebar {
   -moz-appearance: -moz-window-titlebar;
@@ -1231,76 +1238,31 @@ html|*.addon-webext-perm-list {
 }
 
 /* Customization mode */
 
 %include ../shared/customizableui/customizeMode.inc.css
 
 /* End customization mode */
 
-.private-browsing-indicator {
-  background-image: url("chrome://browser/skin/privatebrowsing-mask.png");
-  background-repeat: no-repeat;
-  background-size: 100% auto;
-  width: 38px;
-  height: 28px;
-  /**
-   * The private browsing mask graphic has a 3px flare at the top. The distance
-   * we want between the mask and items on either side is 7px, so we use 4px,
-   * since the other 3px is accounted for by the empty space on either side.
-   */
-  margin-left: 4px;
-  margin-right: 4px;
-}
+/* Private browsing and accessibility indicators */
 
-#titlebar-secondary-buttonbox > .private-browsing-indicator {
-  position: relative;
-}
-
-#main-window[privatebrowsingmode=temporary]:not([tabsintitlebar]) > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > .private-browsing-indicator {
-  background-image: url("chrome://browser/skin/privatebrowsing-mask-short.png");
-  height: 20px;
-}
-
-#main-window:not([privatebrowsingmode=temporary]) .private-browsing-indicator,
-#main-window[privatebrowsingmode=temporary][inFullscreen] > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > .private-browsing-indicator,
-#main-window[privatebrowsingmode=temporary]:not([inFullscreen]) > #tab-view-deck > #browser-panel > #navigator-toolbox > #TabsToolbar > .private-browsing-indicator {
+:root[accessibilitymode][tabsintitlebar]:not([inFullscreen]) > #tab-view-deck > #browser-panel > #navigator-toolbox > #TabsToolbar > .accessibility-indicator,
+:root[privatebrowsingmode=temporary][tabsintitlebar]:not([inFullscreen]) > #tab-view-deck > #browser-panel > #navigator-toolbox > #TabsToolbar > .private-browsing-indicator,
+:root[accessibilitymode]:not([tabsintitlebar]) > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > .accessibility-indicator,
+:root[privatebrowsingmode=temporary]:not([tabsintitlebar]) > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > .private-browsing-indicator {
   display: none;
 }
 
-@media (min-resolution: 2dppx) {
-  .private-browsing-indicator {
-    background-image: url("chrome://browser/skin/privatebrowsing-mask@2x.png");
-  }
-  #main-window[privatebrowsingmode=temporary]:not([tabsintitlebar]) > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > .private-browsing-indicator {
-    background-image: url("chrome://browser/skin/privatebrowsing-mask-short@2x.png");
-  }
+#TabsToolbar > .private-browsing-indicator:-moz-locale-dir(rtl),
+#TabsToolbar > .accessibility-indicator:-moz-locale-dir(rtl) {
+  -moz-box-ordinal-group: 0;
 }
 
-#TabsToolbar > .private-browsing-indicator {
-  /* We offset by 38px for mask graphic, plus 4px to account for the
-   * margin-left, which sums to 42px.
-   */
-  margin-right: -42px;
-}
-
-#main-window[privatebrowsingmode=temporary] .titlebar-placeholder[type="fullscreen-button"],
-#main-window[privatebrowsingmode=temporary] > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > #titlebar-fullscreen-button {
-  margin-left: 0px;
-}
-
-#main-window[privatebrowsingmode=temporary][inFullscreen] .titlebar-placeholder[type="fullscreen-button"] {
-  /* Override display:none for .titlebar-placeholder in fullscreen so we can have consistent
-     position and padding for the private browsing indicator. */
-  display: -moz-box;
-}
-
-#TabsToolbar > .private-browsing-indicator:-moz-locale-dir(rtl) {
-  -moz-box-ordinal-group: 0;
-}
+/* End private browsing and accessibility indicators */
 
 %include ../shared/UITour.inc.css
 
 #UITourTooltipDescription {
   font-size: 1.18rem;
   line-height: 2rem;
 }
 
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -22,20 +22,16 @@ browser.jar:
   skin/classic/browser/panel-expander-closed.png
   skin/classic/browser/panel-expander-closed@2x.png
   skin/classic/browser/panel-expander-open.png
   skin/classic/browser/panel-expander-open@2x.png
   skin/classic/browser/panel-plus-sign.png
   skin/classic/browser/page-livemarks.png
   skin/classic/browser/page-livemarks@2x.png
   skin/classic/browser/pageInfo.css
-  skin/classic/browser/privatebrowsing-mask.png
-  skin/classic/browser/privatebrowsing-mask@2x.png
-  skin/classic/browser/privatebrowsing-mask-short.png
-  skin/classic/browser/privatebrowsing-mask-short@2x.png
   skin/classic/browser/searchbar.css
   skin/classic/browser/slowStartup-16.png
   skin/classic/browser/toolbarbutton-dropmarker.png
   skin/classic/browser/toolbarbutton-dropmarker@2x.png
   skin/classic/browser/webRTC-indicator.css
 * skin/classic/browser/controlcenter/panel.css        (controlcenter/panel.css)
 * skin/classic/browser/customizableui/panelUI.css    (customizableui/panelUI.css)
 * skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css)
deleted file mode 100644
index 92f60e29f9fd71ed62101b7f7f22ea37423387a0..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index ec1cf7452ef6e0251dad9113e8119793bfed4b6f..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 586d3d9f0a4bda4175ea75f7c3725fa607875e7c..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index efb6d9b8070f646a39b04a40ee556386fb5291f6..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/themes/shared/browser.inc.css
+++ b/browser/themes/shared/browser.inc.css
@@ -74,8 +74,52 @@
 #library-animatable-box {
   display: none;
 }
 
 #library-animatable-box[animate] {
   display: -moz-box;
 }
 
+/* Private browsing and accessibility indicators */
+
+.accessibility-indicator,
+.private-browsing-indicator {
+  background-repeat: no-repeat;
+  background-size: 100% auto;
+  background-position: center;
+  width: 24px;
+  height: 24px;
+  margin-left: 8px;
+  margin-right: 8px;
+  position: relative;
+  /* Need to ensure this gets positioned on top of the position:relative #navigator-toolbox
+   * in case the dark/light themes give that item a background. */
+  z-index: 1;
+}
+
+.accessibility-indicator {
+  background-image: url("chrome://browser/skin/accessibility.svg");
+  -moz-user-focus: normal;
+  /* Clear default button styling */
+  -moz-appearance: none;
+  margin-top: unset;
+  margin-bottom: unset;
+  min-width: unset;
+  color: unset;
+  text-shadow: unset;
+}
+
+.accessibility-indicator:-moz-any(:hover, :active, :focus, :-moz-focusring) {
+  background-image: url("chrome://browser/skin/accessibility-active.svg");
+  outline: 0;
+}
+
+.private-browsing-indicator {
+  background-image: url("chrome://browser/skin/private-browsing.svg");
+}
+
+:root:not([accessibilitymode]) .accessibility-indicator,
+:root:not([privatebrowsingmode=temporary]) .private-browsing-indicator {
+  display: none;
+}
+
+/* End private browsing and accessibility indicators */
--- a/browser/themes/shared/compacttheme.inc.css
+++ b/browser/themes/shared/compacttheme.inc.css
@@ -2,16 +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/.
 
 /* compacttheme.css is loaded in browser.xul after browser.css when it is
    preffed on.  The bulk of the styling is here in the shared file, but
    there are overrides for each platform in their compacttheme.css files. */
 
 :root:-moz-lwtheme {
+  text-shadow: none;
+
   --toolbar-bgcolor: var(--chrome-secondary-background-color);
   --toolbar-gbimage: none;
   --toolbar-non-lwt-bgcolor: var(--toolbar-bgcolor);
   --toolbar-non-lwt-textcolor: var(--chrome-color);
   --toolbar-non-lwt-bgimage: none;
 
   --toolbarbutton-icon-fill-opacity: .7;
 
@@ -82,25 +84,16 @@ toolbar[brighttext] .toolbarbutton-1 {
 }
 
 /* Default findbar text color doesn't look good - Bug 1125677 */
 .browserContainer > findbar .findbar-find-status,
 .browserContainer > findbar .found-matches {
   color: inherit;
 }
 
-.browserContainer > findbar .findbar-button,
-#PlacesToolbar toolbarbutton.bookmark-item {
-  text-shadow: none;
-}
-
-#TabsToolbar {
-  text-shadow: none !important;
-}
-
 /* URL bar and search bar*/
 #urlbar:not([focused="true"]),
 .searchbar-textbox:not([focused="true"]) {
   border-color: var(--chrome-nav-bar-controls-border-color);
 }
 
 #urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity > #identity-icon-labels:-moz-lwtheme-brighttext {
   color: #30e60b;
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -1617,18 +1617,18 @@ toolbarpaletteitem[place="palette"] > .t
 #bookmarks-menu-button[cui-areatype="menu-panel"] > .toolbarbutton-menu-dropmarker,
 #bookmarks-menu-button[overflowedItem] > .toolbarbutton-menu-dropmarker,
 toolbarpaletteitem[place="palette"] > .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker,
 #bookmarks-menu-button[cui-areatype="menu-panel"] > .toolbarbutton-menubutton-dropmarker {
   display: none;
 }
 
 #search-container[cui-areatype="menu-panel"] {
-  margin-top: 6px;
-  margin-bottom: 6px;
+  padding-top: 6px;
+  padding-bottom: 6px;
 }
 
 toolbarpaletteitem[place="palette"] > #search-container {
   min-width: 7em;
   width: 7em;
   min-height: 37px;
 }
 
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/icons/accessibility-active.svg
@@ -0,0 +1,10 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px">
+<path fill="#008EA4" fill-opacity="0.9886" d="M12,24L12,24C5.4,24,0,18.6,0,12l0,0C0,5.4,5.4,0,12,0l0,0c6.6,0,12,5.4,12,12l0,0  C24,18.6,18.6,24,12,24z"/>
+<g>
+	<circle fill="#FFFFFF" cx="12" cy="6" r="2"/>
+	<path fill="#FFFFFF" d="M18.1,8.5h-3.6l0,0h-5l0,0H6c-0.6,0-1,0.4-1,1s0.4,1,1,1h3.5v0.6l0,0v7.8c0,0.6,0.4,1.1,1,1.1s1-0.5,1-1.1   v-4.1h1v4.1c0,0.6,0.4,1.1,1,1.1s1-0.5,1-1.1v-4.1l0,0v-4.5H18c0.6,0,1-0.4,1-1C19,8.8,18.6,8.5,18.1,8.5z"/>
+</g>
+</svg>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/icons/accessibility.svg
@@ -0,0 +1,11 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px">
+<path fill="#00C8D7" fill-opacity="0.9886" d="M12,24L12,24C5.4,24,0,18.6,0,12l0,0C0,5.4,5.4,0,12,0l0,0c6.6,0,12,5.4,12,12l0,0  C24,18.6,18.6,24,12,24z"/>
+<g>
+	<circle fill="#FFFFFF" cx="12" cy="6" r="2"/>
+	<path fill="#FFFFFF" d="M18.1,8.5h-3.6l0,0h-5l0,0H6c-0.6,0-1,0.4-1,1c0,0.6,0.4,1,1,1h3.5v0.6l0,0v7.8c0,0.6,0.4,1.1,1,1.1s1-0.5,1-1.1
+  v-4.1h1v4.1c0,0.6,0.4,1.1,1,1.1s1-0.5,1-1.1v-4.1l0,0v-4.5H18c0.6,0,1-0.4,1-1C19,8.8,18.6,8.5,18.1,8.5z"/>
+</g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/icons/private-browsing.svg
@@ -0,0 +1,11 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px">
+<path fill="#8000D7" d="M12,24L12,24C5.4,24,0,18.6,0,12l0,0C0,5.4,5.4,0,12,0l0,0c6.6,0,12,5.4,12,12l0,0
+	C24,18.6,18.6,24,12,24z"/>
+<path fill="#FFFFFF" d="M15.4,11c-1-0.1-1.9,0.5-2.1,1.5c0,0.3,1.2,0.7,2.3,0.7s2.1-0.7,2.1-0.9C17.7,12.1,17,11.1,15.4,11
+	L15.4,11z M8.6,11c-1.6,0.1-2.3,1-2.3,1.3c0,0.2,1.1,0.9,2.1,0.9c1,0,2.3-0.4,2.3-0.7C10.5,11.5,9.5,10.9,8.6,11L8.6,11z M16.4,16.5
+	c-1.7,0-2.8-2-4.4-2c-1.6,0-2.8,2-4.4,2c-2.1,0-3.6-1.9-3.6-5.3c0-2.1,0.6-2.7,3.3-2.7s3.4,1.1,4.7,1.1s2.1-1.1,4.7-1.1
+	S20,9.2,20,11.2C20,14.6,18.5,16.5,16.4,16.5L16.4,16.5z"/>
+</svg>
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -109,16 +109,18 @@
   skin/classic/browser/preferences/in-content/sync-devices.svg (../shared/incontentprefs/sync-devices.svg)
   skin/classic/browser/preferences/in-content/sync.svg         (../shared/incontentprefs/sync.svg)
 * skin/classic/browser/preferences/in-content/containers.css   (../shared/incontentprefs/containers.css)
 * skin/classic/browser/preferences/containers.css              (../shared/preferences/containers.css)
   skin/classic/browser/fxa/default-avatar.svg                  (../shared/fxa/default-avatar.svg)
   skin/classic/browser/fxa/sync-illustration.svg               (../shared/fxa/sync-illustration.svg)
 
 
+  skin/classic/browser/accessibility.svg              (../shared/icons/accessibility.svg)
+  skin/classic/browser/accessibility-active.svg       (../shared/icons/accessibility-active.svg)
   skin/classic/browser/arrow-left.svg                 (../shared/icons/arrow-left.svg)
   skin/classic/browser/back.svg                       (../shared/icons/back.svg)
   skin/classic/browser/back-12.svg                    (../shared/icons/back-12.svg)
   skin/classic/browser/bookmark.svg                   (../shared/icons/bookmark.svg)
   skin/classic/browser/bookmark-animation.svg         (../shared/icons/bookmark-animation.svg)
   skin/classic/browser/bookmark-hollow.svg            (../shared/icons/bookmark-hollow.svg)
   skin/classic/browser/bookmark-star-on-tray.svg      (../shared/icons/bookmark-star-on-tray.svg)
   skin/classic/browser/characterEncoding.svg          (../shared/icons/characterEncoding.svg)
@@ -150,16 +152,17 @@
   skin/classic/browser/mail.svg                       (../shared/icons/mail.svg)
   skin/classic/browser/menu.svg                       (../shared/icons/menu.svg)
   skin/classic/browser/menu-badged.svg                (../shared/icons/menu-badged.svg)
   skin/classic/browser/new-tab.svg                    (../shared/icons/new-tab.svg)
   skin/classic/browser/new-window.svg                 (../shared/icons/new-window.svg)
   skin/classic/browser/open.svg                       (../shared/icons/open.svg)
   skin/classic/browser/page-action.svg                (../shared/icons/page-action.svg)
   skin/classic/browser/print.svg                      (../shared/icons/print.svg)
+  skin/classic/browser/private-browsing.svg           (../shared/icons/private-browsing.svg)
   skin/classic/browser/privateBrowsing.svg            (../shared/icons/privateBrowsing.svg)
   skin/classic/browser/restore-session.svg            (../shared/icons/restore-session.svg)
   skin/classic/browser/quit.svg                       (../shared/icons/quit.svg)
   skin/classic/browser/reload.svg                     (../shared/icons/reload.svg)
   skin/classic/browser/reload-to-stop.svg             (../shared/icons/reload-to-stop.svg)
   skin/classic/browser/save.svg                       (../shared/icons/save.svg)
   skin/classic/browser/settings.svg                   (../shared/icons/settings.svg)
   skin/classic/browser/sidebars.svg                   (../shared/icons/sidebars.svg)
--- a/browser/themes/shared/toolbarbutton-icons.inc.css
+++ b/browser/themes/shared/toolbarbutton-icons.inc.css
@@ -18,17 +18,18 @@ toolbar[brighttext] {
   fill-opacity: var(--toolbarbutton-icon-fill-opacity);
 }
 
 #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,
 #PlacesChevron:-moz-locale-dir(rtl) > .toolbarbutton-icon,
-#panic-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+#panic-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
+#PanelUI-menu-button:-moz-locale-dir(rtl) > .toolbarbutton-badge-stack > .toolbarbutton-icon {
   transform: scaleX(-1);
 }
 
 #back-button {
   list-style-image: url("chrome://browser/skin/back.svg");
 }
 
 #forward-button {
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -277,16 +277,27 @@
  * window state. Otherwise, elements in the navigator-toolbox, like the menubar,
  * can swallow those events. It will also place the buttons above the fog on
  * Windows 7 with Aero Glass.
  */
 #titlebar-buttonbox {
   z-index: 1;
 }
 
+#titlebar-buttonbox-container {
+  -moz-box-align: center;
+}
+
+@media (-moz-os-version: windows-win7) {
+  /* Preserve window control buttons position at the top of the button box. */
+  #titlebar-buttonbox-container {
+    -moz-box-align: start;
+  }
+}
+
 .titlebar-placeholder[type="caption-buttons"] {
   margin-left: 22px; /* space needed for Aero Snap */
 }
 
 /* titlebar command buttons */
 
 #titlebar-min {
   -moz-appearance: -moz-window-button-minimize;
@@ -1110,102 +1121,49 @@ notification[value="translation"] {
  * paint, so this hack is how we sidestep that performance bottleneck.
  */
 #main-window:-moz-any([customize-entering],[customize-exiting]) label {
   transform: perspective(0.01px);
 }
 
 /* End customization mode */
 
-/* Private browsing indicators */
-
-/**
- * Currently, we have two places where we put private browsing indicators on
- * Windows. When tabsintitlebar is enabled, we draw the indicator in the titlebar.
- * When tabsintitlebar is disabled, we draw the indicator at the end of the
- * tabstrip. The titlebar indicator is the fiddliest of the bunch, since we
- * want the bottom border of the image to line up with the bottom of the window
- * caption buttons. That's why there's so much special-casing going on in here.
- */
-.private-browsing-indicator {
-  display: none;
-  pointer-events: none;
-}
-
-#private-browsing-indicator-titlebar {
-  display: block;
-  position: absolute;
-  /* Need to ensure this gets positioned on top of the position:relative #navigator-toolbox
-   * in case the dark/light themes give that item a background. */
-  z-index: 1;
-}
-
-#main-window[privatebrowsingmode=temporary][tabsintitlebar] #private-browsing-indicator-titlebar > .private-browsing-indicator {
-  display: block;
-}
+/* Private browsing and accessibility indicators */
 
-#main-window[privatebrowsingmode=temporary]:-moz-any([inFullscreen],:not([tabsintitlebar])) #TabsToolbar > .private-browsing-indicator {
-  display: -moz-box;
-}
-
-#TabsToolbar > .private-browsing-indicator {
-  background: url("chrome://browser/skin/privatebrowsing-mask-tabstrip.png") no-repeat center -3px;
-  margin-inline-start: 4px;
-  width: 48px;
-}
+@media (-moz-os-version: windows-win7) {
+  /* Making sure that indicators take up all available vertical space. */
+  :root[tabsintitlebar]:not([inFullscreen]) .private-browsing-indicator,
+  :root[tabsintitlebar]:not([inFullscreen]) .accessibility-indicator {
+    height: var(--tab-min-height);
+  }
 
-/* Bug 1008183: We're intentionally using the titlebar asset here for fullscreen
- * mode, since the tabstrip "mimics" the titlebar in that case with its own
- * min/max/close window buttons.
- */
-#private-browsing-indicator-titlebar > .private-browsing-indicator,
-#main-window[inFullscreen] #TabsToolbar > .private-browsing-indicator {
-  background: url("chrome://browser/skin/privatebrowsing-mask-titlebar.png") no-repeat center 0px;
-  margin-inline-end: 4px;
-  width: 40px;
-  height: 20px;
-  position: relative;
-}
-
-@media (-moz-windows-classic) {
-  /**
-   * We have to use top instead of background-position in this case, otherwise
-   * the bottom of the indicator would get cut off by the bounds of the
-   * private-browsing-indicator element.
-   */
-  #main-window[sizemode="normal"] > #titlebar > #titlebar-content > #titlebar-buttonbox-container > #private-browsing-indicator-titlebar > .private-browsing-indicator {
-    top: 4px;
+  :root[tabsintitlebar][sizemode="normal"]:not([inFullscreen]) .private-browsing-indicator,
+  :root[tabsintitlebar][sizemode="normal"]:not([inFullscreen]) .accessibility-indicator {
+    height: calc(var(--tab-min-height) + 4px);
   }
 }
 
-@media (-moz-os-version: windows-win7) {
-  @media (-moz-windows-glass) {
-    #main-window[sizemode="normal"] > #titlebar > #titlebar-content > #titlebar-buttonbox-container > #private-browsing-indicator-titlebar > .private-browsing-indicator {
-      top: 1px;
-    }
-    #main-window[sizemode="maximized"] > #titlebar > #titlebar-content > #titlebar-buttonbox-container > #private-browsing-indicator-titlebar > .private-browsing-indicator {
-      top: -1px;
-    }
-  }
-
-  /**
-   * This next block targets Aero Basic, which has different positioning for the
-   * window caption buttons, and therefore needs to be special-cased.
-   */
-  @media (-moz-windows-default-theme) {
-    @media (-moz-windows-compositor: 0) {
-      #main-window[sizemode="normal"] > #titlebar > #titlebar-content > #titlebar-buttonbox-container > #private-browsing-indicator-titlebar > .private-browsing-indicator {
-        background-image: url("chrome://browser/skin/privatebrowsing-mask-titlebar-win7-tall.png");
-        height: 28px;
-      }
-    }
-  }
+:root:-moz-any([tabsintitlebar], [inFullscreen]):not([privatebrowsingmode=temporary]) .accessibility-indicator,
+:root:-moz-any([tabsintitlebar], [inFullscreen]) .private-browsing-indicator {
+  margin-inline-end: 12px;
 }
 
-/* End private browsing indicators */
+:root:not([accessibilitymode]) .private-browsing-indicator,
+.accessibility-indicator {
+  margin-inline-start: 12px;
+}
+
+:root[accessibilitymode][tabsintitlebar]:not([inFullscreen]) > #tab-view-deck > #browser-panel > #navigator-toolbox > #TabsToolbar > .accessibility-indicator,
+:root[privatebrowsingmode=temporary][tabsintitlebar]:not([inFullscreen]) > #tab-view-deck > #browser-panel > #navigator-toolbox > #TabsToolbar > .private-browsing-indicator,
+:root[accessibilitymode]:not([tabsintitlebar]) > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > .accessibility-indicator,
+:root[privatebrowsingmode=temporary]:not([tabsintitlebar]) > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > .private-browsing-indicator {
+  display: none;
+}
+
+/* End private browsing and accessibility indicators */
 
 %include ../shared/UITour.inc.css
 
 #UITourTooltipButtons {
   /**
    * Override the --arrowpanel-padding so the background extends
    * to the sides and bottom of the panel.
    */
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -18,21 +18,16 @@ browser.jar:
   skin/classic/browser/menuPanel-exit.png
   skin/classic/browser/menuPanel-exit@2x.png
   skin/classic/browser/menuPanel-help.png
   skin/classic/browser/menuPanel-help@2x.png
   skin/classic/browser/monitor.png
   skin/classic/browser/monitor_16-10.png
   skin/classic/browser/pageInfo.css
   skin/classic/browser/pageInfo.png
-  skin/classic/browser/privatebrowsing-mask-tabstrip.png
-  skin/classic/browser/privatebrowsing-mask-tabstrip-win7.png
-  skin/classic/browser/privatebrowsing-mask-titlebar.png
-  skin/classic/browser/privatebrowsing-mask-titlebar-win7.png
-  skin/classic/browser/privatebrowsing-mask-titlebar-win7-tall.png
   skin/classic/browser/searchbar.css
   skin/classic/browser/setDesktopBackground.css
   skin/classic/browser/slowStartup-16.png
   skin/classic/browser/sync-desktopIcon.svg  (../shared/sync-desktopIcon.svg)
   skin/classic/browser/sync-mobileIcon.svg  (../shared/sync-mobileIcon.svg)
   skin/classic/browser/toolbarbutton-dropdown-arrow-win7.png
   skin/classic/browser/webRTC-indicator.css  (../shared/webRTC-indicator.css)
 * skin/classic/browser/controlcenter/panel.css                 (controlcenter/panel.css)
@@ -86,11 +81,9 @@ browser.jar:
 
 [extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar:
 % override chrome://browser/skin/page-livemarks.png                   chrome://browser/skin/feeds/feedIcon16.png
 % override chrome://browser/skin/feeds/audioFeedIcon.png              chrome://browser/skin/feeds/feedIcon.png
 % override chrome://browser/skin/feeds/audioFeedIcon16.png            chrome://browser/skin/feeds/feedIcon16.png
 % override chrome://browser/skin/feeds/videoFeedIcon.png              chrome://browser/skin/feeds/feedIcon.png
 % override chrome://browser/skin/feeds/videoFeedIcon16.png            chrome://browser/skin/feeds/feedIcon16.png
 
-% override chrome://browser/skin/privatebrowsing-mask-tabstrip.png    chrome://browser/skin/privatebrowsing-mask-tabstrip-win7.png  os=WINNT osversion<=6.1
-% override chrome://browser/skin/privatebrowsing-mask-titlebar.png    chrome://browser/skin/privatebrowsing-mask-titlebar-win7.png  os=WINNT osversion<=6.1
 % override chrome://browser/skin/toolbarbutton-dropdown-arrow.png     chrome://browser/skin/toolbarbutton-dropdown-arrow-win7.png   os=WINNT osversion<=6.1
deleted file mode 100644
index bd5d46a76a8def4a3d8db31aed36126f5b7b062b..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index e2eba8b89e4fd2b1b9d33a5f93f6c1bbfdaf9889..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 4a723c54e2316e978062be38a7be8eaa1a0d11f2..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 835912b534b2d16ac01a52c44ff6f3548ac0032c..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 111dc7d04167c3767538fb6b64e34717fb259ef0..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/build/autoconf/android.m4
+++ b/build/autoconf/android.m4
@@ -1,21 +1,15 @@
 dnl This Source Code Form is subject to the terms of the Mozilla Public
 dnl License, v. 2.0. If a copy of the MPL was not distributed with this
 dnl file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 AC_DEFUN([MOZ_ANDROID_NDK],
 [
 
-MOZ_ARG_WITH_STRING(android-cxx-stl,
-[  --with-android-cxx-stl=VALUE
-                          use the specified C++ STL (libstdc++, libc++)],
-    android_cxx_stl=$withval,
-    android_cxx_stl=libc++)
-
 case "$target" in
 *-android*|*-linuxandroid*)
     dnl $android_platform will be set for us by Python configure.
     CPPFLAGS="-idirafter $android_platform/usr/include $CPPFLAGS"
     CFLAGS="-fno-short-enums -fno-exceptions $CFLAGS"
     CXXFLAGS="-fno-short-enums -fno-exceptions $CXXFLAGS"
     ASFLAGS="-idirafter $android_platform/usr/include -DANDROID $ASFLAGS"
 
@@ -59,92 +53,63 @@ if test "$OS_TARGET" = "Android"; then
 fi
 ])
 
 AC_DEFUN([MOZ_ANDROID_STLPORT],
 [
 
 if test "$OS_TARGET" = "Android"; then
     if test -z "$STLPORT_CPPFLAGS$STLPORT_LIBS"; then
-        case "$android_cxx_stl" in
-        libstdc++)
-            # android-ndk-r8b and later
-            ndk_base="$android_ndk/sources/cxx-stl/gnu-libstdc++/$android_gnu_compiler_version"
-            ndk_libs_include="$ndk_base/libs/$ANDROID_CPU_ARCH"
-            # NDK r12 removed the arm/thumb library split and just made
-            # everything thumb by default.  Attempt to compensate.
-            if test "$MOZ_THUMB2" = 1 -a -d "$ndk_libs_include/thumb"; then
-                ndk_libs="$ndk_libs_include/thumb"
-            else
-                ndk_libs="$ndk_libs_include"
-            fi
-            ndk_include="$ndk_base/include"
+        # android-ndk-r8b and later
+        ndk_base="$android_ndk/sources/cxx-stl"
+        cxx_base="$ndk_base/llvm-libc++"
+        cxx_libs="$cxx_base/libs/$ANDROID_CPU_ARCH"
+        # NDK r12 removed the arm/thumb library split and just made
+        # everything thumb by default.  Attempt to compensate.
+        if test "$MOZ_THUMB2" = 1 -a -d "$cxx_libs/thumb"; then
+            cxx_libs="$cxx_libs/thumb"
+        fi
+        cxx_include="$cxx_base/libcxx/include"
+        cxxabi_base="$ndk_base/llvm-libc++abi"
+        cxxabi_include="$cxxabi_base/libcxxabi/include"
 
-            if ! test -e "$ndk_libs/libgnustl_static.a"; then
-                AC_MSG_ERROR([Couldn't find path to gnu-libstdc++ in the android ndk])
-            fi
-
-            STLPORT_LIBS="-L$ndk_libs -lgnustl_static"
-            STLPORT_CPPFLAGS="-I$ndk_include -I$ndk_include/backward -I$ndk_libs_include/include"
-            ;;
-        libc++)
-            # android-ndk-r8b and later
-            ndk_base="$android_ndk/sources/cxx-stl"
-            cxx_base="$ndk_base/llvm-libc++"
-            cxx_libs="$cxx_base/libs/$ANDROID_CPU_ARCH"
-            # NDK r12 removed the arm/thumb library split and just made
-            # everything thumb by default.  Attempt to compensate.
-            if test "$MOZ_THUMB2" = 1 -a -d "$cxx_libs/thumb"; then
-                cxx_libs="$cxx_libs/thumb"
-            fi
-            cxx_include="$cxx_base/libcxx/include"
-            cxxabi_base="$ndk_base/llvm-libc++abi"
-            cxxabi_include="$cxxabi_base/libcxxabi/include"
+        if ! test -e "$cxx_libs/libc++_static.a"; then
+            AC_MSG_ERROR([Couldn't find path to llvm-libc++ in the android ndk])
+        fi
 
-            if ! test -e "$cxx_libs/libc++_static.a"; then
-                AC_MSG_ERROR([Couldn't find path to llvm-libc++ in the android ndk])
-            fi
-
+        if ! test -e "$cxx_include"; then
+            # NDK r13 removes the inner "libcxx" directory.
+            cxx_include="$cxx_base/include"
             if ! test -e "$cxx_include"; then
-                # NDK r13 removes the inner "libcxx" directory.
-                cxx_include="$cxx_base/include"
-                if ! test -e "$cxx_include"; then
-                    AC_MSG_ERROR([Couldn't find path to libc++ includes in the android ndk])
-                fi
+                AC_MSG_ERROR([Couldn't find path to libc++ includes in the android ndk])
             fi
+        fi
 
+        if ! test -e "$cxxabi_include"; then
+            # NDK r13 removes the inner "libcxxabi" directory.
+            cxxabi_include="$cxxabi_base/include"
             if ! test -e "$cxxabi_include"; then
-                # NDK r13 removes the inner "libcxxabi" directory.
-                cxxabi_include="$cxxabi_base/include"
-                if ! test -e "$cxxabi_include"; then
-                    AC_MSG_ERROR([Couldn't find path to libc++abi includes in the android ndk])
-                fi
+                AC_MSG_ERROR([Couldn't find path to libc++abi includes in the android ndk])
             fi
+        fi
 
-            STLPORT_LIBS="-L$cxx_libs -lc++_static"
-            # NDK r12 split the libc++ runtime libraries into pieces.
-            for lib in c++abi unwind android_support; do
-                if test -e "$cxx_libs/lib${lib}.a"; then
-                     STLPORT_LIBS="$STLPORT_LIBS -l${lib}"
-                fi
-            done
-            # Add android/support/include/ for prototyping long double math
-            # functions, locale-specific C library functions, multibyte support,
-            # etc.
-            STLPORT_CPPFLAGS="-I$cxx_include -I$android_ndk/sources/android/support/include -I$cxxabi_include"
-            ;;
-        *)
-            AC_MSG_ERROR([Bad value for --enable-android-cxx-stl])
-            ;;
-        esac
+        STLPORT_LIBS="-L$cxx_libs -lc++_static"
+        # NDK r12 split the libc++ runtime libraries into pieces.
+        for lib in c++abi unwind android_support; do
+            if test -e "$cxx_libs/lib${lib}.a"; then
+                 STLPORT_LIBS="$STLPORT_LIBS -l${lib}"
+            fi
+        done
+        # Add android/support/include/ for prototyping long double math
+        # functions, locale-specific C library functions, multibyte support,
+        # etc.
+        STLPORT_CPPFLAGS="-I$cxx_include -I$android_ndk/sources/android/support/include -I$cxxabi_include"
     fi
     CXXFLAGS="$CXXFLAGS $STLPORT_CPPFLAGS"
 fi
-MOZ_ANDROID_CXX_STL=$android_cxx_stl
-AC_SUBST([MOZ_ANDROID_CXX_STL])
 AC_SUBST([STLPORT_LIBS])
 
 ])
 
 
 AC_DEFUN([concat],[$1$2$3$4])
 
 dnl Find a component of an AAR.
--- a/build/docs/mozinfo.rst
+++ b/build/docs/mozinfo.rst
@@ -155,22 +155,8 @@ toolkit
    ``MOZ_WIDGET_TOOLKIT`` ``config.status`` variable.
 
    Always defined.
 
 topsrcdir
    The path to the source directory the build came from.
 
    Always defined.
-
-wave
-   Whether Wave audio support is enabled.
-
-   Values are ``true`` and ``false``.
-
-   Always defined.
-
-webm
-   Whether WebM support is enabled.
-
-   Values are ``true`` and ``false``.
-
-   Always defined.
--- a/build/gecko_templates.mozbuild
+++ b/build/gecko_templates.mozbuild
@@ -53,21 +53,18 @@ def GeckoBinary(linkage='dependent', msv
     elif linkage != None:
         error('`linkage` must be "dependent", "standalone" or None')
 
     if mozglue:
         LDFLAGS += CONFIG['MOZ_GLUE_WRAP_LDFLAGS']
         if mozglue == 'program':
             USE_LIBS += ['mozglue']
             DEFINES['MOZ_HAS_MOZGLUE'] = True
-            if CONFIG['MOZ_GLUE_IN_PROGRAM']:
-                if CONFIG['GNU_CC']:
-                    LDFLAGS += ['-rdynamic']
-                if CONFIG['MOZ_MEMORY']:
-                    USE_LIBS += ['memory']
+            if CONFIG['MOZ_GLUE_IN_PROGRAM'] and CONFIG['GNU_CC']:
+                LDFLAGS += ['-rdynamic']
         elif mozglue == 'library':
             LIBRARY_DEFINES['MOZ_HAS_MOZGLUE'] = True
             if not CONFIG['MOZ_GLUE_IN_PROGRAM']:
                 USE_LIBS += ['mozglue']
         else:
             error('`mozglue` must be "program" or "library"')
 
     if not CONFIG['JS_STANDALONE']:
--- a/build/moz.configure/android-ndk.configure
+++ b/build/moz.configure/android-ndk.configure
@@ -6,19 +6,16 @@
 
 
 js_option('--with-android-ndk', nargs=1,
           help='location where the Android NDK can be found')
 
 js_option('--with-android-toolchain', nargs=1,
           help='location of the Android toolchain')
 
-js_option('--with-android-gnu-compiler-version', nargs=1,
-          help='GNU compiler version to use')
-
 @depends(target)
 def min_android_version(target):
     if target.cpu in ['aarch64', 'x86_64', 'mips64']:
         # 64-bit support was added in API 21.
         return '21'
     return '9'
 
 js_option('--with-android-version',
@@ -136,55 +133,50 @@ add_old_configure_assignment('android_pl
 
 @depends(android_platform)
 def extra_toolchain_flags(platform_dir):
     if not platform_dir:
         return []
     return ['-idirafter',
             os.path.join(platform_dir, 'usr', 'include')]
 
-@depends(target, host, ndk, '--with-android-toolchain',
-         '--with-android-gnu-compiler-version')
+@depends(target, host, ndk, '--with-android-toolchain')
 @checking('for the Android toolchain directory', lambda x: x or 'not found')
 @imports(_from='os.path', _import='isdir')
 @imports(_from='mozbuild.shellutil', _import='quote')
-def android_toolchain(target, host, ndk, toolchain, gnu_compiler_version):
+def android_toolchain(target, host, ndk, toolchain):
     if not ndk:
         return
     if toolchain:
         return toolchain[0]
     else:
         if target.cpu == 'arm' and target.endianness == 'little':
             target_base = 'arm-linux-androideabi'
         elif target.cpu == 'x86':
             target_base = 'x86'
         elif target.cpu == 'mips32' and target.endianness == 'little':
             target_base = 'mipsel-linux-android'
         elif target.cpu == 'aarch64' and target.endianness == 'little':
             target_base = 'aarch64-linux-android'
         else:
             die('Target cpu is not supported.')
 
-        toolchain_format = '%s/toolchains/%s-%s/prebuilt/%s-%s'
+        toolchain_format = '%s/toolchains/%s-4.9/prebuilt/%s-%s'
 
-        for version in gnu_compiler_version or ['4.9', '4.8', '4.7']:
+        toolchain = toolchain_format % (ndk, target_base,
+                                        host.kernel.lower(), host.cpu)
+        log.debug('Trying %s' % quote(toolchain))
+        if not isdir(toolchain) and host.cpu == 'x86_64':
             toolchain = toolchain_format % (ndk, target_base, version,
-                                            host.kernel.lower(), host.cpu)
+                                            host.kernel.lower(), 'x86')
             log.debug('Trying %s' % quote(toolchain))
-            if not isdir(toolchain) and host.cpu == 'x86_64':
-                toolchain = toolchain_format % (ndk, target_base, version,
-                                                host.kernel.lower(), 'x86')
-                log.debug('Trying %s' % quote(toolchain))
-            if isdir(toolchain):
-                return toolchain
-        else:
-            if gnu_compiler_version:
-                die('Your --with-android-gnu-compiler-version may be wrong')
-            die('You have to specify --with-android-toolchain='
-                '/path/to/ndk/toolchain.')
+        if isdir(toolchain):
+            return toolchain
+        die('You have to specify --with-android-toolchain='
+            '/path/to/ndk/toolchain.')
 
 set_config('ANDROID_TOOLCHAIN', android_toolchain)
 
 @depends(target, android_toolchain)
 def android_toolchain_prefix(target, toolchain):
     if toolchain:
         if target.cpu == 'x86':
             # Ideally, the --target should just have the right x86 variant
--- a/build/moz.configure/old.configure
+++ b/build/moz.configure/old.configure
@@ -229,17 +229,16 @@ def old_configure_options(*options):
     '--enable-verify-mar',
     '--enable-webrtc',
     '--enable-xul',
     '--enable-zipwriter',
     '--includedir',
     '--libdir',
     '--no-create',
     '--prefix',
-    '--with-android-cxx-stl',
     '--with-android-distribution-directory',
     '--with-android-max-sdk',
     '--with-android-min-sdk',
     '--with-android-sdk',
     '--with-app-basename',
     '--with-app-name',
     '--with-arch',
     '--with-branding',
--- a/build/templates.mozbuild
+++ b/build/templates.mozbuild
@@ -47,16 +47,18 @@ def SimplePrograms(names, ext='.cpp'):
 
 
 @template
 def CppUnitTests(names, ext='.cpp'):
     '''Template for C++ unit tests.
 
     Those have a single source with the same base name as the executable.
     '''
+    COMPILE_FLAGS['EXTRA_INCLUDES'] = ['-I%s/dist/include' % TOPOBJDIR,
+                                       '-I%s/dist/include/testing' % TOPOBJDIR]
     CPP_UNIT_TESTS += names
     SOURCES += ['%s%s' % (name, ext) for name in names]
 
     Binary()
 
 
 @template
 def Library(name):
--- a/config/config.mk
+++ b/config/config.mk
@@ -248,28 +248,16 @@ CCC = $(CXX)
 
 INCLUDES = \
   -I$(srcdir) \
   -I$(CURDIR) \
   $(LOCAL_INCLUDES) \
   -I$(ABS_DIST)/include \
   $(NULL)
 
-ifndef IS_GYP_DIR
-# NSPR_CFLAGS and NSS_CFLAGS must appear ahead of the other flags to avoid Linux
-# builds wrongly picking up system NSPR/NSS header files.
-OS_INCLUDES := \
-  $(NSPR_CFLAGS) $(NSS_CFLAGS) \
-  $(MOZ_JPEG_CFLAGS) \
-  $(MOZ_PNG_CFLAGS) \
-  $(MOZ_ZLIB_CFLAGS) \
-  $(MOZ_PIXMAN_CFLAGS) \
-  $(NULL)
-endif
-
 include $(MOZILLA_DIR)/config/static-checking-config.mk
 
 CFLAGS		= $(OS_CPPFLAGS) $(OS_CFLAGS)
 CXXFLAGS	= $(OS_CPPFLAGS) $(OS_CXXFLAGS)
 LDFLAGS		= $(OS_LDFLAGS) $(MOZBUILD_LDFLAGS) $(MOZ_FIX_LINK_PATHS)
 
 ifdef MOZ_OPTIMIZE
 ifeq (1,$(MOZ_OPTIMIZE))
@@ -319,18 +307,18 @@ endif # CLANG_CL
 
 # Use warnings-as-errors if ALLOW_COMPILER_WARNINGS is not set to 1 (which
 # includes the case where it's undefined).
 ifneq (1,$(ALLOW_COMPILER_WARNINGS))
 CXXFLAGS += $(WARNINGS_AS_ERRORS)
 CFLAGS   += $(WARNINGS_AS_ERRORS)
 endif # ALLOW_COMPILER_WARNINGS
 
-COMPILE_CFLAGS	= $(COMPUTED_CFLAGS) $(DEFINES) $(INCLUDES) $(OS_INCLUDES) $(DSO_CFLAGS) $(DSO_PIC_CFLAGS) $(RTL_FLAGS) $(OS_COMPILE_CFLAGS) $(_DEPEND_CFLAGS) $(CFLAGS) $(MOZBUILD_CFLAGS) $(MK_COMPILE_DEFINES)
-COMPILE_CXXFLAGS = $(COMPUTED_CXXFLAGS) $(DEFINES) $(INCLUDES) $(OS_INCLUDES) $(DSO_CFLAGS) $(DSO_PIC_CFLAGS) $(RTL_FLAGS) $(OS_COMPILE_CXXFLAGS) $(_DEPEND_CFLAGS) $(CXXFLAGS) $(MOZBUILD_CXXFLAGS) $(MK_COMPILE_DEFINES)
+COMPILE_CFLAGS	= $(COMPUTED_CFLAGS) $(DSO_CFLAGS) $(DSO_PIC_CFLAGS) $(RTL_FLAGS) $(OS_COMPILE_CFLAGS) $(_DEPEND_CFLAGS) $(CFLAGS) $(MOZBUILD_CFLAGS) $(MK_COMPILE_DEFINES)
+COMPILE_CXXFLAGS = $(COMPUTED_CXXFLAGS) $(DSO_CFLAGS) $(DSO_PIC_CFLAGS) $(RTL_FLAGS) $(OS_COMPILE_CXXFLAGS) $(_DEPEND_CFLAGS) $(CXXFLAGS) $(MOZBUILD_CXXFLAGS) $(MK_COMPILE_DEFINES)
 COMPILE_CMFLAGS = $(OS_COMPILE_CMFLAGS) $(MOZBUILD_CMFLAGS)
 COMPILE_CMMFLAGS = $(OS_COMPILE_CMMFLAGS) $(MOZBUILD_CMMFLAGS)
 ASFLAGS += $(MOZBUILD_ASFLAGS)
 
 ifndef CROSS_COMPILE
 HOST_CFLAGS += $(RTL_FLAGS)
 endif
 
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -63,17 +63,16 @@ testxpcobjdir = $(DEPTH)/_tests/xpcshell
 ifdef ENABLE_TESTS
 ifdef CPP_UNIT_TESTS
 ifdef COMPILE_ENVIRONMENT
 
 # Compile the tests to $(DIST)/bin.  Make lots of niceties available by default
 # through TestHarness.h, by modifying the list of includes and the libs against
 # which stuff links.
 SIMPLE_PROGRAMS += $(CPP_UNIT_TESTS)
-INCLUDES += -I$(ABS_DIST)/include/testing
 
 ifndef MOZ_PROFILE_GENERATE
 CPP_UNIT_TESTS_FILES = $(CPP_UNIT_TESTS)
 CPP_UNIT_TESTS_DEST = $(DIST)/cppunittests
 CPP_UNIT_TESTS_TARGET = target
 INSTALL_TARGETS += CPP_UNIT_TESTS
 endif
 
--- a/devtools/client/animationinspector/test/head.js
+++ b/devtools/client/animationinspector/test/head.js
@@ -435,18 +435,26 @@ function* clickOnAnimation(panel, index,
 
   // Expect a selection event.
   let onSelectionChanged = timeline.once(shouldAlreadySelected
                                          ? "animation-already-selected"
                                          : "animation-selected");
 
   info("Click on animation " + index + " in the timeline");
   let timeBlock = timeline.rootWrapperEl.querySelectorAll(".time-block")[index];
-  EventUtils.sendMouseEvent({type: "click"}, timeBlock,
-                            timeBlock.ownerDocument.defaultView);
+  // Scroll to show the timeBlock since the element may be out of displayed area.
+  timeBlock.scrollIntoView(false);
+  let timeBlockBounds = timeBlock.getBoundingClientRect();
+  let x = timeBlockBounds.width / 2;
+  let y = timeBlockBounds.height / 2;
+  if (timeBlock != timeBlock.ownerDocument.elementFromPoint(x, y)) {
+    // Move the mouse pointer a little since scrubber element may be at the point.
+    x += timeBlockBounds.width / 4;
+  }
+  EventUtils.synthesizeMouse(timeBlock, x, y, {}, timeBlock.ownerDocument.defaultView);
 
   return yield onSelectionChanged;
 }
 
 /**
  * Get an instance of the Keyframes component from the timeline.
  * @param {AnimationsPanel} panel The panel instance.
  * @param {String} propertyName The name of the animated property.
--- a/devtools/client/netmonitor/src/components/headers-panel.js
+++ b/devtools/client/netmonitor/src/components/headers-panel.js
@@ -61,22 +61,26 @@ const HeadersPanel = createClass({
   getInitialState() {
     return {
       rawHeadersOpened: false,
     };
   },
 
   getProperties(headers, title) {
     if (headers && headers.headers.length) {
-      return {
-        [`${title} (${getFormattedSize(headers.headersSize, 3)})`]:
+      let headerKey = `${title} (${getFormattedSize(headers.headersSize, 3)})`;
+      let propertiesResult = {
+        [headerKey]:
           headers.headers.reduce((acc, { name, value }) =>
             name ? Object.assign(acc, { [name]: value }) : acc
           , {})
       };
+
+      propertiesResult[headerKey] = this.sortByKey(propertiesResult[headerKey]);
+      return propertiesResult;
     }
 
     return null;
   },
 
   toggleRawHeaders() {
     this.setState({
       rawHeadersOpened: !this.state.rawHeadersOpened,
@@ -119,16 +123,26 @@ const HeadersPanel = createClass({
         })),
         headerDocURL ? MDNLink({
           url: headerDocURL,
         }) : null
       )
     );
   },
 
+  sortByKey: function (object) {
+    let result = {};
+    Object.keys(object).sort(function (left, right) {
+      return left.toLowerCase().localeCompare(right.toLowerCase());
+    }).forEach(function (key) {
+      result[key] = object[key];
+    });
+    return result;
+  },
+
   render() {
     const {
       openLink,
       cloneSelectedRequest,
       request: {
         fromCache,
         fromServiceWorker,
         httpVersion,
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -111,16 +111,17 @@ skip-if = (os == 'linux' && debug && bit
 [browser_net_filter-01.js]
 skip-if = (os == 'linux' && debug && bits == 32) # Bug 1303439
 [browser_net_filter-02.js]
 [browser_net_filter-03.js]
 [browser_net_filter-04.js]
 [browser_net_filter-autocomplete.js]
 [browser_net_filter-flags.js]
 [browser_net_footer-summary.js]
+[browser_net_headers_sorted.js]
 [browser_net_icon-preview.js]
 [browser_net_image-tooltip.js]
 [browser_net_json-b64.js]
 [browser_net_json-null.js]
 [browser_net_json-long.js]
 [browser_net_json-malformed.js]
 [browser_net_json_custom_mime.js]
 [browser_net_json_text_mime.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_headers_sorted.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if Request-Headers and Response-Headers are sorted in Headers tab.
+ */
+add_task(function* () {
+  let { tab, monitor } = yield initNetMonitor(SIMPLE_SJS);
+  info("Starting test... ");
+
+  let { document, store, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+
+  store.dispatch(Actions.batchEnable(false));
+
+  tab.linkedBrowser.reload();
+
+  let wait = waitForNetworkEvents(monitor, 1);
+  yield wait;
+
+  wait = waitForDOM(document, ".headers-overview");
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+    document.querySelectorAll(".request-list-item")[0]);
+  yield wait;
+
+  info("Check if Request-Headers and Response-Headers are sorted");
+  let expectedResponseHeaders = ["cache-control", "connection", "content-length",
+                                 "content-type", "date", "expires", "foo-bar",
+                                 "pragma", "server", "set-cookie"];
+  let expectedRequestHeaders = ["Accept", "Accept-Encoding", "Accept-Language",
+                                "Cache-Control", "Connection", "Cookie", "Host",
+                                "Pragma", "Upgrade-Insecure-Requests", "User-Agent"];
+
+  let labelCells = document.querySelectorAll(".treeLabelCell");
+  let actualResponseHeaders = [];
+  let actualRequestHeaders = [];
+
+  for (let i = 1; i < 11; i++) {
+    actualResponseHeaders.push(labelCells[i].innerText);
+  }
+
+  for (let i = 12; i < labelCells.length; i++) {
+    actualRequestHeaders.push(labelCells[i].innerText);
+  }
+
+  is(actualResponseHeaders.toString(), expectedResponseHeaders.toString(),
+    "Response Headers are sorted");
+
+  is(actualRequestHeaders.toString(), expectedRequestHeaders.toString(),
+    "Request Headers are sorted");
+
+  yield teardown(monitor);
+});
--- a/devtools/client/shared/components/notification-box.css
+++ b/devtools/client/shared/components/notification-box.css
@@ -28,17 +28,17 @@
 .notificationbox .details:dir(rtl)
 .notificationbox .notificationInner:dir(rtl) {
   flex-direction: row-reverse;
 }
 
 /* Style */
 
 .notificationbox .notification {
-  color: var(--theme-body-color-alt);
+  color: var(--theme-toolbar-color);
   background-color: var(--theme-body-background);
   text-shadow: none;
   border-bottom: 1px solid var(--theme-splitter-color);
 }
 
 .notificationbox .notification[data-type="info"] {
   color: -moz-DialogText;
   background-color: -moz-Dialog;
--- a/devtools/client/themes/animationinspector.css
+++ b/devtools/client/themes/animationinspector.css
@@ -258,16 +258,21 @@ body {
   right: var(--keyframes-marker-size);
   height: var(--timeline-animation-height);
 }
 
 .animation-detail .track-container {
   height: var(--detail-animation-height);
 }
 
+.time-body.track-container {
+  height: 0;
+  pointer-events: none;
+}
+
 .animation-timeline .scrubber-wrapper {
   position: absolute;
   z-index: 5;
   left: var(--timeline-sidebar-width);
   /* Leave the width of a marker right of a track so the 100% markers can be
      selected easily */
   right: var(--keyframes-marker-size);
   pointer-events: none;
@@ -338,17 +343,16 @@ body {
   top: var(--toolbar-height);
   z-index: 3;
 }
 
 .progress-tick-container .progress-tick,
 .animation-timeline .time-body .time-tick {
   -moz-user-select: none;
   position: absolute;
-  height: 100%;
 }
 
 .progress-tick-container .progress-tick::before,
 .animation-timeline .time-body .time-tick::before {
   content: "";
   position: fixed;
   height: 100vh;
   width: 0;
--- a/devtools/server/actors/object.js
+++ b/devtools/server/actors/object.js
@@ -1223,16 +1223,20 @@ DebuggerServer.ObjectActorPreviewers = {
   Boolean: [function (objectActor, grip, rawObj) {
     return wrappedPrimitivePreviewer("Boolean", Boolean, objectActor, grip, rawObj);
   }],
 
   Number: [function (objectActor, grip, rawObj) {
     return wrappedPrimitivePreviewer("Number", Number, objectActor, grip, rawObj);
   }],
 
+  Symbol: [function (objectActor, grip, rawObj) {
+    return wrappedPrimitivePreviewer("Symbol", Symbol, objectActor, grip, rawObj);
+  }],
+
   Function: [function ({obj, hooks}, grip) {
     if (obj.name) {
       grip.name = obj.name;
     }
 
     if (obj.displayName) {
       grip.displayName = obj.displayName.substr(0, 500);
     }
@@ -1503,20 +1507,16 @@ DebuggerServer.ObjectActorPreviewers = {
  *        The object actor
  * @param Object grip
  *        The result grip to fill in
  * @return Booolean true if the object was handled, false otherwise
  */
 function wrappedPrimitivePreviewer(className, classObj, objectActor, grip, rawObj) {
   let {obj, hooks} = objectActor;
 
-  if (!obj.proto || obj.proto.class != className) {
-    return false;
-  }
-
   let v = null;
   try {
     v = classObj.prototype.valueOf.call(rawObj);
   } catch (ex) {
     // valueOf() can throw if the raw JS object is "misbehaved".
     return false;
   }
 
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/unit/test_objectgrips-19.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint-disable no-shadow, max-nested-callbacks */
+
+"use strict";
+
+var gDebuggee;
+var gThreadClient;
+
+function run_test() {
+  run_test_with_server(DebuggerServer, function () {
+    run_test_with_server(WorkerDebuggerServer, do_test_finished);
+  });
+  do_test_pending();
+}
+
+async function run_test_with_server(server, callback) {
+  initTestDebuggerServer(server);
+  gDebuggee = gDebuggee = addTestGlobal("test-grips", server);
+  gDebuggee.eval(function stopMe(arg1) {
+    debugger;
+  }.toString());
+  let client = new DebuggerClient(server.connectPipe());
+  await client.connect();
+  const [,, threadClient] = await attachTestTabAndResume(client, "test-grips");
+  gThreadClient = threadClient;
+  await test_wrapped_primitive_grips();
+  await client.close();
+  callback();
+}
+
+async function test_wrapped_primitive_grips() {
+  let tests = [{
+    value: true,
+    class: "Boolean"
+  }, {
+    value: 123,
+    class: "Number"
+  }, {
+    value: "foo",
+    class: "String"
+  }, {
+    value: Symbol("bar"),
+    class: "Symbol",
+    name: "bar"
+  }];
+  for (let data of tests) {
+    await new Promise(function (resolve) {
+      gThreadClient.addOneTimeListener("paused", async function (event, packet) {
+        let [grip] = packet.frame.arguments;
+        check_wrapped_primitive_grip(grip, data);
+
+        await gThreadClient.resume();
+        resolve();
+      });
+      gDebuggee.primitive = data.value;
+      gDebuggee.eval("stopMe(Object(primitive));");
+    });
+  }
+}
+
+function check_wrapped_primitive_grip(grip, data) {
+  strictEqual(grip.class, data.class, "The grip has the proper class.");
+
+  if (!grip.preview) {
+    // In a worker thread Cu does not exist, the objects are considered unsafe and
+    // can't be unwrapped, so there is no preview.
+    return;
+  }
+
+  let value = grip.preview.wrappedValue;
+  if (data.class === "Symbol") {
+    strictEqual(value.type, "symbol", "The wrapped value grip has symbol type.");
+    strictEqual(value.name, data.name, "The wrapped value grip has the proper name.");
+  } else {
+    strictEqual(value, data.value, "The wrapped value is the primitive one.");
+  }
+}
--- a/devtools/server/tests/unit/xpcshell.ini
+++ b/devtools/server/tests/unit/xpcshell.ini
@@ -169,16 +169,17 @@ reason = only ran on B2G
 [test_objectgrips-11.js]
 [test_objectgrips-12.js]
 [test_objectgrips-13.js]
 [test_objectgrips-14.js]
 [test_objectgrips-15.js]
 [test_objectgrips-16.js]
 [test_objectgrips-17.js]
 [test_objectgrips-18.js]
+[test_objectgrips-19.js]
 [test_promise_state-01.js]
 [test_promise_state-02.js]
 [test_promise_state-03.js]
 [test_interrupt.js]
 [test_stepping-01.js]
 [test_stepping-02.js]
 [test_stepping-03.js]
 [test_stepping-04.js]
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/crashtests/1401809.html
@@ -0,0 +1,14 @@
+<html>
+  <head>
+    <style></style>
+    <script>
+      o1 = document.createElement('t');
+      document.documentElement.appendChild(o1);
+      document.styleSheets[0].insertRule('* { will-change:an }', 0);
+      k = new KeyframeEffect(o1, [{'willChange':'s'}], {'':''});
+      k = null;
+      SpecialPowers.forceGC();
+      SpecialPowers.forceCC();
+    </script>
+  </head>
+</html>
--- a/dom/animation/test/crashtests/crashtests.list
+++ b/dom/animation/test/crashtests/crashtests.list
@@ -28,8 +28,9 @@ pref(dom.animations-api.core.enabled,tru
 pref(dom.animations-api.core.enabled,true) load 1334583-1.html
 pref(dom.animations-api.core.enabled,true) load 1335998-1.html
 pref(dom.animations-api.core.enabled,true) load 1343589-1.html
 pref(dom.animations-api.core.enabled,true) load 1359658-1.html
 pref(dom.animations-api.core.enabled,true) load 1373712-1.html
 pref(dom.animations-api.core.enabled,true) load 1379606-1.html
 pref(dom.animations-api.core.enabled,true) load 1393605-1.html
 load 1400022-1.html
+pref(dom.animations-api.core.enabled,true) load 1401809.html
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -80,24 +80,29 @@ already_AddRefed<nsContentList>
 NS_GetContentList(nsINode* aRootNode,
                   int32_t  aMatchNameSpaceId,
                   const nsAString& aTagname);
 
 #define ELEMENT_FLAG_BIT(n_) NODE_FLAG_BIT(NODE_TYPE_SPECIFIC_BITS_OFFSET + (n_))
 
 // Element-specific flags
 enum {
-  // These two bits are shared by Gecko's and Servo's restyle systems for
+  // These four bits are shared by Gecko's and Servo's restyle systems for
   // different purposes. They should not be accessed directly, and access to
   // them should be properly guarded by asserts.
   ELEMENT_SHARED_RESTYLE_BIT_1 = ELEMENT_FLAG_BIT(0),
   ELEMENT_SHARED_RESTYLE_BIT_2 = ELEMENT_FLAG_BIT(1),
   ELEMENT_SHARED_RESTYLE_BIT_3 = ELEMENT_FLAG_BIT(2),
   ELEMENT_SHARED_RESTYLE_BIT_4 = ELEMENT_FLAG_BIT(3),
 
+  ELEMENT_SHARED_RESTYLE_BITS = ELEMENT_SHARED_RESTYLE_BIT_1 |
+                                ELEMENT_SHARED_RESTYLE_BIT_2 |
+                                ELEMENT_SHARED_RESTYLE_BIT_3 |
+                                ELEMENT_SHARED_RESTYLE_BIT_4,
+
   // Whether this node has dirty descendants for Servo's style system.
   ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO = ELEMENT_SHARED_RESTYLE_BIT_1,
 
   // Whether this node has dirty descendants for animation-only restyle for
   // Servo's style system.
   ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO =
     ELEMENT_SHARED_RESTYLE_BIT_2,
 
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -1488,24 +1488,29 @@ Navigator::GetVRDisplays(ErrorResult& aR
   return p.forget();
 }
 
 void
 Navigator::GetActiveVRDisplays(nsTArray<RefPtr<VRDisplay>>& aDisplays) const
 {
   /**
    * Get only the active VR displays.
-   * Callers do not wish to VRDisplay::RefreshVRDisplays, as the enumeration may
-   * activate hardware that is not yet intended to be used.
+   * GetActiveVRDisplays should only enumerate displays that
+   * are already active without causing any other hardware to be
+   * activated.
+   * We must not call nsGlobalWindow::NotifyVREventListenerAdded here,
+   * as that would cause enumeration and activation of other VR hardware.
+   * Activating VR hardware is intrusive to the end user, as it may
+   * involve physically powering on devices that the user did not
+   * intend to use.
    */
   if (!mWindow || !mWindow->GetDocShell()) {
     return;
   }
   nsGlobalWindow* win = nsGlobalWindow::Cast(mWindow);
-  win->NotifyVREventListenerAdded();
   nsTArray<RefPtr<VRDisplay>> displays;
   if (win->UpdateVRDisplays(displays)) {
     for (auto display : displays) {
       if (display->IsPresenting()) {
         aDisplays.AppendElement(display);
       }
     }
   }
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -7787,16 +7787,39 @@ nsDOMAttributeMap::BlastSubtreeToPieces(
 
   uint32_t count = aNode->GetChildCount();
   for (uint32_t i = 0; i < count; ++i) {
     BlastSubtreeToPieces(aNode->GetFirstChild());
     aNode->RemoveChildAt(0, false);
   }
 }
 
+// Recursively check whether this node or its descendants contain any
+// pre-existing style declaration or any shared restyle flags.
+static bool
+NodeContainsAnyStyleData(nsINode* aNode)
+{
+  if (aNode->IsElement()) {
+    Element* elem = aNode->AsElement();
+    if (elem->GetInlineStyleDeclaration() ||
+        elem->GetSMILOverrideStyleDeclaration() ||
+        elem->HasAnyOfFlags(ELEMENT_SHARED_RESTYLE_BITS) ||
+        elem->HasServoData()) {
+      return true;
+    }
+  }
+  for (nsIContent* child = aNode->GetFirstChild();
+       child; child = child->GetNextSibling()) {
+    if (NodeContainsAnyStyleData(child)) {
+      return true;
+    }
+  }
+  return false;
+}
+
 nsINode*
 nsIDocument::AdoptNode(nsINode& aAdoptedNode, ErrorResult& rv)
 {
   nsINode* adoptedNode = &aAdoptedNode;
 
   // Scope firing mutation events so that we don't carry any state that
   // might be stale
   {
@@ -7902,16 +7925,22 @@ nsIDocument::AdoptNode(nsINode& aAdopted
   }
 
   nsCOMPtr<nsIDocument> oldDocument = adoptedNode->OwnerDoc();
   bool sameDocument = oldDocument == this;
 
   AutoJSContext cx;
   JS::Rooted<JSObject*> newScope(cx, nullptr);
   if (!sameDocument) {
+    if (MOZ_UNLIKELY(oldDocument->GetStyleBackendType() != GetStyleBackendType())) {
+      NS_WARNING("Adopting node across different style backend");
+      MOZ_RELEASE_ASSERT(!NodeContainsAnyStyleData(adoptedNode),
+                         "Must not adopt a node with pre-existing style data "
+                         "into a document with different style backend");
+    }
     newScope = GetWrapper();
     if (!newScope && GetScopeObject() && GetScopeObject()->GetGlobalJSObject()) {
       // Make sure cx is in a semi-sane compartment before we call WrapNative.
       // It's kind of irrelevant, given that we're passing aAllowWrapping =
       // false, and documents should always insist on being wrapped in an
       // canonical scope. But we try to pass something sane anyway.
       JSAutoCompartment ac(cx, GetScopeObject()->GetGlobalJSObject());
       JS::Rooted<JS::Value> v(cx);
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -7542,17 +7542,17 @@ nsGlobalWindow::MakeScriptDialogTitle(ns
         fixedURI->GetHost(host);
 
         if (!host.IsEmpty()) {
           // if this URI has a host we'll show it. For other
           // schemes (e.g. file:) we fall back to the localized
           // generic string
 
           nsAutoCString prepath;
-          fixedURI->GetPrePath(prepath);
+          fixedURI->GetDisplayPrePath(prepath);
 
           NS_ConvertUTF8toUTF16 ucsPrePath(prepath);
           const char16_t *formatStrings[] = { ucsPrePath.get() };
           nsContentUtils::FormatLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES,
                                                 "ScriptDlgHeading",
                                                 formatStrings,
                                                 aOutTitle);
         }
--- a/dom/webidl/StyleSheet.webidl
+++ b/dom/webidl/StyleSheet.webidl
@@ -34,9 +34,15 @@ interface StyleSheet {
   //
   // If the style sheet has the special "# sourceMappingURL=" comment,
   // then this is the URL specified there.
   //
   // If the source map URL is not found by either of these methods,
   // then this is an empty string.
   [ChromeOnly, Pure]
   readonly attribute DOMString sourceMapURL;
+  // The source URL for this style sheet.  If the style sheet has the
+  // special "# sourceURL=" comment, then this is the URL specified
+  // there.  If no such comment is found, then this is the empty
+  // string.
+  [ChromeOnly, Pure]
+  readonly attribute DOMString sourceURL;
 };
--- a/gfx/layers/PaintThread.cpp
+++ b/gfx/layers/PaintThread.cpp
@@ -4,17 +4,16 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "PaintThread.h"
 
 #include "base/task.h"
 #include "gfxPrefs.h"
 #include "mozilla/layers/CompositorBridgeChild.h"
-#include "mozilla/layers/ContentClient.h"
 #include "mozilla/layers/ShadowLayers.h"
 #include "mozilla/layers/SyncObject.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/SyncRunnable.h"
 
 namespace mozilla {
 namespace layers {
@@ -141,47 +140,16 @@ PaintThread::Get()
 
 /* static */ bool
 PaintThread::IsOnPaintThread()
 {
   return sThreadId == PlatformThread::CurrentId();
 }
 
 void
-PaintThread::CopyFrontToBack(ContentClientRemoteBuffer* aContentClient,
-                             nsIntRegion aRegionToDraw)
-{
-  MOZ_ASSERT(PaintThread::IsOnPaintThread());
-  aContentClient->FinalizeFrameOnPaintThread(aRegionToDraw);
-}
-
-void
-PaintThread::CopyFrontBufferToBackBuffer(ContentClientRemoteBuffer* aContentClient,
-                                         nsIntRegion aRegionToDraw)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aContentClient);
-
-  RefPtr<ContentClientRemoteBuffer> cc(aContentClient);
-  RefPtr<PaintThread> self = this;
-
-  RefPtr<Runnable> task = NS_NewRunnableFunction("PaintThread::CopyFrontToBack",
-    [self, cc, aRegionToDraw]() -> void
-  {
-    self->CopyFrontToBack(cc, aRegionToDraw);
-  });
-
-  if (!gfxPrefs::LayersOMTPForceSync()) {
-    sThread->Dispatch(task.forget());
-  } else {
-    SyncRunnable::DispatchToThread(sThread, task);
-  }
-}
-
-void
 PaintThread::PaintContents(CapturedPaintState* aState,
                            PrepDrawTargetForPaintingCallback aCallback)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aState);
 
   // If painting asynchronously, we need to acquire the compositor bridge which
   // owns the underlying MessageChannel. Otherwise we leave it null and use
--- a/gfx/layers/PaintThread.h
+++ b/gfx/layers/PaintThread.h
@@ -16,17 +16,16 @@
 
 namespace mozilla {
 namespace gfx {
 class DrawTarget;
 class DrawTargetCapture;
 };
 
 namespace layers {
-class ContentClientRemoteBuffer;
 
 // Holds the key parts from a RotatedBuffer::PaintState
 // required to draw the captured paint state
 class CapturedPaintState {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CapturedPaintState)
 public:
   CapturedPaintState(nsIntRegion& aRegionToDraw,
                      gfx::DrawTarget* aTarget,
@@ -67,19 +66,16 @@ class PaintThread final
 public:
   static void Start();
   static void Shutdown();
   static PaintThread* Get();
 
   // Helper for asserts.
   static bool IsOnPaintThread();
 
-  void CopyFrontBufferToBackBuffer(ContentClientRemoteBuffer* aContentClient,
-                                   nsIntRegion aRegionToDraw);
-
   void PaintContents(CapturedPaintState* aState,
                      PrepDrawTargetForPaintingCallback aCallback);
 
   // Must be called on the main thread. Signifies that the current
   // batch of CapturedPaintStates* for PaintContents have been recorded
   // and the main thread is finished recording this layer.
   void EndLayer();
 
@@ -97,18 +93,16 @@ public:
   // Override release/addref but don't do anything.
   void Release();
   void AddRef();
 
 private:
   bool Init();
   void ShutdownOnPaintThread();
   void InitOnPaintThread();
-  void CopyFrontToBack(ContentClientRemoteBuffer* aContentClient,
-                       nsIntRegion aRegionToDraw);
 
   void AsyncPaintContents(CompositorBridgeChild* aBridge,
                           CapturedPaintState* aState,
                           PrepDrawTargetForPaintingCallback aCallback);
   void AsyncEndLayer();
   void AsyncEndLayerTransaction(CompositorBridgeChild* aBridge,
                                 SyncObjectClient* aSyncObject);
 
--- a/gfx/layers/RotatedBuffer.cpp
+++ b/gfx/layers/RotatedBuffer.cpp
@@ -429,18 +429,17 @@ RotatedContentBuffer::FlushBuffers()
   }
   if (mDTBufferOnWhite) {
     mDTBufferOnWhite->Flush();
   }
 }
 
 RotatedContentBuffer::PaintState
 RotatedContentBuffer::BeginPaint(PaintedLayer* aLayer,
-                                 uint32_t aFlags,
-                                 bool aCopyToBackBuffer)
+                                 uint32_t aFlags)
 {
   PaintState result;
   // We need to disable rotation if we're going to be resampled when
   // drawing, because we might sample across the rotation boundary.
   // Also disable buffer rotation when using webrender.
   bool canHaveRotation = gfxPlatform::BufferRotationEnabled() &&
                          !(aFlags & (PAINT_WILL_RESAMPLE | PAINT_NO_ROTATION)) &&
                          !(aLayer->Manager()->AsWebRenderLayerManager());
@@ -551,17 +550,17 @@ RotatedContentBuffer::BeginPaint(Painted
                "Destination rect doesn't contain what we need to paint");
 
   result.mRegionToDraw.Sub(neededRegion, validRegion);
 
   if (result.mRegionToDraw.IsEmpty())
     return result;
 
   if (HaveBuffer()) {
-    if (aCopyToBackBuffer && LockBuffers()) {
+    if (LockBuffers()) {
       // Do not modify result.mRegionToDraw or result.mContentType after this call.
       // Do not modify mBufferRect, mBufferRotation, or mDidSelfCopy,
       // or call CreateBuffer before this call.
       FinalizeFrame(result.mRegionToDraw);
     } else {
       // Abandon everything and redraw it all. Ideally we'd reallocate and copy
       // the old to the new and then call FinalizeFrame on the new buffer so that
       // we only need to draw the latest bits, but we need a big refactor to support
@@ -658,17 +657,16 @@ RotatedContentBuffer::BeginPaint(Painted
             mBufferRect = destBufferRect;
             mBufferRotation = IntPoint(0, 0);
           }
 
           if (!result.mDidSelfCopy) {
             destBufferRect = ComputeBufferRect(neededRegion.GetBounds());
             CreateBuffer(result.mContentType, destBufferRect, bufferFlags,
                          &destDTBuffer, &destDTBufferOnWhite);
-            result.mFinalizeOnPaintThread = false;
             if (!destDTBuffer ||
                 (!destDTBufferOnWhite && (bufferFlags & BUFFER_COMPONENT_ALPHA))) {
               if (Factory::ReasonableSurfaceSize(IntSize(destBufferRect.Width(), destBufferRect.Height()))) {
                 gfxCriticalNote << "Failed 1 buffer db=" << hexa(destDTBuffer.get()) << " dw=" << hexa(destDTBufferOnWhite.get()) << " for " << destBufferRect.x << ", " << destBufferRect.y << ", " << destBufferRect.Width() << ", " << destBufferRect.Height();
               }
               return result;
             }
           }
@@ -683,17 +681,16 @@ RotatedContentBuffer::BeginPaint(Painted
       // set destBuffer.
       mBufferRect = destBufferRect;
       mBufferRotation = IntPoint(0,0);
     }
   } else {
     // The buffer's not big enough, so allocate a new one
     CreateBuffer(result.mContentType, destBufferRect, bufferFlags,
                  &destDTBuffer, &destDTBufferOnWhite);
-    result.mFinalizeOnPaintThread = false;
     if (!destDTBuffer ||
         (!destDTBufferOnWhite && (bufferFlags & BUFFER_COMPONENT_ALPHA))) {
       if (Factory::ReasonableSurfaceSize(IntSize(destBufferRect.Width(), destBufferRect.Height()))) {
         gfxCriticalNote << "Failed 2 buffer db=" << hexa(destDTBuffer.get()) << " dw=" << hexa(destDTBufferOnWhite.get()) << " for " << destBufferRect.x << ", " << destBufferRect.y << ", " << destBufferRect.Width() << ", " << destBufferRect.Height();
       }
       return result;
     }
   }
--- a/gfx/layers/RotatedBuffer.h
+++ b/gfx/layers/RotatedBuffer.h
@@ -220,37 +220,33 @@ public:
   /**
    * This is returned by BeginPaint. The caller should draw into mTarget.
    * mRegionToDraw must be drawn. mRegionToInvalidate has been invalidated
    * by RotatedContentBuffer and must be redrawn on the screen.
    * mRegionToInvalidate is set when the buffer has changed from
    * opaque to transparent or vice versa, since the details of rendering can
    * depend on the buffer type.  mDidSelfCopy is true if we kept our buffer
    * but used MovePixels() to shift its content.
-   * mFinalizeOnPaintThread is true if we need to copy the front
-   * to back buffer and we didn't destroy the buffer during BeginPaint.
    */
   struct PaintState {
     PaintState()
       : mRegionToDraw()
       , mRegionToInvalidate()
       , mMode(SurfaceMode::SURFACE_NONE)
       , mClip(DrawRegionClip::NONE)
       , mContentType(gfxContentType::SENTINEL)
       , mDidSelfCopy(false)
-      , mFinalizeOnPaintThread(true)
     {}
 
     nsIntRegion mRegionToDraw;
     nsIntRegion mRegionToInvalidate;
     SurfaceMode mMode;
     DrawRegionClip mClip;
     ContentType mContentType;
     bool mDidSelfCopy;
-    bool mFinalizeOnPaintThread;
   };
 
   enum {
     PAINT_WILL_RESAMPLE = 0x01,
     PAINT_NO_ROTATION = 0x02,
     PAINT_CAN_DRAW_ROTATED = 0x04
   };
   /**
@@ -267,23 +263,19 @@ public:
    * to resample across the rotation boundary) and will ensure that we
    * make the entire buffer contents valid (since we don't want to sample
    * invalid pixels outside the visible region, if the visible region doesn't
    * fill the buffer bounds).
    * PAINT_CAN_DRAW_ROTATED can be passed if the caller supports drawing
    * rotated content that crosses the physical buffer boundary. The caller
    * will need to call BorrowDrawTargetForPainting multiple times to achieve
    * this.
-   * @param aCopyToBackBuffer Whether to copy the front buffer to back buffer
-   * This should generally be true unless OMTP is enabled where we want to do
-   * this on the paint thread instead.
    */
   PaintState BeginPaint(PaintedLayer* aLayer,
-                        uint32_t aFlags,
-                        bool aCopyToBackbuffer = true);
+                        uint32_t aFlags);
 
   struct DrawIterator {
     friend class RotatedContentBuffer;
     DrawIterator()
       : mCount(0)
     {}
 
     nsIntRegion mDrawRegion;
--- a/gfx/layers/client/ClientPaintedLayer.cpp
+++ b/gfx/layers/client/ClientPaintedLayer.cpp
@@ -77,17 +77,17 @@ ClientPaintedLayer::CanRecordLayer(Readb
 void
 ClientPaintedLayer::UpdateContentClient(PaintState& aState)
 {
   Mutated();
 
   AddToValidRegion(aState.mRegionToDraw);
 
   ContentClientRemote *contentClientRemote =
-      static_cast<ContentClientRemote*>(mContentClient.get());
+      static_cast<ContentClientRemote *>(mContentClient.get());
   MOZ_ASSERT(contentClientRemote->GetIPCHandle());
 
   // Hold(this) ensures this layer is kept alive through the current transaction
   // The ContentClient assumes this layer is kept alive (e.g., in CreateBuffer),
   // so deleting this Hold for whatever reason will break things.
   ClientManager()->Hold(this);
   contentClientRemote->Updated(aState.mRegionToDraw,
                                mVisibleRegion.ToUnknownRegion(),
@@ -208,23 +208,17 @@ ClientPaintedLayer::PaintThebes(nsTArray
  */
 bool
 ClientPaintedLayer::PaintOffMainThread()
 {
   mContentClient->BeginAsyncPaint();
 
   uint32_t flags = GetPaintFlags();
 
-  PaintState state = mContentClient->BeginPaintBuffer(this, flags, false);
-  MOZ_ASSERT(mContentClient->IsRemoteBuffer());
-  if (state.mFinalizeOnPaintThread) {
-    PaintThread::Get()->CopyFrontBufferToBackBuffer(static_cast<ContentClientRemoteBuffer*>(mContentClient.get()),
-                                                    state.mRegionToDraw);
-  }
-
+  PaintState state = mContentClient->BeginPaintBuffer(this, flags);
   if (!UpdatePaintRegion(state)) {
     return false;
   }
 
   bool didUpdate = false;
   RotatedContentBuffer::DrawIterator iter;
 
   // Debug Protip: Change to BorrowDrawTargetForPainting if using sync OMTP.
--- a/gfx/layers/client/ContentClient.cpp
+++ b/gfx/layers/client/ContentClient.cpp
@@ -18,17 +18,16 @@
 #include "mozilla/gfx/BasePoint.h"      // for BasePoint
 #include "mozilla/gfx/BaseSize.h"       // for BaseSize
 #include "mozilla/gfx/Rect.h"           // for Rect
 #include "mozilla/gfx/Types.h"
 #include "mozilla/layers/CompositorBridgeChild.h" // for CompositorBridgeChild
 #include "mozilla/layers/LayerManagerComposite.h"
 #include "mozilla/layers/LayersMessages.h"  // for ThebesBufferData
 #include "mozilla/layers/LayersTypes.h"
-#include "mozilla/layers/PaintThread.h"
 #include "nsDebug.h"                    // for NS_ASSERTION, NS_WARNING, etc
 #include "nsISupportsImpl.h"            // for gfxContext::Release, etc
 #include "nsIWidget.h"                  // for nsIWidget
 #include "nsLayoutUtils.h"
 #ifdef XP_WIN
 #include "gfxWindowsPlatform.h"
 #endif
 #ifdef MOZ_WIDGET_GTK
@@ -608,21 +607,16 @@ ContentClientDoubleBuffered::BeginAsyncP
 
 // Sync front/back buffers content
 // After executing, the new back buffer has the same (interesting) pixels as
 // the new front buffer, and mValidRegion et al. are correct wrt the new
 // back buffer (i.e. as they were for the old back buffer)
 void
 ContentClientDoubleBuffered::FinalizeFrame(const nsIntRegion& aRegionToDraw)
 {
-  MOZ_ASSERT(NS_IsMainThread() || PaintThread::IsOnPaintThread());
-  if (!HaveBuffer()) {
-    return;
-  }
-
   if (!mFrontAndBackBufferDiffer) {
     MOZ_ASSERT(!mDidSelfCopy, "If we have to copy the world, then our buffers are different, right?");
     return;
   }
   MOZ_ASSERT(mFrontClient);
   if (!mFrontClient) {
     return;
   }
@@ -644,24 +638,16 @@ ContentClientDoubleBuffered::FinalizeFra
 
   // No point in sync'ing what we are going to draw over anyway. And if there is
   // nothing to sync at all, there is nothing to do and we can go home early.
   updateRegion.Sub(updateRegion, aRegionToDraw);
   if (updateRegion.IsEmpty()) {
     return;
   }
 
-  CopyFrontBufferToBackBuffer(updateRegion);
-}
-
-void
-ContentClientDoubleBuffered::CopyFrontBufferToBackBuffer(nsIntRegion& aUpdateRegion)
-{
-  MOZ_ASSERT(NS_IsMainThread() || PaintThread::IsOnPaintThread());
-
   // We need to ensure that we lock these two buffers in the same
   // order as the compositor to prevent deadlocks.
   TextureClientAutoLock frontLock(mFrontClient, OpenMode::OPEN_READ_ONLY);
   if (!frontLock.Succeeded()) {
     return;
   }
   Maybe<TextureClientAutoLock> frontOnWhiteLock;
   if (mFrontClientOnWhite) {
@@ -678,17 +664,17 @@ ContentClientDoubleBuffered::CopyFrontBu
   gfx::DrawTarget* dtw = mFrontClientOnWhite ? mFrontClientOnWhite->BorrowDrawTarget() : nullptr;
   if (dt && dt->IsValid()) {
     RefPtr<SourceSurface> surf = dt->Snapshot();
     RefPtr<SourceSurface> surfOnWhite = dtw ? dtw->Snapshot() : nullptr;
     SourceRotatedBuffer frontBuffer(surf,
                                     surfOnWhite,
                                     mFrontBufferRect,
                                     mFrontBufferRotation);
-    UpdateDestinationFrom(frontBuffer, aUpdateRegion);
+    UpdateDestinationFrom(frontBuffer, updateRegion);
   } else {
     // We know this can happen, but we want to track it somewhat, in case it leads
     // to other problems.
     gfxCriticalNote << "Invalid draw target(s) " << hexa(dt) << " and " << hexa(dtw);
   }
 }
 
 void
--- a/gfx/layers/client/ContentClient.h
+++ b/gfx/layers/client/ContentClient.h
@@ -12,17 +12,16 @@
 #include "gfxPlatform.h"                // for gfxPlatform
 #include "mozilla/Assertions.h"         // for MOZ_CRASH
 #include "mozilla/Attributes.h"         // for override
 #include "mozilla/RefPtr.h"             // for RefPtr, already_AddRefed
 #include "mozilla/gfx/Point.h"          // for IntSize
 #include "mozilla/layers/CompositableClient.h"  // for CompositableClient
 #include "mozilla/layers/CompositableForwarder.h"
 #include "mozilla/layers/CompositorTypes.h"  // for TextureInfo, etc
-#include "mozilla/layers/PaintThread.h"
 #include "mozilla/layers/ISurfaceAllocator.h"
 #include "mozilla/layers/LayersSurfaces.h"  // for SurfaceDescriptor
 #include "mozilla/layers/LayersTypes.h"  // for TextureDumpMode
 #include "mozilla/layers/TextureClient.h"  // for TextureClient
 #include "mozilla/mozalloc.h"           // for operator delete
 #include "ReadbackProcessor.h"          // For ReadbackProcessor::Update
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsPoint.h"                    // for nsIntPoint
@@ -89,35 +88,33 @@ public:
   {}
   virtual ~ContentClient()
   {}
 
   virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix);
 
   virtual void Clear() = 0;
   virtual RotatedContentBuffer::PaintState BeginPaintBuffer(PaintedLayer* aLayer,
-                                                            uint32_t aFlags,
-                                                            bool aCopyFrontToBack = true) = 0;
+                                                            uint32_t aFlags) = 0;
   virtual gfx::DrawTarget* BorrowDrawTargetForPainting(RotatedContentBuffer::PaintState& aPaintState,
                                                        RotatedContentBuffer::DrawIterator* aIter = nullptr) = 0;
   virtual void ReturnDrawTargetToBuffer(gfx::DrawTarget*& aReturned) = 0;
   virtual RefPtr<CapturedPaintState> BorrowDrawTargetForRecording(
     RotatedContentBuffer::PaintState& aPaintState,
     RotatedContentBuffer::DrawIterator* aIter) = 0;
 
   // Called as part of the layers transation reply. Conveys data about our
   // buffer(s) from the compositor. If appropriate we should swap references
   // to our buffers.
   virtual void SwapBuffers(const nsIntRegion& aFrontUpdatedRegion) {}
 
   // call before and after painting into this content client
   virtual void BeginPaint() {}
   virtual void BeginAsyncPaint();
   virtual void EndPaint(nsTArray<ReadbackProcessor::Update>* aReadbackUpdates = nullptr);
-  virtual bool IsRemoteBuffer() { return false; }
 
 protected:
   bool mInAsyncPaint;
 };
 
 /**
  * A ContentClient for use with OMTC.
  */
@@ -144,20 +141,19 @@ class ContentClientBasic final : public 
 public:
   explicit ContentClientBasic(gfx::BackendType aBackend);
 
   typedef RotatedContentBuffer::PaintState PaintState;
   typedef RotatedContentBuffer::ContentType ContentType;
 
   virtual void Clear() override { RotatedContentBuffer::Clear(); }
   virtual PaintState BeginPaintBuffer(PaintedLayer* aLayer,
-                                      uint32_t aFlags,
-                                      bool aCopyFrontToBack = true) override
+                                      uint32_t aFlags) override
   {
-    return RotatedContentBuffer::BeginPaint(aLayer, aFlags, aCopyFrontToBack);
+    return RotatedContentBuffer::BeginPaint(aLayer, aFlags);
   }
   virtual gfx::DrawTarget* BorrowDrawTargetForPainting(PaintState& aPaintState,
                                                        RotatedContentBuffer::DrawIterator* aIter = nullptr) override
   {
     return RotatedContentBuffer::BorrowDrawTargetForPainting(aPaintState, aIter);
   }
   virtual RefPtr<CapturedPaintState> BorrowDrawTargetForRecording(PaintState& aPaintState,
                                                                   RotatedContentBuffer::DrawIterator* aIter) override;
@@ -231,20 +227,19 @@ public:
   }
 
   virtual void Dump(std::stringstream& aStream,
                     const char* aPrefix="",
                     bool aDumpHtml=false,
                     TextureDumpMode aCompress=TextureDumpMode::Compress) override;
 
   virtual PaintState BeginPaintBuffer(PaintedLayer* aLayer,
-                                      uint32_t aFlags,
-                                      bool aCopyFrontToBack = true) override
+                                      uint32_t aFlags) override
   {
-    return RotatedContentBuffer::BeginPaint(aLayer, aFlags, aCopyFrontToBack);
+    return RotatedContentBuffer::BeginPaint(aLayer, aFlags);
   }
   virtual gfx::DrawTarget* BorrowDrawTargetForPainting(PaintState& aPaintState,
                                                        RotatedContentBuffer::DrawIterator* aIter = nullptr) override
   {
     return RotatedContentBuffer::BorrowDrawTargetForPainting(aPaintState, aIter);
   }
   virtual RefPtr<CapturedPaintState> BorrowDrawTargetForRecording(PaintState& aPaintState,
                                                                   RotatedContentBuffer::DrawIterator* aIter) override;
@@ -285,22 +280,16 @@ public:
   virtual void CreateBuffer(ContentType aType, const gfx::IntRect& aRect, uint32_t aFlags,
                             RefPtr<gfx::DrawTarget>* aBlackDT, RefPtr<gfx::DrawTarget>* aWhiteDT) override;
 
   virtual TextureFlags ExtraTextureFlags() const
   {
     return TextureFlags::IMMEDIATE_UPLOAD;
   }
 
-  virtual bool IsRemoteBuffer() override { return true; }
-  virtual void FinalizeFrameOnPaintThread(nsIntRegion aRegionToDraw) {
-    MOZ_ASSERT(PaintThread::IsOnPaintThread());
-    FinalizeFrame(aRegionToDraw);
-  }
-
 protected:
   void DestroyBuffers();
 
   virtual nsIntRegion GetUpdatedRegion(const nsIntRegion& aRegionToDraw,
                                        const nsIntRegion& aVisibleRegion,
                                        bool aDidSelfCopy);
 
   void BuildTextureClients(gfx::SurfaceFormat aFormat,
@@ -391,18 +380,16 @@ public:
                     TextureDumpMode aCompress=TextureDumpMode::Compress) override;
 protected:
   virtual void DestroyFrontBuffer() override;
 
 private:
   void UpdateDestinationFrom(const RotatedBuffer& aSource,
                              const nsIntRegion& aUpdateRegion);
 
-  void CopyFrontBufferToBackBuffer(nsIntRegion& aUpdateRegion);
-
   virtual void AbortTextureClientCreation() override
   {
     mTextureClient = nullptr;
     mTextureClientOnWhite = nullptr;
     mFrontClient = nullptr;
     mFrontClientOnWhite = nullptr;
   }
 
--- a/gfx/layers/composite/TextureHost.cpp
+++ b/gfx/layers/composite/TextureHost.cpp
@@ -625,21 +625,22 @@ void
 BufferTextureHost::PushExternalImage(wr::DisplayListBuilder& aBuilder,
                                      const wr::LayoutRect& aBounds,
                                      const wr::LayoutRect& aClip,
                                      wr::ImageRendering aFilter,
                                      Range<const wr::ImageKey>& aImageKeys)
 {
   if (GetFormat() != gfx::SurfaceFormat::YUV) {
     MOZ_ASSERT(aImageKeys.length() == 1);
-    aBuilder.PushImage(aBounds, aClip, aFilter, aImageKeys[0]);
+    aBuilder.PushImage(aBounds, aClip, true, aFilter, aImageKeys[0]);
   } else {
     MOZ_ASSERT(aImageKeys.length() == 3);
     aBuilder.PushYCbCrPlanarImage(aBounds,
                                   aClip,
+                                  true,
                                   aImageKeys[0],
                                   aImageKeys[1],
                                   aImageKeys[2],
                                   wr::WrYuvColorSpace::Rec601,
                                   aFilter);
   }
 }
 
--- a/gfx/layers/d3d11/TextureD3D11.cpp
+++ b/gfx/layers/d3d11/TextureD3D11.cpp
@@ -1124,23 +1124,24 @@ DXGITextureHostD3D11::PushExternalImage(
                                         Range<const wr::ImageKey>& aImageKeys)
 {
   switch (GetFormat()) {
     case gfx::SurfaceFormat::R8G8B8X8:
     case gfx::SurfaceFormat::R8G8B8A8:
     case gfx::SurfaceFormat::B8G8R8A8:
     case gfx::SurfaceFormat::B8G8R8X8: {
       MOZ_ASSERT(aImageKeys.length() == 1);
-      aBuilder.PushImage(aBounds, aClip, aFilter, aImageKeys[0]);
+      aBuilder.PushImage(aBounds, aClip, true, aFilter, aImageKeys[0]);
       break;
     }
     case gfx::SurfaceFormat::NV12: {
       MOZ_ASSERT(aImageKeys.length() == 2);
       aBuilder.PushNV12Image(aBounds,
                              aClip,
+                             true,
                              aImageKeys[0],
                              aImageKeys[1],
                              wr::WrYuvColorSpace::Rec601,
                              aFilter);
       break;
     }
     default: {
       MOZ_ASSERT_UNREACHABLE("unexpected to be called");
@@ -1359,17 +1360,17 @@ void
 DXGIYCbCrTextureHostD3D11::PushExternalImage(wr::DisplayListBuilder& aBuilder,
                                              const wr::LayoutRect& aBounds,
                                              const wr::LayoutRect& aClip,
                                              wr::ImageRendering aFilter,
                                              Range<const wr::ImageKey>& aImageKeys)
 {
   // 1 image key
   MOZ_ASSERT(aImageKeys.length() == 1);
-  aBuilder.PushImage(aBounds, aClip, aFilter, aImageKeys[0]);
+  aBuilder.PushImage(aBounds, aClip, true, aFilter, aImageKeys[0]);
 }
 
 bool
 DXGIYCbCrTextureHostD3D11::AcquireTextureSource(CompositableTextureSourceRef& aTexture)
 {
   if (!EnsureTextureSource()) {
     return false;
   }
--- a/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp
+++ b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp
@@ -231,34 +231,36 @@ MacIOSurfaceTextureHostOGL::PushExternal
 {
   switch (GetFormat()) {
     case gfx::SurfaceFormat::R8G8B8X8:
     case gfx::SurfaceFormat::R8G8B8A8:
     case gfx::SurfaceFormat::B8G8R8A8:
     case gfx::SurfaceFormat::B8G8R8X8: {
       MOZ_ASSERT(aImageKeys.length() == 1);
       MOZ_ASSERT(mSurface->GetPlaneCount() == 0);
-      aBuilder.PushImage(aBounds, aClip, aFilter, aImageKeys[0]);
+      aBuilder.PushImage(aBounds, aClip, true, aFilter, aImageKeys[0]);
       break;
     }
     case gfx::SurfaceFormat::YUV422: {
       MOZ_ASSERT(aImageKeys.length() == 1);
       MOZ_ASSERT(mSurface->GetPlaneCount() == 0);
       aBuilder.PushYCbCrInterleavedImage(aBounds,
                                          aClip,
+                                         true,
                                          aImageKeys[0],
                                          wr::WrYuvColorSpace::Rec601,
                                          aFilter);
       break;
     }
     case gfx::SurfaceFormat::NV12: {
       MOZ_ASSERT(aImageKeys.length() == 2);
       MOZ_ASSERT(mSurface->GetPlaneCount() == 2);
       aBuilder.PushNV12Image(aBounds,
                              aClip,
+                             true,
                              aImageKeys[0],
                              aImageKeys[1],
                              wr::WrYuvColorSpace::Rec601,
                              aFilter);
       break;
     }
     default: {
       MOZ_ASSERT_UNREACHABLE("unexpected to be called");
--- a/gfx/layers/wr/AsyncImagePipelineManager.cpp
+++ b/gfx/layers/wr/AsyncImagePipelineManager.cpp
@@ -286,17 +286,18 @@ AsyncImagePipelineManager::ApplyAsyncIma
       float opacity = 1.0f;
       builder.PushStackingContext(wr::ToLayoutRect(pipeline->mScBounds),
                                   0,
                                   &opacity,
                                   pipeline->mScTransform.IsIdentity() ? nullptr : &pipeline->mScTransform,
                                   wr::TransformStyle::Flat,
                                   nullptr,
                                   pipeline->mMixBlendMode,
-                                  nsTArray<wr::WrFilterOp>());
+                                  nsTArray<wr::WrFilterOp>(),
+                                  true);
 
       LayerRect rect(0, 0, pipeline->mCurrentTexture->GetSize().width, pipeline->mCurrentTexture->GetSize().height);
       if (pipeline->mScaleToSize.isSome()) {
         rect = LayerRect(0, 0, pipeline->mScaleToSize.value().width, pipeline->mScaleToSize.value().height);
       }
 
       if (useExternalImage) {
         MOZ_ASSERT(pipeline->mCurrentTexture->AsWebRenderTextureHost());
@@ -306,16 +307,17 @@ AsyncImagePipelineManager::ApplyAsyncIma
                                                      wr::ToLayoutRect(rect),
                                                      pipeline->mFilter,
                                                      range_keys);
         HoldExternalImage(pipelineId, epoch, pipeline->mCurrentTexture->AsWebRenderTextureHost());
       } else {
         MOZ_ASSERT(keys.Length() == 1);
         builder.PushImage(wr::ToLayoutRect(rect),
                           wr::ToLayoutRect(rect),
+                          true,
                           pipeline->mFilter,
                           keys[0]);
       }
       builder.PopStackingContext();
     }
 
     wr::BuiltDisplayList dl;
     wr::LayoutSize builderContentSize;
--- a/gfx/layers/wr/StackingContextHelper.cpp
+++ b/gfx/layers/wr/StackingContextHelper.cpp
@@ -30,17 +30,18 @@ StackingContextHelper::StackingContextHe
   mTransform = aTransform.valueOr(layer->GetTransform());
 
   float opacity = 1.0f;
   mBuilder->PushStackingContext(scBounds, 0, &opacity,
                                 mTransform.IsIdentity() ? nullptr : &mTransform,
                                 wr::TransformStyle::Flat,
                                 nullptr,
                                 wr::ToMixBlendMode(layer->GetMixBlendMode()),
-                                aFilters);
+                                aFilters,
+                                true);
   mOrigin = aLayer->Bounds().TopLeft();
 }
 
 StackingContextHelper::StackingContextHelper(const StackingContextHelper& aParentSC,
                                              wr::DisplayListBuilder& aBuilder,
                                              WebRenderLayer* aLayer,
                                              uint64_t aAnimationsId,
                                              float* aOpacityPtr,
@@ -55,47 +56,50 @@ StackingContextHelper::StackingContextHe
 
   mBuilder->PushStackingContext(scBounds,
                                 aAnimationsId,
                                 aOpacityPtr,
                                 aTransformPtr,
                                 wr::TransformStyle::Flat,
                                 nullptr,
                                 wr::ToMixBlendMode(aLayer->GetLayer()->GetMixBlendMode()),
-                                aFilters);
+                                aFilters,
+                                true);
   mOrigin = aLayer->Bounds().TopLeft();
 }
 
 StackingContextHelper::StackingContextHelper(const StackingContextHelper& aParentSC,
                                              wr::DisplayListBuilder& aBuilder,
                                              nsDisplayListBuilder* aDisplayListBuilder,
                                              nsDisplayItem* aItem,
                                              nsDisplayList* aDisplayList,
                                              gfx::Matrix4x4Typed<LayerPixel, LayerPixel>* aBoundTransform,
                                              uint64_t aAnimationsId,
                                              float* aOpacityPtr,
                                              gfx::Matrix4x4* aTransformPtr,
                                              gfx::Matrix4x4* aPerspectivePtr,
                                              const nsTArray<wr::WrFilterOp>& aFilters,
-                                             const gfx::CompositionOp& aMixBlendMode)
+                                             const gfx::CompositionOp& aMixBlendMode,
+                                             bool aBackfaceVisible)
   : mBuilder(&aBuilder)
 {
   bool is2d = !aTransformPtr || (aTransformPtr->Is2D() && !aPerspectivePtr);
   if (aTransformPtr) {
     mTransform = *aTransformPtr;
   }
 
   mBuilder->PushStackingContext(wr::LayoutRect(),
                                 aAnimationsId,
                                 aOpacityPtr,
                                 aTransformPtr,
                                 is2d ? wr::TransformStyle::Flat : wr::TransformStyle::Preserve3D,
                                 aPerspectivePtr,
                                 wr::ToMixBlendMode(aMixBlendMode),
-                                aFilters);
+                                aFilters,
+                                aBackfaceVisible);
 }
 
 StackingContextHelper::~StackingContextHelper()
 {
   if (mBuilder) {
     mBuilder->PopStackingContext();
   }
 }
--- a/gfx/layers/wr/StackingContextHelper.h
+++ b/gfx/layers/wr/StackingContextHelper.h
@@ -53,17 +53,18 @@ public:
                         nsDisplayItem* aItem,
                         nsDisplayList* aDisplayList,
                         gfx::Matrix4x4Typed<LayerPixel, LayerPixel>* aBoundTransform,
                         uint64_t aAnimationsId,
                         float* aOpacityPtr,
                         gfx::Matrix4x4* aTransformPtr,
                         gfx::Matrix4x4* aPerspectivePtr = nullptr,
                         const nsTArray<wr::WrFilterOp>& aFilters = nsTArray<wr::WrFilterOp>(),
-                        const gfx::CompositionOp& aMixBlendMode = gfx::CompositionOp::OP_OVER);
+                        const gfx::CompositionOp& aMixBlendMode = gfx::CompositionOp::OP_OVER,
+                        bool aBackfaceVisible = true);
   // This version of the constructor should only be used at the root level
   // of the tree, so that we have a StackingContextHelper to pass down into
   // the RenderLayer traversal, but don't actually want it to push a stacking
   // context on the display list builder.
   StackingContextHelper();
 
   // Pops the stacking context, if one was pushed during the constructor.
   ~StackingContextHelper();
--- a/gfx/layers/wr/WebRenderBridgeChild.cpp
+++ b/gfx/layers/wr/WebRenderBridgeChild.cpp
@@ -158,40 +158,47 @@ WebRenderBridgeChild::EndTransaction(wr:
   mParentCommands.Clear();
   mDestroyedActors.Clear();
   mIsInTransaction = false;
 }
 
 void
 WebRenderBridgeChild::ProcessWebRenderParentCommands()
 {
+  MOZ_ASSERT(!mDestroyed);
+
   if (mParentCommands.IsEmpty()) {
     return;
   }
   this->SendParentCommands(mParentCommands);
   mParentCommands.Clear();
 }
 
 void
 WebRenderBridgeChild::AddPipelineIdForAsyncCompositable(const wr::PipelineId& aPipelineId,
                                                         const CompositableHandle& aHandle)
 {
+  MOZ_ASSERT(!mDestroyed);
   SendAddPipelineIdForCompositable(aPipelineId, aHandle, true);
 }
 
 void
 WebRenderBridgeChild::AddPipelineIdForCompositable(const wr::PipelineId& aPipelineId,
                                                    const CompositableHandle& aHandle)
 {
+  MOZ_ASSERT(!mDestroyed);
   SendAddPipelineIdForCompositable(aPipelineId, aHandle, false);
 }
 
 void
 WebRenderBridgeChild::RemovePipelineIdForCompositable(const wr::PipelineId& aPipelineId)
 {
+  if (!IPCOpen()) {
+    return;
+  }
   SendRemovePipelineIdForCompositable(aPipelineId);
 }
 
 wr::ExternalImageId
 WebRenderBridgeChild::GetNextExternalImageId()
 {
   wr::MaybeExternalImageId id = GetCompositorBridgeChild()->GetNextExternalImageId();
   MOZ_RELEASE_ASSERT(id.isSome());
@@ -238,17 +245,17 @@ WriteFontFileData(const uint8_t* aData, 
   memcpy(data->mFontBuffer.mData, aData, aLength);
 
   data->mFontIndex = aIndex;
 }
 
 void
 WebRenderBridgeChild::PushGlyphs(wr::DisplayListBuilder& aBuilder, const nsTArray<gfx::Glyph>& aGlyphs,
                                  gfx::ScaledFont* aFont, const gfx::Color& aColor, const StackingContextHelper& aSc,
-                                 const LayerRect& aBounds, const LayerRect& aClip)
+                                 const LayerRect& aBounds, const LayerRect& aClip, bool aBackfaceVisible)
 {
   MOZ_ASSERT(aFont);
   MOZ_ASSERT(!aGlyphs.IsEmpty());
 
   wr::WrFontInstanceKey key = GetFontKeyForScaledFont(aFont);
   MOZ_ASSERT(key.mNamespace.mHandle && key.mHandle);
 
   nsTArray<wr::GlyphInstance> wr_glyph_instances;
@@ -257,16 +264,17 @@ WebRenderBridgeChild::PushGlyphs(wr::Dis
   for (size_t j = 0; j < aGlyphs.Length(); j++) {
     wr_glyph_instances[j].index = aGlyphs[j].mIndex;
     wr_glyph_instances[j].point = aSc.ToRelativeLayoutPoint(
             LayerPoint::FromUnknownPoint(aGlyphs[j].mPosition));
   }
 
   aBuilder.PushText(aSc.ToRelativeLayoutRect(aBounds),
                     aSc.ToRelativeLayoutRect(aClip),
+                    aBackfaceVisible,
                     aColor,
                     key,
                     Range<const wr::GlyphInstance>(wr_glyph_instances.Elements(), wr_glyph_instances.Length()));
 }
 
 wr::FontInstanceKey
 WebRenderBridgeChild::GetFontKeyForScaledFont(gfx::ScaledFont* aScaledFont)
 {
@@ -357,16 +365,17 @@ WebRenderBridgeChild::GetLayersIPCActor(
 {
   return static_cast<LayersIPCActor*>(GetCompositorBridgeChild());
 }
 
 void
 WebRenderBridgeChild::Connect(CompositableClient* aCompositable,
                               ImageContainer* aImageContainer)
 {
+  MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(aCompositable);
 
   static uint64_t sNextID = 1;
   uint64_t id = sNextID++;
 
   mCompositables.Put(id, aCompositable);
 
   CompositableHandle handle(id);
@@ -523,16 +532,20 @@ void
 WebRenderBridgeChild::BeginClearCachedResources()
 {
   mIsInClearCachedResources = true;
 }
 
 void
 WebRenderBridgeChild::EndClearCachedResources()
 {
+  if (!IPCOpen()) {
+    mIsInClearCachedResources = false;
+    return;
+  }
   ProcessWebRenderParentCommands();
   SendClearCachedResources();
   mIsInClearCachedResources = false;
 }
 
 ipc::IShmemAllocator*
 WebRenderBridgeChild::GetShmemAllocator()
 {
--- a/gfx/layers/wr/WebRenderBridgeChild.h
+++ b/gfx/layers/wr/WebRenderBridgeChild.h
@@ -111,17 +111,18 @@ public:
   wr::WrImageKey GetNextImageKey()
   {
     return wr::WrImageKey{ GetNamespace(), GetNextResourceId() };
   }
 
   void PushGlyphs(wr::DisplayListBuilder& aBuilder, const nsTArray<gfx::Glyph>& aGlyphs,
                   gfx::ScaledFont* aFont, const gfx::Color& aColor,
                   const StackingContextHelper& aSc,
-                  const LayerRect& aBounds, const LayerRect& aClip);
+                  const LayerRect& aBounds, const LayerRect& aClip,
+                  bool aBackfaceVisible);
 
   wr::FontInstanceKey GetFontKeyForScaledFont(gfx::ScaledFont* aScaledFont);
 
   void RemoveExpiredFontKeys();
   void ClearReadLocks();
 
   void BeginClearCachedResources();
   void EndClearCachedResources();
--- a/gfx/layers/wr/WebRenderCanvasLayer.cpp
+++ b/gfx/layers/wr/WebRenderCanvasLayer.cpp
@@ -68,17 +68,17 @@ WebRenderCanvasLayer::RenderLayer(wr::Di
   // Eww. Re-creating image keys every time is bad. Probably not worth fixing here
   // since layers-full webrender is going away soon-ish. But don't reproduce what
   // you see here.
   wr::WrImageKey key = GenerateImageKey();
   aResources.AddExternalImage(canvasRenderer->GetExternalImageId().value(), key);
   WrManager()->AddImageKeyForDiscard(key);
 
   wr::LayoutRect r = sc.ToRelativeLayoutRect(rect);
-  aBuilder.PushImage(r, r, filter, key);
+  aBuilder.PushImage(r, r, true, filter, key);
 }
 
 void
 WebRenderCanvasLayer::ClearCachedResources()
 {
   mCanvasRenderer->ClearCachedResources();
 }
 
--- a/gfx/layers/wr/WebRenderColorLayer.cpp
+++ b/gfx/layers/wr/WebRenderColorLayer.cpp
@@ -25,13 +25,13 @@ WebRenderColorLayer::RenderLayer(wr::Dis
 {
   ScrollingLayersHelper scroller(this, aBuilder, aResources, aSc);
   StackingContextHelper sc(aSc, aBuilder, this);
 
   LayerRect rect = Bounds();
   DumpLayerInfo("ColorLayer", rect);
 
   wr::LayoutRect r = sc.ToRelativeLayoutRect(rect);
-  aBuilder.PushRect(r, r, wr::ToColorF(mColor));
+  aBuilder.PushRect(r, r, true, wr::ToColorF(mColor));
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/wr/WebRenderContainerLayer.cpp
+++ b/gfx/layers/wr/WebRenderContainerLayer.cpp
@@ -135,13 +135,13 @@ WebRenderRefLayer::RenderLayer(wr::Displ
   // The conversion from ParentLayerPixel to LayerPixel below is a result of
   // changing the reference layer from "this layer" to the "the layer that
   // created aSc".
   LayerRect rect = ViewAs<LayerPixel>(bounds,
       PixelCastJustification::MovingDownToChildren);
   DumpLayerInfo("RefLayer", rect);
 
   wr::LayoutRect r = aSc.ToRelativeLayoutRect(rect);
-  aBuilder.PushIFrame(r, wr::AsPipelineId(mId));
+  aBuilder.PushIFrame(r, true, wr::AsPipelineId(mId));
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/wr/WebRenderImageLayer.cpp
+++ b/gfx/layers/wr/WebRenderImageLayer.cpp
@@ -203,17 +203,17 @@ WebRenderImageLayer::RenderLayer(wr::Dis
     // where it will be done when we build the display list for the iframe.
     // That happens in AsyncImagePipelineManager.
 
     LayerRect rect = ViewAs<LayerPixel>(bounds,
         PixelCastJustification::MovingDownToChildren);
     DumpLayerInfo("Image Layer async", rect);
 
     wr::LayoutRect r = aSc.ToRelativeLayoutRect(rect);
-    aBuilder.PushIFrame(r, mPipelineId.ref());
+    aBuilder.PushIFrame(r, true, mPipelineId.ref());
 
     gfx::Matrix4x4 scTransform = GetTransform();
     // Translate is applied as part of PushIFrame()
     scTransform.PostTranslate(-rect.x, -rect.y, 0);
     // Adjust transform as to apply origin
     LayerPoint scOrigin = Bounds().TopLeft();
     scTransform.PreTranslate(-scOrigin.x, -scOrigin.y, 0);
 
@@ -268,17 +268,17 @@ WebRenderImageLayer::RenderLayer(wr::Dis
 
   DumpLayerInfo("Image Layer", rect);
   if (gfxPrefs::LayersDump()) {
     printf_stderr("ImageLayer %p texture-filter=%s \n",
                   GetLayer(),
                   Stringify(filter).c_str());
   }
   wr::LayoutRect r = sc.ToRelativeLayoutRect(rect);
-  aBuilder.PushImage(r, r, filter, mKey.value());
+  aBuilder.PushImage(r, r, true, filter, mKey.value());
 }
 
 Maybe<wr::WrImageMask>
 WebRenderImageLayer::RenderMaskLayer(const StackingContextHelper& aSc,
                                      const gfx::Matrix4x4& aTransform,
                                      wr::IpcResourceUpdateQueue& aResources)
 {
   if (!mContainer) {
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -252,21 +252,16 @@ WebRenderLayerManager::CreateWebRenderCo
     nsDisplayList* itemSameCoordinateSystemChildren
       = item->GetSameCoordinateSystemChildren();
     if (item->ShouldFlattenAway(aDisplayListBuilder)) {
       aDisplayList->AppendToBottom(itemSameCoordinateSystemChildren);
       item->Destroy(aDisplayListBuilder);
       continue;
     }
 
-    if (item->BackfaceIsHidden() && aSc.IsBackfaceVisible()) {
-      item->Destroy(aDisplayListBuilder);
-      continue;
-    }
-
     savedItems.AppendToTop(item);
 
     bool forceNewLayerData = false;
     size_t layerCountBeforeRecursing = mLayerScrollData.size();
     if (apzEnabled) {
       // For some types of display items we want to force a new
       // WebRenderLayerScrollData object, to ensure we preserve the APZ-relevant
       // data that is in the display item.
@@ -416,17 +411,18 @@ WebRenderLayerManager::CreateImageKey(ns
     imageData->CreateAsyncImageWebRenderCommands(aBuilder,
                                                  aContainer,
                                                  aSc,
                                                  rect,
                                                  scBounds,
                                                  gfx::Matrix4x4(),
                                                  scaleToSize,
                                                  wr::ImageRendering::Auto,
-                                                 wr::MixBlendMode::Normal);
+                                                 wr::MixBlendMode::Normal,
+                                                 !aItem->BackfaceIsHidden());
     return Nothing();
   }
 
   AutoLockImage autoLock(aContainer);
   if (!autoLock.HasImage()) {
     return Nothing();
   }
   mozilla::layers::Image* image = autoLock.GetImage();
@@ -453,17 +449,17 @@ WebRenderLayerManager::PushImage(nsDispl
     return true;
   }
   if (!key) {
     return false;
   }
 
   auto r = aSc.ToRelativeLayoutRect(aRect);
   SamplingFilter sampleFilter = nsLayoutUtils::GetSamplingFilterForFrame(aItem->Frame());
-  aBuilder.PushImage(r, r, wr::ToImageRendering(sampleFilter), key.value());
+  aBuilder.PushImage(r, r, !aItem->BackfaceIsHidden(), wr::ToImageRendering(sampleFilter), key.value());
 
   return true;
 }
 
 static void
 PaintItemByDrawTarget(nsDisplayItem* aItem,
                       DrawTarget* aDT,
                       const LayerRect& aImageRect,
@@ -679,16 +675,17 @@ WebRenderLayerManager::PushItemAsImage(n
   if (!fallbackData) {
     return false;
   }
 
   wr::LayoutRect dest = aSc.ToRelativeLayoutRect(imageRect + offset);
   SamplingFilter sampleFilter = nsLayoutUtils::GetSamplingFilterForFrame(aItem->Frame());
   aBuilder.PushImage(dest,
                      dest,
+                     !aItem->BackfaceIsHidden(),
                      wr::ToImageRendering(sampleFilter),
                      fallbackData->GetKey().value());
   return true;
 }
 
 void
 WebRenderLayerManager::EndTransaction(DrawPaintedLayerCallback aCallback,
                                       void* aCallbackData,
@@ -962,17 +959,18 @@ void
 WebRenderLayerManager::AddCompositorAnimationsIdForDiscard(uint64_t aId)
 {
   mDiscardedCompositorAnimationsIds.AppendElement(aId);
 }
 
 void
 WebRenderLayerManager::DiscardCompositorAnimations()
 {
-  if (WrBridge()->IPCOpen() && !mDiscardedCompositorAnimationsIds.IsEmpty()) {
+  if (WrBridge()->IPCOpen() &&
+      !mDiscardedCompositorAnimationsIds.IsEmpty()) {
     WrBridge()->
       SendDeleteCompositorAnimations(mDiscardedCompositorAnimationsIds);
   }
   mDiscardedCompositorAnimationsIds.Clear();
 }
 
 void
 WebRenderLayerManager::DiscardLocalImages()
--- a/gfx/layers/wr/WebRenderPaintedLayer.cpp
+++ b/gfx/layers/wr/WebRenderPaintedLayer.cpp
@@ -102,17 +102,17 @@ WebRenderPaintedLayer::CreateWebRenderDi
   DumpLayerInfo("PaintedLayer", rect);
 
   wr::WrImageKey key = GenerateImageKey();
   aResources.AddExternalImage(mExternalImageId.value(), key);
   // TODO: reuse image keys!
   WrManager()->AddImageKeyForDiscard(key);
 
   wr::LayoutRect r = sc.ToRelativeLayoutRect(rect);
-  aBuilder.PushImage(r, r, wr::ImageRendering::Auto, key);
+  aBuilder.PushImage(r, r, true, wr::ImageRendering::Auto, key);
 }
 
 void
 WebRenderPaintedLayer::RenderLayer(wr::DisplayListBuilder& aBuilder,
                                    wr::IpcResourceUpdateQueue& aResources,
                                    const StackingContextHelper& aSc)
 {
   if (!SetupExternalImages()) {
--- a/gfx/layers/wr/WebRenderPaintedLayerBlob.cpp
+++ b/gfx/layers/wr/WebRenderPaintedLayerBlob.cpp
@@ -88,13 +88,14 @@ WebRenderPaintedLayerBlob::RenderLayer(w
 
   ScrollingLayersHelper scroller(this, aBuilder, aResources, aSc);
   StackingContextHelper sc(aSc, aBuilder, this);
   LayerRect rect = Bounds();
   DumpLayerInfo("PaintedLayer", rect);
 
   aBuilder.PushImage(sc.ToRelativeLayoutRect(LayerRect(mImageBounds)),
                      sc.ToRelativeLayoutRect(rect),
+                     true,
                      wr::ImageRendering::Auto, mImageKey.value());
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/wr/WebRenderTextLayer.cpp
+++ b/gfx/layers/wr/WebRenderTextLayer.cpp
@@ -38,14 +38,14 @@ WebRenderTextLayer::RenderLayer(wr::Disp
         // we apply it here. The glyphs that we push to WR should already be
         // taking the transform into account.
         GetTransform().TransformBounds(IntRectToRect(mBounds))
     );
     DumpLayerInfo("TextLayer", rect);
 
     for (GlyphArray& glyphs : mGlyphs) {
         WrBridge()->PushGlyphs(aBuilder, glyphs.glyphs(), mFont,
-                               glyphs.color().value(), aSc, rect, rect);
+                               glyphs.color().value(), aSc, rect, rect, true);
     }
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/wr/WebRenderUserData.cpp
+++ b/gfx/layers/wr/WebRenderUserData.cpp
@@ -130,17 +130,18 @@ void
 WebRenderImageData::CreateAsyncImageWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                                       ImageContainer* aContainer,
                                                       const StackingContextHelper& aSc,
                                                       const LayerRect& aBounds,
                                                       const LayerRect& aSCBounds,
                                                       const gfx::Matrix4x4& aSCTransform,
                                                       const gfx::MaybeIntSize& aScaleToSize,
                                                       const wr::ImageRendering& aFilter,
-                                                      const wr::MixBlendMode& aMixBlendMode)
+                                                      const wr::MixBlendMode& aMixBlendMode,
+                                                      bool aIsBackfaceVisible)
 {
   MOZ_ASSERT(aContainer->IsAsync());
   if (!mPipelineId) {
     // Alloc async image pipeline id.
     mPipelineId = Some(WrBridge()->GetCompositorBridgeChild()->GetNextPipelineId());
     WrBridge()->AddPipelineIdForAsyncCompositable(mPipelineId.ref(),
                                                   aContainer->GetAsyncContainerHandle());
   }
@@ -151,17 +152,17 @@ WebRenderImageData::CreateAsyncImageWebR
   //
   // We don't push a stacking context for this async image pipeline here.
   // Instead, we do it inside the iframe that hosts the image. As a result,
   // a bunch of the calculations normally done as part of that stacking
   // context need to be done manually and pushed over to the parent side,
   // where it will be done when we build the display list for the iframe.
   // That happens in AsyncImagePipelineManager.
   wr::LayoutRect r = aSc.ToRelativeLayoutRect(aBounds);
-  aBuilder.PushIFrame(r, mPipelineId.ref());
+  aBuilder.PushIFrame(r, aIsBackfaceVisible, mPipelineId.ref());
 
   WrBridge()->AddWebRenderParentCommand(OpUpdateAsyncImagePipeline(mPipelineId.value(),
                                                                    aSCBounds,
                                                                    aSCTransform,
                                                                    aScaleToSize,
                                                                    aFilter,
                                                                    aMixBlendMode));
 }
--- a/gfx/layers/wr/WebRenderUserData.h
+++ b/gfx/layers/wr/WebRenderUserData.h
@@ -90,17 +90,18 @@ public:
   void CreateAsyncImageWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                          ImageContainer* aContainer,
                                          const StackingContextHelper& aSc,
                                          const LayerRect& aBounds,
                                          const LayerRect& aSCBounds,
                                          const gfx::Matrix4x4& aSCTransform,
                                          const gfx::MaybeIntSize& aScaleToSize,
                                          const wr::ImageRendering& aFilter,
-                                         const wr::MixBlendMode& aMixBlendMode);
+                                         const wr::MixBlendMode& aMixBlendMode,
+                                         bool aIsBackfaceVisible);
 
   void CreateImageClientIfNeeded();
 
 protected:
   void CreateExternalImageIfNeeded();
 
   wr::MaybeExternalImageId mExternalImageId;
   Maybe<wr::ImageKey> mKey;
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -605,17 +605,17 @@ private:
   DECL_GFX_PREF(Live, "layers.offmainthreadcomposition.frame-rate", LayersCompositionFrameRate, int32_t,-1);
   DECL_GFX_PREF(Live, "layers.omtp.force-sync",                LayersOMTPForceSync, bool, false);
   DECL_GFX_PREF(Live, "layers.orientation.sync.timeout",       OrientationSyncMillis, uint32_t, (uint32_t)0);
   DECL_GFX_PREF(Once, "layers.prefer-opengl",                  LayersPreferOpenGL, bool, false);
   DECL_GFX_PREF(Live, "layers.progressive-paint",              ProgressivePaint, bool, false);
   DECL_GFX_PREF(Live, "layers.shared-buffer-provider.enabled", PersistentBufferProviderSharedEnabled, bool, false);
   DECL_GFX_PREF(Live, "layers.single-tile.enabled",            LayersSingleTileEnabled, bool, true);
   DECL_GFX_PREF(Once, "layers.stereo-video.enabled",           StereoVideoEnabled, bool, false);
-  DECL_GFX_PREF(Live, "layers.force-synchronous-resize",       LayersForceSynchronousResize, bool, false);
+  DECL_GFX_PREF(Live, "layers.force-synchronous-resize",       LayersForceSynchronousResize, bool, true);
 
   // We allow for configurable and rectangular tile size to avoid wasting memory on devices whose
   // screen size does not align nicely to the default tile size. Although layers can be any size,
   // they are often the same size as the screen, especially for width.
   DECL_GFX_PREF(Once, "layers.tile-width",                     LayersTileWidth, int32_t, 256);
   DECL_GFX_PREF(Once, "layers.tile-height",                    LayersTileHeight, int32_t, 256);
   DECL_GFX_PREF(Once, "layers.tile-initial-pool-size",         LayersTileInitialPoolSize, uint32_t, (uint32_t)50);
   DECL_GFX_PREF(Once, "layers.tile-pool-unused-size",          LayersTilePoolUnusedSize, uint32_t, (uint32_t)10);
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -658,33 +658,34 @@ DisplayListBuilder::Finalize(wr::LayoutS
 void
 DisplayListBuilder::PushStackingContext(const wr::LayoutRect& aBounds,
                                         const uint64_t& aAnimationId,
                                         const float* aOpacity,
                                         const gfx::Matrix4x4* aTransform,
                                         wr::TransformStyle aTransformStyle,
                                         const gfx::Matrix4x4* aPerspective,
                                         const wr::MixBlendMode& aMixBlendMode,
-                                        const nsTArray<wr::WrFilterOp>& aFilters)
+                                        const nsTArray<wr::WrFilterOp>& aFilters,
+                                        bool aIsBackfaceVisible)
 {
   wr::LayoutTransform matrix;
   if (aTransform) {
     matrix = ToLayoutTransform(*aTransform);
   }
   const wr::LayoutTransform* maybeTransform = aTransform ? &matrix : nullptr;
   wr::LayoutTransform perspective;
   if (aPerspective) {
     perspective = ToLayoutTransform(*aPerspective);
   }
   const wr::LayoutTransform* maybePerspective = aPerspective ? &perspective : nullptr;
   WRDL_LOG("PushStackingContext b=%s t=%s\n", mWrState, Stringify(aBounds).c_str(),
       aTransform ? Stringify(*aTransform).c_str() : "none");
   wr_dp_push_stacking_context(mWrState, aBounds, aAnimationId, aOpacity,
                               maybeTransform, aTransformStyle, maybePerspective,
-                              aMixBlendMode, aFilters.Elements(), aFilters.Length());
+                              aMixBlendMode, aFilters.Elements(), aFilters.Length(), aIsBackfaceVisible);
 }
 
 void
 DisplayListBuilder::PopStackingContext()
 {
   WRDL_LOG("PopStackingContext\n", mWrState);
   wr_dp_pop_stacking_context(mWrState);
 }
@@ -795,228 +796,246 @@ DisplayListBuilder::PopClipAndScrollInfo
   WRDL_LOG("PopClipAndScroll\n", mWrState);
   mScrollIdStack.pop_back();
   wr_dp_pop_clip_and_scroll_info(mWrState);
 }
 
 void
 DisplayListBuilder::PushRect(const wr::LayoutRect& aBounds,
                              const wr::LayoutRect& aClip,
+                             bool aIsBackfaceVisible,
                              const wr::ColorF& aColor)
 {
   WRDL_LOG("PushRect b=%s cl=%s c=%s\n", mWrState,
       Stringify(aBounds).c_str(),
       Stringify(aClip).c_str(),
       Stringify(aColor).c_str());
-  wr_dp_push_rect(mWrState, aBounds, aClip, aColor);
+  wr_dp_push_rect(mWrState, aBounds, aClip, aIsBackfaceVisible, aColor);
 }
 
 void
 DisplayListBuilder::PushLinearGradient(const wr::LayoutRect& aBounds,
                                        const wr::LayoutRect& aClip,
+                                       bool aIsBackfaceVisible,
                                        const wr::LayoutPoint& aStartPoint,
                                        const wr::LayoutPoint& aEndPoint,
                                        const nsTArray<wr::GradientStop>& aStops,
                                        wr::ExtendMode aExtendMode,
                                        const wr::LayoutSize aTileSize,
                                        const wr::LayoutSize aTileSpacing)
 {
   wr_dp_push_linear_gradient(mWrState,
-                             aBounds, aClip,
+                             aBounds, aClip, aIsBackfaceVisible,
                              aStartPoint, aEndPoint,
                              aStops.Elements(), aStops.Length(),
                              aExtendMode,
                              aTileSize, aTileSpacing);
 }
 
 void
 DisplayListBuilder::PushRadialGradient(const wr::LayoutRect& aBounds,
                                        const wr::LayoutRect& aClip,
+                                       bool aIsBackfaceVisible,
                                        const wr::LayoutPoint& aCenter,
                                        const wr::LayoutSize& aRadius,
                                        const nsTArray<wr::GradientStop>& aStops,
                                        wr::ExtendMode aExtendMode,
                                        const wr::LayoutSize aTileSize,
                                        const wr::LayoutSize aTileSpacing)
 {
   wr_dp_push_radial_gradient(mWrState,
-                             aBounds, aClip,
+                             aBounds, aClip, aIsBackfaceVisible,
                              aCenter, aRadius,
                              aStops.Elements(), aStops.Length(),
                              aExtendMode,
                              aTileSize, aTileSpacing);
 }
 
 void
 DisplayListBuilder::PushImage(const wr::LayoutRect& aBounds,
                               const wr::LayoutRect& aClip,
+                              bool aIsBackfaceVisible,
                               wr::ImageRendering aFilter,
                               wr::ImageKey aImage)
 {
   wr::LayoutSize size;
   size.width = aBounds.size.width;
   size.height = aBounds.size.height;
-  PushImage(aBounds, aClip, size, size, aFilter, aImage);
+  PushImage(aBounds, aClip, aIsBackfaceVisible, size, size, aFilter, aImage);
 }
 
 void
 DisplayListBuilder::PushImage(const wr::LayoutRect& aBounds,
                               const wr::LayoutRect& aClip,
+                              bool aIsBackfaceVisible,
                               const wr::LayoutSize& aStretchSize,
                               const wr::LayoutSize& aTileSpacing,
                               wr::ImageRendering aFilter,
                               wr::ImageKey aImage)
 {
   WRDL_LOG("PushImage b=%s cl=%s s=%s t=%s\n", mWrState,
       Stringify(aBounds).c_str(),
       Stringify(aClip).c_str(), Stringify(aStretchSize).c_str(),
       Stringify(aTileSpacing).c_str());
-  wr_dp_push_image(mWrState, aBounds, aClip, aStretchSize, aTileSpacing, aFilter, aImage);
+  wr_dp_push_image(mWrState, aBounds, aClip, aIsBackfaceVisible, aStretchSize, aTileSpacing, aFilter, aImage);
 }
 
 void
 DisplayListBuilder::PushYCbCrPlanarImage(const wr::LayoutRect& aBounds,
                                          const wr::LayoutRect& aClip,
+                                         bool aIsBackfaceVisible,
                                          wr::ImageKey aImageChannel0,
                                          wr::ImageKey aImageChannel1,
                                          wr::ImageKey aImageChannel2,
                                          wr::WrYuvColorSpace aColorSpace,
                                          wr::ImageRendering aRendering)
 {
   wr_dp_push_yuv_planar_image(mWrState,
                               aBounds,
                               aClip,
+                              aIsBackfaceVisible,
                               aImageChannel0,
                               aImageChannel1,
                               aImageChannel2,
                               aColorSpace,
                               aRendering);
 }
 
 void
 DisplayListBuilder::PushNV12Image(const wr::LayoutRect& aBounds,
                                   const wr::LayoutRect& aClip,
+                                  bool aIsBackfaceVisible,
                                   wr::ImageKey aImageChannel0,
                                   wr::ImageKey aImageChannel1,
                                   wr::WrYuvColorSpace aColorSpace,
                                   wr::ImageRendering aRendering)
 {
   wr_dp_push_yuv_NV12_image(mWrState,
                             aBounds,
                             aClip,
+                            aIsBackfaceVisible,
                             aImageChannel0,
                             aImageChannel1,
                             aColorSpace,
                             aRendering);
 }
 
 void
 DisplayListBuilder::PushYCbCrInterleavedImage(const wr::LayoutRect& aBounds,
                                               const wr::LayoutRect& aClip,
+                                              bool aIsBackfaceVisible,
                                               wr::ImageKey aImageChannel0,
                                               wr::WrYuvColorSpace aColorSpace,
                                               wr::ImageRendering aRendering)
 {
   wr_dp_push_yuv_interleaved_image(mWrState,
                                    aBounds,
                                    aClip,
+                                   aIsBackfaceVisible,
                                    aImageChannel0,
                                    aColorSpace,
                                    aRendering);
 }
 
 void
 DisplayListBuilder::PushIFrame(const wr::LayoutRect& aBounds,
+                               bool aIsBackfaceVisible,
                                PipelineId aPipeline)
 {
-  wr_dp_push_iframe(mWrState, aBounds, aPipeline);
+  wr_dp_push_iframe(mWrState, aBounds, aIsBackfaceVisible, aPipeline);
 }
 
 void
 DisplayListBuilder::PushBorder(const wr::LayoutRect& aBounds,
                                const wr::LayoutRect& aClip,
+                               bool aIsBackfaceVisible,
                                const wr::BorderWidths& aWidths,
                                const Range<const wr::BorderSide>& aSides,
                                const wr::BorderRadius& aRadius)
 {
   MOZ_ASSERT(aSides.length() == 4);
   if (aSides.length() != 4) {
     return;
   }
-  wr_dp_push_border(mWrState, aBounds, aClip,
+  wr_dp_push_border(mWrState, aBounds, aClip, aIsBackfaceVisible,
                     aWidths, aSides[0], aSides[1], aSides[2], aSides[3], aRadius);
 }
 
 void
 DisplayListBuilder::PushBorderImage(const wr::LayoutRect& aBounds,
                                     const wr::LayoutRect& aClip,
+                                    bool aIsBackfaceVisible,
                                     const wr::BorderWidths& aWidths,
                                     wr::ImageKey aImage,
                                     const wr::NinePatchDescriptor& aPatch,
                                     const wr::SideOffsets2D_f32& aOutset,
                                     const wr::RepeatMode& aRepeatHorizontal,
                                     const wr::RepeatMode& aRepeatVertical)
 {
-  wr_dp_push_border_image(mWrState, aBounds, aClip,
+  wr_dp_push_border_image(mWrState, aBounds, aClip, aIsBackfaceVisible,
                           aWidths, aImage, aPatch, aOutset,
                           aRepeatHorizontal, aRepeatVertical);
 }
 
 void
 DisplayListBuilder::PushBorderGradient(const wr::LayoutRect& aBounds,
                                        const wr::LayoutRect& aClip,
+                                       bool aIsBackfaceVisible,
                                        const wr::BorderWidths& aWidths,
                                        const wr::LayoutPoint& aStartPoint,
                                        const wr::LayoutPoint& aEndPoint,
                                        const nsTArray<wr::GradientStop>& aStops,
                                        wr::ExtendMode aExtendMode,
                                        const wr::SideOffsets2D_f32& aOutset)
 {
-  wr_dp_push_border_gradient(mWrState, aBounds, aClip,
+  wr_dp_push_border_gradient(mWrState, aBounds, aClip, aIsBackfaceVisible,
                              aWidths, aStartPoint, aEndPoint,
                              aStops.Elements(), aStops.Length(),
                              aExtendMode, aOutset);
 }
 
 void
 DisplayListBuilder::PushBorderRadialGradient(const wr::LayoutRect& aBounds,
                                              const wr::LayoutRect& aClip,
+                                             bool aIsBackfaceVisible,
                                              const wr::BorderWidths& aWidths,
                                              const wr::LayoutPoint& aCenter,
                                              const wr::LayoutSize& aRadius,
                                              const nsTArray<wr::GradientStop>& aStops,
                                              wr::ExtendMode aExtendMode,
                                              const wr::SideOffsets2D_f32& aOutset)
 {
   wr_dp_push_border_radial_gradient(
-    mWrState, aBounds, aClip, aWidths, aCenter,
+    mWrState, aBounds, aClip, aIsBackfaceVisible, aWidths, aCenter,
     aRadius, aStops.Elements(), aStops.Length(),
     aExtendMode, aOutset);
 }
 
 void
 DisplayListBuilder::PushText(const wr::LayoutRect& aBounds,
                              const wr::LayoutRect& aClip,
+                             bool aIsBackfaceVisible,
                              const gfx::Color& aColor,
                              wr::FontInstanceKey aFontKey,
                              Range<const wr::GlyphInstance> aGlyphBuffer,
                              const wr::GlyphOptions* aGlyphOptions)
 {
-  wr_dp_push_text(mWrState, aBounds, aClip,
+  wr_dp_push_text(mWrState, aBounds, aClip, aIsBackfaceVisible,
                   ToColorF(aColor),
                   aFontKey,
                   &aGlyphBuffer[0], aGlyphBuffer.length(),
                   aGlyphOptions);
 }
 
 void
 DisplayListBuilder::PushLine(const wr::LayoutRect& aClip,
+                             bool aIsBackfaceVisible,
                              const wr::Line& aLine)
 {
- wr_dp_push_line(mWrState, aClip, aLine.baseline, aLine.start, aLine.end,
+ wr_dp_push_line(mWrState, aClip, aIsBackfaceVisible, aLine.baseline, aLine.start, aLine.end,
                  aLine.orientation, aLine.width, aLine.color, aLine.style);
 
 /* TODO(Gankro): remove this
   LayoutRect rect;
   if (aLine.orientation == wr::LineOrientation::Horizontal) {
     rect.origin.x = aLine.start;
     rect.origin.y = aLine.baseline;
     rect.size.width = aLine.end - aLine.start;
@@ -1030,39 +1049,41 @@ DisplayListBuilder::PushLine(const wr::L
 
   PushRect(rect, aClip, aLine.color);
 */
 }
 
 void
 DisplayListBuilder::PushTextShadow(const wr::LayoutRect& aRect,
                                    const wr::LayoutRect& aClip,
+                                   bool aIsBackfaceVisible,
                                    const wr::TextShadow& aShadow)
 {
-  wr_dp_push_text_shadow(mWrState, aRect, aClip, aShadow);
+  wr_dp_push_text_shadow(mWrState, aRect, aClip, aIsBackfaceVisible, aShadow);
 }
 
 void
 DisplayListBuilder::PopTextShadow()
 {
   wr_dp_pop_text_shadow(mWrState);
 }
 
 void
 DisplayListBuilder::PushBoxShadow(const wr::LayoutRect& aRect,
                                   const wr::LayoutRect& aClip,
+                                  bool aIsBackfaceVisible,
                                   const wr::LayoutRect& aBoxBounds,
                                   const wr::LayoutVector2D& aOffset,
                                   const wr::ColorF& aColor,
                                   const float& aBlurRadius,
                                   const float& aSpreadRadius,
                                   const float& aBorderRadius,
                                   const wr::BoxShadowClipMode& aClipMode)
 {
-  wr_dp_push_box_shadow(mWrState, aRect, aClip,
+  wr_dp_push_box_shadow(mWrState, aRect, aClip, aIsBackfaceVisible,
                         aBoxBounds, aOffset, aColor,
                         aBlurRadius, aSpreadRadius, aBorderRadius,
                         aClipMode);
 }
 
 Maybe<wr::WrClipId>
 DisplayListBuilder::TopmostClipId()
 {
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -214,17 +214,18 @@ public:
 
   void PushStackingContext(const wr::LayoutRect& aBounds, // TODO: We should work with strongly typed rects
                            const uint64_t& aAnimationId,
                            const float* aOpacity,
                            const gfx::Matrix4x4* aTransform,
                            wr::TransformStyle aTransformStyle,
                            const gfx::Matrix4x4* aPerspective,
                            const wr::MixBlendMode& aMixBlendMode,
-                           const nsTArray<wr::WrFilterOp>& aFilters);
+                           const nsTArray<wr::WrFilterOp>& aFilters,
+                           bool aIsBackfaceVisible);
   void PopStackingContext();
 
   wr::WrClipId DefineClip(const wr::LayoutRect& aClipRect,
                           const nsTArray<wr::WrComplexClipRegion>* aComplex = nullptr,
                           const wr::WrImageMask* aMask = nullptr);
   void PushClip(const wr::WrClipId& aClipId, bool aRecordInStack = true);
   void PopClip(bool aRecordInStack = true);
 
@@ -238,127 +239,144 @@ public:
   void PopScrollLayer();
 
   void PushClipAndScrollInfo(const layers::FrameMetrics::ViewID& aScrollId,
                              const wr::WrClipId* aClipId);
   void PopClipAndScrollInfo();
 
   void PushRect(const wr::LayoutRect& aBounds,
                 const wr::LayoutRect& aClip,
+                bool aIsBackfaceVisible,
                 const wr::ColorF& aColor);
 
   void PushLinearGradient(const wr::LayoutRect& aBounds,
                           const wr::LayoutRect& aClip,
+                          bool aIsBackfaceVisible,
                           const wr::LayoutPoint& aStartPoint,
                           const wr::LayoutPoint& aEndPoint,
                           const nsTArray<wr::GradientStop>& aStops,
                           wr::ExtendMode aExtendMode,
                           const wr::LayoutSize aTileSize,
                           const wr::LayoutSize aTileSpacing);
 
   void PushRadialGradient(const wr::LayoutRect& aBounds,
                           const wr::LayoutRect& aClip,
+                          bool aIsBackfaceVisible,
                           const wr::LayoutPoint& aCenter,
                           const wr::LayoutSize& aRadius,
                           const nsTArray<wr::GradientStop>& aStops,
                           wr::ExtendMode aExtendMode,
                           const wr::LayoutSize aTileSize,
                           const wr::LayoutSize aTileSpacing);
 
   void PushImage(const wr::LayoutRect& aBounds,
                  const wr::LayoutRect& aClip,
+                 bool aIsBackfaceVisible,
                  wr::ImageRendering aFilter,
                  wr::ImageKey aImage);
 
   void PushImage(const wr::LayoutRect& aBounds,
                  const wr::LayoutRect& aClip,
+                 bool aIsBackfaceVisible,
                  const wr::LayoutSize& aStretchSize,
                  const wr::LayoutSize& aTileSpacing,
                  wr::ImageRendering aFilter,
                  wr::ImageKey aImage);
 
   void PushYCbCrPlanarImage(const wr::LayoutRect& aBounds,
                             const wr::LayoutRect& aClip,
+                            bool aIsBackfaceVisible,
                             wr::ImageKey aImageChannel0,
                             wr::ImageKey aImageChannel1,
                             wr::ImageKey aImageChannel2,
                             wr::WrYuvColorSpace aColorSpace,
                             wr::ImageRendering aFilter);
 
   void PushNV12Image(const wr::LayoutRect& aBounds,
                      const wr::LayoutRect& aClip,
+                     bool aIsBackfaceVisible,
                      wr::ImageKey aImageChannel0,
                      wr::ImageKey aImageChannel1,
                      wr::WrYuvColorSpace aColorSpace,
                      wr::ImageRendering aFilter);
 
   void PushYCbCrInterleavedImage(const wr::LayoutRect& aBounds,
                                  const wr::LayoutRect& aClip,
+                                 bool aIsBackfaceVisible,
                                  wr::ImageKey aImageChannel0,
                                  wr::WrYuvColorSpace aColorSpace,
                                  wr::ImageRendering aFilter);
 
   void PushIFrame(const wr::LayoutRect& aBounds,
+                  bool aIsBackfaceVisible,
                   wr::PipelineId aPipeline);
 
   // XXX WrBorderSides are passed with Range.
   // It is just to bypass compiler bug. See Bug 1357734.
   void PushBorder(const wr::LayoutRect& aBounds,
                   const wr::LayoutRect& aClip,
+                  bool aIsBackfaceVisible,
                   const wr::BorderWidths& aWidths,
                   const Range<const wr::BorderSide>& aSides,
                   const wr::BorderRadius& aRadius);
 
   void PushBorderImage(const wr::LayoutRect& aBounds,
                        const wr::LayoutRect& aClip,
+                       bool aIsBackfaceVisible,
                        const wr::BorderWidths& aWidths,
                        wr::ImageKey aImage,
                        const wr::NinePatchDescriptor& aPatch,
                        const wr::SideOffsets2D_f32& aOutset,
                        const wr::RepeatMode& aRepeatHorizontal,
                        const wr::RepeatMode& aRepeatVertical);
 
   void PushBorderGradient(const wr::LayoutRect& aBounds,
                           const wr::LayoutRect& aClip,
+                          bool aIsBackfaceVisible,
                           const wr::BorderWidths& aWidths,
                           const wr::LayoutPoint& aStartPoint,
                           const wr::LayoutPoint& aEndPoint,
                           const nsTArray<wr::GradientStop>& aStops,
                           wr::ExtendMode aExtendMode,
                           const wr::SideOffsets2D_f32& aOutset);
 
   void PushBorderRadialGradient(const wr::LayoutRect& aBounds,
                                 const wr::LayoutRect& aClip,
+                                bool aIsBackfaceVisible,
                                 const wr::BorderWidths& aWidths,
                                 const wr::LayoutPoint& aCenter,
                                 const wr::LayoutSize& aRadius,
                                 const nsTArray<wr::GradientStop>& aStops,
                                 wr::ExtendMode aExtendMode,
                                 const wr::SideOffsets2D_f32& aOutset);
 
   void PushText(const wr::LayoutRect& aBounds,
                 const wr::LayoutRect& aClip,
+                bool aIsBackfaceVisible,
                 const gfx::Color& aColor,
                 wr::FontInstanceKey aFontKey,
                 Range<const wr::GlyphInstance> aGlyphBuffer,
                 const wr::GlyphOptions* aGlyphOptions = nullptr);
 
   void PushLine(const wr::LayoutRect& aClip,
+                bool aIsBackfaceVisible,
                 const wr::Line& aLine);
 
   void PushTextShadow(const wr::LayoutRect& aBounds,
                       const wr::LayoutRect& aClip,
+                      bool aIsBackfaceVisible,
                       const wr::TextShadow& aShadow);
 
   void PopTextShadow();
 
 
 
   void PushBoxShadow(const wr::LayoutRect& aRect,
                      const wr::LayoutRect& aClip,
+                     bool aIsBackfaceVisible,
                      const wr::LayoutRect& aBoxBounds,
                      const wr::LayoutVector2D& aOffset,
                      const wr::ColorF& aColor,
                      const float& aBlurRadius,
                      const float& aSpreadRadius,
                      const float& aBorderRadius,
                      const wr::BoxShadowClipMode& aClipMode);
 
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -1075,17 +1075,17 @@ pub extern "C" fn wr_state_delete(state:
         Box::from_raw(state);
     }
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_begin(state: &mut WrState,
                               width: u32,
                               height: u32) {
-    assert!(unsafe { !is_in_render_thread() });
+    debug_assert!(unsafe { !is_in_render_thread() });
     state.frame_builder.dl_builder.data.clear();
 
     let bounds = LayoutRect::new(LayoutPoint::new(0.0, 0.0),
                                  LayoutSize::new(width as f32, height as f32));
     let prim_info = LayoutPrimitiveInfo::new(bounds);
 
     state.frame_builder
          .dl_builder
@@ -1095,32 +1095,33 @@ pub extern "C" fn wr_dp_begin(state: &mu
                                 TransformStyle::Flat,
                                 None,
                                 MixBlendMode::Normal,
                                 Vec::new());
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_end(state: &mut WrState) {
-    assert!(unsafe { !is_in_render_thread() });
+    debug_assert!(unsafe { !is_in_render_thread() });
     state.frame_builder.dl_builder.pop_stacking_context();
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_stacking_context(state: &mut WrState,
                                               bounds: LayoutRect,
                                               animation_id: u64,
                                               opacity: *const f32,
                                               transform: *const LayoutTransform,
                                               transform_style: TransformStyle,
                                               perspective: *const LayoutTransform,
                                               mix_blend_mode: MixBlendMode,
                                               filters: *const WrFilterOp,
-                                              filter_count: usize) {
-    assert!(unsafe { !is_in_render_thread() });
+                                              filter_count: usize,
+                                              is_backface_visible: bool) {
+    debug_assert!(unsafe { !is_in_render_thread() });
 
     let c_filters = make_slice(filters, filter_count);
     let mut filters : Vec<FilterOp> = c_filters.iter().map(|c_filter| {
         match c_filter.filter_type {
             WrFilterOpType::Blur => FilterOp::Blur(c_filter.argument),
             WrFilterOpType::Brightness => FilterOp::Brightness(c_filter.argument),
             WrFilterOpType::Contrast => FilterOp::Contrast(c_filter.argument),
             WrFilterOpType::Grayscale => FilterOp::Grayscale(c_filter.argument),
@@ -1151,42 +1152,44 @@ pub extern "C" fn wr_dp_push_stacking_co
     };
 
     let perspective_ref = unsafe { perspective.as_ref() };
     let perspective = match perspective_ref {
         Some(perspective) => Some(perspective.clone()),
         None => None,
     };
 
-    let prim_info = LayoutPrimitiveInfo::new(bounds);
+    let mut prim_info = LayoutPrimitiveInfo::new(bounds);
+    prim_info.is_backface_visible = is_backface_visible;
+
     state.frame_builder
          .dl_builder
          .push_stacking_context(&prim_info,
                                 webrender_api::ScrollPolicy::Scrollable,
                                 transform_binding,
                                 transform_style,
                                 perspective,
                                 mix_blend_mode,
                                 filters);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_pop_stacking_context(state: &mut WrState) {
-    assert!(unsafe { !is_in_render_thread() });
+    debug_assert!(unsafe { !is_in_render_thread() });
     state.frame_builder.dl_builder.pop_stacking_context();
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_define_clip(state: &mut WrState,
                                     clip_rect: LayoutRect,
                                     complex: *const WrComplexClipRegion,
                                     complex_count: usize,
                                     mask: *const WrImageMask)
                                     -> u64 {
-    assert!(unsafe { is_in_main_thread() });
+    debug_assert!(unsafe { is_in_main_thread() });
     let complex_slice = make_slice(complex, complex_count);
     let complex_iter = complex_slice.iter().map(|x| x.into());
     let mask : Option<ImageMask> = unsafe { mask.as_ref() }.map(|x| x.into());
 
     let clip_id = state.frame_builder.dl_builder.define_clip(None, clip_rect, complex_iter, mask);
     // return the u64 id value from inside the ClipId::Clip(..)
     match clip_id {
         ClipId::Clip(id, nesting_index, pipeline_id) => {
@@ -1196,23 +1199,23 @@ pub extern "C" fn wr_dp_define_clip(stat
         },
         _ => panic!("Got unexpected clip id type"),
     }
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_clip(state: &mut WrState,
                                   clip_id: u64) {
-    assert!(unsafe { is_in_main_thread() });
+    debug_assert!(unsafe { is_in_main_thread() });
     state.frame_builder.dl_builder.push_clip_id(ClipId::Clip(clip_id, 0, state.pipeline_id));
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_pop_clip(state: &mut WrState) {
-    assert!(unsafe { !is_in_render_thread() });
+    debug_assert!(unsafe { !is_in_render_thread() });
     state.frame_builder.dl_builder.pop_clip_id();
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_define_scroll_layer(state: &mut WrState,
                                             scroll_id: u64,
                                             content_rect: LayoutRect,
                                             clip_rect: LayoutRect) {
@@ -1221,24 +1224,24 @@ pub extern "C" fn wr_dp_define_scroll_la
     state.frame_builder.dl_builder.define_scroll_frame(
         Some(clip_id), content_rect, clip_rect, vec![], None,
         ScrollSensitivity::Script);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_scroll_layer(state: &mut WrState,
                                           scroll_id: u64) {
-    assert!(unsafe { is_in_main_thread() });
+    debug_assert!(unsafe { is_in_main_thread() });
     let clip_id = ClipId::new(scroll_id, state.pipeline_id);
     state.frame_builder.dl_builder.push_clip_id(clip_id);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_pop_scroll_layer(state: &mut WrState) {
-    assert!(unsafe { is_in_main_thread() });
+    debug_assert!(unsafe { is_in_main_thread() });
     state.frame_builder.dl_builder.pop_clip_id();
 }
 
 #[no_mangle]
 pub extern "C" fn wr_scroll_layer_with_id(dh: &mut DocumentHandle,
                                           pipeline_id: WrPipelineId,
                                           scroll_id: u64,
                                           new_scroll_origin: LayoutPoint) {
@@ -1246,190 +1249,208 @@ pub extern "C" fn wr_scroll_layer_with_i
     let clip_id = ClipId::new(scroll_id, pipeline_id);
     dh.api.scroll_node_with_id(dh.document_id, new_scroll_origin, clip_id, ScrollClamping::NoClamping);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_clip_and_scroll_info(state: &mut WrState,
                                                   scroll_id: u64,
                                                   clip_id: *const u64) {
-    assert!(unsafe { is_in_main_thread() });
+    debug_assert!(unsafe { is_in_main_thread() });
     let scroll_id = ClipId::new(scroll_id, state.pipeline_id);
     let info = if let Some(&id) = unsafe { clip_id.as_ref() } {
         ClipAndScrollInfo::new(
             scroll_id,
             ClipId::Clip(id, 0, state.pipeline_id))
     } else {
         ClipAndScrollInfo::simple(scroll_id)
     };
     state.frame_builder.dl_builder.push_clip_and_scroll_info(info);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_pop_clip_and_scroll_info(state: &mut WrState) {
-    assert!(unsafe { is_in_main_thread() });
+    debug_assert!(unsafe { is_in_main_thread() });
     state.frame_builder.dl_builder.pop_clip_id();
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_iframe(state: &mut WrState,
                                     rect: LayoutRect,
+                                    is_backface_visible: bool,
                                     pipeline_id: WrPipelineId) {
-    assert!(unsafe { is_in_main_thread() });
+    debug_assert!(unsafe { is_in_main_thread() });
 
-    let prim_info = LayoutPrimitiveInfo::new(rect);
+    let mut prim_info = LayoutPrimitiveInfo::new(rect);
+    prim_info.is_backface_visible = is_backface_visible;
     state.frame_builder.dl_builder.push_iframe(&prim_info, pipeline_id);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_rect(state: &mut WrState,
                                   rect: LayoutRect,
                                   clip: LayoutRect,
+                                  is_backface_visible: bool,
                                   color: ColorF) {
-    assert!(unsafe { !is_in_render_thread() });
+    debug_assert!(unsafe { !is_in_render_thread() });
 
-    let prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
+    let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
+    prim_info.is_backface_visible = is_backface_visible;
     state.frame_builder.dl_builder.push_rect(&prim_info,
                                              color);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_image(state: &mut WrState,
                                    bounds: LayoutRect,
                                    clip: LayoutRect,
+                                   is_backface_visible: bool,
                                    stretch_size: LayoutSize,
                                    tile_spacing: LayoutSize,
                                    image_rendering: ImageRendering,
                                    key: WrImageKey) {
-    assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() });
+    debug_assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() });
 
-    let prim_info = LayoutPrimitiveInfo::with_clip_rect(bounds, clip.into());
+    let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(bounds, clip.into());
+    prim_info.is_backface_visible = is_backface_visible;
     state.frame_builder
          .dl_builder
          .push_image(&prim_info,
                      stretch_size,
                      tile_spacing,
                      image_rendering,
                      key);
 }
 
 /// Push a 3 planar yuv image.
 #[no_mangle]
 pub extern "C" fn wr_dp_push_yuv_planar_image(state: &mut WrState,
                                               bounds: LayoutRect,
                                               clip: LayoutRect,
+                                              is_backface_visible: bool,
                                               image_key_0: WrImageKey,
                                               image_key_1: WrImageKey,
                                               image_key_2: WrImageKey,
                                               color_space: WrYuvColorSpace,
                                               image_rendering: ImageRendering) {
-    assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() });
+    debug_assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() });
 
-    let prim_info = LayoutPrimitiveInfo::with_clip_rect(bounds, clip.into());
+    let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(bounds, clip.into());
+    prim_info.is_backface_visible = is_backface_visible;
     state.frame_builder
          .dl_builder
          .push_yuv_image(&prim_info,
                          YuvData::PlanarYCbCr(image_key_0, image_key_1, image_key_2),
                          color_space,
                          image_rendering);
 }
 
 /// Push a 2 planar NV12 image.
 #[no_mangle]
 pub extern "C" fn wr_dp_push_yuv_NV12_image(state: &mut WrState,
                                             bounds: LayoutRect,
                                             clip: LayoutRect,
+                                            is_backface_visible: bool,
                                             image_key_0: WrImageKey,
                                             image_key_1: WrImageKey,
                                             color_space: WrYuvColorSpace,
                                             image_rendering: ImageRendering) {
-    assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() });
+    debug_assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() });
 
-    let prim_info = LayoutPrimitiveInfo::with_clip_rect(bounds, clip.into());
+    let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(bounds, clip.into());
+    prim_info.is_backface_visible = is_backface_visible;
     state.frame_builder
          .dl_builder
          .push_yuv_image(&prim_info,
                          YuvData::NV12(image_key_0, image_key_1),
                          color_space,
                          image_rendering);
 }
 
 /// Push a yuv interleaved image.
 #[no_mangle]
 pub extern "C" fn wr_dp_push_yuv_interleaved_image(state: &mut WrState,
                                                    bounds: LayoutRect,
                                                    clip: LayoutRect,
+                                                   is_backface_visible: bool,
                                                    image_key_0: WrImageKey,
                                                    color_space: WrYuvColorSpace,
                                                    image_rendering: ImageRendering) {
-    assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() });
+    debug_assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() });
 
-    let prim_info = LayoutPrimitiveInfo::with_clip_rect(bounds, clip.into());
+    let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(bounds, clip.into());
+    prim_info.is_backface_visible = is_backface_visible;
     state.frame_builder
          .dl_builder
          .push_yuv_image(&prim_info,
                          YuvData::InterleavedYCbCr(image_key_0),
                          color_space,
                          image_rendering);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_text(state: &mut WrState,
                                   bounds: LayoutRect,
                                   clip: LayoutRect,
+                                  is_backface_visible: bool,
                                   color: ColorF,
                                   font_key: WrFontInstanceKey,
                                   glyphs: *const GlyphInstance,
                                   glyph_count: u32,
                                   glyph_options: *const GlyphOptions) {
-    assert!(unsafe { is_in_main_thread() });
+    debug_assert!(unsafe { is_in_main_thread() });
 
     let glyph_slice = make_slice(glyphs, glyph_count as usize);
 
-    let prim_info = LayoutPrimitiveInfo::with_clip_rect(bounds, clip.into());
+    let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(bounds, clip.into());
+    prim_info.is_backface_visible = is_backface_visible;
     state.frame_builder
          .dl_builder
          .push_text(&prim_info,
                     &glyph_slice,
                     font_key,
                     color,
                     unsafe { glyph_options.as_ref().cloned() });
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_text_shadow(state: &mut WrState,
                                          bounds: LayoutRect,
                                          clip: LayoutRect,
+                                         is_backface_visible: bool,
                                          shadow: TextShadow) {
-    assert!(unsafe { is_in_main_thread() });
+    debug_assert!(unsafe { is_in_main_thread() });
 
-    let prim_info = LayoutPrimitiveInfo::with_clip_rect(bounds, clip.into());
+    let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(bounds, clip.into());
+    prim_info.is_backface_visible = is_backface_visible;
     state.frame_builder.dl_builder.push_text_shadow(&prim_info, shadow.into());
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_pop_text_shadow(state: &mut WrState) {
-    assert!(unsafe { is_in_main_thread() });
+    debug_assert!(unsafe { is_in_main_thread() });
 
     state.frame_builder.dl_builder.pop_text_shadow();
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_line(state: &mut WrState,
                                   clip: LayoutRect,
+                                  is_backface_visible: bool,
                                   baseline: f32,
                                   start: f32,
                                   end: f32,
                                   orientation: LineOrientation,
                                   width: f32,
                                   color: ColorF,
                                   style: LineStyle) {
-    assert!(unsafe { is_in_main_thread() });
+    debug_assert!(unsafe { is_in_main_thread() });
 
-    let prim_info = LayoutPrimitiveInfo::with_clip_rect(LayoutRect::zero(), clip.into());
+    let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(LayoutRect::zero(), clip.into());
+    prim_info.is_backface_visible = is_backface_visible;
     state.frame_builder
          .dl_builder
          .push_line(&prim_info,
                     baseline,
                     start,
                     end,
                     orientation,
                     width,
@@ -1437,212 +1458,226 @@ pub extern "C" fn wr_dp_push_line(state:
                     style);
 
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_border(state: &mut WrState,
                                     rect: LayoutRect,
                                     clip: LayoutRect,
+                                    is_backface_visible: bool,
                                     widths: BorderWidths,
                                     top: BorderSide,
                                     right: BorderSide,
                                     bottom: BorderSide,
                                     left: BorderSide,
                                     radius: BorderRadius) {
-    assert!(unsafe { is_in_main_thread() });
+    debug_assert!(unsafe { is_in_main_thread() });
 
     let border_details = BorderDetails::Normal(NormalBorder {
                                                    left: left.into(),
                                                    right: right.into(),
                                                    top: top.into(),
                                                    bottom: bottom.into(),
                                                    radius: radius.into(),
                                                });
-    let prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
+    let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
+    prim_info.is_backface_visible = is_backface_visible;
     state.frame_builder
          .dl_builder
          .push_border(&prim_info,
                       widths,
                       border_details);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_border_image(state: &mut WrState,
                                           rect: LayoutRect,
                                           clip: LayoutRect,
+                                          is_backface_visible: bool,
                                           widths: BorderWidths,
                                           image: WrImageKey,
                                           patch: NinePatchDescriptor,
                                           outset: SideOffsets2D<f32>,
                                           repeat_horizontal: RepeatMode,
                                           repeat_vertical: RepeatMode) {
-    assert!(unsafe { is_in_main_thread() });
+    debug_assert!(unsafe { is_in_main_thread() });
     let border_details =
         BorderDetails::Image(ImageBorder {
                                  image_key: image,
                                  patch: patch.into(),
                                  fill: false,
                                  outset: outset.into(),
                                  repeat_horizontal: repeat_horizontal.into(),
                                  repeat_vertical: repeat_vertical.into(),
                              });
-    let prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
+    let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
+    prim_info.is_backface_visible = is_backface_visible;
     state.frame_builder
          .dl_builder
          .push_border(&prim_info,
                       widths.into(),
                       border_details);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_border_gradient(state: &mut WrState,
                                              rect: LayoutRect,
                                              clip: LayoutRect,
+                                             is_backface_visible: bool,
                                              widths: BorderWidths,
                                              start_point: LayoutPoint,
                                              end_point: LayoutPoint,
                                              stops: *const GradientStop,
                                              stops_count: usize,
                                              extend_mode: ExtendMode,
                                              outset: SideOffsets2D<f32>) {
-    assert!(unsafe { is_in_main_thread() });
+    debug_assert!(unsafe { is_in_main_thread() });
 
     let stops_slice = make_slice(stops, stops_count);
     let stops_vector = stops_slice.to_owned();
 
     let border_details = BorderDetails::Gradient(GradientBorder {
                                                      gradient:
                                                          state.frame_builder
                                                               .dl_builder
                                                               .create_gradient(start_point.into(),
                                                                                end_point.into(),
                                                                                stops_vector,
                                                                                extend_mode.into()),
                                                      outset: outset.into(),
                                                  });
-    let prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
+    let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
+    prim_info.is_backface_visible = is_backface_visible;
     state.frame_builder
          .dl_builder
          .push_border(&prim_info,
                       widths.into(),
                       border_details);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_border_radial_gradient(state: &mut WrState,
                                                     rect: LayoutRect,
                                                     clip: LayoutRect,
+                                                    is_backface_visible: bool,
                                                     widths: BorderWidths,
                                                     center: LayoutPoint,
                                                     radius: LayoutSize,
                                                     stops: *const GradientStop,
                                                     stops_count: usize,
                                                     extend_mode: ExtendMode,
                                                     outset: SideOffsets2D<f32>) {
-    assert!(unsafe { is_in_main_thread() });
+    debug_assert!(unsafe { is_in_main_thread() });
 
     let stops_slice = make_slice(stops, stops_count);
     let stops_vector = stops_slice.to_owned();
 
     let border_details =
         BorderDetails::RadialGradient(RadialGradientBorder {
                                           gradient:
                                               state.frame_builder
                                                    .dl_builder
                                                    .create_radial_gradient(center.into(),
                                                                            radius.into(),
                                                                            stops_vector,
                                                                            extend_mode.into()),
                                           outset: outset.into(),
                                       });
-    let prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
+    let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
+    prim_info.is_backface_visible = is_backface_visible;
     state.frame_builder
          .dl_builder
          .push_border(&prim_info,
                       widths.into(),
                       border_details);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_linear_gradient(state: &mut WrState,
                                              rect: LayoutRect,
                                              clip: LayoutRect,
+                                             is_backface_visible: bool,
                                              start_point: LayoutPoint,
                                              end_point: LayoutPoint,
                                              stops: *const GradientStop,
                                              stops_count: usize,
                                              extend_mode: ExtendMode,
                                              tile_size: LayoutSize,
                                              tile_spacing: LayoutSize) {
-    assert!(unsafe { is_in_main_thread() });
+    debug_assert!(unsafe { is_in_main_thread() });
 
     let stops_slice = make_slice(stops, stops_count);
     let stops_vector = stops_slice.to_owned();
 
     let gradient = state.frame_builder
                         .dl_builder
                         .create_gradient(start_point.into(),
                                          end_point.into(),
                                          stops_vector,
                                          extend_mode.into());
-    let prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
+    let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
+    prim_info.is_backface_visible = is_backface_visible;
     state.frame_builder
          .dl_builder
          .push_gradient(&prim_info,
                         gradient,
                         tile_size.into(),
                         tile_spacing.into());
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_radial_gradient(state: &mut WrState,
                                              rect: LayoutRect,
                                              clip: LayoutRect,
+                                             is_backface_visible: bool,
                                              center: LayoutPoint,
                                              radius: LayoutSize,
                                              stops: *const GradientStop,
                                              stops_count: usize,
                                              extend_mode: ExtendMode,
                                              tile_size: LayoutSize,
                                              tile_spacing: LayoutSize) {
-    assert!(unsafe { is_in_main_thread() });
+    debug_assert!(unsafe { is_in_main_thread() });
 
     let stops_slice = make_slice(stops, stops_count);
     let stops_vector = stops_slice.to_owned();
 
     let gradient = state.frame_builder
                         .dl_builder
                         .create_radial_gradient(center.into(),
                                                 radius.into(),
                                                 stops_vector,
                                                 extend_mode.into());
-    let prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
+    let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
+    prim_info.is_backface_visible = is_backface_visible;
     state.frame_builder
          .dl_builder
          .push_radial_gradient(&prim_info,
                                gradient,
                                tile_size,
                                tile_spacing);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_box_shadow(state: &mut WrState,
                                         rect: LayoutRect,
                                         clip: LayoutRect,
+                                        is_backface_visible: bool,
                                         box_bounds: LayoutRect,
                                         offset: LayoutVector2D,
                                         color: ColorF,
                                         blur_radius: f32,
                                         spread_radius: f32,
                                         border_radius: f32,
                                         clip_mode: BoxShadowClipMode) {
-    assert!(unsafe { is_in_main_thread() });
+    debug_assert!(unsafe { is_in_main_thread() });
 
-    let prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
+    let mut prim_info = LayoutPrimitiveInfo::with_clip_rect(rect, clip.into());
+    prim_info.is_backface_visible = is_backface_visible;
     state.frame_builder
          .dl_builder
          .push_box_shadow(&prim_info,
                           box_bounds,
                           offset,
                           color,
                           blur_radius,
                           spread_radius,
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -881,66 +881,71 @@ WR_FUNC;
 WR_INLINE
 void wr_dp_pop_text_shadow(WrState *aState)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_border(WrState *aState,
                        LayoutRect aRect,
                        LayoutRect aClip,
+                       bool aIsBackfaceVisible,
                        BorderWidths aWidths,
                        BorderSide aTop,
                        BorderSide aRight,
                        BorderSide aBottom,
                        BorderSide aLeft,
                        BorderRadius aRadius)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_border_gradient(WrState *aState,
                                 LayoutRect aRect,
                                 LayoutRect aClip,
+                                bool aIsBackfaceVisible,
                                 BorderWidths aWidths,
                                 LayoutPoint aStartPoint,
                                 LayoutPoint aEndPoint,
                                 const GradientStop *aStops,
                                 size_t aStopsCount,
                                 ExtendMode aExtendMode,
                                 SideOffsets2D_f32 aOutset)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_border_image(WrState *aState,
                              LayoutRect aRect,
                              LayoutRect aClip,
+                             bool aIsBackfaceVisible,
                              BorderWidths aWidths,
                              WrImageKey aImage,
                              NinePatchDescriptor aPatch,
                              SideOffsets2D_f32 aOutset,
                              RepeatMode aRepeatHorizontal,
                              RepeatMode aRepeatVertical)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_border_radial_gradient(WrState *aState,
                                        LayoutRect aRect,
                                        LayoutRect aClip,
+                                       bool aIsBackfaceVisible,
                                        BorderWidths aWidths,
                                        LayoutPoint aCenter,
                                        LayoutSize aRadius,
                                        const GradientStop *aStops,
                                        size_t aStopsCount,
                                        ExtendMode aExtendMode,
                                        SideOffsets2D_f32 aOutset)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_box_shadow(WrState *aState,
                            LayoutRect aRect,
                            LayoutRect aClip,
+                           bool aIsBackfaceVisible,
                            LayoutRect aBoxBounds,
                            LayoutVector2D aOffset,
                            ColorF aColor,
                            float aBlurRadius,
                            float aSpreadRadius,
                            float aBorderRadius,
                            BoxShadowClipMode aClipMode)
 WR_FUNC;
@@ -960,71 +965,77 @@ WR_INLINE
 void wr_dp_push_clip_and_scroll_info(WrState *aState,
                                      uint64_t aScrollId,
                                      const uint64_t *aClipId)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_iframe(WrState *aState,
                        LayoutRect aRect,
+                       bool aIsBackfaceVisible,
                        WrPipelineId aPipelineId)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_image(WrState *aState,
                       LayoutRect aBounds,
                       LayoutRect aClip,
+                      bool aIsBackfaceVisible,
                       LayoutSize aStretchSize,
                       LayoutSize aTileSpacing,
                       ImageRendering aImageRendering,
                       WrImageKey aKey)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_line(WrState *aState,
                      LayoutRect aClip,
+                     bool aIsBackfaceVisible,
                      float aBaseline,
                      float aStart,
                      float aEnd,
                      LineOrientation aOrientation,
                      float aWidth,
                      ColorF aColor,
                      LineStyle aStyle)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_linear_gradient(WrState *aState,
                                 LayoutRect aRect,
                                 LayoutRect aClip,
+                                bool aIsBackfaceVisible,
                                 LayoutPoint aStartPoint,
                                 LayoutPoint aEndPoint,
                                 const GradientStop *aStops,
                                 size_t aStopsCount,
                                 ExtendMode aExtendMode,
                                 LayoutSize aTileSize,
                                 LayoutSize aTileSpacing)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_radial_gradient(WrState *aState,
                                 LayoutRect aRect,
                                 LayoutRect aClip,
+                                bool aIsBackfaceVisible,
                                 LayoutPoint aCenter,
                                 LayoutSize aRadius,
                                 const GradientStop *aStops,
                                 size_t aStopsCount,
                                 ExtendMode aExtendMode,
                                 LayoutSize aTileSize,
                                 LayoutSize aTileSpacing)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_rect(WrState *aState,
                      LayoutRect aRect,
                      LayoutRect aClip,
+                     bool aIsBackfaceVisible,
                      ColorF aColor)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_scroll_layer(WrState *aState,
                              uint64_t aScrollId)
 WR_FUNC;
 
@@ -1033,63 +1044,69 @@ void wr_dp_push_stacking_context(WrState
                                  LayoutRect aBounds,
                                  uint64_t aAnimationId,
                                  const float *aOpacity,
                                  const LayoutTransform *aTransform,
                                  TransformStyle aTransformStyle,
                                  const LayoutTransform *aPerspective,
                                  MixBlendMode aMixBlendMode,
                                  const WrFilterOp *aFilters,
-                                 size_t aFilterCount)
+                                 size_t aFilterCount,
+                                 bool aIsBackfaceVisible)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_text(WrState *aState,
                      LayoutRect aBounds,
                      LayoutRect aClip,
+                     bool aIsBackfaceVisible,
                      ColorF aColor,
                      WrFontInstanceKey aFontKey,
                      const GlyphInstance *aGlyphs,
                      uint32_t aGlyphCount,
                      const GlyphOptions *aGlyphOptions)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_text_shadow(WrState *aState,
                             LayoutRect aBounds,
                             LayoutRect aClip,
+                            bool aIsBackfaceVisible,
                             TextShadow aShadow)
 WR_FUNC;
 
 // Push a 2 planar NV12 image.
 WR_INLINE
 void wr_dp_push_yuv_NV12_image(WrState *aState,
                                LayoutRect aBounds,
                                LayoutRect aClip,
+                               bool aIsBackfaceVisible,
                                WrImageKey aImageKey0,
                                WrImageKey aImageKey1,
                                WrYuvColorSpace aColorSpace,
                                ImageRendering aImageRendering)
 WR_FUNC;
 
 // Push a yuv interleaved image.
 WR_INLINE
 void wr_dp_push_yuv_interleaved_image(WrState *aState,
                                       LayoutRect aBounds,
                                       LayoutRect aClip,
+                                      bool aIsBackfaceVisible,
                                       WrImageKey aImageKey0,
                                       WrYuvColorSpace aColorSpace,
                                       ImageRendering aImageRendering)
 WR_FUNC;
 
 // Push a 3 planar yuv image.
 WR_INLINE
 void wr_dp_push_yuv_planar_image(WrState *aState,
                                  LayoutRect aBounds,
                                  LayoutRect aClip,
+                                 bool aIsBackfaceVisible,
                                  WrImageKey aImageKey0,
                                  WrImageKey aImageKey1,
                                  WrImageKey aImageKey2,
                                  WrYuvColorSpace aColorSpace,
                                  ImageRendering aImageRendering)
 WR_FUNC;
 
 extern bool wr_moz2d_render_cb(ByteSlice aBlob,
--- a/js/src/devtools/rootAnalysis/analyzeHeapWrites.js
+++ b/js/src/devtools/rootAnalysis/analyzeHeapWrites.js
@@ -232,19 +232,16 @@ function treatAsSafeArgument(entry, varN
         ["Gecko_nsStyleFont_PrefillDefaultForGeneric", "aFont", null],
         ["Gecko_nsStyleSVG_SetContextPropertiesLength", "aSvg", null],
         ["Gecko_ClearAlternateValues", "aFont", null],
         ["Gecko_AppendAlternateValues", "aFont", null],
         ["Gecko_CopyAlternateValuesFrom", "aDest", null],
         ["Gecko_CounterStyle_GetName", "aResult", null],
         ["Gecko_CounterStyle_GetSingleString", "aResult", null],
         ["Gecko_EnsureMozBorderColors", "aBorder", null],
-        ["Gecko_ClearMozBorderColors", "aBorder", null],
-        ["Gecko_AppendMozBorderColors", "aBorder", null],
-        ["Gecko_CopyMozBorderColors", "aDest", null],
     ];
     for (var [entryMatch, varMatch, csuMatch] of whitelist) {
         assert(entryMatch || varMatch || csuMatch);
         if (entryMatch && !nameMatches(entry.name, entryMatch))
             continue;
         if (varMatch && !nameMatches(varName, varMatch))
             continue;
         if (csuMatch && (!csuName || !nameMatches(csuName, csuMatch)))
@@ -465,19 +462,16 @@ function ignoreContents(entry)
 
         // Unable to analyze safety of linked list initialization.
         "Gecko_NewCSSValueSharedList",
         "Gecko_CSSValue_InitSharedList",
 
         // Unable to trace through dataflow, but straightforward if inspected.
         "Gecko_NewNoneTransform",
 
-        // Bug 1400438
-        "Gecko_AppendMozBorderColors",
-
         // Need main thread assertions or other fixes.
         /EffectCompositor::GetServoAnimationRule/,
         /LookAndFeel::GetColor/,
     ];
     if (entry.matches(whitelist))
         return true;
 
     if (entry.isSafeArgument(0)) {
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -1563,16 +1563,28 @@ RestyleManager::ProcessRestyledFrames(ns
            !(frame->GetStateBits() & NS_STATE_IS_OUTER_SVG))) {
         SVGObserverUtils::InvalidateRenderingObservers(frame);
       }
       if (hint & nsChangeHint_NeedReflow) {
         StyleChangeReflow(frame, hint);
         didReflowThisFrame = true;
       }
 
+      // Here we need to propagate repaint frame change hint instead of update
+      // opacity layer change hint when we do opacity optimization for SVG.
+      // We can't do it in nsStyleEffects::CalcDifference() just like we do
+      // for the optimization for 0.99 over opacity values since we have no way
+      // to call nsSVGUtils::CanOptimizeOpacity() there.
+      if ((hint & nsChangeHint_UpdateOpacityLayer) &&
+          nsSVGUtils::CanOptimizeOpacity(frame) &&
+          frame->IsFrameOfType(nsIFrame::eSVGGeometry)) {
+        hint &= ~nsChangeHint_UpdateOpacityLayer;
+        hint |= nsChangeHint_RepaintFrame;
+      }
+
       if ((hint & nsChangeHint_UpdateUsesOpacity) &&
           frame->IsFrameOfType(nsIFrame::eTablePart)) {
         NS_ASSERTION(hint & nsChangeHint_UpdateOpacityLayer,
                      "should only return UpdateUsesOpacity hint "
                      "when also returning UpdateOpacityLayer hint");
         // When an internal table part (including cells) changes between
         // having opacity 1 and non-1, it changes whether its
         // backgrounds (and those of table parts inside of it) are
new file mode 100644
--- /dev/null
+++ b/layout/base/crashtests/1400438-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<style>
+div {
+  width: 100px; height: 100px;
+  border-left: 10px solid;
+  -moz-border-left-colors: red green;
+}
+</style>
+<div></div>
--- a/layout/base/crashtests/crashtests.list
+++ b/layout/base/crashtests/crashtests.list
@@ -496,9 +496,10 @@ load 1381323.html
 asserts-if(!stylo,1) load 1388625-1.html # bug 1389286
 load 1390389.html
 load 1395591-1.html
 load 1395715-1.html
 load 1397398-1.html
 load 1397398-2.html
 load 1397398-3.html
 load 1398500.html
+load 1400438-1.html
 load 1400599-1.html
--- a/layout/forms/nsButtonFrameRenderer.cpp
+++ b/layout/forms/nsButtonFrameRenderer.cpp
@@ -246,16 +246,17 @@ nsDisplayButtonBoxShadowOuter::CreateWeb
     mozilla::gfx::Point shadowOffset;
     shadowOffset.x = (shadow->mXOffset / appUnitsPerDevPixel);
     shadowOffset.y = (shadow->mYOffset / appUnitsPerDevPixel);
 
     float spreadRadius = float(shadow->mSpread) / float(appUnitsPerDevPixel);
 
     aBuilder.PushBoxShadow(deviceBoxRect,
                            deviceClipRect,
+                           !BackfaceIsHidden(),
                            deviceBoxRect,
                            wr::ToLayoutVector2D(shadowOffset),
                            wr::ToColorF(shadowColor),
                            blurRadius,
                            spreadRadius,
                            borderRadius,
                            wr::BoxShadowClipMode::Outset);
   }
--- a/layout/generic/nsBulletFrame.cpp
+++ b/layout/generic/nsBulletFrame.cpp
@@ -478,16 +478,17 @@ BulletRenderer::CreateWebRenderCommandsF
   }
 
   const int32_t appUnitsPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
   LayoutDeviceRect destRect = LayoutDeviceRect::FromAppUnits(mDest, appUnitsPerDevPixel);
   wr::LayoutRect dest = aSc.ToRelativeLayoutRect(destRect);
 
   aBuilder.PushImage(dest,
                      dest,
+                     !aItem->BackfaceIsHidden(),
                      wr::ImageRendering::Auto,
                      key.value());
 }
 
 void
 BulletRenderer::CreateWebRenderCommandsForPath(nsDisplayItem* aItem,
                                                wr::DisplayListBuilder& aBuilder,
                                                wr::IpcResourceUpdateQueue& aResources,
@@ -519,17 +520,17 @@ BulletRenderer::CreateWebRenderCommandsF
   LayerRect destRect = ViewAs<LayerPixel>(
       LayoutDeviceRect::FromAppUnits(
           aItem->GetBounds(aDisplayListBuilder, &dummy), appUnitsPerDevPixel),
       PixelCastJustification::WebRenderHasUnitResolution);
 
   for (layers::GlyphArray& glyphs : mGlyphs) {
     aManager->WrBridge()->PushGlyphs(aBuilder, glyphs.glyphs(), mFont,
                                      glyphs.color().value(),
-                                     aSc, destRect, destRect);
+                                     aSc, destRect, destRect, !aItem->BackfaceIsHidden());
   }
 }
 
 class nsDisplayBullet final : public nsDisplayItem {
 public:
   nsDisplayBullet(nsDisplayListBuilder* aBuilder, nsBulletFrame* aFrame)
     : nsDisplayItem(aBuilder, aFrame)
   {
--- a/layout/generic/nsCanvasFrame.cpp
+++ b/layout/generic/nsCanvasFrame.cpp
@@ -325,16 +325,17 @@ nsDisplayCanvasBackgroundColor::CreateWe
   int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
 
   LayoutDeviceRect rect = LayoutDeviceRect::FromAppUnits(
           bgClipRect, appUnitsPerDevPixel);
 
   wr::LayoutRect transformedRect = aSc.ToRelativeLayoutRect(rect);
   aBuilder.PushRect(transformedRect,
                     transformedRect,
+                    !BackfaceIsHidden(),
                     wr::ToColorF(ToDeviceColor(mColor)));
   return true;
 }
 
 #ifdef MOZ_DUMP_PAINTING
 void
 nsDisplayCanvasBackgroundColor::WriteDebugInfo(std::stringstream& aStream)
 {
--- a/layout/generic/nsHTMLCanvasFrame.cpp
+++ b/layout/generic/nsHTMLCanvasFrame.cpp
@@ -170,17 +170,17 @@ public:
         // We don't push a stacking context for this async image pipeline here.
         // Instead, we do it inside the iframe that hosts the image. As a result,
         // a bunch of the calculations normally done as part of that stacking
         // context need to be done manually and pushed over to the parent side,
         // where it will be done when we build the display list for the iframe.
         // That happens in WebRenderCompositableHolder.
 
         wr::LayoutRect r = aSc.ToRelativeLayoutRect(bounds);
-        aBuilder.PushIFrame(r, data->GetPipelineId().ref());
+        aBuilder.PushIFrame(r, !BackfaceIsHidden(), data->GetPipelineId().ref());
 
         gfx::Matrix4x4 scTransform;
         if (data->NeedsYFlip()) {
           scTransform = scTransform.PreTranslate(0, data->GetSize().height, 0).PreScale(1, -1, 1);
         }
 
         gfxRect destGFXRect = mFrame->PresContext()->AppUnitsToGfxUnits(dest);
         scTransform.PreScale(destGFXRect.Width() / canvasSizeInPx.width,
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -5197,53 +5197,55 @@ nsDisplayText::CreateWebRenderCommands(m
     layoutClipRect = LayoutDeviceRect::FromAppUnits(
                 GetClip().GetClipRect(), appUnitsPerDevPixel);
   }
 
   LayerRect boundsRect = LayerRect::FromUnknownRect(layoutBoundsRect.ToUnknownRect());
   LayerRect clipRect = LayerRect::FromUnknownRect(layoutClipRect.ToUnknownRect());
   wr::LayoutRect wrClipRect = aSc.ToRelativeLayoutRect(clipRect); // wr::ToLayoutRect(clipRect);
   wr::LayoutRect wrBoundsRect = aSc.ToRelativeLayoutRect(boundsRect); //wr::ToLayoutRect(boundsRect);
+  bool backfaceVisible = !BackfaceIsHidden();
 
   // Drawing order: selections, shadows,
   //                underline, overline, [grouped in one array]
   //                text, emphasisText,  [grouped in one array]
   //                lineThrough
 
   for (auto& part : mTextDrawer->GetParts()) {
     if (part.selection) {
       auto selection = part.selection.value();
-      aBuilder.PushRect(selection.rect, wrClipRect, selection.color);
+      aBuilder.PushRect(selection.rect, wrClipRect, backfaceVisible, selection.color);
     }
   }
 
   for (auto& part : mTextDrawer->GetParts()) {
     // WR takes the shadows in CSS-order (reverse of rendering order),
     // because the drawing of a shadow actually occurs when it's popped.
     for (const wr::TextShadow& shadow : part.shadows) {
-      aBuilder.PushTextShadow(wrBoundsRect, wrClipRect, shadow);
+      aBuilder.PushTextShadow(wrBoundsRect, wrClipRect, backfaceVisible, shadow);
     }
 
     for (const wr::Line& decoration : part.beforeDecorations) {
-      aBuilder.PushLine(wrClipRect, decoration);
+      aBuilder.PushLine(wrClipRect, backfaceVisible, decoration);
     }
 
     for (const mozilla::layout::TextRunFragment& text : part.text) {
       // mOpacity is set after we do our analysis, so we need to apply it here.
       // mOpacity is only non-trivial when we have "pure" text, so we don't
       // ever need to apply it to shadows or decorations.
       auto color = text.color;
       color.a *= mOpacity;
 
       aManager->WrBridge()->PushGlyphs(aBuilder, text.glyphs, text.font,
-                                       color, aSc, boundsRect, clipRect);
+                                       color, aSc, boundsRect, clipRect,
+                                       backfaceVisible);
     }
 
     for (const wr::Line& decoration : part.afterDecorations) {
-      aBuilder.PushLine(wrClipRect, decoration);
+      aBuilder.PushLine(wrClipRect, backfaceVisible, decoration);
     }
 
     for (size_t i = 0; i < part.shadows.Length(); ++i) {
       aBuilder.PopTextShadow();
     }
   }
 
   return true;
--- a/layout/ipc/RenderFrameParent.cpp
+++ b/layout/ipc/RenderFrameParent.cpp
@@ -392,16 +392,17 @@ nsDisplayRemote::CreateWebRenderCommands
 
   mOffset = mozilla::layout::GetContentRectLayerOffset(mFrame, aDisplayListBuilder);
 
   mozilla::LayoutDeviceRect visible = mozilla::LayoutDeviceRect::FromAppUnits(
       GetVisibleRect(), mFrame->PresContext()->AppUnitsPerDevPixel());
   visible += mOffset;
 
   aBuilder.PushIFrame(aSc.ToRelativeLayoutRect(visible),
+      !BackfaceIsHidden(),
       mozilla::wr::AsPipelineId(GetRemoteLayersId()));
 
   return true;
 }
 
 bool
 nsDisplayRemote::UpdateScrollData(mozilla::layers::WebRenderScrollData* aData,
                                   mozilla::layers::WebRenderLayerScrollData* aLayerData)
--- a/layout/painting/nsCSSRendering.cpp
+++ b/layout/painting/nsCSSRendering.cpp
@@ -756,23 +756,21 @@ ConstructBorderRenderer(nsPresContext* a
   Float borderWidths[4] = { Float(border.top) / oneDevPixel,
                                    Float(border.right) / oneDevPixel,
                                    Float(border.bottom) / oneDevPixel,
                                    Float(border.left) / oneDevPixel };
   Rect dirtyRect = NSRectToRect(aDirtyRect, oneDevPixel);
 
   uint8_t borderStyles[4];
   nscolor borderColors[4];
-  nsBorderColors* compositeColors[4];
-
-  // pull out styles, colors, composite colors
+
+  // pull out styles, colors
   NS_FOR_CSS_SIDES (i) {
     borderStyles[i] = aStyleBorder.GetBorderStyle(i);
     borderColors[i] = ourColor->CalcComplexColor(aStyleBorder.mBorderColor[i]);
-    aStyleBorder.GetCompositeColors(i, &compositeColors[i]);
   }
 
   PrintAsFormatString(" borderStyles: %d %d %d %d\n", borderStyles[0], borderStyles[1], borderStyles[2], borderStyles[3]);
 
   nsIDocument* document = nullptr;
   nsIContent* content = aForFrame->GetContent();
   if (content) {
     document = content->OwnerDoc();
@@ -782,18 +780,19 @@ ConstructBorderRenderer(nsPresContext* a
                              document,
                              aDrawTarget,
                              dirtyRect,
                              joinedBorderAreaPx,
                              borderStyles,
                              borderWidths,
                              bgRadii,
                              borderColors,
-                             compositeColors,
-                             bgColor);
+                             aStyleBorder.mBorderColors.get(),
+                             bgColor,
+                             !aForFrame->BackfaceIsHidden());
 }
 
 
 DrawResult
 nsCSSRendering::PaintBorderWithStyleBorder(nsPresContext* aPresContext,
                                            gfxContext& aRenderingContext,
                                            nsIFrame* aForFrame,
                                            const nsRect& aDirtyRect,
@@ -1062,17 +1061,18 @@ nsCSSRendering::CreateBorderRendererForO
                          dt,
                          dirtyRect,
                          oRect,
                          outlineStyles,
                          outlineWidths,
                          outlineRadii,
                          outlineColors,
                          nullptr,
-                         bgColor);
+                         bgColor,
+                         !aForFrame->BackfaceIsHidden());
 
   return Some(br);
 }
 
 void
 nsCSSRendering::PaintOutline(nsPresContext* aPresContext,
                              gfxContext& aRenderingContext,
                              nsIFrame* aForFrame,
@@ -1124,27 +1124,31 @@ nsCSSRendering::PaintFocus(nsPresContext
   nscolor focusColors[4] = { aColor, aColor, aColor, aColor };
 
   // Because this renders a dotted border, the background color
   // should not be used.  Therefore, we provide a value that will
   // be blatantly wrong if it ever does get used.  (If this becomes
   // something that CSS can style, this function will then have access
   // to a style context and can use the same logic that PaintBorder
   // and PaintOutline do.)
+  //
+  // WebRender layers-free mode don't use PaintFocus function. Just assign
+  // the backface-visibility to true for this case.
   nsCSSBorderRenderer br(aPresContext,
                          nullptr,
                          aDrawTarget,
                          focusRect,
                          focusRect,
                          focusStyles,
                          focusWidths,
                          focusRadii,
                          focusColors,
                          nullptr,
-                         NS_RGB(255, 0, 0));
+                         NS_RGB(255, 0, 0),
+                         true);
   br.DrawBorders();
 
   PrintAsStringNewline();
 }
 
 // Thebes Border Rendering Code End
 //----------------------------------------------------------------------
 
--- a/layout/painting/nsCSSRenderingBorders.cpp
+++ b/layout/painting/nsCSSRenderingBorders.cpp
@@ -85,19 +85,16 @@ static Color MakeBorderColor(nscolor aCo
 // border going inwards) and an array of line styles, calculate the
 // color that that stripe of the border should be rendered in.
 static Color ComputeColorForLine(uint32_t aLineIndex,
                                    const BorderColorStyle* aBorderColorStyle,
                                    uint32_t aBorderColorStyleCount,
                                    nscolor aBorderColor,
                                    nscolor aBackgroundColor);
 
-static Color ComputeCompositeColorForLine(uint32_t aLineIndex,
-                                          const nsBorderColors* aBorderColors);
-
 // little helper function to check if the array of 4 floats given are
 // equal to the given value
 static bool
 CheckFourFloatsEqual(const Float *vals, Float k)
 {
   return (vals[0] == k &&
           vals[1] == k &&
           vals[2] == k &&
@@ -173,34 +170,37 @@ nsCSSBorderRenderer::nsCSSBorderRenderer
                                          const nsIDocument* aDocument,
                                          DrawTarget* aDrawTarget,
                                          const Rect& aDirtyRect,
                                          Rect& aOuterRect,
                                          const uint8_t* aBorderStyles,
                                          const Float* aBorderWidths,
                                          RectCornerRadii& aBorderRadii,
                                          const nscolor* aBorderColors,
-                                         nsBorderColors* const* aCompositeColors,
-                                         nscolor aBackgroundColor)
+                                         const nsBorderColors* aCompositeColors,
+                                         nscolor aBackgroundColor,
+                                         bool aBackfaceIsVisible)
   : mPresContext(aPresContext),
     mDocument(aDocument),
     mDrawTarget(aDrawTarget),
     mDirtyRect(aDirtyRect),
     mOuterRect(aOuterRect),
     mBorderRadii(aBorderRadii),
-    mBackgroundColor(aBackgroundColor)
+    mBackgroundColor(aBackgroundColor),
+    mBackfaceIsVisible(aBackfaceIsVisible)
 {
   PodCopy(mBorderStyles, aBorderStyles, 4);
   PodCopy(mBorderWidths, aBorderWidths, 4);
   PodCopy(mBorderColors, aBorderColors, 4);
-  if (aCompositeColors) {
-    PodCopy(mCompositeColors, aCompositeColors, 4);
-  } else {
-    static nsBorderColors * const noColors[4] = { nullptr };
-    PodCopy(mCompositeColors, noColors, 4);
+  NS_FOR_CSS_SIDES(side) {
+    if (aCompositeColors && !(*aCompositeColors)[side].IsEmpty()) {
+      mCompositeColors[side] = &(*aCompositeColors)[side];
+    } else {
+      mCompositeColors[side] = nullptr;
+    }
   }
 
   mInnerRect = mOuterRect;
   mInnerRect.Deflate(
       Margin(mBorderStyles[0] != NS_STYLE_BORDER_STYLE_NONE ? mBorderWidths[0] : 0,
              mBorderStyles[1] != NS_STYLE_BORDER_STYLE_NONE ? mBorderWidths[1] : 0,
              mBorderStyles[2] != NS_STYLE_BORDER_STYLE_NONE ? mBorderWidths[2] : 0,
              mBorderStyles[3] != NS_STYLE_BORDER_STYLE_NONE ? mBorderWidths[3] : 0));
@@ -314,19 +314,21 @@ nsCSSBorderRenderer::AreBorderSideFinalS
     }
 
     if (((1 << i) & aSides) == 0) {
       continue;
     }
 
     if (mBorderStyles[firstStyle] != mBorderStyles[i] ||
         mBorderColors[firstStyle] != mBorderColors[i] ||
-        !nsBorderColors::Equal(mCompositeColors[firstStyle],
-                               mCompositeColors[i]))
+        !mCompositeColors[firstStyle] != !mCompositeColors[i] ||
+        (mCompositeColors[firstStyle] &&
+         *mCompositeColors[firstStyle] != *mCompositeColors[i])) {
       return false;
+    }
   }
 
   /* Then if it's one of the two-tone styles and we're not
    * just comparing the TL or BR sides */
   switch (mBorderStyles[firstStyle]) {
     case NS_STYLE_BORDER_STYLE_GROOVE:
     case NS_STYLE_BORDER_STYLE_RIDGE:
     case NS_STYLE_BORDER_STYLE_INSET:
@@ -1260,46 +1262,42 @@ ComputeColorForLine(uint32_t aLineIndex,
                     nscolor aBackgroundColor)
 {
   NS_ASSERTION(aLineIndex < aBorderColorStyleCount, "Invalid lineIndex given");
 
   return MakeBorderColor(aBorderColor, aBackgroundColor,
                          aBorderColorStyle[aLineIndex]);
 }
 
-Color
-ComputeCompositeColorForLine(uint32_t aLineIndex,
-                             const nsBorderColors* aBorderColors)
-{
-  while (aLineIndex-- && aBorderColors->mNext)
-    aBorderColors = aBorderColors->mNext;
-
-  return Color::FromABGR(aBorderColors->mColor);
-}
-
 void
-nsCSSBorderRenderer::DrawBorderSidesCompositeColors(int aSides, const nsBorderColors *aCompositeColors)
+nsCSSBorderRenderer::DrawBorderSidesCompositeColors(
+  int aSides, const nsTArray<nscolor>& aCompositeColors)
 {
   RectCornerRadii radii = mBorderRadii;
 
   // the generic composite colors path; each border is 1px in size
   Rect soRect = mOuterRect;
   Float maxBorderWidth = 0;
   NS_FOR_CSS_SIDES (i) {
     maxBorderWidth = std::max(maxBorderWidth, Float(mBorderWidths[i]));
   }
 
   Float fakeBorderSizes[4];
 
   Point itl = mInnerRect.TopLeft();
   Point ibr = mInnerRect.BottomRight();
 
+  MOZ_ASSERT(!aCompositeColors.IsEmpty());
+  Color compositeColor;
   for (uint32_t i = 0; i < uint32_t(maxBorderWidth); i++) {
-    ColorPattern color(ToDeviceColor(
-                         ComputeCompositeColorForLine(i, aCompositeColors)));
+    // advance to next color if exists.
+    if (i < aCompositeColors.Length()) {
+      compositeColor = ToDeviceColor(Color::FromABGR(aCompositeColors[i]));
+    }
+    ColorPattern color(compositeColor);
 
     Rect siRect = soRect;
     siRect.Deflate(1.0);
 
     // now cap the rects to the real mInnerRect
     Point tl = siRect.TopLeft();
     Point br = siRect.BottomRight();
 
@@ -1329,17 +1327,17 @@ nsCSSBorderRenderer::DrawBorderSides(int
 {
   if (aSides == 0 || (aSides & ~eSideBitsAll) != 0) {
     NS_WARNING("DrawBorderSides: invalid sides!");
     return;
   }
 
   uint8_t borderRenderStyle = NS_STYLE_BORDER_STYLE_NONE;
   nscolor borderRenderColor;
-  const nsBorderColors *compositeColors = nullptr;
+  const nsTArray<nscolor>* compositeColors = nullptr;
 
   uint32_t borderColorStyleCount = 0;
   BorderColorStyle borderColorStyleTopLeft[3], borderColorStyleBottomRight[3];
   BorderColorStyle *borderColorStyle = nullptr;
 
   NS_FOR_CSS_SIDES (i) {
     if ((aSides & (1 << i)) == 0)
       continue;
@@ -1387,17 +1385,17 @@ nsCSSBorderRenderer::DrawBorderSides(int
   // color is used for the remainder of the border's size.  Just
   // hand off to another function to do all that.
   if (compositeColors) {
     Float maxBorderWidth(0);
     NS_FOR_CSS_SIDES (i) {
       maxBorderWidth = std::max(maxBorderWidth, mBorderWidths[i]);
     }
     if (maxBorderWidth <= MAX_COMPOSITE_BORDER_WIDTH) {
-      DrawBorderSidesCompositeColors(aSides, compositeColors);
+      DrawBorderSidesCompositeColors(aSides, *compositeColors);
       return;
     }
     NS_WARNING("DrawBorderSides: too large border width for composite colors");
  }
 
   // We're not doing compositeColors, so we can calculate the
   // borderColorStyle based on the specified style.  The
   // borderColorStyle array goes from the outer to the inner style.
@@ -3140,80 +3138,73 @@ nsCSSBorderRenderer::DrawNoCompositeColo
                  firstColor, secondColor, skirtSize, skirtSlope);
     }
   }
 }
 
 void
 nsCSSBorderRenderer::DrawRectangularCompositeColors()
 {
-  nsBorderColors *currentColors[4];
-  memcpy(currentColors, mCompositeColors, sizeof(nsBorderColors*) * 4);
+  nscolor currentColors[4];
+  NS_FOR_CSS_SIDES(side) {
+    currentColors[side] = mBorderColors[side];
+  }
   Rect rect = mOuterRect;
   rect.Deflate(0.5);
 
   const twoFloats cornerAdjusts[4] = { { +0.5,  0   },
                                         {    0, +0.5 },
                                         { -0.5,  0   },
                                         {    0, -0.5 } };
 
   for (int i = 0; i < mBorderWidths[0]; i++) {
     NS_FOR_CSS_SIDES(side) {
+      // advance to the next composite color if one exists
+      if (mCompositeColors[side] &&
+          uint32_t(i) < mCompositeColors[side]->Length()) {
+        currentColors[side] = (*mCompositeColors[side])[i];
+      }
+    }
+    NS_FOR_CSS_SIDES(side) {
       int sideNext = (side + 1) % 4;
 
       Point firstCorner = rect.CCWCorner(side) + cornerAdjusts[side];
       Point secondCorner = rect.CWCorner(side) - cornerAdjusts[side];
 
-      Color currentColor = Color::FromABGR(
-        currentColors[side] ? currentColors[side]->mColor
-                            : mBorderColors[side]);
+      Color currentColor = Color::FromABGR(currentColors[side]);
 
       mDrawTarget->StrokeLine(firstCorner, secondCorner,
                               ColorPattern(ToDeviceColor(currentColor)));
 
       Point cornerTopLeft = rect.CWCorner(side) - Point(0.5, 0.5);
-      Color nextColor = Color::FromABGR(
-        currentColors[sideNext] ? currentColors[sideNext]->mColor
-                                : mBorderColors[sideNext]);
+      Color nextColor = Color::FromABGR(currentColors[sideNext]);
 
       Color cornerColor((currentColor.r + nextColor.r) / 2.f,
                         (currentColor.g + nextColor.g) / 2.f,
                         (currentColor.b + nextColor.b) / 2.f,
                         (currentColor.a + nextColor.a) / 2.f);
       mDrawTarget->FillRect(Rect(cornerTopLeft, Size(1, 1)),
                             ColorPattern(ToDeviceColor(cornerColor)));
-
-      if (side != 0) {
-        // We'll have to keep side 0 for the color averaging on side 3.
-        if (currentColors[side] && currentColors[side]->mNext) {
-          currentColors[side] = currentColors[side]->mNext;
-        }
-      }
-    }
-    // Now advance the color for side 0.
-    if (currentColors[0] && currentColors[0]->mNext) {
-      currentColors[0] = currentColors[0]->mNext;
     }
     rect.Deflate(1);
   }
 }
 
 void
 nsCSSBorderRenderer::DrawBorders()
 {
   bool forceSeparateCorners = false;
 
   if (mAllBordersSameStyle &&
       ((mCompositeColors[0] == nullptr &&
        (mBorderStyles[0] == NS_STYLE_BORDER_STYLE_NONE ||
         mBorderStyles[0] == NS_STYLE_BORDER_STYLE_HIDDEN ||
         mBorderColors[0] == NS_RGBA(0,0,0,0))) ||
-       (mCompositeColors[0] &&
-        (mCompositeColors[0]->mColor == NS_RGBA(0,0,0,0) &&
-         !mCompositeColors[0]->mNext))))
+       (mCompositeColors[0] && mCompositeColors[0]->Length() == 1 &&
+        (*mCompositeColors[0])[0] == NS_RGBA(0,0,0,0))))
   {
     // All borders are the same style, and the style is either none or hidden, or the color
     // is transparent.
     // This also checks if the first composite color is transparent, and there are
     // no others. It doesn't check if there are subsequent transparent ones, because
     // that would be very silly.
     return;
   }
@@ -3623,16 +3614,17 @@ nsCSSBorderRenderer::CreateWebRenderComm
 
   wr::BorderRadius borderRadius = wr::ToBorderRadius(LayerSize(mBorderRadii[0].width, mBorderRadii[0].height),
                                                      LayerSize(mBorderRadii[1].width, mBorderRadii[1].height),
                                                      LayerSize(mBorderRadii[3].width, mBorderRadii[3].height),
                                                      LayerSize(mBorderRadii[2].width, mBorderRadii[2].height));
   Range<const wr::BorderSide> wrsides(side, 4);
   aBuilder.PushBorder(transformedRect,
                       transformedRect,
+                      mBackfaceIsVisible,
                       wr::ToBorderWidths(mBorderWidths[0], mBorderWidths[1], mBorderWidths[2], mBorderWidths[3]),
                       wrsides,
                       borderRadius);
 }
 
 /* static */Maybe<nsCSSBorderImageRenderer>
 nsCSSBorderImageRenderer::CreateBorderImageRenderer(nsPresContext* aPresContext,
                                                     nsIFrame* aForFrame,
--- a/layout/painting/nsCSSRenderingBorders.h
+++ b/layout/painting/nsCSSRenderingBorders.h
@@ -95,18 +95,19 @@ public:
                       const nsIDocument* aDocument,
                       DrawTarget* aDrawTarget,
                       const Rect& aDirtyRect,
                       Rect& aOuterRect,
                       const uint8_t* aBorderStyles,
                       const Float* aBorderWidths,
                       RectCornerRadii& aBorderRadii,
                       const nscolor* aBorderColors,
-                      nsBorderColors* const* aCompositeColors,
-                      nscolor aBackgroundColor);
+                      const nsBorderColors* aCompositeColors,
+                      nscolor aBackgroundColor,
+                      bool aBackfaceIsVisible);
 
   // draw the entire border
   void DrawBorders();
 
   bool CanCreateWebRenderCommands();
   void CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                mozilla::wr::IpcResourceUpdateQueue& aResources,
                                const mozilla::layers::StackingContextHelper& aSc);
@@ -146,27 +147,30 @@ private:
   uint8_t mBorderStyles[4];
   Float mBorderWidths[4];
   RectCornerRadii mBorderRadii;
 
   // the colors for 'border-top-color' et. al.
   nscolor mBorderColors[4];
 
   // the lists of colors for '-moz-border-top-colors' et. al.
-  nsBorderColors* mCompositeColors[4];
+  // the pointers here are either nullptr, or referring to a non-empty
+  // nsTArray, so no additional empty check is needed.
+  const nsTArray<nscolor>* mCompositeColors[4];
 
   // the background color
   nscolor mBackgroundColor;
 
   // calculated values
   bool mAllBordersSameStyle;
   bool mAllBordersSameWidth;
   bool mOneUnitBorder;
   bool mNoBorderRadius;
   bool mAvoidStroke;
+  bool mBackfaceIsVisible;
 
   // For all the sides in the bitmask, would they be rendered
   // in an identical color and style?
   bool AreBorderSideFinalStylesSame(uint8_t aSides);
 
   // For the given style, is the given corner a solid color?
   bool IsSolidCornerStyle(uint8_t aStyle, mozilla::Corner aCorner);
 
@@ -230,17 +234,18 @@ private:
   // core rendering
   //
 
   // draw the border for the given sides, using the style of the first side
   // present in the bitmask
   void DrawBorderSides (int aSides);
 
   // function used by the above to handle -moz-border-colors
-  void DrawBorderSidesCompositeColors(int aSides, const nsBorderColors *compositeColors);
+  void DrawBorderSidesCompositeColors(
+    int aSides, const nsTArray<nscolor>& compositeColors);
 
   // Setup the stroke options for the given dashed/dotted side
   void SetupDashedOptions(StrokeOptions* aStrokeOptions,
                           Float aDash[2], mozilla::Side aSide,
                           Float aBorderLength, bool isCorner);
 
   // Draw the given dashed/dotte side
   void DrawDashedOrDottedSide(mozilla::Side aSide);
--- a/layout/painting/nsCSSRenderingGradients.cpp
+++ b/layout/painting/nsCSSRenderingGradients.cpp
@@ -1026,16 +1026,17 @@ nsCSSGradientRenderer::BuildWebRenderPar
 void
 nsCSSGradientRenderer::BuildWebRenderDisplayItems(wr::DisplayListBuilder& aBuilder,
                                                   const layers::StackingContextHelper& aSc,
                                                   layers::WebRenderDisplayItemLayer* aLayer,
                                                   const nsRect& aDest,
                                                   const nsRect& aFillArea,
                                                   const nsSize& aRepeatSize,
                                                   const CSSIntRect& aSrc,
+                                                  bool aIsBackfaceVisible,
                                                   float aOpacity)
 {
   if (aDest.IsEmpty() || aFillArea.IsEmpty()) {
     return;
   }
 
   wr::ExtendMode extendMode;
   nsTArray<wr::GradientStop> stops;
@@ -1080,29 +1081,31 @@ nsCSSGradientRenderer::BuildWebRenderDis
 
   if (mGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) {
     lineEnd.x = (lineEnd.x - srcTransform.x) * srcTransform.width;
     lineEnd.y = (lineEnd.y - srcTransform.y) * srcTransform.height;
 
     aBuilder.PushLinearGradient(
       wrGradientBounds,
       wrClipBounds,
+      aIsBackfaceVisible,
       mozilla::wr::ToLayoutPoint(lineStart),
       mozilla::wr::ToLayoutPoint(lineEnd),
       stops,
       extendMode,
       mozilla::wr::ToLayoutSize(layerFirstTileSize),
       mozilla::wr::ToLayoutSize(tileSpacing));
   } else {
     gradientRadius.width *= srcTransform.width;
     gradientRadius.height *= srcTransform.height;
 
     aBuilder.PushRadialGradient(
       wrGradientBounds,
       wrClipBounds,
+      aIsBackfaceVisible,
       mozilla::wr::ToLayoutPoint(lineStart),
       mozilla::wr::ToLayoutSize(gradientRadius),
       stops,
       extendMode,
       mozilla::wr::ToLayoutSize(layerFirstTileSize),
       mozilla::wr::ToLayoutSize(tileSpacing));
   }
 }
--- a/layout/painting/nsCSSRenderingGradients.h
+++ b/layout/painting/nsCSSRenderingGradients.h
@@ -82,16 +82,17 @@ public:
    */
   void BuildWebRenderDisplayItems(wr::DisplayListBuilder& aBuilder,
                                   const layers::StackingContextHelper& aSc,
                                   layers::WebRenderDisplayItemLayer* aLayer,
                                   const nsRect& aDest,
                                   const nsRect& aFill,
                                   const nsSize& aRepeatSize,
                                   const mozilla::CSSIntRect& aSrc,
+                                  bool aIsBackfaceVisible,
                                   float aOpacity = 1.0);
 
 private:
   nsCSSGradientRenderer() {}
 
   nsPresContext* mPresContext;
   nsStyleGradient* mGradient;
   nsTArray<ColorStop> mStops;
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -2941,16 +2941,17 @@ nsDisplaySolidColor::CreateWebRenderComm
   }
 
   LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
         mVisibleRect, mFrame->PresContext()->AppUnitsPerDevPixel());
   wr::LayoutRect transformedRect = aSc.ToRelativeLayoutRect(bounds);
 
   aBuilder.PushRect(transformedRect,
                     transformedRect,
+                    !BackfaceIsHidden(),
                     wr::ToColorF(ToDeviceColor(mColor)));
 
   return true;
 }
 
 nsRect
 nsDisplaySolidColorRegion::GetBounds(nsDisplayListBuilder* aBuilder,
                                      bool* aSnap) const
@@ -2992,16 +2993,17 @@ nsDisplaySolidColorRegion::CreateWebRend
 {
   for (auto iter = mRegion.RectIter(); !iter.Done(); iter.Next()) {
     nsRect rect = iter.Get();
     LayoutDeviceRect layerRects = LayoutDeviceRect::FromAppUnits(
       rect, mFrame->PresContext()->AppUnitsPerDevPixel());
     wr::LayoutRect transformedRect = aSc.ToRelativeLayoutRect(layerRects);
     aBuilder.PushRect(transformedRect,
                       transformedRect,
+                      !BackfaceIsHidden(),
                       wr::ToColorF(ToDeviceColor(mColor)));
   }
 
   return true;
 }
 
 static void
 RegisterThemeGeometry(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
@@ -4261,16 +4263,17 @@ nsDisplayBackgroundColor::CreateWebRende
   }
 
   LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
         mBackgroundRect, mFrame->PresContext()->AppUnitsPerDevPixel());
   wr::LayoutRect transformedRect = aSc.ToRelativeLayoutRect(bounds);
 
   aBuilder.PushRect(transformedRect,
                     transformedRect,
+                    !BackfaceIsHidden(),
                     wr::ToColorF(ToDeviceColor(mColor)));
 
   return true;
 }
 
 void
 nsDisplayBackgroundColor::Paint(nsDisplayListBuilder* aBuilder,
                                 gfxContext* aCtx)
@@ -4780,21 +4783,23 @@ nsDisplayCaret::CreateWebRenderCommands(
     hookRect + ToReferenceFrame(), appUnitsPerDevPixel);
 
   wr::LayoutRect caret = aSc.ToRelativeLayoutRect(devCaretRect);
   wr::LayoutRect hook = aSc.ToRelativeLayoutRect(devHookRect);
 
   // Note, WR will pixel snap anything that is layout aligned.
   aBuilder.PushRect(caret,
                     caret,
+                    !BackfaceIsHidden(),
                     wr::ToColorF(color));
 
   if (!devHookRect.IsEmpty()) {
     aBuilder.PushRect(hook,
                       hook,
+                      !BackfaceIsHidden(),
                       wr::ToColorF(color));
   }
   return true;
 }
 
 LayerState
 nsDisplayCaret::GetLayerState(nsDisplayListBuilder* aBuilder,
                               LayerManager* aManager,
@@ -5083,16 +5088,17 @@ nsDisplayBorder::CreateBorderImageWebRen
       gfx::IntSize size;
       Maybe<wr::ImageKey> key = aManager->CreateImageKey(this, container, aBuilder, aResources, aSc, size);
       if (key.isNothing()) {
         return;
       }
 
       aBuilder.PushBorderImage(dest,
                                clip,
+                               !BackfaceIsHidden(),
                                wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]),
                                key.value(),
                                wr::ToNinePatchDescriptor(
                                  (float)(mBorderImageRenderer->mImageSize.width) / appUnitsPerDevPixel,
                                  (float)(mBorderImageRenderer->mImageSize.height) / appUnitsPerDevPixel,
                                  wr::ToSideOffsets2D_u32(slice[0], slice[1], slice[2], slice[3])),
                                wr::ToSideOffsets2D_f32(outset[0], outset[1], outset[2], outset[3]),
                                wr::ToRepeatMode(mBorderImageRenderer->mRepeatModeHorizontal),
@@ -5116,25 +5122,27 @@ nsDisplayBorder::CreateBorderImageWebRen
       if (gradientData->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) {
         LayerPoint startPoint = LayerPoint(dest.origin.x, dest.origin.y);
         startPoint = startPoint + ViewAs<LayerPixel>(lineStart, PixelCastJustification::WebRenderHasUnitResolution);
         LayerPoint endPoint = LayerPoint(dest.origin.x, dest.origin.y);
         endPoint = endPoint + ViewAs<LayerPixel>(lineEnd, PixelCastJustification::WebRenderHasUnitResolution);
 
         aBuilder.PushBorderGradient(dest,
                                     clip,
+                                    !BackfaceIsHidden(),
                                     wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]),
                                     wr::ToLayoutPoint(startPoint),
                                     wr::ToLayoutPoint(endPoint),
                                     stops,
                                     extendMode,
                                     wr::ToSideOffsets2D_f32(outset[0], outset[1], outset[2], outset[3]));
       } else {
         aBuilder.PushBorderRadialGradient(dest,
                                           clip,
+                                          !BackfaceIsHidden(),
                                           wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]),
                                           wr::ToLayoutPoint(lineStart),
                                           wr::ToLayoutSize(gradientRadius),
                                           stops,
                                           extendMode,
                                           wr::ToSideOffsets2D_f32(outset[0], outset[1], outset[2], outset[3]));
       }
       break;
@@ -5447,16 +5455,17 @@ nsDisplayBoxShadowOuter::CreateWebRender
 
       // TODO: support non-uniform border radius.
       float borderRadius = hasBorderRadius ? borderRadii.TopLeft().width
                                            : 0.0;
       float spreadRadius = float(shadow->mSpread) / float(appUnitsPerDevPixel);
 
       aBuilder.PushBoxShadow(deviceBoxRect,
                              deviceClipRect,
+                             !BackfaceIsHidden(),
                              deviceBoxRect,
                              wr::ToLayoutVector2D(shadowOffset),
                              wr::ToColorF(shadowColor),
                              blurRadius,
                              spreadRadius,
                              borderRadius,
                              wr::BoxShadowClipMode::Outset);
     }
@@ -5619,16 +5628,17 @@ nsDisplayBoxShadowInner::CreateInsetBoxS
       float blurRadius = float(shadowItem->mRadius) / float(appUnitsPerDevPixel);
       // TODO: WR doesn't support non-uniform border radii
       float borderRadius = innerRadii.TopLeft().width;
       // NOTE: Any spread radius > 0 will render nothing. WR Bug.
       float spreadRadius = float(shadowItem->mSpread) / float(appUnitsPerDevPixel);
 
       aBuilder.PushBoxShadow(wr::ToLayoutRect(deviceBoxRect),
                              deviceClipRect,
+                             !aFrame->BackfaceIsHidden(),
                              wr::ToLayoutRect(deviceBoxRect),
                              wr::ToLayoutVector2D(shadowOffset),
                              wr::ToColorF(shadowColor),
                              blurRadius,
                              spreadRadius,
                              borderRadius,
                              wr::BoxShadowClipMode::Inset
                              );
@@ -7938,17 +7948,19 @@ nsDisplayTransform::CreateWebRenderComma
                            aDisplayListBuilder,
                            this,
                            mStoredList.GetChildren(),
                            &boundTransform,
                            animationsId,
                            nullptr,
                            transformForSC,
                            nullptr,
-                           filters);
+                           filters,
+                           gfx::CompositionOp::OP_OVER,
+                           !BackfaceIsHidden());
 
   return mStoredList.CreateWebRenderCommands(aBuilder, aResources, sc,
                                              aManager, aDisplayListBuilder);
 }
 
 bool
 nsDisplayTransform::UpdateScrollData(mozilla::layers::WebRenderScrollData* aData,
                                      mozilla::layers::WebRenderLayerScrollData* aLayerData)
@@ -8543,17 +8555,19 @@ nsDisplayPerspective::CreateWebRenderCom
                            aDisplayListBuilder,
                            this,
                            mList.GetChildren(),
                            nullptr,
                            0,
                            nullptr,
                            &transformForSC,
                            &perspectiveMatrix,
-                           filters);
+                           filters,
+                           gfx::CompositionOp::OP_OVER,
+                           !BackfaceIsHidden());
 
   return mList.CreateWebRenderCommands(aBuilder, aResources, sc,
                                        aManager, aDisplayListBuilder);
 }
 
 int32_t
 nsDisplayPerspective::ZIndex() const
 {
--- a/layout/painting/nsImageRenderer.cpp
+++ b/layout/painting/nsImageRenderer.cpp
@@ -610,17 +610,18 @@ nsImageRenderer::BuildWebRenderDisplayIt
   }
 
   switch (mType) {
     case eStyleImageType_Gradient:
     {
       nsCSSGradientRenderer renderer =
         nsCSSGradientRenderer::Create(aPresContext, mGradientData, mSize);
 
-      renderer.BuildWebRenderDisplayItems(aBuilder, aSc, aLayer, aDest, aFill, aRepeatSize, aSrc, aOpacity);
+      renderer.BuildWebRenderDisplayItems(aBuilder, aSc, aLayer, aDest, aFill,
+                                          aRepeatSize, aSrc, !aItem->BackfaceIsHidden(), aOpacity);
       break;
     }
     case eStyleImageType_Image:
     {
       // XXX(aosmond): We will support downscale-on-decode in bug 1368776. Until
       // then, don't pass FLAG_HIGH_QUALITY_SCALING.
       uint32_t containerFlags = imgIContainer::FLAG_NONE;
       if (mFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES) {
@@ -654,17 +655,17 @@ nsImageRenderer::BuildWebRenderDisplayIt
       wr::LayoutRect fill = aSc.ToRelativeLayoutRect(fillRect);
       wr::LayoutRect clip = aSc.ToRelativeLayoutRect(
           LayoutDeviceRect::FromAppUnits(aFill, appUnitsPerDevPixel));
 
       LayoutDeviceSize gapSize = LayoutDeviceSize::FromAppUnits(
           aRepeatSize - aDest.Size(), appUnitsPerDevPixel);
 
       SamplingFilter samplingFilter = nsLayoutUtils::GetSamplingFilterForFrame(mForFrame);
-      aBuilder.PushImage(fill, clip,
+      aBuilder.PushImage(fill, clip, !aItem->BackfaceIsHidden(),
                          wr::ToLayoutSize(destRect.Size()), wr::ToLayoutSize(gapSize),
                          wr::ToImageRendering(samplingFilter), key.value());
       break;
     }
     default:
       break;
   }
 
new file mode 100644
--- /dev/null
+++ b/layout/reftests/table-bordercollapse/bug1394226-notref.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Table border collapse</title>
+<style>
+  table {
+    border-collapse: collapse;
+  }
+  tr.odd {
+    background-color: LightCyan;
+  }
+  tr.even {
+    background-color: LightSkyBlue;
+  }
+  td {
+    border: 5px solid DarkBlue;
+  }
+  .inner td {
+    border: 5px solid red;
+  }
+  .inner tr:first-child td {
+    border-top: 5px solid DarkBlue;
+  }
+  .inner tr td:first-child {
+    border-left: 5px solid DarkBlue;
+  }
+  .inner tr:last-child td {
+    border-bottom: 5px solid DarkBlue;
+  }
+  .inner tr td:last-child {
+    border-right: 5px solid DarkBlue;
+  }
+  div {
+    height: 10px;
+  }
+</style>
+</head>
+<body>
+  <div></div>
+  <table>
+    <caption></caption>
+    <tr class="odd">
+      <td>Cell 1-1</td>
+      <td>Cell 1-2</td>
+    </tr>
+    <tr class="even">
+      <td>Cell 2-1</td>
+      <td>Cell 2-2
+        <table class="inner">
+          <tr class="odd">
+            <td>Cell 2-2/1-1</td>
+            <td>Cell 2-2/1-2</td>
+          </tr>
+          <tr class="even">
+            <td>Cell 2-2/2-1</td>
+            <td>Cell 2-2/2-2</td>
+          </tr>
+        </table>
+      </td>
+    </tr>
+  </table>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/table-bordercollapse/bug1394226-ref.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Table border collapse</title>
+<style>
+  table {
+    border-collapse: collapse;
+  }
+  tr.odd {
+    background-color: LightCyan;
+  }
+  tr.even {
+    background-color: LightSkyBlue;
+  }
+  td {
+    border: 5px solid DarkBlue;
+  }
+  div {
+    height: 10px;
+  }
+</style>
+</head>
+<body>
+  <div></div>
+  <table>
+    <tr class="odd">
+      <td>Cell 1-1</td>
+      <td>Cell 1-2</td>
+    </tr>
+    <tr class="even">
+      <td>Cell 2-1</td>
+      <td>Cell 2-2
+        <table>
+          <tr class="odd">
+            <td>Cell 2-2/1-1</td>
+            <td>Cell 2-2/1-2</td>
+          </tr>
+          <tr class="even">
+            <td>Cell 2-2/2-1</td>
+            <td>Cell 2-2/2-2</td>
+          </tr>
+        </table>
+      </td>
+    </tr>
+  </table>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/table-bordercollapse/bug1394226.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Table border collapse</title>
+<style>
+  table {
+    border-collapse: collapse;
+  }
+  tr.odd {
+    background-color: LightCyan;
+  }
+  tr.even {
+    background-color: LightSkyBlue;
+  }
+  td {
+    border: 5px solid DarkBlue;
+  }
+  caption {
+    height: 10px
+  }
+</style>
+</head>
+<body>
+  <table>
+    <caption></caption>
+    <tr class="odd">
+      <td>Cell 1-1</td>
+      <td>Cell 1-2</td>
+    </tr>
+    <tr class="even">
+      <td>Cell 2-1</td>
+      <td>Cell 2-2
+        <table>
+          <tr class="odd">
+            <td>Cell 2-2/1-1</td>
+            <td>Cell 2-2/1-2</td>
+          </tr>
+          <tr class="even">
+            <td>Cell 2-2/2-1</td>
+            <td>Cell 2-2/2-2</td>
+          </tr>
+        </table>
+      </td>
+    </tr>
+  </table>
+</body>
+</html>
--- a/layout/reftests/table-bordercollapse/reftest.list
+++ b/layout/reftests/table-bordercollapse/reftest.list
@@ -1,14 +1,16 @@
 == bug1375518.html bug1375518-ref.html
 == bug1375518-2.html bug1375518-ref.html
 == bug1375518-3.html bug1375518-ref.html
 == bug1375518-4.html bug1375518-4-ref.html
 == bug1375518-5.html bug1375518-5-ref.html
 == bug1379306.html bug1379306-ref.html
+== bug1394226.html bug1394226-ref.html
+!= bug1394226.html bug1394226-notref.html
 == bc_dyn_cell1.html bc_dyn_cell1_ref.html
 == bc_dyn_cell2.html bc_dyn_cell2_ref.html
 == bc_dyn_cell3.html bc_dyn_cell3_ref.html
 == bc_dyn_cell4.html bc_dyn_cell4_ref.html
 == bc_dyn_cell5.html bc_dyn_cell5_ref.html
 == bc_dyn_row1.html bc_dyn_rg1_ref.html
 == bc_dyn_row2.html bc_dyn_rg2_ref.html
 == bc_dyn_row3.html bc_dyn_rg3_ref.html
--- a/layout/reftests/transform-3d/reftest.list
+++ b/layout/reftests/transform-3d/reftest.list
@@ -34,19 +34,19 @@ fuzzy-if(winWidget,143,689) fuzzy-if(OSX
 == translatez-1a.html translatez-1-ref.html
 != translatez-1b.html translatez-1-ref.html
 == translate3d-1a.html translate3d-1-ref.html
 fuzzy-if(skiaContent,1,4) == matrix3d-1a.html matrix3d-1-ref.html
 == matrix3d-2a.html matrix3d-2-ref.html
 == rotate3d-1a.html rotatex-1-ref.html
 == rotate3d-2a.html rotatey-1-ref.html
 != backface-visibility-1a.html about:blank
-fails-if(webrender) == backface-visibility-1b.html about:blank
-fails-if(webrender) == backface-visibility-1c.html about:blank
-fuzzy-if(winWidget&&!layersGPUAccelerated,1,251) fails-if(webrender) == backface-visibility-2.html backface-visibility-2-ref.html
+== backface-visibility-1b.html about:blank
+== backface-visibility-1c.html about:blank
+fuzzy-if(winWidget&&!layersGPUAccelerated,1,251) == backface-visibility-2.html backface-visibility-2-ref.html
 == backface-visibility-3.html backface-visibility-3-ref.html
 fails-if(webrender) == perspective-clipping-1.html perspective-clipping-1-ref.html
 == perspective-clipping-2.html perspective-clipping-2-ref.html
 != perspective-origin-1a.html rotatex-perspective-1a.html
 fuzzy-if(webrender,0-1,0-1) == perspective-origin-1b.html perspective-origin-1a.html
 fuzzy(3,99) random-if(Android&&!browserIsRemote) == perspective-origin-2a.html perspective-origin-2-ref.html # subpixel AA, bug 732568
 fuzzy-if(winWidget&&!layersGPUAccelerated,1,61) == perspective-origin-3a.html perspective-origin-3-ref.html
 == perspective-origin-4a.html perspective-origin-4-ref.html
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -63,16 +63,18 @@ SERVO_BINDING_FUNC(Servo_StyleSheet_Clon
                    RawServoStyleSheetContentsBorrowed sheet,
                    const mozilla::ServoStyleSheet* reference_sheet);
 SERVO_BINDING_FUNC(Servo_StyleSheet_SizeOfIncludingThis, size_t,
                    mozilla::MallocSizeOf malloc_size_of,
                    mozilla::MallocSizeOf malloc_enclosing_size_of,
                    RawServoStyleSheetContentsBorrowed sheet)
 SERVO_BINDING_FUNC(Servo_StyleSheet_GetSourceMapURL, void,
                    RawServoStyleSheetContentsBorrowed sheet, nsAString* result)
+SERVO_BINDING_FUNC(Servo_StyleSheet_GetSourceURL, void,
+                   RawServoStyleSheetContentsBorrowed sheet, nsAString* result)
 // We'd like to return `OriginFlags` here, but bindgen bitfield enums don't
 // work as return values with the Linux 32-bit ABI at the moment because
 // they wrap the value in a struct.
 SERVO_BINDING_FUNC(Servo_StyleSheet_GetOrigin, uint8_t,
                    RawServoStyleSheetContentsBorrowed sheet)
 SERVO_BINDING_FUNC(Servo_StyleSet_Init, RawServoStyleSet*, RawGeckoPresContextOwned pres_context)
 SERVO_BINDING_FUNC(Servo_StyleSet_RebuildCachedData, void,
                    RawServoStyleSetBorrowed set)
@@ -542,16 +544,18 @@ SERVO_BINDING_FUNC(Servo_ComputedValues_
 // to RawServoStyleRule.
 SERVO_BINDING_FUNC(Servo_ComputedValues_GetStyleRuleList, void,
                    ServoStyleContextBorrowed values,
                    RawGeckoServoStyleRuleListBorrowedMut rules)
 
 // Initialize Servo components. Should be called exactly once at startup.
 SERVO_BINDING_FUNC(Servo_Initialize, void,
                    RawGeckoURLExtraData* dummy_url_data)
+// Initialize Servo on a cooperative Quantum DOM thread.
+SERVO_BINDING_FUNC(Servo_InitializeCooperativeThread, void);
 // Shut down Servo components. Should be called exactly once at shutdown.
 SERVO_BINDING_FUNC(Servo_Shutdown, void)
 
 // Restyle and change hints.
 SERVO_BINDING_FUNC(Servo_NoteExplicitHints, void, RawGeckoElementBorrowed element,
                    nsRestyleHint restyle_hint, nsChangeHint change_hint)
 // We'd like to return `nsChangeHint` here, but bindgen bitfield enums don't
 // work as return values with the Linux 32-bit ABI at the moment because
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -1251,44 +1251,16 @@ Gecko_AtomEqualsUTF8IgnoreCase(nsIAtom* 
 }
 
 void
 Gecko_EnsureMozBorderColors(nsStyleBorder* aBorder)
 {
   aBorder->EnsureBorderColors();
 }
 
-void Gecko_ClearMozBorderColors(nsStyleBorder* aBorder, mozilla::Side aSide)
-{
-  aBorder->ClearBorderColors(aSide);
-}
-
-void
-Gecko_AppendMozBorderColors(nsStyleBorder* aBorder, mozilla::Side aSide,
-                            nscolor aColor)
-{
-  aBorder->AppendBorderColor(aSide, aColor);
-}
-
-void
-Gecko_CopyMozBorderColors(nsStyleBorder* aDest, const nsStyleBorder* aSrc,
-                          mozilla::Side aSide)
-{
-  if (aSrc->mBorderColors) {
-    aDest->CopyBorderColorsFrom(aSrc->mBorderColors[aSide], aSide);
-  }
-}
-
-const nsBorderColors*
-Gecko_GetMozBorderColors(const nsStyleBorder* aBorder, mozilla::Side aSide)
-{
-  MOZ_ASSERT(aBorder);
-  return aBorder->mBorderColors ? aBorder->mBorderColors[aSide] : nullptr;
-}
-
 void
 Gecko_FontFamilyList_Clear(FontFamilyList* aList) {
   aList->Clear();
 }
 
 void
 Gecko_FontFamilyList_AppendNamed(FontFamilyList* aList, nsIAtom* aName, bool aQuoted)
 {
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -277,23 +277,16 @@ nsIAtom* Gecko_Atomize16(const nsAString
 void Gecko_AddRefAtom(nsIAtom* aAtom);
 void Gecko_ReleaseAtom(nsIAtom* aAtom);
 const uint16_t* Gecko_GetAtomAsUTF16(nsIAtom* aAtom, uint32_t* aLength);
 bool Gecko_AtomEqualsUTF8(nsIAtom* aAtom, const char* aString, uint32_t aLength);
 bool Gecko_AtomEqualsUTF8IgnoreCase(nsIAtom* aAtom, const char* aString, uint32_t aLength);
 
 // Border style
 void Gecko_EnsureMozBorderColors(nsStyleBorder* aBorder);
-void Gecko_ClearMozBorderColors(nsStyleBorder* aBorder, mozilla::Side aSide);
-void Gecko_AppendMozBorderColors(nsStyleBorder* aBorder, mozilla::Side aSide,
-                                 nscolor aColor);
-void Gecko_CopyMozBorderColors(nsStyleBorder* aDest, const nsStyleBorder* aSrc,
-                               mozilla::Side aSide);
-const nsBorderColors* Gecko_GetMozBorderColors(const nsStyleBorder* aBorder,
-                                               mozilla::Side aSide);
 
 // Font style
 void Gecko_FontFamilyList_Clear(FontFamilyList* aList);
 void Gecko_FontFamilyList_AppendNamed(FontFamilyList* aList, nsIAtom* aName, bool aQuoted);
 void Gecko_FontFamilyList_AppendGeneric(FontFamilyList* list, FontFamilyType familyType);
 void Gecko_CopyFontFamilyFrom(nsFont* dst, const nsFont* src);
 // will not run destructors on dst, give it uninitialized memory
 // font_id is LookAndFeel::FontID
--- a/layout/style/ServoBindings.toml
+++ b/layout/style/ServoBindings.toml
@@ -185,17 +185,16 @@ whitelist-types = [
     "gfxFontVariation",
     "GridNamedArea",
     "HalfCorner",
     "Image",
     "ImageURL",
     "Keyframe",
     "nsAttrName",
     "nsAttrValue",
-    "nsBorderColors",
     "nscolor",
     "nsChangeHint",
     "nsCSSCounterDesc",
     "nsCSSCounterStyleRule",
     "nsCSSFontFaceRule",
     "nsCSSKeyword",
     "nsCSSPropertyID",
     "nsCSSPropertyIDSet",
@@ -384,17 +383,16 @@ structs-types = [
     "mozilla::css::GridTemplateAreasValue",
     "mozilla::css::ErrorReporter",
     "mozilla::css::ImageValue",
     "mozilla::css::URLValue",
     "mozilla::css::URLValueData",
     "mozilla::AnonymousCounterStyle",
     "mozilla::MallocSizeOf",
     "mozilla::OriginFlags",
-    "mozilla::Side",
     "mozilla::UniquePtr",
     "ServoRawOffsetArc",
     "nsIContent",
     "nsIDocument",
     "nsIDocument_DocumentTheme",
     "RawGeckoAnimationPropertySegment",
     "RawGeckoComputedTiming",
     "RawGeckoCSSPropertyIDList",
@@ -438,17 +436,16 @@ structs-types = [
     "ServoElementSnapshotTable",
     "ServoStyleSetSizes",
     "SheetParsingMode",
     "StyleBasicShape",
     "StyleBasicShapeType",
     "StyleShapeSource",
     "StyleTransition",
     "gfxFontFeatureValueSet",
-    "nsBorderColors",
     "nsCSSCounterStyleRule",
     "nsCSSFontFaceRule",
     "nsCSSKeyword",
     "nsCSSPropertyID",
     "nsCSSPropertyIDSet",
     "nsCSSShadowArray",
     "nsCSSUnit",
     "nsCSSValue",
--- a/layout/style/ServoStyleSheet.cpp
+++ b/layout/style/ServoStyleSheet.cpp
@@ -220,16 +220,20 @@ ServoStyleSheet::ParseSheet(css::Loader*
                                                       aCompatMode,
                                                       aReusableSheets)
                          .Consume();
 
   nsString sourceMapURL;
   Servo_StyleSheet_GetSourceMapURL(Inner()->mContents, &sourceMapURL);
   SetSourceMapURLFromComment(sourceMapURL);
 
+  nsString sourceURL;
+  Servo_StyleSheet_GetSourceURL(Inner()->mContents, &sourceURL);
+  SetSourceURL(sourceURL);
+
   Inner()->mURLData = extraData.forget();
   return NS_OK;
 }
 
 nsresult
 ServoStyleSheet::ReparseSheet(const nsAString& aInput)
 {
   if (!mInner->mComplete) {
--- a/layout/style/StyleSheet.cpp
+++ b/layout/style/StyleSheet.cpp
@@ -256,16 +256,17 @@ StyleSheetInfo::StyleSheetInfo(StyleShee
   , mCORSMode(aCopy.mCORSMode)
   , mReferrerPolicy(aCopy.mReferrerPolicy)
   , mIntegrity(aCopy.mIntegrity)
   , mComplete(aCopy.mComplete)
   , mFirstChild()  // We don't rebuild the child because we're making a copy
                    // without children.
   , mSourceMapURL(aCopy.mSourceMapURL)
   , mSourceMapURLFromComment(aCopy.mSourceMapURLFromComment)
+  , mSourceURL(aCopy.mSourceURL)
 #ifdef DEBUG
   , mPrincipalSet(aCopy.mPrincipalSet)
 #endif
 {
   AddSheet(aPrimarySheet);
 }
 
 StyleSheetInfo::~StyleSheetInfo()
@@ -525,16 +526,28 @@ StyleSheet::SetSourceMapURL(const nsAStr
 }
 
 void
 StyleSheet::SetSourceMapURLFromComment(const nsAString& aSourceMapURLFromComment)
 {
   mInner->mSourceMapURLFromComment = aSourceMapURLFromComment;
 }
 
+void
+StyleSheet::GetSourceURL(nsAString& aSourceURL)
+{
+  aSourceURL = mInner->mSourceURL;
+}
+
+void
+StyleSheet::SetSourceURL(const nsAString& aSourceURL)
+{
+  mInner->mSourceURL = aSourceURL;
+}
+
 css::Rule*
 StyleSheet::GetDOMOwnerRule() const
 {
   return mOwnerRule;
 }
 
 uint32_t
 StyleSheet::InsertRule(const nsAString& aRule, uint32_t aIndex,
--- a/layout/style/StyleSheet.h
+++ b/layout/style/StyleSheet.h
@@ -211,16 +211,18 @@ public:
   inline StyleSheet* GetParentStyleSheet() const;
   // The XPCOM GetTitle is fine for WebIDL.
   dom::MediaList* Media();
   bool Disabled() const { return mDisabled; }
   // The XPCOM SetDisabled is fine for WebIDL.
   void GetSourceMapURL(nsAString& aTitle);
   void SetSourceMapURL(const nsAString& aSourceMapURL);
   void SetSourceMapURLFromComment(const nsAString& aSourceMapURLFromComment);
+  void GetSourceURL(nsAString& aSourceURL);
+  void SetSourceURL(const nsAString& aSourceURL);
 
   // WebIDL CSSStyleSheet API
   // Can't be inline because we can't include ImportRule here.  And can't be
   // called GetOwnerRule because that would be ambiguous with the ImportRule
   // version.
   css::Rule* GetDOMOwnerRule() const;
   dom::CSSRuleList* GetCssRules(nsIPrincipal& aSubjectPrincipal,
                                 ErrorResult& aRv);
--- a/layout/style/StyleSheetInfo.h
+++ b/layout/style/StyleSheetInfo.h
@@ -65,16 +65,19 @@ struct StyleSheetInfo
   // the value.  If both are seen, SourceMap is preferred.  If neither
   // is seen, this will be an empty string.
   nsString mSourceMapURL;
   // This stores any source map URL that might have been seen in a
   // comment in the style sheet.  This is separate from mSourceMapURL
   // so that the value does not overwrite any value that might have
   // come from a response header.
   nsString mSourceMapURLFromComment;
+  // This stores any source URL that might have been seen in a comment
+  // in the style sheet.
+  nsString mSourceURL;
 
 #ifdef DEBUG
   bool                   mPrincipalSet;
 #endif
 };
 
 } // namespace mozilla
 
new file mode 100644
--- /dev/null
+++ b/layout/style/crashtests/1400035.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html id="landing-page-home">
+<style>
+@keyframes opacity {
+  from { opacity: 0; }
+  to { opacity: 1; }
+}
+#circle {
+  animation: opacity 1s infinite;
+}
+</style>
+<svg width="100" height="100">
+  <mask id="mask">
+    <circle id="circle" cx="50" cy="50" r="50" fill="red"/>
+  </mask>
+  <rect class="rect" x="0" y="0" width="100" height="100" fill="blue" mask="url(#mask)"/>
+</svg>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/style/crashtests/1400926.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<style>
+@keyframes anim {
+  0% { background-color: black; }
+  100% { background-color: yellow; }
+}
+</style>
+<script>
+document.styleSheets[0].cssRules[0].cssRules[0].style.setProperty('background-color', 'red', 'important');
+</script>
--- a/layout/style/crashtests/crashtests.list
+++ b/layout/style/crashtests/crashtests.list
@@ -213,10 +213,12 @@ load 1384232.html
 load 1395725.html
 load 1396041.html
 load 1397363-1.html
 load 1397439-1.html
 load 1395719.html
 load 1397091.html
 load 1398479.html
 load 1398581.html
+load 1400035.html
 load 1399546.html
 load 1400325.html
+load 1400926.html
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -1643,16 +1643,17 @@ CSSParserImpl::ParseSheet(const nsAStrin
     }
     UngetToken();
     if (ParseRuleSet(AppendRuleToSheet, this)) {
       mSection = eCSSSection_General;
     }
   }
 
   mSheet->SetSourceMapURLFromComment(scanner.GetSourceMapURL());
+  mSheet->SetSourceURL(scanner.GetSourceURL());
   ReleaseScanner();
 
   mParsingMode = css::eAuthorSheetFeatures;
   mIsChrome = false;
   mReusableSheets = nullptr;
 
   return NS_OK;
 }
--- a/layout/style/nsCSSScanner.cpp
+++ b/layout/style/nsCSSScanner.cpp
@@ -538,92 +538,99 @@ nsCSSScanner::SkipWhitespace()
       AdvanceLine();
     } else {
       Advance();
     }
   }
 }
 
 /**
+ * If the given text appears at the current offset in the buffer,
+ * advance over the text and return true.  Otherwise, return false.
+ * mLength is the number of characters in mDirective.
+ */
+bool
+nsCSSScanner::CheckCommentDirective(const nsAString& aDirective)
+{
+  nsDependentSubstring text(&mBuffer[mOffset], &mBuffer[mCount]);
+
+  if (StringBeginsWith(text, aDirective)) {
+    Advance(aDirective.Length());
+    return true;
+  }
+  return false;
+}
+
+/**
  * Skip over one CSS comment starting at the current read position.
  */
 void
 nsCSSScanner::SkipComment()
 {
-  static const char sourceMappingURLDirective[] = "# sourceMappingURL=";
+  // Note that these do not start with "#" or "@" -- that is handled
+  // separately, below.
+  static NS_NAMED_LITERAL_STRING(kSourceMappingURLDirective, " sourceMappingURL=");
+  static NS_NAMED_LITERAL_STRING(kSourceURLDirective, " sourceURL=");
 
   MOZ_ASSERT(Peek() == '/' && Peek(1) == '*', "should not have been called");
   Advance(2);
-  // Look in each comment for a source map directive; using a simple
-  // state machine.  The states are:
-  // * sourceMapIndex >= 0 means that we're still looking for the
-  //   directive and expect the next character to be at that index of
-  //   sourceMappingURLDirective.
-  //   As a special case, when sourceMapIndex == 0, '@' is also recognized.
-  // * sourceMapIndex < 0 means that we don't need to look for the
-  //   directive any more -- whether it was found or not.
-  // * copying == true means that the directive was found and we're
-  //   copying characters into mSourceMapURL.  This stops at the first
-  //   whitespace, or at the end of the comment.
-  int sourceMapIndex = 0;
-  bool copying = false;
+
+  // If we saw one of the directives, this will be non-NULL and will
+  // point to the string into which the URL will be written.
+  nsString* directive = nullptr;
+  if (Peek() == '#' || Peek() == '@') {
+    // Check for the comment directives.
+    Advance();
+    if (CheckCommentDirective(kSourceMappingURLDirective)) {
+      mSourceMapURL.Truncate();
+      directive = &mSourceMapURL;
+    } else if (CheckCommentDirective(kSourceURLDirective)) {
+      mSourceURL.Truncate();
+      directive = &mSourceURL;
+    }
+  }
+
   for (;;) {
     int32_t ch = Peek();
     if (ch < 0) {
       if (mReporter)
         mReporter->ReportUnexpectedEOF("PECommentEOF");
       SetEOFCharacters(eEOFCharacters_Asterisk | eEOFCharacters_Slash);
       return;
     }
-    if (sourceMapIndex >= 0) {
-      if ((sourceMapIndex == 0 && ch == '@') || ch == sourceMappingURLDirective[sourceMapIndex]) {
-        ++sourceMapIndex;
-        if (sourceMappingURLDirective[sourceMapIndex] == '\0') {
-          sourceMapIndex = -1;
-          mSourceMapURL.Truncate();
-          copying = true;
-          Advance();
-          // Make sure we don't copy out the '=' by falling through.
-          continue;
-        }
-      } else {
-        // Did not see the directive.
-        sourceMapIndex = -1;
-      }
-    }
 
     if (ch == '*') {
       Advance();
       ch = Peek();
       if (ch < 0) {
         // In this case, even if we saw a source map directive, leave
         // the "*" out of it.
         if (mReporter)
           mReporter->ReportUnexpectedEOF("PECommentEOF");
         SetEOFCharacters(eEOFCharacters_Slash);
         return;
       }
       if (ch == '/') {
         Advance();
         return;
       }
-      if (copying) {
-        mSourceMapURL.Append('*');
+      if (directive != nullptr) {
+        directive->Append('*');
       }
     } else if (IsVertSpace(ch)) {
       AdvanceLine();
       // Done with the directive, so stop copying.
-      copying = false;
+      directive = nullptr;
     } else if (IsWhitespace(ch)) {
       Advance();
       // Done with the directive, so stop copying.
-      copying = false;
+      directive = nullptr;
     } else {
-      if (copying) {
-        mSourceMapURL.Append(ch);
+      if (directive != nullptr) {
+        directive->Append(ch);
       }
       Advance();
     }
   }
 }
 
 /**
  * If there is a valid escape sequence starting at the current read
--- a/layout/style/nsCSSScanner.h
+++ b/layout/style/nsCSSScanner.h
@@ -230,16 +230,19 @@ class nsCSSScanner {
   { return mTokenOffset; }
 
   uint32_t GetTokenEndOffset() const
   { return mOffset; }
 
   const nsAString& GetSourceMapURL() const
   { return mSourceMapURL; }
 
+  const nsAString& GetSourceURL() const
+  { return mSourceURL; }
+
   // Get the text of the line containing the first character of
   // the most recently processed token.
   nsDependentSubstring GetCurrentLine() const;
 
   // Get the next token.  Return false on EOF.  aTokenResult is filled
   // in with the data for the token.  aSkip controls whether
   // whitespace and/or comment tokens are ever returned.
   bool Next(nsCSSToken& aTokenResult, nsCSSScannerExclude aSkip);
@@ -325,16 +328,17 @@ class nsCSSScanner {
 #endif
 
 protected:
   int32_t Peek(uint32_t n = 0);
   void Advance(uint32_t n = 1);
   void AdvanceLine();
 
   void SkipWhitespace();
+  bool CheckCommentDirective(const nsAString& aDirective);
   void SkipComment();
 
   bool GatherEscape(nsString& aOutput, bool aInString);
   bool GatherText(uint8_t aClass, nsString& aIdent);
 
   bool ScanIdent(nsCSSToken& aResult);
   bool ScanAtKeyword(nsCSSToken& aResult);
   bool ScanHash(nsCSSToken& aResult);
@@ -361,16 +365,17 @@ protected:
 
   mozilla::css::ErrorReporter *mReporter;
 
   bool mRecording;
   bool mSeenBadToken;
   bool mSeenVariableReference;
 
   nsString mSourceMapURL;
+  nsString mSourceURL;
 };
 
 // Token for the grid-template-areas micro-syntax
 // http://dev.w3.org/csswg/css-grid/#propdef-grid-template-areas
 struct MOZ_STACK_CLASS nsCSSGridTemplateAreaToken {
   nsAutoString mName;  // Empty for a null cell, non-empty for a named cell
   bool isTrash;  // True for a trash token, mName is ignored in this case.
 };
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -5485,29 +5485,24 @@ nsComputedDOMStyle::GetLineHeightCoord(n
 }
 
 already_AddRefed<CSSValue>
 nsComputedDOMStyle::GetBorderColorsFor(mozilla::Side aSide)
 {
   const nsStyleBorder *border = StyleBorder();
 
   if (border->mBorderColors) {
-    nsBorderColors* borderColors = border->mBorderColors[aSide];
-    if (borderColors) {
+    const nsTArray<nscolor>& borderColors = (*border->mBorderColors)[aSide];
+    if (!borderColors.IsEmpty()) {
       RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
-
-      do {
+      for (nscolor color : borderColors) {
         RefPtr<nsROCSSPrimitiveValue> primitive = new nsROCSSPrimitiveValue;
-
-        SetToRGBAColor(primitive, borderColors->mColor);
-
+        SetToRGBAColor(primitive, color);
         valueList->AppendCSSValue(primitive.forget());
-        borderColors = borderColors->mNext;
-      } while (borderColors);
-
+      }
       return valueList.forget();
     }
   }
 
   RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
   val->SetIdent(eCSSKeyword_none);
   return val.forget();
 }
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -7743,38 +7743,35 @@ nsRuleNode::ComputeBorderData(void* aSta
     case eCSSUnit_Unset:
     case eCSSUnit_None:
       border->ClearBorderColors(side);
       break;
 
     case eCSSUnit_Inherit: {
       conditions.SetUncacheable();
       border->ClearBorderColors(side);
-      if (parentContext) {
-        nsBorderColors *parentColors;
-        parentBorder->GetCompositeColors(side, &parentColors);
-        if (parentColors) {
-          border->EnsureBorderColors();
-          border->mBorderColors[side] = parentColors->Clone();
-        }
+      if (parentBorder->mBorderColors) {
+        border->EnsureBorderColors();
+        border->mBorderColors->mColors[side] =
+          parentBorder->mBorderColors->mColors[side];
       }
       break;
     }
 
     case eCSSUnit_List:
     case eCSSUnit_ListDep: {
       // Some composite border color information has been specified for this
       // border side.
       border->EnsureBorderColors();
       border->ClearBorderColors(side);
       const nsCSSValueList* list = value.GetListValue();
       while (list) {
         if (SetColor(list->mValue, unused, mPresContext,
                      aContext, borderColor, conditions))
-          border->AppendBorderColor(side, borderColor);
+          border->mBorderColors->mColors[side].AppendElement(borderColor);
         else {
           NS_NOTREACHED("unexpected item in -moz-border-*-colors list");
         }
         list = list->mNext;
       }
       break;
     }
 
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -315,18 +315,17 @@ nsStylePadding::CalcDifference(const nsS
   // FIXME: It would be good to return a weaker hint here that doesn't
   // force reflow of all descendants, but the hint would need to force
   // reflow of the frame's children (see how
   // ReflowInput::InitResizeFlags initializes the inline-resize flag).
   return NS_STYLE_HINT_REFLOW & ~nsChangeHint_ClearDescendantIntrinsics;
 }
 
 nsStyleBorder::nsStyleBorder(const nsPresContext* aContext)
-  : mBorderColors(nullptr)
-  , mBorderImageFill(NS_STYLE_BORDER_IMAGE_SLICE_NOFILL)
+  : mBorderImageFill(NS_STYLE_BORDER_IMAGE_SLICE_NOFILL)
   , mBorderImageRepeatH(NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH)
   , mBorderImageRepeatV(NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH)
   , mFloatEdge(StyleFloatEdge::ContentBox)
   , mBoxDecorationBreak(StyleBoxDecorationBreak::Slice)
   , mComputedBorder(0, 0, 0, 0)
 {
   MOZ_COUNT_CTOR(nsStyleBorder);
 
@@ -344,72 +343,45 @@ nsStyleBorder::nsStyleBorder(const nsPre
     mBorder.Side(side) = medium;
     mBorderStyle[side] = NS_STYLE_BORDER_STYLE_NONE;
     mBorderColor[side] = StyleComplexColor::CurrentColor();
   }
 
   mTwipsPerPixel = aContext->DevPixelsToAppUnits(1);
 }
 
-nsBorderColors::~nsBorderColors()
-{