blob: 56550d479da12dd29e558e2277245c95cfb6f437 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/bind.h"
#include "base/callback.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "components/viz/common/features.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/frame_host/render_frame_host_manager.h"
#include "content/browser/frame_host/render_frame_proxy_host.h"
#include "content/browser/portal/portal.h"
#include "content/browser/portal/portal_created_observer.h"
#include "content/browser/portal/portal_interceptor_for_testing.h"
#include "content/browser/renderer_host/input/synthetic_smooth_scroll_gesture.h"
#include "content/browser/renderer_host/input/synthetic_tap_gesture.h"
#include "content/browser/renderer_host/render_widget_host_input_event_router.h"
#include "content/browser/renderer_host/render_widget_host_view_child_frame.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/hit_test_region_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "mojo/public/cpp/bindings/strong_associated_binding.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/portal/portal.mojom.h"
#include "url/url_constants.h"
using testing::_;
namespace content {
class PortalBrowserTest : public ContentBrowserTest {
protected:
PortalBrowserTest() {}
void SetUp() override {
scoped_feature_list_.InitAndEnableFeature(blink::features::kPortals);
ContentBrowserTest::SetUp();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(switches::kValidateInputEventStream);
command_line->AppendSwitchASCII("--enable-blink-features",
"OverscrollCustomization");
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
ContentBrowserTest::SetUpOnMainThread();
ASSERT_TRUE(embedded_test_server()->Start());
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Tests that the renderer can create a Portal.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest, CreatePortal) {
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
PortalCreatedObserver portal_created_observer(main_frame);
EXPECT_TRUE(
ExecJs(main_frame,
"document.body.appendChild(document.createElement('portal'));"));
Portal* portal = portal_created_observer.WaitUntilPortalCreated();
EXPECT_NE(nullptr, portal);
}
// Tests the the renderer can navigate a Portal.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest, NavigatePortal) {
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
PortalCreatedObserver portal_created_observer(main_frame);
// Tests that a portal can navigate by setting its src before appending it to
// the DOM.
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(
ExecJs(main_frame,
base::StringPrintf("var portal = document.createElement('portal');"
"portal.src = '%s';"
"document.body.appendChild(portal);",
a_url.spec().c_str())));
PortalInterceptorForTesting* portal_interceptor =
PortalInterceptorForTesting::From(
portal_created_observer.WaitUntilPortalCreated());
WebContents* portal_contents = portal_interceptor->GetPortalContents();
EXPECT_NE(nullptr, portal_contents);
EXPECT_NE(portal_contents->GetLastCommittedURL(), a_url);
// The portal should not have navigated yet, so we can observe the Portal's
// first navigation.
TestNavigationObserver navigation_observer(portal_contents);
navigation_observer.Wait();
EXPECT_EQ(navigation_observer.last_navigation_url(), a_url);
EXPECT_EQ(portal_contents->GetLastCommittedURL(), a_url);
// Tests that a portal can navigate by setting its src.
{
TestNavigationObserver navigation_observer(portal_contents);
GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
EXPECT_TRUE(ExecJs(
main_frame,
base::StringPrintf("document.querySelector('portal').src = '%s';",
b_url.spec().c_str())));
navigation_observer.Wait();
EXPECT_EQ(navigation_observer.last_navigation_url(), b_url);
EXPECT_EQ(portal_contents->GetLastCommittedURL(), b_url);
}
// Tests that a portal can navigating by attribute.
{
TestNavigationObserver navigation_observer(portal_contents);
GURL c_url(embedded_test_server()->GetURL("c.com", "/title1.html"));
EXPECT_TRUE(ExecJs(
main_frame,
base::StringPrintf(
"document.querySelector('portal').setAttribute('src', '%s');",
c_url.spec().c_str())));
navigation_observer.Wait();
EXPECT_EQ(navigation_observer.last_navigation_url(), c_url);
EXPECT_EQ(portal_contents->GetLastCommittedURL(), c_url);
}
}
// Tests that a portal can be activated.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest, ActivatePortal) {
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
Portal* portal = nullptr;
{
PortalCreatedObserver portal_created_observer(main_frame);
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(ExecJs(
main_frame, JsReplace("var portal = document.createElement('portal');"
"portal.src = $1;"
"document.body.appendChild(portal);",
a_url)));
portal = portal_created_observer.WaitUntilPortalCreated();
}
PortalInterceptorForTesting* portal_interceptor =
PortalInterceptorForTesting::From(portal);
// Ensure that the portal WebContents exists and is different from the tab's
// WebContents.
WebContents* portal_contents = portal->GetPortalContents();
EXPECT_NE(nullptr, portal_contents);
EXPECT_NE(portal_contents, shell()->web_contents());
ExecuteScriptAsync(main_frame,
"document.querySelector('portal').activate();");
portal_interceptor->WaitForActivate();
// After activation, the shell's WebContents should be the previous portal's
// WebContents.
EXPECT_EQ(portal_contents, shell()->web_contents());
}
// Tests if a portal can be activated and the predecessor can be adopted.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest, ReactivatePredecessor) {
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
Portal* portal = nullptr;
{
PortalCreatedObserver portal_created_observer(main_frame);
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(ExecJs(
main_frame, JsReplace("var portal = document.createElement('portal');"
"portal.src = $1;"
"document.body.appendChild(portal);",
a_url)));
portal = portal_created_observer.WaitUntilPortalCreated();
}
PortalInterceptorForTesting* portal_interceptor =
PortalInterceptorForTesting::From(portal);
// Ensure that the portal WebContents exists and is different from the tab's
// WebContents.
WebContentsImpl* portal_contents = portal->GetPortalContents();
EXPECT_NE(nullptr, portal_contents);
EXPECT_NE(portal_contents, shell()->web_contents());
// The portal should not have navigated yet, so we can observe the Portal's
// first navigation.
TestNavigationObserver navigation_observer(portal_contents);
navigation_observer.Wait();
RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
EXPECT_TRUE(ExecJs(portal_frame,
"window.addEventListener('portalactivate', e => { "
" var portal = e.adoptPredecessor(); "
" document.body.appendChild(portal); "
"});"));
{
PortalCreatedObserver adoption_observer(portal_frame);
EXPECT_TRUE(ExecJs(main_frame,
"portal.activate().then(() => { "
" document.body.removeChild(portal); "
"});"));
portal_interceptor->WaitForActivate();
adoption_observer.WaitUntilPortalCreated();
}
// After activation, the shell's WebContents should be the previous portal's
// WebContents.
EXPECT_EQ(portal_contents, shell()->web_contents());
// The original predecessor WebContents should be adopted as a portal.
EXPECT_TRUE(web_contents_impl->IsPortal());
EXPECT_EQ(web_contents_impl->GetOuterWebContents(), portal_contents);
}
// Tests that the RenderFrameProxyHost is created and initialized when the
// portal is initialized.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest, RenderFrameProxyHostCreated) {
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
Portal* portal = nullptr;
PortalCreatedObserver portal_created_observer(main_frame);
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(ExecJs(main_frame,
JsReplace("var portal = document.createElement('portal');"
"portal.src = $1;"
"document.body.appendChild(portal);",
a_url)));
portal = portal_created_observer.WaitUntilPortalCreated();
WebContentsImpl* portal_contents = portal->GetPortalContents();
RenderFrameProxyHost* proxy_host = portal_contents->GetFrameTree()
->root()
->render_manager()
->GetProxyToOuterDelegate();
EXPECT_TRUE(proxy_host->is_render_frame_proxy_live());
}
// Tests that the portal's outer delegate frame tree node and any iframes
// inside the portal are deleted when the portal element is removed from the
// document.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest, DetachPortal) {
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHostImpl* main_frame = web_contents->GetMainFrame();
Portal* portal = nullptr;
PortalCreatedObserver portal_created_observer(main_frame);
GURL a_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(a)"));
EXPECT_TRUE(ExecJs(main_frame,
JsReplace("var portal = document.createElement('portal');"
"portal.src = $1;"
"document.body.appendChild(portal);",
a_url)));
// Wait for portal to be created.
portal = portal_created_observer.WaitUntilPortalCreated();
WebContentsImpl* portal_contents = portal->GetPortalContents();
FrameTreeNode* portal_main_frame_node =
portal_contents->GetFrameTree()->root();
// The portal should not have navigated yet, wait for the first navigation.
TestNavigationObserver navigation_observer(portal_contents);
navigation_observer.Wait();
// Remove portal from document and wait for frames to be deleted.
FrameDeletedObserver fdo1(portal_main_frame_node->render_manager()
->GetOuterDelegateNode()
->current_frame_host());
FrameDeletedObserver fdo2(
portal_main_frame_node->child_at(0)->current_frame_host());
EXPECT_TRUE(ExecJs(main_frame, "document.body.removeChild(portal);"));
fdo1.Wait();
fdo2.Wait();
}
// This is for testing how portals interact with input hit testing. It is
// parameterized on the kind of viz hit testing used.
class PortalHitTestBrowserTest : public PortalBrowserTest,
public ::testing::WithParamInterface<bool> {
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
PortalBrowserTest::SetUpCommandLine(command_line);
IsolateAllSitesForTesting(command_line);
const bool use_viz_hit_test_surface_layer = GetParam();
if (use_viz_hit_test_surface_layer) {
feature_list_.InitAndEnableFeature(
features::kEnableVizHitTestSurfaceLayer);
} else {
feature_list_.InitAndDisableFeature(
features::kEnableVizHitTestSurfaceLayer);
}
}
base::test::ScopedFeatureList feature_list_;
};
INSTANTIATE_TEST_SUITE_P(/* no prefix */,
PortalHitTestBrowserTest,
::testing::Bool());
namespace {
// Fails the test if an input event is sent to the given RenderWidgetHost.
class FailOnInputEvent : public RenderWidgetHost::InputEventObserver {
public:
explicit FailOnInputEvent(RenderWidgetHostImpl* rwh)
: rwh_(rwh->GetWeakPtr()) {
rwh->AddInputEventObserver(this);
}
~FailOnInputEvent() override {
if (rwh_)
rwh_->RemoveInputEventObserver(this);
}
void OnInputEvent(const blink::WebInputEvent& event) override {
FAIL() << "Unexpected " << blink::WebInputEvent::GetName(event.GetType());
}
private:
base::WeakPtr<RenderWidgetHostImpl> rwh_;
};
} // namespace
// Tests that input events targeting the portal are only received by the parent
// renderer.
IN_PROC_BROWSER_TEST_P(PortalHitTestBrowserTest, DispatchInputEvent) {
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
// Create portal and wait for navigation.
Portal* portal = nullptr;
PortalCreatedObserver portal_created_observer(main_frame);
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(ExecJs(main_frame,
JsReplace("var portal = document.createElement('portal');"
"portal.src = $1;"
"document.body.appendChild(portal);",
a_url)));
portal = portal_created_observer.WaitUntilPortalCreated();
WebContentsImpl* portal_contents = portal->GetPortalContents();
RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
EXPECT_TRUE(static_cast<RenderWidgetHostViewBase*>(portal_frame->GetView())
->IsRenderWidgetHostViewChildFrame());
RenderWidgetHostViewChildFrame* portal_view =
static_cast<RenderWidgetHostViewChildFrame*>(portal_frame->GetView());
TestNavigationObserver navigation_observer(portal_contents);
navigation_observer.Wait();
WaitForHitTestData(portal_frame);
FailOnInputEvent no_input_to_portal_frame(
portal_frame->GetRenderWidgetHost());
EXPECT_TRUE(ExecJs(main_frame,
"var clicked = false;"
"portal.onmousedown = _ => clicked = true;"));
EXPECT_TRUE(ExecJs(portal_frame,
"var clicked = false;"
"document.body.onmousedown = _ => clicked = true;"));
EXPECT_EQ(false, EvalJs(main_frame, "clicked"));
EXPECT_EQ(false, EvalJs(portal_frame, "clicked"));
// Route the mouse event.
gfx::Point root_location =
portal_view->TransformPointToRootCoordSpace(gfx::Point(5, 5));
InputEventAckWaiter waiter(main_frame->GetRenderWidgetHost(),
blink::WebInputEvent::kMouseDown);
SimulateRoutedMouseEvent(web_contents_impl, blink::WebInputEvent::kMouseDown,
blink::WebPointerProperties::Button::kLeft,
root_location);
waiter.Wait();
// Check that the click event was only received by the main frame.
EXPECT_EQ(true, EvalJs(main_frame, "clicked"));
EXPECT_EQ(false, EvalJs(portal_frame, "clicked"));
}
// Tests that input events performed over on OOPIF inside a portal are targeted
// to the portal's parent.
IN_PROC_BROWSER_TEST_P(PortalHitTestBrowserTest, NoInputToOOPIFInPortal) {
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
// Create portal and wait for navigation.
// In the case of crbug.com/1002228 , this does not appear to reproduce if the
// portal element is too small, so we give it an explicit size.
Portal* portal = nullptr;
PortalCreatedObserver portal_created_observer(main_frame);
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(ExecJs(main_frame,
JsReplace("var portal = document.createElement('portal');"
"portal.src = $1;"
"portal.style.width = '500px';"
"portal.style.height = '500px';"
"portal.style.border = 'solid';"
"document.body.appendChild(portal);",
a_url)));
portal = portal_created_observer.WaitUntilPortalCreated();
WebContentsImpl* portal_contents = portal->GetPortalContents();
RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
TestNavigationObserver navigation_observer(portal_contents);
navigation_observer.Wait();
WaitForHitTestData(portal_frame);
// Add an out-of-process iframe to the portal.
GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
TestNavigationObserver iframe_navigation_observer(portal_contents);
EXPECT_TRUE(ExecJs(portal_frame,
JsReplace("var iframe = document.createElement('iframe');"
"iframe.src = $1;"
"document.body.appendChild(iframe);",
b_url)));
iframe_navigation_observer.Wait();
EXPECT_EQ(b_url, iframe_navigation_observer.last_navigation_url());
RenderFrameHostImpl* portal_iframe =
portal_frame->child_at(0)->current_frame_host();
EXPECT_TRUE(static_cast<RenderWidgetHostViewBase*>(portal_iframe->GetView())
->IsRenderWidgetHostViewChildFrame());
RenderWidgetHostViewChildFrame* oopif_view =
static_cast<RenderWidgetHostViewChildFrame*>(portal_iframe->GetView());
EXPECT_NE(portal_frame->GetSiteInstance(), portal_iframe->GetSiteInstance());
WaitForHitTestData(portal_iframe);
FailOnInputEvent no_input_to_portal_frame(
portal_frame->GetRenderWidgetHost());
FailOnInputEvent no_input_to_oopif(portal_iframe->GetRenderWidgetHost());
EXPECT_TRUE(ExecJs(main_frame,
"var clicked = false;"
"portal.onmousedown = _ => clicked = true;"));
EXPECT_TRUE(ExecJs(portal_frame,
"var clicked = false;"
"document.body.onmousedown = _ => clicked = true;"));
EXPECT_TRUE(ExecJs(portal_iframe,
"var clicked = false;"
"document.body.onmousedown = _ => clicked = true;"));
// Route the mouse event.
gfx::Point root_location =
oopif_view->TransformPointToRootCoordSpace(gfx::Point(5, 5));
InputEventAckWaiter waiter(main_frame->GetRenderWidgetHost(),
blink::WebInputEvent::kMouseDown);
SimulateRoutedMouseEvent(web_contents_impl, blink::WebInputEvent::kMouseDown,
blink::WebPointerProperties::Button::kLeft,
root_location);
waiter.Wait();
// Check that the click event was only received by the main frame.
EXPECT_EQ(true, EvalJs(main_frame, "clicked"));
EXPECT_EQ(false, EvalJs(portal_frame, "clicked"));
EXPECT_EQ(false, EvalJs(portal_iframe, "clicked"));
}
// Tests that async hit testing does not target portals.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest, AsyncEventTargetingIgnoresPortals) {
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
// Create portal and wait for navigation.
PortalCreatedObserver portal_created_observer(main_frame);
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(ExecJs(main_frame,
JsReplace("var portal = document.createElement('portal');"
"portal.src = $1;"
"document.body.appendChild(portal);",
a_url)));
Portal* portal = portal_created_observer.WaitUntilPortalCreated();
WebContentsImpl* portal_contents = portal->GetPortalContents();
RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
ASSERT_TRUE(static_cast<RenderWidgetHostViewBase*>(portal_frame->GetView())
->IsRenderWidgetHostViewChildFrame());
RenderWidgetHostViewChildFrame* portal_view =
static_cast<RenderWidgetHostViewChildFrame*>(portal_frame->GetView());
TestNavigationObserver navigation_observer(portal_contents);
navigation_observer.Wait();
WaitForHitTestData(portal_frame);
viz::mojom::InputTargetClient* target_client =
main_frame->GetRenderWidgetHost()->input_target_client();
ASSERT_TRUE(target_client);
gfx::PointF root_location =
portal_view->TransformPointToRootCoordSpaceF(gfx::PointF(5, 5));
// Query the renderer for the target widget. The root should claim the point
// for itself, not the portal.
base::RunLoop run_loop;
base::OnceClosure quit_closure = run_loop.QuitClosure();
viz::FrameSinkId received_frame_sink_id;
target_client->FrameSinkIdAt(
root_location, 0,
base::BindLambdaForTesting(
[&](const viz::FrameSinkId& id, const gfx::PointF& point) {
received_frame_sink_id = id;
std::move(quit_closure).Run();
}));
run_loop.Run();
viz::FrameSinkId root_frame_sink_id =
static_cast<RenderWidgetHostViewBase*>(main_frame->GetView())
->GetFrameSinkId();
EXPECT_EQ(root_frame_sink_id, received_frame_sink_id)
<< "Note: The portal's FrameSinkId is " << portal_view->GetFrameSinkId();
}
// Tests that trying to navigate to a chrome:// URL kills the renderer.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest, NavigateToChrome) {
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
// Create portal.
PortalCreatedObserver portal_created_observer(main_frame);
EXPECT_TRUE(ExecJs(main_frame,
"var portal = document.createElement('portal');"
"document.body.appendChild(portal);"));
Portal* portal = portal_created_observer.WaitUntilPortalCreated();
PortalInterceptorForTesting* portal_interceptor =
PortalInterceptorForTesting::From(portal);
// Try to navigate to chrome://settings and wait for the process to die.
portal_interceptor->SetNavigateCallback(base::BindRepeating(
[](Portal* portal, const GURL& url, blink::mojom::ReferrerPtr referrer,
blink::mojom::Portal::NavigateCallback callback) {
GURL chrome_url("chrome://settings");
portal->Navigate(chrome_url, std::move(referrer), std::move(callback));
},
portal));
RenderProcessHostKillWaiter kill_waiter(main_frame->GetProcess());
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
ignore_result(ExecJs(main_frame, JsReplace("portal.src = $1;", a_url)));
EXPECT_EQ(bad_message::RPH_MOJO_PROCESS_ERROR, kill_waiter.Wait());
}
// Regression test for crbug.com/969714. Tests that receiving a touch ack
// from the predecessor after portal activation doesn't cause a crash.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest, TouchAckAfterActivate) {
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
// Create portal and wait for navigation.
Portal* portal = nullptr;
{
PortalCreatedObserver portal_created_observer(main_frame);
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(ExecJs(
main_frame, JsReplace("var portal = document.createElement('portal');"
"portal.src = $1;"
"document.body.appendChild(portal);"
"document.body.addEventListener('touchstart', "
"e => { portal.activate(); }, {passive: false});",
a_url)));
portal = portal_created_observer.WaitUntilPortalCreated();
}
WebContentsImpl* portal_contents = portal->GetPortalContents();
// The portal should not have navigated yet, wait for the first navigation.
TestNavigationObserver navigation_observer(portal_contents);
navigation_observer.Wait();
PortalInterceptorForTesting* portal_interceptor =
PortalInterceptorForTesting::From(portal);
RenderWidgetHostImpl* render_widget_host = main_frame->GetRenderWidgetHost();
RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
RenderWidgetHostViewChildFrame* portal_view =
static_cast<RenderWidgetHostViewChildFrame*>(portal_frame->GetView());
InputEventAckWaiter input_event_ack_waiter(
render_widget_host, blink::WebInputEvent::Type::kTouchStart);
WaitForHitTestData(portal_frame);
SyntheticTapGestureParams params;
params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
params.position =
portal_view->TransformPointToRootCoordSpaceF(gfx::PointF(5, 5));
std::unique_ptr<SyntheticTapGesture> gesture =
std::make_unique<SyntheticTapGesture>(params);
render_widget_host->QueueSyntheticGesture(
std::move(gesture), base::Bind([](SyntheticGesture::Result) {}));
portal_interceptor->WaitForActivate();
EXPECT_EQ(portal_contents, shell()->web_contents());
// Wait for a touch ack to be sent from the predecessor.
input_event_ack_waiter.Wait();
}
// Regression test for crbug.com/973647. Tests that receiving a touch ack
// after activation and predecessor adoption doesn't cause a crash.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest, TouchAckAfterActivateAndAdopt) {
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
// Create portal and wait for navigation.
Portal* portal = nullptr;
{
PortalCreatedObserver portal_created_observer(main_frame);
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
const int TOUCH_ACK_DELAY_IN_MILLISECONDS = 500;
EXPECT_TRUE(
ExecJs(main_frame,
JsReplace("var portal = document.createElement('portal');"
"portal.src = $1;"
"document.body.appendChild(portal);"
"document.body.addEventListener('touchstart', e => {"
" portal.activate();"
" var stop = performance.now() + $2;"
" while (performance.now() < stop) {}"
"}, {passive: false});",
a_url, TOUCH_ACK_DELAY_IN_MILLISECONDS)));
portal = portal_created_observer.WaitUntilPortalCreated();
}
WebContentsImpl* portal_contents = portal->GetPortalContents();
// The portal should not have navigated yet, wait for the first navigation.
TestNavigationObserver navigation_observer(portal_contents);
navigation_observer.Wait();
RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
EXPECT_TRUE(ExecJs(portal_frame,
"window.addEventListener('portalactivate', e => {"
" var portal = e.adoptPredecessor();"
" document.body.appendChild(portal);"
"});"));
WaitForHitTestData(portal_frame);
PortalInterceptorForTesting* portal_interceptor =
PortalInterceptorForTesting::From(portal);
RenderWidgetHostImpl* render_widget_host = main_frame->GetRenderWidgetHost();
InputEventAckWaiter input_event_ack_waiter(
render_widget_host, blink::WebInputEvent::Type::kTouchStart);
SyntheticTapGestureParams params;
RenderWidgetHostViewChildFrame* portal_view =
static_cast<RenderWidgetHostViewChildFrame*>(portal_frame->GetView());
params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
params.position =
portal_view->TransformPointToRootCoordSpaceF(gfx::PointF(5, 5));
std::unique_ptr<SyntheticTapGesture> gesture =
std::make_unique<SyntheticTapGesture>(params);
{
PortalCreatedObserver adoption_observer(portal_frame);
render_widget_host->QueueSyntheticGesture(
std::move(gesture), base::Bind([](SyntheticGesture::Result) {}));
portal_interceptor->WaitForActivate();
EXPECT_EQ(portal_contents, shell()->web_contents());
// Wait for predecessor to be adopted.
adoption_observer.WaitUntilPortalCreated();
}
// Wait for a touch ack to be sent from the predecessor.
input_event_ack_waiter.Wait();
}
// Regression test for crbug.com/973647. Tests that receiving a touch ack
// after activation and reactivating a predecessor doesn't cause a crash.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest, TouchAckAfterActivateAndReactivate) {
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
// Create portal and wait for navigation.
Portal* portal = nullptr;
{
PortalCreatedObserver portal_created_observer(main_frame);
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
const int TOUCH_ACK_DELAY_IN_MILLISECONDS = 500;
EXPECT_TRUE(
ExecJs(main_frame,
JsReplace("var portal = document.createElement('portal');"
"portal.src = $1;"
"document.body.appendChild(portal);"
"document.body.addEventListener('touchstart', e => {"
" portal.activate();"
" var stop = performance.now() + $2;"
" while (performance.now() < stop) {}"
"}, {passive: false});",
a_url, TOUCH_ACK_DELAY_IN_MILLISECONDS)));
portal = portal_created_observer.WaitUntilPortalCreated();
}
WebContentsImpl* portal_contents = portal->GetPortalContents();
// The portal should not have navigated yet, wait for the first navigation.
TestNavigationObserver navigation_observer(portal_contents);
navigation_observer.Wait();
RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
EXPECT_TRUE(ExecJs(portal_frame,
"window.addEventListener('portalactivate', e => {"
" var portal = e.adoptPredecessor();"
" document.body.appendChild(portal);"
" portal.activate();"
"});"));
WaitForHitTestData(portal_frame);
PortalInterceptorForTesting* portal_interceptor =
PortalInterceptorForTesting::From(portal);
RenderWidgetHostImpl* render_widget_host = main_frame->GetRenderWidgetHost();
InputEventAckWaiter input_event_ack_waiter(
render_widget_host, blink::WebInputEvent::Type::kTouchStart);
SyntheticTapGestureParams params;
params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
params.position = gfx::PointF(20, 20);
std::unique_ptr<SyntheticTapGesture> gesture =
std::make_unique<SyntheticTapGesture>(params);
Portal* predecessor_portal = nullptr;
{
PortalCreatedObserver adoption_observer(portal_frame);
render_widget_host->QueueSyntheticGesture(
std::move(gesture), base::Bind([](SyntheticGesture::Result) {}));
portal_interceptor->WaitForActivate();
EXPECT_EQ(portal_contents, shell()->web_contents());
predecessor_portal = adoption_observer.WaitUntilPortalCreated();
}
portal_interceptor = PortalInterceptorForTesting::From(predecessor_portal);
portal_interceptor->WaitForActivate();
// Sanity check to see if the predecessor was reactivated.
EXPECT_EQ(web_contents_impl, shell()->web_contents());
// Wait for a touch ack to be sent from the predecessor.
input_event_ack_waiter.Wait();
}
// TODO(crbug.com/985078): Fix on Mac.
#if !defined(OS_MACOSX)
IN_PROC_BROWSER_TEST_F(PortalBrowserTest, TouchStateClearedBeforeActivation) {
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
// Create portal and wait for navigation.
Portal* portal = nullptr;
{
PortalCreatedObserver portal_created_observer(main_frame);
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(
ExecJs(main_frame,
JsReplace("var portal = document.createElement('portal');"
"portal.src = $1;"
"document.body.appendChild(portal);"
"document.body.addEventListener('touchstart', e => {"
" portal.activate();"
"}, {passive: false});",
a_url)));
portal = portal_created_observer.WaitUntilPortalCreated();
}
WebContentsImpl* portal_contents = portal->GetPortalContents();
// The portal should not have navigated yet, wait for the first navigation.
TestNavigationObserver navigation_observer(portal_contents);
navigation_observer.Wait();
RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
EXPECT_TRUE(ExecJs(portal_frame,
"window.addEventListener('portalactivate', e => {"
" var portal = e.adoptPredecessor();"
" document.body.appendChild(portal);"
" portal.activate();"
"});"));
WaitForHitTestData(portal_frame);
PortalInterceptorForTesting* portal_interceptor =
PortalInterceptorForTesting::From(portal);
RenderWidgetHostImpl* render_widget_host = main_frame->GetRenderWidgetHost();
SyntheticTapGestureParams params;
params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
params.position = gfx::PointF(20, 20);
// Activate the portal, and then wait for the predecessor to be reactivated.
Portal* adopted_portal = nullptr;
{
PortalCreatedObserver adoption_observer(portal_frame);
std::unique_ptr<SyntheticTapGesture> gesture =
std::make_unique<SyntheticTapGesture>(params);
InputEventAckWaiter input_event_ack_waiter(
render_widget_host, blink::WebInputEvent::Type::kTouchCancel);
render_widget_host->QueueSyntheticGestureCompleteImmediately(
std::move(gesture));
// Wait for synthetic cancel event to be sent.
input_event_ack_waiter.Wait();
portal_interceptor->WaitForActivate();
EXPECT_EQ(portal_contents, shell()->web_contents());
adopted_portal = adoption_observer.WaitUntilPortalCreated();
}
PortalInterceptorForTesting* adopted_portal_interceptor =
PortalInterceptorForTesting::From(adopted_portal);
adopted_portal_interceptor->WaitForActivate();
// Sanity check to see if the predecessor was reactivated.
EXPECT_EQ(web_contents_impl, shell()->web_contents());
InputEventAckWaiter input_event_ack_waiter(
render_widget_host, blink::WebInputEvent::Type::kTouchStart);
std::unique_ptr<SyntheticTapGesture> gesture =
std::make_unique<SyntheticTapGesture>(params);
render_widget_host->QueueSyntheticGesture(
std::move(gesture), base::Bind([](SyntheticGesture::Result) {}));
// Waits for touch to be acked. If touch state wasn't cleared before initial
// activation, a DCHECK will be hit before the ack is sent.
input_event_ack_waiter.Wait();
}
#endif
// TODO(crbug.com/985078): Fix on Mac.
#if !defined(OS_MACOSX)
IN_PROC_BROWSER_TEST_F(PortalBrowserTest, GestureCleanedUpBeforeActivation) {
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
// Create portal and wait for navigation.
Portal* portal = nullptr;
{
PortalCreatedObserver portal_created_observer(main_frame);
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(
ExecJs(main_frame,
JsReplace("var portal = document.createElement('portal');"
"portal.src = $1;"
"document.body.appendChild(portal);"
"document.body.addEventListener('touchstart', e => {"
" portal.activate();"
"}, {once: true});",
a_url)));
portal = portal_created_observer.WaitUntilPortalCreated();
}
WebContentsImpl* portal_contents = portal->GetPortalContents();
// The portal should not have navigated yet, wait for the first navigation.
TestNavigationObserver navigation_observer(portal_contents);
navigation_observer.Wait();
RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
EXPECT_TRUE(ExecJs(portal_frame,
"window.addEventListener('portalactivate', e => {"
" var portal = e.adoptPredecessor();"
" document.body.appendChild(portal);"
" portal.activate(); "
"});"));
WaitForHitTestData(portal_frame);
PortalInterceptorForTesting* portal_interceptor =
PortalInterceptorForTesting::From(portal);
RenderWidgetHostImpl* render_widget_host = main_frame->GetRenderWidgetHost();
SyntheticTapGestureParams params;
params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
params.position = gfx::PointF(20, 20);
params.duration_ms = 1;
// Simulate a tap and activate the portal.
Portal* adopted_portal = nullptr;
{
PortalCreatedObserver adoption_observer(portal_frame);
std::unique_ptr<SyntheticTapGesture> gesture =
std::make_unique<SyntheticTapGesture>(params);
render_widget_host->QueueSyntheticGestureCompleteImmediately(
std::move(gesture));
portal_interceptor->WaitForActivate();
EXPECT_EQ(portal_contents, shell()->web_contents());
adopted_portal = adoption_observer.WaitUntilPortalCreated();
}
// Wait for predecessor to be reactivated.
PortalInterceptorForTesting* adopted_portal_interceptor =
PortalInterceptorForTesting::From(adopted_portal);
adopted_portal_interceptor->WaitForActivate();
EXPECT_EQ(web_contents_impl, shell()->web_contents());
// Simulate another tap.
InputEventAckWaiter input_event_ack_waiter(
render_widget_host, blink::WebInputEvent::Type::kGestureTap);
auto gesture = std::make_unique<SyntheticTapGesture>(params);
render_widget_host->QueueSyntheticGesture(
std::move(gesture), base::Bind([](SyntheticGesture::Result) {}));
// Wait for the tap gesture ack. If the initial gesture wasn't cleaned up, the
// new gesture created will cause an error in the gesture validator.
input_event_ack_waiter.Wait();
}
#endif
// Touch input transfer is only implemented in the content layer for Aura.
#if defined(USE_AURA)
IN_PROC_BROWSER_TEST_F(PortalBrowserTest, TouchInputTransferAcrossActivation) {
EXPECT_TRUE(NavigateToURL(
shell(),
embedded_test_server()->GetURL("portal.test", "/portals/scroll.html")));
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
RenderWidgetHostImpl* render_widget_host = main_frame->GetRenderWidgetHost();
// Create portal and wait for navigation.
Portal* portal = nullptr;
{
PortalCreatedObserver portal_created_observer(main_frame);
GURL portal_url(embedded_test_server()->GetURL(
"portal.test", "/portals/scroll-portal.html"));
EXPECT_TRUE(ExecJs(
main_frame, JsReplace("var portal = document.createElement('portal');"
"portal.src = $1;"
"document.body.appendChild(portal);",
portal_url)));
portal = portal_created_observer.WaitUntilPortalCreated();
}
WebContentsImpl* portal_contents = portal->GetPortalContents();
RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
// The portal should not have navigated yet, wait for the first navigation.
TestNavigationObserver navigation_observer(portal_contents);
navigation_observer.Wait();
WaitForHitTestData(portal_frame);
// Create and dispatch a synthetic scroll to trigger activation.
SyntheticSmoothScrollGestureParams params;
params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
params.anchor = gfx::PointF(50, 150);
params.distances.push_back(-gfx::Vector2d(0, 100));
std::unique_ptr<SyntheticSmoothScrollGesture> gesture =
std::make_unique<SyntheticSmoothScrollGesture>(params);
base::RunLoop run_loop;
render_widget_host->QueueSyntheticGesture(
std::move(gesture),
base::BindLambdaForTesting([&](SyntheticGesture::Result result) {
EXPECT_EQ(SyntheticGesture::Result::GESTURE_FINISHED, result);
run_loop.Quit();
}));
run_loop.Run();
// Check if the activated page scrolled.
EXPECT_NE(0, EvalJs(portal_frame, "window.scrollY"));
}
#endif
// Touch input transfer is only implemented in the content layer for Aura.
#if defined(USE_AURA)
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,
TouchInputTransferAcrossReactivation) {
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL(
"portal.test",
"/portals/touch-input-transfer-across-reactivation.html")));
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
RenderWidgetHostImpl* render_widget_host = main_frame->GetRenderWidgetHost();
// Create portal and wait for navigation.
Portal* portal = nullptr;
{
PortalCreatedObserver portal_created_observer(main_frame);
GURL portal_url(embedded_test_server()->GetURL(
"portal.test", "/portals/reactivate-predecessor.html"));
EXPECT_TRUE(ExecJs(
main_frame, JsReplace("var portal = document.createElement('portal');"
"portal.src = $1;"
"document.body.appendChild(portal);",
portal_url)));
portal = portal_created_observer.WaitUntilPortalCreated();
}
WebContentsImpl* portal_contents = portal->GetPortalContents();
// The portal should not have navigated yet, wait for the first navigation.
TestNavigationObserver navigation_observer(portal_contents);
navigation_observer.Wait();
PortalInterceptorForTesting* portal_interceptor =
PortalInterceptorForTesting::From(portal);
// Create and dispatch a synthetic scroll to trigger activation.
SyntheticSmoothScrollGestureParams params;
params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
params.anchor = gfx::PointF(50, 150);
params.distances.push_back(-gfx::Vector2d(0, 1000));
std::unique_ptr<SyntheticSmoothScrollGesture> gesture =
std::make_unique<SyntheticSmoothScrollGesture>(params);
base::RunLoop run_loop;
render_widget_host->QueueSyntheticGesture(
std::move(gesture),
base::BindLambdaForTesting([&](SyntheticGesture::Result result) {
EXPECT_EQ(SyntheticGesture::Result::GESTURE_FINISHED, result);
run_loop.Quit();
}));
// Portal should activate when the gesture begins.
portal_interceptor->WaitForActivate();
// Wait till the scroll gesture finishes.
run_loop.Run();
// The predecessor should have been reactivated (we should be back to the
// starting page).
EXPECT_EQ(web_contents_impl, shell()->web_contents());
// The starting page should have scrolled.
// NOTE: This assumes that the scroll gesture is long enough that touch events
// are still sent after the predecessor is reactivated.
EXPECT_NE(0, EvalJs(main_frame, "window.scrollY"));
}
#endif
// Tests that the outer FrameTreeNode is deleted after activation.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest, FrameDeletedAfterActivation) {
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
Portal* portal = nullptr;
{
PortalCreatedObserver portal_created_observer(main_frame);
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(ExecJs(
main_frame, JsReplace("var portal = document.createElement('portal');"
"portal.src = $1;"
"document.body.appendChild(portal);",
a_url)));
portal = portal_created_observer.WaitUntilPortalCreated();
}
WebContentsImpl* portal_contents = portal->GetPortalContents();
// The portal should not have navigated yet; wait for the first navigation.
TestNavigationObserver navigation_observer(portal_contents);
navigation_observer.Wait();
FrameTreeNode* outer_frame_tree_node = FrameTreeNode::GloballyFindByID(
portal_contents->GetOuterDelegateFrameTreeNodeId());
EXPECT_TRUE(outer_frame_tree_node);
EXPECT_TRUE(ExecJs(portal_contents->GetMainFrame(),
"window.onportalactivate = e => "
"document.body.appendChild(e.adoptPredecessor());"));
{
FrameDeletedObserver observer(outer_frame_tree_node->current_frame_host());
PortalCreatedObserver portal_created_observer(
portal_contents->GetMainFrame());
ExecuteScriptAsync(main_frame,
"document.querySelector('portal').activate();");
observer.Wait();
// Observes the creation of a new portal due to the adoption of the
// predecessor during the activate event.
// TODO(lfg): We only wait for the adoption callback to avoid a race
// receiving a sync IPC in a nested message loop while the browser is
// sending out another sync IPC to the GPU process.
// https://crbug.com/976367.
portal_created_observer.WaitUntilPortalCreated();
}
}
// Tests that activating a portal at the same time as it is being removed
// doesn't crash the browser.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest, RemovePortalWhenUnloading) {
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
// Create a container for the portal.
EXPECT_TRUE(ExecJs(main_frame,
"var div = document.createElement('div');"
"document.body.appendChild(div);"));
// Create portal.
PortalCreatedObserver portal_created_observer(main_frame);
EXPECT_TRUE(ExecJs(main_frame,
"var portal = document.createElement('portal');"
"div.appendChild(portal);"));
// Add a same-origin iframe in the same div as the portal that activates the
// portal on its unload handler.
EXPECT_TRUE(
ExecJs(main_frame,
"var iframe = document.createElement('iframe');"
"iframe.src = 'about:blank';"
"div.appendChild(iframe);"
"iframe.contentWindow.onunload = () => portal.activate();"));
// Remove the div from the document. This destroys the portal's WebContents
// and should destroy the Portal object as well, so that the activate message
// is not processed.
EXPECT_TRUE(ExecJs(main_frame, "div.remove();"));
}
// Tests that a portal can navigate while orphaned.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest, OrphanedNavigation) {
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
Portal* portal = nullptr;
{
PortalCreatedObserver portal_created_observer(main_frame);
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(ExecJs(
main_frame, JsReplace("var portal = document.createElement('portal');"
"portal.src = $1;"
"document.body.appendChild(portal);",
a_url)));
portal = portal_created_observer.WaitUntilPortalCreated();
}
PortalInterceptorForTesting* portal_interceptor =
PortalInterceptorForTesting::From(portal);
WebContentsImpl* portal_contents = portal->GetPortalContents();
// The portal should not have navigated yet; wait for the first navigation.
TestNavigationObserver navigation_observer(portal_contents);
navigation_observer.Wait();
// Block the activate callback so that the predecessor portal stays orphaned.
EXPECT_TRUE(ExecJs(portal_contents->GetMainFrame(),
"window.onportalactivate = e => { while(true) {} };"));
// Acticate the portal and navigate the predecessor.
TestNavigationObserver main_frame_navigation_observer(web_contents_impl);
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
ExecuteScriptAsync(main_frame,
"document.querySelector('portal').activate();"
"window.location.reload()");
portal_interceptor->WaitForActivate();
main_frame_navigation_observer.Wait();
}
// Tests that the browser doesn't crash if the renderer tries to create the
// PortalHost after the parent renderer dropped the portal.
IN_PROC_BROWSER_TEST_F(PortalBrowserTest,
AccessPortalHostAfterPortalDestruction) {
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
Portal* portal = nullptr;
{
PortalCreatedObserver portal_created_observer(main_frame);
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(ExecJs(
main_frame, JsReplace("var portal = document.createElement('portal');"
"portal.src = $1;"
"document.body.appendChild(portal);",
a_url)));
portal = portal_created_observer.WaitUntilPortalCreated();
}
WebContentsImpl* portal_contents = portal->GetPortalContents();
RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame();
// The portal should not have navigated yet; wait for the first navigation.
TestNavigationObserver navigation_observer(portal_contents);
navigation_observer.Wait();
// Simulate the portal being dropped, but not the destruction of the
// WebContents.
portal->GetBindingForTesting()->SwapImplForTesting(nullptr);
// Get the portal renderer to access the WebContents.
RenderProcessHostKillWaiter kill_waiter(portal_frame->GetProcess());
ExecuteScriptAsync(portal_frame,
"window.portalHost.postMessage('message', '*');");
EXPECT_EQ(bad_message::RPH_MOJO_PROCESS_ERROR, kill_waiter.Wait());
}
class PortalOOPIFBrowserTest : public PortalBrowserTest {
protected:
PortalOOPIFBrowserTest() {}
void SetUpCommandLine(base::CommandLine* command_line) override {
IsolateAllSitesForTesting(command_line);
}
};
// Tests that creating and destroying OOPIFs inside the portal works as
// intended.
IN_PROC_BROWSER_TEST_F(PortalOOPIFBrowserTest, OOPIFInsidePortal) {
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
// Create portal and wait for navigation.
PortalCreatedObserver portal_created_observer(main_frame);
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(ExecJs(main_frame,
JsReplace("var portal = document.createElement('portal');"
"portal.src = $1;"
"document.body.appendChild(portal);",
a_url)));
Portal* portal = portal_created_observer.WaitUntilPortalCreated();
WebContentsImpl* portal_contents = portal->GetPortalContents();
RenderFrameHostImpl* portal_main_frame = portal_contents->GetMainFrame();
TestNavigationObserver portal_navigation_observer(portal_contents);
portal_navigation_observer.Wait();
// Add an out-of-process iframe to the portal.
GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
TestNavigationObserver iframe_navigation_observer(portal_contents);
EXPECT_TRUE(ExecJs(portal_main_frame,
JsReplace("var iframe = document.createElement('iframe');"
"iframe.src = $1;"
"document.body.appendChild(iframe);",
b_url)));
iframe_navigation_observer.Wait();
EXPECT_EQ(b_url, iframe_navigation_observer.last_navigation_url());
RenderFrameHostImpl* portal_iframe =
portal_main_frame->child_at(0)->current_frame_host();
EXPECT_NE(portal_main_frame->GetSiteInstance(),
portal_iframe->GetSiteInstance());
// Remove the OOPIF from the portal.
RenderFrameDeletedObserver deleted_observer(portal_iframe);
EXPECT_TRUE(
ExecJs(portal_main_frame, "document.querySelector('iframe').remove();"));
deleted_observer.WaitUntilDeleted();
}
} // namespace content