1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "content/browser/renderer_host/render_widget_host_view_child_frame.h"
6 
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/macros.h"
10 #include "base/run_loop.h"
11 #include "base/test/bind_test_util.h"
12 #include "base/test/scoped_feature_list.h"
13 #include "base/test/test_timeouts.h"
14 #include "build/build_config.h"
15 #include "components/viz/common/surfaces/surface_id.h"
16 #include "content/browser/frame_host/frame_tree_node.h"
17 #include "content/browser/portal/portal.h"
18 #include "content/browser/renderer_host/render_process_host_impl.h"
19 #include "content/browser/web_contents/web_contents_impl.h"
20 #include "content/common/frame_messages.h"
21 #include "content/common/view_messages.h"
22 #include "content/common/widget_messages.h"
23 #include "content/public/browser/render_frame_host.h"
24 #include "content/public/browser/web_contents.h"
25 #include "content/public/test/browser_test_utils.h"
26 #include "content/public/test/content_browser_test.h"
27 #include "content/public/test/content_browser_test_utils.h"
28 #include "content/public/test/test_navigation_observer.h"
29 #include "content/public/test/test_utils.h"
30 #include "content/shell/browser/shell.h"
31 #include "content/test/content_browser_test_utils_internal.h"
32 #include "content/test/portal/portal_created_observer.h"
33 #include "content/test/test_content_browser_client.h"
34 #include "net/dns/mock_host_resolver.h"
35 #include "net/test/embedded_test_server/embedded_test_server.h"
36 #include "third_party/blink/public/common/features.h"
37 #include "ui/gfx/geometry/size.h"
38 
39 namespace content {
40 
41 class RenderWidgetHostViewChildFrameBrowserTest : public ContentBrowserTest {
42  public:
43   RenderWidgetHostViewChildFrameBrowserTest() = default;
44 
SetUpCommandLine(base::CommandLine * command_line)45   void SetUpCommandLine(base::CommandLine* command_line) override {
46     IsolateAllSitesForTesting(command_line);
47 
48     scoped_feature_list_.InitAndEnableFeature(blink::features::kPortals);
49   }
50 
SetUpOnMainThread()51   void SetUpOnMainThread() override {
52     host_resolver()->AddRule("*", "127.0.0.1");
53     SetupCrossSiteRedirector(embedded_test_server());
54     ASSERT_TRUE(embedded_test_server()->Start());
55   }
56 
57   // Tests that the FrameSinkId of each child frame has been updated by the
58   // RenderFrameProxy.
CheckFrameSinkId(RenderFrameHost * render_frame_host)59   void CheckFrameSinkId(RenderFrameHost* render_frame_host) {
60     RenderWidgetHostViewBase* child_view =
61         static_cast<RenderFrameHostImpl*>(render_frame_host)
62             ->GetRenderWidgetHost()
63             ->GetView();
64     // Only interested in updated FrameSinkIds on child frames.
65     if (!child_view || !child_view->IsRenderWidgetHostViewChildFrame())
66       return;
67 
68     // Ensure that the received viz::FrameSinkId was correctly set on the child
69     // frame.
70     viz::FrameSinkId actual_frame_sink_id_ = child_view->GetFrameSinkId();
71     EXPECT_EQ(expected_frame_sink_id_, actual_frame_sink_id_);
72 
73     // The viz::FrameSinkID will be replaced while the test blocks for
74     // navigation. It should differ from the information stored in the child's
75     // RenderWidgetHost.
76     EXPECT_NE(base::checked_cast<uint32_t>(
77                   child_view->GetRenderWidgetHost()->GetProcess()->GetID()),
78               actual_frame_sink_id_.client_id());
79     EXPECT_NE(base::checked_cast<uint32_t>(
80                   child_view->GetRenderWidgetHost()->GetRoutingID()),
81               actual_frame_sink_id_.sink_id());
82   }
83 
CreatePortalToUrl(WebContentsImpl * host_contents,GURL portal_url,int number_of_navigations=1)84   Portal* CreatePortalToUrl(WebContentsImpl* host_contents,
85                             GURL portal_url,
86                             int number_of_navigations = 1) {
87     EXPECT_GE(number_of_navigations, 1);
88     RenderFrameHostImpl* main_frame = host_contents->GetMainFrame();
89 
90     // Create portal and wait for navigation.
91     PortalCreatedObserver portal_created_observer(main_frame);
92     TestNavigationObserver navigation_observer(nullptr, number_of_navigations);
93     navigation_observer.set_wait_event(
94         TestNavigationObserver::WaitEvent::kNavigationFinished);
95     navigation_observer.StartWatchingNewWebContents();
96     EXPECT_TRUE(ExecJs(
97         main_frame, JsReplace("{"
98                               "  let portal = document.createElement('portal');"
99                               "  portal.src = $1;"
100                               "  portal.setAttribute('id', 'portal');"
101                               "  document.body.appendChild(portal);"
102                               "}",
103                               portal_url)));
104     Portal* portal = portal_created_observer.WaitUntilPortalCreated();
105     navigation_observer.StopWatchingNewWebContents();
106 
107     WebContentsImpl* portal_contents = portal->GetPortalContents();
108     EXPECT_TRUE(portal_contents);
109 
110     navigation_observer.WaitForNavigationFinished();
111     EXPECT_TRUE(WaitForLoadStop(portal_contents));
112 
113     return portal;
114   }
115 
GiveItSomeTime()116   void GiveItSomeTime() {
117     base::RunLoop run_loop;
118     base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
119         FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
120     run_loop.Run();
121   }
122 
set_expected_frame_sink_id(viz::FrameSinkId frame_sink_id)123   void set_expected_frame_sink_id(viz::FrameSinkId frame_sink_id) {
124     expected_frame_sink_id_ = frame_sink_id;
125   }
126 
127  private:
128   base::test::ScopedFeatureList scoped_feature_list_;
129   viz::FrameSinkId expected_frame_sink_id_;
130 
131   DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewChildFrameBrowserTest);
132 };
133 
134 // Tests that the screen is properly reflected for RWHVChildFrame.
IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewChildFrameBrowserTest,Screen)135 IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewChildFrameBrowserTest, Screen) {
136   GURL main_url(embedded_test_server()->GetURL("/site_per_process_main.html"));
137   EXPECT_TRUE(NavigateToURL(shell(), main_url));
138 
139   FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
140                             ->GetFrameTree()
141                             ->root();
142 
143   // Load cross-site page into iframe.
144   GURL cross_site_url(
145       embedded_test_server()->GetURL("foo.com", "/title2.html"));
146   NavigateFrameToURL(root->child_at(0), cross_site_url);
147 
148   int main_frame_screen_width =
149       ExecuteScriptAndGetValue(shell()->web_contents()->GetMainFrame(),
150                                "window.screen.width")
151           .GetInt();
152   EXPECT_NE(main_frame_screen_width, 0);
153 
154   auto check_screen_width = [&](RenderFrameHost* frame_host) {
155     int width =
156         ExecuteScriptAndGetValue(frame_host, "window.screen.width").GetInt();
157     EXPECT_EQ(width, main_frame_screen_width);
158   };
159   shell()->web_contents()->ForEachFrame(
160       base::BindLambdaForTesting(check_screen_width));
161 }
162 
163 class OutgoingVisualPropertiesIPCWatcher {
164  public:
OutgoingVisualPropertiesIPCWatcher(RenderProcessHostImpl * rph,FrameTreeNode * root,base::RepeatingCallback<void (const VisualProperties &)> callback)165   OutgoingVisualPropertiesIPCWatcher(
166       RenderProcessHostImpl* rph,
167       FrameTreeNode* root,
168       base::RepeatingCallback<void(const VisualProperties&)> callback)
169       : rph_(rph), root_(root), callback_(std::move(callback)) {
170     rph_->SetIpcSendWatcherForTesting(
171         base::BindRepeating(&OutgoingVisualPropertiesIPCWatcher::OnMessage,
172                             base::Unretained(this)));
173   }
~OutgoingVisualPropertiesIPCWatcher()174   ~OutgoingVisualPropertiesIPCWatcher() {
175     rph_->SetIpcSendWatcherForTesting(base::NullCallback());
176   }
177 
178  private:
IsMessageForFrameTreeWidget(int routing_id,FrameTreeNode * node)179   bool IsMessageForFrameTreeWidget(int routing_id, FrameTreeNode* node) {
180     auto* render_widget_host =
181         node->current_frame_host()->GetRenderWidgetHost();
182     if (routing_id == render_widget_host->GetRoutingID())
183       return true;
184     for (size_t i = 0; i < node->child_count(); ++i) {
185       if (IsMessageForFrameTreeWidget(routing_id, node->child_at(i)))
186         return true;
187     }
188     return false;
189   }
OnMessage(const IPC::Message & message)190   void OnMessage(const IPC::Message& message) {
191     if (!IsMessageForFrameTreeWidget(message.routing_id(), root_))
192       return;
193     IPC_BEGIN_MESSAGE_MAP(OutgoingVisualPropertiesIPCWatcher, message)
194       IPC_MESSAGE_HANDLER(WidgetMsg_UpdateVisualProperties, ProcessMessage)
195     IPC_END_MESSAGE_MAP()
196   }
197 
ProcessMessage(const VisualProperties & props)198   void ProcessMessage(const VisualProperties& props) { callback_.Run(props); }
199 
200   RenderProcessHostImpl* const rph_;
201   FrameTreeNode* const root_;
202   base::RepeatingCallback<void(const VisualProperties&)> callback_;
203 };
204 
205 // Auto-resize is only implemented for Ash and GuestViews. So we need to inject
206 // an implementation that actually resizes the top level widget.
207 class AutoResizeWebContentsDelegate : public WebContentsDelegate {
ResizeDueToAutoResize(WebContents * web_contents,const gfx::Size & new_size)208   void ResizeDueToAutoResize(WebContents* web_contents,
209                              const gfx::Size& new_size) override {
210     RenderWidgetHostView* view =
211         web_contents->GetTopLevelRenderWidgetHostView();
212     view->SetSize(new_size);
213   }
214 };
215 
216 // Test that the |visible_viewport_size| from the top frame is propagated to all
217 // local roots (aka RenderWidgets):
218 // a) Initially upon adding the child frame (we create this scenario by
219 // navigating a child on b.com to c.com, where we already have a RenderProcess
220 // active for c.com).
221 // b) When resizing the top level widget.
222 // c) When auto-resize is enabled for the top level main frame and the renderer
223 // resizes the top level widget.
224 // d) When auto-resize is enabled for the nested main frame and the renderer
225 // resizes the nested widget.
226 // See https://crbug.com/726743 and https://crbug.com/1050635.
IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewChildFrameBrowserTest,VisualPropertiesPropagation_VisibleViewportSize)227 IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewChildFrameBrowserTest,
228                        VisualPropertiesPropagation_VisibleViewportSize) {
229   GURL main_url(embedded_test_server()->GetURL(
230       "a.com", "/cross_site_iframe_factory.html?a(b,c)"));
231   EXPECT_TRUE(NavigateToURL(shell(), main_url));
232 
233   auto* web_contents = static_cast<WebContentsImpl*>(shell()->web_contents());
234   FrameTreeNode* root = web_contents->GetFrameTree()->root();
235   RenderWidgetHostView* root_view =
236       root->current_frame_host()->GetRenderWidgetHost()->GetView();
237 
238   Portal* portal = CreatePortalToUrl(web_contents, main_url);
239   WebContentsImpl* nested_contents = portal->GetPortalContents();
240   FrameTreeNode* nested_root = nested_contents->GetFrameTree()->root();
241   RenderWidgetHostView* nested_root_view =
242       nested_root->current_frame_host()->GetRenderWidgetHost()->GetView();
243 
244   // Watch processes for a.com and c.com, as we will throw away b.com when we
245   // navigate it below.
246   auto* root_rph = static_cast<RenderProcessHostImpl*>(
247       root->current_frame_host()->GetProcess());
248   auto* child_rph = static_cast<RenderProcessHostImpl*>(
249       root->child_at(1)->current_frame_host()->GetProcess());
250   ASSERT_NE(root_rph, child_rph);
251 
252   auto* nested_root_rph = static_cast<RenderProcessHostImpl*>(
253       nested_root->current_frame_host()->GetProcess());
254   auto* nested_child_rph = static_cast<RenderProcessHostImpl*>(
255       nested_root->child_at(1)->current_frame_host()->GetProcess());
256   ASSERT_NE(nested_root_rph, nested_child_rph);
257 
258   const gfx::Size initial_size = root_view->GetVisibleViewportSize();
259   const gfx::Size nested_initial_size =
260       nested_root_view->GetVisibleViewportSize();
261   ASSERT_NE(initial_size, nested_initial_size);
262 
263   // We should see the top level widget's size in the visible_viewport_size
264   // in both local roots. When a child local root is added in the parent
265   // renderer, the value is propagated down through the browser to the child
266   // renderer's RenderWidget.
267   //
268   // This property is not directly visible in the renderer, so we can only
269   // check that the value is sent to the appropriate RenderWidget.
270   {
271     base::RunLoop loop;
272 
273     gfx::Size child_visible_viewport_size;
274     OutgoingVisualPropertiesIPCWatcher child_watcher(
275         child_rph, root,
276         base::BindLambdaForTesting([&](const VisualProperties& props) {
277           child_visible_viewport_size = props.visible_viewport_size;
278 
279           if (child_visible_viewport_size == initial_size)
280             loop.Quit();
281         }));
282 
283     GURL cross_site_url(
284         embedded_test_server()->GetURL("c.com", "/title2.html"));
285     NavigateFrameToURL(root->child_at(0), cross_site_url);
286 
287     // Wait to see the size sent to the child RenderWidget.
288     loop.Run();
289 
290     // The child widget was also informed of the same size.
291     EXPECT_EQ(initial_size, child_visible_viewport_size);
292   }
293 
294   // Same check as abvoe but for a nested WebContents.
295   {
296     base::RunLoop loop;
297 
298     gfx::Size child_visible_viewport_size;
299     OutgoingVisualPropertiesIPCWatcher child_watcher(
300         nested_child_rph, nested_root,
301         base::BindLambdaForTesting([&](const VisualProperties& props) {
302           child_visible_viewport_size = props.visible_viewport_size;
303 
304           if (child_visible_viewport_size == nested_initial_size)
305             loop.Quit();
306         }));
307 
308     GURL cross_site_url(
309         embedded_test_server()->GetURL("c.com", "/title2.html"));
310     NavigateFrameToURL(nested_root->child_at(0), cross_site_url);
311 
312     // Wait to see the size sent to the child RenderWidget.
313     loop.Run();
314 
315     // The child widget was also informed of the same size.
316     EXPECT_EQ(nested_initial_size, child_visible_viewport_size);
317   }
318 
319 // This part of the test does not work well on Android, for a few reasons:
320 // 1. RenderWidgetHostViewAndroid can not be resized, the Java objects need to
321 // be resized somehow through ui::ViewAndroid.
322 // 2. AutoResize on Android does not size to the min/max bounds specified, it
323 // ends up ignoring them and sizing to the screen (I think).
324 // Luckily this test is verifying interactions and behaviour of
325 // RenderWidgetHostImpl - RenderWidget - RenderFrameProxy -
326 // CrossProcessFrameConnector, and this isn't Android-specific code.
327 #if !defined(OS_ANDROID)
328 
329   // Resize the top level widget to cause its |visible_viewport_size| to be
330   // changed. The change should propagate down to the child RenderWidget.
331   {
332     base::RunLoop loop;
333 
334     const gfx::Size resize_to(initial_size.width() - 10,
335                               initial_size.height() - 10);
336 
337     gfx::Size root_visible_viewport_size;
338     gfx::Size child_visible_viewport_size;
339     OutgoingVisualPropertiesIPCWatcher root_watcher(
340         root_rph, root,
341         base::BindLambdaForTesting([&](const VisualProperties& props) {
342           root_visible_viewport_size = props.visible_viewport_size;
343         }));
344     OutgoingVisualPropertiesIPCWatcher child_watcher(
345         child_rph, root,
346         base::BindLambdaForTesting([&](const VisualProperties& props) {
347           child_visible_viewport_size = props.visible_viewport_size;
348 
349           if (child_visible_viewport_size == resize_to)
350             loop.Quit();
351         }));
352 
353     root_view->SetSize(resize_to);
354 
355     // Wait to see both RenderWidgets receive the message.
356     loop.Run();
357 
358     // The top level widget was resized.
359     EXPECT_EQ(resize_to, root_visible_viewport_size);
360     // The child widget was also informed of the same size.
361     EXPECT_EQ(resize_to, child_visible_viewport_size);
362   }
363 
364   // Same check as above but resizing the nested WebContents' main frame
365   // instead.
366   // Resize the top level widget to cause its |visible_viewport_size| to be
367   // changed. The change should propagate down to the child RenderWidget.
368   {
369     base::RunLoop loop;
370 
371     const gfx::Size resize_to(nested_initial_size.width() - 10,
372                               nested_initial_size.height() - 10);
373 
374     gfx::Size root_visible_viewport_size;
375     gfx::Size child_visible_viewport_size;
376     OutgoingVisualPropertiesIPCWatcher root_watcher(
377         nested_root_rph, nested_root,
378         base::BindLambdaForTesting([&](const VisualProperties& props) {
379           root_visible_viewport_size = props.visible_viewport_size;
380         }));
381     OutgoingVisualPropertiesIPCWatcher child_watcher(
382         nested_child_rph, nested_root,
383         base::BindLambdaForTesting([&](const VisualProperties& props) {
384           child_visible_viewport_size = props.visible_viewport_size;
385 
386           if (child_visible_viewport_size == resize_to)
387             loop.Quit();
388         }));
389 
390     EXPECT_TRUE(ExecJs(
391         root->current_frame_host(),
392         JsReplace("document.getElementById('portal').style.width = '$1px';"
393                   "document.getElementById('portal').style.height = '$2px';",
394                   resize_to.width(), resize_to.height())));
395 
396     // Wait to see both RenderWidgets receive the message.
397     loop.Run();
398 
399     // The top level widget was resized.
400     EXPECT_EQ(resize_to, root_visible_viewport_size);
401     // The child widget was also informed of the same size.
402     EXPECT_EQ(resize_to, child_visible_viewport_size);
403   }
404 
405   // Informs the top-level frame it can auto-resize. It will shrink down to its
406   // minimum window size based on the empty content we've loaded, which is
407   // 100x100.
408   //
409   // When the renderer resizes, thanks to our AutoResizeWebContentsDelegate
410   // the top level widget will resize. That size will be reflected in the
411   // visible_viewport_size which is sent back through the top level RenderWidget
412   // to propagte down to child local roots.
413   //
414   // This property is not directly visible in the renderer, so we can only
415   // check that the value is sent to both RenderWidgets.
416   {
417     base::RunLoop loop;
418 
419     const gfx::Size auto_resize_to(105, 100);
420 
421     gfx::Size root_visible_viewport_size;
422     gfx::Size child_visible_viewport_size;
423     OutgoingVisualPropertiesIPCWatcher root_watcher(
424         root_rph, root,
425         base::BindLambdaForTesting([&](const VisualProperties& props) {
426           root_visible_viewport_size = props.visible_viewport_size;
427         }));
428     OutgoingVisualPropertiesIPCWatcher child_watcher(
429         child_rph, root,
430         base::BindLambdaForTesting([&](const VisualProperties& props) {
431           child_visible_viewport_size = props.visible_viewport_size;
432 
433           if (child_visible_viewport_size == auto_resize_to)
434             loop.Quit();
435         }));
436 
437     // Replace the WebContentsDelegate so that we can use the auto-resize
438     // changes to adjust the size of the top widget.
439     WebContentsDelegate* old_delegate = shell()->web_contents()->GetDelegate();
440     AutoResizeWebContentsDelegate resize_delegate;
441     shell()->web_contents()->SetDelegate(&resize_delegate);
442 
443     root_view->EnableAutoResize(auto_resize_to, auto_resize_to);
444 
445     // Wait for the renderer side to resize itself and the RenderWidget
446     // waterfall to pass the new |visible_viewport_size| down.
447     loop.Run();
448 
449     // The top level widget was resized to match the auto-resized renderer.
450     EXPECT_EQ(auto_resize_to, root_visible_viewport_size);
451     // The child widget was also informed of the same size.
452     EXPECT_EQ(auto_resize_to, child_visible_viewport_size);
453 
454     shell()->web_contents()->SetDelegate(old_delegate);
455   }
456 
457   // TODO(danakj): We'd like to run the same check as above but tell the main
458   // frame of the nested WebContents that it can auto-resize. However this seems
459   // to get through to the main frame's RenderWidget and propagate correctly but
460   // no size change occurs in the renderer.
461 #endif
462 }
463 
464 // Validate that OOPIFs receive presentation feedbacks.
IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewChildFrameBrowserTest,PresentationFeedback)465 IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewChildFrameBrowserTest,
466                        PresentationFeedback) {
467   base::HistogramTester histogram_tester;
468   GURL main_url(embedded_test_server()->GetURL("/site_per_process_main.html"));
469   EXPECT_TRUE(NavigateToURL(shell(), main_url));
470 
471   FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
472                             ->GetFrameTree()
473                             ->root();
474   // Load cross-site page into iframe.
475   GURL cross_site_url(
476       embedded_test_server()->GetURL("foo.com", "/title2.html"));
477   NavigateFrameToURL(root->child_at(0), cross_site_url);
478 
479   auto* child_rwh_impl =
480       root->child_at(0)->current_frame_host()->GetRenderWidgetHost();
481   // Hide the frame and make it visible again, to force it to record the
482   // tab-switch time, which is generated from presentation-feedback.
483   child_rwh_impl->WasHidden();
484   child_rwh_impl->WasShown(RecordContentToVisibleTimeRequest{
485       base::TimeTicks::Now(), /* destination_is_loaded */ true,
486       /* destination_is_frozen */ false, /* show_reason_tab_switching */ true,
487       /* show_reason_unoccluded */ false,
488       /* show_reason_bfcache_restore */ false});
489   // Force the child to submit a new frame.
490   ASSERT_TRUE(ExecuteScript(root->child_at(0)->current_frame_host(),
491                             "document.write('Force a new frame.');"));
492   do {
493     FetchHistogramsFromChildProcesses();
494     GiveItSomeTime();
495   } while (histogram_tester.GetAllSamples("MPArch.RWH_TabSwitchPaintDuration")
496                .size() != 1);
497 }
498 
499 }  // namespace content
500