Reland "Display Cutout: Notify the browser process on fullscreen frame change"

This is a reland of 326c044a30ccc53226a008a26500785dfa0d6ef9

This is unchanged except for a test fix to web_contents_impl_browsertest.cc.

[email protected],[email protected],[email protected],[email protected]

Original change's description:
> Display Cutout: Notify the browser process on fullscreen frame change
>
> Notify the browser process when the frame owning the current
> fullscreen element is changed.
>
> Design: https://docs.google.com/document/d/1j3jqmGRXAHzpeKeS_tLlOo4C9DsrvPOf_-PMnFzTmeE/edit#heading=h.wozo6ynsdlp3
>
> BUG=838400
>
> Change-Id: I7771f7b58a8c1cdb7ec0b89b30db1de1fe708b32
> Reviewed-on: https://chromium-review.googlesource.com/1054441
> Reviewed-by: Scott Violet <[email protected]>
> Reviewed-by: Alex Moshchuk <[email protected]>
> Reviewed-by: Kinuko Yasuda <[email protected]>
> Reviewed-by: Mounir Lamouri <[email protected]>
> Reviewed-by: Philip Jägenstedt <[email protected]>
> Commit-Queue: Becca Hughes <[email protected]>
> Cr-Commit-Position: refs/heads/master@{#567162}

Bug: 838400
Change-Id: I72c8dff2b4b1d9df5410c3fa14ea32eb5d606ca9
Reviewed-on: https://chromium-review.googlesource.com/1101140
Reviewed-by: Alex Moshchuk <[email protected]>
Commit-Queue: Becca Hughes <[email protected]>
Cr-Commit-Position: refs/heads/master@{#567351}
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 07724d7..c586ac2 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -2311,6 +2311,74 @@
   }
 }
 
+void WebContentsImpl::FullscreenStateChanged(RenderFrameHost* rfh,
+                                             bool is_fullscreen) {
+  int frame_tree_node_id = rfh->GetFrameTreeNodeId();
+  auto it = fullscreen_frame_tree_nodes_.find(frame_tree_node_id);
+  bool changed = false;
+
+  if (is_fullscreen) {
+    // If we are fullscreen then add the FrameTreeNode ID to the set.
+    if (it == fullscreen_frame_tree_nodes_.end()) {
+      fullscreen_frame_tree_nodes_.insert(frame_tree_node_id);
+      changed = true;
+    }
+  } else {
+    FrameTreeNode* ancestor =
+        static_cast<RenderFrameHostImpl*>(rfh)->frame_tree_node();
+    DCHECK(ancestor);
+
+    // If we are not fullscreen then remove this frame and any descendants
+    // from the set.
+    for (it = fullscreen_frame_tree_nodes_.begin();
+         it != fullscreen_frame_tree_nodes_.end();) {
+      FrameTreeNode* node = FrameTreeNode::GloballyFindByID(*it);
+
+      if (!node || frame_tree_node_id == *it ||
+          node->IsDescendantOf(ancestor)) {
+        it = fullscreen_frame_tree_nodes_.erase(it);
+        changed = true;
+      } else {
+        ++it;
+      }
+    }
+  }
+
+  // If we have changed then find the current fullscreen FrameTreeNode
+  // and call the observers. If we have exited fullscreen then this
+  // will be the last frame that was fullscreen.
+  if (changed && fullscreen_frame_tree_nodes_.size() > 0) {
+    unsigned int max_depth = 0;
+    RenderFrameHost* max_depth_rfh = nullptr;
+
+    for (auto node_id : fullscreen_frame_tree_nodes_) {
+      FrameTreeNode* fullscreen_node = FrameTreeNode::GloballyFindByID(node_id);
+      DCHECK(fullscreen_node);
+
+      if (max_depth_rfh == nullptr || fullscreen_node->depth() > max_depth) {
+        max_depth = fullscreen_node->depth();
+        max_depth_rfh = fullscreen_node->current_frame_host();
+      }
+    }
+
+    // If we have already notified observers about this frame then we should not
+    // fire the observers again.
+    DCHECK(max_depth_rfh);
+    if (max_depth_rfh->GetFrameTreeNodeId() ==
+        current_fullscreen_frame_tree_node_id_)
+      return;
+
+    current_fullscreen_frame_tree_node_id_ =
+        max_depth_rfh->GetFrameTreeNodeId();
+
+    for (auto& observer : observers_)
+      observer.DidAcquireFullscreen(max_depth_rfh);
+  } else if (fullscreen_frame_tree_nodes_.size() == 0) {
+    current_fullscreen_frame_tree_node_id_ =
+        RenderFrameHost::kNoFrameTreeNodeId;
+  }
+}
+
 bool WebContentsImpl::IsFullscreenForCurrentTab() const {
   return delegate_ ? delegate_->IsFullscreenForTabOrPending(this) : false;
 }
