Handle WebContents occlusion on aura.

With this CL, when a WebContents is occluded on aura:
- WebContents::GetVisibility returns OCCLUDED
- RenderWidgetHostImpl::WasHidden is called on all
  RenderWidgetHostImpls that belong to the WebContents.
  That allows:
  - Changing the priority of renderer processes to background.
  - Not rendering frames unnecessarily.

Bug: 668690
Change-Id: Ie1c86d271276576a6b5c7adfb11bd0e42d5d491e
Reviewed-on: https://chromium-review.googlesource.com/937820
Reviewed-by: Peter Beverloo <[email protected]>
Reviewed-by: Scott Violet <[email protected]>
Reviewed-by: Dominick Ng <[email protected]>
Reviewed-by: Avi Drissman <[email protected]>
Commit-Queue: François Doray <[email protected]>
Cr-Commit-Position: refs/heads/master@{#540149}
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 0185340..ca66c49 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -531,9 +531,6 @@
       last_sent_theme_color_(SK_ColorTRANSPARENT),
       did_first_visually_non_empty_paint_(false),
       capturer_count_(0),
-      should_normally_be_visible_(true),
-      should_normally_be_occluded_(false),
-      did_first_set_visible_(false),
       is_being_destroyed_(false),
       is_notifying_observers_(false),
       notify_disconnection_(false),
@@ -1317,6 +1314,7 @@
 
 void WebContentsImpl::IncrementCapturerCount(const gfx::Size& capture_size) {
   DCHECK(!is_being_destroyed_);
+  const bool was_captured = IsBeingCaptured();
   ++capturer_count_;
   DVLOG(1) << "There are now " << capturer_count_
            << " capturing(s) of WebContentsImpl@" << this;
@@ -1328,8 +1326,14 @@
     OnPreferredSizeChanged(preferred_size_);
   }
 
-  // Ensure that all views are un-occluded before capture begins.
-  DoWasUnOccluded();
+  if (GetVisibility() != Visibility::VISIBLE && !was_captured) {
+    // Ensure that all views act as if they were visible before capture begins.
+    // TODO(fdoray): Replace RenderWidgetHostView::WasUnOccluded() with a method
+    // to explicitly notify the RenderWidgetHostView that capture began.
+    // https://crbug.com/668690
+    for (RenderWidgetHostView* view : GetRenderWidgetHostViewsInTree())
+      view->WasUnOccluded();
+  }
 }
 
 void WebContentsImpl::DecrementCapturerCount() {
@@ -1346,13 +1350,12 @@
     preferred_size_for_capture_ = gfx::Size();
     OnPreferredSizeChanged(old_size);
 
-    if (IsHidden()) {
+    if (visibility_ == Visibility::HIDDEN) {
       DVLOG(1) << "Executing delayed WasHidden().";
       WasHidden();
-    }
-
-    if (should_normally_be_occluded_)
+    } else if (visibility_ == Visibility::OCCLUDED) {
       WasOccluded();
+    }
   }
 }
 
