Bug 1397426 - Add IPC interface to tell TabChild's to render and clear layers, distinct from setting the active state on the DocShell. r=billm draft
authorMike Conley <mconley@mozilla.com>
Fri, 03 Nov 2017 10:27:05 -0400
changeset 715455 6c53729ae4b07c67d9e3de99c12e1165a85ffaac
parent 715271 ac93fdadf1022211eec62258ad22b42cb37a6d14
child 715456 3909bfac189a980784fff998c6a71b1b38e7c9aa
push id94165
push usermconley@mozilla.com
push dateWed, 03 Jan 2018 23:25:52 +0000
reviewersbillm
bugs1397426
milestone59.0a1
Bug 1397426 - Add IPC interface to tell TabChild's to render and clear layers, distinct from setting the active state on the DocShell. r=billm Originally, setting the active state on the DocShell for remote browsers also did the work of making the TabChild render its layers and upload them to the compositor. This patch adds a new renderLayers method to nsITabParent which allows more fine-grained control - we can now, for example, cause layers to be rendered and uploaded without activating the DocShell. Note that if one activates or deactivates the DocShell, we'll still do the work of attempting to render / clear the layers if it hasn't already been done. MozReview-Commit-ID: KkLaMDTzfHi
dom/interfaces/base/nsITabParent.idl
dom/ipc/PBrowser.ipdl
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
--- a/dom/interfaces/base/nsITabParent.idl
+++ b/dom/interfaces/base/nsITabParent.idl
@@ -12,21 +12,32 @@ typedef unsigned long long nsViewID;
 [builtinclass, scriptable, uuid(8e49f7b0-1f98-4939-bf91-e9c39cd56434)]
 interface nsITabParent : nsISupports
 {
   void getChildProcessOffset(out int32_t aCssX, out int32_t aCssY);
 
   readonly attribute boolean useAsyncPanZoom;
 
   /**
-    * Manages the docshell active state of the remote browser.
+    * Manages the docshell active state of the remote browser. Setting the
+    * docShell to be active will also cause it to render layers and upload
+    * them to the compositor. Setting the docShell as not active will clear
+    * those layers.
     */
   attribute boolean docShellIsActive;
 
   /**
+   * When aEnabled is set to true, this tells the child to paint and
+   * upload layers to the compositor. When aEnabled is set to false,
+   * previous layers are cleared from the compositor, but only if
+   * preserveLayers is also set to false.
+   */
+  void renderLayers(in bool aEnabled);
+
+  /**
    * Whether this tabParent is in prerender mode.
    */
   [infallible] readonly attribute boolean isPrerendered;
 
   /**
     * As an optimisation, setting the docshell's active state to
     * inactive also triggers a layer invalidation to free up some
     * potentially unhelpful memory usage. Calling preserveLayers
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -786,21 +786,32 @@ child:
 
     /**
      * Update the child side docShell active (resource use) state.
      *
      * @param aIsActive
      *        Whether to activate or deactivate the docshell.
      * @param aPreserveLayers
      *        Whether layer trees should be preserved for inactive docshells.
+     */
+    async SetDocShellIsActive(bool aIsActive);
+
+    /**
+     * 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 aLayerObserverEpoch
      *        The layer observer epoch for this activation. This message should be
      *        ignored if this epoch has already been observed (via ForcePaint).
      */
-    async SetDocShellIsActive(bool aIsActive, bool aPreserveLayers, uint64_t aLayerObserverEpoch);
+    async RenderLayers(bool aEnabled, 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/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -431,17 +431,16 @@ TabChild::TabChild(nsIContentChild* aMan
   , mLayerObserverEpoch(0)
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
   , mNativeWindowHandle(0)
 #endif
 #if defined(ACCESSIBILITY)
   , mTopLevelDocAccessibleChild(nullptr)
 #endif
   , mPendingDocShellIsActive(false)
-  , mPendingDocShellPreserveLayers(false)
   , mPendingDocShellReceivedMessage(false)
   , mPendingDocShellBlockers(0)
   , mWidgetNativeData(0)
 {
   mozilla::HoldJSObjects(this);
 
   nsWeakPtr weakPtrThis(do_GetWeakReference(static_cast<nsITabChild*>(this)));  // for capture by the lambda
   mSetAllowedTouchBehaviorCallback = [weakPtrThis](uint64_t aInputBlockId,
@@ -2657,18 +2656,17 @@ TabChild::AddPendingDocShellBlocker()
 }
 
 void
 TabChild::RemovePendingDocShellBlocker()
 {
   mPendingDocShellBlockers--;
   if (!mPendingDocShellBlockers && mPendingDocShellReceivedMessage) {
     mPendingDocShellReceivedMessage = false;
-    InternalSetDocShellIsActive(mPendingDocShellIsActive,
-                                mPendingDocShellPreserveLayers);
+    InternalSetDocShellIsActive(mPendingDocShellIsActive);
   }
 }
 
 void
 TabChild::OnDocShellActivated(bool aIsActive)
 {
   if (aIsActive) {
     if (!sActiveTabs) {
@@ -2680,71 +2678,102 @@ TabChild::OnDocShellActivated(bool aIsAc
       sActiveTabs->RemoveEntry(this);
       // We don't delete sActiveTabs here when it's empty since that
       // could cause a lot of churn. Instead, we wait until ~TabChild.
     }
   }
 }
 
 void
-TabChild::InternalSetDocShellIsActive(bool aIsActive, bool aPreserveLayers)
+TabChild::InternalSetDocShellIsActive(bool aIsActive)
+{
+  // docshell is consider prerendered only if not active yet
+  mIsPrerendered &= !aIsActive;
+  nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
+
+  if (docShell) {
+    docShell->SetIsActive(aIsActive);
+  }
+}
+
+mozilla::ipc::IPCResult
+TabChild::RecvSetDocShellIsActive(const bool& aIsActive)
 {
+  // If we're currently waiting for window opening to complete, we need to hold
+  // off on setting the docshell active. We queue up the values we're receiving
+  // in the mWindowOpenDocShellActiveStatus.
+  if (mPendingDocShellBlockers > 0) {
+    mPendingDocShellReceivedMessage = true;
+    mPendingDocShellIsActive = aIsActive;
+    return IPC_OK();
+  }
+
+  InternalSetDocShellIsActive(aIsActive);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+TabChild::RecvRenderLayers(const bool& aEnabled, const uint64_t& aLayerObserverEpoch)
+{
+  // Since requests to change the rendering state 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 IPC_OK();
+  }
+  mLayerObserverEpoch = aLayerObserverEpoch;
+
   auto clearForcePaint = 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 (aIsActive) {
+    if (aEnabled) {
       ProcessHangMonitor::ClearForcePaint();
     }
   });
 
   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 SetDocShellIsActive request that was made.
+    // latest RecvRenderLayers request that was made.
     lm->SetLayerObserverEpoch(mLayerObserverEpoch);
   }
 
-  // docshell is consider prerendered only if not active yet
-  mIsPrerendered &= !aIsActive;
-  nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
-  if (docShell) {
-    bool wasActive;
-    docShell->GetIsActive(&wasActive);
-    if (aIsActive && wasActive) {
+  if (aEnabled) {
+    if (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(mLayerObserverEpoch);
-        return;
+        return IPC_OK();
       }
     }
 
-    docShell->SetIsActive(aIsActive);
-  }
-
-  if (aIsActive) {
     MakeVisible();
 
+    nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
     if (!docShell) {
-      return;
+      return IPC_OK();
     }
 
     // We don't use TabChildBase::GetPresShell() here because that would create
     // a content viewer if one doesn't exist yet. Creating a content viewer can
     // cause JS to run, which we want to avoid. nsIDocShell::GetPresShell
     // returns null if no content viewer exists yet.
     if (nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell()) {
+      presShell->SetIsActive(true);
+
       if (nsIFrame* root = presShell->FrameConstructor()->GetRootFrame()) {
         FrameLayerBuilder::InvalidateAllLayersForFrame(
           nsLayoutUtils::GetDisplayRootFrame(root));
         root->SchedulePaint();
       }
 
       Telemetry::AutoTimer<Telemetry::TABCHILD_PAINT_TIME> timer;
       // If we need to repaint, let's do that right away. No sense waiting until
@@ -2758,46 +2787,20 @@ TabChild::InternalSetDocShellIsActive(bo
         RefPtr<nsViewManager> vm = presShell->GetViewManager();
         if (nsView* view = vm->GetRootView()) {
           presShell->Paint(view, view->GetBounds(),
                            nsIPresShell::PAINT_LAYERS);
         }
       }
       APZCCallbackHelper::SuppressDisplayport(false, presShell);
     }
-  } else if (!aPreserveLayers) {
+  } else {
     MakeHidden();
   }
-}
-
-mozilla::ipc::IPCResult
-TabChild::RecvSetDocShellIsActive(const bool& aIsActive,
-                                  const bool& aPreserveLayers,
-                                  const uint64_t& aLayerObserverEpoch)
-{
-  // Since requests to change the active docshell 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 IPC_OK();
-  }
-  mLayerObserverEpoch = aLayerObserverEpoch;
-
-  // If we're currently waiting for window opening to complete, we need to hold
-  // off on setting the docshell active. We queue up the values we're receiving
-  // in the mWindowOpenDocShellActiveStatus.
-  if (mPendingDocShellBlockers > 0) {
-    mPendingDocShellReceivedMessage = true;
-    mPendingDocShellIsActive = aIsActive;
-    mPendingDocShellPreserveLayers = aPreserveLayers;
-    return IPC_OK();
-  }
-
-  InternalSetDocShellIsActive(aIsActive, aPreserveLayers);
+
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 TabChild::RecvNavigateByKey(const bool& aForward, const bool& aForDocumentNavigation)
 {
   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
   if (fm) {
@@ -3032,49 +3035,56 @@ TabChild::NotifyPainted()
         mRemoteFrame->SendNotifyCompositorTransaction();
         mNotified = true;
     }
 }
 
 void
 TabChild::MakeVisible()
 {
-  if (mPuppetWidget && mPuppetWidget->IsVisible()) {
+  if (IsVisible()) {
     return;
   }
 
   if (mPuppetWidget) {
     mPuppetWidget->Show(true);
   }
 }
 
 void
 TabChild::MakeHidden()
 {
-  if (mPuppetWidget && !mPuppetWidget->IsVisible()) {
+  if (!IsVisible()) {
     return;
   }
 
   ClearCachedResources();
 
   // Hide all plugins in this tab.
   if (nsCOMPtr<nsIPresShell> shell = GetPresShell()) {
     if (nsPresContext* presContext = shell->GetPresContext()) {
       nsRootPresContext* rootPresContext = presContext->GetRootPresContext();
       nsIFrame* rootFrame = shell->FrameConstructor()->GetRootFrame();
       rootPresContext->ComputePluginGeometryUpdates(rootFrame, nullptr, nullptr);
       rootPresContext->ApplyPluginGeometryUpdates();
     }
+    shell->SetIsActive(false);
   }
 
   if (mPuppetWidget) {
     mPuppetWidget->Show(false);
   }
 }
 
+bool
+TabChild::IsVisible()
+{
+  return mPuppetWidget && mPuppetWidget->IsVisible();
+}
+
 NS_IMETHODIMP
 TabChild::GetMessageManager(nsIContentFrameMessageManager** aResult)
 {
   if (mTabChildGlobal) {
     NS_ADDREF(*aResult = mTabChildGlobal);
     return NS_OK;
   }
   *aResult = nullptr;
@@ -3553,17 +3563,17 @@ TabChild::ForcePaint(uint64_t aLayerObse
 {
   if (!IPCOpen()) {
     // Don't bother doing anything now. Better to wait until we receive the
     // message on the PContent channel.
     return;
   }
 
   nsAutoScriptBlocker scriptBlocker;
-  RecvSetDocShellIsActive(true, false, aLayerObserverEpoch);
+  RecvRenderLayers(true, 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
@@ -574,16 +574,17 @@ public:
 
   /**
    * Signal to this TabChild that it should be made visible:
    * activated widget, retained layer tree, etc.  (Respectively,
    * made not visible.)
    */
   void MakeVisible();
   void MakeHidden();
+  bool IsVisible();
 
   void OnDocShellActivated(bool aIsActive);
 
   nsIContentChild* Manager() const { return mManager; }
 
   static inline TabChild*
   GetFrom(nsIDocShell* aDocShell)
   {
@@ -797,19 +798,19 @@ protected:
   virtual ~TabChild();
 
   virtual PRenderFrameChild* AllocPRenderFrameChild() override;
 
   virtual bool DeallocPRenderFrameChild(PRenderFrameChild* aFrame) override;
 
   virtual mozilla::ipc::IPCResult RecvDestroy() override;
 
-  virtual mozilla::ipc::IPCResult RecvSetDocShellIsActive(const bool& aIsActive,
-                                                          const bool& aIsHidden,
-                                                          const uint64_t& aLayerObserverEpoch) 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 RecvNavigateByKey(const bool& aForward,
                                                     const bool& aForDocumentNavigation) override;
 
   virtual mozilla::ipc::IPCResult RecvRequestNotifyAfterRemotePaint() override;
 
   virtual mozilla::ipc::IPCResult RecvSuppressDisplayport(const bool& aEnabled) override;
 
@@ -890,18 +891,17 @@ private:
    * Dispatch aEvent on aEvent.mWidget.
    */
   nsEventStatus DispatchWidgetEventViaAPZ(WidgetGUIEvent& aEvent);
 
   void DispatchWheelEvent(const WidgetWheelEvent& aEvent,
                           const ScrollableLayerGuid& aGuid,
                           const uint64_t& aInputBlockId);
 
-  void InternalSetDocShellIsActive(bool aIsActive,
-                                   bool aPreserveLayers);
+  void InternalSetDocShellIsActive(bool aIsActive);
 
   bool CreateRemoteLayerManager(mozilla::layers::PCompositorBridgeChild* aCompositorChild);
 
   class DelayedDeleteRunnable;
 
   TextureFactoryIdentifier mTextureFactoryIdentifier;
   nsCOMPtr<nsIWebNavigation> mWebNav;
   RefPtr<mozilla::dom::TabGroup> mTabGroup;
@@ -981,17 +981,16 @@ private:
 #endif // defined(XP_WIN)
 
 #if defined(ACCESSIBILITY)
   PDocAccessibleChild* mTopLevelDocAccessibleChild;
 #endif
   bool mCoalesceMouseMoveEvents;
 
   bool mPendingDocShellIsActive;
-  bool mPendingDocShellPreserveLayers;
   bool mPendingDocShellReceivedMessage;
   uint32_t mPendingDocShellBlockers;
 
   WindowsHandle mWidgetNativeData;
 
   // This state is used to keep track of the current active tabs (the ones in
   // the foreground). There may be more than one if there are multiple browser
   // windows open. There may be none if this process does not host any
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -168,16 +168,17 @@ TabParent::TabParent(nsIContentParent* a
   , mCursor(eCursorInvalid)
   , mTabSetsCursor(false)
   , mHasContentOpener(false)
 #ifdef DEBUG
   , mActiveSupressDisplayportCount(0)
 #endif
   , mLayerTreeEpoch(0)
   , mPreserveLayers(false)
+  , mRenderingLayers(false)
   , mHasPresented(false)
   , mHasBeforeUnload(false)
   , mIsMouseEnterIntoWidgetEventSuppressed(false)
 {
   MOZ_ASSERT(aManager);
   // When the input event queue is disabled, we don't need to handle the case
   // that some input events are dispatched before PBrowserConstructor.
   mIsReadyToHandleInputEvents = !ContentParent::IsInputEventQueueSupported();
@@ -2897,24 +2898,21 @@ 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, mPreserveLayers, mLayerTreeEpoch);
+  RenderLayers(isActive);
+  Unused << SendSetDocShellIsActive(isActive);
 
   // update active accessible documents on windows
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
   if (a11y::Compatibility::IsDolphin()) {
     if (a11y::DocAccessibleParent* tabDoc = GetTopLevelDocAccessible()) {
       HWND window = tabDoc->GetEmulatedWindowHandle();
       MOZ_ASSERT(window);
       if (window) {
@@ -2927,23 +2925,16 @@ TabParent::SetDocShellIsActive(bool isAc
     }
   }
 #endif
 
   // Let's inform the priority manager. This operation can end up with the
   // changing of the process priority.
   ProcessPriorityManager::TabActivityChanged(this, isActive);
 
-  // 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;
@@ -2952,16 +2943,47 @@ TabParent::GetDocShellIsActive(bool* aIs
 NS_IMETHODIMP
 TabParent::GetIsPrerendered(bool* aIsPrerendered)
 {
   *aIsPrerendered = mIsPrerendered;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+TabParent::RenderLayers(bool aEnabled)
+{
+  if (aEnabled == mRenderingLayers) {
+    return NS_OK;
+  }
+
+  // Preserve layers means that attempts to stop rendering layers
+  // will be ignored.
+  if (!aEnabled && mPreserveLayers) {
+    return NS_OK;
+  }
+
+  mRenderingLayers = 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);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 TabParent::PreserveLayers(bool aPreserveLayers)
 {
   mPreserveLayers = aPreserveLayers;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabParent::SuppressDisplayport(bool aEnabled)
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -777,16 +777,19 @@ private:
   static void RemoveTabParentFromTable(uint64_t aLayersId);
 
   uint64_t mLayerTreeEpoch;
 
   // If this flag is set, then the tab's layers will be preserved even when
   // the tab's docshell is inactive.
   bool mPreserveLayers;
 
+  // Holds the most recent value passed to the RenderLayers function.
+  bool mRenderingLayers;
+
   // True if this TabParent has had its layer tree sent to the compositor
   // at least once.
   bool mHasPresented;
 
   // True if at least one window hosted in the TabChild has added a
   // beforeunload event listener.
   bool mHasBeforeUnload;