@@ -4895,6 +4963,9 @@
   pepper_playback_observer_->RenderFrameDeleted(render_frame_host);
 #endif
   display_cutout_host_impl_->RenderFrameDeleted(render_frame_host);
+
+  // Remove any fullscreen state that the frame has stored.
+  FullscreenStateChanged(render_frame_host, false /* is_fullscreen */);
 }
 
 void WebContentsImpl::ShowContextMenu(RenderFrameHost* render_frame_host,
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 93919c36..0b541ff 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -539,6 +539,8 @@
   void EnterFullscreenMode(const GURL& origin,
                            const blink::WebFullscreenOptions& options) override;
   void ExitFullscreenMode(bool will_cause_resize) override;
+  void FullscreenStateChanged(RenderFrameHost* rfh,
+                              bool is_fullscreen) override;
   bool ShouldRouteMessageEvent(
       RenderFrameHost* target_rfh,
       SiteInstance* source_site_instance) const override;
@@ -961,6 +963,8 @@
   friend class WebContentsObserver;
   friend class WebContents;  // To implement factory methods.
 
+  friend class WebContentsImplBrowserTest;
+
   FRIEND_TEST_ALL_PREFIXES(WebContentsImplTest, NoJSMessageOnInterstitials);
   FRIEND_TEST_ALL_PREFIXES(WebContentsImplTest, UpdateTitle);
   FRIEND_TEST_ALL_PREFIXES(WebContentsImplTest, FindOpenerRVHWhenPending);
@@ -977,6 +981,12 @@
   FRIEND_TEST_ALL_PREFIXES(WebContentsImplTest,
                            ResetJavaScriptDialogOnUserNavigate);
   FRIEND_TEST_ALL_PREFIXES(WebContentsImplTest, ParseDownloadHeaders);
+  FRIEND_TEST_ALL_PREFIXES(WebContentsImplBrowserTest,
+                           NotifyFullscreenAcquired);
+  FRIEND_TEST_ALL_PREFIXES(WebContentsImplBrowserTest,
+                           NotifyFullscreenAcquired_Navigate);
+  FRIEND_TEST_ALL_PREFIXES(WebContentsImplBrowserTest,
+                           NotifyFullscreenAcquired_SameOrigin);
   FRIEND_TEST_ALL_PREFIXES(FormStructureBrowserTest, HTMLFiles);
   FRIEND_TEST_ALL_PREFIXES(NavigationControllerTest, HistoryNavigate);
   FRIEND_TEST_ALL_PREFIXES(RenderFrameHostManagerTest, PageDoesBackAndReload);
@@ -1731,6 +1741,15 @@
   class DisplayCutoutHostImpl;
   std::unique_ptr<DisplayCutoutHostImpl> display_cutout_host_impl_;
 
+  // Stores a set of FrameTreeNode ids that are fullscreen.
+  using FullscreenFrameNodes = std::set<int>;
+  FullscreenFrameNodes fullscreen_frame_tree_nodes_;
+
+  // Stores the ID of the current fullscreen |FrameTreeNode| or
+  // |kNoFrameTreeNodeId| if the tab is not currently fullscreen.
+  int current_fullscreen_frame_tree_node_id_ =
+      RenderFrameHost::kNoFrameTreeNodeId;
+
   base::WeakPtrFactory<WebContentsImpl> loading_weak_factory_;
   base::WeakPtrFactory<WebContentsImpl> weak_factory_;
 
diff --git a/content/browser/web_contents/web_contents_impl_browsertest.cc b/content/browser/web_contents/web_contents_impl_browsertest.cc
index 9767bed5..4286d9a5c 100644
--- a/content/browser/web_contents/web_contents_impl_browsertest.cc
+++ b/content/browser/web_contents/web_contents_impl_browsertest.cc
@@ -34,9 +34,11 @@
 #include "content/public/browser/notification_details.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_view_host.h"
 #include "content/public/browser/render_widget_host_view.h"
 #include "content/public/browser/resource_dispatcher_host.h"
+#include "content/public/browser/site_isolation_policy.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_delegate.h"
 #include "content/public/browser/web_contents_observer.h"
@@ -121,6 +123,13 @@
     host_resolver()->AddRule("*", "127.0.0.1");
   }
 
+  bool CurrentFullscreenFrameTreeNodeIsEmpty() {
+    WebContentsImpl* web_contents =
+        static_cast<WebContentsImpl*>(shell()->web_contents());
+    return web_contents->current_fullscreen_frame_tree_node_id_ ==
+           RenderFrameHost::kNoFrameTreeNodeId;
+  }
+
  private:
   DISALLOW_COPY_AND_ASSIGN(WebContentsImplBrowserTest);
 };
