| // 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/scoped_feature_list.h" |
| #include "content/browser/frame_host/render_frame_host_impl.h" |
| #include "content/browser/portal/portal.h" |
| #include "content/browser/web_contents/web_contents_impl.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/test_navigation_observer.h" |
| #include "content/shell/browser/shell.h" |
| #include "mojo/public/cpp/bindings/strong_binding.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/mojom/portal/portal.mojom.h" |
| #include "url/url_constants.h" |
| |
| namespace content { |
| |
| // The PortalInterceptorForTesting can be used in tests to inspect Portal IPCs. |
| class PortalInterceptorForTesting final |
| : public blink::mojom::PortalInterceptorForTesting { |
| public: |
| static PortalInterceptorForTesting* Create( |
| RenderFrameHostImpl* render_frame_host_impl, |
| blink::mojom::PortalRequest request); |
| static PortalInterceptorForTesting* From(content::Portal* portal); |
| |
| void Init(InitCallback callback) override { |
| portal_->Init(std::move(callback)); |
| |
| // Init should be called only once. |
| ASSERT_FALSE(portal_initialized_); |
| portal_initialized_ = true; |
| |
| if (run_loop_) |
| run_loop_->Quit(); |
| } |
| |
| void WaitForInit() { |
| if (portal_initialized_) |
| return; |
| |
| base::RunLoop run_loop; |
| run_loop_ = &run_loop; |
| run_loop.Run(); |
| run_loop_ = nullptr; |
| } |
| |
| // Test getters. |
| content::Portal* GetPortal() { return portal_.get(); } |
| WebContents* GetPortalContents() { return portal_->GetPortalContents(); } |
| |
| private: |
| PortalInterceptorForTesting(RenderFrameHostImpl* render_frame_host_impl) |
| : portal_(content::Portal::CreateForTesting(render_frame_host_impl)) {} |
| |
| blink::mojom::Portal* GetForwardingInterface() override { |
| return portal_.get(); |
| } |
| |
| std::unique_ptr<content::Portal> portal_; |
| bool portal_initialized_ = false; |
| base::RunLoop* run_loop_ = nullptr; |
| }; |
| |
| // static |
| PortalInterceptorForTesting* PortalInterceptorForTesting::Create( |
| RenderFrameHostImpl* render_frame_host_impl, |
| blink::mojom::PortalRequest request) { |
| auto test_portal_ptr = |
| base::WrapUnique(new PortalInterceptorForTesting(render_frame_host_impl)); |
| PortalInterceptorForTesting* test_portal = test_portal_ptr.get(); |
| test_portal->GetPortal()->SetBindingForTesting( |
| mojo::MakeStrongBinding(std::move(test_portal_ptr), std::move(request))); |
| return test_portal; |
| } |
| |
| // static |
| PortalInterceptorForTesting* PortalInterceptorForTesting::From( |
| content::Portal* portal) { |
| blink::mojom::Portal* impl = portal->GetBindingForTesting()->impl(); |
| auto* interceptor = static_cast<PortalInterceptorForTesting*>(impl); |
| CHECK_NE(static_cast<blink::mojom::Portal*>(portal), impl); |
| CHECK_EQ(interceptor->GetPortal(), portal); |
| return interceptor; |
| } |
| |
| // The PortalCreatedObserver observes portal creations on |
| // |render_frame_host_impl|. This observer can be used to monitor for multiple |
| // Portal creations on the same RenderFrameHost, by repeatedly calling |
| // WaitUntilPortalCreated(). |
| // |
| // The PortalCreatedObserver replaces the Portal interface in the |
| // RenderFrameHosts' BinderRegistry, so when the observer is destroyed the |
| // RenderFrameHost is left without an interface and attempts to create the |
| // interface will fail. |
| class PortalCreatedObserver { |
| public: |
| explicit PortalCreatedObserver(RenderFrameHostImpl* render_frame_host_impl) |
| : render_frame_host_impl_(render_frame_host_impl) { |
| service_manager::BinderRegistry& registry = |
| render_frame_host_impl->BinderRegistryForTesting(); |
| |
| registry.AddInterface(base::BindRepeating( |
| [](PortalCreatedObserver* observer, |
| RenderFrameHostImpl* render_frame_host_impl, |
| blink::mojom::PortalRequest request) { |
| observer->portal_ = PortalInterceptorForTesting::Create( |
| render_frame_host_impl, std::move(request)) |
| ->GetPortal(); |
| if (observer->run_loop_) |
| observer->run_loop_->Quit(); |
| }, |
| base::Unretained(this), base::Unretained(render_frame_host_impl))); |
| } |
| |
| ~PortalCreatedObserver() { |
| service_manager::BinderRegistry& registry = |
| render_frame_host_impl_->BinderRegistryForTesting(); |
| |
| registry.RemoveInterface<Portal>(); |
| } |
| |
| Portal* WaitUntilPortalCreated() { |
| Portal* portal = portal_; |
| if (portal) { |
| portal_ = nullptr; |
| return portal; |
| } |
| |
| base::RunLoop run_loop; |
| run_loop_ = &run_loop; |
| run_loop.Run(); |
| run_loop_ = nullptr; |
| |
| portal = portal_; |
| portal_ = nullptr; |
| return portal; |
| } |
| |
| private: |
| RenderFrameHostImpl* render_frame_host_impl_; |
| base::RunLoop* run_loop_ = nullptr; |
| Portal* portal_ = nullptr; |
| }; |
| |
| class PortalBrowserTest : public ContentBrowserTest { |
| protected: |
| PortalBrowserTest() {} |
| |
| void SetUp() override { |
| scoped_feature_list_.InitAndEnableFeature(blink::features::kPortals); |
| ContentBrowserTest::SetUp(); |
| } |
| |
| 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()); |
| portal_interceptor->WaitForInit(); |
| WebContents* portal_contents = portal_interceptor->GetPortalContents(); |
| EXPECT_NE(nullptr, portal_contents); |
| EXPECT_NE(portal_contents->GetLastCommittedURL(), a_url); |
| |
| // WaitForInit() above only waits for the Portal::Init call, which is when the |
| // Portal's WebContents is created. Portal::Navigate is a diffent IPC, so the |
| // portal should not have navigated yet, and 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); |
| } |
| } |
| |
| } // namespace content |