Bug 1176019 - Force a paint when switching to a loaded tab r=mconley
authorDoug Thayer <dothayer@mozilla.com>
Mon, 14 May 2018 23:45:00 -0700
changeset 419219 27f6f789b1940eaaad96223fac928ca2bb371dd7
parent 419218 dd85a2be4527c4dfaff44280d26bec7e8ffc7bd8
child 419220 94bfb6800a01efd5560ad29dc2e770ced662c70b
push id34031
push usernbeleuzu@mozilla.com
push dateTue, 22 May 2018 09:47:31 +0000
treeherdermozilla-central@ac1c5c363e29 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley
bugs1176019
milestone62.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 1176019 - Force a paint when switching to a loaded tab r=mconley This is fairly straightforward, other than the fact that the nomenclature gets a bit awkward with the aForce parameter on the ForcePaint methods. I'm not sure which direction to go with this - "aForce" seems a fairly intuitive name for what we want, and I'm kind of inclined to say the existing ForcePaint mechanic should be renamed to something like PaintWithInterrupt, or PaintWithPriority. MozReview-Commit-ID: Bj9DROug1pC
browser/modules/AsyncTabSwitcher.jsm
dom/interfaces/base/nsITabParent.idl
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PBrowser.ipdl
dom/ipc/PProcessHangMonitor.ipdl
dom/ipc/ProcessHangMonitor.cpp
dom/ipc/ProcessHangMonitor.h
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
toolkit/content/widgets/remote-browser.xml
--- a/browser/modules/AsyncTabSwitcher.jsm
+++ b/browser/modules/AsyncTabSwitcher.jsm
@@ -974,16 +974,19 @@ class AsyncTabSwitcher {
 
     this._requestingTab = true;
     this.logState("requestTab " + this.tinfo(tab));
     this.startTabSwitch();
 
     this.requestedTab = tab;
     if (tabState == this.STATE_LOADED) {
       this.maybeVisibleTabs.clear();
+      if (tab.linkedBrowser.isRemoteBrowser) {
+        tab.linkedBrowser.forceRepaint();
+      }
     }
 
     tab.linkedBrowser.setAttribute("primary", "true");
     if (this.lastPrimaryTab && this.lastPrimaryTab != tab) {
       this.lastPrimaryTab.linkedBrowser.removeAttribute("primary");
     }
     this.lastPrimaryTab = tab;
 
--- a/dom/interfaces/base/nsITabParent.idl
+++ b/dom/interfaces/base/nsITabParent.idl
@@ -31,16 +31,23 @@ interface nsITabParent : nsISupports
 
   /**
    * True if layers are being rendered and the compositor has reported
    * receiving them.
    */
   readonly attribute boolean hasLayers;
 
   /**
+   * Sends a message to the child ensuring that they paint as early as
+   * possible. This will send the message to paint even if renderLayers
+   * is already true.
+   */
+  void forceRepaint();
+
+  /**
    * As an optimisation, setting the docshell's active state to
    * inactive also triggers a layer invalidation to free up some
    * potentially unhelpful memory usage. Calling preserveLayers
    * will cause the layers to be preserved even for inactive
    * docshells.
    */
   void preserveLayers(in boolean aPreserveLayers);
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -5313,22 +5313,27 @@ ContentParent::SendGetFilesResponseAndFo
                                              const GetFilesResponseResult& aResult)
 {
   if (mGetFilesPendingRequests.Remove(aUUID)) {
     Unused << SendGetFilesResponse(aUUID, aResult);
   }
 }
 
 void
