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