Bug 1279086 - Allow painting for tab switch when JS is running (r=dvander,mconley,mrbkap)
☠☠ backed out by 083f1c75c0a4 ☠ ☠
authorBill McCloskey <billm@mozilla.com>
Fri, 22 Jul 2016 16:36:45 -0700
changeset 310373 00bb53b58e9690ee1c0faad4b637381865c348e8
parent 310372 cff59fe2b93354a76acb1aced150e00696175ac9
child 310374 a62fbb0065fcdf31b9daf48168ebc92501a9d50a
push id30582
push userkwierso@gmail.com
push dateSat, 20 Aug 2016 01:16:25 +0000
treeherdermozilla-central@f97a056ae623 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdvander, mconley, mrbkap
bugs1279086
milestone51.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
dom/base/nsFrameLoader.cpp
dom/base/nsIFrameLoader.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
toolkit/components/printing/content/printUtils.js
toolkit/components/viewsource/content/viewSource.js
toolkit/content/widgets/browser.xml
widget/PuppetWidget.cpp
widget/PuppetWidget.h
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3295,16 +3295,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
@@ -3350,17 +3351,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
@@ -1629,18 +1629,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);
 
@@ -2799,18 +2798,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;
@@ -3306,16 +3308,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
@@ -3336,45 +3388,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;
           }
 
@@ -3411,21 +3440,16 @@
             unloadTimer: null, // UNLOAD_DELAY nsITimer instance.
 
             // Map from tabs to STATE_* (below).
             tabState: new Map(),
 
             // True if we're in the midst of switching tabs.
             switchInProgress: false,
 
-            // Keep an exact list of content processes (tabParent) in which
-            // we're actively suppressing the display port. This gives a robust
-            // way to make sure we don't forget to un-suppress.
-            activeSuppressDisplayport: new Set(),
-
             // Set of tabs that might be visible right now. We maintain
             // this set because we can't be sure when a tab is actually
             // drawn. A tab is added to this set when we ask to make it
             // visible. All tabs but the most recently shown tab are
             // removed from the set upon MozAfterPaint.
             maybeVisibleTabs: new Set([this.selectedTab]),
 
             STATE_UNLOADED: 0,
@@ -3457,79 +3481,93 @@
             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");
 
               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();
             },
 
             finish: function() {
               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);
 
@@ -3559,17 +3597,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");
@@ -3614,24 +3652,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);
 
@@ -3666,23 +3705,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++;
                 }
               }
 
@@ -3707,16 +3754,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);
                 }
@@ -3740,19 +3791,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;
@@ -3764,29 +3817,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);
                 }
               }
             },
@@ -3799,44 +3853,108 @@
                 // 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)) {
-                fl.tabParent.suppressDisplayport(true);
-                this.activeSuppressDisplayport.add(fl.tabParent);
-              }
-
               this.preActions();
 
               if (this.unloadTimer) {
                 this.clearTimer(this.unloadTimer);
               }
               this.unloadTimer = this.setTimer(() => this.onUnloadTimeout(), this.UNLOAD_DELAY);
 
               this.postActions();
@@ -3858,16 +3976,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
@@ -3941,52 +4065,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) {
@@ -4345,17 +4433,17 @@
               this._handleKeyDownEvent(aEvent);
               break;
             case "keypress":
               this._handleKeyPressEventMac(aEvent);
               break;
             case "sizemodechange":
               if (aEvent.target == window) {
                 this.mCurrentBrowser.setDocShellIsActiveAndForeground(
-                  window.windowState != window.STATE_MINIMIZED);
+                  this.shouldActivateDocShell(this.mCurrentBrowser));
               }
               break;
           }
         ]]></body>
       </method>
 
       <method name="receiveMessage">
         <parameter name="aMessage"/>
--- 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;
   }
@@ -3199,56 +3197,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/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -5496,8 +5496,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
@@ -546,16 +546,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);
@@ -716,17 +721,17 @@ 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.
      */