-ContentParent::ForceTabPaint(TabParent* aTabParent, uint64_t aLayerObserverEpoch)
+ContentParent::PaintTabWhileInterruptingJS(TabParent* aTabParent,
+                                           bool aForceRepaint,
+                                           uint64_t aLayerObserverEpoch)
 {
   if (!mHangMonitorActor) {
     return;
   }
-  ProcessHangMonitor::ForcePaint(mHangMonitorActor, aTabParent, aLayerObserverEpoch);
+  ProcessHangMonitor::PaintWhileInterruptingJS(mHangMonitorActor,
+                                               aTabParent,
+                                               aForceRepaint,
+                                               aLayerObserverEpoch);
 }
 
 void
 ContentParent::UpdateCookieStatus(nsIChannel   *aChannel)
 {
   PNeckoParent *neckoParent = LoneManagedOrNullAsserts(ManagedPNeckoParent());
   PCookieServiceParent *csParent = LoneManagedOrNullAsserts(neckoParent->ManagedPCookieServiceParent());
   if (csParent) {
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -634,17 +634,17 @@ public:
 
   virtual mozilla::ipc::IPCResult
   RecvClassifyLocal(const URIParams& aURI,
                     const nsCString& aTables,
                     nsresult* aRv,
                     nsTArray<nsCString>* aResults) override;
 
   // Use the PHangMonitor channel to ask the child to repaint a tab.
-  void ForceTabPaint(TabParent* aTabParent, uint64_t aLayerObserverEpoch);
+  void PaintTabWhileInterruptingJS(TabParent* aTabParent, bool aForceRepaint, uint64_t aLayerObserverEpoch);
 
   // This function is called when we are about to load a document from an
   // HTTP(S), FTP or wyciwyg channel for a content process.  It is a useful
   // place to start to kick off work as early as possible in response to such
   // document loads.
   nsresult AboutToLoadHttpFtpWyciwygDocumentForChild(nsIChannel* aChannel);
 
   nsresult TransmitPermissionsForPrincipal(nsIPrincipal* aPrincipal);
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -555,17 +555,17 @@ parent:
      * Child informs the parent that the content is ready to handle input
      * events. This is sent when the TabChild is created.
      */
     async RemoteIsReadyToHandleInputEvents();
 
     /**
      * Child informs the parent that the layer tree is already available.
      */
-    async ForcePaintNoOp(uint64_t aLayerObserverEpoch);
+    async PaintWhileInterruptingJSNoOp(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);
 
     nested(inside_sync) sync DispatchWheelEvent(WidgetWheelEvent event);
@@ -762,21 +762,25 @@ child:
     /**
      * If aEnabled is true, tells the child to paint and upload layers to
      * the compositor. If aEnabled is false, the child stops painting and
      * clears the layers from the compositor.
      *
      * @param aEnabled
      *        True if the child should render and upload layers, false if the
      *        child should clear layers.
+     * @param aForceRepaint
+     *        True if the child should force a paint even if it's already
+     *        visible.
      * @param aLayerObserverEpoch
      *        The layer observer epoch for this activation. This message should be
-     *        ignored if this epoch has already been observed (via ForcePaint).
+     *        ignored if this epoch has already been observed (via
+     *        PaintWhileInterruptingJS).
      */
-    async RenderLayers(bool aEnabled, uint64_t aLayerObserverEpoch);
+    async RenderLayers(bool aEnabled, bool aForceRepaint, 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
@@ -36,12 +36,12 @@ parent:
   async ClearHang();
 
 child:
   async TerminateScript(bool aTerminateGlobal);
 
   async BeginStartingDebugger();
   async EndStartingDebugger();
 
-  async ForcePaint(TabId tabId, uint64_t aLayerObserverEpoch);
+  async PaintWhileInterruptingJS(TabId tabId, bool forceRepaint, uint64_t aLayerObserverEpoch);
 };
 
 } // namespace mozilla
--- a/dom/ipc/ProcessHangMonitor.cpp
+++ b/dom/ipc/ProcessHangMonitor.cpp
@@ -91,29 +91,31 @@ class HangMonitorChild
 
   bool IsDebuggerStartupComplete();
 
   void NotifyPluginHang(uint32_t aPluginId);
   void NotifyPluginHangAsync(uint32_t aPluginId);
 
   void ClearHang();
   void ClearHangAsync();
-  void ClearForcePaint(uint64_t aLayerObserverEpoch);
+  void ClearPaintWhileInterruptingJS(uint64_t aLayerObserverEpoch);
 
-  // MaybeStartForcePaint will notify the background hang monitor of activity
-  // if this is the first time calling it since ClearForcePaint. It should be
+  // MaybeStartPaintWhileInterruptingJS will notify the background hang monitor of activity
+  // if this is the first time calling it since ClearPaintWhileInterruptingJS. It should be
   // callable from any thread, but you must be holding mMonitor if using it off
-  // the main thread, since it could race with ClearForcePaint.
-  void MaybeStartForcePaint();
+  // the main thread, since it could race with ClearPaintWhileInterruptingJS.
+  void MaybeStartPaintWhileInterruptingJS();
 
   mozilla::ipc::IPCResult RecvTerminateScript(const bool& aTerminateGlobal) override;
   mozilla::ipc::IPCResult RecvBeginStartingDebugger() override;
   mozilla::ipc::IPCResult RecvEndStartingDebugger() override;
 
-  mozilla::ipc::IPCResult RecvForcePaint(const TabId& aTabId, const uint64_t& aLayerObserverEpoch) override;
+  mozilla::ipc::IPCResult RecvPaintWhileInterruptingJS(const TabId& aTabId,
+                                                       const bool& aForceRepaint,
+                                                       const uint64_t& aLayerObserverEpoch) override;
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   void InterruptCallback();
   void Shutdown();
 
   static HangMonitorChild* Get() { return sInstance; }
 
@@ -122,32 +124,33 @@ class HangMonitorChild
     mHangMonitor->Dispatch(Move(aRunnable));
   }
   bool IsOnThread() { return mHangMonitor->IsOnThread(); }
 
  private:
   void ShutdownOnThread();
 
   static Atomic<HangMonitorChild*> sInstance;
