diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-12 14:27:29 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:35:20 +0000 |
commit | c30a6232df03e1efbd9f3b226777b07e087a1122 (patch) | |
tree | e992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/content/browser/portal | |
parent | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff) | |
download | qtwebengine-chromium-85-based.tar.gz |
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/content/browser/portal')
-rw-r--r-- | chromium/content/browser/portal/portal.cc | 151 | ||||
-rw-r--r-- | chromium/content/browser/portal/portal.h | 9 | ||||
-rw-r--r-- | chromium/content/browser/portal/portal_browsertest.cc | 411 |
3 files changed, 481 insertions, 90 deletions
diff --git a/chromium/content/browser/portal/portal.cc b/chromium/content/browser/portal/portal.cc index ce5f6fe75fe..1c4921f71eb 100644 --- a/chromium/content/browser/portal/portal.cc +++ b/chromium/content/browser/portal/portal.cc @@ -9,6 +9,7 @@ #include "base/feature_list.h" #include "base/memory/ptr_util.h" +#include "content/browser/bad_message.h" #include "content/browser/child_process_security_policy_impl.h" #include "content/browser/devtools/devtools_instrumentation.h" #include "content/browser/frame_host/navigation_request.h" @@ -30,6 +31,15 @@ namespace content { +namespace { +void CreatePortalRenderWidgetHostView(WebContentsImpl* web_contents, + RenderViewHostImpl* render_view_host) { + if (auto* view = render_view_host->GetWidget()->GetView()) + view->Destroy(); + web_contents->CreateRenderWidgetHostViewForRenderManager(render_view_host); +} +} // namespace + Portal::Portal(RenderFrameHostImpl* owner_render_frame_host) : WebContentsObserver( WebContents::FromRenderFrameHost(owner_render_frame_host)), @@ -44,17 +54,14 @@ Portal::Portal(RenderFrameHostImpl* owner_render_frame_host, } Portal::~Portal() { - WebContentsImpl* outer_contents_impl = static_cast<WebContentsImpl*>( - WebContents::FromRenderFrameHost(owner_render_frame_host_)); - devtools_instrumentation::PortalDetached(outer_contents_impl->GetMainFrame()); + devtools_instrumentation::PortalDetached( + GetPortalHostContents()->GetMainFrame()); Observe(nullptr); } // static bool Portal::IsEnabled() { - return base::FeatureList::IsEnabled(blink::features::kPortals) || - base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kEnableExperimentalWebPlatformFeatures); + return base::FeatureList::IsEnabled(blink::features::kPortals); } // static @@ -109,8 +116,7 @@ void Portal::DestroySelf() { } RenderFrameProxyHost* Portal::CreateProxyAndAttachPortal() { - WebContentsImpl* outer_contents_impl = static_cast<WebContentsImpl*>( - WebContents::FromRenderFrameHost(owner_render_frame_host_)); + WebContentsImpl* outer_contents_impl = GetPortalHostContents(); // Check if portal has already been attached. if (portal_contents_ && portal_contents_->GetOuterWebContents()) { @@ -157,16 +163,17 @@ RenderFrameProxyHost* Portal::CreateProxyAndAttachPortal() { portal_contents_.ReleaseOwnership(), outer_node->current_frame_host(), false /* is_full_page */); - // If a cross-process navigation started while the predecessor was orphaned, - // we need to create a view for the speculative RFH as well. - if (RenderFrameHostImpl* speculative_rfh = - portal_contents_->GetPendingMainFrame()) { - if (RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>( - speculative_rfh->GetView())) { - view->Destroy(); + // Create the view for all RenderViewHosts that don't have a + // RenderWidgetHostViewChildFrame view. + for (auto& render_view_host : + portal_contents_->GetFrameTree()->render_view_hosts()) { + if (!render_view_host.second->GetWidget()->GetView() || + !render_view_host.second->GetWidget() + ->GetView() + ->IsRenderWidgetHostViewChildFrame()) { + CreatePortalRenderWidgetHostView(portal_contents_.get(), + render_view_host.second); } - portal_contents_->CreateRenderWidgetHostViewForRenderManager( - speculative_rfh->render_view_host()); } FrameTreeNode* frame_tree_node = @@ -179,6 +186,8 @@ RenderFrameProxyHost* Portal::CreateProxyAndAttachPortal() { if (web_contents_created) PortalWebContentsCreated(portal_contents_.get()); + outer_contents_impl->NotifyNavigationStateChanged(INVALIDATE_TYPE_TAB); + devtools_instrumentation::PortalAttached(outer_contents_impl->GetMainFrame()); return proxy_host; @@ -235,7 +244,7 @@ void Portal::Navigate(const GURL& url, // TODO(https://crbug.com/1074422): It is possible for a portal to be // navigated by a frame other than the owning frame. Find a way to route the // correct initiator of the portal navigation to this call. - portal_root->navigator()->NavigateFromFrameProxy( + portal_root->navigator().NavigateFromFrameProxy( portal_frame, url, GlobalFrameRoutingId(owner_render_frame_host_->GetProcess()->GetID(), owner_render_frame_host_->GetRoutingID()), @@ -257,26 +266,6 @@ void FlushTouchEventQueues(RenderWidgetHostImpl* host) { FlushTouchEventQueues(static_cast<RenderWidgetHostImpl*>(child_widget)); } -void CreateRenderWidgetHostViewForUnattachedPredecessor( - WebContentsImpl* predecessor) { - if (RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>( - predecessor->GetMainFrame()->GetView())) { - view->Destroy(); - } - predecessor->CreateRenderWidgetHostViewForRenderManager( - predecessor->GetRenderViewHost()); - - if (RenderFrameHostImpl* speculative_rfh = - predecessor->GetPendingMainFrame()) { - if (RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>( - speculative_rfh->GetView())) { - view->Destroy(); - } - predecessor->CreateRenderWidgetHostViewForRenderManager( - speculative_rfh->render_view_host()); - } -} - // Copies |predecessor_contents|'s navigation entries to // |activated_contents|. |activated_contents| will have its last committed entry // combined with the entries in |predecessor_contents|. |predecessor_contents| @@ -315,9 +304,9 @@ void TakeHistoryForActivation(WebContentsImpl* activated_contents, } // namespace void Portal::Activate(blink::TransferableMessage data, + base::TimeTicks activation_time, ActivateCallback callback) { - WebContentsImpl* outer_contents = static_cast<WebContentsImpl*>( - WebContents::FromRenderFrameHost(owner_render_frame_host_)); + WebContentsImpl* outer_contents = GetPortalHostContents(); if (outer_contents->portal()) { mojo::ReportBadMessage("Portal::Activate called on nested portal"); @@ -348,16 +337,15 @@ void Portal::Activate(blink::TransferableMessage data, // restriction. DCHECK_EQ(PAGE_TYPE_NORMAL, predecessor_controller.GetLastCommittedEntry()->GetPageType()); - DCHECK(!predecessor_controller.GetTransientEntry()); - // If the portal is showing an error page, reject activation. - if (portal_controller.GetLastCommittedEntry()->GetPageType() != - PAGE_TYPE_NORMAL) { + // If the portal is crashed or is showing an error page, reject activation. + if (portal_contents_->IsCrashed() || + portal_controller.GetLastCommittedEntry()->GetPageType() != + PAGE_TYPE_NORMAL) { std::move(callback).Run( blink::mojom::PortalActivateResult::kRejectedDueToErrorInPortal); return; } - DCHECK(!portal_controller.GetTransientEntry()); // If a navigation in the main frame is occurring, stop it if possible and // reject the activation if it's too late or if an ongoing navigation takes @@ -384,7 +372,7 @@ void Portal::Activate(blink::TransferableMessage data, kRejectedDueToPredecessorNavigation); return; } - outer_root_node->navigator()->CancelNavigation(outer_root_node); + outer_root_node->navigator().CancelNavigation(outer_root_node); DCHECK(!is_closing_) << "Portal should not be shutting down when contents " "ownership is yielded"; @@ -403,7 +391,11 @@ void Portal::Activate(blink::TransferableMessage data, // attached to an outer WebContents, and may not have an outer frame tree // node created (i.e. CreateProxyAndAttachPortal isn't called). In this // case, we can skip a few of the detachment steps above. - CreateRenderWidgetHostViewForUnattachedPredecessor(portal_contents_.get()); + for (auto& render_view_host : + portal_contents_->GetFrameTree()->render_view_hosts()) { + CreatePortalRenderWidgetHostView(portal_contents_.get(), + render_view_host.second); + } successor_contents = portal_contents_.ReleaseOwnership(); } DCHECK(!portal_contents_.OwnsContents()); @@ -440,7 +432,7 @@ void Portal::Activate(blink::TransferableMessage data, std::unique_ptr<WebContents> predecessor_web_contents = delegate->ActivatePortalWebContents(outer_contents, std::move(successor_contents)); - CHECK_EQ(predecessor_web_contents.get(), outer_contents); + DCHECK_EQ(predecessor_web_contents.get(), outer_contents); if (outer_contents_main_frame_view) { portal_contents_main_frame_view->TransferTouches(touch_events); @@ -458,14 +450,25 @@ void Portal::Activate(blink::TransferableMessage data, // be a portal at that time. portal_contents_.Clear(); - successor_contents_raw->GetMainFrame()->OnPortalActivated( - std::move(predecessor_web_contents), std::move(data), - std::move(callback)); + mojo::PendingAssociatedRemote<blink::mojom::Portal> pending_portal; + auto portal_receiver = pending_portal.InitWithNewEndpointAndPassReceiver(); + mojo::PendingAssociatedRemote<blink::mojom::PortalClient> pending_client; + auto client_receiver = pending_client.InitWithNewEndpointAndPassReceiver(); + + RenderFrameHostImpl* successor_main_frame = + successor_contents_raw->GetMainFrame(); + auto predecessor = std::make_unique<Portal>( + successor_main_frame, std::move(predecessor_web_contents)); + predecessor->Bind(std::move(portal_receiver), std::move(pending_client)); + successor_main_frame->OnPortalActivated( + std::move(predecessor), std::move(pending_portal), + std::move(client_receiver), std::move(data), std::move(callback)); + // Notifying of activation happens later than ActivatePortalWebContents so // that it is observed after predecessor_web_contents has been moved into a // portal. DCHECK(outer_contents->IsPortal()); - successor_contents_raw->DidActivatePortal(outer_contents); + successor_contents_raw->DidActivatePortal(outer_contents, activation_time); } void Portal::PostMessageToGuest( @@ -519,19 +522,50 @@ void Portal::LoadingStateChanged(WebContents* source, } void Portal::PortalWebContentsCreated(WebContents* portal_web_contents) { - WebContentsImpl* outer_contents = static_cast<WebContentsImpl*>( - WebContents::FromRenderFrameHost(owner_render_frame_host_)); + WebContentsImpl* outer_contents = GetPortalHostContents(); DCHECK(outer_contents->GetDelegate()); outer_contents->GetDelegate()->PortalWebContentsCreated(portal_web_contents); } void Portal::CloseContents(WebContents* web_contents) { DCHECK_EQ(web_contents, portal_contents_.get()); - DestroySelf(); // Deletes |this|. + if (portal_contents_->GetOuterWebContents()) { + // This portal was still attached, we shouldn't have received a request to + // close it. + bad_message::ReceivedBadMessage(web_contents->GetMainFrame()->GetProcess(), + bad_message::RWH_CLOSE_PORTAL); + } else { + // Orphaned portal was closed. + DestroySelf(); // Deletes |this|. + } } WebContents* Portal::GetResponsibleWebContents(WebContents* web_contents) { - return WebContents::FromRenderFrameHost(owner_render_frame_host_); + return GetPortalHostContents(); +} + +void Portal::NavigationStateChanged(WebContents* source, + InvalidateTypes changed_flags) { + WebContents* outer_contents = GetPortalHostContents(); + // Can be null in tests. + if (!outer_contents->GetDelegate()) + return; + outer_contents->GetDelegate()->NavigationStateChanged(source, changed_flags); +} + +bool Portal::ShouldFocusPageAfterCrash() { + return false; +} + +void Portal::CanDownload(const GURL& url, + const std::string& request_method, + base::OnceCallback<void(bool)> callback) { + // Downloads are not allowed in portals. + owner_render_frame_host()->AddMessageToConsole( + blink::mojom::ConsoleMessageLevel::kWarning, + base::StringPrintf("Download in a portal (from %s) was blocked.", + url.spec().c_str())); + std::move(callback).Run(false); } base::UnguessableToken Portal::GetDevToolsFrameToken() const { @@ -542,6 +576,11 @@ WebContentsImpl* Portal::GetPortalContents() { return portal_contents_.get(); } +WebContentsImpl* Portal::GetPortalHostContents() { + return static_cast<WebContentsImpl*>( + WebContents::FromRenderFrameHost(owner_render_frame_host_)); +} + Portal::WebContentsHolder::WebContentsHolder(Portal* portal) : portal_(portal) {} diff --git a/chromium/content/browser/portal/portal.h b/chromium/content/browser/portal/portal.h index b0bb818a804..5f9615baea0 100644 --- a/chromium/content/browser/portal/portal.h +++ b/chromium/content/browser/portal/portal.h @@ -79,6 +79,7 @@ class CONTENT_EXPORT Portal : public blink::mojom::Portal, blink::mojom::ReferrerPtr referrer, NavigateCallback callback) override; void Activate(blink::TransferableMessage data, + base::TimeTicks activation_time, ActivateCallback callback) override; void PostMessageToGuest( const blink::TransferableMessage message, @@ -102,6 +103,12 @@ class CONTENT_EXPORT Portal : public blink::mojom::Portal, void PortalWebContentsCreated(WebContents* portal_web_contents) override; void CloseContents(WebContents*) override; WebContents* GetResponsibleWebContents(WebContents* web_contents) override; + void NavigationStateChanged(WebContents* source, + InvalidateTypes changed_flags) override; + bool ShouldFocusPageAfterCrash() override; + void CanDownload(const GURL& url, + const std::string& request_method, + base::OnceCallback<void(bool)> callback) override; // Returns the token which uniquely identifies this Portal. const base::UnguessableToken& portal_token() const { return portal_token_; } @@ -111,6 +118,8 @@ class CONTENT_EXPORT Portal : public blink::mojom::Portal, // Returns the Portal's WebContents. WebContentsImpl* GetPortalContents(); + // Returns the WebContents that hosts this portal. + WebContentsImpl* GetPortalHostContents(); RenderFrameHostImpl* owner_render_frame_host() { return owner_render_frame_host_; diff --git a/chromium/content/browser/portal/portal_browsertest.cc b/chromium/content/browser/portal/portal_browsertest.cc index 03886cfb01a..45533326bf8 100644 --- a/chromium/content/browser/portal/portal_browsertest.cc +++ b/chromium/content/browser/portal/portal_browsertest.cc @@ -9,11 +9,15 @@ #include "base/bind_helpers.h" #include "base/callback.h" #include "base/memory/ptr_util.h" +#include "base/optional.h" +#include "base/run_loop.h" +#include "base/strings/strcat.h" #include "base/strings/stringprintf.h" #include "base/test/bind_test_util.h" #include "base/test/scoped_feature_list.h" #include "base/test/test_timeouts.h" #include "build/build_config.h" +#include "components/download/public/common/download_item.h" #include "components/viz/host/host_frame_sink_manager.h" #include "content/browser/compositor/surface_utils.h" #include "content/browser/frame_host/render_frame_host_impl.h" @@ -27,6 +31,8 @@ #include "content/browser/renderer_host/render_widget_host_view_child_frame.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/common/frame.mojom-test-utils.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/download_manager.h" #include "content/public/browser/site_isolation_policy.h" #include "content/public/browser/web_contents_delegate.h" #include "content/public/common/content_switches.h" @@ -38,14 +44,20 @@ #include "content/public/test/content_browser_test_utils.h" #include "content/public/test/hit_test_region_observer.h" #include "content/public/test/navigation_handle_observer.h" +#include "content/public/test/render_frame_host_test_support.h" #include "content/public/test/test_navigation_observer.h" +#include "content/public/test/url_loader_interceptor.h" #include "content/shell/browser/shell.h" +#include "content/shell/browser/shell_browser_context.h" +#include "content/shell/browser/shell_content_browser_client.h" #include "content/test/content_browser_test_utils_internal.h" #include "content/test/portal/portal_activated_observer.h" #include "content/test/portal/portal_created_observer.h" #include "content/test/portal/portal_interceptor_for_testing.h" #include "content/test/test_render_frame_host_factory.h" +#include "net/base/escape.h" #include "net/base/net_errors.h" +#include "net/base/url_util.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" @@ -54,6 +66,7 @@ #include "third_party/blink/public/common/features.h" #include "third_party/blink/public/mojom/input/focus_type.mojom.h" #include "third_party/blink/public/mojom/portal/portal.mojom.h" +#include "url/gurl.h" #include "url/url_constants.h" using testing::_; @@ -65,7 +78,10 @@ class PortalBrowserTest : public ContentBrowserTest { PortalBrowserTest() {} void SetUp() override { - scoped_feature_list_.InitAndEnableFeature(blink::features::kPortals); + scoped_feature_list_.InitWithFeatures( + /*enabled_features=*/{blink::features::kPortals, + blink::features::kPortalsCrossOrigin}, + /*disabled_features=*/{}); ContentBrowserTest::SetUp(); } @@ -408,9 +424,8 @@ IN_PROC_BROWSER_TEST_F(PortalHitTestBrowserTest, DispatchInputEvent) { portal_view->TransformPointToRootCoordSpace(gfx::Point(5, 5)); InputEventAckWaiter waiter(main_frame->GetRenderWidgetHost(), blink::WebInputEvent::Type::kMouseDown); - SimulateRoutedMouseEvent( - web_contents_impl, blink::WebInputEvent::Type::kMouseDown, - blink::WebPointerProperties::Button::kLeft, root_location); + SimulateMouseEvent(web_contents_impl, blink::WebInputEvent::Type::kMouseDown, + blink::WebPointerProperties::Button::kLeft, root_location); waiter.Wait(); // Check that the click event was only received by the main frame. @@ -488,9 +503,8 @@ IN_PROC_BROWSER_TEST_F(PortalHitTestBrowserTest, NoInputToOOPIFInPortal) { oopif_view->TransformPointToRootCoordSpace(gfx::Point(5, 5)); InputEventAckWaiter waiter(main_frame->GetRenderWidgetHost(), blink::WebInputEvent::Type::kMouseDown); - SimulateRoutedMouseEvent( - web_contents_impl, blink::WebInputEvent::Type::kMouseDown, - blink::WebPointerProperties::Button::kLeft, root_location); + SimulateMouseEvent(web_contents_impl, blink::WebInputEvent::Type::kMouseDown, + blink::WebPointerProperties::Button::kLeft, root_location); waiter.Wait(); // Check that the click event was only received by the main frame. @@ -603,9 +617,9 @@ IN_PROC_BROWSER_TEST_F(PortalHitTestBrowserTest, oopif_view->TransformPointToRootCoordSpace(gfx::Point(10, 10)); InputEventAckWaiter waiter(oopif->GetRenderWidgetHost(), blink::WebInputEvent::Type::kMouseDown); - SimulateRoutedMouseEvent( - shell()->web_contents(), blink::WebInputEvent::Type::kMouseDown, - blink::WebPointerProperties::Button::kLeft, root_location); + SimulateMouseEvent(shell()->web_contents(), + blink::WebInputEvent::Type::kMouseDown, + blink::WebPointerProperties::Button::kLeft, root_location); waiter.Wait(); // Check that the click event was received by the iframe. @@ -631,7 +645,7 @@ IN_PROC_BROWSER_TEST_F(PortalBrowserTest, AsyncEventTargetingIgnoresPortals) { WaitForHitTestData(portal_frame); viz::mojom::InputTargetClient* target_client = - main_frame->GetRenderWidgetHost()->input_target_client(); + main_frame->GetRenderWidgetHost()->input_target_client().get(); ASSERT_TRUE(target_client); gfx::PointF root_location = @@ -1275,8 +1289,9 @@ IN_PROC_BROWSER_TEST_F(PortalBrowserTest, EXPECT_EQ(bad_message::RPH_MOJO_PROCESS_ERROR, kill_waiter.Wait()); } -// Tests that activation early in navigation succeeds, cancelling the pending -// navigation. +// Tests that activation early in navigation fails. Even though the navigation +// hasn't yet committed, allowing activation could allow a portal to prevent +// the user from navigating away. IN_PROC_BROWSER_TEST_F(PortalBrowserTest, ActivateEarlyInNavigation) { EXPECT_TRUE(NavigateToURL( shell(), embedded_test_server()->GetURL("portal.test", "/title1.html"))); @@ -1285,8 +1300,7 @@ IN_PROC_BROWSER_TEST_F(PortalBrowserTest, ActivateEarlyInNavigation) { RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame(); GURL url = embedded_test_server()->GetURL("a.com", "/title2.html"); - Portal* portal = CreatePortalToUrl(web_contents_impl, url); - WebContents* portal_contents = portal->GetPortalContents(); + CreatePortalToUrl(web_contents_impl, url); // Have the outer page try to navigate away but stop it early in the request, // where it is still possible to stop. @@ -1301,16 +1315,21 @@ IN_PROC_BROWSER_TEST_F(PortalBrowserTest, ActivateEarlyInNavigation) { web_contents_impl->GetController().LoadURLWithParams(params); ASSERT_TRUE(navigation_manager.WaitForRequestStart()); - // Then activate the portal. Since this is early in navigation, it should be - // aborted and the portal activation should succeed. - PortalActivatedObserver activated_observer(portal); - ExecuteScriptAsync(main_frame, - "document.querySelector('portal').activate();"); - EXPECT_EQ(blink::mojom::PortalActivateResult::kPredecessorWillUnload, - activated_observer.WaitForActivateResult()); - EXPECT_EQ(portal_contents, shell()->web_contents()); + // Then activate the portal, because navigation has begun and beforeunload + // has been dispatched, the activation should fail. + EvalJsResult result = EvalJs(main_frame, + "document.querySelector('portal').activate()" + ".then(() => 'success', e => e.message)"); + EXPECT_THAT(result.ExtractString(), + ::testing::HasSubstr("Cannot activate portal while document is in" + " beforeunload or has started unloading")); + + // The navigation should commit properly thereafter. + navigation_manager.WaitForNavigationFinished(); navigation_observer.Wait(); - EXPECT_EQ(handle_observer.net_error_code(), net::ERR_ABORTED); + EXPECT_EQ(web_contents_impl, shell()->web_contents()); + EXPECT_TRUE(navigation_observer.last_navigation_succeeded()); + EXPECT_EQ(destination, navigation_observer.last_navigation_url()); } // Tests that activation late in navigation is rejected (since it's too late to @@ -1323,7 +1342,7 @@ IN_PROC_BROWSER_TEST_F(PortalBrowserTest, ActivateLateInNavigation) { RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame(); GURL url = embedded_test_server()->GetURL("a.com", "/title2.html"); - Portal* portal = CreatePortalToUrl(web_contents_impl, url); + CreatePortalToUrl(web_contents_impl, url); // Have the outer page try to navigate away and reach the point where it's // about to process the response (after which it will commit). It is too late @@ -1341,15 +1360,12 @@ IN_PROC_BROWSER_TEST_F(PortalBrowserTest, ActivateLateInNavigation) { // Then activate the portal. Since this is late in navigation, we expect the // activation to fail. Since commit hasn't actually happened yet, though, // there is time for the renderer to process the promise rejection. - PortalActivatedObserver activated_observer(portal); EvalJsResult result = EvalJs(main_frame, "document.querySelector('portal').activate()" ".then(() => 'success', e => e.message)"); EXPECT_THAT(result.ExtractString(), - ::testing::HasSubstr("navigation is in progress")); - EXPECT_EQ( - blink::mojom::PortalActivateResult::kRejectedDueToPredecessorNavigation, - activated_observer.result()); + ::testing::HasSubstr("Cannot activate portal while document is in" + " beforeunload or has started unloading")); // The navigation should commit properly thereafter. navigation_manager.ResumeNavigation(); @@ -1678,9 +1694,8 @@ IN_PROC_BROWSER_TEST_F(PortalBrowserTest, AdvanceFocusIntoPortal) { ->GetProxyToOuterDelegate(); RenderProcessHostBadIpcMessageWaiter rph_kill_waiter( main_frame->GetProcess()); - outer_delegate_proxy->OnMessageReceived(FrameHostMsg_AdvanceFocus( - outer_delegate_proxy->GetRoutingID(), blink::mojom::FocusType::kNone, - main_frame->GetRoutingID())); + outer_delegate_proxy->AdvanceFocus(blink::mojom::FocusType::kNone, + main_frame->GetFrameToken()); base::Optional<bad_message::BadMessageReason> result = rph_kill_waiter.Wait(); EXPECT_TRUE(result.has_value()); EXPECT_EQ(result.value(), bad_message::RFPH_ADVANCE_FOCUS_INTO_PORTAL); @@ -1709,11 +1724,13 @@ IN_PROC_BROWSER_TEST_F(PortalBrowserTest, WebContentsImpl* portal_contents = portal->GetPortalContents(); RenderFrameHostImpl* portal_frame = portal_contents->GetMainFrame(); WaitForAccessibilityTree(portal_contents); + if (!main_frame->browser_accessibility_manager() || + !portal_frame->browser_accessibility_manager()->GetRootManager()) + WaitForAccessibilityTree(web_contents_impl); EXPECT_NE(nullptr, portal_frame->browser_accessibility_manager()); EXPECT_EQ(main_frame->browser_accessibility_manager(), portal_frame->browser_accessibility_manager()->GetRootManager()); - // Activate portal and adopt predecessor. EXPECT_TRUE(ExecJs(portal_frame, "window.addEventListener('portalactivate', e => { " @@ -1877,6 +1894,68 @@ IN_PROC_BROWSER_TEST_F(PortalBrowserTest, activated_observer.result()); } +namespace { +void CrashContents(WebContentsImpl* contents) { +#if defined(OS_WIN) + // TODO(mcnee): |CrashTab| on windows makes it look like the process + // terminated normally. For now we crash it properly here. + RenderProcessHost* rph = contents->GetMainFrame()->GetProcess(); + RenderProcessHostWatcher watcher( + rph, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); + EXPECT_TRUE(rph->Shutdown(RESULT_CODE_KILLED)); + watcher.Wait(); + EXPECT_FALSE(watcher.did_exit_normally()); +#else + CrashTab(contents); +#endif + EXPECT_TRUE(contents->IsCrashed()); +} +} // namespace + +IN_PROC_BROWSER_TEST_F(PortalBrowserTest, RejectActivationOfCrashedPages) { + GURL main_url(embedded_test_server()->GetURL("portal.test", "/title1.html")); + ASSERT_TRUE(NavigateToURL(shell(), main_url)); + WebContentsImpl* web_contents_impl = + static_cast<WebContentsImpl*>(shell()->web_contents()); + RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame(); + + GURL portal_url(embedded_test_server()->GetURL("a.com", "/title1.html")); + Portal* portal = CreatePortalToUrl(web_contents_impl, portal_url); + WebContentsImpl* portal_contents = portal->GetPortalContents(); + CrashContents(portal_contents); + + PortalActivatedObserver activated_observer(portal); + EXPECT_TRUE( + ExecJs(main_frame, "document.querySelector('portal').activate();")); + EXPECT_EQ(blink::mojom::PortalActivateResult::kRejectedDueToErrorInPortal, + activated_observer.WaitForActivateResult()); +} + +IN_PROC_BROWSER_TEST_F(PortalBrowserTest, ActivatePreviouslyCrashedPortal) { + GURL main_url(embedded_test_server()->GetURL("portal.test", "/title1.html")); + ASSERT_TRUE(NavigateToURL(shell(), main_url)); + WebContentsImpl* web_contents_impl = + static_cast<WebContentsImpl*>(shell()->web_contents()); + RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame(); + + GURL portal_url(embedded_test_server()->GetURL("a.com", "/title1.html")); + Portal* portal = CreatePortalToUrl(web_contents_impl, portal_url); + WebContentsImpl* portal_contents = portal->GetPortalContents(); + CrashContents(portal_contents); + + TestNavigationObserver navigation_observer(portal_contents); + EXPECT_TRUE(ExecJs( + main_frame, + JsReplace("document.querySelector('portal').src = $1;", portal_url))); + navigation_observer.Wait(); + + PortalActivatedObserver activated_observer(portal); + EXPECT_TRUE( + ExecJs(main_frame, "document.querySelector('portal').activate();")); + EXPECT_EQ(blink::mojom::PortalActivateResult::kPredecessorWillUnload, + activated_observer.WaitForActivateResult()); +} + IN_PROC_BROWSER_TEST_F(PortalBrowserTest, CallCreateProxyAndAttachPortalTwice) { EXPECT_TRUE(NavigateToURL( shell(), embedded_test_server()->GetURL("portal.test", "/title1.html"))); @@ -1906,6 +1985,40 @@ IN_PROC_BROWSER_TEST_F(PortalBrowserTest, CallCreateProxyAndAttachPortalTwice) { EXPECT_EQ(bad_message::RPH_MOJO_PROCESS_ERROR, rph_kill_waiter.Wait()); } +IN_PROC_BROWSER_TEST_F(PortalBrowserTest, CrossSiteActivationReusingRVH) { + EXPECT_TRUE(NavigateToURL( + shell(), embedded_test_server()->GetURL("portal.test", "/title1.html"))); + WebContentsImpl* web_contents_impl = + static_cast<WebContentsImpl*>(shell()->web_contents()); + + GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html")); + GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html")); + + Portal* portal = CreatePortalToUrl(web_contents_impl, a_url); + WebContentsImpl* portal_contents = portal->GetPortalContents(); + LeaveInPendingDeletionState(portal_contents->GetMainFrame()); + + // Navigate portal to b.com. + TestNavigationObserver b_nav_observer(portal_contents); + EXPECT_TRUE(ExecJs(portal_contents->GetMainFrame(), + JsReplace("location.href = $1;", b_url))); + b_nav_observer.Wait(); + + // Navigate portal to a.com once activated. + EXPECT_TRUE( + ExecJs(portal_contents->GetMainFrame(), + JsReplace("window.addEventListener('portalactivate', (e) => {" + " location.href = $1;" + "});", + a_url))); + + TestNavigationObserver nav_observer(portal_contents); + ExecuteScriptAsync(web_contents_impl->GetMainFrame(), + "document.querySelector('portal').activate();"); + nav_observer.Wait(); + EXPECT_TRUE(nav_observer.last_navigation_succeeded()); +} + class PortalOOPIFBrowserTest : public PortalBrowserTest { protected: PortalOOPIFBrowserTest() {} @@ -1950,4 +2063,234 @@ IN_PROC_BROWSER_TEST_F(PortalOOPIFBrowserTest, OOPIFInsidePortal) { deleted_observer.WaitUntilDeleted(); } +namespace { + +class DownloadObserver : public DownloadManager::Observer { + public: + DownloadObserver() + : manager_(BrowserContext::GetDownloadManager( + ShellContentBrowserClient::Get()->browser_context())) { + manager_->AddObserver(this); + } + + ~DownloadObserver() override { + if (manager_) + manager_->RemoveObserver(this); + } + + ::testing::AssertionResult DownloadObserved() { + if (download_url_.is_empty()) + return ::testing::AssertionFailure() << "no download observed"; + return ::testing::AssertionSuccess() + << "download observed: " << download_url_; + } + + ::testing::AssertionResult AwaitDownload() { + if (download_url_.is_empty() && !dropped_download_) { + base::RunLoop run_loop; + quit_closure_ = run_loop.QuitClosure(); + run_loop.Run(); + quit_closure_.Reset(); + } + return DownloadObserved(); + } + + // DownloadManager::Observer + + void ManagerGoingDown(DownloadManager* manager) override { + DCHECK_EQ(manager_, manager); + manager_->RemoveObserver(this); + manager_ = nullptr; + } + + void OnDownloadCreated(DownloadManager* manager, + download::DownloadItem* item) override { + DCHECK_EQ(manager_, manager); + if (download_url_.is_empty()) { + download_url_ = item->GetURL(); + if (!quit_closure_.is_null()) + std::move(quit_closure_).Run(); + } + } + + void OnDownloadDropped(DownloadManager* manager) override { + DCHECK_EQ(manager_, manager); + dropped_download_ = true; + if (!quit_closure_.is_null()) + std::move(quit_closure_).Run(); + } + + private: + DownloadManager* manager_; + bool dropped_download_ = false; + GURL download_url_; + base::OnceClosure quit_closure_; +}; + +} // namespace + +IN_PROC_BROWSER_TEST_F(PortalBrowserTest, DownloadsBlockedInMainFrame) { + WebContentsImpl* web_contents_impl = + static_cast<WebContentsImpl*>(shell()->web_contents()); + ASSERT_TRUE(NavigateToURL( + web_contents_impl, + embedded_test_server()->GetURL("portal.test", "/title1.html"))); + CreatePortalToUrl(web_contents_impl, embedded_test_server()->GetURL( + "portal.test", "/title2.html")); + + GURL download_url = embedded_test_server()->GetURL( + "portal.test", "/set-header?Content-Disposition: attachment"); + + DownloadObserver download_observer; + EXPECT_TRUE(ExecJs( + web_contents_impl, + JsReplace("document.querySelector('portal').src = $1", download_url))); + EXPECT_FALSE(download_observer.AwaitDownload()); +} + +IN_PROC_BROWSER_TEST_F(PortalBrowserTest, DownloadsBlockedInSubframe) { + WebContentsImpl* web_contents_impl = + static_cast<WebContentsImpl*>(shell()->web_contents()); + ASSERT_TRUE(NavigateToURL( + web_contents_impl, + embedded_test_server()->GetURL("portal.test", "/title1.html"))); + CreatePortalToUrl(web_contents_impl, embedded_test_server()->GetURL( + "portal.test", "/title2.html")); + + GURL download_url = embedded_test_server()->GetURL( + "portal.test", "/set-header?Content-Disposition: attachment"); + GURL iframe_url = embedded_test_server()->GetURL( + "portal.test", "/iframe?" + net::EscapeQueryParamValue( + download_url.spec(), /*use_plus=*/false)); + + DownloadObserver download_observer; + EXPECT_TRUE(ExecJs( + web_contents_impl, + JsReplace("document.querySelector('portal').src = $1", iframe_url))); + EXPECT_FALSE(download_observer.AwaitDownload()); +} + +IN_PROC_BROWSER_TEST_F(PortalBrowserTest, DownloadsBlockedViaDownloadLink) { + WebContentsImpl* web_contents_impl = + static_cast<WebContentsImpl*>(shell()->web_contents()); + ASSERT_TRUE(NavigateToURL( + web_contents_impl, + embedded_test_server()->GetURL("portal.test", "/title1.html"))); + Portal* portal = CreatePortalToUrl( + web_contents_impl, + embedded_test_server()->GetURL("portal.test", "/title2.html")); + + DownloadObserver download_observer; + EXPECT_TRUE(ExecJs(portal->GetPortalContents(), + "let a = document.createElement('a');\n" + "a.download = 'download.html';\n" + "a.href = '/title3.html';\n" + "a.click();\n")); + EXPECT_FALSE(download_observer.AwaitDownload()); +} + +namespace { + +static constexpr struct { + base::StringPiece token_id; + base::StringPiece token; +} kOriginTrialTokens[] = { + // Generated by: + // tools/origin_trials/generate_token.py --version 3 --expire-days 3650 \ + // https://portal.test Portals + // Token details: + // Version: 3 + // Origin: https://portal.test:443 + // Is Subdomain: None + // Is Third Party: None + // Usage Restriction: None + // Feature: Portals + // Expiry: 1907172789 (2030-06-08 18:13:09 UTC) + // Signature (Base64): + // 3nrCPtI01xhkOinmRegbwhnA5VrNBJUnxLv2yPxSKdtUMyoo9iUZszqtkaTFyV8Al/VJigcAOzLLsKOZ2N6DBQ== + {"portals", + "A956wj7SNNcYZDop5kXoG8IZwOVazQSVJ8S79sj8UinbVDMqKPYlGbM6rZGkxclfAJf1SYoHA" + "Dsyy7CjmdjegwUAAABReyJvcmlnaW4iOiAiaHR0cHM6Ly9wb3J0YWwudGVzdDo0NDMiLCAiZm" + "VhdHVyZSI6ICJQb3J0YWxzIiwgImV4cGlyeSI6IDE5MDcxNzI3ODl9"}, +}; + +} // namespace + +// Tests that origin trials correctly toggle the feature, and that default +// states are as intended for the same-origin origin trial +// (https://crbug.com/1040212). +// +// That these controls provide suitable safeguards and functionality is tested +// elsewhere. +class PortalOriginTrialBrowserTest : public ContentBrowserTest { + protected: + PortalOriginTrialBrowserTest() = default; + + bool PlatformSupportsPortalsOriginTrial() { +#if defined(OS_ANDROID) + return true; +#else + return false; +#endif + } + + void SetUp() override { + ContentBrowserTest::SetUp(); + EXPECT_EQ(base::FeatureList::IsEnabled(blink::features::kPortals), + PlatformSupportsPortalsOriginTrial()); + EXPECT_FALSE( + base::FeatureList::IsEnabled(blink::features::kPortalsCrossOrigin)); + } + + void SetUpOnMainThread() override { + ContentBrowserTest::SetUpOnMainThread(); + url_loader_interceptor_.emplace( + base::BindRepeating(&PortalOriginTrialBrowserTest::InterceptRequest)); + } + + void TearDownOnMainThread() override { url_loader_interceptor_.reset(); } + + // URLLoaderInterceptor callback + static bool InterceptRequest(URLLoaderInterceptor::RequestParams* params) { + // Find the appropriate origin trial token. + base::StringPiece origin_trial_token; + std::string origin_trial_query_param; + if (net::GetValueForKeyInQuery(params->url_request.url, "origintrial", + &origin_trial_query_param)) { + for (const auto& pair : kOriginTrialTokens) + if (pair.token_id == origin_trial_query_param) + origin_trial_token = pair.token; + } + + // Construct and send the response. + std::string headers = + "HTTP/1.1 200 OK\nContent-Type: text/html; charset=utf-8\n"; + if (!origin_trial_token.empty()) + base::StrAppend(&headers, {"Origin-Trial: ", origin_trial_token, "\n"}); + headers += '\n'; + std::string body = "<!DOCTYPE html><body>Hello world!</body>"; + URLLoaderInterceptor::WriteResponse(headers, body, params->client.get()); + return true; + } + + private: + base::Optional<URLLoaderInterceptor> url_loader_interceptor_; +}; + +IN_PROC_BROWSER_TEST_F(PortalOriginTrialBrowserTest, WithoutTrialToken) { + WebContentsImpl* web_contents_impl = + static_cast<WebContentsImpl*>(shell()->web_contents()); + ASSERT_TRUE(NavigateToURL(web_contents_impl, GURL("https://portal.test/"))); + EXPECT_EQ(false, EvalJs(web_contents_impl, "'HTMLPortalElement' in self")); +} + +IN_PROC_BROWSER_TEST_F(PortalOriginTrialBrowserTest, WithTrialToken) { + WebContentsImpl* web_contents_impl = + static_cast<WebContentsImpl*>(shell()->web_contents()); + ASSERT_TRUE(NavigateToURL(web_contents_impl, + GURL("https://portal.test/?origintrial=portals"))); + EXPECT_EQ(PlatformSupportsPortalsOriginTrial(), + EvalJs(web_contents_impl, "'HTMLPortalElement' in self")); +} + } // namespace content |