merge autoland to mozilla-central.r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Mon, 25 Sep 2017 11:56:13 +0200
changeset 669739 13ce77b78e364cc952d51b959f22202502be2941
parent 669596 7e962631ba4298bcefa571008661983d77c3e652 (current diff)
parent 669690 f01e65b7038feefbc93f198016c3c82ba5c02fb3 (diff)
child 669742 5f3f19824efa14cc6db546baf59c54a0fc15ddc9
push id81419
push userbmo:standard8@mozilla.com
push dateMon, 25 Sep 2017 10:59:32 +0000
reviewersmerge, merge
milestone58.0a1
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