-  UniquePtr<BackgroundHangMonitor> mForcePaintMonitor;
+  UniquePtr<BackgroundHangMonitor> mPaintWhileInterruptingJSMonitor;
 
   const RefPtr<ProcessHangMonitor> mHangMonitor;
   Monitor mMonitor;
 
   // Main thread-only.
   bool mSentReport;
 
   // These fields must be accessed with mMonitor held.
   bool mTerminateScript;
   bool mTerminateGlobal;
   bool mStartDebugger;
   bool mFinishedStartingDebugger;
-  bool mForcePaint;
-  TabId mForcePaintTab;
-  MOZ_INIT_OUTSIDE_CTOR uint64_t mForcePaintEpoch;
+  bool mPaintWhileInterruptingJS;
+  bool mPaintWhileInterruptingJSForce;
+  TabId mPaintWhileInterruptingJSTab;
+  MOZ_INIT_OUTSIDE_CTOR uint64_t mPaintWhileInterruptingJSEpoch;
   JSContext* mContext;
   bool mShutdownDone;
 
   // This field is only accessed on the hang thread.
   bool mIPCOpen;
 
   // Allows us to ensure we NotifyActivity only once, allowing
   // either thread to do so.
@@ -222,17 +225,19 @@ public:
   mozilla::ipc::IPCResult RecvClearHang() override;
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   void SetProcess(HangMonitoredProcess* aProcess) { mProcess = aProcess; }
 
   void Shutdown();
 
-  void ForcePaint(dom::TabParent* aTabParent, uint64_t aLayerObserverEpoch);
+  void PaintWhileInterruptingJS(dom::TabParent* aTabParent,
+                                bool aForceRepaint,
+                                uint64_t aLayerObserverEpoch);
 
   void TerminateScript(bool aTerminateGlobal);
   void BeginStartingDebugger();
   void EndStartingDebugger();
   void CleanupPluginHang(uint32_t aPluginId, bool aRemoveFiles);
 
   /**
    * Update the dump for the specified plugin. This method is thread-safe and
@@ -253,17 +258,17 @@ private:
   void SendHangNotification(const HangData& aHangData,
                             const nsString& aBrowserDumpId,
                             bool aTakeMinidump);
   void OnTakeFullMinidumpComplete(const HangData& aHangData,
                                   const nsString& aDumpId);
 
   void ClearHangNotification();
 
-  void ForcePaintOnThread(TabId aTabId, uint64_t aLayerObserverEpoch);
+  void PaintWhileInterruptingJSOnThread(TabId aTabId, bool aForceRepaint, uint64_t aLayerObserverEpoch);
 
   void ShutdownOnThread();
 
   const RefPtr<ProcessHangMonitor> mHangMonitor;
 
   // This field is read-only after construction.
   bool mReportHangs;
 
@@ -275,77 +280,81 @@ private:
   // Must be accessed with mMonitor held.
   RefPtr<HangMonitoredProcess> mProcess;
   bool mShutdownDone;
   // Map from plugin ID to crash dump ID. Protected by mBrowserCrashDumpHashLock.
   nsDataHashtable<nsUint32HashKey, nsString> mBrowserCrashDumpIds;
   Mutex mBrowserCrashDumpHashLock;
   mozilla::ipc::TaskFactory<HangMonitorParent> mMainThreadTaskFactory;
 
-  static bool sShouldForcePaint;
+  static bool sShouldPaintWhileInterruptingJS;
 };
 
-bool HangMonitorParent::sShouldForcePaint = true;
+bool HangMonitorParent::sShouldPaintWhileInterruptingJS = true;
 
 } // namespace
 
 /* HangMonitorChild implementation */
 
 HangMonitorChild::HangMonitorChild(ProcessHangMonitor* aMonitor)
  : mHangMonitor(aMonitor),
    mMonitor("HangMonitorChild lock"),
    mSentReport(false),
    mTerminateScript(false),
    mTerminateGlobal(false),
    mStartDebugger(false),
    mFinishedStartingDebugger(false),
-   mForcePaint(false),
+   mPaintWhileInterruptingJS(false),
+   mPaintWhileInterruptingJSForce(false),
    mShutdownDone(false),
    mIPCOpen(true)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   mContext = danger::GetJSContext();
