1 // Copyright 2018 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 <memory>
6
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/callback.h"
10 #include "base/memory/ptr_util.h"
11 #include "base/test/bind_test_util.h"
12 #include "base/test/scoped_feature_list.h"
13 #include "build/build_config.h"
14 #include "components/viz/host/host_frame_sink_manager.h"
15 #include "content/browser/compositor/surface_utils.h"
16 #include "content/browser/frame_host/render_frame_host_impl.h"
17 #include "content/browser/frame_host/render_frame_host_manager.h"
18 #include "content/browser/frame_host/render_frame_proxy_host.h"
19 #include "content/browser/portal/portal.h"
20 #include "content/browser/renderer_host/input/synthetic_smooth_scroll_gesture.h"
21 #include "content/browser/renderer_host/input/synthetic_tap_gesture.h"
22 #include "content/browser/renderer_host/render_widget_host_input_event_router.h"
23 #include "content/browser/renderer_host/render_widget_host_view_base.h"
24 #include "content/browser/renderer_host/render_widget_host_view_child_frame.h"
25 #include "content/browser/web_contents/web_contents_impl.h"
26 #include "content/common/frame.mojom-test-utils.h"
27 #include "content/public/browser/site_isolation_policy.h"
28 #include "content/public/browser/web_contents_delegate.h"
29 #include "content/public/common/content_switches.h"
30 #include "content/public/test/accessibility_notification_waiter.h"
31 #include "content/public/test/browser_test_utils.h"
32 #include "content/public/test/content_browser_test.h"
33 #include "content/public/test/content_browser_test_utils.h"
34 #include "content/public/test/hit_test_region_observer.h"
35 #include "content/public/test/navigation_handle_observer.h"
36 #include "content/public/test/test_navigation_observer.h"
37 #include "content/shell/browser/shell.h"
38 #include "content/test/content_browser_test_utils_internal.h"
39 #include "content/test/portal/portal_activated_observer.h"
40 #include "content/test/portal/portal_created_observer.h"
41 #include "content/test/portal/portal_interceptor_for_testing.h"
42 #include "content/test/test_render_frame_host_factory.h"
43 #include "net/dns/mock_host_resolver.h"
44 #include "net/test/embedded_test_server/embedded_test_server.h"
45 #include "testing/gmock/include/gmock/gmock.h"
46 #include "testing/gtest/include/gtest/gtest.h"
47 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
48 #include "third_party/blink/public/common/features.h"
49 #include "third_party/blink/public/mojom/input/focus_type.mojom.h"
50 #include "third_party/blink/public/mojom/portal/portal.mojom.h"
51 #include "url/url_constants.h"
52
53 using testing::_;
54
55 namespace content {
56
57 class PortalBrowserTest : public ContentBrowserTest {
58 protected:
PortalBrowserTest()59 PortalBrowserTest() {}
60
SetUp()61 void SetUp() override {
62 scoped_feature_list_.InitAndEnableFeature(blink::features::kPortals);
63 ContentBrowserTest::SetUp();
64 }
65
SetUpCommandLine(base::CommandLine * command_line)66 void SetUpCommandLine(base::CommandLine* command_line) override {
67 command_line->AppendSwitch(switches::kValidateInputEventStream);
68 command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures,
69 "OverscrollCustomization");
70 }
71
SetUpOnMainThread()72 void SetUpOnMainThread() override {
73 host_resolver()->AddRule("*", "127.0.0.1");
74 ContentBrowserTest::SetUpOnMainThread();
75 ASSERT_TRUE(embedded_test_server()->Start());
76 }
77
CreatePortalToUrl(WebContentsImpl * host_contents,GURL portal_url,int number_of_navigations=1)78 Portal* CreatePortalToUrl(WebContentsImpl* host_contents,
79 GURL portal_url,
80 int number_of_navigations = 1) {
81 EXPECT_GE(number_of_navigations, 1);
82 RenderFrameHostImpl* main_frame = host_contents->GetMainFrame();
83
84 // Create portal and wait for navigation.
85 PortalCreatedObserver portal_created_observer(main_frame);
86 TestNavigationObserver navigation_observer(nullptr, number_of_navigations);
87 navigation_observer.set_wait_event(
88 TestNavigationObserver::WaitEvent::kNavigationFinished);
89 navigation_observer.StartWatchingNewWebContents();
90 EXPECT_TRUE(
91 ExecJs(main_frame,
92 JsReplace("{"
93 " let portal = document.createElement('portal');"
94 " portal.src = $1;"
95 " document.body.appendChild(portal);"
96 "}",
97 portal_url),
98 EXECUTE_SCRIPT_NO_USER_GESTURE));
99 Portal* portal = portal_created_observer.WaitUntilPortalCreated();
100 navigation_observer.StopWatchingNewWebContents();
101
102 WebContentsImpl* portal_contents = portal->GetPortalContents();
103 EXPECT_TRUE(portal_contents);
104
105 navigation_observer.WaitForNavigationFinished();
106 EXPECT_TRUE(WaitForLoadStop(portal_contents));
107
108 return portal;
109 }
110
111 private:
112 base::test::ScopedFeatureList scoped_feature_list_;
113 };
114
115 // Tests that the renderer can create a Portal.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,CreatePortal)116 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, CreatePortal) {
117 EXPECT_TRUE(NavigateToURL(
118 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
119 WebContentsImpl* web_contents_impl =
120 static_cast<WebContentsImpl*>(shell()->web_contents());
121 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
122
123 PortalCreatedObserver portal_created_observer(main_frame);
124 EXPECT_TRUE(
125 ExecJs(main_frame,
126 "document.body.appendChild(document.createElement('portal'));"));
127 Portal* portal = portal_created_observer.WaitUntilPortalCreated();
128 EXPECT_NE(nullptr, portal);
129 }
130
131 // This fixture enables CompositeCrossOriginIframes for the following regression
132 // test. Since we're testing stability and not behaviour specific to this flag,
133 // this fixture and its test may be removed upon removal of the
134 // CompositeCrossOriginIframes flag.
135 class PortalCompositeCrossOriginIframesBrowserTest : public PortalBrowserTest {
136 protected:
SetUpCommandLine(base::CommandLine * command_line)137 void SetUpCommandLine(base::CommandLine* command_line) override {
138 PortalBrowserTest::SetUpCommandLine(command_line);
139 scoped_feature_list_.InitAndEnableFeature(
140 blink::features::kCompositeCrossOriginIframes);
141 }
142
143 private:
144 base::test::ScopedFeatureList scoped_feature_list_;
145 };
146
147 // Regression test for https://crbug.com/1028269
148 // Tests that the renderer can create a portal without crashing when
149 // CompositeCrossOriginIframes is enabled.
IN_PROC_BROWSER_TEST_F(PortalCompositeCrossOriginIframesBrowserTest,CreatePortal)150 IN_PROC_BROWSER_TEST_F(PortalCompositeCrossOriginIframesBrowserTest,
151 CreatePortal) {
152 EXPECT_TRUE(NavigateToURL(
153 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
154 WebContentsImpl* web_contents_impl =
155 static_cast<WebContentsImpl*>(shell()->web_contents());
156 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
157
158 GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
159 Portal* portal = CreatePortalToUrl(web_contents_impl, a_url);
160 EXPECT_NE(nullptr, portal);
161
162 // Ensure that the renderer is still alive.
163 EXPECT_EQ(true, EvalJs(main_frame, "true"));
164 }
165
166 // Tests the the renderer can navigate a Portal.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,NavigatePortal)167 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, NavigatePortal) {
168 EXPECT_TRUE(NavigateToURL(
169 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
170 WebContentsImpl* web_contents_impl =
171 static_cast<WebContentsImpl*>(shell()->web_contents());
172 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
173
174 // Tests that a portal can navigate by setting its src before appending it to
175 // the DOM.
176 GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
177 Portal* portal = CreatePortalToUrl(web_contents_impl, a_url);
178 WebContents* portal_contents = portal->GetPortalContents();
179 EXPECT_EQ(portal_contents->GetLastCommittedURL(), a_url);
180
181 // Tests that a portal can navigate by setting its src.
182 {
183 TestNavigationObserver navigation_observer(portal_contents);
184
185 GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
186 EXPECT_TRUE(
187 ExecJs(main_frame,
188 JsReplace("document.querySelector('portal').src = $1;", b_url)));
189 navigation_observer.Wait();
190 EXPECT_EQ(navigation_observer.last_navigation_url(), b_url);
191 EXPECT_EQ(portal_contents->GetLastCommittedURL(), b_url);
192 }
193
194 // Tests that a portal can navigate by setting the attribute src.
195 {
196 TestNavigationObserver navigation_observer(portal_contents);
197
198 GURL c_url(embedded_test_server()->GetURL("c.com", "/title1.html"));
199 EXPECT_TRUE(ExecJs(
200 main_frame,
201 JsReplace("document.querySelector('portal').setAttribute('src', $1);",
202 c_url)));
203 navigation_observer.Wait();
204 EXPECT_EQ(navigation_observer.last_navigation_url(), c_url);
205 EXPECT_EQ(portal_contents->GetLastCommittedURL(), c_url);
206 }
207 }
208
209 // Tests that a portal can be activated.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,ActivatePortal)210 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, ActivatePortal) {
211 EXPECT_TRUE(NavigateToURL(
212 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
213 WebContentsImpl* web_contents_impl =
214 static_cast<WebContentsImpl*>(shell()->web_contents());
215 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
216
217 GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
218 Portal* portal = CreatePortalToUrl(web_contents_impl, a_url);
219
220 // Ensure that the portal WebContents exists and is different from the tab's
221 // WebContents.
222 WebContents* portal_contents = portal->GetPortalContents();
223 EXPECT_NE(nullptr, portal_contents);
224 EXPECT_NE(portal_contents, shell()->web_contents());
225
226 PortalActivatedObserver activated_observer(portal);
227 ExecuteScriptAsync(main_frame,
228 "document.querySelector('portal').activate();");
229 activated_observer.WaitForActivate();
230
231 // After activation, the shell's WebContents should be the previous portal's
232 // WebContents.
233 EXPECT_EQ(portal_contents, shell()->web_contents());
234
235 EXPECT_EQ(blink::mojom::PortalActivateResult::kPredecessorWillUnload,
236 activated_observer.WaitForActivateResult());
237 }
238
239 // Tests if a portal can be activated and the predecessor can be adopted.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,AdoptPredecessor)240 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, AdoptPredecessor) {
241 EXPECT_TRUE(NavigateToURL(
242 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
243 WebContentsImpl* web_contents_impl =
244 static_cast<WebContentsImpl*>(shell()->web_contents());
245 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
246
247 GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
248 Portal* portal = CreatePortalToUrl(web_contents_impl, a_url);
249
250 // Ensure that the portal WebContents exists and is different from the tab's
251 // WebContents.
252 WebContentsImpl* portal_contents = portal->GetPortalContents();
253 EXPECT_NE(nullptr, portal_contents);
254 EXPECT_NE(portal_contents, shell()->web_contents());
255
256 RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
257 EXPECT_TRUE(ExecJs(portal_frame,
258 "window.addEventListener('portalactivate', e => { "
259 " var portal = e.adoptPredecessor(); "
260 " document.body.appendChild(portal); "
261 "});"));
262
263 {
264 PortalActivatedObserver activated_observer(portal);
265 PortalCreatedObserver adoption_observer(portal_frame);
266 EXPECT_TRUE(ExecJs(main_frame,
267 "let portal = document.querySelector('portal');"
268 "portal.activate().then(() => { "
269 " document.body.removeChild(portal); "
270 "});"));
271 EXPECT_EQ(blink::mojom::PortalActivateResult::kPredecessorWasAdopted,
272 activated_observer.WaitForActivateResult());
273 adoption_observer.WaitUntilPortalCreated();
274 }
275 // After activation, the shell's WebContents should be the previous portal's
276 // WebContents.
277 EXPECT_EQ(portal_contents, shell()->web_contents());
278 // The original predecessor WebContents should be adopted as a portal.
279 EXPECT_TRUE(web_contents_impl->IsPortal());
280 EXPECT_EQ(web_contents_impl->GetOuterWebContents(), portal_contents);
281 }
282
283 // Tests that the RenderFrameProxyHost is created and initialized when the
284 // portal is initialized.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,RenderFrameProxyHostCreated)285 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, RenderFrameProxyHostCreated) {
286 EXPECT_TRUE(NavigateToURL(
287 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
288 WebContentsImpl* web_contents_impl =
289 static_cast<WebContentsImpl*>(shell()->web_contents());
290
291 GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
292 Portal* portal = CreatePortalToUrl(web_contents_impl, a_url);
293 WebContentsImpl* portal_contents = portal->GetPortalContents();
294 RenderFrameProxyHost* proxy_host = portal_contents->GetFrameTree()
295 ->root()
296 ->render_manager()
297 ->GetProxyToOuterDelegate();
298 EXPECT_TRUE(proxy_host->is_render_frame_proxy_live());
299 }
300
301 // Tests that the portal's outer delegate frame tree node and any iframes
302 // inside the portal are deleted when the portal element is removed from the
303 // document.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,DetachPortal)304 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, DetachPortal) {
305 EXPECT_TRUE(NavigateToURL(
306 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
307 WebContentsImpl* web_contents =
308 static_cast<WebContentsImpl*>(shell()->web_contents());
309 RenderFrameHostImpl* main_frame = web_contents->GetMainFrame();
310
311 GURL a_url(embedded_test_server()->GetURL(
312 "a.com", "/cross_site_iframe_factory.html?a(a)"));
313 // Wait for a second navigation for the inner iframe.
314 Portal* portal = CreatePortalToUrl(web_contents, a_url, 2);
315
316 WebContentsImpl* portal_contents = portal->GetPortalContents();
317 FrameTreeNode* portal_main_frame_node =
318 portal_contents->GetFrameTree()->root();
319
320 // Remove portal from document and wait for frames to be deleted.
321 FrameDeletedObserver fdo1(portal_main_frame_node->render_manager()
322 ->GetOuterDelegateNode()
323 ->current_frame_host());
324 FrameDeletedObserver fdo2(
325 portal_main_frame_node->child_at(0)->current_frame_host());
326 EXPECT_TRUE(
327 ExecJs(main_frame,
328 "document.body.removeChild(document.querySelector('portal'));"));
329 fdo1.Wait();
330 fdo2.Wait();
331 }
332
333 // This is for testing how portals interact with input hit testing. It is
334 // parameterized on the kind of viz hit testing used.
335 class PortalHitTestBrowserTest : public PortalBrowserTest {
336 protected:
SetUpCommandLine(base::CommandLine * command_line)337 void SetUpCommandLine(base::CommandLine* command_line) override {
338 PortalBrowserTest::SetUpCommandLine(command_line);
339 IsolateAllSitesForTesting(command_line);
340 }
341 };
342
343 namespace {
344
345 // Fails the test if an input event is sent to the given RenderWidgetHost.
346 class FailOnInputEvent : public RenderWidgetHost::InputEventObserver {
347 public:
FailOnInputEvent(RenderWidgetHostImpl * rwh)348 explicit FailOnInputEvent(RenderWidgetHostImpl* rwh)
349 : rwh_(rwh->GetWeakPtr()) {
350 rwh->AddInputEventObserver(this);
351 }
352
~FailOnInputEvent()353 ~FailOnInputEvent() override {
354 if (rwh_)
355 rwh_->RemoveInputEventObserver(this);
356 }
357
OnInputEvent(const blink::WebInputEvent & event)358 void OnInputEvent(const blink::WebInputEvent& event) override {
359 FAIL() << "Unexpected " << blink::WebInputEvent::GetName(event.GetType());
360 }
361
362 private:
363 base::WeakPtr<RenderWidgetHostImpl> rwh_;
364 };
365
366 } // namespace
367
368 // Tests that input events targeting the portal are only received by the parent
369 // renderer.
IN_PROC_BROWSER_TEST_F(PortalHitTestBrowserTest,DispatchInputEvent)370 IN_PROC_BROWSER_TEST_F(PortalHitTestBrowserTest, DispatchInputEvent) {
371 EXPECT_TRUE(NavigateToURL(
372 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
373 WebContentsImpl* web_contents_impl =
374 static_cast<WebContentsImpl*>(shell()->web_contents());
375 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
376
377 GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
378 Portal* portal = CreatePortalToUrl(web_contents_impl, a_url);
379 WebContentsImpl* portal_contents = portal->GetPortalContents();
380 RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
381 EXPECT_TRUE(static_cast<RenderWidgetHostViewBase*>(portal_frame->GetView())
382 ->IsRenderWidgetHostViewChildFrame());
383 RenderWidgetHostViewChildFrame* portal_view =
384 static_cast<RenderWidgetHostViewChildFrame*>(portal_frame->GetView());
385 WaitForHitTestData(portal_frame);
386
387 FailOnInputEvent no_input_to_portal_frame(
388 portal_frame->GetRenderWidgetHost());
389 EXPECT_TRUE(ExecJs(
390 main_frame,
391 "var clicked = false;"
392 "document.querySelector('portal').onmousedown = _ => clicked = true;"));
393 EXPECT_TRUE(ExecJs(portal_frame,
394 "var clicked = false;"
395 "document.body.onmousedown = _ => clicked = true;"));
396 EXPECT_EQ(false, EvalJs(main_frame, "clicked"));
397 EXPECT_EQ(false, EvalJs(portal_frame, "clicked"));
398
399 // Route the mouse event.
400 gfx::Point root_location =
401 portal_view->TransformPointToRootCoordSpace(gfx::Point(5, 5));
402 InputEventAckWaiter waiter(main_frame->GetRenderWidgetHost(),
403 blink::WebInputEvent::kMouseDown);
404 SimulateRoutedMouseEvent(web_contents_impl, blink::WebInputEvent::kMouseDown,
405 blink::WebPointerProperties::Button::kLeft,
406 root_location);
407 waiter.Wait();
408
409 // Check that the click event was only received by the main frame.
410 EXPECT_EQ(true, EvalJs(main_frame, "clicked"));
411 EXPECT_EQ(false, EvalJs(portal_frame, "clicked"));
412 }
413
414 // Tests that input events performed over on OOPIF inside a portal are targeted
415 // to the portal's parent.
IN_PROC_BROWSER_TEST_F(PortalHitTestBrowserTest,NoInputToOOPIFInPortal)416 IN_PROC_BROWSER_TEST_F(PortalHitTestBrowserTest, NoInputToOOPIFInPortal) {
417 EXPECT_TRUE(NavigateToURL(
418 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
419 WebContentsImpl* web_contents_impl =
420 static_cast<WebContentsImpl*>(shell()->web_contents());
421 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
422
423 // Create portal and wait for navigation.
424 // In the case of crbug.com/1002228 , this does not appear to reproduce if the
425 // portal element is too small, so we give it an explicit size.
426 Portal* portal = nullptr;
427 {
428 PortalCreatedObserver portal_created_observer(main_frame);
429 GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
430 TestNavigationObserver navigation_observer(a_url);
431 navigation_observer.StartWatchingNewWebContents();
432 EXPECT_TRUE(ExecJs(
433 main_frame, JsReplace("var portal = document.createElement('portal');"
434 "portal.src = $1;"
435 "portal.style.width = '500px';"
436 "portal.style.height = '500px';"
437 "portal.style.border = 'solid';"
438 "document.body.appendChild(portal);",
439 a_url)));
440 portal = portal_created_observer.WaitUntilPortalCreated();
441 navigation_observer.Wait();
442 }
443 WebContentsImpl* portal_contents = portal->GetPortalContents();
444 RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
445 WaitForHitTestData(portal_frame);
446
447 // Add an out-of-process iframe to the portal.
448 GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
449 TestNavigationObserver iframe_navigation_observer(portal_contents);
450 EXPECT_TRUE(ExecJs(portal_frame,
451 JsReplace("var iframe = document.createElement('iframe');"
452 "iframe.src = $1;"
453 "document.body.appendChild(iframe);",
454 b_url)));
455 iframe_navigation_observer.Wait();
456 EXPECT_EQ(b_url, iframe_navigation_observer.last_navigation_url());
457 RenderFrameHostImpl* portal_iframe =
458 portal_frame->child_at(0)->current_frame_host();
459 EXPECT_TRUE(static_cast<RenderWidgetHostViewBase*>(portal_iframe->GetView())
460 ->IsRenderWidgetHostViewChildFrame());
461 RenderWidgetHostViewChildFrame* oopif_view =
462 static_cast<RenderWidgetHostViewChildFrame*>(portal_iframe->GetView());
463 EXPECT_NE(portal_frame->GetSiteInstance(), portal_iframe->GetSiteInstance());
464 WaitForHitTestData(portal_iframe);
465
466 FailOnInputEvent no_input_to_portal_frame(
467 portal_frame->GetRenderWidgetHost());
468 FailOnInputEvent no_input_to_oopif(portal_iframe->GetRenderWidgetHost());
469 EXPECT_TRUE(ExecJs(main_frame,
470 "var clicked = false;"
471 "portal.onmousedown = _ => clicked = true;"));
472 EXPECT_TRUE(ExecJs(portal_frame,
473 "var clicked = false;"
474 "document.body.onmousedown = _ => clicked = true;"));
475 EXPECT_TRUE(ExecJs(portal_iframe,
476 "var clicked = false;"
477 "document.body.onmousedown = _ => clicked = true;"));
478
479 // Route the mouse event.
480 gfx::Point root_location =
481 oopif_view->TransformPointToRootCoordSpace(gfx::Point(5, 5));
482 InputEventAckWaiter waiter(main_frame->GetRenderWidgetHost(),
483 blink::WebInputEvent::kMouseDown);
484 SimulateRoutedMouseEvent(web_contents_impl, blink::WebInputEvent::kMouseDown,
485 blink::WebPointerProperties::Button::kLeft,
486 root_location);
487 waiter.Wait();
488
489 // Check that the click event was only received by the main frame.
490 EXPECT_EQ(true, EvalJs(main_frame, "clicked"));
491 EXPECT_EQ(false, EvalJs(portal_frame, "clicked"));
492 EXPECT_EQ(false, EvalJs(portal_iframe, "clicked"));
493 }
494
495 // Tests that an OOPIF inside a portal receives input events after the portal is
496 // activated.
497 // Flaky on macOS: https://crbug.com/1042703
498 #if defined(OS_MACOSX)
499 #define MAYBE_InputToOOPIFAfterActivation DISABLED_InputToOOPIFAfterActivation
500 #else
501 #define MAYBE_InputToOOPIFAfterActivation InputToOOPIFAfterActivation
502 #endif
IN_PROC_BROWSER_TEST_F(PortalHitTestBrowserTest,MAYBE_InputToOOPIFAfterActivation)503 IN_PROC_BROWSER_TEST_F(PortalHitTestBrowserTest,
504 MAYBE_InputToOOPIFAfterActivation) {
505 EXPECT_TRUE(NavigateToURL(
506 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
507 WebContentsImpl* web_contents_impl =
508 static_cast<WebContentsImpl*>(shell()->web_contents());
509 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
510
511 // Create portal.
512 // TODO(crbug.com/1029330): We currently need to give portal a large enough
513 // size to prevent overlap with iframe as this results in the test becoming
514 // flaky.
515 Portal* portal = nullptr;
516 {
517 PortalCreatedObserver portal_created_observer(main_frame);
518 GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
519 TestNavigationObserver navigation_observer(a_url);
520 navigation_observer.StartWatchingNewWebContents();
521 EXPECT_TRUE(ExecJs(
522 main_frame, JsReplace("var portal = document.createElement('portal');"
523 "portal.src = $1;"
524 "portal.style.width = '500px';"
525 "portal.style.height = '500px';"
526 "document.body.appendChild(portal);",
527 a_url)));
528 portal = portal_created_observer.WaitUntilPortalCreated();
529 navigation_observer.Wait();
530 }
531 WebContentsImpl* portal_contents = portal->GetPortalContents();
532 RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
533 WaitForHitTestData(portal_frame);
534
535 // Add an out-of-process iframe to the portal.
536 GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
537 TestNavigationObserver iframe_navigation_observer(portal_contents);
538 EXPECT_TRUE(ExecJs(portal_frame,
539 JsReplace("var iframe = document.createElement('iframe');"
540 "iframe.src = $1;"
541 "document.body.appendChild(iframe);",
542 b_url)));
543 iframe_navigation_observer.Wait();
544 EXPECT_EQ(b_url, iframe_navigation_observer.last_navigation_url());
545
546 RenderFrameHostImpl* oopif = portal_frame->child_at(0)->current_frame_host();
547 RenderWidgetHostViewBase* oopif_view =
548 static_cast<RenderWidgetHostViewBase*>(oopif->GetView());
549 EXPECT_TRUE(oopif_view->IsRenderWidgetHostViewChildFrame());
550 EXPECT_NE(portal_frame->GetSiteInstance(), oopif->GetSiteInstance());
551 WaitForHitTestData(oopif);
552 EXPECT_TRUE(ExecJs(oopif,
553 "var clicked = false;"
554 "document.body.onmousedown = _ => clicked = true;"));
555
556 // Activate the portal.
557 {
558 PortalActivatedObserver activated_observer(portal);
559 EXPECT_TRUE(ExecJs(main_frame,
560 "let portal = document.querySelector('portal');"
561 "portal.activate().then(() => { "
562 " document.body.removeChild(portal); "
563 "});"));
564 activated_observer.WaitForActivate();
565
566 RenderWidgetHostViewBase* view =
567 portal_frame->GetRenderWidgetHost()->GetView();
568 viz::FrameSinkId root_frame_sink_id = view->GetRootFrameSinkId();
569 HitTestRegionObserver hit_test_observer(root_frame_sink_id);
570
571 // The hit test region for the portal frame should be at index 1 after
572 // activation, so we wait for the hit test data to update until it's in
573 // this state.
574 auto hit_test_index = [&]() -> base::Optional<size_t> {
575 const auto& display_hit_test_query_map =
576 GetHostFrameSinkManager()->display_hit_test_query();
577 auto it = display_hit_test_query_map.find(root_frame_sink_id);
578 // On Mac, we create a new root layer after activation, so the hit test
579 // data may not have anything for the new layer yet.
580 if (it == display_hit_test_query_map.end())
581 return base::nullopt;
582 CHECK_EQ(portal_frame->GetRenderWidgetHost()->GetView(), view);
583 size_t index;
584 if (!it->second->FindIndexOfFrameSink(view->GetFrameSinkId(), &index))
585 return base::nullopt;
586 return index;
587 };
588 hit_test_observer.WaitForHitTestData();
589 while (hit_test_index() != 1u)
590 hit_test_observer.WaitForHitTestDataChange();
591 }
592 EXPECT_EQ(shell()->web_contents(), portal_contents);
593
594 // Send a mouse event to the OOPIF.
595 gfx::Point root_location =
596 oopif_view->TransformPointToRootCoordSpace(gfx::Point(10, 10));
597 InputEventAckWaiter waiter(oopif->GetRenderWidgetHost(),
598 blink::WebInputEvent::kMouseDown);
599 SimulateRoutedMouseEvent(
600 shell()->web_contents(), blink::WebInputEvent::kMouseDown,
601 blink::WebPointerProperties::Button::kLeft, root_location);
602 waiter.Wait();
603
604 // Check that the click event was received by the iframe.
605 EXPECT_EQ(true, EvalJs(oopif, "clicked"));
606 }
607
608 // Tests that async hit testing does not target portals.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,AsyncEventTargetingIgnoresPortals)609 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, AsyncEventTargetingIgnoresPortals) {
610 EXPECT_TRUE(NavigateToURL(
611 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
612 WebContentsImpl* web_contents_impl =
613 static_cast<WebContentsImpl*>(shell()->web_contents());
614 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
615
616 GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
617 Portal* portal = CreatePortalToUrl(web_contents_impl, a_url);
618 WebContentsImpl* portal_contents = portal->GetPortalContents();
619 RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
620 ASSERT_TRUE(static_cast<RenderWidgetHostViewBase*>(portal_frame->GetView())
621 ->IsRenderWidgetHostViewChildFrame());
622 RenderWidgetHostViewChildFrame* portal_view =
623 static_cast<RenderWidgetHostViewChildFrame*>(portal_frame->GetView());
624 WaitForHitTestData(portal_frame);
625
626 viz::mojom::InputTargetClient* target_client =
627 main_frame->GetRenderWidgetHost()->input_target_client();
628 ASSERT_TRUE(target_client);
629
630 gfx::PointF root_location =
631 portal_view->TransformPointToRootCoordSpaceF(gfx::PointF(5, 5));
632
633 // Query the renderer for the target widget. The root should claim the point
634 // for itself, not the portal.
635 base::RunLoop run_loop;
636 base::OnceClosure quit_closure = run_loop.QuitClosure();
637 viz::FrameSinkId received_frame_sink_id;
638 target_client->FrameSinkIdAt(
639 root_location, 0,
640 base::BindLambdaForTesting(
641 [&](const viz::FrameSinkId& id, const gfx::PointF& point) {
642 received_frame_sink_id = id;
643 std::move(quit_closure).Run();
644 }));
645 run_loop.Run();
646
647 viz::FrameSinkId root_frame_sink_id =
648 static_cast<RenderWidgetHostViewBase*>(main_frame->GetView())
649 ->GetFrameSinkId();
650 EXPECT_EQ(root_frame_sink_id, received_frame_sink_id)
651 << "Note: The portal's FrameSinkId is " << portal_view->GetFrameSinkId();
652 }
653
654 // Tests that trying to navigate to a chrome:// URL kills the renderer.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,NavigateToChrome)655 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, NavigateToChrome) {
656 EXPECT_TRUE(NavigateToURL(
657 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
658 WebContentsImpl* web_contents_impl =
659 static_cast<WebContentsImpl*>(shell()->web_contents());
660 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
661
662 // Create portal.
663 PortalCreatedObserver portal_created_observer(main_frame);
664 EXPECT_TRUE(ExecJs(main_frame,
665 "var portal = document.createElement('portal');"
666 "document.body.appendChild(portal);"));
667 Portal* portal = portal_created_observer.WaitUntilPortalCreated();
668 PortalInterceptorForTesting* portal_interceptor =
669 PortalInterceptorForTesting::From(portal);
670
671 // Try to navigate to chrome://settings and wait for the process to die.
672 portal_interceptor->SetNavigateCallback(base::BindRepeating(
673 [](Portal* portal, const GURL& url, blink::mojom::ReferrerPtr referrer,
674 blink::mojom::Portal::NavigateCallback callback) {
675 GURL chrome_url("chrome://settings");
676 portal->Navigate(chrome_url, std::move(referrer), std::move(callback));
677 },
678 portal));
679 RenderProcessHostBadIpcMessageWaiter kill_waiter(main_frame->GetProcess());
680 GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
681 ignore_result(ExecJs(main_frame, JsReplace("portal.src = $1;", a_url)));
682
683 EXPECT_EQ(bad_message::RPH_MOJO_PROCESS_ERROR, kill_waiter.Wait());
684 }
685
686 // Regression test for crbug.com/969714. Tests that receiving a touch ack
687 // from the predecessor after portal activation doesn't cause a crash.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,TouchAckAfterActivate)688 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, TouchAckAfterActivate) {
689 EXPECT_TRUE(NavigateToURL(
690 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
691 WebContentsImpl* web_contents_impl =
692 static_cast<WebContentsImpl*>(shell()->web_contents());
693 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
694
695 GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
696 Portal* portal = CreatePortalToUrl(web_contents_impl, a_url);
697 EXPECT_TRUE(
698 ExecJs(main_frame,
699 JsReplace("document.body.addEventListener('touchstart', e => {"
700 " document.querySelector('portal').activate();"
701 "}, {passive: false});")));
702 WebContentsImpl* portal_contents = portal->GetPortalContents();
703
704 RenderWidgetHostImpl* render_widget_host = main_frame->GetRenderWidgetHost();
705 RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
706 RenderWidgetHostViewChildFrame* portal_view =
707 static_cast<RenderWidgetHostViewChildFrame*>(portal_frame->GetView());
708 InputEventAckWaiter input_event_ack_waiter(
709 render_widget_host, blink::WebInputEvent::Type::kTouchStart);
710 WaitForHitTestData(portal_frame);
711
712 SyntheticTapGestureParams params;
713 params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
714 params.position =
715 portal_view->TransformPointToRootCoordSpaceF(gfx::PointF(5, 5));
716
717 PortalActivatedObserver activated_observer(portal);
718 std::unique_ptr<SyntheticTapGesture> gesture =
719 std::make_unique<SyntheticTapGesture>(params);
720 render_widget_host->QueueSyntheticGesture(std::move(gesture),
721 base::DoNothing());
722 activated_observer.WaitForActivate();
723 EXPECT_EQ(portal_contents, shell()->web_contents());
724
725 // Wait for a touch ack to be sent from the predecessor.
726 input_event_ack_waiter.Wait();
727 }
728
729 // Regression test for crbug.com/973647. Tests that receiving a touch ack
730 // after activation and predecessor adoption doesn't cause a crash.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,TouchAckAfterActivateAndAdopt)731 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, TouchAckAfterActivateAndAdopt) {
732 EXPECT_TRUE(NavigateToURL(
733 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
734 WebContentsImpl* web_contents_impl =
735 static_cast<WebContentsImpl*>(shell()->web_contents());
736 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
737
738 GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
739 Portal* portal = CreatePortalToUrl(web_contents_impl, a_url);
740 const int TOUCH_ACK_DELAY_IN_MILLISECONDS = 500;
741 EXPECT_TRUE(
742 ExecJs(main_frame,
743 JsReplace("document.body.addEventListener('touchstart', e => {"
744 " document.querySelector('portal').activate();"
745 " var stop = performance.now() + $1;"
746 " while (performance.now() < stop) {}"
747 "}, {passive: false});",
748 TOUCH_ACK_DELAY_IN_MILLISECONDS)));
749 WebContentsImpl* portal_contents = portal->GetPortalContents();
750
751 RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
752 EXPECT_TRUE(ExecJs(portal_frame,
753 "window.addEventListener('portalactivate', e => {"
754 " var portal = e.adoptPredecessor();"
755 " document.body.appendChild(portal);"
756 "});"));
757 WaitForHitTestData(portal_frame);
758
759 RenderWidgetHostImpl* render_widget_host = main_frame->GetRenderWidgetHost();
760 InputEventAckWaiter input_event_ack_waiter(
761 render_widget_host, blink::WebInputEvent::Type::kTouchStart);
762
763 SyntheticTapGestureParams params;
764 RenderWidgetHostViewChildFrame* portal_view =
765 static_cast<RenderWidgetHostViewChildFrame*>(portal_frame->GetView());
766 params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
767 params.position =
768 portal_view->TransformPointToRootCoordSpaceF(gfx::PointF(5, 5));
769
770 std::unique_ptr<SyntheticTapGesture> gesture =
771 std::make_unique<SyntheticTapGesture>(params);
772 {
773 PortalActivatedObserver activated_observer(portal);
774 PortalCreatedObserver adoption_observer(portal_frame);
775 render_widget_host->QueueSyntheticGesture(std::move(gesture),
776 base::DoNothing());
777 activated_observer.WaitForActivate();
778 EXPECT_EQ(portal_contents, shell()->web_contents());
779 // Wait for predecessor to be adopted.
780 adoption_observer.WaitUntilPortalCreated();
781 }
782
783 // Wait for a touch ack to be sent from the predecessor.
784 input_event_ack_waiter.Wait();
785 }
786
787 // Regression test for crbug.com/973647. Tests that receiving a touch ack
788 // after activation and reactivating a predecessor doesn't cause a crash.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,TouchAckAfterActivateAndReactivate)789 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, TouchAckAfterActivateAndReactivate) {
790 EXPECT_TRUE(NavigateToURL(
791 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
792 WebContentsImpl* web_contents_impl =
793 static_cast<WebContentsImpl*>(shell()->web_contents());
794 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
795
796 GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
797 Portal* portal = CreatePortalToUrl(web_contents_impl, a_url);
798 const int TOUCH_ACK_DELAY_IN_MILLISECONDS = 500;
799 EXPECT_TRUE(
800 ExecJs(main_frame,
801 JsReplace("document.body.addEventListener('touchstart', e => {"
802 " document.querySelector('portal').activate();"
803 " var stop = performance.now() + $1;"
804 " while (performance.now() < stop) {}"
805 "}, {passive: false});",
806 TOUCH_ACK_DELAY_IN_MILLISECONDS)));
807 WebContentsImpl* portal_contents = portal->GetPortalContents();
808
809 RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
810 EXPECT_TRUE(ExecJs(portal_frame,
811 "window.addEventListener('portalactivate', e => {"
812 " var portal = e.adoptPredecessor();"
813 " document.body.appendChild(portal);"
814 " portal.activate();"
815 "});"));
816 WaitForHitTestData(portal_frame);
817
818 RenderWidgetHostImpl* render_widget_host = main_frame->GetRenderWidgetHost();
819 InputEventAckWaiter input_event_ack_waiter(
820 render_widget_host, blink::WebInputEvent::Type::kTouchStart);
821
822 SyntheticTapGestureParams params;
823 params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
824 params.position = gfx::PointF(20, 20);
825
826 std::unique_ptr<SyntheticTapGesture> gesture =
827 std::make_unique<SyntheticTapGesture>(params);
828
829 base::Optional<PortalActivatedObserver> predecessor_activated;
830 {
831 PortalCreatedObserver adoption_observer(portal_frame);
832 adoption_observer.set_created_callback(base::BindLambdaForTesting(
833 [&](Portal* portal) { predecessor_activated.emplace(portal); }));
834
835 PortalActivatedObserver activated_observer(portal);
836 render_widget_host->QueueSyntheticGesture(std::move(gesture),
837 base::DoNothing());
838 activated_observer.WaitForActivate();
839 EXPECT_EQ(portal_contents, shell()->web_contents());
840
841 adoption_observer.WaitUntilPortalCreated();
842 }
843
844 predecessor_activated->WaitForActivate();
845 // Sanity check to see if the predecessor was reactivated.
846 EXPECT_EQ(web_contents_impl, shell()->web_contents());
847
848 // Wait for a touch ack to be sent from the predecessor.
849 input_event_ack_waiter.Wait();
850 }
851
852 // TODO(crbug.com/985078): Fix on Mac.
853 #if !defined(OS_MACOSX)
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,TouchStateClearedBeforeActivation)854 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, TouchStateClearedBeforeActivation) {
855 EXPECT_TRUE(NavigateToURL(
856 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
857 WebContentsImpl* web_contents_impl =
858 static_cast<WebContentsImpl*>(shell()->web_contents());
859 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
860
861 GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
862 Portal* portal = CreatePortalToUrl(web_contents_impl, a_url);
863 EXPECT_TRUE(
864 ExecJs(main_frame,
865 JsReplace("document.body.addEventListener('touchstart', e => {"
866 " document.querySelector('portal').activate();"
867 "}, {passive: false});")));
868 WebContentsImpl* portal_contents = portal->GetPortalContents();
869
870 RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
871 EXPECT_TRUE(ExecJs(portal_frame,
872 "window.addEventListener('portalactivate', e => {"
873 " var portal = e.adoptPredecessor();"
874 " document.body.appendChild(portal);"
875 " portal.activate();"
876 "});"));
877 WaitForHitTestData(portal_frame);
878
879 RenderWidgetHostImpl* render_widget_host = main_frame->GetRenderWidgetHost();
880
881 SyntheticTapGestureParams params;
882 params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
883 params.position = gfx::PointF(20, 20);
884
885 // Activate the portal, and then wait for the predecessor to be reactivated.
886 base::Optional<PortalActivatedObserver> adopted_activated;
887 {
888 PortalCreatedObserver adoption_observer(portal_frame);
889 adoption_observer.set_created_callback(base::BindLambdaForTesting(
890 [&](Portal* portal) { adopted_activated.emplace(portal); }));
891
892 PortalActivatedObserver activated_observer(portal);
893 std::unique_ptr<SyntheticTapGesture> gesture =
894 std::make_unique<SyntheticTapGesture>(params);
895 InputEventAckWaiter input_event_ack_waiter(
896 render_widget_host, blink::WebInputEvent::Type::kTouchCancel);
897 render_widget_host->QueueSyntheticGestureCompleteImmediately(
898 std::move(gesture));
899 // Wait for synthetic cancel event to be sent.
900 input_event_ack_waiter.Wait();
901 activated_observer.WaitForActivate();
902 EXPECT_EQ(portal_contents, shell()->web_contents());
903
904 adoption_observer.WaitUntilPortalCreated();
905 }
906 adopted_activated->WaitForActivate();
907 // Sanity check to see if the predecessor was reactivated.
908 EXPECT_EQ(web_contents_impl, shell()->web_contents());
909
910 InputEventAckWaiter input_event_ack_waiter(
911 render_widget_host, blink::WebInputEvent::Type::kTouchStart);
912 std::unique_ptr<SyntheticTapGesture> gesture =
913 std::make_unique<SyntheticTapGesture>(params);
914 render_widget_host->QueueSyntheticGesture(std::move(gesture),
915 base::DoNothing());
916 // Waits for touch to be acked. If touch state wasn't cleared before initial
917 // activation, a DCHECK will be hit before the ack is sent.
918 input_event_ack_waiter.Wait();
919 }
920 #endif
921
922 // TODO(crbug.com/985078): Fix on Mac.
923 #if !defined(OS_MACOSX)
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,GestureCleanedUpBeforeActivation)924 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, GestureCleanedUpBeforeActivation) {
925 EXPECT_TRUE(NavigateToURL(
926 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
927 WebContentsImpl* web_contents_impl =
928 static_cast<WebContentsImpl*>(shell()->web_contents());
929 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
930
931 GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
932 Portal* portal = CreatePortalToUrl(web_contents_impl, a_url);
933 EXPECT_TRUE(
934 ExecJs(main_frame,
935 JsReplace("document.body.addEventListener('touchstart', e => {"
936 " document.querySelector('portal').activate();"
937 "}, {once: true});")));
938 WebContentsImpl* portal_contents = portal->GetPortalContents();
939 RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
940 EXPECT_TRUE(ExecJs(portal_frame,
941 "window.addEventListener('portalactivate', e => {"
942 " var portal = e.adoptPredecessor();"
943 " document.body.appendChild(portal);"
944 " portal.activate(); "
945 "});"));
946 WaitForHitTestData(portal_frame);
947
948 RenderWidgetHostImpl* render_widget_host = main_frame->GetRenderWidgetHost();
949
950 SyntheticTapGestureParams params;
951 params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
952 params.position = gfx::PointF(20, 20);
953 params.duration_ms = 1;
954
955 // Simulate a tap and activate the portal.
956 base::Optional<PortalActivatedObserver> adopted_activated;
957 {
958 PortalCreatedObserver adoption_observer(portal_frame);
959 adoption_observer.set_created_callback(base::BindLambdaForTesting(
960 [&](Portal* portal) { adopted_activated.emplace(portal); }));
961
962 PortalActivatedObserver activated_observer(portal);
963 std::unique_ptr<SyntheticTapGesture> gesture =
964 std::make_unique<SyntheticTapGesture>(params);
965 render_widget_host->QueueSyntheticGestureCompleteImmediately(
966 std::move(gesture));
967 activated_observer.WaitForActivate();
968 EXPECT_EQ(portal_contents, shell()->web_contents());
969
970 adoption_observer.WaitUntilPortalCreated();
971 }
972
973 // Wait for predecessor to be reactivated.
974 adopted_activated->WaitForActivate();
975 EXPECT_EQ(web_contents_impl, shell()->web_contents());
976
977 // Simulate another tap.
978 InputEventAckWaiter input_event_ack_waiter(
979 render_widget_host, blink::WebInputEvent::Type::kGestureTap);
980 auto gesture = std::make_unique<SyntheticTapGesture>(params);
981 render_widget_host->QueueSyntheticGesture(std::move(gesture),
982 base::DoNothing());
983 // Wait for the tap gesture ack. If the initial gesture wasn't cleaned up, the
984 // new gesture created will cause an error in the gesture validator.
985 input_event_ack_waiter.Wait();
986 }
987 #endif
988
989 // Touch input transfer is only implemented in the content layer for Aura.
990 #if defined(USE_AURA)
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,TouchInputTransferAcrossActivation)991 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, TouchInputTransferAcrossActivation) {
992 EXPECT_TRUE(NavigateToURL(
993 shell(),
994 embedded_test_server()->GetURL("portal.test", "/portals/scroll.html")));
995 WebContentsImpl* web_contents_impl =
996 static_cast<WebContentsImpl*>(shell()->web_contents());
997 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
998 RenderWidgetHostImpl* render_widget_host = main_frame->GetRenderWidgetHost();
999
1000 GURL portal_url(embedded_test_server()->GetURL(
1001 "portal.test", "/portals/scroll-portal.html"));
1002 Portal* portal = CreatePortalToUrl(web_contents_impl, portal_url);
1003 WebContentsImpl* portal_contents = portal->GetPortalContents();
1004 RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
1005 WaitForHitTestData(portal_frame);
1006
1007 // Create and dispatch a synthetic scroll to trigger activation.
1008 SyntheticSmoothScrollGestureParams params;
1009 params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
1010 params.anchor =
1011 gfx::PointF(50, main_frame->GetView()->GetViewBounds().height() - 100);
1012 params.distances.push_back(-gfx::Vector2d(0, 100));
1013
1014 std::unique_ptr<SyntheticSmoothScrollGesture> gesture =
1015 std::make_unique<SyntheticSmoothScrollGesture>(params);
1016 base::RunLoop run_loop;
1017 render_widget_host->QueueSyntheticGesture(
1018 std::move(gesture),
1019 base::BindLambdaForTesting([&](SyntheticGesture::Result result) {
1020 EXPECT_EQ(SyntheticGesture::Result::GESTURE_FINISHED, result);
1021 run_loop.Quit();
1022 }));
1023 run_loop.Run();
1024
1025 // Check if the activated page scrolled.
1026 EXPECT_NE(0, EvalJs(portal_frame, "window.scrollY"));
1027 }
1028 #endif
1029
1030 // TODO(crbug.com/1010675): Test fails flakily.
1031 // Touch input transfer is only implemented in the content layer for Aura.
1032 #if defined(USE_AURA)
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,TouchInputTransferAcrossReactivation)1033 IN_PROC_BROWSER_TEST_F(PortalBrowserTest,
1034 TouchInputTransferAcrossReactivation) {
1035 EXPECT_TRUE(NavigateToURL(
1036 shell(), embedded_test_server()->GetURL(
1037 "portal.test",
1038 "/portals/touch-input-transfer-across-reactivation.html")));
1039 WebContentsImpl* web_contents_impl =
1040 static_cast<WebContentsImpl*>(shell()->web_contents());
1041 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
1042 RenderWidgetHostImpl* render_widget_host = main_frame->GetRenderWidgetHost();
1043
1044 GURL portal_url(embedded_test_server()->GetURL(
1045 "portal.test", "/portals/reactivate-predecessor.html"));
1046 Portal* portal = CreatePortalToUrl(web_contents_impl, portal_url);
1047 WaitForHitTestData(main_frame);
1048
1049 PortalActivatedObserver activated_observer(portal);
1050
1051 // Create and dispatch a synthetic scroll to trigger activation.
1052 SyntheticSmoothScrollGestureParams params;
1053 params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
1054 params.anchor =
1055 gfx::PointF(50, main_frame->GetView()->GetViewBounds().height() - 100);
1056 params.distances.push_back(-gfx::Vector2d(0, 250));
1057 params.speed_in_pixels_s = 200;
1058
1059 std::unique_ptr<SyntheticSmoothScrollGesture> gesture =
1060 std::make_unique<SyntheticSmoothScrollGesture>(params);
1061 base::RunLoop run_loop;
1062 render_widget_host->QueueSyntheticGesture(
1063 std::move(gesture),
1064 base::BindLambdaForTesting([&](SyntheticGesture::Result result) {
1065 EXPECT_EQ(SyntheticGesture::Result::GESTURE_FINISHED, result);
1066 run_loop.Quit();
1067 }));
1068 // Portal should activate when the gesture begins.
1069 activated_observer.WaitForActivate();
1070 // Wait till the scroll gesture finishes.
1071 run_loop.Run();
1072 // The predecessor should have been reactivated (we should be back to the
1073 // starting page).
1074 EXPECT_EQ(web_contents_impl, shell()->web_contents());
1075 // The starting page should have scrolled.
1076 // NOTE: This assumes that the scroll gesture is long enough that touch events
1077 // are still sent after the predecessor is reactivated.
1078 int scroll_y_after_portal_activate =
1079 EvalJs(main_frame, "scrollYAfterPortalActivate").ExtractInt();
1080 EXPECT_LT(scroll_y_after_portal_activate,
1081 EvalJs(main_frame, "window.scrollY"));
1082 }
1083 #endif
1084
1085 // Tests that the outer FrameTreeNode is deleted after activation.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,FrameDeletedAfterActivation)1086 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, FrameDeletedAfterActivation) {
1087 EXPECT_TRUE(NavigateToURL(
1088 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
1089 WebContentsImpl* web_contents_impl =
1090 static_cast<WebContentsImpl*>(shell()->web_contents());
1091 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
1092
1093 GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
1094 Portal* portal = CreatePortalToUrl(web_contents_impl, a_url);
1095 WebContentsImpl* portal_contents = portal->GetPortalContents();
1096
1097 FrameTreeNode* outer_frame_tree_node = FrameTreeNode::GloballyFindByID(
1098 portal_contents->GetOuterDelegateFrameTreeNodeId());
1099 EXPECT_TRUE(outer_frame_tree_node);
1100
1101 EXPECT_TRUE(ExecJs(portal_contents->GetMainFrame(),
1102 "window.onportalactivate = e => "
1103 "document.body.appendChild(e.adoptPredecessor());"));
1104
1105 {
1106 FrameDeletedObserver observer(outer_frame_tree_node->current_frame_host());
1107 PortalCreatedObserver portal_created_observer(
1108 portal_contents->GetMainFrame());
1109 ExecuteScriptAsync(main_frame,
1110 "document.querySelector('portal').activate();");
1111 observer.Wait();
1112
1113 // Observes the creation of a new portal due to the adoption of the
1114 // predecessor during the activate event.
1115 // TODO(lfg): We only wait for the adoption callback to avoid a race
1116 // receiving a sync IPC in a nested message loop while the browser is
1117 // sending out another sync IPC to the GPU process.
1118 // https://crbug.com/976367.
1119 portal_created_observer.WaitUntilPortalCreated();
1120 }
1121 }
1122
1123 // Tests that activating a portal at the same time as it is being removed
1124 // doesn't crash the browser.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,RemovePortalWhenUnloading)1125 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, RemovePortalWhenUnloading) {
1126 EXPECT_TRUE(NavigateToURL(
1127 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
1128 WebContentsImpl* web_contents_impl =
1129 static_cast<WebContentsImpl*>(shell()->web_contents());
1130 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
1131
1132 // Create a container for the portal.
1133 EXPECT_TRUE(ExecJs(main_frame,
1134 "var div = document.createElement('div');"
1135 "document.body.appendChild(div);"));
1136
1137 // Create portal.
1138 PortalCreatedObserver portal_created_observer(main_frame);
1139 EXPECT_TRUE(ExecJs(main_frame,
1140 "var portal = document.createElement('portal');"
1141 "div.appendChild(portal);"));
1142
1143 // Add a same-origin iframe in the same div as the portal that activates the
1144 // portal on its unload handler.
1145 EXPECT_TRUE(
1146 ExecJs(main_frame,
1147 "var iframe = document.createElement('iframe');"
1148 "iframe.src = 'about:blank';"
1149 "div.appendChild(iframe);"
1150 "iframe.contentWindow.onunload = () => portal.activate();"));
1151
1152 // Remove the div from the document. This destroys the portal's WebContents
1153 // and should destroy the Portal object as well, so that the activate message
1154 // is not processed.
1155 EXPECT_TRUE(ExecJs(main_frame, "div.remove();"));
1156 }
1157
1158 // Tests that a portal can navigate while orphaned.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,OrphanedNavigation)1159 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, OrphanedNavigation) {
1160 EXPECT_TRUE(NavigateToURL(
1161 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
1162 WebContentsImpl* web_contents_impl =
1163 static_cast<WebContentsImpl*>(shell()->web_contents());
1164 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
1165
1166 GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
1167 Portal* portal = CreatePortalToUrl(web_contents_impl, a_url);
1168 WebContentsImpl* portal_contents = portal->GetPortalContents();
1169
1170 // Block the activate callback so that the predecessor portal stays orphaned.
1171 EXPECT_TRUE(ExecJs(portal_contents->GetMainFrame(),
1172 "window.onportalactivate = e => { while(true) {} };"));
1173
1174 // Activate the portal and navigate the predecessor.
1175 PortalActivatedObserver activated_observer(portal);
1176 TestNavigationObserver main_frame_navigation_observer(web_contents_impl);
1177 ExecuteScriptAsync(main_frame,
1178 "document.querySelector('portal').activate();"
1179 "window.location.reload()");
1180 activated_observer.WaitForActivate();
1181 main_frame_navigation_observer.Wait();
1182 }
1183
1184 // Tests that the browser doesn't crash if the renderer tries to create the
1185 // PortalHost after the parent renderer dropped the portal.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,AccessPortalHostAfterPortalDestruction)1186 IN_PROC_BROWSER_TEST_F(PortalBrowserTest,
1187 AccessPortalHostAfterPortalDestruction) {
1188 EXPECT_TRUE(NavigateToURL(
1189 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
1190 WebContentsImpl* web_contents_impl =
1191 static_cast<WebContentsImpl*>(shell()->web_contents());
1192
1193 GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
1194 Portal* portal = CreatePortalToUrl(web_contents_impl, a_url);
1195 WebContentsImpl* portal_contents = portal->GetPortalContents();
1196 RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
1197
1198 // Simulate the portal being dropped, but not the destruction of the
1199 // WebContents.
1200 web_contents_impl->GetMainFrame()->DestroyPortal(portal);
1201
1202 // Get the portal renderer to access the WebContents.
1203 RenderProcessHostBadIpcMessageWaiter kill_waiter(portal_frame->GetProcess());
1204 ExecuteScriptAsync(portal_frame,
1205 "window.portalHost.postMessage('message', '*');");
1206 EXPECT_EQ(bad_message::RPH_MOJO_PROCESS_ERROR, kill_waiter.Wait());
1207 }
1208
1209 // Tests that activation early in navigation succeeds, cancelling the pending
1210 // navigation.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,ActivateEarlyInNavigation)1211 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, ActivateEarlyInNavigation) {
1212 EXPECT_TRUE(NavigateToURL(
1213 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
1214 WebContentsImpl* web_contents_impl =
1215 static_cast<WebContentsImpl*>(shell()->web_contents());
1216 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
1217
1218 GURL url = embedded_test_server()->GetURL("a.com", "/title2.html");
1219 Portal* portal = CreatePortalToUrl(web_contents_impl, url);
1220 WebContents* portal_contents = portal->GetPortalContents();
1221
1222 // Have the outer page try to navigate away but stop it early in the request,
1223 // where it is still possible to stop.
1224 GURL destination =
1225 embedded_test_server()->GetURL("portal.test", "/title3.html");
1226 TestNavigationObserver navigation_observer(web_contents_impl);
1227 NavigationHandleObserver handle_observer(web_contents_impl, destination);
1228 TestNavigationManager navigation_manager(web_contents_impl, destination);
1229 NavigationController::LoadURLParams params(destination);
1230 params.transition_type = ui::PageTransitionFromInt(
1231 ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
1232 web_contents_impl->GetController().LoadURLWithParams(params);
1233 ASSERT_TRUE(navigation_manager.WaitForRequestStart());
1234
1235 // Then activate the portal. Since this is early in navigation, it should be
1236 // aborted and the portal activation should succeed.
1237 PortalActivatedObserver activated_observer(portal);
1238 ExecuteScriptAsync(main_frame,
1239 "document.querySelector('portal').activate();");
1240 EXPECT_EQ(blink::mojom::PortalActivateResult::kPredecessorWillUnload,
1241 activated_observer.WaitForActivateResult());
1242 EXPECT_EQ(portal_contents, shell()->web_contents());
1243 navigation_observer.Wait();
1244 EXPECT_EQ(handle_observer.net_error_code(), net::ERR_ABORTED);
1245 }
1246
1247 // Tests that activation late in navigation is rejected (since it's too late to
1248 // stop the navigation).
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,ActivateLateInNavigation)1249 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, ActivateLateInNavigation) {
1250 EXPECT_TRUE(NavigateToURL(
1251 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
1252 WebContentsImpl* web_contents_impl =
1253 static_cast<WebContentsImpl*>(shell()->web_contents());
1254 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
1255
1256 GURL url = embedded_test_server()->GetURL("a.com", "/title2.html");
1257 Portal* portal = CreatePortalToUrl(web_contents_impl, url);
1258
1259 // Have the outer page try to navigate away and reach the point where it's
1260 // about to process the response (after which it will commit). It is too late
1261 // to abort the navigation.
1262 GURL destination =
1263 embedded_test_server()->GetURL("portal.test", "/title3.html");
1264 TestNavigationObserver navigation_observer(web_contents_impl);
1265 TestNavigationManager navigation_manager(web_contents_impl, destination);
1266 NavigationController::LoadURLParams params(destination);
1267 params.transition_type = ui::PageTransitionFromInt(
1268 ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
1269 web_contents_impl->GetController().LoadURLWithParams(params);
1270 ASSERT_TRUE(navigation_manager.WaitForResponse());
1271
1272 // Then activate the portal. Since this is late in navigation, we expect the
1273 // activation to fail. Since commit hasn't actually happened yet, though,
1274 // there is time for the renderer to process the promise rejection.
1275 PortalActivatedObserver activated_observer(portal);
1276 EvalJsResult result = EvalJs(main_frame,
1277 "document.querySelector('portal').activate()"
1278 ".then(() => 'success', e => e.message)");
1279 EXPECT_THAT(result.ExtractString(),
1280 ::testing::HasSubstr("navigation is in progress"));
1281 EXPECT_EQ(
1282 blink::mojom::PortalActivateResult::kRejectedDueToPredecessorNavigation,
1283 activated_observer.result());
1284
1285 // The navigation should commit properly thereafter.
1286 navigation_manager.ResumeNavigation();
1287 navigation_observer.Wait();
1288 EXPECT_EQ(web_contents_impl, shell()->web_contents());
1289 EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
1290 EXPECT_EQ(destination, navigation_observer.last_navigation_url());
1291 }
1292
1293 namespace {
1294
1295 class NavigationControlInterceptorBadPortalActivateResult
1296 : public mojom::FrameNavigationControlInterceptorForTesting {
1297 public:
NavigationControlInterceptorBadPortalActivateResult(RenderFrameHostImpl * frame_host)1298 explicit NavigationControlInterceptorBadPortalActivateResult(
1299 RenderFrameHostImpl* frame_host)
1300 : frame_host_(frame_host) {}
1301
GetForwardingInterface()1302 mojom::FrameNavigationControl* GetForwardingInterface() override {
1303 if (!navigation_control_)
1304 frame_host_->GetRemoteAssociatedInterfaces()->GetInterface(
1305 &navigation_control_);
1306 return navigation_control_.get();
1307 }
1308
OnPortalActivated(const base::UnguessableToken & portal_token,mojo::PendingAssociatedRemote<blink::mojom::Portal> portal,mojo::PendingAssociatedReceiver<blink::mojom::PortalClient> portal_client,blink::TransferableMessage data,OnPortalActivatedCallback callback)1309 void OnPortalActivated(
1310 const base::UnguessableToken& portal_token,
1311 mojo::PendingAssociatedRemote<blink::mojom::Portal> portal,
1312 mojo::PendingAssociatedReceiver<blink::mojom::PortalClient> portal_client,
1313 blink::TransferableMessage data,
1314 OnPortalActivatedCallback callback) override {
1315 GetForwardingInterface()->OnPortalActivated(
1316 portal_token, std::move(portal), std::move(portal_client),
1317 std::move(data),
1318 base::BindOnce(
1319 [](OnPortalActivatedCallback callback,
1320 blink::mojom::PortalActivateResult) {
1321 // Replace the true result with one that the renderer is not
1322 // allowed to send.
1323 std::move(callback).Run(blink::mojom::PortalActivateResult::
1324 kRejectedDueToPredecessorNavigation);
1325 },
1326 std::move(callback)));
1327 }
1328
1329 private:
1330 RenderFrameHostImpl* frame_host_;
1331 mojo::AssociatedRemote<mojom::FrameNavigationControl> navigation_control_;
1332 };
1333
1334 class RenderFrameHostImplForNavigationControlInterceptor
1335 : public RenderFrameHostImpl {
1336 private:
1337 using RenderFrameHostImpl::RenderFrameHostImpl;
1338
GetNavigationControl()1339 mojom::FrameNavigationControl* GetNavigationControl() override {
1340 return &interceptor_;
1341 }
1342
1343 NavigationControlInterceptorBadPortalActivateResult interceptor_{this};
1344
1345 friend class RenderFrameHostFactoryForNavigationControlInterceptor;
1346 };
1347
1348 class RenderFrameHostFactoryForNavigationControlInterceptor
1349 : public TestRenderFrameHostFactory {
1350 protected:
CreateRenderFrameHost(SiteInstance * site_instance,scoped_refptr<RenderViewHostImpl> render_view_host,RenderFrameHostDelegate * delegate,FrameTree * frame_tree,FrameTreeNode * frame_tree_node,int32_t routing_id,bool renderer_initiated_creation)1351 std::unique_ptr<RenderFrameHostImpl> CreateRenderFrameHost(
1352 SiteInstance* site_instance,
1353 scoped_refptr<RenderViewHostImpl> render_view_host,
1354 RenderFrameHostDelegate* delegate,
1355 FrameTree* frame_tree,
1356 FrameTreeNode* frame_tree_node,
1357 int32_t routing_id,
1358 bool renderer_initiated_creation) override {
1359 return base::WrapUnique(
1360 new RenderFrameHostImplForNavigationControlInterceptor(
1361 site_instance, std::move(render_view_host), delegate, frame_tree,
1362 frame_tree_node, routing_id, renderer_initiated_creation));
1363 }
1364 };
1365
1366 } // namespace
1367
1368 // Tests that the browser filters the renderer's replies to the portal
1369 // activation event and terminates misbehaving renderers.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,MisbehavingRendererActivated)1370 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, MisbehavingRendererActivated) {
1371 EXPECT_TRUE(NavigateToURL(
1372 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
1373 WebContentsImpl* web_contents_impl =
1374 static_cast<WebContentsImpl*>(shell()->web_contents());
1375 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
1376
1377 // Arrange for a special kind of RenderFrameHost to be created which permits
1378 // its NavigationControl messages to be intercepted.
1379 RenderFrameHostFactoryForNavigationControlInterceptor scoped_rfh_factory;
1380 GURL url = embedded_test_server()->GetURL("a.com", "/title2.html");
1381 Portal* portal = CreatePortalToUrl(web_contents_impl, url);
1382
1383 // Then activate the portal. Due to the apparent misbehavior from the
1384 // renderer, the caller should be informed that it was aborted due to a bug,
1385 // and the portal's renderer (having been framed for the crime) should be
1386 // killed.
1387 PortalActivatedObserver activated_observer(portal);
1388 RenderProcessHostBadIpcMessageWaiter kill_waiter(
1389 portal->GetPortalContents()->GetMainFrame()->GetProcess());
1390 ExecuteScriptAsync(main_frame,
1391 "document.querySelector('portal').activate();");
1392 EXPECT_EQ(blink::mojom::PortalActivateResult::kAbortedDueToBug,
1393 activated_observer.WaitForActivateResult());
1394 EXPECT_EQ(bad_message::RPH_MOJO_PROCESS_ERROR, kill_waiter.Wait());
1395 }
1396
1397 // Test that user facing session history is retained after a portal activation.
1398 // Before activation, we have the host WebContents's navigation entries, which
1399 // is the session history presented to the user, plus the portal WebContents's
1400 // navigation entry, which is not presented as part of that session history.
1401 // Upon activation, the host WebContents's navigation entries are merged into
1402 // the activated portal's WebContents. The resulting session history in the
1403 // activated WebContents is presented to the user.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,PortalHistoryWithActivation)1404 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, PortalHistoryWithActivation) {
1405 // We have an additional navigation entry in the portal host's contents, so
1406 // that we test that we're retaining more than just the last committed entry
1407 // of the portal host.
1408 GURL previous_url(
1409 embedded_test_server()->GetURL("portal.test", "/title1.html"));
1410 EXPECT_TRUE(NavigateToURL(shell(), previous_url));
1411
1412 GURL main_url(embedded_test_server()->GetURL("portal.test", "/title2.html"));
1413 EXPECT_TRUE(NavigateToURL(shell(), main_url));
1414
1415 WebContentsImpl* web_contents_impl =
1416 static_cast<WebContentsImpl*>(shell()->web_contents());
1417 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
1418
1419 GURL portal_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
1420 Portal* portal = CreatePortalToUrl(web_contents_impl, portal_url);
1421 WebContentsImpl* portal_contents = portal->GetPortalContents();
1422
1423 NavigationControllerImpl& main_controller =
1424 web_contents_impl->GetController();
1425 NavigationControllerImpl& portal_controller =
1426 portal_contents->GetController();
1427
1428 EXPECT_EQ(2, main_controller.GetEntryCount());
1429 ASSERT_TRUE(main_controller.GetLastCommittedEntry());
1430 EXPECT_EQ(main_url, main_controller.GetLastCommittedEntry()->GetURL());
1431 EXPECT_EQ(1, portal_controller.GetEntryCount());
1432 ASSERT_TRUE(portal_controller.GetLastCommittedEntry());
1433 EXPECT_EQ(portal_url, portal_controller.GetLastCommittedEntry()->GetURL());
1434
1435 PortalActivatedObserver activated_observer(portal);
1436 ASSERT_TRUE(
1437 ExecJs(main_frame, "document.querySelector('portal').activate();"));
1438 activated_observer.WaitForActivate();
1439
1440 NavigationControllerImpl& activated_controller = portal_controller;
1441
1442 ASSERT_EQ(3, activated_controller.GetEntryCount());
1443 ASSERT_EQ(2, activated_controller.GetLastCommittedEntryIndex());
1444 EXPECT_EQ(previous_url, activated_controller.GetEntryAtIndex(0)->GetURL());
1445 EXPECT_EQ(main_url, activated_controller.GetEntryAtIndex(1)->GetURL());
1446 EXPECT_EQ(portal_url, activated_controller.GetEntryAtIndex(2)->GetURL());
1447 }
1448
1449 // Test that we may go back/forward across a portal activation as though it
1450 // were a regular navigation.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,PortalHistoryActivateAndGoBackForward)1451 IN_PROC_BROWSER_TEST_F(PortalBrowserTest,
1452 PortalHistoryActivateAndGoBackForward) {
1453 GURL main_url(embedded_test_server()->GetURL("portal.test", "/title1.html"));
1454 EXPECT_TRUE(NavigateToURL(shell(), main_url));
1455 WebContentsImpl* web_contents_impl =
1456 static_cast<WebContentsImpl*>(shell()->web_contents());
1457 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
1458
1459 GURL portal_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
1460 Portal* portal = CreatePortalToUrl(web_contents_impl, portal_url);
1461
1462 PortalActivatedObserver activated_observer(portal);
1463 ASSERT_TRUE(
1464 ExecJs(main_frame, "document.querySelector('portal').activate();"));
1465 activated_observer.WaitForActivate();
1466
1467 web_contents_impl = static_cast<WebContentsImpl*>(shell()->web_contents());
1468 NavigationControllerImpl& controller = web_contents_impl->GetController();
1469
1470 ASSERT_TRUE(controller.CanGoBack());
1471 TestNavigationObserver go_back_observer(web_contents_impl);
1472 controller.GoBack();
1473 go_back_observer.Wait();
1474 // These back/forward navigations do not involve a contents swap, since the
1475 // original contents is gone as it was not adopted.
1476 ASSERT_EQ(web_contents_impl, shell()->web_contents());
1477
1478 ASSERT_EQ(2, controller.GetEntryCount());
1479 ASSERT_EQ(0, controller.GetLastCommittedEntryIndex());
1480 EXPECT_EQ(main_url, controller.GetEntryAtIndex(0)->GetURL());
1481 EXPECT_EQ(portal_url, controller.GetEntryAtIndex(1)->GetURL());
1482
1483 ASSERT_TRUE(controller.CanGoForward());
1484 TestNavigationObserver go_forward_observer(web_contents_impl);
1485 controller.GoForward();
1486 go_forward_observer.Wait();
1487 ASSERT_EQ(web_contents_impl, shell()->web_contents());
1488
1489 ASSERT_EQ(2, controller.GetEntryCount());
1490 ASSERT_EQ(1, controller.GetLastCommittedEntryIndex());
1491 EXPECT_EQ(main_url, controller.GetEntryAtIndex(0)->GetURL());
1492 EXPECT_EQ(portal_url, controller.GetEntryAtIndex(1)->GetURL());
1493 }
1494
1495 // Activation does not cancel new pending navigations in portals.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,ActivationWithPortalPendingNavigation)1496 IN_PROC_BROWSER_TEST_F(PortalBrowserTest,
1497 ActivationWithPortalPendingNavigation) {
1498 GURL main_url(embedded_test_server()->GetURL("portal.test", "/title1.html"));
1499 EXPECT_TRUE(NavigateToURL(shell(), main_url));
1500 WebContentsImpl* web_contents_impl =
1501 static_cast<WebContentsImpl*>(shell()->web_contents());
1502 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
1503
1504 GURL portal_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
1505 Portal* portal = CreatePortalToUrl(web_contents_impl, portal_url);
1506 WebContentsImpl* portal_contents = portal->GetPortalContents();
1507
1508 NavigationControllerImpl& portal_controller =
1509 portal_contents->GetController();
1510
1511 // Have the portal navigate so that we have a pending navigation.
1512 GURL pending_url(embedded_test_server()->GetURL("a.com", "/title2.html"));
1513 TestNavigationManager pending_navigation(portal_contents, pending_url);
1514 EXPECT_TRUE(ExecJs(
1515 main_frame,
1516 JsReplace("document.querySelector('portal').src = $1;", pending_url)));
1517 EXPECT_TRUE(pending_navigation.WaitForRequestStart());
1518
1519 // Navigating via frame proxy does not create a pending NavigationEntry. We'll
1520 // check for an ongoing NavigationRequest instead.
1521 FrameTreeNode* portal_node =
1522 portal_contents->GetMainFrame()->frame_tree_node();
1523 NavigationRequest* navigation_request = portal_node->navigation_request();
1524 ASSERT_TRUE(navigation_request);
1525
1526 PortalActivatedObserver activated_observer(portal);
1527 ASSERT_TRUE(
1528 ExecJs(main_frame, "document.querySelector('portal').activate();"));
1529 activated_observer.WaitForActivate();
1530
1531 NavigationControllerImpl& activated_controller = portal_controller;
1532
1533 pending_navigation.WaitForNavigationFinished();
1534 ASSERT_EQ(2, activated_controller.GetEntryCount());
1535 ASSERT_EQ(1, activated_controller.GetLastCommittedEntryIndex());
1536 EXPECT_EQ(main_url, activated_controller.GetEntryAtIndex(0)->GetURL());
1537 EXPECT_EQ(pending_url, activated_controller.GetEntryAtIndex(1)->GetURL());
1538 }
1539
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,DidFocusIPCFromFrameInsidePortal)1540 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, DidFocusIPCFromFrameInsidePortal) {
1541 EXPECT_TRUE(NavigateToURL(
1542 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
1543 WebContentsImpl* web_contents_impl =
1544 static_cast<WebContentsImpl*>(shell()->web_contents());
1545 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
1546
1547 GURL url = embedded_test_server()->GetURL("a.com", "/title1.html");
1548 Portal* portal = CreatePortalToUrl(web_contents_impl, url);
1549 WebContentsImpl* portal_contents = portal->GetPortalContents();
1550 RenderFrameHostImpl* portal_main_frame = portal_contents->GetMainFrame();
1551
1552 GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
1553 TestNavigationObserver iframe_navigation_observer(portal_contents);
1554 EXPECT_TRUE(ExecJs(portal_main_frame,
1555 JsReplace("var iframe = document.createElement('iframe');"
1556 "iframe.src = $1;"
1557 "document.body.appendChild(iframe);",
1558 b_url)));
1559 iframe_navigation_observer.Wait();
1560 EXPECT_EQ(b_url, iframe_navigation_observer.last_navigation_url());
1561
1562 web_contents_impl->SetAsFocusedWebContentsIfNecessary();
1563 EXPECT_EQ(web_contents_impl->GetFocusedWebContents(), web_contents_impl);
1564 EXPECT_EQ(web_contents_impl->GetFocusedFrame(), main_frame);
1565
1566 // Simulate renderer sending LocalFrameHost::DidFocusFrame IPC.
1567 RenderFrameHostImpl* iframe_rfhi =
1568 portal_main_frame->child_at(0)->current_frame_host();
1569 iframe_rfhi->DidFocusFrame();
1570
1571 // Focus should not have changed.
1572 EXPECT_EQ(web_contents_impl->GetFocusedWebContents(), web_contents_impl);
1573 EXPECT_EQ(web_contents_impl->GetFocusedFrame(), main_frame);
1574
1575 if (!SiteIsolationPolicy::UseDedicatedProcessesForAllSites())
1576 return;
1577
1578 // Simulate renderer sending RemoteFrameHost::DidFocusFrame IPC.
1579 RenderFrameProxyHost* iframe_rfph =
1580 portal_main_frame->child_at(0)->render_manager()->GetRenderFrameProxyHost(
1581 portal_main_frame->GetSiteInstance());
1582 iframe_rfph->DidFocusFrame();
1583
1584 // Focus should not have changed.
1585 EXPECT_EQ(web_contents_impl->GetFocusedWebContents(), web_contents_impl);
1586 EXPECT_EQ(web_contents_impl->GetFocusedFrame(), main_frame);
1587 }
1588
1589 // Test that a renderer process is killed if it sends an AdvanceFocus IPC to
1590 // advance focus into a portal.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,AdvanceFocusIntoPortal)1591 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, AdvanceFocusIntoPortal) {
1592 EXPECT_TRUE(NavigateToURL(
1593 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
1594 WebContentsImpl* web_contents_impl =
1595 static_cast<WebContentsImpl*>(shell()->web_contents());
1596 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
1597
1598 GURL url = embedded_test_server()->GetURL("a.com", "/title1.html");
1599 Portal* portal = CreatePortalToUrl(web_contents_impl, url);
1600 WebContentsImpl* portal_contents = portal->GetPortalContents();
1601 RenderFrameHostImpl* portal_main_frame = portal_contents->GetMainFrame();
1602
1603 RenderFrameProxyHost* outer_delegate_proxy =
1604 portal_main_frame->frame_tree_node()
1605 ->render_manager()
1606 ->GetProxyToOuterDelegate();
1607 RenderProcessHostBadIpcMessageWaiter rph_kill_waiter(
1608 main_frame->GetProcess());
1609 outer_delegate_proxy->OnMessageReceived(FrameHostMsg_AdvanceFocus(
1610 outer_delegate_proxy->GetRoutingID(), blink::mojom::FocusType::kNone,
1611 main_frame->GetRoutingID()));
1612 base::Optional<bad_message::BadMessageReason> result = rph_kill_waiter.Wait();
1613 EXPECT_TRUE(result.has_value());
1614 EXPECT_EQ(result.value(), bad_message::RFPH_ADVANCE_FOCUS_INTO_PORTAL);
1615 }
1616
1617 namespace {
WaitForAccessibilityTree(WebContents * web_contents)1618 void WaitForAccessibilityTree(WebContents* web_contents) {
1619 AccessibilityNotificationWaiter waiter(web_contents, ui::kAXModeComplete,
1620 ax::mojom::Event::kNone);
1621 waiter.WaitForNotification();
1622 }
1623 } // namespace
1624
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,RootAccessibilityManagerShouldUpdateAfterActivation)1625 IN_PROC_BROWSER_TEST_F(PortalBrowserTest,
1626 RootAccessibilityManagerShouldUpdateAfterActivation) {
1627 EXPECT_TRUE(NavigateToURL(
1628 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
1629 WebContentsImpl* web_contents_impl =
1630 static_cast<WebContentsImpl*>(shell()->web_contents());
1631 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
1632 WaitForAccessibilityTree(web_contents_impl);
1633
1634 // Create portal.
1635 GURL url = embedded_test_server()->GetURL("a.com", "/title1.html");
1636 Portal* portal = CreatePortalToUrl(web_contents_impl, url);
1637 WebContentsImpl* portal_contents = portal->GetPortalContents();
1638 RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
1639 WaitForAccessibilityTree(portal_contents);
1640
1641 EXPECT_NE(nullptr, portal_frame->browser_accessibility_manager());
1642 EXPECT_EQ(main_frame->browser_accessibility_manager(),
1643 portal_frame->browser_accessibility_manager()->GetRootManager());
1644
1645 // Activate portal and adopt predecessor.
1646 EXPECT_TRUE(ExecJs(portal_frame,
1647 "window.addEventListener('portalactivate', e => { "
1648 " var portal = e.adoptPredecessor(); "
1649 " document.body.appendChild(portal); "
1650 "});"));
1651 {
1652 PortalActivatedObserver activated_observer(portal);
1653 PortalCreatedObserver adoption_observer(portal_frame);
1654 EXPECT_TRUE(ExecJs(main_frame,
1655 "let portal = document.querySelector('portal');"
1656 "portal.activate().then(() => { "
1657 " document.body.removeChild(portal); "
1658 "});"));
1659 EXPECT_EQ(blink::mojom::PortalActivateResult::kPredecessorWasAdopted,
1660 activated_observer.WaitForActivateResult());
1661 adoption_observer.WaitUntilPortalCreated();
1662 }
1663
1664 EXPECT_EQ(portal_frame->browser_accessibility_manager()->GetRootManager(),
1665 portal_frame->browser_accessibility_manager());
1666 }
1667
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,CrossSitePortalNavCommitsAfterActivation)1668 IN_PROC_BROWSER_TEST_F(PortalBrowserTest,
1669 CrossSitePortalNavCommitsAfterActivation) {
1670 ASSERT_TRUE(NavigateToURL(
1671 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
1672 WebContentsImpl* web_contents_impl =
1673 static_cast<WebContentsImpl*>(shell()->web_contents());
1674 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
1675
1676 GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
1677 Portal* portal = CreatePortalToUrl(web_contents_impl, a_url);
1678
1679 TestNavigationObserver nav_observer(portal->GetPortalContents());
1680 GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
1681 // Start a cross site navigation in the portal contents and immediately
1682 // activate the portal. The navigation starts in a child frame but commits
1683 // after the frame becomes the root frame.
1684 ExecuteScriptAsync(main_frame,
1685 JsReplace("let portal = document.querySelector('portal');"
1686 "portal.src = $1;"
1687 "portal.activate();",
1688 b_url));
1689 nav_observer.Wait();
1690 }
1691
1692 // This is similar to CrossSitePortalNavCommitsAfterActivation, but we navigate
1693 // an adopted predecessor that hasn't been attached and the navigation commits
1694 // after the predecessor is reactivated.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,CrossSitePortalNavInUnattachedPredecessorCommitsAfterActivation)1695 IN_PROC_BROWSER_TEST_F(
1696 PortalBrowserTest,
1697 CrossSitePortalNavInUnattachedPredecessorCommitsAfterActivation) {
1698 ASSERT_TRUE(NavigateToURL(
1699 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
1700 WebContentsImpl* web_contents_impl =
1701 static_cast<WebContentsImpl*>(shell()->web_contents());
1702 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
1703
1704 GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
1705 Portal* portal = CreatePortalToUrl(web_contents_impl, a_url);
1706 WebContentsImpl* portal_contents = portal->GetPortalContents();
1707 RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
1708
1709 GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
1710 EXPECT_TRUE(
1711 ExecJs(portal_frame,
1712 JsReplace("window.addEventListener('portalactivate', (e) => {"
1713 " let predecessor = e.adoptPredecessor();"
1714 " predecessor.src = $1;"
1715 " predecessor.activate();"
1716 "});",
1717 b_url)));
1718
1719 TestNavigationObserver nav_observer(web_contents_impl);
1720 ExecuteScriptAsync(main_frame,
1721 "document.querySelector('portal').activate();");
1722 nav_observer.Wait();
1723 }
1724
1725 // Ensure portal activations respect navigation precedence. If there is an
1726 // ongoing browser initiated navigation, then a portal activation without user
1727 // activation cannot proceed.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,NavigationPrecedence)1728 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, NavigationPrecedence) {
1729 GURL main_url1(embedded_test_server()->GetURL("portal.test", "/title1.html"));
1730 GURL main_url2(embedded_test_server()->GetURL("portal.test", "/title2.html"));
1731 ASSERT_TRUE(NavigateToURL(shell(), main_url1));
1732 ASSERT_TRUE(NavigateToURL(shell(), main_url2));
1733 WebContentsImpl* web_contents_impl =
1734 static_cast<WebContentsImpl*>(shell()->web_contents());
1735 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
1736
1737 GURL portal_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
1738 CreatePortalToUrl(web_contents_impl, portal_url);
1739
1740 TestNavigationManager pending_back_navigation(web_contents_impl, main_url1);
1741 ASSERT_TRUE(web_contents_impl->GetController().CanGoBack());
1742 web_contents_impl->GetController().GoBack();
1743 EXPECT_TRUE(pending_back_navigation.WaitForRequestStart());
1744
1745 EXPECT_EQ("reject", EvalJs(main_frame,
1746 "document.querySelector('portal').activate().then("
1747 " () => 'resolve', () => 'reject');",
1748 EXECUTE_SCRIPT_NO_USER_GESTURE));
1749
1750 pending_back_navigation.WaitForNavigationFinished();
1751 EXPECT_TRUE(pending_back_navigation.was_successful());
1752 }
1753
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,CallCreateProxyAndAttachPortalTwice)1754 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, CallCreateProxyAndAttachPortalTwice) {
1755 EXPECT_TRUE(NavigateToURL(
1756 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
1757 WebContentsImpl* web_contents_impl =
1758 static_cast<WebContentsImpl*>(shell()->web_contents());
1759 RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
1760
1761 GURL url = embedded_test_server()->GetURL("a.com", "/title1.html");
1762 Portal* portal = CreatePortalToUrl(web_contents_impl, url);
1763 PortalInterceptorForTesting* portal_interceptor =
1764 PortalInterceptorForTesting::From(portal);
1765 // Hijacks navigate request and calls CreateProxyAndAttachPortal instead.
1766 portal_interceptor->SetNavigateCallback(base::BindRepeating(
1767 [](Portal* portal, const GURL& url, blink::mojom::ReferrerPtr referrer,
1768 blink::mojom::Portal::NavigateCallback callback) {
1769 portal->CreateProxyAndAttachPortal();
1770 std::move(callback).Run();
1771 },
1772 portal));
1773
1774 RenderProcessHostBadIpcMessageWaiter rph_kill_waiter(
1775 main_frame->GetProcess());
1776 GURL dummy_url = embedded_test_server()->GetURL("b.com", "/title1.html");
1777 ExecuteScriptAsync(
1778 main_frame,
1779 JsReplace("document.querySelector('portal').src = $1", dummy_url));
1780 EXPECT_EQ(bad_message::RPH_MOJO_PROCESS_ERROR, rph_kill_waiter.Wait());
1781 }
1782
1783 class PortalOOPIFBrowserTest : public PortalBrowserTest {
1784 protected:
PortalOOPIFBrowserTest()1785 PortalOOPIFBrowserTest() {}
1786
SetUpCommandLine(base::CommandLine * command_line)1787 void SetUpCommandLine(base::CommandLine* command_line) override {
1788 IsolateAllSitesForTesting(command_line);
1789 }
1790 };
1791
1792 // Tests that creating and destroying OOPIFs inside the portal works as
1793 // intended.
IN_PROC_BROWSER_TEST_F(PortalOOPIFBrowserTest,OOPIFInsidePortal)1794 IN_PROC_BROWSER_TEST_F(PortalOOPIFBrowserTest, OOPIFInsidePortal) {
1795 EXPECT_TRUE(NavigateToURL(
1796 shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
1797 WebContentsImpl* web_contents_impl =
1798 static_cast<WebContentsImpl*>(shell()->web_contents());
1799
1800 GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
1801 Portal* portal = CreatePortalToUrl(web_contents_impl, a_url);
1802 WebContentsImpl* portal_contents = portal->GetPortalContents();
1803 RenderFrameHostImpl* portal_main_frame = portal_contents->GetMainFrame();
1804
1805 // Add an out-of-process iframe to the portal.
1806 GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
1807 TestNavigationObserver iframe_navigation_observer(portal_contents);
1808 EXPECT_TRUE(ExecJs(portal_main_frame,
1809 JsReplace("var iframe = document.createElement('iframe');"
1810 "iframe.src = $1;"
1811 "document.body.appendChild(iframe);",
1812 b_url)));
1813 iframe_navigation_observer.Wait();
1814 EXPECT_EQ(b_url, iframe_navigation_observer.last_navigation_url());
1815 RenderFrameHostImpl* portal_iframe =
1816 portal_main_frame->child_at(0)->current_frame_host();
1817 EXPECT_NE(portal_main_frame->GetSiteInstance(),
1818 portal_iframe->GetSiteInstance());
1819
1820 // Remove the OOPIF from the portal.
1821 RenderFrameDeletedObserver deleted_observer(portal_iframe);
1822 EXPECT_TRUE(
1823 ExecJs(portal_main_frame, "document.querySelector('iframe').remove();"));
1824 deleted_observer.WaitUntilDeleted();
1825 }
1826
1827 } // namespace content
1828