merge autoland to mozilla-central.r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Mon, 25 Sep 2017 11:56:13 +0200
changeset 382684 13ce77b78e364cc952d51b959f22202502be2941
parent 382651 7e962631ba4298bcefa571008661983d77c3e652 (current diff)
parent 382683 f01e65b7038feefbc93f198016c3c82ba5c02fb3 (diff)
child 382685 5f3f19824efa14cc6db546baf59c54a0fc15ddc9
push id95394
push userarchaeopteryx@coole-files.de
push dateMon, 25 Sep 2017 10:03:57 +0000
treeherdermozilla-inbound@ff48fab67d3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge autoland to mozilla-central.r=merge a=merge MozReview-Commit-ID: JRQXsnsRpCS
dom/indexedDB/test/bfcache_iframe1.html
dom/indexedDB/test/bfcache_iframe2.html
dom/media/webspeech/synth/test/file_bfcache_frame.html
dom/media/webspeech/synth/test/file_bfcache_frame2.html
dom/workers/test/WorkerDebugger_frozen_iframe1.html
dom/workers/test/WorkerDebugger_frozen_iframe2.html
dom/workers/test/suspend_iframe.html
layout/painting/nsDisplayList.cpp
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -2271,16 +2271,21 @@ CustomizeMode.prototype = {
     while (aReferenceNode &&
            aReferenceNode.localName == "toolbarpaletteitem" &&
            aReferenceNode.firstChild.hidden) {
       aReferenceNode = aReferenceNode.previousSibling;
     }
     return aReferenceNode;
   },
 
+  onPaletteContextMenuShowing(event) {
+   let isFlexibleSpace = event.target.triggerNode.id.includes("wrapper-customizableui-special-spring");
+   event.target.querySelector(".customize-context-addToPanel").disabled = isFlexibleSpace;
+ },
+
   onPanelContextMenuShowing(event) {
     let inPermanentArea = !!event.target.triggerNode.closest("#widget-overflow-fixed-list");
     let doc = event.target.ownerDocument;
     doc.getElementById("customizationPanelItemContextMenuUnpin").hidden = !inPermanentArea;
     doc.getElementById("customizationPanelItemContextMenuPin").hidden = inPermanentArea;
   },
 
   _checkForDownloadsClick(event) {
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -457,17 +457,18 @@
                      buttonhighlight="true"
                      hidden="true">
     <popupnotificationcontent id="update-restart-notification-content" orient="vertical">
       <description id="update-restart-description">&updateRestart.message2;</description>
     </popupnotificationcontent>
   </popupnotification>
 </panel>
 
-<menupopup id="customizationPaletteItemContextMenu">
+<menupopup id="customizationPaletteItemContextMenu"
+           onpopupshowing="gCustomizeMode.onPaletteContextMenuShowing(event)">
   <menuitem oncommand="gCustomizeMode.addToToolbar(document.popupNode)"
             class="customize-context-addToToolbar"
             accesskey="&customizeMenu.addToToolbar.accesskey;"
             label="&customizeMenu.addToToolbar.label;"/>
   <menuitem oncommand="gCustomizeMode.addToPanel(document.popupNode)"
             class="customize-context-addToPanel"
             accesskey="&customizeMenu.addToOverflowMenu.accesskey;"
             label="&customizeMenu.addToOverflowMenu.label;"/>
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -8319,26 +8319,21 @@ nsDocShell::CanSavePresentation(uint32_t
   }
 
   // Avoid doing the work of saving the presentation state in the case where
   // the content viewer cache is disabled.
   if (nsSHistory::GetMaxTotalViewers() == 0) {
     return false;
   }
 
-  // Don't cache the content viewer if we're in a subframe and the subframe
-  // pref is disabled.
-  bool cacheFrames =
-    Preferences::GetBool("browser.sessionhistory.cache_subframes", false);
-  if (!cacheFrames) {
-    nsCOMPtr<nsIDocShellTreeItem> root;
-    GetSameTypeParent(getter_AddRefs(root));
-    if (root && root != this) {
-      return false;  // this is a subframe load
-    }
+  // Don't cache the content viewer if we're in a subframe.
+  nsCOMPtr<nsIDocShellTreeItem> root;
+  GetSameTypeParent(getter_AddRefs(root));
+  if (root && root != this) {
+    return false;  // this is a subframe load
   }
 
   // If the document does not want its presentation cached, then don't.
   nsCOMPtr<nsIDocument> doc = mScriptGlobal->GetExtantDoc();
   return doc && doc->CanSavePresentation(aNewRequest);
 }
 
 void
--- a/docshell/shistory/nsISHistoryInternal.idl
+++ b/docshell/shistory/nsISHistoryInternal.idl
@@ -116,14 +116,28 @@ interface nsISHistoryInternal: nsISuppor
     *        The container to start looking for dynamic entries. Only the
     *        dynamic descendants of the container will be removed. If not given,
     *        all dynamic entries at the index will be removed.
     */
    [noscript, notxpcom] void RemoveDynEntries(in long aIndex,
                                               in nsISHContainer aContainer);
 
    /**
+    * Similar to RemoveDynEntries, but instead of specifying an index, use the
+    * given BFCacheEntry to find the index and remove dynamic entries from the
+    * index.
+    *
+    * The method takes no effect if the bfcache entry is not or no longer hold
+    * by the SHistory instance.
+    *
+    * @param aEntry
+    *        The bfcache entry to look up for index to remove dynamic entries
+    *        from.
+    */
+   [noscript, notxpcom] void RemoveDynEntriesForBFCacheEntry(in nsIBFCacheEntry aEntry);
+
+   /**
     * Removes entries from the history if their docshellID is in
     * aIDs array.
     */
    [noscript, notxpcom] void RemoveEntries(in nsDocshellIDArray aIDs,
                                            in long aStartIndex);
 };
--- a/docshell/shistory/nsSHEntryShared.cpp
+++ b/docshell/shistory/nsSHEntryShared.cpp
@@ -45,17 +45,28 @@ nsSHEntryShared::nsSHEntryShared()
   , mSticky(true)
   , mDynamicallyCreated(false)
   , mExpired(false)
 {
 }
 
 nsSHEntryShared::~nsSHEntryShared()
 {
+  // The destruction can be caused by either the entry is removed from session
+  // history and no one holds the reference, or the whole session history is on
+  // destruction. We want to ensure that we invoke
+  // shistory->RemoveFromExpirationTracker for the former case.
   RemoveFromExpirationTracker();
+
+  // Calling RemoveDynEntriesForBFCacheEntry on destruction is unnecessary since
+  // there couldn't be any SHEntry holding this shared entry, and we noticed
+  // that calling RemoveDynEntriesForBFCacheEntry in the middle of
+  // nsSHistory::Release can cause a crash, so set mSHistory to null explicitly
+  // before RemoveFromBFCacheSync.
+  mSHistory = nullptr;
   if (mContentViewer) {
     RemoveFromBFCacheSync();
   }
 }
 
 NS_IMPL_ISUPPORTS(nsSHEntryShared, nsIBFCacheEntry, nsIMutationObserver)
 
 already_AddRefed<nsSHEntryShared>
@@ -163,76 +174,76 @@ nsSHEntryShared::SetContentViewer(nsICon
   return NS_OK;
 }
 
 nsresult
 nsSHEntryShared::RemoveFromBFCacheSync()
 {
   MOZ_ASSERT(mContentViewer && mDocument, "we're not in the bfcache!");
 
+  // The call to DropPresentationState could drop the last reference, so hold
+  // |this| until RemoveDynEntriesForBFCacheEntry finishes.
+  RefPtr<nsSHEntryShared> kungFuDeathGrip = this;
+
+  // DropPresentationState would clear mContentViewer.
   nsCOMPtr<nsIContentViewer> viewer = mContentViewer;
   DropPresentationState();
 
-  // Warning! The call to DropPresentationState could have dropped the last
-  // reference to this object, so don't access members beyond here.
-
   if (viewer) {
     viewer->Destroy();
   }
 
+  // Now that we've dropped the viewer, we have to clear associated dynamic
+  // subframe entries.
+  nsCOMPtr<nsISHistoryInternal> shistory = do_QueryReferent(mSHistory);
+  if (shistory) {
+    shistory->RemoveDynEntriesForBFCacheEntry(this);
+  }
+
   return NS_OK;
 }
 
