Bug 1279086 - Allow painting for tab switch when JS is running (r=dvander,mconley,mrbkap)
authorBill McCloskey <billm@mozilla.com>
Fri, 22 Jul 2016 16:36:45 -0700
changeset 342850 cfcd8c4f3a36b958002010a9c6d461a7769996d5
parent 342849 0a47c0c2ba3495fa0a62e6bfc010badbd24ef6c9
child 342851 ea326f6e7e6c485907fdb4e1739bcb2707f7c1bc
push id6570
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:26:13 +0000
treeherdermozilla-esr52@f455459b2ae5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdvander, mconley, mrbkap
bugs1279086
milestone51.0a1
Bug 1279086 - Allow painting for tab switch when JS is running (r=dvander,mconley,mrbkap)
browser/base/content/browser.js
browser/base/content/tabbrowser.xml
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
docshell/base/nsIDocShell.idl
dom/base/nsFrameLoader.cpp
dom/base/nsIFrameLoader.idl
dom/interfaces/base/nsITabParent.idl
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PBrowser.ipdl
dom/ipc/PProcessHangMonitor.ipdl
dom/ipc/ProcessHangMonitor.cpp
dom/ipc/ProcessHangMonitor.h
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
gfx/ipc/GPUProcessManager.cpp
gfx/ipc/GPUProcessManager.h
gfx/layers/client/ClientLayerManager.cpp
gfx/layers/client/ClientLayerManager.h
gfx/layers/ipc/CompositorBridgeParent.cpp
gfx/layers/ipc/CompositorBridgeParent.h
gfx/layers/ipc/LayerTransactionParent.cpp
gfx/layers/ipc/LayerTransactionParent.h
gfx/layers/ipc/PLayerTransaction.ipdl
gfx/layers/ipc/ShadowLayers.cpp
gfx/layers/ipc/ShadowLayers.h
js/src/vm/Interpreter.cpp
layout/base/nsIPresShell.h
layout/base/nsPresShell.cpp
layout/base/nsPresShell.h
toolkit/components/printing/content/printUtils.js
toolkit/components/viewsource/content/viewSource.js
toolkit/content/widgets/browser.xml
toolkit/content/widgets/remote-browser.xml
widget/PuppetWidget.cpp
widget/PuppetWidget.h
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3306,16 +3306,17 @@ var PrintPreviewListener = {
     this._tabBeforePrintPreview = null;
     gInPrintPreviewMode = false;
     this._toggleAffectedChrome();
     if (this._simplifyPageTab) {
       gBrowser.removeTab(this._simplifyPageTab);
       this._simplifyPageTab = null;
     }
     gBrowser.removeTab(this._printPreviewTab);
+    gBrowser.deactivatePrintPreviewBrowsers();
     this._printPreviewTab = null;
   },
   _toggleAffectedChrome: function () {
     gNavToolbox.collapsed = gInPrintPreviewMode;
 
     if (gInPrintPreviewMode)
       this._hideChrome();
     else
@@ -3361,17 +3362,21 @@ var PrintPreviewListener = {
     if (this._chromeState.globalNotificationsOpen)
       document.getElementById("global-notificationbox").notificationsHidden = false;
 
     if (this._chromeState.syncNotificationsOpen)
       document.getElementById("sync-notifications").notificationsHidden = false;
 
     if (this._chromeState.sidebarOpen)
       SidebarUI.show(this._sidebarCommand);
-  }
+  },
+
+  activateBrowser(browser) {
+    gBrowser.activateBrowserForPrintPreview(browser);
+  },
 }
 
 function getMarkupDocumentViewer()
 {
   return gBrowser.markupDocumentViewer;
 }
 
 // This function is obsolete. Newer code should use <tooltip page="true"/> instead.
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1650,18 +1650,17 @@
             }
 
             aBrowser.droppedLinkHandler = droppedLinkHandler;
 
             // Switching a browser's remoteness will create a new frameLoader.
             // As frameLoaders start out with an active docShell we have to
             // deactivate it if this is not the selected tab's browser or the
             // browser window is minimized.
-            aBrowser.docShellIsActive = (aBrowser == this.selectedBrowser &&
-                                         window.windowState != window.STATE_MINIMIZED);
+            aBrowser.docShellIsActive = this.shouldActivateDocShell(aBrowser);
 
             // Create a new tab progress listener for the new browser we just injected,
             // since tab progress listeners have logic for handling the initial about:blank
             // load
             listener = this.mTabProgressListener(tab, aBrowser, false, false);
             this._tabListeners.set(tab, listener);
             filter.addProgressListener(listener, Ci.nsIWebProgress.NOTIFY_ALL);
 
@@ -2754,18 +2753,21 @@
 
             // Unmap old outerWindowIDs.
             this._outerWindowIDBrowserMap.delete(ourBrowser.outerWindowID);
             let remoteBrowser = aOtherBrowser.ownerDocument.defaultView.gBrowser;
             if (remoteBrowser) {
               remoteBrowser._outerWindowIDBrowserMap.delete(aOtherBrowser.outerWindowID);
             }
 