-  mForcePaintMonitor =
+  mPaintWhileInterruptingJSMonitor =
     MakeUnique<mozilla::BackgroundHangMonitor>("Gecko_Child_ForcePaint",
                                                128, /* ms timeout for microhangs */
                                                1024, /* ms timeout for permahangs */
                                                BackgroundHangMonitor::THREAD_PRIVATE);
 }
 
 HangMonitorChild::~HangMonitorChild()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(sInstance == this);
-  mForcePaintMonitor = nullptr;
+  mPaintWhileInterruptingJSMonitor = nullptr;
   sInstance = nullptr;
 }
 
 void
 HangMonitorChild::InterruptCallback()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
-  bool forcePaint;
-  TabId forcePaintTab;
-  uint64_t forcePaintEpoch;
+  bool paintWhileInterruptingJS;
+  bool paintWhileInterruptingJSForce;
+  TabId paintWhileInterruptingJSTab;
+  uint64_t paintWhileInterruptingJSEpoch;
 
   {
     MonitorAutoLock lock(mMonitor);
-    forcePaint = mForcePaint;
-    forcePaintTab = mForcePaintTab;
-    forcePaintEpoch = mForcePaintEpoch;
+    paintWhileInterruptingJS = mPaintWhileInterruptingJS;
+    paintWhileInterruptingJSForce = mPaintWhileInterruptingJSForce;
+    paintWhileInterruptingJSTab = mPaintWhileInterruptingJSTab;
+    paintWhileInterruptingJSEpoch = mPaintWhileInterruptingJSEpoch;
 
-    mForcePaint = false;
+    mPaintWhileInterruptingJS = false;
   }
 
-  if (forcePaint) {
-    RefPtr<TabChild> tabChild = TabChild::FindTabChild(forcePaintTab);
+  if (paintWhileInterruptingJS) {
+    RefPtr<TabChild> tabChild = TabChild::FindTabChild(paintWhileInterruptingJSTab);
     if (tabChild) {
       js::AutoAssertNoContentJS nojs(mContext);
-      tabChild->ForcePaint(forcePaintEpoch);
+      tabChild->PaintWhileInterruptingJS(paintWhileInterruptingJSEpoch,
+                                         paintWhileInterruptingJSForce);
     }
   }
 }
 
 void
 HangMonitorChild::Shutdown()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
@@ -410,46 +419,49 @@ HangMonitorChild::RecvEndStartingDebugge
   MOZ_RELEASE_ASSERT(IsOnThread());
 
   MonitorAutoLock lock(mMonitor);
   mFinishedStartingDebugger = true;
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
-HangMonitorChild::RecvForcePaint(const TabId& aTabId, const uint64_t& aLayerObserverEpoch)
+HangMonitorChild::RecvPaintWhileInterruptingJS(const TabId& aTabId,
+                                               const bool& aForceRepaint,
+                                               const uint64_t& aLayerObserverEpoch)
 {
   MOZ_RELEASE_ASSERT(IsOnThread());
 
   {
     MonitorAutoLock lock(mMonitor);
-    MaybeStartForcePaint();
-    mForcePaint = true;
-    mForcePaintTab = aTabId;
-    mForcePaintEpoch = aLayerObserverEpoch;
+    MaybeStartPaintWhileInterruptingJS();
+    mPaintWhileInterruptingJS = true;
+    mPaintWhileInterruptingJSForce = aForceRepaint;
+    mPaintWhileInterruptingJSTab = aTabId;
+    mPaintWhileInterruptingJSEpoch = aLayerObserverEpoch;
   }
 
   JS_RequestInterruptCallback(mContext);
 
   return IPC_OK();
 }
 
 void
-HangMonitorChild::MaybeStartForcePaint()
+HangMonitorChild::MaybeStartPaintWhileInterruptingJS()
 {
   // See Bug 1449662. The body of this function other than assertions
   // has been temporarily removed to diagnose a tab switch spinner
   // problem.
   if (!NS_IsMainThread()) {
     mMonitor.AssertCurrentThreadOwns();
   }
 }
 
 void
-HangMonitorChild::ClearForcePaint(uint64_t aLayerObserverEpoch)
+HangMonitorChild::ClearPaintWhileInterruptingJS(uint64_t aLayerObserverEpoch)
 {
   // See Bug 1449662. The body of this function other than assertions
   // has been temporarily removed to diagnose a tab switch spinner
   // problem.
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(XRE_IsContentProcess());
 }
 