-class DestroyViewerEvent : public mozilla::Runnable
-{
-public:
-  DestroyViewerEvent(nsIContentViewer* aViewer, nsIDocument* aDocument)
-    : mozilla::Runnable("DestroyViewerEvent")
-    , mViewer(aViewer)
-    , mDocument(aDocument)
-  {
-  }
-
-  NS_IMETHOD Run() override
-  {
-    if (mViewer) {
-      mViewer->Destroy();
-    }
-    return NS_OK;
-  }
-
-  nsCOMPtr<nsIContentViewer> mViewer;
-  nsCOMPtr<nsIDocument> mDocument;
-};
-
 nsresult
 nsSHEntryShared::RemoveFromBFCacheAsync()
 {
   MOZ_ASSERT(mContentViewer && mDocument, "we're not in the bfcache!");
 
-  // Release the reference to the contentviewer asynchronously so that the
-  // document doesn't get nuked mid-mutation.
-
+  // Check it again to play safe in release builds.
   if (!mDocument) {
     return NS_ERROR_UNEXPECTED;
   }
-  nsCOMPtr<nsIRunnable> evt = new DestroyViewerEvent(mContentViewer, mDocument);
-  nsresult rv = mDocument->Dispatch(mozilla::TaskCategory::Other, evt.forget());
+
+  // DropPresentationState would clear mContentViewer & mDocument. Capture and
+  // release the references asynchronously so that the document doesn't get
+  // nuked mid-mutation.
+  nsCOMPtr<nsIContentViewer> viewer = mContentViewer;
+  nsCOMPtr<nsIDocument> document = mDocument;
+  RefPtr<nsSHEntryShared> self = this;
+  nsresult rv = mDocument->Dispatch(mozilla::TaskCategory::Other,
+    NS_NewRunnableFunction("nsSHEntryShared::RemoveFromBFCacheAsync",
+    [self, viewer, document]() {
+      if (viewer) {
+        viewer->Destroy();
+      }
+
+      nsCOMPtr<nsISHistoryInternal> shistory = do_QueryReferent(self->mSHistory);
+      if (shistory) {
+        shistory->RemoveDynEntriesForBFCacheEntry(self);
+      }
+    }));
+
   if (NS_FAILED(rv)) {
-    NS_WARNING("failed to dispatch DestroyViewerEvent");
+    NS_WARNING("Failed to dispatch RemoveFromBFCacheAsync runnable.");
   } else {
     // Drop presentation. Only do this if we succeeded in posting the event
     // since otherwise the document could be torn down mid-mutation, causing
     // crashes.
     DropPresentationState();
   }
 
-  // Careful! The call to DropPresentationState could have dropped the last
-  // reference to this nsSHEntryShared, so don't access members beyond here.
-
   return NS_OK;
 }
 
 nsresult
 nsSHEntryShared::GetID(uint64_t* aID)
 {
   *aID = mID;
   return NS_OK;
--- a/docshell/shistory/nsSHistory.cpp
+++ b/docshell/shistory/nsSHistory.cpp
@@ -208,17 +208,17 @@ nsSHistory::EvictContentViewerForTransac
   entry->GetAnyContentViewer(getter_AddRefs(ownerEntry),
                              getter_AddRefs(viewer));
   if (viewer) {
     NS_ASSERTION(ownerEntry, "Content viewer exists but its SHEntry is null");
 
     LOG_SHENTRY_SPEC(("Evicting content viewer 0x%p for "
                       "owning SHEntry 0x%p at %s.",
                       viewer.get(), ownerEntry.get(), _spec),
-                     ownerEntry);
+                      ownerEntry);
 
     // Drop the presentation state before destroying the viewer, so that
     // document teardown is able to correctly persist the state.
     ownerEntry->SetContentViewer(nullptr);
     ownerEntry->SyncPresentationState();
     viewer->Destroy();
   }
 
@@ -583,17 +583,17 @@ nsSHistory::GetEntryAtIndex(int32_t aInd
         NOTIFY_LISTENERS(OnIndexChanged, (mIndex))
       }
     }
   }
   return rv;
 }
 
 /* Get the transaction at a given index */
