summaryrefslogtreecommitdiff
path: root/chromium/content/browser/portal
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-12 14:27:29 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-13 09:35:20 +0000
commitc30a6232df03e1efbd9f3b226777b07e087a1122 (patch)
treee992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/content/browser/portal
parent7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff)
downloadqtwebengine-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.cc151
-rw-r--r--chromium/content/browser/portal/portal.h9
-rw-r--r--chromium/content/browser/portal/portal_browsertest.cc411
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