@@ -2523,4 +2532,218 @@
   EXPECT_TRUE(new_contents->GetLastCommittedURL().SchemeIs("file"));
 }
 
+namespace {
+
+class FullscreenWebContentsObserver : public WebContentsObserver {
+ public:
+  FullscreenWebContentsObserver(WebContents* web_contents,
+                                RenderFrameHost* wanted_rfh)
+      : WebContentsObserver(web_contents), wanted_rfh_(wanted_rfh) {}
+
+  // WebContentsObserver override.
+  void DidAcquireFullscreen(RenderFrameHost* rfh) override {
+    EXPECT_EQ(wanted_rfh_, rfh);
+    EXPECT_FALSE(found_value_);
+
+    if (rfh == wanted_rfh_) {
+      found_value_ = true;
+      run_loop_.Quit();
+    }
+  }
+
+  void Wait() {
+    if (!found_value_)
+      run_loop_.Run();
+  }
+
+ private:
+  base::RunLoop run_loop_;
+  bool found_value_ = false;
+  RenderFrameHost* wanted_rfh_;
+
+  DISALLOW_COPY_AND_ASSIGN(FullscreenWebContentsObserver);
+};
+
+}  // namespace
+
+IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, NotifyFullscreenAcquired) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  WebContentsImpl* web_contents =
+      static_cast<WebContentsImpl*>(shell()->web_contents());
+  TestWCDelegateForDialogsAndFullscreen test_delegate;
+  web_contents->SetDelegate(&test_delegate);
+
+  GURL url = embedded_test_server()->GetURL(
+      "a.com", "/cross_site_iframe_factory.html?a(b{allowfullscreen})");
+  EXPECT_TRUE(NavigateToURL(shell(), url));
+  RenderFrameHost* main_frame = web_contents->GetMainFrame();
+  int main_frame_id = main_frame->GetFrameTreeNodeId();
+
+  RenderFrameHost* child_frame = ChildFrameAt(main_frame, 0);
+  int child_frame_id = child_frame->GetFrameTreeNodeId();
+
+  WebContentsImpl::FullscreenFrameNodes nodes;
+  EXPECT_EQ(nodes, web_contents->fullscreen_frame_tree_nodes_);
+  EXPECT_TRUE(CurrentFullscreenFrameTreeNodeIsEmpty());
+
+  // Make the top page fullscreen.
+  {
+    FullscreenWebContentsObserver observer(web_contents, main_frame);
+    EXPECT_TRUE(
+        ExecuteScript(main_frame, "document.body.webkitRequestFullscreen();"));
+    observer.Wait();
+  }
+
+  nodes.insert(main_frame_id);
+  EXPECT_EQ(nodes, web_contents->fullscreen_frame_tree_nodes_);
+  EXPECT_EQ(main_frame_id,
+            web_contents->current_fullscreen_frame_tree_node_id_);
+
+  // Make the child frame fullscreen.
+  {
+    FullscreenWebContentsObserver observer(web_contents, child_frame);
+    EXPECT_TRUE(
+        ExecuteScript(child_frame, "document.body.webkitRequestFullscreen();"));
+    observer.Wait();
+  }
+
+  nodes.insert(child_frame_id);
+  EXPECT_EQ(nodes, web_contents->fullscreen_frame_tree_nodes_);
+  EXPECT_EQ(child_frame_id,
+            web_contents->current_fullscreen_frame_tree_node_id_);
+
+  // Exit fullscreen on the child frame.
+  // This will not work with --site-per-process until crbug.com/617369
+  // is fixed.
+  if (!SiteIsolationPolicy::UseDedicatedProcessesForAllSites()) {
+    {
+      FullscreenWebContentsObserver observer(web_contents, main_frame);
+      EXPECT_TRUE(
+          ExecuteScript(child_frame, "document.webkitExitFullscreen();"));
+      observer.Wait();
+    }
+
+    nodes.erase(child_frame_id);
+    EXPECT_EQ(nodes, web_contents->fullscreen_frame_tree_nodes_);
+    EXPECT_EQ(main_frame_id,
+              web_contents->current_fullscreen_frame_tree_node_id_);
+  }
+}
+
+IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
+                       NotifyFullscreenAcquired_Navigate) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  WebContentsImpl* web_contents =
+      static_cast<WebContentsImpl*>(shell()->web_contents());
+  TestWCDelegateForDialogsAndFullscreen test_delegate;
+  test_delegate.WillWaitForFullscreenExit();
+  web_contents->SetDelegate(&test_delegate);
+
+  GURL url = embedded_test_server()->GetURL(
+      "a.com", "/cross_site_iframe_factory.html?a(b{allowfullscreen})");
+  EXPECT_TRUE(NavigateToURL(shell(), url));
+  RenderFrameHost* main_frame = web_contents->GetMainFrame();
+  int main_frame_id = main_frame->GetFrameTreeNodeId();
+
+  RenderFrameHost* child_frame = ChildFrameAt(main_frame, 0);
+  int child_frame_id = child_frame->GetFrameTreeNodeId();
+
+  WebContentsImpl::FullscreenFrameNodes nodes;
+  EXPECT_EQ(nodes, web_contents->fullscreen_frame_tree_nodes_);
+  EXPECT_TRUE(CurrentFullscreenFrameTreeNodeIsEmpty());
+
+  // Make the top page fullscreen.
+  {
+    FullscreenWebContentsObserver observer(web_contents, main_frame);
+    EXPECT_TRUE(
+        ExecuteScript(main_frame, "document.body.webkitRequestFullscreen();"));
+    observer.Wait();
+  }
+
+  nodes.insert(main_frame_id);
+  EXPECT_EQ(nodes, web_contents->fullscreen_frame_tree_nodes_);
+  EXPECT_EQ(main_frame_id,
+            web_contents->current_fullscreen_frame_tree_node_id_);
+
+  // Make the child frame fullscreen.
+  {
+    FullscreenWebContentsObserver observer(web_contents, child_frame);
+    EXPECT_TRUE(
+        ExecuteScript(child_frame, "document.body.webkitRequestFullscreen();"));
+    observer.Wait();
+  }
+
+  nodes.insert(child_frame_id);
+  EXPECT_EQ(nodes, web_contents->fullscreen_frame_tree_nodes_);
+  EXPECT_EQ(child_frame_id,
+            web_contents->current_fullscreen_frame_tree_node_id_);
+
+  // Perform a cross origin navigation on the main frame.
+  EXPECT_TRUE(
+      NavigateToURL(shell(), embedded_test_server()->GetURL(
+                                 "c.com", "/cross_site_iframe_factory.html")));
+  EXPECT_EQ(0u, web_contents->fullscreen_frame_tree_nodes_.size());
+  EXPECT_TRUE(CurrentFullscreenFrameTreeNodeIsEmpty());
+}
+
+IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
+                       NotifyFullscreenAcquired_SameOrigin) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  WebContentsImpl* web_contents =
+      static_cast<WebContentsImpl*>(shell()->web_contents());
+  TestWCDelegateForDialogsAndFullscreen test_delegate;
+  web_contents->SetDelegate(&test_delegate);
+
+  GURL url = embedded_test_server()->GetURL(
+      "a.com", "/cross_site_iframe_factory.html?a(a{allowfullscreen})");
+  EXPECT_TRUE(NavigateToURL(shell(), url));
+  RenderFrameHost* main_frame = web_contents->GetMainFrame();
+  int main_frame_id = main_frame->GetFrameTreeNodeId();
+
+  RenderFrameHost* child_frame = ChildFrameAt(main_frame, 0);
+  int child_frame_id = child_frame->GetFrameTreeNodeId();
+
+  WebContentsImpl::FullscreenFrameNodes nodes;
+  EXPECT_EQ(nodes, web_contents->fullscreen_frame_tree_nodes_);
+  EXPECT_TRUE(CurrentFullscreenFrameTreeNodeIsEmpty());
+
+  // Make the top page fullscreen.
+  {
+    FullscreenWebContentsObserver observer(web_contents, main_frame);
+    EXPECT_TRUE(
+        ExecuteScript(main_frame, "document.body.webkitRequestFullscreen();"));
+    observer.Wait();
+  }
+
+  nodes.insert(main_frame_id);
+  EXPECT_EQ(nodes, web_contents->fullscreen_frame_tree_nodes_);
+  EXPECT_EQ(main_frame_id,
+            web_contents->current_fullscreen_frame_tree_node_id_);
+
+  // Make the child frame fullscreen.
+  {
+    FullscreenWebContentsObserver observer(web_contents, child_frame);
+    EXPECT_TRUE(
+        ExecuteScript(child_frame, "document.body.webkitRequestFullscreen();"));
+    observer.Wait();
+  }
+
+  nodes.insert(child_frame_id);
+  EXPECT_EQ(nodes, web_contents->fullscreen_frame_tree_nodes_);
+  EXPECT_EQ(child_frame_id,
+            web_contents->current_fullscreen_frame_tree_node_id_);
+
+  // Exit fullscreen on the child frame.
+  {
+    FullscreenWebContentsObserver observer(web_contents, main_frame);
+    EXPECT_TRUE(ExecuteScript(child_frame, "document.webkitExitFullscreen();"));
+    observer.Wait();
+  }
+
+  nodes.erase(child_frame_id);
+  EXPECT_EQ(nodes, web_contents->fullscreen_frame_tree_nodes_);
+  EXPECT_EQ(main_frame_id,
+            web_contents->current_fullscreen_frame_tree_node_id_);
+}
+
 }  // namespace content