-NS_IMETHODIMP
+nsresult
 nsSHistory::GetTransactionAtIndex(int32_t aIndex, nsISHTransaction** aResult)
 {
   nsresult rv;
   NS_ENSURE_ARG_POINTER(aResult);
 
   if (mLength <= 0 || aIndex < 0 || aIndex >= mLength) {
     return NS_ERROR_FAILURE;
   }
@@ -1285,18 +1285,23 @@ nsSHistory::GloballyEvictContentViewers(
   for (int32_t i = transactions.Length() - 1; i >= sHistoryMaxTotalViewers;
        --i) {
     (transactions[i].mSHistory)->
       EvictContentViewerForTransaction(transactions[i].mTransaction);
   }
 }
 
 nsresult
-nsSHistory::EvictExpiredContentViewerForEntry(nsIBFCacheEntry* aEntry)
+nsSHistory::FindTransactionForBFCache(nsIBFCacheEntry* aEntry,
+                                      nsISHTransaction** aResult,
+                                      int32_t* aResultIndex)
 {
+  *aResult = nullptr;
+  *aResultIndex = -1;
+
   int32_t startIndex = std::max(0, mIndex - nsISHistory::VIEWER_WINDOW);
   int32_t endIndex = std::min(mLength - 1, mIndex + nsISHistory::VIEWER_WINDOW);
   nsCOMPtr<nsISHTransaction> trans;
   GetTransactionAtIndex(startIndex, getter_AddRefs(trans));
 
   int32_t i;
   for (i = startIndex; trans && i <= endIndex; ++i) {
     nsCOMPtr<nsISHEntry> entry;
@@ -1306,25 +1311,39 @@ nsSHistory::EvictExpiredContentViewerFor
     if (entry->HasBFCacheEntry(aEntry)) {
       break;
     }
 
     nsCOMPtr<nsISHTransaction> temp = trans;
     temp->GetNext(getter_AddRefs(trans));
   }
   if (i > endIndex) {
-    return NS_OK;
+    return NS_ERROR_FAILURE;
   }
 
-  if (i == mIndex) {
+  trans.forget(aResult);
+  *aResultIndex = i;
+  return NS_OK;
+}
+
+nsresult
+nsSHistory::EvictExpiredContentViewerForEntry(nsIBFCacheEntry* aEntry)
+{
+  int32_t index;
+  nsCOMPtr<nsISHTransaction> trans;
+  FindTransactionForBFCache(aEntry, getter_AddRefs(trans), &index);
+
+  if (index == mIndex) {
     NS_WARNING("How did the current SHEntry expire?");
     return NS_OK;
   }
 
-  EvictContentViewerForTransaction(trans);
+  if (trans) {
+    EvictContentViewerForTransaction(trans);
+  }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHistory::AddToExpirationTracker(nsIBFCacheEntry* aEntry)
 {
   RefPtr<nsSHEntryShared> entry = static_cast<nsSHEntryShared*>(aEntry);
@@ -1581,16 +1600,30 @@ nsSHistory::RemoveDynEntries(int32_t aIn
     AutoTArray<nsID, 16> toBeRemovedEntries;
     GetDynamicChildren(container, toBeRemovedEntries, true);
     if (toBeRemovedEntries.Length()) {
       RemoveEntries(toBeRemovedEntries, aIndex);
     }
   }
 }
 
+void
+nsSHistory::RemoveDynEntriesForBFCacheEntry(nsIBFCacheEntry* aEntry)
+{
+  int32_t index;
+  nsCOMPtr<nsISHTransaction> trans;
+  FindTransactionForBFCache(aEntry, getter_AddRefs(trans), &index);
+  if (trans) {
+    nsCOMPtr<nsISHEntry> entry;
+    trans->GetSHEntry(getter_AddRefs(entry));
+    nsCOMPtr<nsISHContainer> container(do_QueryInterface(entry));
+    RemoveDynEntries(index, container);
+  }
+}
+
 NS_IMETHODIMP
 nsSHistory::UpdateIndex()
 {
   // Update the actual index with the right value.
   if (mIndex != mRequestedIndex && mRequestedIndex != -1) {
     mIndex = mRequestedIndex;
     NOTIFY_LISTENERS(OnIndexChanged, (mIndex))
   }
@@ -1698,17 +1731,17 @@ nsSHistory::LoadNextPossibleEntry(int32_
     return LoadEntry(aNewIndex - 1, aLoadType, aHistCmd);
   }
   if (aNewIndex > mIndex) {
     return LoadEntry(aNewIndex + 1, aLoadType, aHistCmd);
   }
   return NS_ERROR_FAILURE;
 }
 
-NS_IMETHODIMP
+nsresult
 nsSHistory::LoadEntry(int32_t aIndex, long aLoadType, uint32_t aHistCmd)
 {
   if (!mRootDocShell) {
     return NS_ERROR_FAILURE;
   }
 
   nsCOMPtr<nsIURI> nextURI;
   nsCOMPtr<nsISHEntry> prevEntry;
--- a/docshell/shistory/nsSHistory.h
+++ b/docshell/shistory/nsSHistory.h
@@ -78,30 +78,35 @@ public:
   // Otherwise, it comes straight from the pref.
   static uint32_t GetMaxTotalViewers() { return sHistoryMaxTotalViewers; }
 
 private:
   virtual ~nsSHistory();
   friend class nsSHEnumerator;
   friend class nsSHistoryObserver;
 
-  // Could become part of nsIWebNavigation
-  NS_IMETHOD GetTransactionAtIndex(int32_t aIndex, nsISHTransaction** aResult);
+  nsresult GetTransactionAtIndex(int32_t aIndex, nsISHTransaction** aResult);
   nsresult LoadDifferingEntries(nsISHEntry* aPrevEntry, nsISHEntry* aNextEntry,
                                 nsIDocShell* aRootDocShell, long aLoadType,
                                 bool& aDifferenceFound);
   nsresult InitiateLoad(nsISHEntry* aFrameEntry, nsIDocShell* aFrameDS,
                         long aLoadType);
 
-  NS_IMETHOD LoadEntry(int32_t aIndex, long aLoadType, uint32_t aHistCmd);
+  nsresult LoadEntry(int32_t aIndex, long aLoadType, uint32_t aHistCmd);
 
 #ifdef DEBUG
   nsresult PrintHistory();
 #endif
 
+  // Find the transaction for a given bfcache entry. It only looks up between
+  // the range where alive viewers may exist (i.e nsISHistory::VIEWER_WINDOW).
+  nsresult FindTransactionForBFCache(nsIBFCacheEntry* aEntry,
+                                     nsISHTransaction** aResult,
+                                     int32_t* aResultIndex);
+
   // Evict content viewers in this window which don't lie in the "safe" range
   // around aIndex.
   void EvictOutOfRangeWindowContentViewers(int32_t aIndex);
   void EvictContentViewerForTransaction(nsISHTransaction* aTrans);
   static void GloballyEvictContentViewers();
   static void GloballyEvictAllContentViewers();
 
   // Calculates a max number of total
--- a/docshell/test/chrome/bug608669.xul
+++ b/docshell/test/chrome/bug608669.xul
@@ -1,6 +1,14 @@
 <?xml version="1.0"?>
 <?xml-stylesheet href="chrome://global/skin" type="text/css"?>
 <window title="Mozilla Bug 608669 - Blank page"
-  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+  onload="notifyOpener();">
   <description flex="1" value="This window is intentionally left blank"/>
+  <script type="application/javascript">
+  function notifyOpener() {
+    if (opener) {
+      opener.postMessage("load", "*");
+    }
+  }
+  </script>
 </window>
--- a/docshell/test/chrome/test_bug608669.xul
+++ b/docshell/test/chrome/test_bug608669.xul
@@ -13,53 +13,28 @@ https://bugzilla.mozilla.org/show_bug.cg
   <body xmlns="http://www.w3.org/1999/xhtml">
   <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=608669"
      target="_blank">Mozilla Bug 608669</a>
   </body>
 
   <!-- test code goes here -->
   <script type="application/javascript"><![CDATA[
 
-var gOrigMaxTotalViewers = undefined;
-function setCachePref(enabled) {
-  var prefBranch = Components.classes["@mozilla.org/preferences-service;1"]
-                             .getService(Components.interfaces.nsIPrefBranch);
-  if (enabled) {
-    is(typeof gOrigMaxTotalViewers, "undefined", "don't double-enable bfcache");
-    prefBranch.setBoolPref("browser.sessionhistory.cache_subframes", true);
-    gOrigMaxTotalViewers = prefBranch.getIntPref("browser.sessionhistory.max_total_viewers");
-    prefBranch.setIntPref("browser.sessionhistory.max_total_viewers", 10);
-  }
-  else {
-    is(typeof gOrigMaxTotalViewers, "number", "don't double-disable bfcache");
-    prefBranch.setIntPref("browser.sessionhistory.max_total_viewers", gOrigMaxTotalViewers);
-    gOrigMaxTotalViewers = undefined;
-    try {
-      prefBranch.clearUserPref("browser.sessionhistory.cache_subframes");
-    } catch (e) { /* Pref didn't exist, meh */ }
-  }
-}
-
-
 /** Test for Bug 608669 **/
 SimpleTest.waitForExplicitFinish();
 
 addLoadEvent(nextTest);
 
 gen = doTest();
 
 function nextTest() {
   gen.next();
 }
 
 function* doTest() {
-  var container = document.getElementById('container');
-
-  setCachePref(true);
-
   var notificationCount = 0;
   var observer = {
     observe: function(aSubject, aTopic, aData) {
       is(aTopic, "chrome-document-global-created",
          "correct topic");
       is(aData, "null",
          "correct data");
       notificationCount++;
@@ -68,49 +43,36 @@ function* doTest() {
 
   var os = Components.classes["@mozilla.org/observer-service;1"].
     getService(Components.interfaces.nsIObserverService);
   os.addObserver(observer, "chrome-document-global-created");
   os.addObserver(observer, "content-document-global-created");
 
   is(notificationCount, 0, "initial count");
 
-  // create a new iframe
-  var iframe = document.createElement("iframe");
-  container.appendChild(iframe);
-  iframe.contentWindow.x = "y";
-  is(notificationCount, 1, "after created iframe");
-  
-  // Try loading in an iframe
-  iframe.setAttribute("src", "bug608669.xul");
-  iframe.onload = nextTest;
+  // create a new window
+  var testWin = window.open("", "bug 608669", "chrome,width=600,height=600");
+  testWin.x = "y";
+  is(notificationCount, 1, "after created window");
+
+  // Try loading in the window
+  testWin.location = "bug608669.xul";
+  window.onmessage = nextTest;
   yield undefined;
   is(notificationCount, 1, "after first load");
-  is(iframe.contentWindow.x, "y", "reused window");
+  is(testWin.x, "y", "reused window");
 
-  // Try loading again in an iframe
-  iframe.setAttribute("src", "bug608669.xul?x");
-  iframe.onload = nextTest;
+  // Try loading again in the window
+  testWin.location = "bug608669.xul?x";
+  window.onmessage = nextTest;
   yield undefined;
   is(notificationCount, 2, "after second load");
-  is("x" in iframe.contentWindow, false, "didn't reuse window");
+  is("x" in testWin, false, "didn't reuse window");
 
-  // Open a new window using window.open
-  popup = window.open("bug608669.xul", "bug 608669",
-                      "chrome,width=600,height=600");
-  popup.onload = nextTest;
-  yield undefined;
-  is(notificationCount, 3, "after window.open load");
-  popup.close();
+  testWin.close();
 
-  setCachePref(false);
   os.removeObserver(observer, "chrome-document-global-created");
   os.removeObserver(observer, "content-document-global-created");
   SimpleTest.finish();
 }
 
-
-
   ]]></script>
-  <vbox id="container" flex="1">
-    <description>Below will an iframe be added</description>
-  </vbox>
 </window>
new file mode 100644
--- /dev/null
+++ b/docshell/test/navigation/file_bug1364364-1.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>title</title>
+  </head>
+  <body onload="loadFramesAndNavigate();">
+    <p id="content"></p>
+    <div id="frameContainer">
+    </div>
+    <script type="application/javascript">
+    function waitForLoad(frame) {
+      return new Promise(r => frame.onload = r);
+    }
+
+    async function loadFramesAndNavigate() {
+      let dynamicFrame = document.createElement("iframe");
+      dynamicFrame.src = "data:text/html,iframe1";
+      document.querySelector("#frameContainer").appendChild(dynamicFrame);
+      await waitForLoad(dynamicFrame);
+      dynamicFrame.src = "data:text/html,iframe2";
+      await waitForLoad(dynamicFrame);
+      dynamicFrame.src = "data:text/html,iframe3";
+      await waitForLoad(dynamicFrame);
+      dynamicFrame.src = "data:text/html,iframe4";
+      await waitForLoad(dynamicFrame);
+      dynamicFrame.src = "data:text/html,iframe5";
+      await waitForLoad(dynamicFrame);
+      location.href = "file_bug1364364-2.html";
+    }
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/docshell/test/navigation/file_bug1364364-2.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>title</title>
+  </head>
+  <body onload="notifyOpener();">
+    <script type="application/javascript">
+    function notifyOpener() {
+      opener.postMessage("navigation-done", "*");
+    }
+    </script>
+  </body>
+</html>
--- a/docshell/test/navigation/mochitest.ini
+++ b/docshell/test/navigation/mochitest.ini
@@ -44,30 +44,33 @@ support-files =
   file_triggeringprincipal_iframe_iframe_window_open_frame_a_nav.html
   file_bug1300461.html
   file_bug1300461_redirect.html
   file_bug1300461_redirect.html^headers^
   file_bug1300461_back.html
   file_contentpolicy_block_window.html
   file_bug1326251.html
   file_bug1326251_evict_cache.html
+  file_bug1364364-1.html
+  file_bug1364364-2.html
   file_bug1375833.html
   file_bug1375833-frame1.html
   file_bug1375833-frame2.html
 
 [test_bug13871.html]
 [test_bug270414.html]
 [test_bug278916.html]
 [test_bug279495.html]
 [test_bug344861.html]
 skip-if = toolkit == "android" || toolkit == "windows" # disabled on Windows because of bug 1234520
 [test_bug386782.html]
 [test_bug430624.html]
 [test_bug430723.html]
 skip-if = (toolkit == 'android') || (!debug && (os == 'mac' || os == 'win')) # Bug 874423
+[test_bug1364364.html]
 [test_bug1375833.html]
 [test_child.html]
 [test_grandchild.html]
 [test_not-opener.html]
 [test_opener.html]
 [test_popup-navigates-children.html]
 [test_reserved.html]
 skip-if = (toolkit == 'android') || (debug && e10s) #too slow on Android 4.3 aws only; bug 1030403; bug 1263213 for debug e10s
new file mode 100644
--- /dev/null
+++ b/docshell/test/navigation/test_bug1364364.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1364364
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1364364</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+  /** Test for Bug 1364364 **/
+  let testWin, testDoc;
+  async function test() {
+    SimpleTest.waitForExplicitFinish();
+    testWin = window.open("file_bug1364364-1.html");
+    await waitForLoad(testWin);
+    testDoc = testWin.document;
+
+    // file_bug1364364-1.html will load a few dynamic iframes and then navigate
+    // top browsing context to file_bug1364364-2.html, which will postMessage
+    // back.
+  }
+
+  function waitForLoad(win) {
+    return new Promise(r => win.addEventListener("load", r, { once: true}));
+  }
+
+  window.addEventListener("message", async function(msg) {
+    if (msg.data == "navigation-done") {
+      is(testWin.history.length, 6, "check history.length");
+
+      // Modify a document in bfcache should cause the cache being dropped tho
+      // RemoveFromBFCacheAsync.
+      testDoc.querySelector("#content").textContent = "modified";
+      await new Promise(r => setTimeout(r, 0));
+
+      is(testWin.history.length, 2, "check history.length after bfcache dropped");
+      testWin.close();
+      SimpleTest.finish();
+    }
+  });
+
+  </script>
+</head>
+<body onload="test();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1364364">Mozilla Bug 1364364</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -1893,16 +1893,29 @@ Element::UnbindFromTree(bool aDeep, bool
     DeleteProperty(nsGkAtoms::transitionsOfBeforeProperty);
     DeleteProperty(nsGkAtoms::transitionsOfAfterProperty);
     DeleteProperty(nsGkAtoms::transitionsProperty);
     DeleteProperty(nsGkAtoms::animationsOfBeforeProperty);
     DeleteProperty(nsGkAtoms::animationsOfAfterProperty);
     DeleteProperty(nsGkAtoms::animationsProperty);
   }
 
+  // Computed style data isn't useful for detached nodes, and we'll need to
+  // recompute it anyway if we ever insert the nodes back into a document.
+  if (IsStyledByServo()) {
+    if (document) {
+      ClearServoData(document);
+    } else {
+      MOZ_ASSERT(!HasServoData());
+      MOZ_ASSERT(!HasAnyOfFlags(kAllServoDescendantBits | NODE_NEEDS_FRAME));
+    }
+  } else {
+    MOZ_ASSERT(!HasServoData());
+  }
+
   // Editable descendant count only counts descendants that
   // are in the uncomposed document.
   ResetEditableDescendantCount();
 
   if (aNullParent || !mParent->IsInShadowTree()) {
     UnsetFlags(NODE_IS_IN_SHADOW_TREE);
 
     // Begin keeping track of our subtree root.
@@ -1987,28 +2000,31 @@ Element::UnbindFromTree(bool aDeep, bool
     for (nsIContent* child = shadowRoot->GetFirstChild(); child;
          child = child->GetNextSibling()) {
       child->UnbindFromTree(true, false);
     }
 
     shadowRoot->SetIsComposedDocParticipant(false);
   }
 
-  // Computed style data isn't useful for detached nodes, and we'll need to
-  // recompute it anyway if we ever insert the nodes back into a document.
-  if (IsStyledByServo()) {
-    if (document) {
-      ClearServoData(document);
-    } else {
-      MOZ_ASSERT(!HasServoData());
-      MOZ_ASSERT(!HasAnyOfFlags(kAllServoDescendantBits | NODE_NEEDS_FRAME));
-    }
-  } else {
-    MOZ_ASSERT(!HasServoData());
-  }
+  // Unbinding of children is the only point in time where we don't enforce the
+  // "child has style data implies parent has it too" invariant.
+  //
+  // As such, the restyle root tracking may incorrectly end up setting dirty
+  // bits on the parent chain when moving from a not yet unbound root with
+  // already unbound parents to a root higher up in the tree, so we clear those
+  // (again, since they're also cleared in ClearServoData) here.
+  //
+  // This can happen when the element changes the state of some ancestor up in
+  // the tree, for example.
+  //
+  // Note that clearing the data itself here would have its own set of problems,
+  // since the invariant we'd be breaking in that case is "HasServoData()
+  // implies InComposedDoc()", which we rely on in various places.
+  UnsetFlags(kAllServoDescendantBits);
 }
 
 nsICSSDeclaration*
 Element::GetSMILOverrideStyle()
 {
   Element::nsExtendedDOMSlots* slots = ExtendedDOMSlots();
 
   if (!slots->mSMILOverrideStyle) {
rename from dom/indexedDB/test/bfcache_iframe1.html
rename to dom/indexedDB/test/bfcache_page1.html
--- a/dom/indexedDB/test/bfcache_iframe1.html
+++ b/dom/indexedDB/test/bfcache_page1.html
@@ -1,27 +1,27 @@
 <!DOCTYPE html>
 <html>
 <head>
   <script>
-  var request = indexedDB.open(parent.location, 1);
+  var request = indexedDB.open(opener.location, 1);
   request.onupgradeneeded = function(e) {
     var db = e.target.result;
     // This should never be called
     db.onversionchange = function(e) {
       db.transaction(["mystore"]).objectStore("mystore").put({ hello: "fail" }, 42);
     }
     var trans = e.target.transaction;
     if (db.objectStoreNames.contains("mystore")) {
       db.deleteObjectStore("mystore");
     }
     var store = db.createObjectStore("mystore");
     store.add({ hello: "world" }, 42);
     trans.oncomplete = function() {
-      parent.postMessage("go", "http://mochi.test:8888");
+      opener.postMessage("go", "http://mochi.test:8888");
     }
   };
   </script>
 </head>
 <body>
   This is page one.
 </body>
 </html>
rename from dom/indexedDB/test/bfcache_iframe2.html
rename to dom/indexedDB/test/bfcache_page2.html
--- a/dom/indexedDB/test/bfcache_iframe2.html
+++ b/dom/indexedDB/test/bfcache_page2.html
@@ -1,28 +1,28 @@
 <!DOCTYPE html>
 <html>
 <head>
   <script>
   var res = {};
-  var request = indexedDB.open(parent.location, 2);
+  var request = indexedDB.open(opener.location, 2);
   request.onblocked = function() {
     res.blockedFired = true;
   }
   request.onupgradeneeded  = function(e) {
     var db = e.target.result;
     res.version = db.version;
     res.storeCount = db.objectStoreNames.length;
 
     var trans = request.transaction;
     trans.objectStore("mystore").get(42).onsuccess = function(e) {
       res.value = JSON.stringify(e.target.result);
     }
     trans.oncomplete = function() {
-      parent.postMessage(JSON.stringify(res), "http://mochi.test:8888");
+      opener.postMessage(JSON.stringify(res), "http://mochi.test:8888");
     }
   };
 
   </script>
 </head>
 <body>
   This is page two.
 </body>
--- a/dom/indexedDB/test/mochitest.ini
+++ b/dom/indexedDB/test/mochitest.ini
@@ -1,16 +1,16 @@
 # 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/.
 
 [DEFAULT]
 support-files =
-  bfcache_iframe1.html
-  bfcache_iframe2.html
+  bfcache_page1.html
+  bfcache_page2.html
   blob_worker_crash_iframe.html
   error_events_abort_transactions_iframe.html
   event_propagation_iframe.html
   exceptions_in_events_iframe.html
   file.js
   helpers.js
   leaving_page_iframe.html
   third_party_iframe1.html
--- a/dom/indexedDB/test/test_bfcache.html
+++ b/dom/indexedDB/test/test_bfcache.html
@@ -5,63 +5,37 @@
 <html>
 <head>
   <title>Indexed Database Property Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="text/javascript">
     /* import-globals-from helpers.js */
-    var gOrigMaxTotalViewers = undefined;
-    function setCachePref(enabled) {
-      if (enabled) {
-        is(typeof gOrigMaxTotalViewers, "undefined",
-           "don't double-enable bfcache");
-        SpecialPowers.setBoolPref("browser.sessionhistory.cache_subframes",
-                                  true);
-        gOrigMaxTotalViewers =
-          SpecialPowers.getIntPref("browser.sessionhistory.max_total_viewers");
-        SpecialPowers.setIntPref("browser.sessionhistory.max_total_viewers",
-                                 10);
-      }
-      else {
-        is(typeof gOrigMaxTotalViewers, "number",
-           "don't double-disable bfcache");
-        SpecialPowers.setIntPref("browser.sessionhistory.max_total_viewers",
-                                 gOrigMaxTotalViewers);
-        gOrigMaxTotalViewers = undefined;
-        try {
-          SpecialPowers.clearUserPref("browser.sessionhistory.cache_subframes");
-        } catch (e) { /* Pref didn't exist, meh */ }
-      }
-    }
 
     function* testSteps()
     {
-      var iframe = $("iframe");
-      setCachePref(true);
       window.onmessage = grabEventAndContinueHandler;
 
-      iframe.src = "bfcache_iframe1.html";
+      let testWin = window.open("bfcache_page1.html", "testWin");
       var event = yield undefined;
       is(event.data, "go", "set up database successfully");
 
-      iframe.src = "bfcache_iframe2.html";
+      testWin.location = "bfcache_page2.html";
       let res = JSON.parse((yield).data);
       is(res.version, 2, "version was set correctly");
       is(res.storeCount, 1, "correct set of stores");
       ok(!("blockedFired" in res), "blocked shouldn't fire");
       is(res.value, JSON.stringify({ hello: "world" }),
          "correct value found in store");
 
-      setCachePref(false);
+      testWin.close();
       finishTest();
     }
   </script>
   <script type="text/javascript" src="helpers.js"></script>
 
 </head>
 
 <body onload="runTest();">
-  <iframe id="iframe"></iframe>
 </body>
 
 </html>
--- a/dom/media/platforms/wmf/WMFUtils.cpp
+++ b/dom/media/platforms/wmf/WMFUtils.cpp
@@ -9,18 +9,18 @@
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/Logging.h"
 #include "mozilla/RefPtr.h"
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
 #include "nsWindowsHelpers.h"
 #include "prenv.h"
-#include <Shlobj.h>
-#include <Shlwapi.h>
+#include <shlobj.h>
+#include <shlwapi.h>
 #include <initguid.h>
 #include <stdint.h>
 #include "mozilla/mscom/EnsureMTA.h"
 #include "mozilla/WindowsVersion.h"
 
 #ifdef WMF_MUST_DEFINE_AAC_MFT_CLSID
 // Some SDK versions don't define the AAC decoder CLSID.
 // {32D186A7-218F-4C75-8876-DD77273A8999}
rename from dom/media/webspeech/synth/test/file_bfcache_frame.html
rename to dom/media/webspeech/synth/test/file_bfcache_page1.html
--- a/dom/media/webspeech/synth/test/file_bfcache_frame.html
+++ b/dom/media/webspeech/synth/test/file_bfcache_page1.html
@@ -2,17 +2,17 @@
 <html>
 <head>
   <meta charset="utf-8">
   <script type="application/javascript">
     addEventListener('pageshow', function onshow(evt) {
       var u = new SpeechSynthesisUtterance('hello');
       u.lang = 'it-IT-noend';
       u.addEventListener('start', function() {
-        location = "file_bfcache_frame2.html";
+        location = "file_bfcache_page2.html";
       });
       speechSynthesis.speak(u);
     });
   </script>
 </head>
 <body>
 </body>
 </html>
rename from dom/media/webspeech/synth/test/file_bfcache_frame2.html
rename to dom/media/webspeech/synth/test/file_bfcache_page2.html
--- a/dom/media/webspeech/synth/test/file_bfcache_frame2.html
+++ b/dom/media/webspeech/synth/test/file_bfcache_page2.html
@@ -1,14 +1,14 @@
 <html>
 <script>
 var frameUnloaded = function() {
   var u = new SpeechSynthesisUtterance('hi');
   u.addEventListener('end', function () {
-    parent.ok(true, 'Successfully spoke utterance from new frame.');
-    parent.onDone();
+    opener.ok(true, 'Successfully spoke utterance from new frame.');
+    opener.onDone();
   });
   speechSynthesis.speak(u);
 };
 </script>
 
 <body onpageshow="frameUnloaded()"></body></html>
 
--- a/dom/media/webspeech/synth/test/mochitest.ini
+++ b/dom/media/webspeech/synth/test/mochitest.ini
@@ -1,15 +1,15 @@
 [DEFAULT]
 tags=msg
 subsuite = media
 support-files =
   common.js
-  file_bfcache_frame.html
-  file_bfcache_frame2.html
+  file_bfcache_page1.html
+  file_bfcache_page2.html
   file_setup.html
   file_speech_queue.html
   file_speech_simple.html
   file_speech_cancel.html
   file_speech_error.html
   file_indirect_service_events.html
   file_global_queue.html
   file_global_queue_cancel.html
--- a/dom/media/webspeech/synth/test/test_bfcache.html
+++ b/dom/media/webspeech/synth/test/test_bfcache.html
@@ -8,38 +8,39 @@ https://bugzilla.mozilla.org/show_bug.cg
   <title>Test for Bug 1230533: Test speech is stopped from a window when unloaded</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1230533">Mozilla Bug 1230533</a>
 <p id="display"></p>
-<iframe id="testFrame"></iframe>
 <div id="content" style="display: none">
-  
+
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 525444 **/
 
 SimpleTest.waitForExplicitFinish();
-
-var iframe;
+let testWin;
 
 function onDone() {
+  testWin.close();
   SimpleTest.finish();
 }
 
 SpecialPowers.pushPrefEnv({ set: [
   ['media.webspeech.synth.enabled', true],
-  ['media.webspeech.synth.force_global_queue', true],
-  ['browser.sessionhistory.cache_subframes', true],
-  ['browser.sessionhistory.max_total_viewers', 10]] },
+  ['media.webspeech.synth.force_global_queue', true]] },
   function() {
-    loadSpeechTest("file_bfcache_frame.html");
+    testWin = window.open("about:blank", "testWin");
+    testWin.onload = function(e) {
+      waitForVoices(testWin)
+        .then(() => testWin.location = "file_bfcache_page1.html")
+    };
   });
 
 </script>
 </pre>
 </body>
 </html>
rename from dom/workers/test/WorkerDebugger_frozen_iframe1.html
rename to dom/workers/test/WorkerDebugger_frozen_window1.html
--- a/dom/workers/test/WorkerDebugger_frozen_iframe1.html
+++ b/dom/workers/test/WorkerDebugger_frozen_window1.html
@@ -1,15 +1,15 @@
 <!DOCTYPE html>
 <html lang="en">
   <head>
     <meta charset="utf-8">
     <script>
       var worker = new Worker("WorkerDebugger_frozen_worker1.js");
       worker.onmessage = function () {
-        parent.postMessage("ready", "*");
+        opener.postMessage("ready", "*");
       };
     </script>
   </head>
   <body>
     This is page 1.
   </body>
 <html>
rename from dom/workers/test/WorkerDebugger_frozen_iframe2.html
rename to dom/workers/test/WorkerDebugger_frozen_window2.html
--- a/dom/workers/test/WorkerDebugger_frozen_iframe2.html
+++ b/dom/workers/test/WorkerDebugger_frozen_window2.html
@@ -1,15 +1,15 @@
 <!DOCTYPE html>
 <html lang="en">
   <head>
     <meta charset="utf-8">
     <script>
       var worker = new Worker("WorkerDebugger_frozen_worker2.js");
       worker.onmessage = function () {
-        parent.postMessage("ready", "*");
+        opener.postMessage("ready", "*");
       };
     </script>
   </head>
   <body>
     This is page 2.
   </body>
 <html>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/blank.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>Blank</title>
+  </head>
+  <body onload="notifyOnload();">
+    <script type="application/javascript">
+    function notifyOnload() {
+      opener.postMessage({event: 'load'}, '*');
+    }
+    </script>
+  </body>
+</html>
+
--- a/dom/workers/test/chrome.ini
+++ b/dom/workers/test/chrome.ini
@@ -19,18 +19,18 @@ support-files =
   WorkerDebuggerGlobalScope.reportError_childWorker.js
   WorkerDebuggerGlobalScope.reportError_debugger.js
   WorkerDebuggerGlobalScope.reportError_worker.js
   WorkerDebuggerGlobalScope.setImmediate_debugger.js
   WorkerDebuggerGlobalScope.setImmediate_worker.js
   WorkerDebuggerManager_childWorker.js
   WorkerDebuggerManager_worker.js
   WorkerDebugger_childWorker.js
-  WorkerDebugger_frozen_iframe1.html
-  WorkerDebugger_frozen_iframe2.html
+  WorkerDebugger_frozen_window1.html
+  WorkerDebugger_frozen_window2.html
   WorkerDebugger_frozen_worker1.js
   WorkerDebugger_frozen_worker2.js
   WorkerDebugger_promise_debugger.js
   WorkerDebugger_promise_worker.js
   WorkerDebugger_sharedWorker.js
   WorkerDebugger_suspended_debugger.js
   WorkerDebugger_suspended_worker.js
   WorkerDebugger_worker.js
--- a/dom/workers/test/mochitest.ini
+++ b/dom/workers/test/mochitest.ini
@@ -1,12 +1,13 @@
 [DEFAULT]
 support-files =
   WorkerTest_badworker.js
   atob_worker.js
+  blank.html
   bug978260_worker.js
   bug1014466_data1.txt
   bug1014466_data2.txt
   bug1014466_worker.js
   bug1020226_worker.js
   bug1020226_frame.html
   bug998474_worker.js
   bug1063538_worker.js
@@ -51,17 +52,17 @@ support-files =
   promise_worker.js
   recursion_worker.js
   recursiveOnerror_worker.js
   redirect_to_foreign.sjs
   rvals_worker.js
   sharedWorker_console.js
   sharedWorker_sharedWorker.js
   simpleThread_worker.js
-  suspend_iframe.html
+  suspend_window.html
   suspend_worker.js
   terminate_worker.js
   test_csp.html^headers^
   test_csp.js
   referrer_worker.html
   threadErrors_worker1.js
   threadErrors_worker2.js
   threadErrors_worker3.js
--- a/dom/workers/test/multi_sharedWorker_frame.html
+++ b/dom/workers/test/multi_sharedWorker_frame.html
@@ -2,25 +2,41 @@
   Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/
 -->
 <!DOCTYPE HTML>
 <html>
   <head>
     <title>Test for SharedWorker</title>
   </head>
-  <body>
+  <body onload="notifyOpenerLoadEvent();">
     <script type="text/javascript">
       "use strict";
 
+      function postMessageToParentOrOpener(message) {
+        if (parent != window) {
+          parent.postMessage(message, "*");
+        }
+        if (opener) {
+          opener.postMessage(message, "*");
+        }
+      }
+
+      // Used by test_multi_sharedWorker_lifetimes.html
+      function notifyOpenerLoadEvent() {
+        if (opener) {
+          opener.postMessage({event: "load"}, "*");
+        }
+      }
+
       function debug(message) {
         if (typeof(message) != "string") {
           throw new Error("debug() only accepts strings!");
         }
-        parent.postMessage(message, "*");
+        postMessageToParentOrOpener(message);
       }
 
       let worker;
 
       window.addEventListener("message", function(event) {
         if (!worker) {
           worker = new SharedWorker("multi_sharedWorker_sharedWorker.js",
                                     "FrameWorker");
@@ -30,22 +46,22 @@
 
             let data = {
               type: "error",
               message: event.message,
               filename: event.filename,
               lineno: event.lineno,
               isErrorEvent: event instanceof ErrorEvent
             };
-            parent.postMessage(data, "*");
+            postMessageToParentOrOpener(data);
           };
 
           worker.port.onmessage = function(event) {
             debug("Worker message: " + JSON.stringify(event.data));
-            parent.postMessage(event.data, "*");
+            postMessageToParentOrOpener(event.data);
           };
         }
 
         debug("Posting message: " + JSON.stringify(event.data));
         worker.port.postMessage(event.data);
       });
     </script>
   </body>
rename from dom/workers/test/suspend_iframe.html
rename to dom/workers/test/suspend_window.html
--- a/dom/workers/test/test_WorkerDebugger_frozen.xul
+++ b/dom/workers/test/test_WorkerDebugger_frozen.xul
@@ -11,80 +11,69 @@
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
   <script type="application/javascript" src="dom_worker_helper.js"/>
 
   <script type="application/javascript">
   <![CDATA[
 
-    const CACHE_SUBFRAMES = "browser.sessionhistory.cache_subframes";
-    const MAX_TOTAL_VIEWERS = "browser.sessionhistory.max_total_viewers";
-
-    const IFRAME1_URL = "WorkerDebugger_frozen_iframe1.html";
-    const IFRAME2_URL = "WorkerDebugger_frozen_iframe2.html";
+    const WINDOW1_URL = "WorkerDebugger_frozen_window1.html";
+    const WINDOW2_URL = "WorkerDebugger_frozen_window2.html";
 
     const WORKER1_URL = "WorkerDebugger_frozen_worker1.js";
     const WORKER2_URL = "WorkerDebugger_frozen_worker2.js";
 
     function test() {
       (async function() {
         SimpleTest.waitForExplicitFinish();
 
-        var oldMaxTotalViewers = SpecialPowers.getIntPref(MAX_TOTAL_VIEWERS);
-
-        SpecialPowers.setBoolPref(CACHE_SUBFRAMES, true);
-        SpecialPowers.setIntPref(MAX_TOTAL_VIEWERS, 10);
-
-        let iframe = $("iframe");
+        SpecialPowers.pushPrefEnv({set:
+          [["browser.sessionhistory.max_total_viewers", 10]]});
 
         let promise = waitForMultiple([
           waitForRegister(WORKER1_URL),
           waitForWindowMessage(window, "ready"),
         ]);
-        iframe.src = IFRAME1_URL;
+        let testWin = window.open(WINDOW1_URL, "testWin");;
         let [dbg1] = await promise;
         is(dbg1.isClosed, false,
            "debugger for worker on page 1 should not be closed");
 
         promise = waitForMultiple([
           waitForUnregister(WORKER1_URL),
           waitForDebuggerClose(dbg1),
           waitForRegister(WORKER2_URL),
           waitForWindowMessage(window, "ready"),
         ]);
-        iframe.src = IFRAME2_URL;
+        testWin.location = WINDOW2_URL;
         let [,, dbg2] = await promise;
         is(dbg1.isClosed, true,
            "debugger for worker on page 1 should be closed");
         is(dbg2.isClosed, false,
            "debugger for worker on page 2 should not be closed");
 
         promise = Promise.all([
           waitForUnregister(WORKER2_URL),
           waitForDebuggerClose(dbg2),
           waitForRegister(WORKER1_URL)
         ]);
-        iframe.contentWindow.history.back();
+        testWin.history.back();
         [,, dbg1] = await promise;
         is(dbg1.isClosed, false,
            "debugger for worker on page 1 should not be closed");
         is(dbg2.isClosed, true,
            "debugger for worker on page 2 should be closed");
 
-        SpecialPowers.clearUserPref(CACHE_SUBFRAMES);
-        SpecialPowers.setIntPref(MAX_TOTAL_VIEWERS, oldMaxTotalViewers);
-
         SimpleTest.finish();
       })();
     }
 
   ]]>
   </script>
 
   <body xmlns="http://www.w3.org/1999/xhtml">
     <p id="display"></p>
     <div id="content" style="display:none;"></div>
     <pre id="test"></pre>
-    <iframe id="iframe"></iframe>
   </body>
   <label id="test-result"/>
 </window>
--- a/dom/workers/test/test_multi_sharedWorker_lifetimes.html
+++ b/dom/workers/test/test_multi_sharedWorker_lifetimes.html
@@ -7,149 +7,134 @@
   <head>
     <title>Test for SharedWorker</title>
     <script src="/tests/SimpleTest/SimpleTest.js"></script>
     <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
       <script class="testbody" type="text/javascript">
         "use strict";
 
         const scrollbarPref = "layout.testing.overlay-scrollbars.always-visible";
-        const bfCacheEnabledPref = "browser.sessionhistory.cache_subframes";
-        const bfCacheDepthPref = "browser.sessionhistory.max_total_viewers";
-        const bfCacheDepth = 10;
+        const viewersPref = "browser.sessionhistory.max_total_viewers";
 
-        const frameRelativeURL = "multi_sharedWorker_frame.html";
+        const windowRelativeURL = "multi_sharedWorker_frame.html";
         const storedData = "0123456789abcdefghijklmnopqrstuvwxyz";
 
         let testGenerator = (function*() {
           SimpleTest.waitForExplicitFinish();
 
-	  // Force scrollbar to always be shown.  The scrollbar setting is
-	  // necessary to avoid the fade-in/fade-out from evicting our document
-	  // from the BF cache below.  If bug 1049277 is fixed, then we can
-	  // stop setting the scrollbar pref here.
-          SpecialPowers.pushPrefEnv({ set: [[scrollbarPref, true]] },
+          // Force scrollbar to always be shown.  The scrollbar setting is
+          // necessary to avoid the fade-in/fade-out from evicting our document
+          // from the BF cache below.  If bug 1049277 is fixed, then we can
+          // stop setting the scrollbar pref here.
+          SpecialPowers.pushPrefEnv({ set: [[scrollbarPref, true],
+                                            [viewersPref, 10]] },
                                     sendToGenerator);
           yield undefined;
 
           window.addEventListener("message", function(event) {
             if (typeof(event.data) == "string") {
               info(event.data);
             } else {
               sendToGenerator(event);
             }
           });
 
-          let frame = document.getElementById("frame");
-          frame.src = frameRelativeURL;
-          frame.onload = sendToGenerator;
-
+          let testWin = window.open(windowRelativeURL, "testWin");
           yield undefined;
 
-          frame = frame.contentWindow;
-          frame.postMessage({ command: "retrieve" }, "*");
+          testWin.postMessage({ command: "retrieve" }, "*");
 
           let event = yield undefined;
           ok(event instanceof MessageEvent, "Got a MessageEvent");
-          is(event.source, frame, "Correct window got the event");
+          is(event.source, testWin, "Correct window got the event");
           is(event.data.type, "result", "Got a result message");
           is(event.data.data, undefined, "No data stored yet");
 
-          frame.postMessage({ command: "store", data: storedData }, "*");
-          frame.postMessage({ command: "retrieve" }, "*");
+          testWin.postMessage({ command: "store", data: storedData }, "*");
+          testWin.postMessage({ command: "retrieve" }, "*");
 
           event = yield undefined;
           ok(event instanceof MessageEvent, "Got a MessageEvent");
-          is(event.source, frame, "Correct window got the event");
+          is(event.source, testWin, "Correct window got the event");
           is(event.data.type, "result", "Got a result message");
           is(event.data.data, storedData, "Got stored data");
 
           // Navigate when the bfcache is disabled.
-          info("Navigating to about:blank");
-          frame = document.getElementById("frame");
-          frame.onload = sendToGenerator;
-          frame.src = "about:blank";
-          frame.contentWindow.document.body.offsetTop;
+          info("Navigating to a blank page");
+          testWin.onunload = function(){};;
+          testWin.location = "blank.html";
+          testWin.document.body.offsetTop;
 
           yield undefined;
 
-          info("Navigating to " + frameRelativeURL);
-          frame.src = frameRelativeURL;
-          frame.contentWindow.document.body.offsetTop;
+          info("Navigating to " + windowRelativeURL);
+          testWin.onunload = function(){};
+          testWin.location = windowRelativeURL;
+          testWin.document.body.offsetTop;
 
           yield undefined;
 
-          frame = frame.contentWindow;
-          frame.postMessage({ command: "retrieve" }, "*");
+          testWin.postMessage({ command: "retrieve" }, "*");
 
           event = yield undefined;
           ok(event instanceof MessageEvent, "Got a MessageEvent");
-          is(event.source, frame, "Correct window got the event");
+          is(event.source, testWin, "Correct window got the event");
           is(event.data.type, "result", "Got a result message");
           is(event.data.data, undefined, "No data stored");
 
-          frame.postMessage({ command: "store", data: storedData }, "*");
-          frame.postMessage({ command: "retrieve" }, "*");
+          testWin.postMessage({ command: "store", data: storedData }, "*");
+          testWin.postMessage({ command: "retrieve" }, "*");
 
           event = yield undefined;
           ok(event instanceof MessageEvent, "Got a MessageEvent");
-          is(event.source, frame, "Correct window got the event");
+          is(event.source, testWin, "Correct window got the event");
           is(event.data.type, "result", "Got a result message");
           is(event.data.data, storedData, "Got stored data");
 
-          info("Enabling '" + bfCacheEnabledPref + "' pref");
-          SpecialPowers.pushPrefEnv({ set: [[bfCacheEnabledPref, true],
-                                            [bfCacheDepthPref, bfCacheDepth]] },
-                                    sendToGenerator);
-          yield undefined;
-
           // Navigate when the bfcache is enabled.
-          frame = document.getElementById("frame");
-          frame.onload = sendToGenerator;
-
-          info("Navigating to about:blank");
-          frame.src = "about:blank";
-          frame.contentWindow.document.body.offsetTop;
+          info("Navigating to a blank page");
+          testWin.location = "blank.html";
+          testWin.document.body.offsetTop;
 
           yield undefined;
 
           for (let i = 0; i < 3; i++) {
             info("Running GC");
             SpecialPowers.exactGC(sendToGenerator);
             yield undefined;
 
+            // It seems using SpecialPowers.executeSoon() would make the
+            // entryGlobal being the TabChildGlobal (and that would make the
+            // baseURI in the location assignment below being incorrect);
+            // setTimeout on the otherhand ensures the entryGlobal is this
+            // window.
             info("Waiting the event queue to clear");
-            SpecialPowers.executeSoon(sendToGenerator);
+            setTimeout(sendToGenerator, 0);
             yield undefined;
           }
 
-          info("Navigating to " + frameRelativeURL);
-          frame.src = frameRelativeURL;
-          frame.contentWindow.document.body.offsetTop;
+          info("Navigating to " + windowRelativeURL);
+          testWin.location = windowRelativeURL;
+          testWin.document.body.offsetTop;
 
           yield undefined;
 
-          frame = frame.contentWindow;
-          frame.postMessage({ command: "retrieve" }, "*");
+          testWin.postMessage({ command: "retrieve" }, "*");
 
           event = yield undefined;
           ok(event instanceof MessageEvent, "Got a MessageEvent");
-          is(event.source, frame, "Correct window got the event");
+          is(event.source, testWin, "Correct window got the event");
           is(event.data.type, "result", "Got a result message");
           is(event.data.data, storedData, "Still have data stored");
 
-          info("Resetting '" + bfCacheEnabledPref + "' pref");
-          SpecialPowers.popPrefEnv(sendToGenerator);
-          yield undefined;
-
           window.removeEventListener("message", sendToGenerator);
 
+          testWin.close();
           SimpleTest.finish();
         })();
 
         let sendToGenerator = testGenerator.next.bind(testGenerator);
 
       </script>
   </head>
   <body onload="testGenerator.next();">
-    <iframe id="frame"></iframe>
   </body>
 </html>
--- a/dom/workers/test/test_onLine.html
+++ b/dom/workers/test/test_onLine.html
@@ -21,17 +21,26 @@ http://creativecommons.org/licenses/publ
 
 <script class="testbody" type="text/javascript">
 
 addLoadEvent(function() {
   var w = new Worker("onLine_worker.js");
 
   w.onmessage = function(e) {
     if (e.data.type === 'ready') {
-      doTest();
+      // XXX Important trick here.
+      //
+      // Setting iosvc.offline would trigger a sync notifyObservers call, and if
+      // there exists a preloaded about:newtab (see tabbrowser._handleNewTab),
+      // that tab will be notified.
+      //
+      // This implies a sync call across different tabGroups, and will hit the
+      // assertion in SchedulerGroup::ValidateAccess(). So use executeSoon to
+      // re-dispatch an unlabeled runnable to the event queue.
+      SpecialPowers.executeSoon(doTest);
     } else if (e.data.type === 'ok') {
       ok(e.data.test, e.data.message);
     } else if (e.data.type === 'finished') {
       SimpleTest.finish();
     }
   }
 
   function doTest() {
--- a/dom/workers/test/test_suspend.html
+++ b/dom/workers/test/test_suspend.html
@@ -9,130 +9,126 @@
   <title>Test for DOM Worker Threads</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test">
-<iframe id="workerFrame" src="suspend_iframe.html" onload="subframeLoaded();">
-</iframe>
 <script class="testbody" type="text/javascript">
 
   SimpleTest.waitForExplicitFinish();
 
-  var iframe;
+  const BLANK_URI = location.href.replace("test_suspend.html", "blank.html");
+
   var lastCount;
 
   var suspended = false;
   var resumed = false;
   var finished = false;
 
   var interval;
   var oldMessageCount;
   var waitCount = 0;
 
+  var testWin = window.open("suspend_window.html", "testWin");
+  testWin.onload = testWinLoaded;
+
+  window.addEventListener("message", msg => {
+    if (suspended) {
+      badOnloadCallback();
+    } else {
+      suspendCallback();
+    }
+  })
+
   function finishTest() {
     if (finished) {
       return;
     }
     finished = true;
-    SpecialPowers.flushPrefEnv(function () {
-      iframe.terminateWorker();
-      SimpleTest.finish();
-    });
+    testWin.terminateWorker();
+    testWin.close();
+    SimpleTest.finish();
   }
 
   function waitInterval() {
     if (finished) {
       return;
     }
-    is(String(iframe.location), "about:blank", "Wrong url!");
+    is(testWin.location.href, BLANK_URI, "Wrong url!");
     is(suspended, true, "Not suspended?");
     is(resumed, false, "Already resumed?!");
     is(lastCount, oldMessageCount, "Received a message while suspended!");
     if (++waitCount == 5) {
       clearInterval(interval);
       resumed = true;
-      iframe.history.back();
+      testWin.history.back();
     }
   }
 
   function badOnloadCallback() {
     if (finished) {
       return;
     }
-    ok(false, "We don't want suspend_iframe.html to fire a new load event, we want it to come out of the bfcache!");
+    ok(false, "We don't want suspend_window.html to fire a new load event, we want it to come out of the bfcache!");
     finishTest();
   }
 
   function suspendCallback() {
     if (finished) {
       return;
     }
-    is(String(iframe.location), "about:blank", "Wrong url!");
+    is(testWin.location.href, BLANK_URI, "Wrong url!");
     is(suspended, false, "Already suspended?");
     is(resumed, false, "Already resumed?");
-    SpecialPowers.popPrefEnv(function () {
-      suspended = true;
-      var iframeElement = document.getElementById("workerFrame");
-      iframeElement.onload = badOnloadCallback;
-      oldMessageCount = lastCount;
-      interval = setInterval(waitInterval, 1000);
-    });
+    suspended = true;
+    oldMessageCount = lastCount;
+    interval = setInterval(waitInterval, 1000);
   }
 
   function messageCallback(data) {
     if (finished) {
       return;
     }
 
     if (!suspended) {
       ok(lastCount === undefined || lastCount == data - 1,
          "Got good data, lastCount = " + lastCount + ", data = " + data);
       lastCount = data;
       if (lastCount == 25) {
-        SpecialPowers.pushPrefEnv({"set": [["browser.sessionhistory.cache_subframes", true]]}, function () {
-          iframe.location = "about:blank";
-          // We want suspend_iframe.html to go into bfcache, so we need to flush
-          // out all pending notifications. Otherwise, if they're flushed too
-          // late, they could kick us out of the bfcache again.
-          iframe.document.body.offsetTop;
-        });
+        testWin.location = "blank.html";
+        // We want suspend_window.html to go into bfcache, so we need to flush
+        // out all pending notifications. Otherwise, if they're flushed too
+        // late, they could kick us out of the bfcache again.
+        testWin.document.body.offsetTop;
       }
       return;
     }
 
-    var newLocation =
-      window.location.toString().replace("test_suspend.html",
-                                         "suspend_iframe.html");
-    is(newLocation.indexOf(iframe.location.toString()), 0, "Wrong url!");
+    var newLocation = location.href.replace("test_suspend.html",
+                                            "suspend_window.html");
+    is(testWin.location.href, newLocation, "Wrong url!");
     is(resumed, true, "Got message before resumed!");
     is(lastCount, data - 1, "Missed a message, suspend failed!");
     finishTest();
   }
 
   function errorCallback(data) {
     if (finished) {
       return;
     }
-    ok(false, "Iframe had an error: '" + data + "'");
+    ok(false, "testWin had an error: '" + data + "'");
     finishTest();
   }
 
-  function subframeLoaded() {
+  function testWinLoaded() {
     if (finished) {
       return;
     }
-    var iframeElement = document.getElementById("workerFrame");
-    iframeElement.onload = suspendCallback;
-
-    iframe = iframeElement.contentWindow;
-    ok(iframe, "No iframe?!");
-
-    iframe.startWorker(messageCallback, errorCallback);
+    testWin.startWorker(messageCallback, errorCallback);
   }
 
 </script>
 </pre>
 </body>
 </html>
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -459,46 +459,70 @@ WebRenderLayerManager::PushImage(nsDispl
   return true;
 }
 
 static void
 PaintItemByDrawTarget(nsDisplayItem* aItem,
                       DrawTarget* aDT,
                       const LayerRect& aImageRect,
                       const LayerPoint& aOffset,
-                      nsDisplayListBuilder* aDisplayListBuilder)
+                      nsDisplayListBuilder* aDisplayListBuilder,
+                      RefPtr<BasicLayerManager>& aManager,
+                      WebRenderLayerManager* aWrManager)
 {
   MOZ_ASSERT(aDT);
 
   aDT->ClearRect(aImageRect.ToUnknownRect());
   RefPtr<gfxContext> context = gfxContext::CreateOrNull(aDT);
   MOZ_ASSERT(context);
 
   context->SetMatrix(gfxMatrix::Translation(-aOffset.x, -aOffset.y));
   switch (aItem->GetType()) {
   case DisplayItemType::TYPE_MASK:
     static_cast<nsDisplayMask*>(aItem)->PaintMask(aDisplayListBuilder, context);
     break;
   case DisplayItemType::TYPE_FILTER:
     {
-      RefPtr<BasicLayerManager> tempManager = new BasicLayerManager(BasicLayerManager::BLM_INACTIVE);
-      FrameLayerBuilder* layerBuilder = new FrameLayerBuilder();
-      layerBuilder->Init(aDisplayListBuilder, tempManager);
-
-      tempManager->BeginTransactionWithTarget(context);
-      ContainerLayerParameters param;
-      RefPtr<Layer> layer = aItem->BuildLayer(aDisplayListBuilder, tempManager, param);
-      if (layer) {
-        tempManager->SetRoot(layer);
-        static_cast<nsDisplayFilter*>(aItem)->PaintAsLayer(aDisplayListBuilder, context, tempManager);
+      if (aManager == nullptr) {
+        aManager = new BasicLayerManager(BasicLayerManager::BLM_INACTIVE);
       }
 
-      if (tempManager->InTransaction()) {
-        tempManager->AbortTransaction();
+      FrameLayerBuilder* layerBuilder = new FrameLayerBuilder();
+      layerBuilder->Init(aDisplayListBuilder, aManager);
+      layerBuilder->DidBeginRetainedLayerTransaction(aManager);
+
+      aManager->BeginTransactionWithTarget(context);
+
+      ContainerLayerParameters param;
+      RefPtr<Layer> layer =
+        static_cast<nsDisplayFilter*>(aItem)->BuildLayer(aDisplayListBuilder,
+                                                         aManager, param);
+
+      if (layer) {
+        UniquePtr<LayerProperties> props;
+        props = Move(LayerProperties::CloneFrom(aManager->GetRoot()));
+
+        aManager->SetRoot(layer);
+        layerBuilder->WillEndTransaction();
+
+        nsIntRegion invalid;
+        props->ComputeDifferences(layer, invalid, nullptr);
+
+        static_cast<nsDisplayFilter*>(aItem)->PaintAsLayer(aDisplayListBuilder,
+                                                           context, aManager);
+
+        if (!invalid.IsEmpty()) {
+          aWrManager->SetNotifyInvalidation(true);
+        }
       }
+
+      if (aManager->InTransaction()) {
+        aManager->AbortTransaction();
+      }
+      aManager->SetTarget(nullptr);
       break;
     }
   default:
     aItem->Paint(aDisplayListBuilder, context);
     break;
   }
 
   if (gfxPrefs::WebRenderHighlightPaintedLayers()) {
@@ -549,51 +573,60 @@ WebRenderLayerManager::GenerateFallbackD
 
   LayerIntSize imageSize = RoundedToInt(bounds.Size());
   aImageRect = LayerRect(LayerPoint(0, 0), LayerSize(imageSize));
   if (imageSize.width == 0 || imageSize.height == 0) {
     return nullptr;
   }
 
   aOffset = RoundedToInt(bounds.TopLeft());
-  nsRegion invalidRegion;
+
+  bool needPaint = true;
   nsAutoPtr<nsDisplayItemGeometry> geometry = fallbackData->GetGeometry();
 
-  if (geometry) {
+
+  // nsDisplayFilter is rendered via BasicLayerManager which means the invalidate
+  // region is unknown until we traverse the displaylist contained by it.
+  if (geometry && !fallbackData->IsInvalid() &&
+      aItem->GetType() != DisplayItemType::TYPE_FILTER) {
     nsRect invalid;
+    nsRegion invalidRegion;
+
     if (aItem->IsInvalid(invalid)) {
       invalidRegion.OrWith(clippedBounds);
     } else {
       nsPoint shift = itemBounds.TopLeft() - geometry->mBounds.TopLeft();
       geometry->MoveBy(shift);
       aItem->ComputeInvalidationRegion(aDisplayListBuilder, geometry, &invalidRegion);
 
       nsRect lastBounds = fallbackData->GetBounds();
       lastBounds.MoveBy(shift);
 
       if (!lastBounds.IsEqualInterior(clippedBounds)) {
         invalidRegion.OrWith(lastBounds);
         invalidRegion.OrWith(clippedBounds);
       }
     }
+    needPaint = !invalidRegion.IsEmpty();
   }
 
-  gfx::SurfaceFormat format = aItem->GetType() == DisplayItemType::TYPE_MASK ?
-                                                    gfx::SurfaceFormat::A8 : gfx::SurfaceFormat::B8G8R8A8;
-  if (!geometry || !invalidRegion.IsEmpty() || fallbackData->IsInvalid()) {
+  if (needPaint) {
+    gfx::SurfaceFormat format = aItem->GetType() == DisplayItemType::TYPE_MASK ?
+                                                      gfx::SurfaceFormat::A8 : gfx::SurfaceFormat::B8G8R8A8;
     if (gfxPrefs::WebRenderBlobImages()) {
       bool snapped;
       bool isOpaque = aItem->GetOpaqueRegion(aDisplayListBuilder, &snapped).Contains(clippedBounds);
 
       RefPtr<gfx::DrawEventRecorderMemory> recorder = MakeAndAddRef<gfx::DrawEventRecorderMemory>();
       // TODO: should use 'format' to replace gfx::SurfaceFormat::B8G8R8A8. Currently blob image doesn't support A8 format.
       RefPtr<gfx::DrawTarget> dummyDt =
         gfx::Factory::CreateDrawTarget(gfx::BackendType::SKIA, gfx::IntSize(1, 1), gfx::SurfaceFormat::B8G8R8A8);
       RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateRecordingDrawTarget(recorder, dummyDt, imageSize.ToUnknownSize());
-      PaintItemByDrawTarget(aItem, dt, aImageRect, aOffset, aDisplayListBuilder);
+      PaintItemByDrawTarget(aItem, dt, aImageRect, aOffset, aDisplayListBuilder,
+                            fallbackData->mBasicLayerManager, this);
       recorder->Finish();
 
       Range<uint8_t> bytes((uint8_t*)recorder->mOutputStream.mData, recorder->mOutputStream.mLength);
       wr::ImageKey key = WrBridge()->GetNextImageKey();
       wr::ImageDescriptor descriptor(imageSize.ToUnknownSize(), 0, dt->GetFormat(), isOpaque);
       aResources.AddBlobImage(key, descriptor, bytes);
       fallbackData->SetKey(key);
     } else {
@@ -603,17 +636,19 @@ WebRenderLayerManager::GenerateFallbackD
 
       {
         UpdateImageHelper helper(imageContainer, imageClient, imageSize.ToUnknownSize(), format);
         {
           RefPtr<gfx::DrawTarget> dt = helper.GetDrawTarget();
           if (!dt) {
             return nullptr;
           }
-          PaintItemByDrawTarget(aItem, dt, aImageRect, aOffset, aDisplayListBuilder);
+          PaintItemByDrawTarget(aItem, dt, aImageRect, aOffset,
+                                aDisplayListBuilder,
+                                fallbackData->mBasicLayerManager, this);
         }
         if (!helper.UpdateImage()) {
           return nullptr;
         }
       }
 
       // Force update the key in fallback data since we repaint the image in this path.
       // If not force update, fallbackData may reuse the original key because it
--- a/gfx/layers/wr/WebRenderLayerManager.h
+++ b/gfx/layers/wr/WebRenderLayerManager.h
@@ -241,16 +241,17 @@ public:
     if (T::Type() == WebRenderUserData::UserDataType::eCanvas) {
       mLastCanvasDatas.PutEntry(data->AsCanvasData());
     }
     RefPtr<T> res = static_cast<T*>(data.get());
     return res.forget();
   }
 
   bool ShouldNotifyInvalidation() const { return mShouldNotifyInvalidation; }
+  void SetNotifyInvalidation(bool aShouldNotifyInvalidation) { mShouldNotifyInvalidation = aShouldNotifyInvalidation; }
 
 private:
   /**
    * Take a snapshot of the parent context, and copy
    * it into mTarget.
    */
   void MakeSnapshotIfRequired(LayoutDeviceIntSize aSize);
 
--- a/gfx/layers/wr/WebRenderUserData.h
+++ b/gfx/layers/wr/WebRenderUserData.h
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * 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/. */
 
 #ifndef GFX_WEBRENDERUSERDATA_H
 #define GFX_WEBRENDERUSERDATA_H
 
+#include "BasicLayers.h"                // for BasicLayerManager
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/webrender/WebRenderAPI.h"
 #include "mozilla/layers/AnimationInfo.h"
 
 class nsDisplayItemGeometry;
 
 namespace mozilla {
 namespace wr {
@@ -122,16 +123,17 @@ public:
   static UserDataType Type() { return UserDataType::eFallback; }
   nsAutoPtr<nsDisplayItemGeometry> GetGeometry();
   void SetGeometry(nsAutoPtr<nsDisplayItemGeometry> aGeometry);
   nsRect GetBounds() { return mBounds; }
   void SetBounds(const nsRect& aRect) { mBounds = aRect; }
   void SetInvalid(bool aInvalid) { mInvalid = aInvalid; }
   bool IsInvalid() { return mInvalid; }
 
+  RefPtr<BasicLayerManager> mBasicLayerManager;
 protected:
   nsAutoPtr<nsDisplayItemGeometry> mGeometry;
   nsRect mBounds;
   bool mInvalid;
 };
 
 class WebRenderAnimationData : public WebRenderUserData
 {
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -1502,16 +1502,18 @@ nsDocumentViewer::Open(nsISupports *aSta
       nsCOMPtr<nsIDocShell> shell = do_QueryInterface(item);
       AttachContainerRecurse(shell);
     }
   }
 
   SyncParentSubDocMap();
 
   if (mFocusListener && mDocument) {
+    // The focus listener may have been disconnected.
+    mFocusListener->Init(this);
     mDocument->AddEventListener(NS_LITERAL_STRING("focus"), mFocusListener,
                                 false, false);
     mDocument->AddEventListener(NS_LITERAL_STRING("blur"), mFocusListener,
                                 false, false);
   }
 
   // XXX re-enable image animations once that works correctly
 
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -9251,17 +9251,16 @@ nsDisplayFilter::BuildLayer(nsDisplayLis
              "By getting here, we must have valid CSS filters.");
 
   ContainerLayerParameters newContainerParameters = aContainerParameters;
   newContainerParameters.mDisableSubpixelAntialiasingInDescendants = true;
 
   RefPtr<ContainerLayer> container = aManager->GetLayerBuilder()->
     BuildContainerLayerFor(aBuilder, aManager, mFrame, this, &mList,
                            newContainerParameters, nullptr);
-
   LayerState state = this->GetLayerState(aBuilder, aManager, newContainerParameters);
   if (container && state != LAYER_SVG_EFFECTS) {
     const nsTArray<nsStyleFilter>& filters = mFrame->StyleEffects()->mFilters;
     nsTArray<layers::CSSFilter> cssFilters = nsTArray<layers::CSSFilter>(filters.Length());
     for (const nsStyleFilter& filter : filters) {
       cssFilters.AppendElement(ToCSSFilter(filter));
     }
 
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1744,17 +1744,17 @@ fuzzy-if(true,17,5886) fuzzy-if(skiaCont
 == 776265-2a.html 776265-2-ref.html
 == 776265-2b.html 776265-2-ref.html
 == 776265-2c.html 776265-2-ref.html
 == 776265-2d.html 776265-2-ref.html
 == 776443-1.html 776443-1-ref.html
 == 776443-2.html 776443-2-ref.html
 == 786254-1.html 786254-1-ref.html
 == 787947-1.html 787947-1-ref.html
-== 796847-1.svg 796847-1-ref.svg
+pref(gfx.webrender.layers-free,true) == 796847-1.svg 796847-1-ref.svg
 fuzzy(40,875) fuzzy-if(skiaContent,1,2500) == 797797-1.html 797797-1-ref.html # 'opacity:N' and rgba(,,,N) text don't match precisely
 fuzzy(40,850) fuzzy-if(skiaContent,2,2310) == 797797-2.html 797797-2-ref.html # 'opacity:N' and rgba(,,,N) text don't match precisely
 == 801994-1.html 801994-1-ref.html
 == 804323-1.html 804323-1-ref.html
 fuzzy-if(Android,8,608) == 811301-1.html 811301-1-ref.html
 == 812824-1.html 812824-1-ref.html
 == 814677.html 814677-ref.html
 == 814952-1.html 814952-1-ref.html