@@ -604,17 +616,17 @@ HangMonitorParent::HangMonitorParent(Pro
    mMainThreadTaskFactory(this)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   mReportHangs = mozilla::Preferences::GetBool("dom.ipc.reportProcessHangs", false);
 
   static bool sInited = false;
   if (!sInited) {
     sInited = true;
-    Preferences::AddBoolVarCache(&sShouldForcePaint,
+    Preferences::AddBoolVarCache(&sShouldPaintWhileInterruptingJS,
                                  "browser.tabs.remote.force-paint", true);
   }
 }
 
 HangMonitorParent::~HangMonitorParent()
 {
   MutexAutoLock lock(mBrowserCrashDumpHashLock);
 
@@ -660,37 +672,43 @@ HangMonitorParent::ShutdownOnThread()
   }
 
   MonitorAutoLock lock(mMonitor);
   mShutdownDone = true;
   mMonitor.Notify();
 }
 
 void
-HangMonitorParent::ForcePaint(dom::TabParent* aTab, uint64_t aLayerObserverEpoch)
+HangMonitorParent::PaintWhileInterruptingJS(dom::TabParent* aTab,
+                                            bool aForceRepaint,
+                                            uint64_t aLayerObserverEpoch)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
-  if (sShouldForcePaint) {
+  if (sShouldPaintWhileInterruptingJS) {
     TabId id = aTab->GetTabId();
-    Dispatch(NewNonOwningRunnableMethod<TabId, uint64_t>(
-      "HangMonitorParent::ForcePaintOnThread",
+    Dispatch(NewNonOwningRunnableMethod<TabId, bool, uint64_t>(
+      "HangMonitorParent::PaintWhileInterruptingJSOnThread",
       this,
-      &HangMonitorParent::ForcePaintOnThread,
+      &HangMonitorParent::PaintWhileInterruptingJSOnThread,
       id,
+      aForceRepaint,
       aLayerObserverEpoch));
   }
 }
 
 void
-HangMonitorParent::ForcePaintOnThread(TabId aTabId, uint64_t aLayerObserverEpoch)
+HangMonitorParent::PaintWhileInterruptingJSOnThread(TabId aTabId,
+                                                    bool aForceRepaint,
+                                                    uint64_t aLayerObserverEpoch)
 {
   MOZ_RELEASE_ASSERT(IsOnThread());
 
   if (mIPCOpen) {
-    Unused << SendForcePaint(aTabId, aLayerObserverEpoch);
+    Unused << SendPaintWhileInterruptingJS(aTabId, aForceRepaint,
+                                           aLayerObserverEpoch);
   }
 }
 
 void
 HangMonitorParent::ActorDestroy(ActorDestroyReason aWhy)
 {
   MOZ_RELEASE_ASSERT(IsOnThread());
   mIPCOpen = false;
@@ -1352,38 +1370,39 @@ 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)
+ProcessHangMonitor::PaintWhileInterruptingJS(PProcessHangMonitorParent* aParent,
+                                             dom::TabParent* aTabParent,
+                                             bool aForceRepaint,
+                                             uint64_t aLayerObserverEpoch)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   auto parent = static_cast<HangMonitorParent*>(aParent);
-  parent->ForcePaint(aTabParent, aLayerObserverEpoch);
+  parent->PaintWhileInterruptingJS(aTabParent, aForceRepaint, aLayerObserverEpoch);
 }
 
 /* static */ void
-ProcessHangMonitor::ClearForcePaint(uint64_t aLayerObserverEpoch)
+ProcessHangMonitor::ClearPaintWhileInterruptingJS(uint64_t aLayerObserverEpoch)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(XRE_IsContentProcess());
 
   if (HangMonitorChild* child = HangMonitorChild::Get()) {
-    child->ClearForcePaint(aLayerObserverEpoch);
+    child->ClearPaintWhileInterruptingJS(aLayerObserverEpoch);
   }
 }
 
 /* static */ void
-ProcessHangMonitor::MaybeStartForcePaint()
+ProcessHangMonitor::MaybeStartPaintWhileInterruptingJS()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(XRE_IsContentProcess());
 
   if (HangMonitorChild* child = HangMonitorChild::Get()) {
-    child->MaybeStartForcePaint();
+    child->MaybeStartPaintWhileInterruptingJS();
   }
 }
--- a/dom/ipc/ProcessHangMonitor.h
+++ b/dom/ipc/ProcessHangMonitor.h
@@ -40,21 +40,22 @@ class ProcessHangMonitor final
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   static PProcessHangMonitorParent* AddProcess(dom::ContentParent* aContentParent);
   static void RemoveProcess(PProcessHangMonitorParent* aParent);
 
   static void ClearHang();
 
