| // Copyright 2021 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/callback.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/time/time.h" |
| #include "content/browser/fenced_frame/fenced_frame.h" |
| #include "content/browser/renderer_host/frame_tree_node.h" |
| #include "content/browser/renderer_host/render_frame_proxy_host.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/common/frame.mojom-test-utils.h" |
| #include "content/public/browser/frame_type.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/test/browser_test.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/fenced_frame_test_util.h" |
| #include "content/public/test/mock_web_contents_observer.h" |
| #include "content/public/test/navigation_handle_observer.h" |
| #include "content/public/test/test_frame_navigation_observer.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 "mojo/public/cpp/test_support/test_utils.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/controllable_http_response.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/mojom/fenced_frame/fenced_frame.mojom.h" |
| #include "url/gurl.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| constexpr char kAddIframeScript[] = R"({ |
| (()=>{ |
| return new Promise((resolve) => { |
| const frame = document.createElement('iframe'); |
| frame.addEventListener('load', () => {resolve();}); |
| frame.src = $1; |
| document.body.appendChild(frame); |
| }); |
| })(); |
| })"; |
| |
| } // namespace |
| |
| class FencedFrameBrowserTest : public ContentBrowserTest { |
| protected: |
| FencedFrameBrowserTest() = default; |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| ContentBrowserTest::SetUpOnMainThread(); |
| |
| https_server()->AddDefaultHandlers(GetTestDataFilePath()); |
| https_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES); |
| SetupCrossSiteRedirector(https_server()); |
| } |
| |
| WebContentsImpl* web_contents() { |
| return static_cast<WebContentsImpl*>(shell()->web_contents()); |
| } |
| |
| RenderFrameHostImpl* primary_main_frame_host() { |
| return web_contents()->GetMainFrame(); |
| } |
| |
| test::FencedFrameTestHelper& fenced_frame_test_helper() { |
| return fenced_frame_test_helper_; |
| } |
| |
| net::EmbeddedTestServer* https_server() { return &https_server_; } |
| |
| private: |
| test::FencedFrameTestHelper fenced_frame_test_helper_; |
| net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS}; |
| }; |
| |
| // Tests that the renderer can create a <fencedframe> that results in a |
| // browser-side content::FencedFrame also being created. |
| IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, CreateFromScriptAndDestroy) { |
| ASSERT_TRUE(https_server()->Start()); |
| const GURL main_url = |
| https_server()->GetURL("c.test", "/fenced_frames/title1.html"); |
| EXPECT_TRUE( |
| NavigateToURL(shell(), https_server()->GetURL("c.test", "/title1.html"))); |
| RenderFrameHostImplWrapper primary_rfh(primary_main_frame_host()); |
| RenderFrameHostImplWrapper fenced_frame_rfh( |
| fenced_frame_test_helper().CreateFencedFrame(primary_rfh.get(), |
| main_url)); |
| FrameTreeNode* fenced_frame_root_node = fenced_frame_rfh->frame_tree_node(); |
| EXPECT_TRUE(fenced_frame_root_node->render_manager() |
| ->GetProxyToOuterDelegate() |
| ->is_render_frame_proxy_live()); |
| |
| // Test `RenderFrameHostImpl::IsInPrimaryMainFrame`. |
| EXPECT_TRUE(primary_rfh->IsInPrimaryMainFrame()); |
| EXPECT_FALSE(fenced_frame_rfh->IsInPrimaryMainFrame()); |
| |
| // Test `FrameTreeNode::IsFencedFrameRoot()`. |
| EXPECT_FALSE( |
| web_contents()->GetPrimaryFrameTree().root()->IsFencedFrameRoot()); |
| EXPECT_FALSE(primary_rfh->child_at(0)->IsFencedFrameRoot()); |
| EXPECT_TRUE(fenced_frame_root_node->IsFencedFrameRoot()); |
| |
| // Test `FrameTreeNode::IsInFencedFrameTree()`. |
| EXPECT_FALSE( |
| web_contents()->GetPrimaryFrameTree().root()->IsInFencedFrameTree()); |
| EXPECT_FALSE(primary_rfh->child_at(0)->IsInFencedFrameTree()); |
| EXPECT_TRUE(fenced_frame_root_node->IsInFencedFrameTree()); |
| |
| EXPECT_TRUE(ExecJs(primary_rfh.get(), |
| "const ff = document.querySelector('fencedframe');\ |
| ff.remove();")); |
| ASSERT_TRUE(fenced_frame_rfh.WaitUntilRenderFrameDeleted()); |
| |
| EXPECT_TRUE(primary_rfh->GetFencedFrames().empty()); |
| EXPECT_TRUE(fenced_frame_rfh.IsDestroyed()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, CreateFromParser) { |
| ASSERT_TRUE(https_server()->Start()); |
| const GURL top_level_url = |
| https_server()->GetURL("c.test", "/fenced_frames/basic.html"); |
| EXPECT_TRUE(NavigateToURL(shell(), top_level_url)); |
| |
| // The fenced frame is set-up synchronously, so it should exist immediately. |
| RenderFrameHostImplWrapper dummy_child_frame( |
| primary_main_frame_host()->child_at(0)->current_frame_host()); |
| EXPECT_NE(dummy_child_frame->inner_tree_main_frame_tree_node_id(), |
| FrameTreeNode::kFrameTreeNodeInvalidId); |
| FrameTreeNode* inner_frame_tree_node = FrameTreeNode::GloballyFindByID( |
| dummy_child_frame->inner_tree_main_frame_tree_node_id()); |
| EXPECT_TRUE(inner_frame_tree_node); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, Navigation) { |
| ASSERT_TRUE(https_server()->Start()); |
| const GURL main_url = https_server()->GetURL("c.test", "/title1.html"); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| // WebContentsObservers should not be notified of commits happening |
| // in the non-primary navigation controller. |
| testing::NiceMock<MockWebContentsObserver> web_contents_observer( |
| web_contents()); |
| EXPECT_CALL(web_contents_observer, NavigationEntryCommitted(testing::_)) |
| .Times(0); |
| EXPECT_CALL(web_contents_observer, NavigationEntryChanged(testing::_)) |
| .Times(0); |
| |
| RenderFrameHostImpl* primary_rfh = primary_main_frame_host(); |
| |
| const GURL fenced_frame_url = |
| https_server()->GetURL("c.test", "/fenced_frames/title1.html"); |
| RenderFrameHost* fenced_frame_rfh = |
| fenced_frame_test_helper().CreateFencedFrame(primary_rfh, |
| fenced_frame_url); |
| |
| // Test that a fenced frame navigation does not impact the primary main |
| // frame... |
| EXPECT_EQ(main_url, primary_rfh->GetLastCommittedURL()); |
| // ... but should target the correct frame. |
| EXPECT_EQ(fenced_frame_url, fenced_frame_rfh->GetLastCommittedURL()); |
| EXPECT_EQ(url::Origin::Create(fenced_frame_url), |
| fenced_frame_rfh->GetLastCommittedOrigin()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, AboutBlankNavigation) { |
| ASSERT_TRUE(https_server()->Start()); |
| const GURL main_url = https_server()->GetURL("a.test", "/title1.html"); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| RenderFrameHostImpl* primary_rfh = primary_main_frame_host(); |
| |
| const GURL fenced_frame_url = |
| https_server()->GetURL("c.test", "/fenced_frames/title1.html"); |
| fenced_frame_test_helper().CreateFencedFrame(primary_rfh, fenced_frame_url); |
| |
| std::vector<FencedFrame*> fenced_frames = primary_rfh->GetFencedFrames(); |
| ASSERT_EQ(1ul, fenced_frames.size()); |
| FencedFrame* fenced_frame = fenced_frames.back(); |
| |
| // Exepct the origin is correct. |
| EXPECT_EQ(url::Origin::Create(fenced_frame_url), |
| EvalJs(fenced_frame->GetInnerRoot(), "self.origin;")); |
| |
| // Assigning the location from the parent cause the SiteInstance |
| // to be calculated incorrectly and crash. see https://crbug.com/1268238. |
| // We can't use `NavigateFrameInFencedFrameTree` because that navigates |
| // from the inner frame tree and we want the navigation to occur from |
| // the outer frame tree. |
| TestFrameNavigationObserver observer(fenced_frame->GetInnerRoot()); |
| EXPECT_TRUE( |
| ExecJs(primary_rfh, |
| "document.querySelector('fencedframe').src = 'about:blank';")); |
| observer.Wait(); |
| |
| EXPECT_TRUE(!fenced_frame->GetInnerRoot()->IsErrorDocument()); |
| EXPECT_EQ("null", EvalJs(fenced_frame->GetInnerRoot(), "self.origin;")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, FrameIteration) { |
| ASSERT_TRUE(https_server()->Start()); |
| const GURL main_url = https_server()->GetURL("c.test", "/title1.html"); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| RenderFrameHostImplWrapper primary_rfh(primary_main_frame_host()); |
| |
| const GURL fenced_frame_url = |
| https_server()->GetURL("c.test", "/fenced_frames/title1.html"); |
| RenderFrameHostImplWrapper fenced_frame_rfh( |
| fenced_frame_test_helper().CreateFencedFrame(primary_rfh.get(), |
| fenced_frame_url)); |
| |
| // Test that the outer => inner delegate mechanism works correctly. |
| EXPECT_THAT(CollectAllRenderFrameHosts(primary_rfh.get()), |
| testing::ElementsAre(primary_rfh.get(), fenced_frame_rfh.get())); |
| |
| // Test that the inner => outer delegate mechanism works correctly. |
| EXPECT_EQ(nullptr, fenced_frame_rfh->GetParent()); |
| EXPECT_EQ(fenced_frame_rfh->GetParentOrOuterDocument(), primary_rfh.get()); |
| EXPECT_EQ(fenced_frame_rfh->GetOutermostMainFrame(), primary_rfh.get()); |
| EXPECT_EQ(fenced_frame_rfh->GetParentOrOuterDocumentOrEmbedder(), |
| primary_rfh.get()); |
| EXPECT_EQ(fenced_frame_rfh->GetOutermostMainFrameOrEmbedder(), |
| primary_rfh.get()); |
| |
| // WebContentsImpl::ForEachFrameTree should include fenced frames. |
| bool visited_fenced_frame_frame_tree = false; |
| web_contents()->ForEachFrameTree( |
| base::BindLambdaForTesting([&](FrameTree* frame_tree) { |
| if (frame_tree == fenced_frame_rfh->frame_tree()) { |
| visited_fenced_frame_frame_tree = true; |
| } |
| })); |
| EXPECT_TRUE(visited_fenced_frame_frame_tree); |
| } |
| |
| namespace { |
| |
| // Intercepts calls to RenderFramHostImpl's CreateFencedFrame mojo method, and |
| // connects a NavigationDelayer which delays the FencedFrameOwnerHost's |
| // Navigate mojo method. |
| class NavigationDelayerInterceptor |
| : public mojom::FrameHostInterceptorForTesting { |
| public: |
| explicit NavigationDelayerInterceptor(RenderFrameHostImpl* render_frame_host, |
| base::TimeDelta duration) |
| : render_frame_host_(render_frame_host), |
| duration_(duration), |
| impl_(render_frame_host_->frame_host_receiver_for_testing() |
| .SwapImplForTesting(this)) {} |
| |
| ~NavigationDelayerInterceptor() override = default; |
| |
| mojom::FrameHost* GetForwardingInterface() override { return impl_; } |
| |
| void CreateFencedFrame( |
| mojo::PendingAssociatedReceiver<blink::mojom::FencedFrameOwnerHost> |
| pending_receiver, |
| blink::mojom::FencedFrameMode mode, |
| CreateFencedFrameCallback callback) override { |
| mojo::PendingAssociatedRemote<blink::mojom::FencedFrameOwnerHost> |
| original_remote; |
| |
| GetForwardingInterface()->CreateFencedFrame( |
| original_remote.InitWithNewEndpointAndPassReceiver(), mode, |
| std::move(callback)); |
| std::vector<FencedFrame*> fenced_frames = |
| render_frame_host_->GetFencedFrames(); |
| ASSERT_FALSE(fenced_frames.empty()); |
| navigate_interceptor_ = std::make_unique<NavigationDelayer>( |
| std::move(original_remote), std::move(pending_receiver), |
| fenced_frames.back(), duration_); |
| } |
| |
| private: |
| class NavigationDelayer : public blink::mojom::FencedFrameOwnerHost { |
| public: |
| explicit NavigationDelayer( |
| mojo::PendingAssociatedRemote<blink::mojom::FencedFrameOwnerHost> |
| original_remote, |
| mojo::PendingAssociatedReceiver<blink::mojom::FencedFrameOwnerHost> |
| receiver, |
| FencedFrame* fenced_frame, |
| base::TimeDelta duration) |
| : original_remote_(std::move(original_remote)), |
| fenced_frame_(fenced_frame), |
| duration_(duration) { |
| receiver_.Bind(std::move(receiver)); |
| } |
| |
| ~NavigationDelayer() override = default; |
| |
| void Navigate(const GURL& url, |
| base::TimeTicks navigation_start_time) override { |
| base::PlatformThread::Sleep(duration_); |
| fenced_frame_->Navigate(url, navigation_start_time); |
| } |
| |
| private: |
| mojo::AssociatedRemote<blink::mojom::FencedFrameOwnerHost> original_remote_; |
| mojo::AssociatedReceiver<blink::mojom::FencedFrameOwnerHost> receiver_{ |
| this}; |
| raw_ptr<FencedFrame> fenced_frame_; |
| const base::TimeDelta duration_; |
| }; |
| |
| raw_ptr<RenderFrameHostImpl> render_frame_host_; |
| std::unique_ptr<NavigationDelayer> navigate_interceptor_; |
| const base::TimeDelta duration_; |
| raw_ptr<mojom::FrameHost> impl_; |
| }; |
| |
| } // namespace |
| |
| IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, NavigationStartTime) { |
| ASSERT_TRUE(https_server()->Start()); |
| const GURL main_url = https_server()->GetURL("a.test", "/title1.html"); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| RenderFrameHostImpl* primary_rfh = primary_main_frame_host(); |
| |
| const GURL fenced_frame_url = |
| https_server()->GetURL("a.test", "/fenced_frames/title1.html"); |
| constexpr char kAddFencedFrameScript[] = R"({ |
| const fenced_frame = document.createElement('fencedframe'); |
| fenced_frame.src = $1; |
| document.body.appendChild(fenced_frame); |
| })"; |
| |
| const int delay_in_milliseconds = 1000; |
| |
| // The UI thread of the browser process will sleep |delay_in_milliseconds| |
| // just before handing FencedFrameOwnerHost's Navigate mojo method. |
| NavigationDelayerInterceptor interceptor( |
| primary_rfh, base::Milliseconds(delay_in_milliseconds)); |
| |
| EXPECT_TRUE( |
| ExecJs(primary_rfh, JsReplace(kAddFencedFrameScript, fenced_frame_url))); |
| std::vector<FencedFrame*> fenced_frames = primary_rfh->GetFencedFrames(); |
| ASSERT_EQ(1U, fenced_frames.size()); |
| FencedFrame* fenced_frame = fenced_frames[0]; |
| WaitForLoadStop(web_contents()); |
| |
| // The duration between navigationStart (measured in the renderer process) and |
| // requestStart (measured in the browser process due to PlzNavigate) must be |
| // greater than or equal to |delay_in_milliseconds|. |
| EXPECT_GE(EvalJs(fenced_frame->GetInnerRoot(), |
| "performance.timing.requestStart - " |
| "performance.timing.navigationStart") |
| .ExtractInt(), |
| delay_in_milliseconds); |
| } |
| |
| // Test that ensures we can post from an cross origin iframe into the |
| // fenced frame root. |
| IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, CrossOriginMessagePost) { |
| ASSERT_TRUE(https_server()->Start()); |
| const GURL main_url = |
| https_server()->GetURL("c.test", "/fenced_frames/title1.html"); |
| const GURL cross_origin_iframe_url = |
| https_server()->GetURL("b.com", "/fenced_frames/title1.html"); |
| EXPECT_TRUE( |
| NavigateToURL(shell(), https_server()->GetURL("c.test", "/title1.html"))); |
| RenderFrameHostImplWrapper primary_rfh(primary_main_frame_host()); |
| RenderFrameHostImplWrapper fenced_frame_rfh( |
| fenced_frame_test_helper().CreateFencedFrame(primary_rfh.get(), |
| main_url)); |
| EXPECT_TRUE(ExecJs(fenced_frame_rfh.get(), |
| JsReplace(kAddIframeScript, cross_origin_iframe_url))); |
| |
| RenderFrameHostImpl* iframe = static_cast<RenderFrameHostImpl*>( |
| ChildFrameAt(fenced_frame_rfh.get(), 0)); |
| |
| EXPECT_TRUE( |
| EvalJs(iframe->GetParent(), R"(window.addEventListener('message', (e) => { |
| e.source.postMessage('echo ' + e.data, "*"); |
| }, false); true)") |
| .ExtractBool()); |
| EXPECT_EQ("echo test", EvalJs(iframe, R"((async() => { |
| let promise = new Promise(function(resolve, reject) { |
| window.addEventListener('message', (e) => { |
| resolve(e.data) |
| }, false); |
| window.parent.postMessage('test', "*"); |
| }); |
| let result = await promise; |
| return result; |
| })())")); |
| } |
| |
| // Test that when the documents inside fenced frame tree are loading, |
| // WebContentsObserver::DocumentOnLoadCompletedInPrimaryMainFrame is not invoked |
| // for fenced frames as it is only invoked for primary main frames. |
| IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, |
| DocumentOnLoadCompletedInPrimaryMainFrame) { |
| ASSERT_TRUE(https_server()->Start()); |
| // Initialize a MockWebContentsObserver to ensure that |
| // DocumentOnLoadCompletedInPrimaryMainFrame is only invoked for primary main |
| // RenderFrameHosts. |
| testing::NiceMock<MockWebContentsObserver> web_contents_observer( |
| web_contents()); |
| |
| // Navigate to an initial primary page. This should result in invoking |
| // DocumentOnLoadCompletedInPrimaryMainFrame once. |
| EXPECT_CALL(web_contents_observer, |
| DocumentOnLoadCompletedInPrimaryMainFrame()) |
| .Times(1); |
| EXPECT_TRUE( |
| NavigateToURL(shell(), https_server()->GetURL("c.test", "/title1.html"))); |
| RenderFrameHostImplWrapper primary_rfh(primary_main_frame_host()); |
| |
| // Once the fenced frame complets loading, it shouldn't result in |
| // invoking DocumentOnLoadCompletedInPrimaryMainFrame. |
| EXPECT_CALL(web_contents_observer, |
| DocumentOnLoadCompletedInPrimaryMainFrame()) |
| .Times(0); |
| const GURL fenced_frame_url = |
| https_server()->GetURL("c.test", "/fenced_frames/title1.html"); |
| RenderFrameHostImplWrapper inner_fenced_frame_rfh( |
| fenced_frame_test_helper().CreateFencedFrame(primary_rfh.get(), |
| fenced_frame_url)); |
| FrameTreeNode* fenced_frame_root_node = |
| inner_fenced_frame_rfh->frame_tree_node(); |
| EXPECT_FALSE(fenced_frame_root_node->IsLoading()); |
| } |
| |
| // Test that when the documents inside the fenced frame tree are loading, |
| // WebContentsObserver::PrimaryMainDocumentElementAvailable is not invoked for |
| // fenced frames as it is only invoked for primary main frames. |
| IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, |
| PrimaryMainDocumentElementAvailable) { |
| ASSERT_TRUE(https_server()->Start()); |
| // Initialize a MockWebContentsObserver to ensure that |
| // PrimaryMainDocumentElementAvailable is only invoked for primary main |
| // RenderFrameHosts. |
| testing::NiceMock<MockWebContentsObserver> web_contents_observer( |
| web_contents()); |
| testing::InSequence s; |
| |
| // Navigate to an initial primary page. This should result in invoking |
| // PrimaryMainDocumentElementAvailable once. |
| EXPECT_CALL(web_contents_observer, PrimaryMainDocumentElementAvailable()) |
| .Times(1); |
| EXPECT_TRUE( |
| NavigateToURL(shell(), https_server()->GetURL("c.test", "/title1.html"))); |
| RenderFrameHostImplWrapper primary_rfh(primary_main_frame_host()); |
| |
| // Once the fenced frame completes loading, it shouldn't result in |
| // invoking PrimaryMainDocumentElementAvailable. |
| EXPECT_CALL(web_contents_observer, PrimaryMainDocumentElementAvailable()) |
| .Times(0); |
| const GURL fenced_frame_url = |
| https_server()->GetURL("c.test", "/fenced_frames/title1.html"); |
| RenderFrameHostImplWrapper inner_fenced_frame_rfh( |
| fenced_frame_test_helper().CreateFencedFrame(primary_rfh.get(), |
| fenced_frame_url)); |
| FrameTreeNode* fenced_frame_root_node = |
| inner_fenced_frame_rfh->frame_tree_node(); |
| EXPECT_FALSE(fenced_frame_root_node->IsLoading()); |
| } |
| |
| // Test that a fenced-frame does not perform any of the Android main-frame |
| // viewport behaviors like zoom-out-to-fit-content or parsing the viewport |
| // <meta>. |
| IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, ViewportSettings) { |
| ASSERT_TRUE(https_server()->Start()); |
| const GURL top_level_url = |
| https_server()->GetURL("c.test", "/fenced_frames/viewport.html"); |
| EXPECT_TRUE(NavigateToURL(shell(), top_level_url)); |
| |
| RenderFrameHostImplWrapper primary_rfh(primary_main_frame_host()); |
| std::vector<FencedFrame*> fenced_frames = primary_rfh->GetFencedFrames(); |
| ASSERT_EQ(1ul, fenced_frames.size()); |
| FencedFrame* fenced_frame = fenced_frames.back(); |
| |
| EXPECT_TRUE(WaitForLoadStop(web_contents())); |
| |
| // Ensure various dimensions and properties in the fenced frame |
| // match the dimensions of the <fencedframe> in the parent and do |
| // not take into account the <meta name="viewport"> in the |
| // fenced-frame page. |
| EXPECT_EQ( |
| EvalJs(fenced_frame->GetInnerRoot(), "window.innerWidth").ExtractInt(), |
| 314); |
| EXPECT_EQ( |
| EvalJs(fenced_frame->GetInnerRoot(), "window.innerHeight").ExtractInt(), |
| 271); |
| EXPECT_EQ(EvalJs(fenced_frame->GetInnerRoot(), |
| "document.documentElement.clientWidth") |
| .ExtractInt(), |
| 314); |
| EXPECT_EQ(EvalJs(fenced_frame->GetInnerRoot(), "window.visualViewport.scale") |
| .ExtractDouble(), |
| 1.0); |
| } |
| |
| // Test that fenced frames use the primary main frame's UKM source id during |
| // navigation. |
| IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, GetPageUkmSourceId) { |
| ASSERT_TRUE(https_server()->Start()); |
| const GURL main_url = https_server()->GetURL("c.test", "/title1.html"); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| const GURL fenced_frame_url = |
| https_server()->GetURL("c.test", "/fenced_frames/title1.html"); |
| NavigationHandleObserver handle_observer(web_contents(), fenced_frame_url); |
| RenderFrameHostImplWrapper fenced_frame_rfh( |
| fenced_frame_test_helper().CreateFencedFrame(primary_main_frame_host(), |
| fenced_frame_url)); |
| ASSERT_TRUE(fenced_frame_rfh); |
| |
| ukm::SourceId nav_request_id = handle_observer.next_page_ukm_source_id(); |
| // Should have the same page UKM ID in navigation as page post commit, and as |
| // the primary main frame. |
| EXPECT_EQ(primary_main_frame_host()->GetPageUkmSourceId(), nav_request_id); |
| EXPECT_EQ(fenced_frame_rfh->GetPageUkmSourceId(), nav_request_id); |
| } |
| |
| // Test that iframes that nested within fenced frames use the primary main |
| // frame's UKM source id during navigation. |
| IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, GetPageUkmSourceId_NestedFrame) { |
| ASSERT_TRUE(https_server()->Start()); |
| const GURL main_url = https_server()->GetURL("c.test", "/title1.html"); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| const GURL fenced_frame_url = |
| https_server()->GetURL("c.test", "/fenced_frames/title1.html"); |
| RenderFrameHostImplWrapper fenced_frame_rfh( |
| fenced_frame_test_helper().CreateFencedFrame(primary_main_frame_host(), |
| fenced_frame_url)); |
| ASSERT_TRUE(fenced_frame_rfh); |
| |
| const GURL iframe_url = |
| https_server()->GetURL("c.test", "/fenced_frames/title1.html"); |
| NavigationHandleObserver handle_observer(web_contents(), iframe_url); |
| EXPECT_TRUE( |
| ExecJs(fenced_frame_rfh.get(), JsReplace(kAddIframeScript, iframe_url))); |
| |
| RenderFrameHostImpl* iframe_rfh = static_cast<RenderFrameHostImpl*>( |
| ChildFrameAt(fenced_frame_rfh.get(), 0)); |
| ukm::SourceId nav_request_id = handle_observer.next_page_ukm_source_id(); |
| // Should have the same page UKM ID in navigation as page post commit, and as |
| // the primary main frame. |
| EXPECT_EQ(primary_main_frame_host()->GetPageUkmSourceId(), nav_request_id); |
| EXPECT_EQ(fenced_frame_rfh->GetPageUkmSourceId(), nav_request_id); |
| EXPECT_EQ(iframe_rfh->GetPageUkmSourceId(), nav_request_id); |
| } |
| |
| // Test that FrameTree::CollectNodesForIsLoading doesn't include inner |
| // WebContents nodes. |
| IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, NodesForIsLoading) { |
| ASSERT_TRUE(https_server()->Start()); |
| GURL url_a(https_server()->GetURL("c.test", "/page_with_iframe.html")); |
| GURL url_b(https_server()->GetURL("c.test", "/title1.html")); |
| GURL fenced_frame_url( |
| https_server()->GetURL("c.test", "/fenced_frames/title1.html")); |
| |
| // 1. Navigate to an initial primary page. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImplWrapper primary_rfh(primary_main_frame_host()); |
| FrameTree& primary_frame_tree = web_contents()->GetPrimaryFrameTree(); |
| |
| // 2. Create a fenced frame embedded inside primary page. |
| RenderFrameHostImplWrapper outer_fenced_frame_rfh( |
| fenced_frame_test_helper().CreateFencedFrame(primary_rfh.get(), |
| fenced_frame_url)); |
| |
| // 3. Create a inner WebContents and attach it to the main contents. Navigate |
| // the inner web contents to an initial page. |
| WebContentsImpl* inner_contents = |
| static_cast<WebContentsImpl*>(CreateAndAttachInnerContents( |
| primary_rfh.get()->child_at(0)->current_frame_host())); |
| ASSERT_TRUE(NavigateToURLFromRenderer(inner_contents, url_b)); |
| |
| RenderFrameHostImpl* inner_contents_rfh = inner_contents->GetMainFrame(); |
| FrameTree& inner_contents_primary_frame_tree = |
| inner_contents->GetPrimaryFrameTree(); |
| ASSERT_TRUE(inner_contents_rfh); |
| |
| // 4. Create a fenced frame embedded inside inner WebContents. |
| RenderFrameHostImplWrapper inner_fenced_frame_rfh( |
| fenced_frame_test_helper().CreateFencedFrame(inner_contents_rfh, |
| fenced_frame_url)); |
| |
| // 5. FrameTree::CollectNodesForIsLoading should only include primary_rfh and |
| // outer_fenced_frame_rfh when checked against outer delegate FrameTree. |
| std::vector<RenderFrameHostImpl*> outer_web_contents_frames; |
| for (auto* ftn : |
| web_contents()->GetPrimaryFrameTree().CollectNodesForIsLoading()) { |
| outer_web_contents_frames.push_back(ftn->current_frame_host()); |
| } |
| EXPECT_EQ(outer_web_contents_frames.size(), 2u); |
| EXPECT_THAT(outer_web_contents_frames, |
| testing::UnorderedElementsAre(primary_rfh.get(), |
| outer_fenced_frame_rfh.get())); |
| |
| // 6. FrameTree::CollectNodesForIsLoading should only include |
| // inner_contents_rfh and inner_fenced_frame_rfh when checked against inner |
| // delegate FrameTree. |
| std::vector<RenderFrameHostImpl*> inner_web_contents_frames; |
| for (auto* ftn : |
| inner_contents->GetPrimaryFrameTree().CollectNodesForIsLoading()) { |
| inner_web_contents_frames.push_back(ftn->current_frame_host()); |
| } |
| EXPECT_EQ(inner_web_contents_frames.size(), 2u); |
| EXPECT_THAT(inner_web_contents_frames, |
| testing::UnorderedElementsAre(inner_contents_rfh, |
| inner_fenced_frame_rfh.get())); |
| |
| // 7. Check that FrameTree::LoadingTree returns the correct FrameTree for both |
| // outer and inner WebContents frame trees. |
| EXPECT_NE(primary_frame_tree.LoadingTree(), |
| inner_contents_primary_frame_tree.LoadingTree()); |
| EXPECT_EQ(primary_frame_tree.LoadingTree(), &primary_frame_tree); |
| EXPECT_EQ(inner_contents_primary_frame_tree.LoadingTree(), |
| &inner_contents_primary_frame_tree); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, |
| NoErrorPageOnEmptyFrameHttpError) { |
| ASSERT_TRUE(https_server()->Start()); |
| const GURL kInitialUrl(https_server()->GetURL("c.test", "/title1.html")); |
| const GURL kEmpty404Url( |
| https_server()->GetURL("c.test", "/fenced_frames/empty404.html")); |
| |
| // Load an initial page. |
| EXPECT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| RenderFrameHostImplWrapper initial_rfh(primary_main_frame_host()); |
| |
| // Add a fenced frame empty page with 404 status. |
| RenderFrameHostImplWrapper fenced_frame_rfh( |
| fenced_frame_test_helper().CreateFencedFrame(initial_rfh.get(), |
| kEmpty404Url)); |
| ASSERT_TRUE(fenced_frame_rfh); |
| |
| // Confirm no error page was generated in its place. |
| std::string contents = |
| EvalJs(fenced_frame_rfh.get(), "document.body.textContent;") |
| .ExtractString(); |
| EXPECT_EQ(contents, std::string()); |
| } |
| |
| // Test that when the documents inside the fenced frame tree are loading, then |
| // `WebContents::IsLoading`, `FrameTree::IsLoading`, and |
| // `FrameTreeNode::IsLoading` should return true. Primary `FrameTree::IsLoading` |
| // value should reflect the loading state of descendant fenced frames. |
| IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, IsLoading) { |
| // Create a HTTP response to control fenced frame navigation. |
| net::test_server::ControllableHttpResponse fenced_frame_response( |
| https_server(), "/fenced_frames/title2.html"); |
| ASSERT_TRUE(https_server()->Start()); |
| |
| // Navigate to primary url. |
| EXPECT_TRUE( |
| NavigateToURL(shell(), https_server()->GetURL("c.test", "/title1.html"))); |
| RenderFrameHostImpl* fenced_frame_parent_rfh = primary_main_frame_host(); |
| |
| // Create a fenced frame for fenced_frame_url. |
| const GURL fenced_frame_url = |
| https_server()->GetURL("c.test", "/fenced_frames/title2.html"); |
| fenced_frame_test_helper().CreateFencedFrameAsync(fenced_frame_parent_rfh, |
| fenced_frame_url); |
| |
| std::vector<FencedFrame*> fenced_frames = |
| fenced_frame_parent_rfh->GetFencedFrames(); |
| EXPECT_EQ(fenced_frames.size(), 1ul); |
| FencedFrame* fenced_frame = fenced_frames.back(); |
| |
| RenderFrameHostImplWrapper inner_fenced_frame_rfh( |
| fenced_frame->GetInnerRoot()); |
| FrameTreeNode* fenced_frame_root_node = |
| inner_fenced_frame_rfh->frame_tree_node(); |
| FrameTree* fenced_frame_tree = fenced_frame_root_node->frame_tree(); |
| |
| // All WebContents::IsLoading, FrameTree::IsLoading, and |
| // FrameTreeNode::IsLoading should return true when the fenced frame is |
| // loading along with primary FrameTree::IsLoading as we check for inner frame |
| // trees loading state. |
| EXPECT_TRUE(web_contents()->IsLoading()); |
| EXPECT_TRUE(primary_main_frame_host()->frame_tree()->IsLoading()); |
| EXPECT_TRUE(fenced_frame_root_node->IsLoading()); |
| EXPECT_TRUE(fenced_frame_tree->IsLoading()); |
| |
| // Complete the fenced frame response and finish fenced frame navigation. |
| fenced_frame_response.WaitForRequest(); |
| fenced_frame_response.Send( |
| "HTTP/1.1 200 OK\r\n" |
| "Content-Type: text/html; charset=utf-8\r\n" |
| "Supports-Loading-Mode: fenced-frame\r\n" |
| "\r\n"); |
| fenced_frame_response.Done(); |
| |
| // Check that all the above loading states should return false once the fenced |
| // frame stops loading. |
| EXPECT_TRUE(WaitForLoadStop(web_contents())); |
| EXPECT_FALSE(web_contents()->IsLoading()); |
| EXPECT_FALSE(primary_main_frame_host()->frame_tree()->IsLoading()); |
| EXPECT_FALSE(fenced_frame_root_node->IsLoading()); |
| EXPECT_FALSE(fenced_frame_tree->IsLoading()); |
| } |
| |
| // Test that when the documents inside the fenced frame tree are loading, |
| // WebContentsObserver::DidStartLoading is fired and when document stops loading |
| // WebContentsObserver::DidStopLoading is fired. In this test primary page |
| // completed loading before fenced frame starts loading and we test the loading |
| // state in the end when both primary page and fenced frame completed loading. |
| IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, DidStartAndDidStopLoading) { |
| // Create a HTTP response to control fenced frame navigation. |
| net::test_server::ControllableHttpResponse fenced_frame_response( |
| https_server(), "/fenced_frames/title2.html"); |
| ASSERT_TRUE(https_server()->Start()); |
| |
| // Navigate to primary url. |
| EXPECT_TRUE( |
| NavigateToURL(shell(), https_server()->GetURL("c.test", "/title1.html"))); |
| EXPECT_FALSE(web_contents()->IsLoading()); |
| RenderFrameHostImpl* fenced_frame_parent_rfh = primary_main_frame_host(); |
| EXPECT_EQ(fenced_frame_parent_rfh->GetFencedFrames().size(), 0ul); |
| |
| // Initialize a MockWebContentsObserver and ensure that |
| // DidStartLoading and DidStopLoading are invoked. |
| testing::NiceMock<MockWebContentsObserver> web_contents_observer( |
| web_contents()); |
| testing::InSequence s; |
| |
| // Create a fenced frame for fenced_frame_url. This will result in invoking |
| // DidStartLoading callback once. |
| EXPECT_CALL(web_contents_observer, DidStartLoading()).Times(1); |
| const GURL fenced_frame_url = |
| https_server()->GetURL("c.test", "/fenced_frames/title2.html"); |
| fenced_frame_test_helper().CreateFencedFrameAsync(fenced_frame_parent_rfh, |
| fenced_frame_url); |
| std::vector<FencedFrame*> fenced_frames = |
| fenced_frame_parent_rfh->GetFencedFrames(); |
| EXPECT_EQ(fenced_frame_parent_rfh->GetFencedFrames().size(), 1ul); |
| EXPECT_TRUE(web_contents()->IsLoading()); |
| |
| // Complete the fenced frame response and finish fenced frame navigation. |
| fenced_frame_response.WaitForRequest(); |
| fenced_frame_response.Send( |
| "HTTP/1.1 200 OK\r\n" |
| "Content-Type: text/html; charset=utf-8\r\n" |
| "Supports-Loading-Mode: fenced-frame\r\n" |
| "\r\n"); |
| fenced_frame_response.Done(); |
| |
| // Once the fenced frame stops loading, this should result in invoking |
| // the DidStopLoading callback once. |
| EXPECT_CALL(web_contents_observer, DidStopLoading()).Times(1); |
| EXPECT_TRUE(WaitForLoadStop(web_contents())); |
| } |
| |
| // Ensure that WebContentsObserver::LoadProgressChanged is not invoked when |
| // there is a change in load state of fenced frame as LoadProgressChanged is |
| // attributed to only primary main frame load progress change. |
| IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, LoadProgressChanged) { |
| ASSERT_TRUE(https_server()->Start()); |
| const GURL fenced_frame_url = |
| https_server()->GetURL("c.test", "/fenced_frames/title1.html"); |
| const GURL main_url = https_server()->GetURL("c.test", "/title1.html"); |
| |
| // Navigate to primary url. |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| RenderFrameHostImplWrapper primary_rfh(primary_main_frame_host()); |
| |
| // Initialize a MockWebContentsObserver and ensure that LoadProgressChanged is |
| // not invoked for fenced frames. |
| testing::NiceMock<MockWebContentsObserver> web_contents_observer( |
| web_contents()); |
| |
| // Create a fenced frame for fenced_frame_url. This shouldn't call |
| // LoadProgressChanged. |
| EXPECT_CALL(web_contents_observer, LoadProgressChanged(testing::_)).Times(0); |
| fenced_frame_test_helper().CreateFencedFrame(primary_rfh.get(), |
| fenced_frame_url); |
| } |
| |
| // Tests that NavigationHandle::GetNavigatingFrameType() returns the correct |
| // type. |
| IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, NavigationHandleFrameType) { |
| ASSERT_TRUE(https_server()->Start()); |
| { |
| DidFinishNavigationObserver observer( |
| web_contents(), |
| base::BindLambdaForTesting([](NavigationHandle* navigation_handle) { |
| EXPECT_TRUE(navigation_handle->IsInPrimaryMainFrame()); |
| DCHECK_EQ(navigation_handle->GetNavigatingFrameType(), |
| FrameType::kPrimaryMainFrame); |
| })); |
| EXPECT_TRUE(NavigateToURL( |
| shell(), https_server()->GetURL("c.test", "/title1.html"))); |
| } |
| |
| { |
| DidFinishNavigationObserver observer( |
| web_contents(), |
| base::BindLambdaForTesting([](NavigationHandle* navigation_handle) { |
| EXPECT_FALSE(navigation_handle->IsInMainFrame()); |
| DCHECK_EQ(navigation_handle->GetNavigatingFrameType(), |
| FrameType::kSubframe); |
| })); |
| EXPECT_TRUE( |
| ExecJs(primary_main_frame_host(), |
| JsReplace(kAddIframeScript, |
| https_server()->GetURL("c.test", "/empty.html")))); |
| } |
| { |
| const GURL fenced_frame_url = |
| https_server()->GetURL("c.test", "/fenced_frames/title1.html"); |
| RenderFrameHostImplWrapper fenced_frame_rfh( |
| fenced_frame_test_helper().CreateFencedFrame(primary_main_frame_host(), |
| fenced_frame_url)); |
| DidFinishNavigationObserver observer( |
| web_contents(), |
| base::BindLambdaForTesting([](NavigationHandle* navigation_handle) { |
| EXPECT_TRUE( |
| navigation_handle->GetRenderFrameHost()->IsFencedFrameRoot()); |
| DCHECK_EQ(navigation_handle->GetNavigatingFrameType(), |
| FrameType::kFencedFrameRoot); |
| })); |
| fenced_frame_test_helper().NavigateFrameInFencedFrameTree( |
| fenced_frame_rfh.get(), fenced_frame_url); |
| } |
| } |
| |
| // Tests that an unload/beforeunload event handler won't be set from |
| // fenced frames. |
| IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, UnloadHandler) { |
| ASSERT_TRUE(https_server()->Start()); |
| const GURL main_url = |
| https_server()->GetURL("c.test", "/fenced_frames/title1.html"); |
| EXPECT_TRUE( |
| NavigateToURL(shell(), https_server()->GetURL("c.test", "/title1.html"))); |
| RenderFrameHostImplWrapper primary_rfh(primary_main_frame_host()); |
| RenderFrameHostImplWrapper fenced_frame_rfh( |
| fenced_frame_test_helper().CreateFencedFrame(primary_rfh.get(), |
| main_url)); |
| |
| const char kConsolePattern[] = |
| "unload/beforeunload handlers are prohibited in fenced frames."; |
| { |
| WebContentsConsoleObserver console_observer(web_contents()); |
| console_observer.SetPattern(kConsolePattern); |
| EXPECT_TRUE(ExecJs(fenced_frame_rfh.get(), |
| "window.addEventListener('beforeunload', (e) => {});")); |
| console_observer.Wait(); |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| } |
| { |
| WebContentsConsoleObserver console_observer(web_contents()); |
| console_observer.SetPattern(kConsolePattern); |
| EXPECT_TRUE(ExecJs(fenced_frame_rfh.get(), |
| "window.addEventListener('unload', (e) => {});")); |
| console_observer.Wait(); |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| } |
| { |
| WebContentsConsoleObserver console_observer(web_contents()); |
| console_observer.SetPattern(kConsolePattern); |
| EXPECT_TRUE(ExecJs(fenced_frame_rfh.get(), |
| "window.>)); |
| console_observer.Wait(); |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| } |
| { |
| WebContentsConsoleObserver console_observer(web_contents()); |
| console_observer.SetPattern(kConsolePattern); |
| EXPECT_TRUE( |
| ExecJs(fenced_frame_rfh.get(), "window.>)); |
| console_observer.Wait(); |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| } |
| } |
| |
| // Tests that an input event targeted to a fenced frame correctly |
| // triggers a user interaction notification for WebContentsObservers. |
| IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, UserInteractionForFencedFrame) { |
| ASSERT_TRUE(https_server()->Start()); |
| const GURL main_url = https_server()->GetURL("c.test", "/title1.html"); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| RenderFrameHostImplWrapper primary_rfh(primary_main_frame_host()); |
| |
| const GURL fenced_frame_url = |
| https_server()->GetURL("c.test", "/fenced_frames/title1.html"); |
| RenderFrameHostImplWrapper fenced_frame_rfh( |
| fenced_frame_test_helper().CreateFencedFrame(primary_rfh.get(), |
| fenced_frame_url)); |
| |
| ::testing::NiceMock<MockWebContentsObserver> web_contents_observer( |
| web_contents()); |
| EXPECT_CALL(web_contents_observer, DidGetUserInteraction(testing::_)) |
| .Times(1); |
| |
| // Target an event to the fenced frame's RenderWidgetHostView. |
| blink::WebMouseEvent mouse_event( |
| blink::WebInputEvent::Type::kMouseDown, |
| blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| mouse_event.button = blink::WebPointerProperties::Button::kLeft; |
| mouse_event.SetPositionInWidget(5, 5); |
| fenced_frame_rfh->GetRenderWidgetHost()->ForwardMouseEvent(mouse_event); |
| } |
| |
| namespace { |
| |
| enum class FrameTypeWithOrigin { |
| kSameOriginIframe, |
| kCrossOriginIframe, |
| kSameOriginFencedFrame, |
| kCrossOriginFencedFrame, |
| }; |
| |
| const std::vector<FrameTypeWithOrigin> kTestParameters[] = { |
| {}, |
| |
| {FrameTypeWithOrigin::kSameOriginIframe}, |
| {FrameTypeWithOrigin::kCrossOriginIframe}, |
| {FrameTypeWithOrigin::kSameOriginIframe, |
| FrameTypeWithOrigin::kSameOriginIframe}, |
| {FrameTypeWithOrigin::kSameOriginIframe, |
| FrameTypeWithOrigin::kCrossOriginIframe}, |
| {FrameTypeWithOrigin::kCrossOriginIframe, |
| FrameTypeWithOrigin::kSameOriginIframe}, |
| {FrameTypeWithOrigin::kCrossOriginIframe, |
| FrameTypeWithOrigin::kCrossOriginIframe}, |
| |
| {FrameTypeWithOrigin::kSameOriginFencedFrame}, |
| {FrameTypeWithOrigin::kCrossOriginFencedFrame}, |
| {FrameTypeWithOrigin::kSameOriginFencedFrame, |
| FrameTypeWithOrigin::kSameOriginIframe}, |
| {FrameTypeWithOrigin::kSameOriginFencedFrame, |
| FrameTypeWithOrigin::kCrossOriginIframe}, |
| {FrameTypeWithOrigin::kCrossOriginFencedFrame, |
| FrameTypeWithOrigin::kSameOriginIframe}, |
| {FrameTypeWithOrigin::kCrossOriginFencedFrame, |
| FrameTypeWithOrigin::kCrossOriginIframe}}; |
| |
| static std::string TestParamToString( |
| ::testing::TestParamInfo<std::tuple<std::vector<FrameTypeWithOrigin>, |
| bool /* shadow_dom_fenced_frame */>> |
| param_info) { |
| std::string out; |
| for (const auto& frame_type : std::get<0>(param_info.param)) { |
| switch (frame_type) { |
| case FrameTypeWithOrigin::kSameOriginIframe: |
| out += "SameI_"; |
| break; |
| case FrameTypeWithOrigin::kCrossOriginIframe: |
| out += "CrossI_"; |
| break; |
| case FrameTypeWithOrigin::kSameOriginFencedFrame: |
| out += "SameF_"; |
| break; |
| case FrameTypeWithOrigin::kCrossOriginFencedFrame: |
| out += "CrossF_"; |
| break; |
| } |
| } |
| if (std::get<1>(param_info.param)) { |
| out += "ShadowDOM"; |
| } else { |
| out += "MPArch"; |
| } |
| return out; |
| } |
| |
| const char* kSameOriginHostName = "a.test"; |
| const char* kCrossOriginHostName = "b.test"; |
| |
| const char* GetHostNameForFrameType(FrameTypeWithOrigin type) { |
| switch (type) { |
| case FrameTypeWithOrigin::kSameOriginIframe: |
| return kSameOriginHostName; |
| case FrameTypeWithOrigin::kCrossOriginIframe: |
| return kCrossOriginHostName; |
| case FrameTypeWithOrigin::kSameOriginFencedFrame: |
| return kSameOriginHostName; |
| case FrameTypeWithOrigin::kCrossOriginFencedFrame: |
| return kCrossOriginHostName; |
| } |
| } |
| |
| bool IsFencedFrameType(FrameTypeWithOrigin type) { |
| switch (type) { |
| case FrameTypeWithOrigin::kSameOriginIframe: |
| return false; |
| case FrameTypeWithOrigin::kCrossOriginIframe: |
| return false; |
| case FrameTypeWithOrigin::kSameOriginFencedFrame: |
| return true; |
| case FrameTypeWithOrigin::kCrossOriginFencedFrame: |
| return true; |
| } |
| } |
| |
| } // namespace |
| |
| class FencedFrameNestedFrameBrowserTest |
| : public ContentBrowserTest, |
| public testing::WithParamInterface< |
| std::tuple<std::vector<FrameTypeWithOrigin>, |
| bool /* shadow_dom_fenced_frame */>> { |
| protected: |
| FencedFrameNestedFrameBrowserTest() { |
| if (std::get<1>(GetParam())) { |
| feature_list_.InitAndEnableFeatureWithParameters( |
| blink::features::kFencedFrames, |
| {{"implementation_type", "shadow_dom"}}); |
| } else { |
| fenced_frame_helper_ = std::make_unique<test::FencedFrameTestHelper>(); |
| } |
| } |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| ContentBrowserTest::SetUpOnMainThread(); |
| |
| https_server()->AddDefaultHandlers(GetTestDataFilePath()); |
| https_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES); |
| SetupCrossSiteRedirector(https_server()); |
| ASSERT_TRUE(https_server()->Start()); |
| } |
| |
| WebContentsImpl* web_contents() { |
| return static_cast<WebContentsImpl*>(shell()->web_contents()); |
| } |
| |
| RenderFrameHostImpl* LoadNestedFrame() { |
| const GURL main_url = |
| https_server()->GetURL(kSameOriginHostName, "/title1.html"); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| RenderFrameHostImpl* frame = |
| static_cast<RenderFrameHostImpl*>(web_contents()->GetMainFrame()); |
| int depth = 0; |
| for (const auto& type : std::get<0>(GetParam())) { |
| ++depth; |
| frame = CreateFrame(frame, type, depth); |
| } |
| return frame; |
| } |
| |
| bool IsInFencedFrameTest() { |
| for (const auto& type : std::get<0>(GetParam())) { |
| if (IsFencedFrameType(type)) |
| return true; |
| } |
| return false; |
| } |
| |
| net::EmbeddedTestServer* https_server() { return &https_server_; } |
| |
| private: |
| RenderFrameHostImpl* CreateFrame(RenderFrameHostImpl* parent, |
| FrameTypeWithOrigin type, |
| int depth) { |
| const GURL url = https_server()->GetURL( |
| GetHostNameForFrameType(type), |
| "/fenced_frames/title1.html?depth=" + base::NumberToString(depth)); |
| |
| if (IsFencedFrameType(type)) { |
| if (fenced_frame_helper_) { |
| return static_cast<RenderFrameHostImpl*>( |
| fenced_frame_helper_->CreateFencedFrame(parent, url)); |
| } |
| // FencedFrameTestHelper only supports the MPArch version of fenced |
| // frames. So need to maually create a fenced frame for the ShadowDOM |
| // version. |
| constexpr char kAddFencedFrameScript[] = R"({ |
| frame = document.createElement('fencedframe'); |
| document.body.appendChild(frame); |
| })"; |
| EXPECT_TRUE(ExecJs(parent, kAddFencedFrameScript)); |
| constexpr char kNavigateFencedFrameScript[] = R"({ |
| document.body.getElementsByTagName('fencedframe')[0].src = $1; |
| })"; |
| RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>( |
| parent->child_at(0)->current_frame_host()); |
| TestFrameNavigationObserver observer(rfh); |
| EXPECT_TRUE(ExecJs(parent, JsReplace(kNavigateFencedFrameScript, url))); |
| observer.Wait(); |
| return static_cast<RenderFrameHostImpl*>(ChildFrameAt(parent, 0)); |
| } |
| EXPECT_TRUE(ExecJs(parent, JsReplace(kAddIframeScript, url))); |
| |
| return static_cast<RenderFrameHostImpl*>(ChildFrameAt(parent, 0)); |
| } |
| |
| std::unique_ptr<test::FencedFrameTestHelper> fenced_frame_helper_; |
| base::test::ScopedFeatureList feature_list_; |
| net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS}; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(FencedFrameNestedFrameBrowserTest, |
| IsNestedWithinFencedFrame) { |
| RenderFrameHostImpl* rfh = LoadNestedFrame(); |
| EXPECT_EQ(IsInFencedFrameTest(), rfh->IsNestedWithinFencedFrame()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(FencedFrameNestedFrameBrowserTest, |
| FencedFrameNestedFrameBrowserTest, |
| testing::Combine(testing::ValuesIn(kTestParameters), |
| testing::Bool()), |
| TestParamToString); |
| |
| namespace { |
| |
| static std::string ModeTestParamToString( |
| ::testing::TestParamInfo<std::tuple<blink::mojom::FencedFrameMode, |
| blink::mojom::FencedFrameMode, |
| bool /* shadow_dom_fenced_frame */>> |
| param_info) { |
| std::string out = "ParentMode"; |
| switch (std::get<0>(param_info.param)) { |
| case blink::mojom::FencedFrameMode::kDefault: |
| out += "Default"; |
| break; |
| case blink::mojom::FencedFrameMode::kOpaqueAds: |
| out += "OpaqueAds"; |
| break; |
| } |
| |
| out += "_ChildMode"; |
| |
| switch (std::get<1>(param_info.param)) { |
| case blink::mojom::FencedFrameMode::kDefault: |
| out += "Default"; |
| break; |
| case blink::mojom::FencedFrameMode::kOpaqueAds: |
| out += "OpaqueAds"; |
| break; |
| } |
| |
| out += "_Arch"; |
| |
| if (std::get<2>(param_info.param)) { |
| out += "ShadowDOM"; |
| } else { |
| out += "MPArch"; |
| } |
| |
| return out; |
| } |
| |
| } // namespace |
| |
| class FencedFrameNestedModesTest |
| : public ContentBrowserTest, |
| public testing::WithParamInterface< |
| std::tuple<blink::mojom::FencedFrameMode, |
| blink::mojom::FencedFrameMode, |
| bool /*shadow_dom_fenced_frame*/>> { |
| protected: |
| FencedFrameNestedModesTest() { |
| if (std::get<2>(GetParam())) { |
| feature_list_.InitAndEnableFeatureWithParameters( |
| blink::features::kFencedFrames, |
| {{"implementation_type", "shadow_dom"}}); |
| } else { |
| fenced_frame_test_helper_ = |
| std::make_unique<test::FencedFrameTestHelper>(); |
| } |
| } |
| |
| std::string GetParentMode() { return ModeToString(std::get<0>(GetParam())); } |
| std::string GetChildMode() { return ModeToString(std::get<1>(GetParam())); } |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| ContentBrowserTest::SetUpOnMainThread(); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| } |
| |
| WebContentsImpl* web_contents() { |
| return static_cast<WebContentsImpl*>(shell()->web_contents()); |
| } |
| |
| RenderFrameHostImpl* primary_main_frame_host() { |
| return web_contents()->GetMainFrame(); |
| } |
| |
| test::FencedFrameTestHelper& fenced_frame_test_helper() { |
| return *fenced_frame_test_helper_.get(); |
| } |
| |
| private: |
| std::string ModeToString(blink::mojom::FencedFrameMode mode) { |
| switch (mode) { |
| case blink::mojom::FencedFrameMode::kDefault: |
| return "default"; |
| case blink::mojom::FencedFrameMode::kOpaqueAds: |
| return "opaque-ads"; |
| } |
| |
| NOTREACHED(); |
| return ""; |
| } |
| |
| // This is a unique ptr because in some test cases we don't want to use it, |
| // and it automatically enables MPArch fenced frames when created. |
| std::unique_ptr<test::FencedFrameTestHelper> fenced_frame_test_helper_; |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| // This test runs the following steps: |
| // 1.) Creates a "parent" fenced frame with a particular mode |
| // 2.) Creates a nested/child fenced frame with a particular mode |
| // 3.) Asserts that creation of the child fenced frame either failed or |
| // succeeded depending on its mode. |
| IN_PROC_BROWSER_TEST_P(FencedFrameNestedModesTest, NestedModes) { |
| // TODO(lbrady): Don't skip this test for the MPArch implementation, once the |
| // `mode` attribute restrictions are implemented for MPArch. |
| if (std::get<2>(GetParam()) == false) { |
| return; |
| } |
| |
| const GURL main_url = |
| embedded_test_server()->GetURL("fencedframe.test", "/title1.html"); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| // 1.) Create the parent fenced frame. |
| constexpr char kAddFencedFrameScript[] = R"({ |
| const fenced_frame = document.createElement('fencedframe'); |
| fenced_frame.mode = $1; |
| document.body.appendChild(fenced_frame); |
| })"; |
| EXPECT_TRUE(ExecJs(primary_main_frame_host(), |
| JsReplace(kAddFencedFrameScript, GetParentMode()))); |
| |
| // Get the fenced frame's RFH. |
| ASSERT_EQ(1u, primary_main_frame_host()->child_count()); |
| RenderFrameHostImpl* parent_fenced_frame_rfh = |
| primary_main_frame_host()->child_at(0)->current_frame_host(); |
| if (blink::features::IsFencedFramesMPArchBased()) { |
| int inner_node_id = |
| parent_fenced_frame_rfh->inner_tree_main_frame_tree_node_id(); |
| parent_fenced_frame_rfh = |
| FrameTreeNode::GloballyFindByID(inner_node_id)->current_frame_host(); |
| } |
| ASSERT_TRUE(parent_fenced_frame_rfh); |
| |
| // 2.) Attempt to create the child fenced frame. |
| EXPECT_TRUE(ExecJs(parent_fenced_frame_rfh, |
| JsReplace(kAddFencedFrameScript, GetChildMode()))); |
| |
| // 3.) Assert that the child fenced frame was created or not created depending |
| // on the test parameters. |
| if (GetParentMode() != GetChildMode()) { |
| EXPECT_EQ(0u, parent_fenced_frame_rfh->child_count()); |
| // Child fenced frame creation should have failed based on its mode. |
| } else { |
| EXPECT_EQ(1u, parent_fenced_frame_rfh->child_count()); |
| // Child fenced frame creation should have succeeded because its mode is the |
| // same as its parent. |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| FencedFrameNestedModesTest, |
| FencedFrameNestedModesTest, |
| testing::Combine(/*parent mode=*/testing::Values( |
| blink::mojom::FencedFrameMode::kDefault, |
| blink::mojom::FencedFrameMode::kOpaqueAds), |
| /*child mode=*/ |
| testing::Values(blink::mojom::FencedFrameMode::kDefault, |
| blink::mojom::FencedFrameMode::kOpaqueAds), |
| /*is_shadowdom=*/testing::Bool()), |
| ModeTestParamToString); |
| |
| } // namespace content |