@@ -1463,8 +1466,6 @@
 }
 
 void WebContentsImpl::WasShown() {
-  const Visibility previous_visibility = GetVisibility();
-
   controller_.SetActive(true);
 
   if (auto* view = GetRenderWidgetHostView()) {
@@ -1480,13 +1481,10 @@
   SendPageMessage(new PageMsg_WasShown(MSG_ROUTING_NONE));
 
   last_active_time_ = base::TimeTicks::Now();
-  should_normally_be_visible_ = true;
-  NotifyVisibilityChanged(previous_visibility);
+  SetVisibility(Visibility::VISIBLE);
 }
 
 void WebContentsImpl::WasHidden() {
-  const Visibility previous_visibility = GetVisibility();
-
   // If there are entities capturing screenshots or video (e.g., mirroring),
   // don't activate the "disable rendering" optimization.
   if (!IsBeingCaptured()) {
@@ -1505,8 +1503,7 @@
     SendPageMessage(new PageMsg_WasHidden(MSG_ROUTING_NONE));
   }
 
-  should_normally_be_visible_ = false;
-  NotifyVisibilityChanged(previous_visibility);
+  SetVisibility(Visibility::HIDDEN);
 }
 
 #if defined(OS_ANDROID)
@@ -1539,39 +1536,15 @@
 #endif
 
 void WebContentsImpl::WasOccluded() {
-  const Visibility previous_visibility = GetVisibility();
-
   if (!IsBeingCaptured()) {
     for (RenderWidgetHostView* view : GetRenderWidgetHostViewsInTree())
       view->WasOccluded();
   }
-
-  should_normally_be_occluded_ = true;
-  NotifyVisibilityChanged(previous_visibility);
-}
-
-void WebContentsImpl::WasUnOccluded() {
-  const Visibility previous_visibility = GetVisibility();
-
-  if (!IsBeingCaptured())
-    DoWasUnOccluded();
-
-  should_normally_be_occluded_ = false;
-  NotifyVisibilityChanged(previous_visibility);
-}
-
-void WebContentsImpl::DoWasUnOccluded() {
-  // TODO(fdoray): Only call WasUnOccluded on frames in the active viewport.
-  for (RenderWidgetHostView* view : GetRenderWidgetHostViewsInTree())
-    view->WasUnOccluded();
+  SetVisibility(Visibility::OCCLUDED);
 }
 
 Visibility WebContentsImpl::GetVisibility() const {
-  if (!should_normally_be_visible_)
-    return Visibility::HIDDEN;
-  if (should_normally_be_occluded_)
-    return Visibility::OCCLUDED;
-  return Visibility::VISIBLE;
+  return visibility_;
 }
 
 bool WebContentsImpl::NeedToFireBeforeUnload() {
@@ -1724,7 +1697,8 @@
   // This is set before initializing the render manager since
   // RenderFrameHostManager::Init calls back into us via its delegate to ask if
   // it should be hidden.
-  should_normally_be_visible_ = !params.initially_hidden;
+  visibility_ =
+      params.initially_hidden ? Visibility::HIDDEN : Visibility::VISIBLE;
 
   // The routing ids must either all be set or all be unset.
   DCHECK((params.routing_id == MSG_ROUTING_NONE &&
@@ -3441,10 +3415,12 @@
   }
 }
 
-void WebContentsImpl::NotifyVisibilityChanged(Visibility previous_visibility) {
+void WebContentsImpl::SetVisibility(Visibility visibility) {
+  const Visibility previous_visibility = visibility_;
+  visibility_ = visibility;
+
   // Notify observers if the visibility changed or if WasShown() is being called
   // for the first time.
-  const Visibility visibility = GetVisibility();
   if (visibility != previous_visibility ||
       (visibility == Visibility::VISIBLE && !did_first_set_visible_)) {
     for (auto& observer : observers_)
@@ -5786,7 +5762,7 @@
 }
 
 bool WebContentsImpl::IsHidden() {
-  return !IsBeingCaptured() && !should_normally_be_visible_;
+  return !IsBeingCaptured() && visibility_ != Visibility::VISIBLE;
 }
 
 int WebContentsImpl::GetOuterDelegateFrameTreeNodeId() {
@@ -6037,25 +6013,40 @@
   return currently_playing_video_count_;
 }
 
-void WebContentsImpl::UpdateWebContentsVisibility(bool visible) {
+void WebContentsImpl::UpdateWebContentsVisibility(Visibility visibility) {
+  // Occlusion can cause flakiness in browser tests.
+  static const bool occlusion_is_disabled =
+      base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kDisableBackgroundingOccludedWindowsForTesting);
+  if (occlusion_is_disabled && visibility == Visibility::OCCLUDED)
+    visibility = Visibility::VISIBLE;
+
   if (!did_first_set_visible_) {
-    // If this WebContents has not yet been set to be visible for the first
-    // time, ignore any requests to make it hidden, since resources would
-    // immediately be destroyed and only re-created after content loaded. In
-    // this state the window content is undefined and can show garbage.
-    // However, the page load mechanism requires an activation call through a
-    // visibility call to (re)load.
-    if (visible) {
+    if (visibility == Visibility::VISIBLE) {
+      // A WebContents created with CreateParams::initially_hidden = false
+      // starts with GetVisibility() == Visibility::VISIBLE even though it is
+      // not really visible. Call WasShown() when it becomes visible for real as
+      // the page load mechanism and some WebContentsObserver rely on that.
       WasShown();
       did_first_set_visible_ = true;
     }
+
+    // Trust the initial visibility of the WebContents and do not switch it to
+    // HIDDEN or OCCLUDED before it becomes VISIBLE for real. Doing so would
+    // result in destroying resources that would immediately be recreated (e.g.
+    // UpdateWebContents(HIDDEN) can be called when a WebContents is added to a
+    // hidden window that is about to be shown).
+
     return;
   }
-  if (visible == should_normally_be_visible_)
+
+  if (visibility == visibility_)
     return;
 
-  if (visible)
+  if (visibility == Visibility::VISIBLE)
     WasShown();
+  else if (visibility == Visibility::OCCLUDED)
+    WasOccluded();
   else
     WasHidden();
 }