-  static void ForcePaint(PProcessHangMonitorParent* aParent,
-                         dom::TabParent* aTab,
-                         uint64_t aLayerObserverEpoch);
-  static void ClearForcePaint(uint64_t aLayerObserverEpoch);
-  static void MaybeStartForcePaint();
+  static void PaintWhileInterruptingJS(PProcessHangMonitorParent* aParent,
+                                       dom::TabParent* aTab,
+                                       bool aForceRepaint,
+                                       uint64_t aLayerObserverEpoch);
+  static void ClearPaintWhileInterruptingJS(uint64_t aLayerObserverEpoch);
+  static void MaybeStartPaintWhileInterruptingJS();
 
   enum SlowScriptAction {
     Continue,
     Terminate,
     StartDebugger,
     TerminateGlobal,
   };
   SlowScriptAction NotifySlowScript(nsITabChild* aTabChild,
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -2531,17 +2531,19 @@ TabChild::RemovePendingDocShellBlocker()
 {
   mPendingDocShellBlockers--;
   if (!mPendingDocShellBlockers && mPendingDocShellReceivedMessage) {
     mPendingDocShellReceivedMessage = false;
     InternalSetDocShellIsActive(mPendingDocShellIsActive);
   }
   if (!mPendingDocShellBlockers && mPendingRenderLayersReceivedMessage) {
     mPendingRenderLayersReceivedMessage = false;
-    RecvRenderLayers(mPendingRenderLayers, mPendingLayerObserverEpoch);
+    RecvRenderLayers(mPendingRenderLayers,
+                     false /* aForceRepaint */,
+                     mPendingLayerObserverEpoch);
   }
 }
 
 void
 TabChild::InternalSetDocShellIsActive(bool aIsActive)
 {
   nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
 
@@ -2562,17 +2564,17 @@ TabChild::RecvSetDocShellIsActive(const 
     return IPC_OK();
   }
 
   InternalSetDocShellIsActive(aIsActive);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
-TabChild::RecvRenderLayers(const bool& aEnabled, const uint64_t& aLayerObserverEpoch)
+TabChild::RecvRenderLayers(const bool& aEnabled, const bool& aForceRepaint, const uint64_t& aLayerObserverEpoch)
 {
   if (mPendingDocShellBlockers > 0) {
     mPendingRenderLayersReceivedMessage = true;
     mPendingRenderLayers = aEnabled;
     mPendingLayerObserverEpoch = aLayerObserverEpoch;
     return IPC_OK();
   }
 
@@ -2580,49 +2582,49 @@ TabChild::RecvRenderLayers(const bool& a
   // 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 IPC_OK();
   }
   mLayerObserverEpoch = aLayerObserverEpoch;
 
-  auto clearForcePaint = MakeScopeExit([&] {
+  auto clearPaintWhileInterruptingJS = MakeScopeExit([&] {
     // We might force a paint, or we might already have painted and this is a
     // no-op. In either case, once we exit this scope, we need to alert the
     // ProcessHangMonitor that we've finished responding to what might have
     // been a request to force paint. This is so that the BackgroundHangMonitor
     // for force painting can be made to wait again.
     if (aEnabled) {
-      ProcessHangMonitor::ClearForcePaint(mLayerObserverEpoch);
+      ProcessHangMonitor::ClearPaintWhileInterruptingJS(mLayerObserverEpoch);
     }
   });
 
   if (aEnabled) {
-    ProcessHangMonitor::MaybeStartForcePaint();
+    ProcessHangMonitor::MaybeStartPaintWhileInterruptingJS();
   }
 
   if (mCompositorOptions) {
     MOZ_ASSERT(mPuppetWidget);
     RefPtr<LayerManager> lm = mPuppetWidget->GetLayerManager();
     MOZ_ASSERT(lm);
 
     // We send the current layer observer epoch to the compositor so that
     // TabParent knows whether a layer update notification corresponds to the
     // latest RecvRenderLayers request that was made.
     lm->SetLayerObserverEpoch(mLayerObserverEpoch);
   }
 
   if (aEnabled) {
-    if (IsVisible()) {
+    if (!aForceRepaint && 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.
+      // updated its epoch). PaintWhileInterruptingJSNoOp does that.
       if (IPCOpen()) {
-        Unused << SendForcePaintNoOp(mLayerObserverEpoch);
+        Unused << SendPaintWhileInterruptingJSNoOp(mLayerObserverEpoch);
         return IPC_OK();
       }
     }
 
     if (!sVisibleTabs) {
       sVisibleTabs = new nsTHashtable<nsPtrHashKey<TabChild>>();
     }
     sVisibleTabs->PutEntry(this);
@@ -3441,26 +3443,27 @@ ScreenIntRect
 TabChild::GetOuterRect()
 {
   LayoutDeviceIntRect outerRect =
     RoundedToInt(mUnscaledOuterRect * mPuppetWidget->GetDefaultScale());
   return ViewAs<ScreenPixel>(outerRect, PixelCastJustification::LayoutDeviceIsScreenForTabDims);
 }
 
 void
-TabChild::ForcePaint(uint64_t aLayerObserverEpoch)
+TabChild::PaintWhileInterruptingJS(uint64_t aLayerObserverEpoch,
+                                   bool aForceRepaint)
 {
   if (!IPCOpen() || !mPuppetWidget || !mPuppetWidget->HasLayerManager()) {
     // Don't bother doing anything now. Better to wait until we receive the
     // message on the PContent channel.
     return;
   }
 
   nsAutoScriptBlocker scriptBlocker;
-  RecvRenderLayers(true, aLayerObserverEpoch);
+  RecvRenderLayers(true /* aEnabled */, aForceRepaint, aLayerObserverEpoch);
 }
 
 void
 TabChild::BeforeUnloadAdded()
 {
   // Don't bother notifying the parent if we don't have an IPC link open.
   if (mBeforeUnloadListeners == 0 && IPCOpen()) {
     SendSetHasBeforeUnload(true);
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -650,17 +650,18 @@ public:
                             const int& aArg);
   void StartScrollbarDrag(const layers::AsyncDragMetrics& aDragMetrics);
   void ZoomToRect(const uint32_t& aPresShellId,
                   const FrameMetrics::ViewID& aViewId,
                   const CSSRect& aRect,
                   const uint32_t& aFlags);
 
   // Request that the docshell be marked as active.
-  void ForcePaint(uint64_t aLayerObserverEpoch);
+  void PaintWhileInterruptingJS(uint64_t aLayerObserverEpoch,
+                                bool aForceRepaint);
 
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
   uintptr_t GetNativeWindowHandle() const { return mNativeWindowHandle; }
 #endif
 
   // These methods return `true` if this TabChild is currently awaiting a
   // Large-Allocation header.
   bool StopAwaitingLargeAlloc();
@@ -723,17 +724,17 @@ protected:
   virtual PRenderFrameChild* AllocPRenderFrameChild() override;
 
   virtual bool DeallocPRenderFrameChild(PRenderFrameChild* aFrame) override;
 
   virtual mozilla::ipc::IPCResult RecvDestroy() override;
 
   virtual mozilla::ipc::IPCResult RecvSetDocShellIsActive(const bool& aIsActive) override;
 
-  virtual mozilla::ipc::IPCResult RecvRenderLayers(const bool& aEnabled, const uint64_t& aLayerObserverEpoch) override;
+  virtual mozilla::ipc::IPCResult RecvRenderLayers(const bool& aEnabled, const bool& aForce, const uint64_t& aLayerObserverEpoch) override;
 
   virtual mozilla::ipc::IPCResult RecvNavigateByKey(const bool& aForward,
                                                     const bool& aForDocumentNavigation) override;
 
   virtual mozilla::ipc::IPCResult RecvRequestNotifyAfterRemotePaint() override;
 
   virtual mozilla::ipc::IPCResult RecvSuppressDisplayport(const bool& aEnabled) override;
 
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -2926,29 +2926,17 @@ TabParent::SetRenderLayers(bool aEnabled
   // Preserve layers means that attempts to stop rendering layers
   // will be ignored.
   if (!aEnabled && mPreserveLayers) {
     return NS_OK;
   }
 
   mRenderLayers = aEnabled;
 
-  // Increment the epoch so that layer tree updates from previous
-  // RenderLayers requests are ignored.
-  mLayerTreeEpoch++;
-
-  Unused << SendRenderLayers(aEnabled, mLayerTreeEpoch);
-
-  // Ask the child to repaint using the PHangMonitor channel/thread (which may
-  // be less congested).
-  if (aEnabled) {
-    ContentParent* cp = Manager()->AsContentParent();
-    cp->ForceTabPaint(this, mLayerTreeEpoch);
-  }
-
+  SetRenderLayersInternal(aEnabled, false /* aForceRepaint */);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabParent::GetRenderLayers(bool* aResult)
 {
   *aResult = mRenderLayers;
   return NS_OK;
@@ -2957,16 +2945,41 @@ TabParent::GetRenderLayers(bool* aResult
 NS_IMETHODIMP
 TabParent::GetHasLayers(bool* aResult)
 {
   *aResult = mHasLayers;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+TabParent::ForceRepaint()
+{
+  SetRenderLayersInternal(true /* aEnabled */,
+                          true /* aForceRepaint */);
+  return NS_OK;
+}
+
+void
+TabParent::SetRenderLayersInternal(bool aEnabled, bool aForceRepaint)
+{
+  // Increment the epoch so that layer tree updates from previous
+  // RenderLayers requests are ignored.
+  mLayerTreeEpoch++;
+
+  Unused << SendRenderLayers(aEnabled, aForceRepaint, mLayerTreeEpoch);
+
+  // Ask the child to repaint using the PHangMonitor channel/thread (which may
+  // be less congested).
+  if (aEnabled) {
+    ContentParent* cp = Manager()->AsContentParent();
+    cp->PaintTabWhileInterruptingJS(this, aForceRepaint, mLayerTreeEpoch);
+  }
+}
+
+NS_IMETHODIMP
 TabParent::PreserveLayers(bool aPreserveLayers)
 {
   mPreserveLayers = aPreserveLayers;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabParent::SuppressDisplayport(bool aEnabled)
@@ -3074,19 +3087,19 @@ TabParent::LayerTreeUpdate(uint64_t aEpo
     event->InitEvent(NS_LITERAL_STRING("MozLayerTreeCleared"), true, false);
   }
   event->SetTrusted(true);
   event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
   mFrameElement->DispatchEvent(*event);
 }
 
 mozilla::ipc::IPCResult
-TabParent::RecvForcePaintNoOp(const uint64_t& aLayerObserverEpoch)
+TabParent::RecvPaintWhileInterruptingJSNoOp(const uint64_t& aLayerObserverEpoch)
 {
-  // We sent a ForcePaint message when layers were already visible. In this
+  // We sent a PaintWhileInterruptingJS 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 IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 TabParent::RecvRemotePaintIsReady()
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -620,17 +620,17 @@ protected:
   virtual PRenderFrameParent* AllocPRenderFrameParent() override;
 
   virtual bool DeallocPRenderFrameParent(PRenderFrameParent* aFrame) override;
 
   virtual mozilla::ipc::IPCResult RecvRemotePaintIsReady() override;
 
   virtual mozilla::ipc::IPCResult RecvRemoteIsReadyToHandleInputEvents() override;
 
-  virtual mozilla::ipc::IPCResult RecvForcePaintNoOp(const uint64_t& aLayerObserverEpoch) override;
+  virtual mozilla::ipc::IPCResult RecvPaintWhileInterruptingJSNoOp(const uint64_t& aLayerObserverEpoch) override;
 
   virtual mozilla::ipc::IPCResult RecvSetDimensions(const uint32_t& aFlags,
                                                     const int32_t& aX, const int32_t& aY,
                                                     const int32_t& aCx, const int32_t& aCy) override;
 
   virtual mozilla::ipc::IPCResult RecvGetTabCount(uint32_t* aValue) override;
 
   virtual mozilla::ipc::IPCResult RecvShowCanvasPermissionPrompt(const nsCString& aFirstPartyURI) override;
@@ -646,16 +646,18 @@ protected:
   bool mUpdatedDimensions;
   nsSizeMode mSizeMode;
   LayoutDeviceIntPoint mClientOffset;
   LayoutDeviceIntPoint mChromeOffset;
 
 private:
   void DestroyInternal();
 
+  void SetRenderLayersInternal(bool aEnabled, bool aForceRepaint);
+
   already_AddRefed<nsFrameLoader>
   GetFrameLoader(bool aUseCachedFrameLoaderAfterDestroy = false) const;
 
   RefPtr<nsIContentParent> mManager;
   void TryCacheDPIAndScale();
 
   bool AsyncPanZoomEnabled() const;
 
--- a/toolkit/content/widgets/remote-browser.xml
+++ b/toolkit/content/widgets/remote-browser.xml
@@ -261,16 +261,27 @@
             if (frameLoader && frameLoader.tabParent) {
               return frameLoader.tabParent.renderLayers = val;
             }
             return false;
           ]]>
         </setter>
       </property>
 
+      <method name="forceRepaint">
+        <body>
+          <![CDATA[
+            let {frameLoader} = this;
+            if (frameLoader && frameLoader.tabParent) {
+              frameLoader.tabParent.forceRepaint();
+            }
+          ]]>
+        </body>
+      </method>
+
       <property name="hasLayers" readonly="true">
         <getter><![CDATA[
           let {frameLoader} = this;
           if (frameLoader.tabParent) {
             return frameLoader.tabParent.hasLayers;
           }
           return false;
         ]]></getter>