-            aOtherBrowser.docShellIsActive = (ourBrowser == this.selectedBrowser &&
-                                              window.windowState != window.STATE_MINIMIZED);
+            // If switcher is active, it will intercept swap events and
+            // react as needed.
+            if (!this._switcher) {
+              aOtherBrowser.docShellIsActive = this.shouldActivateDocShell(ourBrowser);
+            }
 
             // Swap the docshells
             ourBrowser.swapDocShells(aOtherBrowser);
 
             if (ourBrowser.isRemoteBrowser) {
               // Switch outerWindowIDs for remote browsers.
               let ourOuterWindowID = ourBrowser._outerWindowID;
               ourBrowser._outerWindowID = aOtherBrowser._outerWindowID;
@@ -3261,16 +3263,66 @@
         <body>
           <![CDATA[
             return SessionStore.duplicateTab(window, aTab, 0, aRestoreTabImmediately);
           ]]>
         </body>
       </method>
 
       <!--
+        List of browsers whose docshells must be active in order for print preview
+        to work.
+      -->
+      <field name="_printPreviewBrowsers">
+        new Set()
+      </field>
+
+      <method name="activateBrowserForPrintPreview">
+        <parameter name="aBrowser"/>
+        <body>
+          <![CDATA[
+            this._printPreviewBrowsers.add(aBrowser);
+            if (this._switcher) {
+              this._switcher.activateBrowserForPrintPreview(aBrowser);
+            }
+            aBrowser.docShellIsActive = true;
+          ]]>
+        </body>
+      </method>
+
+      <method name="deactivatePrintPreviewBrowsers">
+        <body>
+          <![CDATA[
+            let browsers = this._printPreviewBrowsers;
+            this._printPreviewBrowsers = new Set();
+            for (let browser of browsers) {
+              browser.docShellIsActive = this.shouldActivateDocShell(browser);
+            }
+          ]]>
+        </body>
+      </method>
+
+      <!--
+        Returns true if a given browser's docshell should be active.
+      -->
+      <method name="shouldActivateDocShell">
+        <parameter name="aBrowser"/>
+        <body>
+          <![CDATA[
+            if (this._switcher) {
+              return this._switcher.shouldActivateDocShell(aBrowser);
+            }
+            return (aBrowser == this.selectedBrowser &&
+                    window.windowState != window.STATE_MINIMIZED) ||
+                   this._printPreviewBrowsers.has(aBrowser);
+          ]]>
+        </body>
+      </method>
+
+      <!--
         The tab switcher is responsible for asynchronously switching
         tabs in e10s. It waits until the new tab is ready (i.e., the
         layer tree is available) before switching to it. Then it
         unloads the layer tree for the old tab.
 
         The tab switcher is a state machine. For each tab, it
         maintains state about whether the layer tree for the tab is
         available, being loaded, being unloaded, or unavailable. It
@@ -3291,45 +3343,22 @@
 
         3. We discard layer trees on a delay. This way, if the user is
         switching among the same tabs frequently, we don't continually
         load the same tabs.
 
         It's important that we always show either the spinner or a tab
         whose layers are available. Otherwise the compositor will draw
         an entirely black frame, which is very jarring. To ensure this
-        never happens, we do the following:
-
-        1. When switching away from a tab, we assume the old tab might
-        still be drawn until a MozAfterPaint event occurs. Because
-        layout and compositing happen asynchronously, we don't have
-        any other way of knowing when the switch actually takes
-        place. Therefore, we don't unload the old tab until the next
-        MozAfterPaint event.
-
-        2. Suppose that the user switches from tab A to B and then
-        back to A. Suppose that we ask for tab A's layers to be
-        unloaded via message M1 after the first switch and then
-        request them again via message M2 once the second switch
-        happens. Both loading and unloading of layers happens
-        asynchronously, and this can cause problems. It's possible
-        that the content process publishes one last layer tree before
-        M1 is received. The parent process doesn't know that this
-        layer tree was published before M1 and not after M2, so it
-        will display the tab. However, once M1 arrives, the content
-        process will destroy the layer tree for A and now we will
-        display black for it.
-
-        To counter this problem, we keep tab A in a separate
-        "unloading" state until the layer tree is actually dropped in
-        the compositor thread. While the tab is in the "unloading"
-        state, we're not allowed to request layers for it. Once the
-        layers are dropped in the compositor, an event will fire and
-        we will transition the tab to the "unloaded" state. Then we
-        are free to request the tab's layers again.
+        never happens when switching away from a tab, we assume the
+        old tab might still be drawn until a MozAfterPaint event
+        occurs. Because layout and compositing happen asynchronously,
+        we don't have any other way of knowing when the switch
+        actually takes place. Therefore, we don't unload the old tab
+        until the next MozAfterPaint event.
       -->
       <field name="_switcher">null</field>
       <method name="_getSwitcher">
         <body><![CDATA[
           if (this._switcher) {
             return this._switcher;
           }
 
@@ -3412,60 +3441,84 @@
             getTabState: function(tab) {
               let state = this.tabState.get(tab);
               if (state === undefined) {
                 return this.STATE_UNLOADED;
               }
               return state;
             },
 
-            setTabState: function(tab, state) {
+            setTabStateNoAction(tab, state) {
               if (state == this.STATE_UNLOADED) {
                 this.tabState.delete(tab);
               } else {
                 this.tabState.set(tab, state);
               }
+            },
+
+            setTabState: function(tab, state) {
+              this.setTabStateNoAction(tab, state);
 
               let browser = tab.linkedBrowser;
-              let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
+              let {tabParent} = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
               if (state == this.STATE_LOADING) {
-                // Ask for a MozLayerTreeReady event.
-                fl.requestNotifyLayerTreeReady();
+                this.assert(!this.minimized);
                 browser.docShellIsActive = true;
+                if (!tabParent) {
+                  this.onLayersReady(browser);
+		}
               } else if (state == this.STATE_UNLOADING) {
-                // Ask for MozLayerTreeCleared event.
-                fl.requestNotifyLayerTreeCleared();
                 browser.docShellIsActive = false;
+                if (!tabParent) {
+                  this.onLayersCleared(browser);
+                }
               }
             },
 
+            get minimized() {
+              return window.windowState == window.STATE_MINIMIZED;
+            },
+
             init: function() {
               this.log("START");
 
+              // If we minimized the window before the switcher was activated,
+              // we might have set  the preserveLayers flag for the current
+              // browser. Let's clear it.
+              this.tabbrowser.mCurrentBrowser.preserveLayers(false);
+
               window.addEventListener("MozAfterPaint", this);
               window.addEventListener("MozLayerTreeReady", this);
               window.addEventListener("MozLayerTreeCleared", this);
               window.addEventListener("TabRemotenessChange", this);
-              this.setTabState(this.requestedTab, this.STATE_LOADED);
+              window.addEventListener("sizemodechange", this);
+              window.addEventListener("SwapDocShells", this, true);
+              window.addEventListener("EndSwapDocShells", this, true);
+              if (!this.minimized) {
+                this.setTabState(this.requestedTab, this.STATE_LOADED);
+              }
             },
 
             destroy: function() {
               if (this.unloadTimer) {
                 this.clearTimer(this.unloadTimer);
                 this.unloadTimer = null;
               }
               if (this.loadTimer) {
                 this.clearTimer(this.loadTimer);
                 this.loadTimer = null;
               }
 
               window.removeEventListener("MozAfterPaint", this);
               window.removeEventListener("MozLayerTreeReady", this);
               window.removeEventListener("MozLayerTreeCleared", this);
               window.removeEventListener("TabRemotenessChange", this);
+              window.removeEventListener("sizemodechange", this);
+              window.removeEventListener("SwapDocShells", this, true);
+              window.removeEventListener("EndSwapDocShells", this, true);
 
               this.tabbrowser._switcher = null;
 
               this.activeSuppressDisplayport.forEach(function(tabParent) {
                 tabParent.suppressDisplayport(false);
               });
               this.activeSuppressDisplayport.clear();
             },
@@ -3474,17 +3527,17 @@
               this.log("FINISH");
 
               this.assert(this.tabbrowser._switcher);
               this.assert(this.tabbrowser._switcher === this);
               this.assert(!this.spinnerTab);
               this.assert(!this.loadTimer);
               this.assert(!this.loadingTab);
               this.assert(this.lastVisibleTab === this.requestedTab);
-              this.assert(this.getTabState(this.requestedTab) == this.STATE_LOADED);
+              this.assert(this.minimized || this.getTabState(this.requestedTab) == this.STATE_LOADED);
 
               this.destroy();
 
               let toBrowser = this.requestedTab.linkedBrowser;
               toBrowser.setAttribute("type", "content-primary");
 
               this.tabbrowser._adjustFocusAfterTabSwitch(this.requestedTab);
 
@@ -3514,17 +3567,17 @@
                 // available, show it.
                 showTab = this.lastVisibleTab;
               } else {
                 // Show the requested tab. If it's not available, we'll show the spinner.
                 showTab = this.requestedTab;
               }
 
               // Show or hide the spinner as needed.
-              let needSpinner = this.getTabState(showTab) != this.STATE_LOADED;
+              let needSpinner = this.getTabState(showTab) != this.STATE_LOADED && !this.minimized;
               if (!needSpinner && this.spinnerTab) {
                 this.spinnerHidden();
                 this.tabbrowser.removeAttribute("pendingpaint");
                 this.spinnerTab.linkedBrowser.removeAttribute("pendingpaint");
                 this.spinnerTab = null;
               } else if (needSpinner && this.spinnerTab !== showTab) {
                 if (this.spinnerTab) {
                   this.spinnerTab.linkedBrowser.removeAttribute("pendingpaint");
@@ -3569,24 +3622,25 @@
                 dump("Assertion failure\n" + Error().stack);
                 throw new Error("Assertion failure");
               }
             },
 
             // We've decided to try to load requestedTab.
             loadRequestedTab: function() {
               this.assert(!this.loadTimer);
+              this.assert(!this.minimized);
 
               // loadingTab can be non-null here if we timed out loading the current tab.
               // In that case we just overwrite it with a different tab; it's had its chance.
               this.loadingTab = this.requestedTab;
               this.log("Loading tab " + this.tinfo(this.loadingTab));
 
+              this.loadTimer = this.setTimer(() => this.onLoadTimeout(), this.TAB_SWITCH_TIMEOUT);
               this.setTabState(this.requestedTab, this.STATE_LOADING);
-              this.loadTimer = this.setTimer(() => this.onLoadTimeout(), this.TAB_SWITCH_TIMEOUT);
             },
 
             // This function runs before every event. It fixes up the state
             // to account for closed tabs.
             preActions: function() {
               this.assert(this.tabbrowser._switcher);
               this.assert(this.tabbrowser._switcher === this);
 
@@ -3621,23 +3675,31 @@
                           this.getTabState(this.loadingTab) == this.STATE_LOADING);
 
               // We guarantee that loadingTab is non-null iff loadTimer is non-null. So
               // the timer is set only when we're loading something.
               this.assert(!this.loadTimer || this.loadingTab);
               this.assert(!this.loadingTab || this.loadTimer);
 
               // If we're not loading anything, try loading the requested tab.
-              if (!this.loadTimer && this.getTabState(this.requestedTab) == this.STATE_UNLOADED) {
+              let requestedState = this.getTabState(this.requestedTab);
+              if (!this.loadTimer && !this.minimized &&
+                  (requestedState == this.STATE_UNLOADED ||
+                   requestedState == this.STATE_UNLOADING)) {
                 this.loadRequestedTab();
               }
 
               // See how many tabs still have work to do.
               let numPending = 0;
               for (let [tab, state] of this.tabState) {
+                // Skip print preview browsers since they shouldn't affect tab switching.
+                if (this.tabbrowser._printPreviewBrowsers.has(tab.linkedBrowser)) {
+                  continue;
+                }
+
                 if (state == this.STATE_LOADED && tab !== this.requestedTab) {
                   numPending++;
                 }
                 if (state == this.STATE_LOADING || state == this.STATE_UNLOADING) {
                   numPending++;
                 }
               }
 
@@ -3662,16 +3724,20 @@
               this.logState("onUnloadTimeout");
               this.unloadTimer = null;
               this.preActions();
 
               let numPending = 0;
 
               // Unload any tabs that can be unloaded.
               for (let [tab, state] of this.tabState) {
+                if (this.tabbrowser._printPreviewBrowsers.has(tab.linkedBrowser)) {
+                  continue;
+                }
+
                 if (state == this.STATE_LOADED &&
                     !this.maybeVisibleTabs.has(tab) &&
                     tab !== this.lastVisibleTab &&
                     tab !== this.loadingTab &&
                     tab !== this.requestedTab)
                 {
                   this.setTabState(tab, this.STATE_UNLOADING);
                 }
@@ -3695,19 +3761,21 @@
               this.preActions();
               this.loadTimer = null;
               this.loadingTab = null;
               this.postActions();
             },
 
             // Fires when the layers become available for a tab.
             onLayersReady: function(browser) {
-              this.logState("onLayersReady");
-
               let tab = this.tabbrowser.getTabForBrowser(browser);
+              this.logState(`onLayersReady(${tab._tPos})`);
+
+              this.assert(this.getTabState(tab) == this.STATE_LOADING ||
+                          this.getTabState(tab) == this.STATE_LOADED);
               this.setTabState(tab, this.STATE_LOADED);
 
               this.maybeFinishTabSwitch();
 
               if (this.loadingTab === tab) {
                 this.clearTimer(this.loadTimer);
                 this.loadTimer = null;
                 this.loadingTab = null;
@@ -3719,29 +3787,30 @@
             // around.
             onPaint: function() {
               this.maybeVisibleTabs.clear();
               this.maybeFinishTabSwitch();
             },
 
             // Called when we're done clearing the layers for a tab.
             onLayersCleared: function(browser) {
-              this.logState("onLayersCleared");
-
               let tab = this.tabbrowser.getTabForBrowser(browser);
               if (tab) {
+                this.logState(`onLayersCleared(${tab._tPos})`);
+                this.assert(this.getTabState(tab) == this.STATE_UNLOADING ||
+                            this.getTabState(tab) == this.STATE_UNLOADED);
                 this.setTabState(tab, this.STATE_UNLOADED);
               }
             },
 
             // Called when a tab switches from remote to non-remote. In this case
             // a MozLayerTreeReady notification that we requested may never fire,
             // so we need to simulate it.
             onRemotenessChange: function(tab) {
-              this.logState("onRemotenessChange");
+              this.logState(`onRemotenessChange(${tab._tPos}, ${tab.linkedBrowser.isRemoteBrowser})`);
               if (!tab.linkedBrowser.isRemoteBrowser) {
                 if (this.getTabState(tab) == this.STATE_LOADING) {
                   this.onLayersReady(tab.linkedBrowser);
                 } else if (this.getTabState(tab) == this.STATE_UNLOADING) {
                   this.onLayersCleared(tab.linkedBrowser);
                 }
               }
             },
@@ -3754,32 +3823,103 @@
                 // going to be removed during this tick of the event loop.
                 // This will cause us to show a tab spinner instead.
                 this.preActions();
                 this.lastVisibleTab = null;
                 this.postActions();
               }
             },
 
+            onSizeModeChange() {
+              if (this.minimized) {
+                for (let [tab, state] of this.tabState) {
+                  // Skip print preview browsers since they shouldn't affect tab switching.
+                  if (this.tabbrowser._printPreviewBrowsers.has(tab.linkedBrowser)) {
+                    continue;
+                  }
+
+                  if (state == this.STATE_LOADING || state == this.STATE_LOADED) {
+                    this.setTabState(tab, this.STATE_UNLOADING);
+                  }
+                }
+                if (this.loadTimer) {
+                  this.clearTimer(this.loadTimer);
+                  this.loadTimer = null;
+                }
+                this.loadingTab = null;
+              } else {
+                // Do nothing. We'll automatically start loading the requested tab in
+                // postActions.
+              }
+            },
+
+            onSwapDocShells(ourBrowser, otherBrowser) {
+              // This event fires before the swap. ourBrowser is from
+              // our window. We save the state of otherBrowser since ourBrowser
+              // needs to take on that state at the end of the swap.
+
+              let otherTabbrowser = otherBrowser.ownerDocument.defaultView.gBrowser;
+              let otherState;
+              if (otherTabbrowser && otherTabbrowser._switcher) {
+                let otherTab = otherTabbrowser.getTabForBrowser(otherBrowser);
+                otherState = otherTabbrowser._switcher.getTabState(otherTab);
+              } else {
+                otherState = (otherBrowser.docShellIsActive
+                              ? this.STATE_LOADED
+                              : this.STATE_UNLOADED);
+              }
+
+              if (!this.swapMap) {
+                this.swapMap = new WeakMap();
+              }
+              this.swapMap.set(otherBrowser, otherState);
+            },
+
+            onEndSwapDocShells(ourBrowser, otherBrowser) {
+              // The swap has happened. We reset the loadingTab in
+              // case it has been swapped. We also set ourBrowser's state
+              // to whatever otherBrowser's state was before the swap.
+
+              if (this.loadTimer) {
+                // Clearing the load timer means that we will
+                // immediately display a spinner if ourBrowser isn't
+                // ready yet. Typically it will already be ready
+                // though. If it's not, we're probably in a new window,
+                // in which case we have no other tabs to display anyway.
+                this.clearTimer(this.loadTimer);
+                this.loadTimer = null;
+              }
+              this.loadingTab = null;
+
+              let otherState = this.swapMap.get(otherBrowser);
+              this.swapMap.delete(otherBrowser);
+
+              let ourTab = this.tabbrowser.getTabForBrowser(ourBrowser);
+              if (ourTab) {
+                this.setTabStateNoAction(ourTab, otherState);
+              }
+            },
+
+            shouldActivateDocShell(browser) {
+              let tab = this.tabbrowser.getTabForBrowser(browser);
+              let state = this.getTabState(tab);
+              return state == this.STATE_LOADING || state == this.STATE_LOADED;
+            },
+
+            activateBrowserForPrintPreview(browser) {
+	      let tab = this.tabbrowser.getTabForBrowser(browser);
+	      this.setTabState(tab, this.STATE_LOADING);
+	    },
+
             // Called when the user asks to switch to a given tab.
             requestTab: function(tab) {
               if (tab === this.requestedTab) {
                 return;
               }
 
-              // Instrumentation to figure out bug 1166351 - if the binding
-              // on the browser we're switching to has gone away, try to find out
-              // why. We should remove this and the checkBrowserBindingAlive
-              // method once bug 1166351 has been closed.
-              if (this.tabbrowser.AppConstants.E10S_TESTING_ONLY &&
-                  !this.checkBrowserBindingAlive(tab)) {
-                Cu.reportError("Please report the above errors in bug 1166351.");
-                return;
-              }
-
               this.logState("requestTab " + this.tinfo(tab));
               this.startTabSwitch();
 
               this.requestedTab = tab;
 
               let browser = this.requestedTab.linkedBrowser;
               let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
               if (fl && fl.tabParent && !this.activeSuppressDisplayport.has(fl.tabParent)) {
@@ -3813,16 +3953,22 @@
               if (event.type == "MozLayerTreeReady") {
                 this.onLayersReady(event.originalTarget);
               } if (event.type == "MozAfterPaint") {
                 this.onPaint();
               } else if (event.type == "MozLayerTreeCleared") {
                 this.onLayersCleared(event.originalTarget);
               } else if (event.type == "TabRemotenessChange") {
                 this.onRemotenessChange(event.target);
+              } else if (event.type == "sizemodechange") {
+                this.onSizeModeChange();
+              } else if (event.type == "SwapDocShells") {
+                this.onSwapDocShells(event.originalTarget, event.detail);
+              } else if (event.type == "EndSwapDocShells") {
+                this.onEndSwapDocShells(event.originalTarget, event.detail);
               }
 
               this.postActions();
               this._processing = false;
             },
 
             /*
              * Telemetry and Profiler related helpers for recording tab switch
@@ -3900,52 +4046,16 @@
                 result = Services.prefs.getBoolPref("browser.tabs.remote.logSwitchTiming");
               } catch (ex) {
               }
               this._shouldLog = result;
               this._logInit = true;
               return this._shouldLog;
             },
 
-            // Instrumentation for bug 1166351
-            checkBrowserBindingAlive: function(tab) {
-              let err = Cu.reportError;
-
-              if (!tab.linkedBrowser) {
-                err("Attempting to switch to tab that has no linkedBrowser.");
-                return false;
-              }
-
-              let b = tab.linkedBrowser;
-
-              if (!b._alive) {
-                // The browser binding has been removed. Dump a bunch of
-                // diagnostic information to the browser console.
-                let utils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
-                let results = utils.getBindingURLs(b);
-                let urls = [];
-
-                for (let i = 0; i < results.length; ++i) {
-                  urls.push(results.queryElementAt(i, Ci.nsIURI).spec);
-                }
-                err("The browser has the following bindings:");
-                err(urls);
-                err("MozBinding is currently: " +
-                    window.getComputedStyle(b).MozBinding);
-                if (!b.parentNode) {
-                  err("Browser was removed from the DOM.");
-                } else {
-                  err("Parent is: " + b.parentNode.outerHTML);
-                }
-                return false;
-              }
-
-              return true;
-            },
-
             tinfo: function(tab) {
               if (tab) {
                 return tab._tPos + "(" + tab.linkedBrowser.currentURI.spec + ")";
               }
               return "null";
             },
 
             log: function(s) {
@@ -4302,19 +4412,19 @@
           switch (aEvent.type) {
             case "keydown":
               this._handleKeyDownEvent(aEvent);
               break;
             case "keypress":
               this._handleKeyPressEventMac(aEvent);
               break;
             case "sizemodechange":
-              if (aEvent.target == window) {
-                this.mCurrentBrowser.setDocShellIsActiveAndForeground(
-                  window.windowState != window.STATE_MINIMIZED);
+              if (aEvent.target == window && !this._switcher) {
+                this.mCurrentBrowser.preserveLayers(window.windowState == window.STATE_MINIMIZED);
+                this.mCurrentBrowser.docShellIsActive = this.shouldActivateDocShell(this.mCurrentBrowser);
               }
               break;
           }
         ]]></body>
       </method>
 
       <method name="receiveMessage">
         <parameter name="aMessage"/>
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -6155,45 +6155,33 @@ nsDocShell::SetIsOffScreenBrowser(bool a
 NS_IMETHODIMP
 nsDocShell::GetIsOffScreenBrowser(bool* aIsOffScreen)
 {
   *aIsOffScreen = mIsOffScreenBrowser;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDocShell::SetIsActiveAndForeground(bool aIsActive)
-{
-  return SetIsActiveInternal(aIsActive, false);
-}
-
-NS_IMETHODIMP
 nsDocShell::SetIsActive(bool aIsActive)
 {
-  return SetIsActiveInternal(aIsActive, true);
-}
-
-nsresult
-nsDocShell::SetIsActiveInternal(bool aIsActive, bool aIsHidden)
-{
   // We disallow setting active on chrome docshells.
   if (mItemType == nsIDocShellTreeItem::typeChrome) {
     return NS_ERROR_INVALID_ARG;
   }
 
   // Keep track ourselves.
   mIsActive = aIsActive;
 
   // Clear prerender flag if necessary.
   mIsPrerendered &= !aIsActive;
 
   // Tell the PresShell about it.
   nsCOMPtr<nsIPresShell> pshell = GetPresShell();
   if (pshell) {
-    pshell->SetIsActive(aIsActive, aIsHidden);
+    pshell->SetIsActive(aIsActive);
   }
 
   // Tell the window about it
   if (mScriptGlobal) {
     mScriptGlobal->SetIsBackground(!aIsActive);
     if (nsCOMPtr<nsIDocument> doc = mScriptGlobal->GetExtantDoc()) {
       // Update orientation when the top-level browsing context becomes active.
       // We make an exception for apps because they currently rely on
@@ -6217,21 +6205,17 @@ nsDocShell::SetIsActiveInternal(bool aIs
   nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
   while (iter.HasMore()) {
     nsCOMPtr<nsIDocShell> docshell = do_QueryObject(iter.GetNext());
     if (!docshell) {
       continue;
     }
 
     if (!docshell->GetIsMozBrowserOrApp()) {
-      if (aIsHidden) {
-        docshell->SetIsActive(aIsActive);
-      } else {
-        docshell->SetIsActiveAndForeground(aIsActive);
-      }
+      docshell->SetIsActive(aIsActive);
     }
   }
 
   // Restart or stop meta refresh timers if necessary
   if (mDisableMetaRefreshWhenInactive) {
     if (mIsActive) {
       ResumeRefreshURIs();
     } else {
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -498,18 +498,16 @@ protected:
 
   // overridden from nsDocLoader, this provides more information than the
   // normal OnStateChange with flags STATE_REDIRECTING
   virtual void OnRedirectStateChange(nsIChannel* aOldChannel,
                                      nsIChannel* aNewChannel,
                                      uint32_t aRedirectFlags,
                                      uint32_t aStateFlags) override;
 
-  nsresult SetIsActiveInternal(bool aIsActive, bool aIsHidden);
-
   /**
    * Helper function that determines if channel is an HTTP POST.
    *
    * @param aChannel
    *        The channel to test
    *
    * @return True iff channel is an HTTP post.
    */
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -641,22 +641,16 @@ interface nsIDocShell : nsIDocShellTreeI
   /**
    * Sets whether a docshell is active. An active docshell is one that is
    * visible, and thus is not a good candidate for certain optimizations
    * like image frame discarding. Docshells are active unless told otherwise.
    */
   attribute boolean isActive;
 
   /**
-   * Sets whether a docshell is active, as above, but ensuring it does
-   * not discard its layers
-   */
-  void setIsActiveAndForeground(in boolean aIsActive);
-
-  /**
    * Puts the docshell in prerendering mode. noscript because we want only
    * native code to be able to put a docshell in prerendering.
    */
   [noscript] void SetIsPrerendered();
 
   /**
    * Whether this docshell is in prerender mode.
    */
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -1016,18 +1016,16 @@ nsFrameLoader::SwapWithOtherRemoteLoader
   }
 
   rv = ourFrameFrame->BeginSwapDocShells(otherFrame);
   if (NS_FAILED(rv)) {
     mInSwap = aOther->mInSwap = false;
     return rv;
   }
 
-  mRemoteBrowser->SwapLayerTreeObservers(aOther->mRemoteBrowser);
-
   nsCOMPtr<nsIBrowserDOMWindow> otherBrowserDOMWindow =
     aOther->mRemoteBrowser->GetBrowserDOMWindow();
   nsCOMPtr<nsIBrowserDOMWindow> browserDOMWindow =
     mRemoteBrowser->GetBrowserDOMWindow();
 
   if (!!otherBrowserDOMWindow != !!browserDOMWindow) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
@@ -3249,56 +3247,16 @@ nsFrameLoader::RequestNotifyAfterRemoteP
   if (mRemoteBrowser) {
     Unused << mRemoteBrowser->SendRequestNotifyAfterRemotePaint();
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsFrameLoader::RequestNotifyLayerTreeReady()
-{
-  if (mRemoteBrowser) {
-    return mRemoteBrowser->RequestNotifyLayerTreeReady() ? NS_OK : NS_ERROR_NOT_AVAILABLE;
-  }
-
-  if (!mOwnerContent) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  RefPtr<AsyncEventDispatcher> event =
-    new AsyncEventDispatcher(mOwnerContent,
-                             NS_LITERAL_STRING("MozLayerTreeReady"),
-                             true, false);
-  event->PostDOMEvent();
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsFrameLoader::RequestNotifyLayerTreeCleared()
-{
-  if (mRemoteBrowser) {
-    return mRemoteBrowser->RequestNotifyLayerTreeCleared() ? NS_OK : NS_ERROR_NOT_AVAILABLE;
-  }
-
-  if (!mOwnerContent) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  RefPtr<AsyncEventDispatcher> event =
-    new AsyncEventDispatcher(mOwnerContent,
-                             NS_LITERAL_STRING("MozLayerTreeCleared"),
-                             true, false);
-  event->PostDOMEvent();
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 nsFrameLoader::Print(uint64_t aOuterWindowID,
                      nsIPrintSettings* aPrintSettings,
                      nsIWebProgressListener* aProgressListener)
 {
 #if defined(NS_PRINTING)
   if (mRemoteBrowser) {
     RefPtr<embedding::PrintingParent> printingParent =
       mRemoteBrowser->Manager()->AsContentParent()->GetPrintingParent();
--- a/dom/base/nsIFrameLoader.idl
+++ b/dom/base/nsIFrameLoader.idl
@@ -134,24 +134,16 @@ interface nsIFrameLoader : nsISupports
   /**
    * Request that the next time a remote layer transaction has been
    * received by the Compositor, a MozAfterRemoteFrame event be sent
    * to the window.
    */
   void requestNotifyAfterRemotePaint();
 
   /**
-   * Request an event when the layer tree from the remote tab becomes
-   * available or unavailable. When this happens, a mozLayerTreeReady
-   * or mozLayerTreeCleared event is fired.
-   */
-  void requestNotifyLayerTreeReady();
-  void requestNotifyLayerTreeCleared();
-
-  /**
    * Print the current document.
    *
    * @param aOuterWindowID the ID of the outer window to print
    * @param aPrintSettings optional print settings to use; printSilent can be
    *                       set to prevent prompting.
    * @param aProgressListener optional print progress listener.
    */
   void print(in unsigned long long aOuterWindowID,
--- a/dom/interfaces/base/nsITabParent.idl
+++ b/dom/interfaces/base/nsITabParent.idl
@@ -20,23 +20,21 @@ interface nsITabParent : nsISupports
   /**
    * Whether this tabParent is in prerender mode.
    */
   [infallible] readonly attribute boolean isPrerendered;
 
   /**
     * As an optimisation, setting the docshell's active state to
     * inactive also triggers a layer invalidation to free up some
-    * potentially unhelpful memory usage. This attribute should be
-    * used where callers would like to set the docshell's state
-    * without losing any layer data.
-    *
-    * Otherwise, this does the same as setting the attribute above.
+    * potentially unhelpful memory usage. Calling preserveLayers
+    * will cause the layers to be preserved even for inactive
+    * docshells.
     */
-  void setDocShellIsActiveAndForeground(in boolean aIsActive);
+  void preserveLayers(in boolean aPreserveLayers);
 
   /**
    * During interactions where painting performance
    * is more important than scrolling, we may temporarily
    * suppress the displayport. Each enable called must be matched
    * with a disable call.
    */
   void suppressDisplayport(in bool aEnabled);
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -5399,8 +5399,17 @@ ContentParent::SendGetFilesResponseAndFo
                                              const GetFilesResponseResult& aResult)
 {
   GetFilesHelper* helper = mGetFilesPendingRequests.GetWeak(aUUID);
   if (helper) {
     mGetFilesPendingRequests.Remove(aUUID);
     Unused << SendGetFilesResponse(aUUID, aResult);
   }
 }
+
+void
+ContentParent::ForceTabPaint(TabParent* aTabParent, uint64_t aLayerObserverEpoch)
+{
+  if (!mHangMonitorActor) {
+    return;
+  }
+  ProcessHangMonitor::ForcePaint(mHangMonitorActor, aTabParent, aLayerObserverEpoch);
+}
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -558,16 +558,19 @@ public:
                                            PBlobParent* aBlobParent,
                                            const Principal& aPrincipal) override;
 
   virtual bool
   RecvUnstoreAndBroadcastBlobURLUnregistration(const nsCString& aURI) override;
 
   virtual int32_t Pid() const override;
 
+  // Use the PHangMonitor channel to ask the child to repaint a tab.
+  void ForceTabPaint(TabParent* aTabParent, uint64_t aLayerObserverEpoch);
+
 protected:
   void OnChannelConnected(int32_t pid) override;
 
   virtual void ActorDestroy(ActorDestroyReason why) override;
 
   bool ShouldContinueFromReplyTimeout() override;
 
   void OnVarChanged(const GfxVarUpdate& aVar) override;
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -547,16 +547,21 @@ parent:
      * compositing.  This is sent when all pending changes have been
      * sent to the compositor and are ready to be shown on the next composite.
      * @see PCompositor
      * @see RequestNotifyAfterRemotePaint
      */
     async RemotePaintIsReady();
 
     /**
+     * Child informs the parent that the layer tree is already available.
+     */
+    async ForcePaintNoOp(uint64_t aLayerObserverEpoch);
+
+    /**
      * Sent by the child to the parent to inform it that an update to the
      * dimensions has been requested, likely through win.moveTo or resizeTo
      */
     async SetDimensions(uint32_t aFlags, int32_t aX, int32_t aY, int32_t aCx, int32_t aCy);
 
     prio(high) sync DispatchWheelEvent(WidgetWheelEvent event);
     prio(high) sync DispatchMouseEvent(WidgetMouseEvent event);
     prio(high) sync DispatchKeyboardEvent(WidgetKeyboardEvent event);
@@ -719,18 +724,26 @@ child:
     /**
      * Tell the child side if it has to update it's touchable region
      * to the parent.
      */
     async SetUpdateHitRegion(bool aEnabled);
 
     /**
      * Update the child side docShell active (resource use) state.
+     *
+     * @param aIsActive
+     *        Whether to activate or deactivate the docshell.
+     * @param aPreserveLayers
+     *        Whether layer trees should be preserved for inactive docshells.
+     * @param aLayerObserverEpoch
+     *        The layer observer epoch for this activation. This message should be
+     *        ignored if this epoch has already been observed (via ForcePaint).
      */
-    async SetDocShellIsActive(bool aIsActive, bool aIsHidden);
+    async SetDocShellIsActive(bool aIsActive, bool aPreserveLayers, uint64_t aLayerObserverEpoch);
 
     /**
      * Notify the child that it shouldn't paint the offscreen displayport.
      * This is useful to speed up interactive operations over async
      * scrolling performance like resize, tabswitch, pageload.
      *
      * Each enable call must be matched with a disable call. The child
      * will remain in the suppress mode as long as there's
--- a/dom/ipc/PProcessHangMonitor.ipdl
+++ b/dom/ipc/PProcessHangMonitor.ipdl
@@ -35,11 +35,13 @@ parent:
   async HangEvidence(HangData data);
   async ClearHang();
 
 child:
   async TerminateScript();
 
   async BeginStartingDebugger();
   async EndStartingDebugger();
+
+  async ForcePaint(TabId tabId, uint64_t aLayerObserverEpoch);
 };
 
 } // namespace mozilla
--- a/dom/ipc/ProcessHangMonitor.cpp
+++ b/dom/ipc/ProcessHangMonitor.cpp
@@ -2,19 +2,23 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/ProcessHangMonitor.h"
 #include "mozilla/ProcessHangMonitorIPC.h"
 
+#include "jsapi.h"
+#include "js/GCAPI.h"
+
 #include "mozilla/Atomics.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/Element.h"
+#include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/plugins/PluginBridge.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Unused.h"
 
 #include "nsIFrameLoader.h"
@@ -91,18 +95,21 @@ class HangMonitorChild
 
   void ClearHang();
   void ClearHangAsync();
 
   virtual bool RecvTerminateScript() override;
   virtual bool RecvBeginStartingDebugger() override;
   virtual bool RecvEndStartingDebugger() override;
 
+  virtual bool RecvForcePaint(const TabId& aTabId, const uint64_t& aLayerObserverEpoch) override;
+
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
+  void InterruptCallback();
   void Shutdown();
 
   static HangMonitorChild* Get() { return sInstance; }
 
   MessageLoop* MonitorLoop() { return mHangMonitor->MonitorLoop(); }
 
  private:
   void ShutdownOnThread();
@@ -114,16 +121,20 @@ class HangMonitorChild
 
   // Main thread-only.
   bool mSentReport;
 
   // These fields must be accessed with mMonitor held.
   bool mTerminateScript;
   bool mStartDebugger;
   bool mFinishedStartingDebugger;
+  bool mForcePaint;
+  TabId mForcePaintTab;
+  uint64_t mForcePaintEpoch;
+  JSContext* mContext;
   bool mShutdownDone;
 
   // This field is only accessed on the hang thread.
   bool mIPCOpen;
 };
 
 Atomic<HangMonitorChild*> HangMonitorChild::sInstance;
 
@@ -203,32 +214,37 @@ public:
   virtual bool RecvClearHang() override;
 
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
   void SetProcess(HangMonitoredProcess* aProcess) { mProcess = aProcess; }
 
   void Shutdown();
 
+  void ForcePaint(dom::TabParent* aTabParent, uint64_t aLayerObserverEpoch);
+
   void TerminateScript();
   void BeginStartingDebugger();
   void EndStartingDebugger();
   void CleanupPluginHang(uint32_t aPluginId, bool aRemoveFiles);
 
   /**
    * Update the dump for the specified plugin. This method is thread-safe and
    * is used to replace a browser minidump with a full minidump. If aDumpId is
    * empty this is a no-op.
    */
   void UpdateMinidump(uint32_t aPluginId, const nsString& aDumpId);
 
   MessageLoop* MonitorLoop() { return mHangMonitor->MonitorLoop(); }
 
 private:
   bool TakeBrowserMinidump(const PluginHangData& aPhd, nsString& aCrashId);
+
+  void ForcePaintOnThread(TabId aTabId, uint64_t aLayerObserverEpoch);
+
   void ShutdownOnThread();
 
   const RefPtr<ProcessHangMonitor> mHangMonitor;
 
   // This field is read-only after construction.
   bool mReportHangs;
 
   // This field is only accessed on the hang thread.
@@ -250,30 +266,59 @@ private:
 
 HangMonitorChild::HangMonitorChild(ProcessHangMonitor* aMonitor)
  : mHangMonitor(aMonitor),
    mMonitor("HangMonitorChild lock"),
    mSentReport(false),
    mTerminateScript(false),
    mStartDebugger(false),
    mFinishedStartingDebugger(false),
+   mForcePaint(false),
    mShutdownDone(false),
    mIPCOpen(true)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
+  mContext = danger::GetJSContext();
 }
 
 HangMonitorChild::~HangMonitorChild()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(sInstance == this);
   sInstance = nullptr;
 }
 
 void
+HangMonitorChild::InterruptCallback()
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+  bool forcePaint;
+  TabId forcePaintTab;
+  uint64_t forcePaintEpoch;
+
+  {
+    MonitorAutoLock lock(mMonitor);
+    forcePaint = mForcePaint;
+    forcePaintTab = mForcePaintTab;
+    forcePaintEpoch = mForcePaintEpoch;
+
+    mForcePaint = false;
+  }
+
+  if (forcePaint) {
+    RefPtr<TabChild> tabChild = TabChild::FindTabChild(forcePaintTab);
+    if (tabChild) {
+      JS::AutoAssertOnGC aaogc(mContext);
+      tabChild->ForcePaint(forcePaintEpoch);
+    }
+  }
+}
+
+void
 HangMonitorChild::Shutdown()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   MonitorAutoLock lock(mMonitor);
   while (!mShutdownDone) {
     mMonitor.Wait();
   }
@@ -326,16 +371,33 @@ HangMonitorChild::RecvEndStartingDebugge
 {
   MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
 
   MonitorAutoLock lock(mMonitor);
   mFinishedStartingDebugger = true;
   return true;
 }
 
+bool
+HangMonitorChild::RecvForcePaint(const TabId& aTabId, const uint64_t& aLayerObserverEpoch)
+{
+  MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
+
+  {
+    MonitorAutoLock lock(mMonitor);
+    mForcePaint = true;
+    mForcePaintTab = aTabId;
+    mForcePaintEpoch = aLayerObserverEpoch;
+  }
+
+  JS_RequestInterruptCallback(mContext);
+
+  return true;
+}
+
 void
 HangMonitorChild::Open(Transport* aTransport, ProcessId aPid,
                        MessageLoop* aIOLoop)
 {
   MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
 
   MOZ_ASSERT(!sInstance);
   sInstance = this;
@@ -520,16 +582,35 @@ HangMonitorParent::ShutdownOnThread()
   }
 
   MonitorAutoLock lock(mMonitor);
   mShutdownDone = true;
   mMonitor.Notify();
 }
 
 void
+HangMonitorParent::ForcePaint(dom::TabParent* aTab, uint64_t aLayerObserverEpoch)
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+  TabId id = aTab->GetTabId();
+  MonitorLoop()->PostTask(NewNonOwningRunnableMethod<TabId, uint64_t>(
+                            this, &HangMonitorParent::ForcePaintOnThread, id, aLayerObserverEpoch));
+}
+
+void
+HangMonitorParent::ForcePaintOnThread(TabId aTabId, uint64_t aLayerObserverEpoch)
+{
+  MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
+
+  if (mIPCOpen) {
+    Unused << SendForcePaint(aTabId, aLayerObserverEpoch);
+  }
+}
+
+void
 HangMonitorParent::ActorDestroy(ActorDestroyReason aWhy)
 {
   MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
   mIPCOpen = false;
 }
 
 void
 HangMonitorParent::Open(Transport* aTransport, ProcessId aPid,
@@ -954,16 +1035,26 @@ HangMonitoredProcess::UserCanceled()
 
   if (mActor) {
     uint32_t id = mHangData.get_PluginHangData().pluginId();
     mActor->CleanupPluginHang(id, true);
   }
   return NS_OK;
 }
 
+static bool
+InterruptCallback(JSContext* cx)
+{
+  if (HangMonitorChild* child = HangMonitorChild::Get()) {
+    child->InterruptCallback();
+  }
+
+  return true;
+}
+
 ProcessHangMonitor* ProcessHangMonitor::sInstance;
 
 ProcessHangMonitor::ProcessHangMonitor()
  : mCPOWTimeout(false)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   MOZ_COUNT_CTOR(ProcessHangMonitor);
@@ -1089,16 +1180,19 @@ mozilla::CreateHangMonitorParent(Content
 }
 
 PProcessHangMonitorChild*
 mozilla::CreateHangMonitorChild(mozilla::ipc::Transport* aTransport,
                                 base::ProcessId aOtherPid)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
+  JSContext* cx = danger::GetJSContext();
+  JS_AddInterruptCallback(cx, InterruptCallback);
+
   ProcessHangMonitor* monitor = ProcessHangMonitor::GetOrCreate();
   HangMonitorChild* child = new HangMonitorChild(monitor);
 
   monitor->MonitorLoop()->PostTask(NewNonOwningRunnableMethod
                                    <mozilla::ipc::Transport*,
                                     base::ProcessId,
                                     MessageLoop*>(child,
                                                   &HangMonitorChild::Open,
@@ -1137,8 +1231,18 @@ ProcessHangMonitor::RemoveProcess(PProce
 /* static */ void
 ProcessHangMonitor::ClearHang()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (HangMonitorChild* child = HangMonitorChild::Get()) {
     child->ClearHang();
   }
 }
+
+/* static */ void
+ProcessHangMonitor::ForcePaint(PProcessHangMonitorParent* aParent,
+                               dom::TabParent* aTabParent,
+                               uint64_t aLayerObserverEpoch)
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+  auto parent = static_cast<HangMonitorParent*>(aParent);
+  parent->ForcePaint(aTabParent, aLayerObserverEpoch);
+}
--- a/dom/ipc/ProcessHangMonitor.h
+++ b/dom/ipc/ProcessHangMonitor.h
@@ -17,16 +17,17 @@ class MessageLoop;
 namespace base {
 class Thread;
 } // namespace base
 
 namespace mozilla {
 
 namespace dom {
 class ContentParent;
+class TabParent;
 } // namespace dom
 
 class PProcessHangMonitorParent;
 
 class ProcessHangMonitor final
   : public nsIObserver
 {
  private:
@@ -40,16 +41,20 @@ class ProcessHangMonitor final
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   static void AddProcess(dom::ContentParent* aContentParent);
   static void RemoveProcess(PProcessHangMonitorParent* aParent);
 
   static void ClearHang();
 
+  static void ForcePaint(PProcessHangMonitorParent* aParent,
+                         dom::TabParent* aTab,
+                         uint64_t aLayerObserverEpoch);
+
   enum SlowScriptAction {
     Continue,
     Terminate,
     StartDebugger
   };
   SlowScriptAction NotifySlowScript(nsITabChild* aTabChild,
                                     const char* aFileName,
                                     unsigned aLineNo);
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -42,16 +42,17 @@
 #include "mozilla/Move.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/TouchEvents.h"
 #include "mozilla/Unused.h"
 #include "mozIApplication.h"
 #include "nsContentUtils.h"
+#include "nsCSSFrameConstructor.h"
 #include "nsDocShell.h"
 #include "nsEmbedCID.h"
 #include "nsGlobalWindow.h"
 #include <algorithm>
 #ifdef MOZ_CRASHREPORTER
 #include "nsExceptionHandler.h"
 #endif
 #include "nsFilePickerProxy.h"
@@ -79,16 +80,17 @@
 #include "nsIWebBrowserSetup.h"
 #include "nsIWebProgress.h"
 #include "nsIXULRuntime.h"
 #include "nsPIDOMWindow.h"
 #include "nsPIWindowRoot.h"
 #include "nsLayoutUtils.h"
 #include "nsPrintfCString.h"
 #include "nsThreadUtils.h"
+#include "nsViewManager.h"
 #include "nsWeakReference.h"
 #include "nsWindowWatcher.h"
 #include "PermissionMessageUtils.h"
 #include "PuppetWidget.h"
 #include "StructuredCloneData.h"
 #include "nsViewportInfo.h"
 #include "nsILoadContext.h"
 #include "ipc/nsGUIEventIPC.h"
@@ -544,16 +546,17 @@ TabChild::TabChild(nsIContentChild* aMan
   , mIPCOpen(true)
   , mParentIsActive(false)
   , mDidSetRealShowInfo(false)
   , mDidLoadURLInit(false)
   , mAPZChild(nullptr)
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
   , mNativeWindowHandle(0)
 #endif
+  , mLayerObserverEpoch(0)
 {
   // In the general case having the TabParent tell us if APZ is enabled or not
   // doesn't really work because the TabParent itself may not have a reference
   // to the owning widget during initialization. Instead we assume that this
   // TabChild corresponds to a widget type that would have APZ enabled, and just
   // check the other conditions necessary for enabling APZ.
   mAsyncPanZoomEnabled = gfxPlatform::AsyncPanZoomEnabled();
 
@@ -2617,29 +2620,95 @@ TabChild::RecvSetUpdateHitRegion(const b
     RefPtr<nsPresContext> presContext = presShell->GetPresContext();
     NS_ENSURE_TRUE(presContext, true);
     presContext->InvalidatePaintedLayers();
 
     return true;
 }
 
 bool
-TabChild::RecvSetDocShellIsActive(const bool& aIsActive, const bool& aIsHidden)
+TabChild::RecvSetDocShellIsActive(const bool& aIsActive,
+                                  const bool& aPreserveLayers,
+                                  const uint64_t& aLayerObserverEpoch)
 {
-    // docshell is consider prerendered only if not active yet
-    mIsPrerendered &= !aIsActive;
-    nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
-    if (docShell) {
-      if (aIsHidden) {
-        docShell->SetIsActive(aIsActive);
+  // Since SetDocShellIsActive requests come in from both the hang monitor
+  // channel and the PContent channel, we have an ordering problem. This code
+  // ensures that we respect the order in which the requests were made and
+  // ignore stale requests.
+  if (mLayerObserverEpoch >= aLayerObserverEpoch) {
+    return true;
+  }
+  mLayerObserverEpoch = aLayerObserverEpoch;
+
+  MOZ_ASSERT(mPuppetWidget);
+  MOZ_ASSERT(mPuppetWidget->GetLayerManager());
+  MOZ_ASSERT(mPuppetWidget->GetLayerManager()->GetBackendType() ==
+             LayersBackend::LAYERS_CLIENT);
+
+  // We send the current layer observer epoch to the compositor so that
+  // TabParent knows whether a layer update notification corresponds to the
+  // latest SetDocShellIsActive request that was made.
+  ClientLayerManager *manager = mPuppetWidget->GetLayerManager()->AsClientLayerManager();
+  manager->SetLayerObserverEpoch(aLayerObserverEpoch);
+
+  // docshell is consider prerendered only if not active yet
+  mIsPrerendered &= !aIsActive;
+  nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
+  if (docShell) {
+    bool wasActive;
+    docShell->GetIsActive(&wasActive);
+    if (aIsActive && wasActive) {
+      // This request is a no-op. In this case, we still want a MozLayerTreeReady
+      // notification to fire in the parent (so that it knows that the child has
+      // updated its epoch). ForcePaintNoOp does that.
+      if (IPCOpen()) {
+        Unused << SendForcePaintNoOp(aLayerObserverEpoch);
+      }
+      return true;
+    }
+
+    docShell->SetIsActive(aIsActive);
+  }
+
+  if (aIsActive) {
+    MakeVisible();
+
+    // We don't use TabChildBase::GetPresShell() here because that would create
+    // a content viewer if one doesn't exist yet. Creating a content viewer can
+    // cause JS to run, which we want to avoid. nsIDocShell::GetPresShell
+    // returns null if no content viewer exists yet.
+    if (nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell()) {
+      if (nsIFrame* root = presShell->FrameConstructor()->GetRootFrame()) {
+        FrameLayerBuilder::InvalidateAllLayersForFrame(
+          nsLayoutUtils::GetDisplayRootFrame(root));
+        root->SchedulePaint();
+      }
+
+      // If we need to repaint, let's do that right away. No sense waiting until
+      // we get back to the event loop again. We suppress the display port so that
+      // we only paint what's visible. This ensures that the tab we're switching
+      // to paints as quickly as possible.
+      APZCCallbackHelper::SuppressDisplayport(true, presShell);
+      if (nsContentUtils::IsSafeToRunScript()) {
+        WebWidget()->PaintNowIfNeeded();
       } else {
-        docShell->SetIsActiveAndForeground(aIsActive);
+        RefPtr<nsViewManager> vm = presShell->GetViewManager();
+        if (nsView* view = vm->GetRootView()) {
+          presShell->Paint(view, view->GetBounds(),
+                           nsIPresShell::PAINT_LAYERS |
+                           nsIPresShell::PAINT_SYNC_DECODE_IMAGES);
+        }
       }
+      APZCCallbackHelper::SuppressDisplayport(false, presShell);
     }
-    return true;
+  } else if (!aPreserveLayers) {
+    MakeHidden();
+  }
+
+  return true;
 }
 
 bool
 TabChild::RecvNavigateByKey(const bool& aForward, const bool& aForDocumentNavigation)
 {
   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
   if (fm) {
     nsCOMPtr<nsIDOMElement> result;
@@ -2852,24 +2921,32 @@ TabChild::NotifyPainted()
         mRemoteFrame->SendNotifyCompositorTransaction();
         mNotified = true;
     }
 }
 
 void
 TabChild::MakeVisible()
 {
+  if (mPuppetWidget && mPuppetWidget->IsVisible()) {
+    return;
+  }
+
   if (mPuppetWidget) {
     mPuppetWidget->Show(true);
   }
 }
 
 void
 TabChild::MakeHidden()
 {
+  if (mPuppetWidget && !mPuppetWidget->IsVisible()) {
+    return;
+  }
+
   CompositorBridgeChild* compositor = CompositorBridgeChild::Get();
 
   // Clear cached resources directly. This avoids one extra IPC
   // round-trip from CompositorBridgeChild to CompositorBridgeParent.
   compositor->RecvClearCachedResources(mLayersId);
 
   if (mPuppetWidget) {
     mPuppetWidget->Show(false);
@@ -3218,16 +3295,23 @@ TabChild::GetInnerSize()
 ScreenIntRect
 TabChild::GetOuterRect()
 {
   LayoutDeviceIntRect outerRect =
     RoundedToInt(mUnscaledOuterRect * mPuppetWidget->GetDefaultScale());
   return ViewAs<ScreenPixel>(outerRect, PixelCastJustification::LayoutDeviceIsScreenForTabDims);
 }
 
+void
+TabChild::ForcePaint(uint64_t aLayerObserverEpoch)
+{
+  nsAutoScriptBlocker scriptBlocker;
+  RecvSetDocShellIsActive(true, false, aLayerObserverEpoch);
+}
+
 TabChildGlobal::TabChildGlobal(TabChildBase* aTabChild)
 : mTabChild(aTabChild)
 {
   SetIsNotDOMBinding();
 }
 
 TabChildGlobal::~TabChildGlobal()
 {
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -649,29 +649,33 @@ public:
   {
       mAPZChild = aAPZChild;
   }
 
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
   uintptr_t GetNativeWindowHandle() const { return mNativeWindowHandle; }
 #endif
 
+  // Request that the docshell be marked as active.
+  void ForcePaint(uint64_t aLayerObserverEpoch);
+
 protected:
   virtual ~TabChild();
 
   virtual PRenderFrameChild* AllocPRenderFrameChild() override;
 
   virtual bool DeallocPRenderFrameChild(PRenderFrameChild* aFrame) override;
 
   virtual bool RecvDestroy() override;
 
   virtual bool RecvSetUpdateHitRegion(const bool& aEnabled) override;
 
   virtual bool RecvSetDocShellIsActive(const bool& aIsActive,
-                                       const bool& aIsHidden) override;
+                                       const bool& aIsHidden,
+                                       const uint64_t& aLayerObserverEpoch) override;
 
   virtual bool RecvNavigateByKey(const bool& aForward,
                                  const bool& aForDocumentNavigation) override;
 
   virtual bool RecvRequestNotifyAfterRemotePaint() override;
 
   virtual bool RecvSuppressDisplayport(const bool& aEnabled) override;
 
@@ -788,15 +792,18 @@ private:
   // dangling pointer.
   layers::APZChild* mAPZChild;
 
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
   // The handle associated with the native window that contains this tab
   uintptr_t mNativeWindowHandle;
 #endif // defined(XP_WIN)
 
+  // The most recently seen layer observer epoch in RecvSetDocShellIsActive.
+  uint64_t mLayerObserverEpoch;
+
   DISALLOW_EVIL_CONSTRUCTORS(TabChild);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_TabChild_h
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -287,23 +287,25 @@ TabParent::TabParent(nsIContentParent* a
   , mAppPackageFileDescriptorSent(false)
   , mSendOfflineStatus(true)
   , mChromeFlags(aChromeFlags)
   , mDragAreaX(0)
   , mDragAreaY(0)
   , mInitedByParent(false)
   , mTabId(aTabId)
   , mCreatingWindow(false)
-  , mNeedLayerTreeReadyNotification(false)
   , mCursor(nsCursor(-1))
   , mTabSetsCursor(false)
   , mHasContentOpener(false)
 #ifdef DEBUG
   , mActiveSupressDisplayportCount(0)
 #endif
+  , mLayerTreeEpoch(0)
+  , mPreserveLayers(false)
+  , mFirstActivate(true)
 {
   MOZ_ASSERT(aManager);
 }
 
 TabParent::~TabParent()
 {
 }
 
@@ -499,23 +501,16 @@ TabParent::DestroyInternal()
   // If this fails, it's most likely due to a content-process crash,
   // and auto-cleanup will kick in.  Otherwise, the child side will
   // destroy itself and send back __delete__().
   Unused << SendDestroy();
 
   if (RenderFrameParent* frame = GetRenderFrame()) {
     RemoveTabParentFromTable(frame->GetLayersId());
     frame->Destroy();
-
-    // Notify our layer tree update observer that we're going away. It's
-    // possible that we race with a notification and there can be an
-    // LayerTreeUpdateRunnable on the main thread's event queue with a pointer
-    // to us. However, our actual destruction won't be until yet another event
-    // *after* that one is processed, so this should be safe.
-    mLayerUpdateObserver->TabParentDestroyed();
   }
 
   // Let all PluginWidgets know we are tearing down. Prevents
   // these objects from sending async events after the child side
   // is shut down.
   const ManagedContainer<PPluginWidgetParent>& kids =
     ManagedPPluginWidgetParent();
   for (auto iter = kids.ConstIter(); !iter.Done(); iter.Next()) {
@@ -2307,22 +2302,20 @@ TabParent::GetTabIdFrom(nsIDocShell *doc
     return static_cast<TabChild*>(tabChild.get())->GetTabId();
   }
   return TabId(0);
 }
 
 RenderFrameParent*
 TabParent::GetRenderFrame()
 {
-  if (!mLayerUpdateObserver) {
-    mLayerUpdateObserver = new LayerTreeUpdateObserver(this);
-  }
-
   PRenderFrameParent* p = LoneManagedOrNullAsserts(ManagedPRenderFrameParent());
-  return static_cast<RenderFrameParent*>(p);
+  RenderFrameParent* frame = static_cast<RenderFrameParent*>(p);
+
+  return frame;
 }
 
 bool
 TabParent::RecvRequestIMEToCommitComposition(const bool& aCancel,
                                              bool* aIsCommitted,
                                              nsString* aCommittedString)
 {
   nsCOMPtr<nsIWidget> widget = GetWidget();
@@ -2663,21 +2656,16 @@ TabParent::SetRenderFrame(PRenderFramePa
   bool success = renderFrame->Init(frameLoader);
   if (!success) {
     return false;
   }
 
   uint64_t layersId = renderFrame->GetLayersId();
   AddTabParentToTable(layersId, this);
 
-  if (mNeedLayerTreeReadyNotification) {
-    RequestNotifyLayerTreeReady();
-    mNeedLayerTreeReadyNotification = false;
-  }
-
   return true;
 }
 
 bool
 TabParent::GetRenderFrameInfo(TextureFactoryIdentifier* aTextureFactoryIdentifier,
                               uint64_t* aLayersId)
 {
   RenderFrameParent* rfp = GetRenderFrame();
@@ -2850,20 +2838,36 @@ TabParent::GetUseAsyncPanZoom(bool* useA
   *useAsyncPanZoom = AsyncPanZoomEnabled();
   return NS_OK;
 }
 
 // defined in nsITabParent
 NS_IMETHODIMP
 TabParent::SetDocShellIsActive(bool isActive)
 {
+  // Increment the epoch so that layer tree updates from previous
+  // SetDocShellIsActive requests are ignored.
+  mLayerTreeEpoch++;
+
   // docshell is consider prerendered only if not active yet
   mIsPrerendered &= !isActive;
   mDocShellIsActive = isActive;
-  Unused << SendSetDocShellIsActive(isActive, true);
+  Unused << SendSetDocShellIsActive(isActive, mPreserveLayers, mLayerTreeEpoch);
+
+  // Ask the child to repaint using the PHangMonitor channel/thread (which may
+  // be less congested).
+  if (isActive) {
+    if (mFirstActivate) {
+      mFirstActivate = false;
+    } else {
+      ContentParent* cp = Manager()->AsContentParent();
+      cp->ForceTabPaint(this, mLayerTreeEpoch);
+    }
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabParent::GetDocShellIsActive(bool* aIsActive)
 {
   *aIsActive = mDocShellIsActive;
   return NS_OK;
@@ -2872,22 +2876,19 @@ TabParent::GetDocShellIsActive(bool* aIs
 NS_IMETHODIMP
 TabParent::GetIsPrerendered(bool* aIsPrerendered)
 {
   *aIsPrerendered = mIsPrerendered;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-TabParent::SetDocShellIsActiveAndForeground(bool isActive)
+TabParent::PreserveLayers(bool aPreserveLayers)
 {
-  // docshell is consider prerendered only if not active yet
-  mIsPrerendered &= !isActive;
-  mDocShellIsActive = isActive;
-  Unused << SendSetDocShellIsActive(isActive, false);
+  mPreserveLayers = aPreserveLayers;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabParent::SuppressDisplayport(bool aEnabled)
 {
   if (IsDestroyed()) {
     return NS_OK;
@@ -2938,121 +2939,83 @@ TabParent::NavigateByKey(bool aForward, 
 {
   Unused << SendNavigateByKey(aForward, aForDocumentNavigation);
   return NS_OK;
 }
 
 class LayerTreeUpdateRunnable final
   : public mozilla::Runnable
 {
-  RefPtr<LayerTreeUpdateObserver> mUpdateObserver;
+  uint64_t mLayersId;
+  uint64_t mEpoch;
   bool mActive;
 
 public:
-  explicit LayerTreeUpdateRunnable(LayerTreeUpdateObserver* aObs, bool aActive)
-    : mUpdateObserver(aObs), mActive(aActive)
+  explicit LayerTreeUpdateRunnable(uint64_t aLayersId, uint64_t aEpoch, bool aActive)
+    : mLayersId(aLayersId), mEpoch(aEpoch), mActive(aActive)
   {
     MOZ_ASSERT(!NS_IsMainThread());
   }
 
 private:
   NS_IMETHOD Run() override {
     MOZ_ASSERT(NS_IsMainThread());
-    if (RefPtr<TabParent> tabParent = mUpdateObserver->GetTabParent()) {
-      tabParent->LayerTreeUpdate(mActive);
+    if (RefPtr<TabParent> tabParent = TabParent::GetTabParentFromLayersId(mLayersId)) {
+      tabParent->LayerTreeUpdate(mEpoch, mActive);
     }
     return NS_OK;
   }
 };
 
-void
-LayerTreeUpdateObserver::ObserveUpdate(uint64_t aLayersId, bool aActive)
+/* static */ void
+TabParent::ObserveLayerUpdate(uint64_t aLayersId, uint64_t aEpoch, bool aActive)
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
   RefPtr<LayerTreeUpdateRunnable> runnable =
-    new LayerTreeUpdateRunnable(this, aActive);
+    new LayerTreeUpdateRunnable(aLayersId, aEpoch, aActive);
   NS_DispatchToMainThread(runnable);
 }
 
-
-bool
-TabParent::RequestNotifyLayerTreeReady()
+void
+TabParent::LayerTreeUpdate(uint64_t aEpoch, bool aActive)
 {
-  RenderFrameParent* frame = GetRenderFrame();
-  if (!frame || !frame->IsInitted()) {
-    mNeedLayerTreeReadyNotification = true;
-  } else {
-    GPUProcessManager::Get()->RequestNotifyLayerTreeReady(
-      frame->GetLayersId(),
-      mLayerUpdateObserver);
+  // Ignore updates from old epochs. They might tell us that layers are
+  // available when we've already sent a message to clear them. We can't trust
+  // the update in that case since layers could disappear anytime after that.
+  if (aEpoch != mLayerTreeEpoch || mIsDestroyed) {
+    return;
   }
-  return true;
-}
-
-bool
-TabParent::RequestNotifyLayerTreeCleared()
-{
-  RenderFrameParent* frame = GetRenderFrame();
-  if (!frame) {
-    return false;
-  }
-
-  GPUProcessManager::Get()->RequestNotifyLayerTreeCleared(
-    frame->GetLayersId(),
-    mLayerUpdateObserver);
-  return true;
-}
-
-bool
-TabParent::LayerTreeUpdate(bool aActive)
-{
+
   nsCOMPtr<mozilla::dom::EventTarget> target = do_QueryInterface(mFrameElement);
   if (!target) {
     NS_WARNING("Could not locate target for layer tree message.");
-    return true;
+    return;
   }
 
   RefPtr<Event> event = NS_NewDOMEvent(mFrameElement, nullptr, nullptr);
   if (aActive) {
     event->InitEvent(NS_LITERAL_STRING("MozLayerTreeReady"), true, false);
   } else {
     event->InitEvent(NS_LITERAL_STRING("MozLayerTreeCleared"), true, false);
   }
   event->SetTrusted(true);
   event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
   bool dummy;
   mFrameElement->DispatchEvent(event, &dummy);
-  return true;
 }
 
-void
-TabParent::SwapLayerTreeObservers(TabParent* aOther)
+bool
+TabParent::RecvForcePaintNoOp(const uint64_t& aLayerObserverEpoch)
 {
-  if (IsDestroyed() || aOther->IsDestroyed()) {
-    return;
-  }
-
-  RenderFrameParent* rfp = GetRenderFrame();
-  RenderFrameParent* otherRfp = aOther->GetRenderFrame();
-  if (!rfp || !otherRfp) {
-    return;
-  }
-
-  // The swap that happens for the observers in GPUProcessManager has to
-  // happen in a lock so that an update being processed on the compositor thread
-  // can't grab the layer update observer for the wrong tab parent.
-  GPUProcessManager::Get()->SwapLayerTreeObservers(
-    rfp->GetLayersId(),
-    otherRfp->GetLayersId());
-
-  // No need for a lock, destruction can only happen on the main thread and we
-  // only read mLayerUpdateObserver::mTabParent on the main thread.
-  Swap(mLayerUpdateObserver, aOther->mLayerUpdateObserver);
-  mLayerUpdateObserver->SwapTabParent(aOther->mLayerUpdateObserver);
+  // We sent a ForcePaint message when layers were already visible. In this
+  // case, we should act as if an update occurred even though we already have
+  // the layers.
+  LayerTreeUpdate(aLayerObserverEpoch, true);
+  return true;
 }
 
 bool
 TabParent::RecvRemotePaintIsReady()
 {
   nsCOMPtr<mozilla::dom::EventTarget> target = do_QueryInterface(mFrameElement);
   if (!target) {
     NS_WARNING("Could not locate target for MozAfterRemotePaint message.");
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -75,49 +75,16 @@ class ClonedMessageData;
 class nsIContentParent;
 class Element;
 class DataTransfer;
 
 namespace ipc {
 class StructuredCloneData;
 } // ipc namespace
 
-// This observer runs on the compositor thread, so we dispatch a runnable to the
-// main thread to actually dispatch the event.
-class LayerTreeUpdateObserver : public layers::CompositorUpdateObserver
-{
-public:
-  explicit LayerTreeUpdateObserver(TabParent* aTabParent)
-    : mTabParent(aTabParent)
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-  }
-
-  virtual void ObserveUpdate(uint64_t aLayersId, bool aActive) override;
-
-  virtual void SwapTabParent(LayerTreeUpdateObserver* aOther) {
-    MOZ_ASSERT(NS_IsMainThread());
-    Swap(mTabParent, aOther->mTabParent);
-  }
-
-  void TabParentDestroyed() {
-    MOZ_ASSERT(NS_IsMainThread());
-    mTabParent = nullptr;
-  }
-
-  TabParent* GetTabParent() {
-    MOZ_ASSERT(NS_IsMainThread());
-    return mTabParent;
-  }
-
-private:
-  // NB: Should never be touched off the main thread!
-  TabParent* mTabParent;
-};
-
 class TabParent final : public PBrowserParent
                       , public nsIDOMEventListener
                       , public nsITabParent
                       , public nsIAuthPromptProvider
                       , public nsISecureBrowserUI
                       , public nsIKeyEventInPluginCallback
                       , public nsSupportsWeakReference
                       , public TabContext
@@ -586,24 +553,18 @@ public:
 
   bool IsInitedByParent() const { return mInitedByParent; }
 
   static TabParent* GetNextTabParent();
 
   bool SendLoadRemoteScript(const nsString& aURL,
                             const bool& aRunInGlobalScope);
 
-  // See nsIFrameLoader requestNotifyLayerTreeReady.
-  bool RequestNotifyLayerTreeReady();
-
-  bool RequestNotifyLayerTreeCleared();
-
-  bool LayerTreeUpdate(bool aActive);
-
-  void SwapLayerTreeObservers(TabParent* aOther);
+  static void ObserveLayerUpdate(uint64_t aLayersId, uint64_t aEpoch, bool aActive);
+  void LayerTreeUpdate(uint64_t aEpoch, bool aActive);
 
   virtual bool
   RecvInvokeDragSession(nsTArray<IPCDataTransfer>&& aTransfers,
                         const uint32_t& aAction,
                         const OptionalShmem& aVisualDnDData,
                         const uint32_t& aWidth, const uint32_t& aHeight,
                         const uint32_t& aStride, const uint8_t& aFormat,
                         const int32_t& aDragAreaX, const int32_t& aDragAreaY) override;
@@ -643,16 +604,18 @@ protected:
   nsCOMPtr<nsIBrowserDOMWindow> mBrowserDOMWindow;
 
   virtual PRenderFrameParent* AllocPRenderFrameParent() override;
 
   virtual bool DeallocPRenderFrameParent(PRenderFrameParent* aFrame) override;
 
   virtual bool RecvRemotePaintIsReady() override;
 
+  virtual bool RecvForcePaintNoOp(const uint64_t& aLayerObserverEpoch) override;
+
   virtual bool RecvSetDimensions(const uint32_t& aFlags,
                                  const int32_t& aX, const int32_t& aY,
                                  const int32_t& aCx, const int32_t& aCy) override;
 
   virtual bool RecvGetTabCount(uint32_t* aValue) override;
 
   virtual bool RecvAudioChannelActivityNotification(const uint32_t& aAudioChannel,
                                                     const bool& aActive) override;
@@ -760,21 +723,16 @@ private:
   // frame scripts for that tab are loaded before any scripts start to run in
   // the window. We can't load the frame scripts the normal way, using
   // separate IPC messages, since they won't be processed by the child until
   // returning to the event loop, which is too late. Instead, we queue up
   // frame scripts that we intend to load and send them as part of the
   // CreateWindow response. Then TabChild loads them immediately.
   nsTArray<FrameScriptInfo> mDelayedFrameScripts;
 
-  // If the user called RequestNotifyLayerTreeReady and the RenderFrameParent
-  // wasn't ready yet, we set this flag and call RequestNotifyLayerTreeReady
-  // again once the RenderFrameParent arrives.
-  bool mNeedLayerTreeReadyNotification;
-
   // Cached cursor setting from TabChild.  When the cursor is over the tab,
   // it should take this appearance.
   nsCursor mCursor;
   nsCOMPtr<imgIContainer> mCustomCursor;
   uint32_t mCustomCursorHotspotX, mCustomCursorHotspotY;
 
   // True if the cursor changes from the TabChild should change the widget
   // cursor.  This happens whenever the cursor is in the tab's region.
@@ -795,17 +753,27 @@ private:
   // to dispatch events.
   typedef nsDataHashtable<nsUint64HashKey, TabParent*> LayerToTabParentTable;
   static LayerToTabParentTable* sLayerToTabParentTable;
 
   static void AddTabParentToTable(uint64_t aLayersId, TabParent* aTabParent);
 
   static void RemoveTabParentFromTable(uint64_t aLayersId);
 
-  RefPtr<LayerTreeUpdateObserver> mLayerUpdateObserver;
+  uint64_t mLayerTreeEpoch;
+
+  // If this flag is set, then the tab's layers will be preserved even when
+  // the tab's docshell is inactive.
+  bool mPreserveLayers;
+
+  // Normally we call ForceTabPaint when activating a tab. But we don't do this
+  // the first time we activate a tab. The tab is probably busy running the
+  // initial content scripts and we don't want to force painting then; they're
+  // probably quick and there's some cost to forcing painting.
+  bool mFirstActivate;
 
 public:
   static TabParent* GetTabParentFromLayersId(uint64_t aLayersId);
 };
 
 struct MOZ_STACK_CLASS TabParent::AutoUseNewTab final
 {
 public:
--- a/gfx/ipc/GPUProcessManager.cpp
+++ b/gfx/ipc/GPUProcessManager.cpp
@@ -553,34 +553,16 @@ GPUProcessManager::DeallocateLayerTreeId
   if (mGPUChild) {
     mGPUChild->SendDeallocateLayerTreeId(aLayersId);
     return;
   }
   CompositorBridgeParent::DeallocateLayerTreeId(aLayersId);
 }
 
 void
-GPUProcessManager::RequestNotifyLayerTreeReady(uint64_t aLayersId, CompositorUpdateObserver* aObserver)
-{
-  CompositorBridgeParent::RequestNotifyLayerTreeReady(aLayersId, aObserver);
-}
-
-void
-GPUProcessManager::RequestNotifyLayerTreeCleared(uint64_t aLayersId, CompositorUpdateObserver* aObserver)
-{
-  CompositorBridgeParent::RequestNotifyLayerTreeCleared(aLayersId, aObserver);
-}
-
-void
-GPUProcessManager::SwapLayerTreeObservers(uint64_t aLayer, uint64_t aOtherLayer)
-{
-  CompositorBridgeParent::SwapLayerTreeObservers(aLayer, aOtherLayer);
-}
-
-void
 GPUProcessManager::EnsureVsyncIOThread()
 {
   if (mVsyncIOThread) {
     return;
   }
 
   mVsyncIOThread = new VsyncIOThreadHolder();
   MOZ_RELEASE_ASSERT(mVsyncIOThread->Start());
--- a/gfx/ipc/GPUProcessManager.h
+++ b/gfx/ipc/GPUProcessManager.h
@@ -106,20 +106,16 @@ public:
   // Must run on the content main thread.
   uint64_t AllocateLayerTreeId();
 
   // Release compositor-thread resources referred to by |aID|.
   //
   // Must run on the content main thread.
   void DeallocateLayerTreeId(uint64_t aLayersId);
 
-  void RequestNotifyLayerTreeReady(uint64_t aLayersId, CompositorUpdateObserver* aObserver);
-  void RequestNotifyLayerTreeCleared(uint64_t aLayersId, CompositorUpdateObserver* aObserver);
-  void SwapLayerTreeObservers(uint64_t aLayer, uint64_t aOtherLayer);
-
   void OnProcessLaunchComplete(GPUProcessHost* aHost) override;
   void OnProcessUnexpectedShutdown(GPUProcessHost* aHost) override;
 
   // Notify the GPUProcessManager that a top-level PGPU protocol has been
   // terminated. This may be called from any thread.
   void NotifyRemoteActorDestroyed(const uint64_t& aProcessToken);
 
   // Returns access to the PGPU protocol if a GPU process is present.
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -813,16 +813,22 @@ ClientLayerManager::AsyncPanZoomEnabled(
 
 void
 ClientLayerManager::SetNextPaintSyncId(int32_t aSyncId)
 {
   mForwarder->SetPaintSyncId(aSyncId);
 }
 
 void
+ClientLayerManager::SetLayerObserverEpoch(uint64_t aLayerObserverEpoch)
+{
+  mForwarder->SetLayerObserverEpoch(aLayerObserverEpoch);
+}
+
+void
 ClientLayerManager::AddDidCompositeObserver(DidCompositeObserver* aObserver)
 {
   if (!mDidCompositeObservers.Contains(aObserver)) {
     mDidCompositeObservers.AppendElement(aObserver);
   }
 }
 
 void
--- a/gfx/layers/client/ClientLayerManager.h
+++ b/gfx/layers/client/ClientLayerManager.h
@@ -217,16 +217,18 @@ public:
   void SetTransactionIdAllocator(TransactionIdAllocator* aAllocator) { mTransactionIdAllocator = aAllocator; }
 
   float RequestProperty(const nsAString& aProperty) override;
 
   bool AsyncPanZoomEnabled() const override;
 
   void SetNextPaintSyncId(int32_t aSyncId);
 
+  void SetLayerObserverEpoch(uint64_t aLayerObserverEpoch);
+
   class DidCompositeObserver {
   public:
     virtual void DidComposite() = 0;
   };
 
   void AddDidCompositeObserver(DidCompositeObserver* aObserver);
   void RemoveDidCompositeObserver(DidCompositeObserver* aObserver);
 
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -20,16 +20,17 @@
 #include "TreeTraversal.h"              // for ForEachNode
 #ifdef MOZ_WIDGET_GTK
 #include "gfxPlatformGtk.h"             // for gfxPlatform
 #endif
 #include "gfxPrefs.h"                   // for gfxPrefs
 #include "mozilla/AutoRestore.h"        // for AutoRestore
 #include "mozilla/ClearOnShutdown.h"    // for ClearOnShutdown
 #include "mozilla/DebugOnly.h"          // for DebugOnly
+#include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/gfx/2D.h"          // for DrawTarget
 #include "mozilla/gfx/Point.h"          // for IntSize
 #include "mozilla/gfx/Rect.h"          // for IntSize
 #include "VRManager.h"                  // for VRManager
 #include "mozilla/ipc/Transport.h"      // for Transport
 #include "mozilla/layers/APZCTreeManager.h"  // for APZCTreeManager
 #include "mozilla/layers/APZCTreeManagerParent.h"  // for APZCTreeManagerParent
@@ -1936,29 +1937,16 @@ CompositorBridgeParent::DeallocateLayerT
   // Checking the elements of sIndirectLayerTrees exist or not before using.
   if (!CompositorLoop()) {
     gfxCriticalError() << "Attempting to post to a invalid Compositor Loop";
     return;
   }
   CompositorLoop()->PostTask(NewRunnableFunction(&EraseLayerState, aId));
 }
 
-/* static */ void
-CompositorBridgeParent::SwapLayerTreeObservers(uint64_t aLayerId, uint64_t aOtherLayerId)
-{
-  EnsureLayerTreeMapReady();
-  MonitorAutoLock lock(*sIndirectLayerTreesLock);
-  NS_ASSERTION(sIndirectLayerTrees.find(aLayerId) != sIndirectLayerTrees.end(),
-    "SwapLayerTrees missing layer 1");
-  NS_ASSERTION(sIndirectLayerTrees.find(aOtherLayerId) != sIndirectLayerTrees.end(),
-    "SwapLayerTrees missing layer 2");
-  std::swap(sIndirectLayerTrees[aLayerId].mLayerTreeReadyObserver,
-    sIndirectLayerTrees[aOtherLayerId].mLayerTreeReadyObserver);
-}
-
 static void
 UpdateControllerForLayersId(uint64_t aLayersId,
                             GeckoContentController* aController)
 {
   // Adopt ref given to us by SetControllerForLayerTree()
   MonitorAutoLock lock(*sIndirectLayerTreesLock);
   sIndirectLayerTrees[aLayersId].mController =
     already_AddRefed<GeckoContentController>(aController);
@@ -2025,32 +2013,16 @@ CompositorBridgeParent::PostInsertVsyncP
 {
   // Called in the vsync thread
   if (profiler_is_active() && CompositorThreadHolder::IsActive()) {
     CompositorLoop()->PostTask(
       NewRunnableFunction(InsertVsyncProfilerMarker, aVsyncTimestamp));
   }
 }
 
-/* static */ void
-CompositorBridgeParent::RequestNotifyLayerTreeReady(uint64_t aLayersId, CompositorUpdateObserver* aObserver)
-{
-  EnsureLayerTreeMapReady();
-  MonitorAutoLock lock(*sIndirectLayerTreesLock);
-  sIndirectLayerTrees[aLayersId].mLayerTreeReadyObserver = aObserver;
-}
-
-/* static */ void
-CompositorBridgeParent::RequestNotifyLayerTreeCleared(uint64_t aLayersId, CompositorUpdateObserver* aObserver)
-{
-  EnsureLayerTreeMapReady();
-  MonitorAutoLock lock(*sIndirectLayerTreesLock);
-  sIndirectLayerTrees[aLayersId].mLayerTreeClearedObserver = aObserver;
-}
-
 widget::PCompositorWidgetParent*
 CompositorBridgeParent::AllocPCompositorWidgetParent(const CompositorWidgetInitData& aInitData)
 {
 #if defined(MOZ_WIDGET_SUPPORTS_OOP_COMPOSITING)
   if (mWidget) {
     // Should not create two widgets on the same compositor.
     return nullptr;
   }
@@ -2693,20 +2665,18 @@ CrossProcessCompositorBridgeParent::Shad
       aPaintSequenceNumber, aIsRepeatTransaction, aHitTestUpdate);
 
   // Send the 'remote paint ready' message to the content thread if it has already asked.
   if(mNotifyAfterRemotePaint)  {
     Unused << SendRemotePaintIsReady();
     mNotifyAfterRemotePaint = false;
   }
 
-  if (state->mLayerTreeReadyObserver) {
-    RefPtr<CompositorUpdateObserver> observer = state->mLayerTreeReadyObserver;
-    state->mLayerTreeReadyObserver = nullptr;
-    observer->ObserveUpdate(id, true);
+  if (aLayerTree->ShouldParentObserveEpoch()) {
+    dom::TabParent::ObserveLayerUpdate(id, aLayerTree->GetChildEpoch(), true);
   }
 
   aLayerTree->SetPendingTransactionId(aTransactionId);
 }
 
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
 //#define PLUGINS_LOG(...) printf_stderr("CP [%s]: ", __FUNCTION__);
 //                         printf_stderr(__VA_ARGS__);
@@ -2925,25 +2895,17 @@ CrossProcessCompositorBridgeParent::Forc
 }
 
 void
 CrossProcessCompositorBridgeParent::NotifyClearCachedResources(LayerTransactionParent* aLayerTree)
 {
   uint64_t id = aLayerTree->GetId();
   MOZ_ASSERT(id != 0);
 
-  RefPtr<CompositorUpdateObserver> observer;
-  { // scope lock
-    MonitorAutoLock lock(*sIndirectLayerTreesLock);
-    observer = sIndirectLayerTrees[id].mLayerTreeClearedObserver;
-    sIndirectLayerTrees[id].mLayerTreeClearedObserver = nullptr;
-  }
-  if (observer) {
-    observer->ObserveUpdate(id, false);
-  }
+  dom::TabParent::ObserveLayerUpdate(id, aLayerTree->GetChildEpoch(), false);
 }
 
 bool
 CrossProcessCompositorBridgeParent::SetTestSampleTime(
   LayerTransactionParent* aLayerTree, const TimeStamp& aTime)
 {
   uint64_t id = aLayerTree->GetId();
   MOZ_ASSERT(id != 0);
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -186,27 +186,16 @@ private:
 #if ANDROID_VERSION >= 19
   bool mDisplayEnabled;
   mozilla::Monitor mSetDisplayMonitor;
   RefPtr<CancelableRunnable> mSetDisplayTask;
 #endif
 #endif
 };
 
-class CompositorUpdateObserver
-{
-public:
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositorUpdateObserver);
-
-  virtual void ObserveUpdate(uint64_t aLayersId, bool aActive) = 0;
-
-protected:
-  virtual ~CompositorUpdateObserver() {}
-};
-
 class CompositorBridgeParentBase : public PCompositorBridgeParent,
                                    public HostIPCAllocator,
                                    public ShmemAllocator
 {
 public:
   virtual void ShadowLayersUpdated(LayerTransactionParent* aLayerTree,
                                    const uint64_t& aTransactionId,
                                    const TargetConfig& aTargetConfig,
@@ -459,18 +448,16 @@ public:
     // their FrameMetrics with the corresponding child process that holds
     // the PCompositorBridgeChild
     CrossProcessCompositorBridgeParent* mCrossProcessParent;
     TargetConfig mTargetConfig;
     APZTestData mApzTestData;
     LayerTransactionParent* mLayerTree;
     nsTArray<PluginWindowData> mPluginData;
     bool mUpdatedPluginDataAvailable;
-    RefPtr<CompositorUpdateObserver> mLayerTreeReadyObserver;
-    RefPtr<CompositorUpdateObserver> mLayerTreeClearedObserver;
 
     // Number of times the compositor has been reset without having been
     // acknowledged by the child.
     uint32_t mPendingCompositorUpdates;
 
     PCompositorBridgeParent* CrossProcessPCompositorBridge() const;
   };
 
@@ -544,20 +531,16 @@ private:
 
   /**
    * Release compositor-thread resources referred to by |aID|.
    *
    * Must run on the content main thread.
    */
   static void DeallocateLayerTreeId(uint64_t aId);
 
-  static void RequestNotifyLayerTreeReady(uint64_t aLayersId, CompositorUpdateObserver* aObserver);
-  static void RequestNotifyLayerTreeCleared(uint64_t aLayersId, CompositorUpdateObserver* aObserver);
-  static void SwapLayerTreeObservers(uint64_t aLayer, uint64_t aOtherLayer);
-
 protected:
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~CompositorBridgeParent();
 
   void DeferredDestroy();
 
   virtual PLayerTransactionParent*
     AllocPLayerTransactionParent(const nsTArray<LayersBackend>& aBackendHints,
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -144,16 +144,18 @@ ShadowChild(const OpRaiseToTopChild& op)
 //--------------------------------------------------
 // LayerTransactionParent
 LayerTransactionParent::LayerTransactionParent(LayerManagerComposite* aManager,
                                                CompositorBridgeParentBase* aBridge,
                                                uint64_t aId)
   : mLayerManager(aManager)
   , mCompositorBridge(aBridge)
   , mId(aId)
+  , mChildEpoch(0)
+  , mParentEpoch(0)
   , mPendingTransaction(0)
   , mPendingCompositorUpdates(0)
   , mDestroyed(false)
   , mIPCOpen(false)
 {
 }
 
 LayerTransactionParent::~LayerTransactionParent()
@@ -709,16 +711,34 @@ LayerTransactionParent::RecvUpdate(Infal
     }
   }
 
   profiler_tracing("Paint", "LayerTransaction", TRACING_INTERVAL_END);
   return true;
 }
 
 bool
+LayerTransactionParent::RecvSetLayerObserverEpoch(const uint64_t& aLayerObserverEpoch)
+{
+  mChildEpoch = aLayerObserverEpoch;
+  return true;
+}
+
+bool
+LayerTransactionParent::ShouldParentObserveEpoch()
+{
+  if (mParentEpoch == mChildEpoch) {
+    return false;
+  }
+
+  mParentEpoch = mChildEpoch;
+  return true;
+}
+
+bool
 LayerTransactionParent::RecvSetTestSampleTime(const TimeStamp& aTime)
 {
   return mCompositorBridge->SetTestSampleTime(this, aTime);
 }
 
 bool
 LayerTransactionParent::RecvLeaveTestMode()
 {
--- a/gfx/layers/ipc/LayerTransactionParent.h
+++ b/gfx/layers/ipc/LayerTransactionParent.h
@@ -55,16 +55,19 @@ protected:
 public:
   void Destroy();
 
   LayerManagerComposite* layer_manager() const { return mLayerManager; }
 
   uint64_t GetId() const { return mId; }
   Layer* GetRoot() const { return mRoot; }
 
+  uint64_t GetChildEpoch() const { return mChildEpoch; }
+  bool ShouldParentObserveEpoch();
+
   virtual ShmemAllocator* AsShmemAllocator() override { return this; }
 
   virtual bool AllocShmem(size_t aSize,
                           ipc::SharedMemory::SharedMemoryType aType,
                           ipc::Shmem* aShmem) override;
 
   virtual bool AllocUnsafeShmem(size_t aSize,
                                 ipc::SharedMemory::SharedMemoryType aType,
@@ -134,16 +137,18 @@ protected:
                                 PluginsArray&& aPlugins,
                                 const bool& isFirstPaint,
                                 const bool& scheduleComposite,
                                 const uint32_t& paintSequenceNumber,
                                 const bool& isRepeatTransaction,
                                 const mozilla::TimeStamp& aTransactionStart,
                                 const int32_t& aPaintSyncId) override;
 
+  virtual bool RecvSetLayerObserverEpoch(const uint64_t& aLayerObserverEpoch) override;
+
   virtual bool RecvClearCachedResources() override;
   virtual bool RecvForceComposite() override;
   virtual bool RecvSetTestSampleTime(const TimeStamp& aTime) override;
   virtual bool RecvLeaveTestMode() override;
   virtual bool RecvGetAnimationOpacity(PLayerParent* aParent,
                                        float* aOpacity,
                                        bool* aHasAnimationOpacity) override;
   virtual bool RecvGetAnimationTransform(PLayerParent* aParent,
@@ -192,16 +197,23 @@ private:
   // containers in the "real" layer tree
   RefPtr<Layer> mRoot;
   // When this is nonzero, it refers to a layer tree owned by the
   // compositor thread.  It is always true that
   //   mId != 0 => mRoot == null
   // because the "real tree" is owned by the compositor.
   uint64_t mId;
 
+  // These fields keep track of the latest epoch values in the child and the
+  // parent. mChildEpoch is the latest epoch value received from the child.
+  // mParentEpoch is the latest epoch value that we have told TabParent about
+  // (via ObserveLayerUpdate).
+  uint64_t mChildEpoch;
+  uint64_t mParentEpoch;
+
   uint64_t mPendingTransaction;
 
   // Number of compositor updates we're waiting for the child to
   // acknowledge.
   uint32_t mPendingCompositorUpdates;
 
   // When the widget/frame/browser stuff in this process begins its
   // destruction process, we need to Disconnect() all the currently
--- a/gfx/layers/ipc/PLayerTransaction.ipdl
+++ b/gfx/layers/ipc/PLayerTransaction.ipdl
@@ -65,16 +65,18 @@ parent:
   async UpdateNoSwap(Edit[] cset, OpDestroy[] toDestroy,
                      uint64_t fwdTransactionId,
                      uint64_t id, TargetConfig targetConfig,
                      PluginWindowData[] plugins, bool isFirstPaint,
                      bool scheduleComposite, uint32_t paintSequenceNumber,
                      bool isRepeatTransaction, TimeStamp transactionStart,
                      int32_t paintSyncId);
 
+  async SetLayerObserverEpoch(uint64_t layerObserverEpoch);
+
   // Testing APIs
 
   // Enter test mode, set the sample time to sampleTime, and resample
   // animations. sampleTime must not be null.
   sync SetTestSampleTime(TimeStamp sampleTime);
   // Leave test mode and resume normal compositing
   sync LeaveTestMode();
 
--- a/gfx/layers/ipc/ShadowLayers.cpp
+++ b/gfx/layers/ipc/ShadowLayers.cpp
@@ -767,16 +767,25 @@ ShadowLayerForwarder::EndTransaction(Inf
 
   *aSent = true;
   mIsFirstPaint = false;
   mPaintSyncId = 0;
   MOZ_LAYERS_LOG(("[LayersForwarder] ... done"));
   return true;
 }
 
+void
+ShadowLayerForwarder::SetLayerObserverEpoch(uint64_t aLayerObserverEpoch)
+{
+  if (!HasShadowManager() || !mShadowManager->IPCOpen()) {
+    return;
+  }
+  Unused << mShadowManager->SendSetLayerObserverEpoch(aLayerObserverEpoch);
+}
+
 bool
 ShadowLayerForwarder::AllocUnsafeShmem(size_t aSize,
                                        ipc::SharedMemory::SharedMemoryType aShmType,
                                        ipc::Shmem* aShmem)
 {
   MOZ_ASSERT(HasShadowManager(), "no shadow manager");
   if (!IPCOpen()) {
     return false;
--- a/gfx/layers/ipc/ShadowLayers.h
+++ b/gfx/layers/ipc/ShadowLayers.h
@@ -371,16 +371,18 @@ public:
 
   /**
    * Flag the next paint as the first for a document.
    */
   void SetIsFirstPaint() { mIsFirstPaint = true; }
 
   void SetPaintSyncId(int32_t aSyncId) { mPaintSyncId = aSyncId; }
 
+  void SetLayerObserverEpoch(uint64_t aLayerObserverEpoch);
+
   static void PlatformSyncBeforeUpdate();
 
   virtual bool AllocSurfaceDescriptor(const gfx::IntSize& aSize,
                                       gfxContentType aContent,
                                       SurfaceDescriptor* aBuffer) override;
 
   virtual bool AllocSurfaceDescriptorWithCaps(const gfx::IntSize& aSize,
                                               gfxContentType aContent,
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -357,16 +357,19 @@ ExecuteState::pushInterpreterFrame(JSCon
 #ifdef _MSC_VER
 # pragma optimize("g", off)
 #endif
 bool
 js::RunScript(JSContext* cx, RunState& state)
 {
     JS_CHECK_RECURSION(cx, return false);
 
+    // Since any script can conceivably GC, make sure it's safe to do so.
+    JS::AutoAssertOnGC::VerifyIsSafeToGC(cx->runtime());
+
     if (!Debugger::checkNoExecute(cx, state.script()))
         return false;
 
 #if defined(MOZ_HAVE_RDTSC)
     js::AutoStopwatch stopwatch(cx);
 #endif // defined(MOZ_HAVE_RDTSC)
 
     SPSEntryMarker marker(cx->runtime(), state.script());
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -1245,17 +1245,17 @@ public:
   {
     mObservesMutationsForPrint = aObserve;
   }
   bool ObservesNativeAnonMutationsForPrint()
   {
     return mObservesMutationsForPrint;
   }
 
-  virtual nsresult SetIsActive(bool aIsActive, bool aIsHidden = true) = 0;
+  virtual nsresult SetIsActive(bool aIsActive) = 0;
 
   bool IsActive()
   {
     return mIsActive;
   }
 
   // mouse capturing
   static CapturingContentInfo gCaptureInfo;
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -756,17 +756,16 @@ PresShell::PresShell()
   mReflowCountMgr->SetPresContext(mPresContext);
   mReflowCountMgr->SetPresShell(this);
 #endif
   mLoadBegin = TimeStamp::Now();
 
   mSelectionFlags = nsISelectionDisplay::DISPLAY_TEXT | nsISelectionDisplay::DISPLAY_IMAGES;
   mIsThemeSupportDisabled = false;
   mIsActive = true;
-  mIsHidden = false;
   // FIXME/bug 735029: find a better solution to this problem
   mIsFirstPaint = true;
   mPresShellId = sNextPresShellId++;
   mFrozen = false;
   mRenderFlags = 0;
 
   mScrollPositionClampingScrollPortSizeSet = false;
 
@@ -10887,26 +10886,22 @@ SetPluginIsActive(nsISupports* aSupports
   nsIFrame *frame = content->GetPrimaryFrame();
   nsIObjectFrame *objectFrame = do_QueryFrame(frame);
   if (objectFrame) {
     objectFrame->SetIsDocumentActive(*static_cast<bool*>(aClosure));
   }
 }
 
 nsresult
-PresShell::SetIsActive(bool aIsActive, bool aIsHidden)
+PresShell::SetIsActive(bool aIsActive)
 {
   NS_PRECONDITION(mDocument, "should only be called with a document");
 
   mIsActive = aIsActive;
 
-  // Keep track of whether we've called TabChild::MakeHidden() or not.
-  // This can still be true even if aIsHidden is false.
-  mIsHidden |= aIsHidden;
-
   nsPresContext* presContext = GetPresContext();
   if (presContext &&
       presContext->RefreshDriver()->PresContext() == presContext) {
     presContext->RefreshDriver()->SetThrottled(!mIsActive);
   }
 
   // Propagate state-change to my resource documents' PresShells
   mDocument->EnumerateExternalResources(SetExternalResourceIsActive,
@@ -10917,55 +10912,16 @@ PresShell::SetIsActive(bool aIsActive, b
 #ifdef ACCESSIBILITY
   if (aIsActive) {
     nsAccessibilityService* accService = AccService();
     if (accService) {
       accService->PresShellActivated(this);
     }
   }
 #endif
-
-  // We have this odd special case here because remote content behaves
-  // differently from same-process content when "hidden".  In
-  // desktop-type "browser UIs", hidden "tabs" have documents that are
-  // part of the chrome tree.  When the tabs are hidden, their content
-  // is no longer part of the visible document tree, and the layers
-  // for the content are naturally released.
-  //
-  // Remote content is its own top-level tree in its subprocess.  When
-  // it's "hidden", there's no transaction in which the document
-  // thinks it's not visible, so layers can be retained forever.  This
-  // is problematic when those layers uselessly hold on to precious
-  // resources like directly texturable memory.
-  //
-  // PresShell::SetIsActive() is the first C++ entry point at which we
-  // (i) know that our parent process wants our content to be hidden;
-  // and (ii) has easy access to the TabChild.  So we use this
-  // notification to signal the TabChild to drop its layer tree and
-  // stop trying to repaint.
-  if (mIsHidden) {
-    if (TabChild* tab = TabChild::GetFrom(this)) {
-      if (aIsActive) {
-        tab->MakeVisible();
-        // The only time we should set this to false is when
-        // TabChild::MakeVisible() is called.
-        mIsHidden = false;
-
-        if (!mIsZombie) {
-          if (nsIFrame* root = mFrameConstructor->GetRootFrame()) {
-            FrameLayerBuilder::InvalidateAllLayersForFrame(
-              nsLayoutUtils::GetDisplayRootFrame(root));
-            root->SchedulePaint();
-          }
-        }
-      } else {
-        tab->MakeHidden();
-      }
-    }
-  }
   return rv;
 }
 
 /*
  * Determines the current image locking state. Called when one of the
  * dependent factors changes.
  */
 nsresult
--- a/layout/base/nsPresShell.h
+++ b/layout/base/nsPresShell.h
@@ -364,17 +364,17 @@ public:
 
   virtual void AddPrintPreviewBackgroundItem(nsDisplayListBuilder& aBuilder,
                                              nsDisplayList& aList,
                                              nsIFrame* aFrame,
                                              const nsRect& aBounds) override;
 
   virtual nscolor ComputeBackstopColor(nsView* aDisplayRoot) override;
 
-  virtual nsresult SetIsActive(bool aIsActive, bool aIsHidden = true) override;
+  virtual nsresult SetIsActive(bool aIsActive) override;
 
   virtual bool GetIsViewportOverridden() override {
     return (mMobileViewportManager != nullptr);
   }
 
   virtual bool IsLayoutFlushObserver() override
   {
     return GetPresContext()->RefreshDriver()->
@@ -527,17 +527,16 @@ protected:
 
 
   void SetRenderingState(const RenderingState& aState);
 
   friend class nsPresShellEventCB;
 
   bool mCaretEnabled;
 
-  bool mIsHidden;
 #ifdef DEBUG
   nsStyleSet* CloneStyleSet(nsStyleSet* aSet);
   bool VerifyIncrementalReflow();
   bool mInVerifyReflow;
   void ShowEventTargetDebug();
 #endif
 
   void RecordStyleSheetChange(mozilla::StyleSheetHandle aStyleSheet);
--- a/toolkit/components/printing/content/printUtils.js
+++ b/toolkit/components/printing/content/printUtils.js
@@ -567,17 +567,21 @@ var PrintUtils = {
         printPreviewTB.updateToolbar();
         ppBrowser.collapsed = false;
         ppBrowser.focus();
         return;
       }
 
       // Set the original window as an active window so any mozPrintCallbacks can
       // run without delayed setTimeouts.
-      this._sourceBrowser.docShellIsActive = true;
+      if (this._listener.activateBrowser) {
+        this._listener.activateBrowser(this._sourceBrowser);
+      } else {
+        this._sourceBrowser.docShellIsActive = true;
+      }
 
       // show the toolbar after we go into print preview mode so
       // that we can initialize the toolbar with total num pages
       const XUL_NS =
         "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
       printPreviewTB = document.createElementNS(XUL_NS, "toolbar");
       printPreviewTB.setAttribute("printpreview", true);
       printPreviewTB.setAttribute("fullscreentoolbar", true);
--- a/toolkit/components/viewsource/content/viewSource.js
+++ b/toolkit/components/viewsource/content/viewSource.js
@@ -752,16 +752,20 @@ var PrintPreviewListener = {
     gBrowser.collapsed = true;
   },
 
   onExit() {
     this._ppBrowser.remove();
     gBrowser.collapsed = false;
     document.getElementById("viewSource-toolbox").hidden = false;
   },
+
+  activateBrowser(browser) {
+    browser.docShellIsActive = true;
+  },
 };
 
 // viewZoomOverlay.js uses this
 function getBrowser() {
   return gBrowser;
 }
 
 this.__defineGetter__("gPageLoader", function () {
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser.xml
@@ -291,22 +291,20 @@
           <![CDATA[
             if (this.docShell)
               return this.docShell.isActive = val;
             return false;
           ]]>
         </setter>
       </property>
 
-      <method name="setDocShellIsActiveAndForeground">
-        <parameter name="isActive"/>
+      <method name="preserveLayers">
+        <parameter name="preserve"/>
         <body>
-          <![CDATA[
-            this.docShell && this.docShell.setIsActiveAndForeground(isActive);
-          ]]>
+          // Only useful for remote browsers.
         </body>
       </method>
 
       <method name="makePrerenderedBrowserActive">
         <body>
         <![CDATA[
           let frameLoader = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
           if (frameLoader) {
@@ -1283,16 +1281,21 @@
               aOtherBrowser._remoteWebProgressManager.swapBrowser(aOtherBrowser);
             }
 
             if (this._remoteFinder)
               this._remoteFinder.swapBrowser(this);
             if (aOtherBrowser._remoteFinder)
               aOtherBrowser._remoteFinder.swapBrowser(aOtherBrowser);
           }
+
+          event = new CustomEvent("EndSwapDocShells", {"detail": aOtherBrowser});
+          this.dispatchEvent(event);
+          event = new CustomEvent("EndSwapDocShells", {"detail": this});
+          aOtherBrowser.dispatchEvent(event);
         ]]>
         </body>
       </method>
 
       <method name="getInPermitUnload">
         <parameter name="aCallback"/>
         <body>
         <![CDATA[
@@ -1328,19 +1331,16 @@
                                          Components.results.NS_ERROR_FAILURE);
             }
 
             owner.frameLoader.print(aOuterWindowID, aPrintSettings,
                                     aPrintProgressListener);
           ]]>
         </body>
       </method>
-
-      <!-- This will go away if the binding has been removed for some reason. -->
-      <field name="_alive">true</field>
     </implementation>
 
     <handlers>
       <handler event="keypress" keycode="VK_F7" group="system">
         <![CDATA[
           if (event.defaultPrevented || !event.isTrusted)
             return;
 
--- a/toolkit/content/widgets/remote-browser.xml
+++ b/toolkit/content/widgets/remote-browser.xml
@@ -254,23 +254,23 @@
           <![CDATA[
             let {frameLoader} = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
             frameLoader.tabParent.docShellIsActive = val;
             return val;
           ]]>
         </setter>
       </property>
 
-      <method name="setDocShellIsActiveAndForeground">
-        <parameter name="isActive"/>
+      <method name="preserveLayers">
+        <parameter name="preserve"/>
         <body><![CDATA[
-          // See the explanation for what this does in nsITabParent.ipdl
-
           let {frameLoader} = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
-          frameLoader.tabParent.setDocShellIsActiveAndForeground(isActive);
+          if (frameLoader.tabParent) {
+            frameLoader.tabParent.preserveLayers(preserve);
+          }
         ]]></body>
       </method>
 
       <field name="_manifestURI"/>
       <property name="manifestURI"
                 onget="return this._manifestURI"
                 readonly="true"/>
 
--- a/widget/PuppetWidget.cpp
+++ b/widget/PuppetWidget.cpp
@@ -1100,16 +1100,24 @@ NS_IMETHODIMP
 PuppetWidget::PaintTask::Run()
 {
   if (mWidget) {
     mWidget->Paint();
   }
   return NS_OK;
 }
 
+void
+PuppetWidget::PaintNowIfNeeded()
+{
+  if (IsVisible() && mPaintTask.IsPending()) {
+    Paint();
+  }
+}
+
 NS_IMPL_ISUPPORTS(PuppetWidget::MemoryPressureObserver, nsIObserver)
 
 NS_IMETHODIMP
 PuppetWidget::MemoryPressureObserver::Observe(nsISupports* aSubject,
                                               const char* aTopic,
                                               const char16_t* aData)
 {
   if (!mWidget) {
--- a/widget/PuppetWidget.h
+++ b/widget/PuppetWidget.h
@@ -189,16 +189,19 @@ public:
   // Contacts the parent process which gets the DPI from the
   // proper widget there. TODO: Handle DPI changes that happen
   // later on.
   virtual float GetDPI() override;
   virtual double GetDefaultScaleInternal() override;
 
   virtual bool NeedsPaint() override;
 
+  // Paint the widget immediately if any paints are queued up.
+  void PaintNowIfNeeded();
+
   virtual TabChild* GetOwningTabChild() override { return mTabChild; }
 
   void UpdateBackingScaleCache(float aDpi, double aScale)
   {
     mDPI = aDpi;
     mDefaultScale = aScale;
   }