-    async SetDocShellIsActive(bool aIsActive, bool aIsHidden);
+    async SetDocShellIsActive(bool aIsActive, bool aIsHidden, 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
@@ -78,16 +78,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"
@@ -539,16 +540,17 @@ TabChild::TabChild(nsIContentChild* aMan
   , mDPI(0)
   , mDefaultScale(0)
   , mIsTransparent(false)
   , mIPCOpen(true)
   , mParentIsActive(false)
   , mDidSetRealShowInfo(false)
   , mDidLoadURLInit(false)
   , mAPZChild(nullptr)
+  , 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();
 
@@ -2578,29 +2580,85 @@ 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& aIsHidden, 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);
-      } else {
-        docShell->SetIsActiveAndForeground(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;
+
+  if (aIsActive && WebWidget()->IsVisible()) {
+    // 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;
+  }
+
+  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) {
+    if (aIsHidden) {
+      docShell->SetIsActive(aIsActive);
+    } else {
+      docShell->SetIsActiveAndForeground(aIsActive);
+    }
+  }
+
+  if (aIsActive) {
+    // We don't use 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 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 {
+        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;
 }
 
 bool
 TabChild::RecvNavigateByKey(const bool& aForward, const bool& aForDocumentNavigation)
 {
   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
   if (fm) {
     nsCOMPtr<nsIDOMElement> result;
@@ -2811,24 +2869,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);
@@ -3177,16 +3243,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
@@ -641,29 +641,33 @@ public:
                   const CSSRect& aRect,
                   const uint32_t& aFlags);
 
   void SetAPZChild(layers::APZChild* aAPZChild)
   {
       mAPZChild = aAPZChild;
   }
 
+  // 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;
 
@@ -773,15 +777,18 @@ private:
   bool mDidLoadURLInit;
 
   AutoTArray<bool, NUMBER_OF_AUDIO_CHANNELS> mAudioChannelsActive;
 
   // APZChild clears this pointer from its destructor, so it shouldn't be a
   // dangling pointer.
   layers::APZChild* mAPZChild;
 
+  // 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,23 @@ 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)
 {
   MOZ_ASSERT(aManager);
 }
 
 TabParent::~TabParent()
 {
 }
 
@@ -488,23 +488,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()) {
@@ -2275,22 +2268,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();
@@ -2631,21 +2622,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();
@@ -2880,20 +2866,32 @@ 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, true, mLayerTreeEpoch);
+
+  // Ask the child to repaint using the PHangMonitor channel/thread (which may
+  // be less congested).
+  if (isActive) {
+    ContentParent* cp = Manager()->AsContentParent();
+    cp->ForceTabPaint(this, mLayerTreeEpoch);
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabParent::GetDocShellIsActive(bool* aIsActive)
 {
   *aIsActive = mDocShellIsActive;
   return NS_OK;
@@ -2907,17 +2905,17 @@ TabParent::GetIsPrerendered(bool* aIsPre
 }
 
 NS_IMETHODIMP
 TabParent::SetDocShellIsActiveAndForeground(bool isActive)
 {
   // docshell is consider prerendered only if not active yet
   mIsPrerendered &= !isActive;
   mDocShellIsActive = isActive;
-  Unused << SendSetDocShellIsActive(isActive, false);
+  Unused << SendSetDocShellIsActive(isActive, false, mLayerTreeEpoch);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabParent::SuppressDisplayport(bool aEnabled)
 {
   if (IsDestroyed()) {
     return NS_OK;
@@ -2968,121 +2966,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
@@ -582,24 +549,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;
@@ -639,16 +600,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;
@@ -756,21 +719,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.
@@ -791,17 +749,17 @@ 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;
 
 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
@@ -506,34 +506,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);
-}
-
 bool
 GPUProcessManager::UpdateRemoteContentController(uint64_t aLayersId,
                                                  dom::ContentParent* aContentParent,
                                                  const dom::TabId& aTabId,
                                                  dom::TabParent* aBrowserParent)
 {
   return CompositorBridgeParent::UpdateRemoteContentController(
     aLayersId,
--- a/gfx/ipc/GPUProcessManager.h
+++ b/gfx/ipc/GPUProcessManager.h
@@ -99,20 +99,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);
-
   // Creates a new RemoteContentController for aTabId. Should only be called on
   // the main thread.
   //
   // aLayersId      The layers id for the browser corresponding to aTabId.
   // aContentParent The ContentParent for the process that the TabChild for
   //                aTabId lives in.
   // aBrowserParent The toplevel TabParent for aTabId.
   bool UpdateRemoteContentController(uint64_t aLayersId,
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -831,16 +831,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
@@ -233,16 +233,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
@@ -21,16 +21,17 @@
 #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/APZThreadUtils.h"  // for APZCTreeManager
 #include "mozilla/layers/AsyncCompositionManager.h"
@@ -1814,29 +1815,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);
@@ -1903,32 +1891,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;
   }
@@ -2523,20 +2495,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__);
@@ -2755,25 +2725,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 CompositorBridgeParent final : public PCompositorBridgeParent,
                                      public ShadowLayersManager,
                                      public CompositorBridgeParentIPCAllocator,
                                      public ShmemAllocator
 {
   friend class CompositorVsyncScheduler;
   friend class CompositorThreadHolder;
   friend class InProcessCompositorSession;
@@ -435,18 +424,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;
   };
 
@@ -510,20 +497,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);
-
   /**
    * Creates a new RemoteContentController for aTabId. Should only be called on
    * the main thread.
    *
    * aLayersId The layers id for the browser corresponding to aTabId.
    * aContentParent The ContentParent for the process that the TabChild for
    *                aTabId lives in.
    * aBrowserParent The toplevel TabParent for aTabId.
--- 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,
                                                ShadowLayersManager* aLayersManager,
                                                uint64_t aId)
   : mLayerManager(aManager)
   , mShadowLayersManager(aLayersManager)
   , mId(aId)
+  , mChildEpoch(0)
+  , mParentEpoch(0)
   , mPendingTransaction(0)
   , mPendingCompositorUpdates(0)
   , mDestroyed(false)
   , mIPCOpen(false)
 {
 }
 
 LayerTransactionParent::~LayerTransactionParent()
@@ -707,16 +709,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 mShadowLayersManager->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 RecvGetOpacity(PLayerParent* aParent,
                               float* aOpacity) override;
   virtual bool RecvGetAnimationTransform(PLayerParent* aParent,
                                          MaybeTransform* aTransform)
@@ -191,16 +196,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
@@ -792,16 +792,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
@@ -355,16 +355,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/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
@@ -742,16 +742,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
@@ -1273,16 +1273,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[
@@ -1318,19 +1323,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/widget/PuppetWidget.cpp
+++ b/widget/PuppetWidget.cpp
@@ -1102,16 +1102,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
@@ -195,16 +195,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;
   }