Bug 1176019 - Force a paint when switching to a loaded tab r?mconley draft
authorDoug Thayer <dothayer@mozilla.com>
Mon, 14 May 2018 23:45:00 -0700
changeset 797878 a7f61dc64a71250f448f8d7e1f8e6c28a71c88a9
parent 797871 fcdefab4f1d545c9140cd803bd0023fb0b182616
child 797879 72125d5d399da91ca48d7f02e109d3a28a9161e9
push id110615
push userbmo:dothayer@mozilla.com
push dateMon, 21 May 2018 22:41:06 +0000
reviewersmconley
bugs1176019
milestone62.0a1
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>