// Copyright (c) 2012 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 "content/browser/site_per_process_browsertest.h" #include #include #include "base/command_line.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "content/browser/frame_host/cross_process_frame_connector.h" #include "content/browser/frame_host/frame_tree.h" #include "content/browser/frame_host/navigator.h" #include "content/browser/frame_host/render_frame_proxy_host.h" #include "content/browser/frame_host/render_widget_host_view_child_frame.h" #include "content/browser/renderer_host/render_view_host_impl.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/common/frame_messages.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_types.h" #include "content/public/common/content_switches.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/content_browser_test_utils.h" #include "content/public/test/test_navigation_observer.h" #include "content/public/test/test_utils.h" #include "content/shell/browser/shell.h" #include "content/test/content_browser_test_utils_internal.h" #include "content/test/test_frame_navigation_observer.h" #include "ipc/ipc_security_test_util.h" #include "net/dns/mock_host_resolver.h" #include "net/test/embedded_test_server/embedded_test_server.h" namespace content { class RedirectNotificationObserver : public NotificationObserver { public: // Register to listen for notifications of the given type from either a // specific source, or from all sources if |source| is // NotificationService::AllSources(). RedirectNotificationObserver(int notification_type, const NotificationSource& source); ~RedirectNotificationObserver() override; // Wait until the specified notification occurs. If the notification was // emitted between the construction of this object and this call then it // returns immediately. void Wait(); // Returns NotificationService::AllSources() if we haven't observed a // notification yet. const NotificationSource& source() const { return source_; } const NotificationDetails& details() const { return details_; } // NotificationObserver: void Observe(int type, const NotificationSource& source, const NotificationDetails& details) override; private: bool seen_; bool seen_twice_; bool running_; NotificationRegistrar registrar_; NotificationSource source_; NotificationDetails details_; scoped_refptr message_loop_runner_; DISALLOW_COPY_AND_ASSIGN(RedirectNotificationObserver); }; RedirectNotificationObserver::RedirectNotificationObserver( int notification_type, const NotificationSource& source) : seen_(false), running_(false), source_(NotificationService::AllSources()) { registrar_.Add(this, notification_type, source); } RedirectNotificationObserver::~RedirectNotificationObserver() {} void RedirectNotificationObserver::Wait() { if (seen_ && seen_twice_) return; running_ = true; message_loop_runner_ = new MessageLoopRunner; message_loop_runner_->Run(); EXPECT_TRUE(seen_); } void RedirectNotificationObserver::Observe( int type, const NotificationSource& source, const NotificationDetails& details) { source_ = source; details_ = details; seen_twice_ = seen_; seen_ = true; if (!running_) return; message_loop_runner_->Quit(); running_ = false; } // This observer keeps track of the number of created RenderFrameHosts. Tests // can use this to ensure that a certain number of child frames has been // created after navigating. class RenderFrameHostCreatedObserver : public WebContentsObserver { public: RenderFrameHostCreatedObserver(WebContents* web_contents, int expected_frame_count) : WebContentsObserver(web_contents), expected_frame_count_(expected_frame_count), frames_created_(0), message_loop_runner_(new MessageLoopRunner) {} ~RenderFrameHostCreatedObserver() override; // Runs a nested message loop and blocks until the expected number of // RenderFrameHosts is created. void Wait(); private: // WebContentsObserver void RenderFrameCreated(RenderFrameHost* render_frame_host) override; // The number of RenderFrameHosts to wait for. int expected_frame_count_; // The number of RenderFrameHosts that have been created. int frames_created_; // The MessageLoopRunner used to spin the message loop. scoped_refptr message_loop_runner_; DISALLOW_COPY_AND_ASSIGN(RenderFrameHostCreatedObserver); }; RenderFrameHostCreatedObserver::~RenderFrameHostCreatedObserver() { } void RenderFrameHostCreatedObserver::Wait() { message_loop_runner_->Run(); } void RenderFrameHostCreatedObserver::RenderFrameCreated( RenderFrameHost* render_frame_host) { frames_created_++; if (frames_created_ == expected_frame_count_) { message_loop_runner_->Quit(); } } // // SitePerProcessBrowserTest // SitePerProcessBrowserTest::SitePerProcessBrowserTest() { }; std::string SitePerProcessBrowserTest::DepictFrameTree(FrameTreeNode* node) { return visualizer_.DepictFrameTree(node); } void SitePerProcessBrowserTest::StartFrameAtDataURL() { std::string data_url_script = "var iframes = document.getElementById('test');iframes.src=" "'data:text/html,dataurl';"; ASSERT_TRUE(ExecuteScript(shell()->web_contents(), data_url_script)); } void SitePerProcessBrowserTest::SetUpCommandLine( base::CommandLine* command_line) { command_line->AppendSwitch(switches::kSitePerProcess); }; void SitePerProcessBrowserTest::SetUpOnMainThread() { host_resolver()->AddRule("*", "127.0.0.1"); ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); SetupCrossSiteRedirector(embedded_test_server()); } // Ensure that navigating subframes in --site-per-process mode works and the // correct documents are committed. IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, CrossSiteIframe) { GURL main_url(embedded_test_server()->GetURL("/site_per_process_main.html")); NavigateToURL(shell(), main_url); // It is safe to obtain the root frame tree node here, as it doesn't change. FrameTreeNode* root = static_cast(shell()->web_contents())-> GetFrameTree()->root(); TestNavigationObserver observer(shell()->web_contents()); // Load same-site page into iframe. FrameTreeNode* child = root->child_at(0); GURL http_url(embedded_test_server()->GetURL("/title1.html")); NavigateFrameToURL(child, http_url); EXPECT_EQ(http_url, observer.last_navigation_url()); EXPECT_TRUE(observer.last_navigation_succeeded()); { // There should be only one RenderWidgetHost when there are no // cross-process iframes. std::set views_set = static_cast(shell()->web_contents()) ->GetRenderWidgetHostViewsInTree(); EXPECT_EQ(1U, views_set.size()); } EXPECT_EQ( " Site A\n" " |--Site A\n" " +--Site A\n" " |--Site A\n" " +--Site A\n" " +--Site A\n" "Where A = http://127.0.0.1/", DepictFrameTree(root)); // Load cross-site page into iframe. GURL url = embedded_test_server()->GetURL("foo.com", "/title2.html"); NavigateFrameToURL(root->child_at(0), url); // Verify that the navigation succeeded and the expected URL was loaded. EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(url, observer.last_navigation_url()); // Ensure that we have created a new process for the subframe. ASSERT_EQ(2U, root->child_count()); SiteInstance* site_instance = child->current_frame_host()->GetSiteInstance(); RenderViewHost* rvh = child->current_frame_host()->render_view_host(); RenderProcessHost* rph = child->current_frame_host()->GetProcess(); EXPECT_NE(shell()->web_contents()->GetRenderViewHost(), rvh); EXPECT_NE(shell()->web_contents()->GetSiteInstance(), site_instance); EXPECT_NE(shell()->web_contents()->GetRenderProcessHost(), rph); { // There should be now two RenderWidgetHosts, one for each process // rendering a frame. std::set views_set = static_cast(shell()->web_contents()) ->GetRenderWidgetHostViewsInTree(); EXPECT_EQ(2U, views_set.size()); } RenderFrameProxyHost* proxy_to_parent = child->render_manager()->GetProxyToParent(); EXPECT_TRUE(proxy_to_parent); EXPECT_TRUE(proxy_to_parent->cross_process_frame_connector()); // The out-of-process iframe should have its own RenderWidgetHost, // independent of any RenderViewHost. EXPECT_NE( rvh->GetView(), proxy_to_parent->cross_process_frame_connector()->get_view_for_testing()); EXPECT_TRUE(child->current_frame_host()->GetRenderWidgetHost()); EXPECT_EQ( " Site A ------------ proxies for B\n" " |--Site B ------- proxies for A\n" " +--Site A ------- proxies for B\n" " |--Site A -- proxies for B\n" " +--Site A -- proxies for B\n" " +--Site A -- proxies for B\n" "Where A = http://127.0.0.1/\n" " B = http://foo.com/", DepictFrameTree(root)); // Load another cross-site page into the same iframe. url = embedded_test_server()->GetURL("bar.com", "/title3.html"); NavigateFrameToURL(root->child_at(0), url); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(url, observer.last_navigation_url()); // Check again that a new process is created and is different from the // top level one and the previous one. ASSERT_EQ(2U, root->child_count()); child = root->child_at(0); EXPECT_NE(shell()->web_contents()->GetRenderViewHost(), child->current_frame_host()->render_view_host()); EXPECT_NE(rvh, child->current_frame_host()->render_view_host()); EXPECT_NE(shell()->web_contents()->GetSiteInstance(), child->current_frame_host()->GetSiteInstance()); EXPECT_NE(site_instance, child->current_frame_host()->GetSiteInstance()); EXPECT_NE(shell()->web_contents()->GetRenderProcessHost(), child->current_frame_host()->GetProcess()); EXPECT_NE(rph, child->current_frame_host()->GetProcess()); { std::set views_set = static_cast(shell()->web_contents()) ->GetRenderWidgetHostViewsInTree(); EXPECT_EQ(2U, views_set.size()); } EXPECT_EQ(proxy_to_parent, child->render_manager()->GetProxyToParent()); EXPECT_TRUE(proxy_to_parent->cross_process_frame_connector()); EXPECT_NE( child->current_frame_host()->render_view_host()->GetView(), proxy_to_parent->cross_process_frame_connector()->get_view_for_testing()); EXPECT_TRUE(child->current_frame_host()->GetRenderWidgetHost()); EXPECT_EQ( " Site A ------------ proxies for C\n" " |--Site C ------- proxies for A\n" " +--Site A ------- proxies for C\n" " |--Site A -- proxies for C\n" " +--Site A -- proxies for C\n" " +--Site A -- proxies for C\n" "Where A = http://127.0.0.1/\n" " C = http://bar.com/", DepictFrameTree(root)); } // Tests OOPIF rendering by checking that the RWH of the iframe generates // OnSwapCompositorFrame message. #if defined(OS_ANDROID) // http://crbug.com/471850 #define MAYBE_CompositorFrameSwapped DISABLED_CompositorFrameSwapped #else #define MAYBE_CompositorFrameSwapped CompositorFrameSwapped #endif IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, MAYBE_CompositorFrameSwapped) { GURL main_url( embedded_test_server()->GetURL("/frame_tree/page_with_one_frame.html")); NavigateToURL(shell(), main_url); // It is safe to obtain the root frame tree node here, as it doesn't change. FrameTreeNode* root = static_cast(shell()->web_contents()) ->GetFrameTree() ->root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child_node = root->child_at(0); GURL site_url(embedded_test_server()->GetURL("baz.com", "/title1.html")); EXPECT_EQ(site_url, child_node->current_url()); EXPECT_NE(shell()->web_contents()->GetSiteInstance(), child_node->current_frame_host()->GetSiteInstance()); RenderWidgetHostViewBase* rwhv_base = static_cast( child_node->current_frame_host()->GetRenderWidgetHost()->GetView()); // Wait for OnSwapCompositorFrame message. while (rwhv_base->RendererFrameNumber() <= 0) { // TODO(lazyboy): Find a better way to avoid sleeping like this. See // http://crbug.com/405282 for details. base::RunLoop run_loop; base::MessageLoop::current()->PostDelayedTask( FROM_HERE, run_loop.QuitClosure(), base::TimeDelta::FromMilliseconds(10)); run_loop.Run(); } } // Ensure that OOPIFs are deleted after navigating to a new main frame. IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, CleanupCrossSiteIframe) { GURL main_url(embedded_test_server()->GetURL("/site_per_process_main.html")); NavigateToURL(shell(), main_url); // It is safe to obtain the root frame tree node here, as it doesn't change. FrameTreeNode* root = static_cast(shell()->web_contents())-> GetFrameTree()->root(); TestNavigationObserver observer(shell()->web_contents()); // Load a cross-site page into both iframes. GURL foo_url = embedded_test_server()->GetURL("foo.com", "/title2.html"); NavigateFrameToURL(root->child_at(0), foo_url); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(foo_url, observer.last_navigation_url()); NavigateFrameToURL(root->child_at(1), foo_url); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(foo_url, observer.last_navigation_url()); // Ensure that we have created a new process for the subframes. ASSERT_EQ(2U, root->child_count()); EXPECT_NE(shell()->web_contents()->GetSiteInstance(), root->child_at(0)->current_frame_host()->GetSiteInstance()); EXPECT_EQ(root->child_at(0)->current_frame_host()->GetSiteInstance(), root->child_at(1)->current_frame_host()->GetSiteInstance()); // Use Javascript in the parent to remove one of the frames and ensure that // the subframe goes away. EXPECT_TRUE(ExecuteScript(shell()->web_contents(), "document.body.removeChild(" "document.querySelectorAll('iframe')[0])")); ASSERT_EQ(1U, root->child_count()); // Load a new same-site page in the top-level frame and ensure the other // subframe goes away. GURL new_url(embedded_test_server()->GetURL("/title1.html")); NavigateToURL(shell(), new_url); ASSERT_EQ(0U, root->child_count()); } // Ensure that root frames cannot be detached. IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, RestrictFrameDetach) { GURL main_url(embedded_test_server()->GetURL("/site_per_process_main.html")); NavigateToURL(shell(), main_url); // It is safe to obtain the root frame tree node here, as it doesn't change. FrameTreeNode* root = static_cast(shell()->web_contents())-> GetFrameTree()->root(); TestNavigationObserver observer(shell()->web_contents()); // Load cross-site pages into both iframes. GURL foo_url = embedded_test_server()->GetURL("foo.com", "/title2.html"); NavigateFrameToURL(root->child_at(0), foo_url); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(foo_url, observer.last_navigation_url()); GURL bar_url = embedded_test_server()->GetURL("bar.com", "/title2.html"); NavigateFrameToURL(root->child_at(1), bar_url); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(bar_url, observer.last_navigation_url()); // Ensure that we have created new processes for the subframes. ASSERT_EQ(2U, root->child_count()); FrameTreeNode* foo_child = root->child_at(0); SiteInstance* foo_site_instance = foo_child->current_frame_host()->GetSiteInstance(); EXPECT_NE(shell()->web_contents()->GetSiteInstance(), foo_site_instance); FrameTreeNode* bar_child = root->child_at(1); SiteInstance* bar_site_instance = bar_child->current_frame_host()->GetSiteInstance(); EXPECT_NE(shell()->web_contents()->GetSiteInstance(), bar_site_instance); // TODO(nick): The following EXPECTs are disabled because of // http://crbug.com/476628, where the Site C node sometimes (flakily) has // children even though it's committed a nav to a page with no iframes. #if 0 EXPECT_EQ( " Site A ------------ proxies for B C\n" " |--Site B ------- proxies for A C\n" " +--Site C ------- proxies for A B\n" "Where A = http://127.0.0.1/\n" " B = http://foo.com/\n" " C = http://bar.com/", DepictFrameTree(root)); #endif // Simulate an attempt to detach the root frame from foo_site_instance. This // should kill foo_site_instance's process. RenderFrameProxyHost* foo_mainframe_rfph = root->render_manager()->GetRenderFrameProxyHost(foo_site_instance); content::RenderProcessHostWatcher foo_terminated( foo_mainframe_rfph->GetProcess(), content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); FrameHostMsg_Detach evil_msg2(foo_mainframe_rfph->GetRoutingID()); IPC::IpcSecurityTestUtil::PwnMessageReceived( foo_mainframe_rfph->GetProcess()->GetChannel(), evil_msg2); foo_terminated.Wait(); #if 0 EXPECT_EQ( " Site A ------------ proxies for B C\n" " |--Site B ------- proxies for A C\n" " +--Site C ------- proxies for A B\n" "Where A = http://127.0.0.1/\n" " B = http://foo.com/ (no process)\n" " C = http://bar.com/", DepictFrameTree(root)); #endif } // Disabled for flaky crashing: crbug.com/446575 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, NavigateRemoteFrame) { GURL main_url(embedded_test_server()->GetURL("/site_per_process_main.html")); NavigateToURL(shell(), main_url); // It is safe to obtain the root frame tree node here, as it doesn't change. FrameTreeNode* root = static_cast(shell()->web_contents())-> GetFrameTree()->root(); TestNavigationObserver observer(shell()->web_contents()); // Load same-site page into iframe. FrameTreeNode* child = root->child_at(0); GURL http_url(embedded_test_server()->GetURL("/title1.html")); NavigateFrameToURL(child, http_url); EXPECT_EQ(http_url, observer.last_navigation_url()); EXPECT_TRUE(observer.last_navigation_succeeded()); // Load cross-site page into iframe. GURL url = embedded_test_server()->GetURL("foo.com", "/title2.html"); NavigateFrameToURL(root->child_at(0), url); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(url, observer.last_navigation_url()); // Ensure that we have created a new process for the subframe. ASSERT_EQ(2U, root->child_count()); SiteInstance* site_instance = child->current_frame_host()->GetSiteInstance(); EXPECT_NE(shell()->web_contents()->GetSiteInstance(), site_instance); // Emulate the main frame changing the src of the iframe such that it // navigates cross-site. url = embedded_test_server()->GetURL("bar.com", "/title3.html"); NavigateIframeToURL(shell()->web_contents(), "test", url); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(url, observer.last_navigation_url()); // Check again that a new process is created and is different from the // top level one and the previous one. ASSERT_EQ(2U, root->child_count()); child = root->child_at(0); EXPECT_NE(shell()->web_contents()->GetSiteInstance(), child->current_frame_host()->GetSiteInstance()); EXPECT_NE(site_instance, child->current_frame_host()->GetSiteInstance()); // Navigate back to the parent's origin and ensure we return to the // parent's process. NavigateFrameToURL(child, http_url); EXPECT_EQ(http_url, observer.last_navigation_url()); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(shell()->web_contents()->GetSiteInstance(), child->current_frame_host()->GetSiteInstance()); } #if defined(OS_WIN) // http://crbug.com/446575 #define MAYBE_NavigateRemoteFrameToBlankAndDataURLs \ DISABLED_NavigateRemoteFrameToBlankAndDataURLs #else #define MAYBE_NavigateRemoteFrameToBlankAndDataURLs \ NavigateRemoteFrameToBlankAndDataURLs #endif IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, MAYBE_NavigateRemoteFrameToBlankAndDataURLs) { GURL main_url(embedded_test_server()->GetURL("/site_per_process_main.html")); NavigateToURL(shell(), main_url); // It is safe to obtain the root frame tree node here, as it doesn't change. FrameTreeNode* root = static_cast(shell()->web_contents())-> GetFrameTree()->root(); TestNavigationObserver observer(shell()->web_contents()); // Load same-site page into iframe. FrameTreeNode* child = root->child_at(0); GURL http_url(embedded_test_server()->GetURL("/title1.html")); NavigateFrameToURL(child, http_url); EXPECT_EQ(http_url, observer.last_navigation_url()); EXPECT_TRUE(observer.last_navigation_succeeded()); // Load cross-site page into iframe. GURL url = embedded_test_server()->GetURL("foo.com", "/title2.html"); NavigateFrameToURL(root->child_at(0), url); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(url, observer.last_navigation_url()); ASSERT_EQ(2U, root->child_count()); EXPECT_NE(shell()->web_contents()->GetSiteInstance(), root->child_at(0)->current_frame_host()->GetSiteInstance()); // Navigate iframe to a data URL. The navigation happens from a script in the // parent frame, so the data URL should be committed in the same SiteInstance // as the parent frame. GURL data_url("data:text/html,dataurl"); NavigateIframeToURL(shell()->web_contents(), "test", data_url); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(data_url, observer.last_navigation_url()); // Ensure that we have navigated using the top level process. EXPECT_EQ(shell()->web_contents()->GetSiteInstance(), root->child_at(0)->current_frame_host()->GetSiteInstance()); // Load cross-site page into iframe. url = embedded_test_server()->GetURL("bar.com", "/title2.html"); NavigateFrameToURL(root->child_at(0), url); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(url, observer.last_navigation_url()); ASSERT_EQ(2U, root->child_count()); EXPECT_NE(shell()->web_contents()->GetSiteInstance(), root->child_at(0)->current_frame_host()->GetSiteInstance()); // Navigate iframe to about:blank. The navigation happens from a script in the // parent frame, so it should be committed in the same SiteInstance as the // parent frame. GURL about_blank_url("about:blank"); NavigateIframeToURL(shell()->web_contents(), "test", about_blank_url); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(about_blank_url, observer.last_navigation_url()); // Ensure that we have navigated using the top level process. EXPECT_EQ(shell()->web_contents()->GetSiteInstance(), root->child_at(0)->current_frame_host()->GetSiteInstance()); } // This test checks that killing a renderer process of a remote frame // and then navigating some other frame to the same SiteInstance of the killed // process works properly. // This can be illustrated as follows, // where 1/2/3 are FrameTreeNode-s and A/B are processes and B* is the killed // B process: // // 1 A A A // / \ -> / \ -> Kill B -> / \ -> Navigate 3 to B -> / \ . // 2 3 B A B* A B* B // // Initially, node1.proxy_hosts_ = {B} // After we kill B, we make sure B stays in node1.proxy_hosts_, then we navigate // 3 to B and we expect that to complete normally. // See http://crbug.com/432107. // // Note that due to http://crbug.com/450681, node2 cannot be re-navigated to // site B and stays in not rendered state. IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, NavigateRemoteFrameToKilledProcess) { GURL main_url(embedded_test_server()->GetURL( "/frame_tree/page_with_two_frames.html")); NavigateToURL(shell(), main_url); // It is safe to obtain the root frame tree node here, as it doesn't change. FrameTreeNode* root = static_cast(shell()->web_contents())-> GetFrameTree()->root(); TestNavigationObserver observer(shell()->web_contents()); ASSERT_EQ(2U, root->child_count()); // Make sure node2 points to the correct cross-site page. GURL site_b_url = embedded_test_server()->GetURL("bar.com", "/title1.html"); FrameTreeNode* node2 = root->child_at(0); EXPECT_EQ(site_b_url, node2->current_url()); // Kill that cross-site renderer. RenderProcessHost* child_process = node2->current_frame_host()->GetProcess(); RenderProcessHostWatcher crash_observer( child_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); child_process->Shutdown(0, false); crash_observer.Wait(); // Now navigate the second iframe (node3) to the same site as the node2. FrameTreeNode* node3 = root->child_at(1); NavigateFrameToURL(node3, site_b_url); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(site_b_url, observer.last_navigation_url()); } // This test is similar to // SitePerProcessBrowserTest.NavigateRemoteFrameToKilledProcess with // addition that node2 also has a cross-origin frame to site C. // // 1 A A A // / \ / \ / \ / \ . // 2 3 -> B A -> Kill B -> B* A -> Navigate 3 -> B* B // / / // 4 C // // Initially, node1.proxy_hosts_ = {B, C} // After we kill B, we make sure B stays in node1.proxy_hosts_, but // C gets cleared from node1.proxy_hosts_. // // Note that due to http://crbug.com/450681, node2 cannot be re-navigated to // site B and stays in not rendered state. IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, NavigateRemoteFrameToKilledProcessWithSubtree) { GURL main_url(embedded_test_server()->GetURL( "a.com", "/frame_tree/page_with_two_frames_nested.html")); NavigateToURL(shell(), main_url); // It is safe to obtain the root frame tree node here, as it doesn't change. FrameTreeNode* root = static_cast(shell()->web_contents())-> GetFrameTree()->root(); TestNavigationObserver observer(shell()->web_contents()); ASSERT_EQ(2U, root->child_count()); GURL site_b_url( embedded_test_server()->GetURL( "bar.com", "/frame_tree/page_with_one_frame.html")); // We can't use a TestNavigationObserver to verify the URL here, // since the frame has children that may have clobbered it in the observer. EXPECT_EQ(site_b_url, root->child_at(0)->current_url()); // Ensure that a new process is created for node2. EXPECT_NE(shell()->web_contents()->GetSiteInstance(), root->child_at(0)->current_frame_host()->GetSiteInstance()); // Ensure that a new process is *not* created for node3. EXPECT_EQ(shell()->web_contents()->GetSiteInstance(), root->child_at(1)->current_frame_host()->GetSiteInstance()); ASSERT_EQ(1U, root->child_at(0)->child_count()); // Make sure node4 points to the correct cross-site page. FrameTreeNode* node4 = root->child_at(0)->child_at(0); GURL site_c_url(embedded_test_server()->GetURL("baz.com", "/title1.html")); EXPECT_EQ(site_c_url, node4->current_url()); // |site_instance_c| is expected to go away once we kill |child_process_b| // below, so create a local scope so we can extend the lifetime of // |site_instance_c| with a refptr. { // Initially each frame has proxies for the other sites. EXPECT_EQ( " Site A ------------ proxies for B C\n" " |--Site B ------- proxies for A C\n" " | +--Site C -- proxies for A B\n" " +--Site A ------- proxies for B C\n" "Where A = http://a.com/\n" " B = http://bar.com/\n" " C = http://baz.com/", DepictFrameTree(root)); // Kill the render process for Site B. RenderProcessHost* child_process_b = root->child_at(0)->current_frame_host()->GetProcess(); RenderProcessHostWatcher crash_observer( child_process_b, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); child_process_b->Shutdown(0, false); crash_observer.Wait(); // The Site C frame (a child of the crashed Site B frame) should go away, // and there should be no remaining proxies for site C anywhere. EXPECT_EQ( " Site A ------------ proxies for B\n" " |--Site B ------- proxies for A\n" " +--Site A ------- proxies for B\n" "Where A = http://a.com/\n" " B = http://bar.com/ (no process)", DepictFrameTree(root)); } // Now navigate the second iframe (node3) to Site B also. FrameTreeNode* node3 = root->child_at(1); GURL url = embedded_test_server()->GetURL("bar.com", "/title1.html"); NavigateFrameToURL(node3, url); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(url, observer.last_navigation_url()); EXPECT_EQ( " Site A ------------ proxies for B\n" " |--Site B ------- proxies for A\n" " +--Site B ------- proxies for A\n" "Where A = http://a.com/\n" " B = http://bar.com/", DepictFrameTree(root)); } // In A-embed-B-embed-C scenario, verify that killing process B clears proxies // of C from the tree. // // 1 A A // / \ / \ / \ . // 2 3 -> B A -> Kill B -> B* A // / / // 4 C // // node1 is the root. // Initially, both node1.proxy_hosts_ and node3.proxy_hosts_ contain C. // After we kill B, make sure proxies for C are cleared. IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, KillingRendererClearsDescendantProxies) { GURL main_url(embedded_test_server()->GetURL( "a.com", "/frame_tree/page_with_two_frames_nested.html")); NavigateToURL(shell(), main_url); // It is safe to obtain the root frame tree node here, as it doesn't change. FrameTreeNode* root = static_cast(shell()->web_contents())-> GetFrameTree()->root(); TestNavigationObserver observer(shell()->web_contents()); ASSERT_EQ(2U, root->child_count()); GURL site_b_url( embedded_test_server()->GetURL( "bar.com", "/frame_tree/page_with_one_frame.html")); // We can't use a TestNavigationObserver to verify the URL here, // since the frame has children that may have clobbered it in the observer. EXPECT_EQ(site_b_url, root->child_at(0)->current_url()); // Ensure that a new process is created for node2. EXPECT_NE(shell()->web_contents()->GetSiteInstance(), root->child_at(0)->current_frame_host()->GetSiteInstance()); // Ensure that a new process is *not* created for node3. EXPECT_EQ(shell()->web_contents()->GetSiteInstance(), root->child_at(1)->current_frame_host()->GetSiteInstance()); ASSERT_EQ(1U, root->child_at(0)->child_count()); // Make sure node4 points to the correct cross-site-page. FrameTreeNode* node4 = root->child_at(0)->child_at(0); GURL site_c_url(embedded_test_server()->GetURL("baz.com", "/title1.html")); EXPECT_EQ(site_c_url, node4->current_url()); // |site_instance_c| is expected to go away once we kill |child_process_b| // below; refcount it to extend the lifetime. scoped_refptr site_instance_c = node4->current_frame_host()->GetSiteInstance(); // Initially proxies for both B and C will be present in the root. EXPECT_EQ( " Site A ------------ proxies for B C\n" " |--Site B ------- proxies for A C\n" " | +--Site C -- proxies for A B\n" " +--Site A ------- proxies for B C\n" "Where A = http://a.com/\n" " B = http://bar.com/\n" " C = http://baz.com/", DepictFrameTree(root)); // Kill process B. RenderProcessHost* child_process_b = root->child_at(0)->current_frame_host()->GetProcess(); RenderProcessHostWatcher crash_observer( child_process_b, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); child_process_b->Shutdown(0, false); crash_observer.Wait(); // Make sure proxy C has gone from root. // Make sure proxy C has gone from node3 as well. // Make sure proxy B stays around in root and node3. EXPECT_EQ( " Site A ------------ proxies for B\n" " |--Site B ------- proxies for A\n" " +--Site A ------- proxies for B\n" "Where A = http://a.com/\n" " B = http://bar.com/ (no process)", DepictFrameTree(root)); EXPECT_TRUE(site_instance_c->HasOneRef()); } // Crash a subframe and ensures its children are cleared from the FrameTree. // See http://crbug.com/338508. // TODO(creis): Disabled for flakiness; see http://crbug.com/405582. IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, DISABLED_CrashSubframe) { GURL main_url(embedded_test_server()->GetURL("/site_per_process_main.html")); NavigateToURL(shell(), main_url); StartFrameAtDataURL(); // These must stay in scope with replace_host. GURL::Replacements replace_host; std::string foo_com("foo.com"); // Load cross-site page into iframe. EXPECT_TRUE(NavigateIframeToURL( shell()->web_contents(), "test", embedded_test_server()->GetURL("/cross-site/foo.com/title2.html"))); // Check the subframe process. FrameTreeNode* root = static_cast(shell()->web_contents())-> GetFrameTree()->root(); ASSERT_EQ(2U, root->child_count()); FrameTreeNode* child = root->child_at(0); EXPECT_EQ(main_url, root->current_url()); EXPECT_EQ("foo.com", child->current_url().host()); EXPECT_EQ("/title2.html", child->current_url().path()); EXPECT_TRUE( child->current_frame_host()->render_view_host()->IsRenderViewLive()); EXPECT_TRUE(child->current_frame_host()->IsRenderFrameLive()); // Crash the subframe process. RenderProcessHost* root_process = root->current_frame_host()->GetProcess(); RenderProcessHost* child_process = child->current_frame_host()->GetProcess(); { RenderProcessHostWatcher crash_observer( child_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); child_process->Shutdown(0, false); crash_observer.Wait(); } // Ensure that the child frame still exists but has been cleared. EXPECT_EQ(2U, root->child_count()); EXPECT_EQ(main_url, root->current_url()); EXPECT_EQ(GURL(), child->current_url()); EXPECT_FALSE( child->current_frame_host()->render_view_host()->IsRenderViewLive()); EXPECT_FALSE(child->current_frame_host()->IsRenderFrameLive()); EXPECT_FALSE(child->current_frame_host()->render_frame_created_); // Now crash the top-level page to clear the child frame. { RenderProcessHostWatcher crash_observer( root_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); root_process->Shutdown(0, false); crash_observer.Wait(); } EXPECT_EQ(0U, root->child_count()); EXPECT_EQ(GURL(), root->current_url()); } // When a new subframe is added, related SiteInstances that can reach the // subframe should create proxies for it (https://crbug.com/423587). This test // checks that if A embeds B and later adds a new subframe A2, A2 gets a proxy // in B's process. IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, CreateProxiesForNewFrames) { GURL main_url(embedded_test_server()->GetURL( "b.com", "/frame_tree/page_with_one_frame.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // It is safe to obtain the root frame tree node here, as it doesn't change. FrameTreeNode* root = static_cast(shell()->web_contents()) ->GetFrameTree() ->root(); ASSERT_EQ(1U, root->child_count()); // Make sure the frame starts out at the correct cross-site URL. EXPECT_EQ(embedded_test_server()->GetURL("baz.com", "/title1.html"), root->child_at(0)->current_url()); EXPECT_EQ( " Site A ------------ proxies for B\n" " +--Site B ------- proxies for A\n" "Where A = http://b.com/\n" " B = http://baz.com/", DepictFrameTree(root)); // Add a new child frame to the top-level frame. RenderFrameHostCreatedObserver frame_observer(shell()->web_contents(), 1); EXPECT_TRUE(ExecuteScript(shell()->web_contents(), "window.domAutomationController.send(" " addFrame('data:text/html,foo'));")); frame_observer.Wait(); // The new frame should have a proxy in Site B, for use by the old frame. EXPECT_EQ( " Site A ------------ proxies for B\n" " |--Site B ------- proxies for A\n" " +--Site A ------- proxies for B\n" "Where A = http://b.com/\n" " B = http://baz.com/", DepictFrameTree(root)); } // TODO(nasko): Disable this test until out-of-process iframes is ready and the // security checks are back in place. // TODO(creis): Replace SpawnedTestServer with host_resolver to get test to run // on Android (http://crbug.com/187570). IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, DISABLED_CrossSiteIframeRedirectOnce) { ASSERT_TRUE(test_server()->Start()); net::SpawnedTestServer https_server( net::SpawnedTestServer::TYPE_HTTPS, net::SpawnedTestServer::kLocalhost, base::FilePath(FILE_PATH_LITERAL("content/test/data"))); ASSERT_TRUE(https_server.Start()); GURL main_url(test_server()->GetURL("files/site_per_process_main.html")); GURL http_url(test_server()->GetURL("files/title1.html")); GURL https_url(https_server.GetURL("files/title1.html")); NavigateToURL(shell(), main_url); TestNavigationObserver observer(shell()->web_contents()); { // Load cross-site client-redirect page into Iframe. // Should be blocked. GURL client_redirect_https_url(https_server.GetURL( "client-redirect?files/title1.html")); EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test", client_redirect_https_url)); // DidFailProvisionalLoad when navigating to client_redirect_https_url. EXPECT_EQ(observer.last_navigation_url(), client_redirect_https_url); EXPECT_FALSE(observer.last_navigation_succeeded()); } { // Load cross-site server-redirect page into Iframe, // which redirects to same-site page. GURL server_redirect_http_url(https_server.GetURL( "server-redirect?" + http_url.spec())); EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test", server_redirect_http_url)); EXPECT_EQ(observer.last_navigation_url(), http_url); EXPECT_TRUE(observer.last_navigation_succeeded()); } { // Load cross-site server-redirect page into Iframe, // which redirects to cross-site page. GURL server_redirect_http_url(https_server.GetURL( "server-redirect?files/title1.html")); EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test", server_redirect_http_url)); // DidFailProvisionalLoad when navigating to https_url. EXPECT_EQ(observer.last_navigation_url(), https_url); EXPECT_FALSE(observer.last_navigation_succeeded()); } { // Load same-site server-redirect page into Iframe, // which redirects to cross-site page. GURL server_redirect_http_url(test_server()->GetURL( "server-redirect?" + https_url.spec())); EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test", server_redirect_http_url)); EXPECT_EQ(observer.last_navigation_url(), https_url); EXPECT_FALSE(observer.last_navigation_succeeded()); } { // Load same-site client-redirect page into Iframe, // which redirects to cross-site page. GURL client_redirect_http_url(test_server()->GetURL( "client-redirect?" + https_url.spec())); RedirectNotificationObserver load_observer2( NOTIFICATION_LOAD_STOP, Source( &shell()->web_contents()->GetController())); EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test", client_redirect_http_url)); // Same-site Client-Redirect Page should be loaded successfully. EXPECT_EQ(observer.last_navigation_url(), client_redirect_http_url); EXPECT_TRUE(observer.last_navigation_succeeded()); // Redirecting to Cross-site Page should be blocked. load_observer2.Wait(); EXPECT_EQ(observer.last_navigation_url(), https_url); EXPECT_FALSE(observer.last_navigation_succeeded()); } { // Load same-site server-redirect page into Iframe, // which redirects to same-site page. GURL server_redirect_http_url(test_server()->GetURL( "server-redirect?files/title1.html")); EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test", server_redirect_http_url)); EXPECT_EQ(observer.last_navigation_url(), http_url); EXPECT_TRUE(observer.last_navigation_succeeded()); } { // Load same-site client-redirect page into Iframe, // which redirects to same-site page. GURL client_redirect_http_url(test_server()->GetURL( "client-redirect?" + http_url.spec())); RedirectNotificationObserver load_observer2( NOTIFICATION_LOAD_STOP, Source( &shell()->web_contents()->GetController())); EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test", client_redirect_http_url)); // Same-site Client-Redirect Page should be loaded successfully. EXPECT_EQ(observer.last_navigation_url(), client_redirect_http_url); EXPECT_TRUE(observer.last_navigation_succeeded()); // Redirecting to Same-site Page should be loaded successfully. load_observer2.Wait(); EXPECT_EQ(observer.last_navigation_url(), http_url); EXPECT_TRUE(observer.last_navigation_succeeded()); } } // TODO(nasko): Disable this test until out-of-process iframes is ready and the // security checks are back in place. // TODO(creis): Replace SpawnedTestServer with host_resolver to get test to run // on Android (http://crbug.com/187570). IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, DISABLED_CrossSiteIframeRedirectTwice) { ASSERT_TRUE(test_server()->Start()); net::SpawnedTestServer https_server( net::SpawnedTestServer::TYPE_HTTPS, net::SpawnedTestServer::kLocalhost, base::FilePath(FILE_PATH_LITERAL("content/test/data"))); ASSERT_TRUE(https_server.Start()); GURL main_url(test_server()->GetURL("files/site_per_process_main.html")); GURL http_url(test_server()->GetURL("files/title1.html")); GURL https_url(https_server.GetURL("files/title1.html")); NavigateToURL(shell(), main_url); TestNavigationObserver observer(shell()->web_contents()); { // Load client-redirect page pointing to a cross-site client-redirect page, // which eventually redirects back to same-site page. GURL client_redirect_https_url(https_server.GetURL( "client-redirect?" + http_url.spec())); GURL client_redirect_http_url(test_server()->GetURL( "client-redirect?" + client_redirect_https_url.spec())); // We should wait until second client redirect get cancelled. RedirectNotificationObserver load_observer2( NOTIFICATION_LOAD_STOP, Source( &shell()->web_contents()->GetController())); EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test", client_redirect_http_url)); // DidFailProvisionalLoad when navigating to client_redirect_https_url. load_observer2.Wait(); EXPECT_EQ(observer.last_navigation_url(), client_redirect_https_url); EXPECT_FALSE(observer.last_navigation_succeeded()); } { // Load server-redirect page pointing to a cross-site server-redirect page, // which eventually redirect back to same-site page. GURL server_redirect_https_url(https_server.GetURL( "server-redirect?" + http_url.spec())); GURL server_redirect_http_url(test_server()->GetURL( "server-redirect?" + server_redirect_https_url.spec())); EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test", server_redirect_http_url)); EXPECT_EQ(observer.last_navigation_url(), http_url); EXPECT_TRUE(observer.last_navigation_succeeded()); } { // Load server-redirect page pointing to a cross-site server-redirect page, // which eventually redirects back to cross-site page. GURL server_redirect_https_url(https_server.GetURL( "server-redirect?" + https_url.spec())); GURL server_redirect_http_url(test_server()->GetURL( "server-redirect?" + server_redirect_https_url.spec())); EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test", server_redirect_http_url)); // DidFailProvisionalLoad when navigating to https_url. EXPECT_EQ(observer.last_navigation_url(), https_url); EXPECT_FALSE(observer.last_navigation_succeeded()); } { // Load server-redirect page pointing to a cross-site client-redirect page, // which eventually redirects back to same-site page. GURL client_redirect_http_url(https_server.GetURL( "client-redirect?" + http_url.spec())); GURL server_redirect_http_url(test_server()->GetURL( "server-redirect?" + client_redirect_http_url.spec())); EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test", server_redirect_http_url)); // DidFailProvisionalLoad when navigating to client_redirect_http_url. EXPECT_EQ(observer.last_navigation_url(), client_redirect_http_url); EXPECT_FALSE(observer.last_navigation_succeeded()); } } // Ensure that when navigating a frame cross-process RenderFrameProxyHosts are // created in the FrameTree skipping the subtree of the navigating frame. // // Disabled on Mac due to flakiness on ASAN. http://crbug.com/425248 // Disabled on Windows due to flakiness on Win 7 bot. http://crbug.com/444563 #if defined(OS_MACOSX) || defined(OS_WIN) #define MAYBE_ProxyCreationSkipsSubtree DISABLED_ProxyCreationSkipsSubtree #else #define MAYBE_ProxyCreationSkipsSubtree ProxyCreationSkipsSubtree #endif IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, MAYBE_ProxyCreationSkipsSubtree) { GURL main_url(embedded_test_server()->GetURL("/site_per_process_main.html")); NavigateToURL(shell(), main_url); // It is safe to obtain the root frame tree node here, as it doesn't change. FrameTreeNode* root = static_cast(shell()->web_contents())-> GetFrameTree()->root(); EXPECT_TRUE(root->child_at(1) != NULL); EXPECT_EQ(2U, root->child_at(1)->child_count()); { // Load same-site page into iframe. TestNavigationObserver observer(shell()->web_contents()); GURL http_url(embedded_test_server()->GetURL("/title1.html")); NavigateFrameToURL(root->child_at(0), http_url); EXPECT_EQ(http_url, observer.last_navigation_url()); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ( " Site A\n" " |--Site A\n" " +--Site A\n" " |--Site A\n" " +--Site A\n" " +--Site A\n" "Where A = http://127.0.0.1/", DepictFrameTree(root)); } // Create the cross-site URL to navigate to. GURL cross_site_url = embedded_test_server()->GetURL("foo.com", "/frame_tree/1-1.html"); // Load cross-site page into the second iframe without waiting for the // navigation to complete. Once LoadURLWithParams returns, we would expect // proxies to have been created in the frame tree, but children of the // navigating frame to still be present. The reason is that we don't run the // message loop, so no IPCs that alter the frame tree can be processed. FrameTreeNode* child = root->child_at(1); SiteInstance* site = NULL; { TestNavigationObserver observer(shell()->web_contents()); TestFrameNavigationObserver navigation_observer(child); NavigationController::LoadURLParams params(cross_site_url); params.transition_type = PageTransitionFromInt(ui::PAGE_TRANSITION_LINK); params.frame_tree_node_id = child->frame_tree_node_id(); child->navigator()->GetController()->LoadURLWithParams(params); site = child->render_manager()->pending_frame_host()->GetSiteInstance(); EXPECT_NE(shell()->web_contents()->GetSiteInstance(), site); EXPECT_EQ( " Site A ------------ proxies for B\n" " |--Site A ------- proxies for B\n" " +--Site A (B pending)\n" " |--Site A\n" " +--Site A\n" " +--Site A\n" "Where A = http://127.0.0.1/\n" " B = http://foo.com/", DepictFrameTree(root)); // Now that the verification is done, run the message loop and wait for the // navigation to complete. navigation_observer.Wait(); EXPECT_FALSE(child->render_manager()->pending_frame_host()); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(cross_site_url, observer.last_navigation_url()); } // Load another cross-site page into the same iframe. cross_site_url = embedded_test_server()->GetURL("bar.com", "/title2.html"); { // Perform the same checks as the first cross-site navigation, since // there have been issues in subsequent cross-site navigations. Also ensure // that the SiteInstance has properly changed. // TODO(nasko): Once we have proper cleanup of resources, add code to // verify that the intermediate SiteInstance/RenderFrameHost have been // properly cleaned up. TestNavigationObserver observer(shell()->web_contents()); TestFrameNavigationObserver navigation_observer(child); NavigationController::LoadURLParams params(cross_site_url); params.transition_type = PageTransitionFromInt(ui::PAGE_TRANSITION_LINK); params.frame_tree_node_id = child->frame_tree_node_id(); child->navigator()->GetController()->LoadURLWithParams(params); SiteInstance* site2 = child->render_manager()->pending_frame_host()->GetSiteInstance(); EXPECT_NE(shell()->web_contents()->GetSiteInstance(), site2); EXPECT_NE(site, site2); EXPECT_EQ( " Site A ------------ proxies for B C\n" " |--Site A ------- proxies for B C\n" " +--Site B (C pending) -- proxies for A\n" " |--Site A\n" " +--Site A\n" " +--Site A\n" "Where A = http://127.0.0.1/\n" " B = http://foo.com/\n" " C = http://bar.com/", DepictFrameTree(root)); navigation_observer.Wait(); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(cross_site_url, observer.last_navigation_url()); EXPECT_EQ(0U, child->child_count()); } } // Verify that origin replication works for an A-embed-B-embed-C hierarchy. IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, OriginReplication) { GURL main_url(embedded_test_server()->GetURL("/site_per_process_main.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // It is safe to obtain the root frame tree node here, as it doesn't change. FrameTreeNode* root = static_cast(shell()->web_contents()) ->GetFrameTree() ->root(); TestNavigationObserver observer(shell()->web_contents()); // Navigate the first subframe to a cross-site page with two subframes. // NavigateFrameToURL can't be used here because it doesn't guarantee that // FrameTreeNodes will have been created for child frames when it returns. RenderFrameHostCreatedObserver frame_observer(shell()->web_contents(), 4); GURL foo_url( embedded_test_server()->GetURL("foo.com", "/frame_tree/1-1.html")); NavigationController::LoadURLParams params(foo_url); params.transition_type = ui::PAGE_TRANSITION_LINK; params.frame_tree_node_id = root->child_at(0)->frame_tree_node_id(); root->child_at(0)->navigator()->GetController()->LoadURLWithParams(params); frame_observer.Wait(); // We can't use a TestNavigationObserver to verify the URL here, // since the frame has children that may have clobbered it in the observer. EXPECT_EQ(foo_url, root->child_at(0)->current_url()); // Ensure that a new process is created for the subframe. EXPECT_NE(shell()->web_contents()->GetSiteInstance(), root->child_at(0)->current_frame_host()->GetSiteInstance()); // Load cross-site page into subframe's subframe. ASSERT_EQ(2U, root->child_at(0)->child_count()); GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title1.html")); NavigateFrameToURL(root->child_at(0)->child_at(0), bar_url); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(bar_url, observer.last_navigation_url()); // Check that a new process is created and is different from the top one and // the middle one. FrameTreeNode* bottom_child = root->child_at(0)->child_at(0); EXPECT_NE(shell()->web_contents()->GetSiteInstance(), bottom_child->current_frame_host()->GetSiteInstance()); EXPECT_NE(root->child_at(0)->current_frame_host()->GetSiteInstance(), bottom_child->current_frame_host()->GetSiteInstance()); // Check that foo.com frame's location.ancestorOrigins contains the correct // origin for the parent. The origin should have been replicated as part of // the ViewMsg_New message that created the parent's RenderFrameProxy in // foo.com's process. int ancestor_origins_length = 0; EXPECT_TRUE(ExecuteScriptAndExtractInt( root->child_at(0)->current_frame_host(), "window.domAutomationController.send(location.ancestorOrigins.length);", &ancestor_origins_length)); EXPECT_EQ(1, ancestor_origins_length); std::string result; EXPECT_TRUE(ExecuteScriptAndExtractString( root->child_at(0)->current_frame_host(), "window.domAutomationController.send(location.ancestorOrigins[0]);", &result)); EXPECT_EQ(result + "/", main_url.GetOrigin().spec()); // Check that bar.com frame's location.ancestorOrigins contains the correct // origin for its two ancestors. The topmost parent origin should be // replicated as part of ViewMsg_New, and the middle frame (foo.com's) origin // should be replicated as part of FrameMsg_NewFrameProxy sent for foo.com's // frame in bar.com's process. EXPECT_TRUE(ExecuteScriptAndExtractInt( bottom_child->current_frame_host(), "window.domAutomationController.send(location.ancestorOrigins.length);", &ancestor_origins_length)); EXPECT_EQ(2, ancestor_origins_length); EXPECT_TRUE(ExecuteScriptAndExtractString( bottom_child->current_frame_host(), "window.domAutomationController.send(location.ancestorOrigins[0]);", &result)); EXPECT_EQ(result + "/", foo_url.GetOrigin().spec()); EXPECT_TRUE(ExecuteScriptAndExtractString( bottom_child->current_frame_host(), "window.domAutomationController.send(location.ancestorOrigins[1]);", &result)); EXPECT_EQ(result + "/", main_url.GetOrigin().spec()); } // Check that iframe sandbox flags are replicated correctly. IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, SandboxFlagsReplication) { GURL main_url(embedded_test_server()->GetURL("/sandboxed_frames.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // It is safe to obtain the root frame tree node here, as it doesn't change. FrameTreeNode* root = static_cast(shell()->web_contents()) ->GetFrameTree() ->root(); TestNavigationObserver observer(shell()->web_contents()); // Navigate the second (sandboxed) subframe to a cross-site page with a // subframe. Use RenderFrameHostCreatedObserver to guarantee that all // FrameTreeNodes are created for child frames. RenderFrameHostCreatedObserver frame_observer(shell()->web_contents(), 4); GURL foo_url( embedded_test_server()->GetURL("foo.com", "/frame_tree/1-1.html")); NavigateFrameToURL(root->child_at(1), foo_url); frame_observer.Wait(); // We can't use a TestNavigationObserver to verify the URL here, // since the frame has children that may have clobbered it in the observer. EXPECT_EQ(foo_url, root->child_at(1)->current_url()); // Load cross-site page into subframe's subframe. ASSERT_EQ(2U, root->child_at(1)->child_count()); GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title1.html")); NavigateFrameToURL(root->child_at(1)->child_at(0), bar_url); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(bar_url, observer.last_navigation_url()); // Opening a popup in the sandboxed foo.com iframe should fail. bool success = false; EXPECT_TRUE( ExecuteScriptAndExtractBool(root->child_at(1)->current_frame_host(), "window.domAutomationController.send(" "!window.open('data:text/html,dataurl'));", &success)); EXPECT_TRUE(success); EXPECT_EQ(1u, Shell::windows().size()); // Opening a popup in a frame whose parent is sandboxed should also fail. // Here, bar.com frame's sandboxed parent frame is a remote frame in // bar.com's process. success = false; EXPECT_TRUE(ExecuteScriptAndExtractBool( root->child_at(1)->child_at(0)->current_frame_host(), "window.domAutomationController.send(" "!window.open('data:text/html,dataurl'));", &success)); EXPECT_TRUE(success); EXPECT_EQ(1u, Shell::windows().size()); // Same, but now try the case where bar.com frame's sandboxed parent is a // local frame in bar.com's process. success = false; EXPECT_TRUE(ExecuteScriptAndExtractBool( root->child_at(2)->child_at(0)->current_frame_host(), "window.domAutomationController.send(" "!window.open('data:text/html,dataurl'));", &success)); EXPECT_TRUE(success); EXPECT_EQ(1u, Shell::windows().size()); // Check that foo.com frame's location.ancestorOrigins contains the correct // origin for the parent, which should be unaffected by sandboxing. int ancestor_origins_length = 0; EXPECT_TRUE(ExecuteScriptAndExtractInt( root->child_at(1)->current_frame_host(), "window.domAutomationController.send(location.ancestorOrigins.length);", &ancestor_origins_length)); EXPECT_EQ(1, ancestor_origins_length); std::string result; EXPECT_TRUE(ExecuteScriptAndExtractString( root->child_at(1)->current_frame_host(), "window.domAutomationController.send(location.ancestorOrigins[0]);", &result)); EXPECT_EQ(result + "/", main_url.GetOrigin().spec()); // Now check location.ancestorOrigins for the bar.com frame. The middle frame // (foo.com's) origin should be unique, since that frame is sandboxed, and // the top frame should match |main_url|. FrameTreeNode* bottom_child = root->child_at(1)->child_at(0); EXPECT_TRUE(ExecuteScriptAndExtractInt( bottom_child->current_frame_host(), "window.domAutomationController.send(location.ancestorOrigins.length);", &ancestor_origins_length)); EXPECT_EQ(2, ancestor_origins_length); EXPECT_TRUE(ExecuteScriptAndExtractString( bottom_child->current_frame_host(), "window.domAutomationController.send(location.ancestorOrigins[0]);", &result)); EXPECT_EQ("null", result); EXPECT_TRUE(ExecuteScriptAndExtractString( bottom_child->current_frame_host(), "window.domAutomationController.send(location.ancestorOrigins[1]);", &result)); EXPECT_EQ(main_url.GetOrigin().spec(), result + "/"); } // Check that dynamic updates to iframe sandbox flags are propagated correctly. IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, DynamicSandboxFlags) { GURL main_url( embedded_test_server()->GetURL("/frame_tree/page_with_two_frames.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // It is safe to obtain the root frame tree node here, as it doesn't change. FrameTreeNode* root = static_cast(shell()->web_contents()) ->GetFrameTree() ->root(); TestNavigationObserver observer(shell()->web_contents()); ASSERT_EQ(2U, root->child_count()); // Make sure first frame starts out at the correct cross-site page. EXPECT_EQ(embedded_test_server()->GetURL("bar.com", "/title1.html"), root->child_at(0)->current_url()); // Navigate second frame to another cross-site page. GURL baz_url(embedded_test_server()->GetURL("baz.com", "/title1.html")); NavigateFrameToURL(root->child_at(1), baz_url); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(baz_url, observer.last_navigation_url()); // Both frames should not be sandboxed to start with. EXPECT_EQ(SandboxFlags::NONE, root->child_at(0)->current_replication_state().sandbox_flags); EXPECT_EQ(SandboxFlags::NONE, root->child_at(0)->effective_sandbox_flags()); EXPECT_EQ(SandboxFlags::NONE, root->child_at(1)->current_replication_state().sandbox_flags); EXPECT_EQ(SandboxFlags::NONE, root->child_at(1)->effective_sandbox_flags()); // Dynamically update sandbox flags for the first frame. EXPECT_TRUE(ExecuteScript(shell()->web_contents(), "window.domAutomationController.send(" "document.querySelector('iframe').sandbox=" "'allow-scripts');")); // Check that updated sandbox flags are propagated to browser process. // The new flags should be set in current_replication_state(), while // effective_sandbox_flags() should still reflect the old flags, because // sandbox flag updates take place only after navigations. "allow-scripts" // resets both SandboxFlags::Scripts and SandboxFlags::AutomaticFeatures bits // per blink::parseSandboxPolicy(). SandboxFlags expected_flags = SandboxFlags::ALL & ~SandboxFlags::SCRIPTS & ~SandboxFlags::AUTOMATIC_FEATURES; EXPECT_EQ(expected_flags, root->child_at(0)->current_replication_state().sandbox_flags); EXPECT_EQ(SandboxFlags::NONE, root->child_at(0)->effective_sandbox_flags()); // Navigate the first frame to a page on the same site. The new sandbox // flags should take effect. The new page has a child frame, so use // TestFrameNavigationObserver to wait for it to be loaded. TestFrameNavigationObserver frame_observer(root->child_at(0), 2); GURL bar_url( embedded_test_server()->GetURL("bar.com", "/frame_tree/2-4.html")); NavigateFrameToURL(root->child_at(0), bar_url); frame_observer.Wait(); EXPECT_EQ(bar_url, root->child_at(0)->current_url()); ASSERT_EQ(1U, root->child_at(0)->child_count()); // Confirm that the browser process has updated the frame's current sandbox // flags. EXPECT_EQ(expected_flags, root->child_at(0)->current_replication_state().sandbox_flags); EXPECT_EQ(expected_flags, root->child_at(0)->effective_sandbox_flags()); // Opening a popup in the now-sandboxed frame should fail. bool success = false; EXPECT_TRUE( ExecuteScriptAndExtractBool(root->child_at(0)->current_frame_host(), "window.domAutomationController.send(" "!window.open('data:text/html,dataurl'));", &success)); EXPECT_TRUE(success); EXPECT_EQ(1u, Shell::windows().size()); // Navigate the child of the now-sandboxed frame to a page on baz.com. The // child should inherit the latest sandbox flags from its parent frame, which // is currently a proxy in baz.com's renderer process. This checks that the // proxies of |root->child_at(0)| were also updated with the latest sandbox // flags. GURL baz_child_url(embedded_test_server()->GetURL("baz.com", "/title2.html")); NavigateFrameToURL(root->child_at(0)->child_at(0), baz_child_url); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(baz_child_url, observer.last_navigation_url()); // Opening a popup in the child of a sandboxed frame should fail. success = false; EXPECT_TRUE(ExecuteScriptAndExtractBool( root->child_at(0)->child_at(0)->current_frame_host(), "window.domAutomationController.send(" "!window.open('data:text/html,dataurl'));", &success)); EXPECT_TRUE(success); EXPECT_EQ(1u, Shell::windows().size()); } // Check that dynamic updates to iframe sandbox flags are propagated correctly. IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, DynamicSandboxFlagsRemoteToLocal) { GURL main_url( embedded_test_server()->GetURL("/frame_tree/page_with_two_frames.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // It is safe to obtain the root frame tree node here, as it doesn't change. FrameTreeNode* root = static_cast(shell()->web_contents()) ->GetFrameTree() ->root(); TestNavigationObserver observer(shell()->web_contents()); ASSERT_EQ(2U, root->child_count()); // Make sure the two frames starts out at correct URLs. EXPECT_EQ(embedded_test_server()->GetURL("bar.com", "/title1.html"), root->child_at(0)->current_url()); EXPECT_EQ(embedded_test_server()->GetURL("/title1.html"), root->child_at(1)->current_url()); // Update the second frame's sandbox flags. EXPECT_TRUE(ExecuteScript(shell()->web_contents(), "window.domAutomationController.send(" "document.querySelectorAll('iframe')[1].sandbox=" "'allow-scripts');")); // Check that the current sandbox flags are updated but the effective // sandbox flags are not. SandboxFlags expected_flags = SandboxFlags::ALL & ~SandboxFlags::SCRIPTS & ~SandboxFlags::AUTOMATIC_FEATURES; EXPECT_EQ(expected_flags, root->child_at(1)->current_replication_state().sandbox_flags); EXPECT_EQ(SandboxFlags::NONE, root->child_at(1)->effective_sandbox_flags()); // Navigate the second subframe to a page on bar.com. This will trigger a // remote-to-local frame swap in bar.com's process. The target page has // another frame, so use TestFrameNavigationObserver to wait for all frames // to be loaded. TestFrameNavigationObserver frame_observer(root->child_at(1), 2); GURL bar_url(embedded_test_server()->GetURL( "bar.com", "/frame_tree/page_with_one_frame.html")); NavigateFrameToURL(root->child_at(1), bar_url); frame_observer.Wait(); EXPECT_EQ(bar_url, root->child_at(1)->current_url()); ASSERT_EQ(1U, root->child_at(1)->child_count()); // Confirm that the browser process has updated the current sandbox flags. EXPECT_EQ(expected_flags, root->child_at(1)->current_replication_state().sandbox_flags); EXPECT_EQ(expected_flags, root->child_at(1)->effective_sandbox_flags()); // Opening a popup in the sandboxed second frame should fail. bool success = false; EXPECT_TRUE( ExecuteScriptAndExtractBool(root->child_at(1)->current_frame_host(), "window.domAutomationController.send(" "!window.open('data:text/html,dataurl'));", &success)); EXPECT_TRUE(success); EXPECT_EQ(1u, Shell::windows().size()); // Make sure that the child frame inherits the sandbox flags of its // now-sandboxed parent frame. success = false; EXPECT_TRUE(ExecuteScriptAndExtractBool( root->child_at(1)->child_at(0)->current_frame_host(), "window.domAutomationController.send(" "!window.open('data:text/html,dataurl'));", &success)); EXPECT_TRUE(success); EXPECT_EQ(1u, Shell::windows().size()); } // Check that dynamic updates to iframe sandbox flags are propagated correctly. IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, DynamicSandboxFlagsRendererInitiatedNavigation) { GURL main_url( embedded_test_server()->GetURL("/frame_tree/page_with_one_frame.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // It is safe to obtain the root frame tree node here, as it doesn't change. FrameTreeNode* root = static_cast(shell()->web_contents()) ->GetFrameTree() ->root(); TestNavigationObserver observer(shell()->web_contents()); ASSERT_EQ(1U, root->child_count()); // Make sure the frame starts out at the correct cross-site page. EXPECT_EQ(embedded_test_server()->GetURL("baz.com", "/title1.html"), root->child_at(0)->current_url()); // The frame should not be sandboxed to start with. EXPECT_EQ(SandboxFlags::NONE, root->child_at(0)->current_replication_state().sandbox_flags); EXPECT_EQ(SandboxFlags::NONE, root->child_at(0)->effective_sandbox_flags()); // Dynamically update the frame's sandbox flags. EXPECT_TRUE(ExecuteScript(shell()->web_contents(), "window.domAutomationController.send(" "document.querySelector('iframe').sandbox=" "'allow-scripts');")); // Check that updated sandbox flags are propagated to browser process. // The new flags should be set in current_replication_state(), while // effective_sandbox_flags() should still reflect the old flags, because // sandbox flag updates take place only after navigations. "allow-scripts" // resets both SandboxFlags::Scripts and SandboxFlags::AutomaticFeatures bits // per blink::parseSandboxPolicy(). SandboxFlags expected_flags = SandboxFlags::ALL & ~SandboxFlags::SCRIPTS & ~SandboxFlags::AUTOMATIC_FEATURES; EXPECT_EQ(expected_flags, root->child_at(0)->current_replication_state().sandbox_flags); EXPECT_EQ(SandboxFlags::NONE, root->child_at(0)->effective_sandbox_flags()); // Perform a renderer-initiated same-site navigation in the first frame. The // new sandbox flags should take effect. TestFrameNavigationObserver frame_observer(root->child_at(0)); ASSERT_TRUE(ExecuteScript(root->child_at(0)->current_frame_host(), "window.location.href='/title2.html'")); frame_observer.Wait(); EXPECT_EQ(embedded_test_server()->GetURL("baz.com", "/title2.html"), root->child_at(0)->current_url()); // Confirm that the browser process has updated the frame's current sandbox // flags. EXPECT_EQ(expected_flags, root->child_at(0)->current_replication_state().sandbox_flags); EXPECT_EQ(expected_flags, root->child_at(0)->effective_sandbox_flags()); // Opening a popup in the now-sandboxed frame should fail. bool success = false; EXPECT_TRUE( ExecuteScriptAndExtractBool(root->child_at(0)->current_frame_host(), "window.domAutomationController.send(" "!window.open('data:text/html,dataurl'));", &success)); EXPECT_TRUE(success); EXPECT_EQ(1u, Shell::windows().size()); } // Verify that a child frame can retrieve the name property set by its parent. IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, WindowNameReplication) { GURL main_url(embedded_test_server()->GetURL("/frame_tree/2-4.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // It is safe to obtain the root frame tree node here, as it doesn't change. FrameTreeNode* root = static_cast(shell()->web_contents()) ->GetFrameTree() ->root(); TestNavigationObserver observer(shell()->web_contents()); // Load cross-site page into iframe. GURL frame_url = embedded_test_server()->GetURL("foo.com", "/frame_tree/3-1.html"); NavigateFrameToURL(root->child_at(0), frame_url); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(frame_url, observer.last_navigation_url()); // Ensure that a new process is created for the subframe. EXPECT_NE(shell()->web_contents()->GetSiteInstance(), root->child_at(0)->current_frame_host()->GetSiteInstance()); // Check that the window.name seen by the frame matches the name attribute // specified by its parent in the iframe tag. std::string result; EXPECT_TRUE(ExecuteScriptAndExtractString( root->child_at(0)->current_frame_host(), "window.domAutomationController.send(window.name);", &result)); EXPECT_EQ("3-1-name", result); } // Verify that dynamic updates to a frame's window.name propagate to the // frame's proxies, so that the latest frame names can be used in navigations. IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, DynamicWindowName) { GURL main_url(embedded_test_server()->GetURL("/frame_tree/2-4.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // It is safe to obtain the root frame tree node here, as it doesn't change. FrameTreeNode* root = static_cast(shell()->web_contents()) ->GetFrameTree() ->root(); TestNavigationObserver observer(shell()->web_contents()); // Load cross-site page into iframe. GURL frame_url = embedded_test_server()->GetURL("foo.com", "/frame_tree/3-1.html"); NavigateFrameToURL(root->child_at(0), frame_url); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(frame_url, observer.last_navigation_url()); // Browser process should know the child frame's original window.name // specified in the iframe element. EXPECT_EQ(root->child_at(0)->frame_name(), "3-1-name"); // Update the child frame's window.name. EXPECT_TRUE(ExecuteScript(root->child_at(0)->current_frame_host(), "window.domAutomationController.send(" "window.name = 'updated-name');")); // The change should propagate to the browser process. EXPECT_EQ(root->child_at(0)->frame_name(), "updated-name"); // The proxy in the parent process should also receive the updated name. // Check that it can reference the child frame by its new name. bool success = false; EXPECT_TRUE( ExecuteScriptAndExtractBool(shell()->web_contents(), "window.domAutomationController.send(" "frames['updated-name'] == frames[0]);", &success)); EXPECT_TRUE(success); // Issue a renderer-initiated navigation from the root frame to the child // frame using the frame's name. Make sure correct frame is navigated. // // TODO(alexmos): When blink::createWindow is refactored to handle // RemoteFrames, this should also be tested via window.open(url, frame_name) // and a more complicated frame hierarchy (https://crbug.com/463742) TestFrameNavigationObserver frame_observer(root->child_at(0)); GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); std::string script = base::StringPrintf( "window.domAutomationController.send(" "frames['updated-name'].location.href = '%s');", foo_url.spec().c_str()); EXPECT_TRUE(ExecuteScript(shell()->web_contents(), script)); frame_observer.Wait(); EXPECT_EQ(foo_url, root->child_at(0)->current_url()); } // Ensure that navigating subframes in --site-per-process mode properly fires // the DidStopLoading event on WebContentsObserver. IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, CrossSiteDidStopLoading) { GURL main_url(embedded_test_server()->GetURL("/site_per_process_main.html")); NavigateToURL(shell(), main_url); // It is safe to obtain the root frame tree node here, as it doesn't change. FrameTreeNode* root = static_cast(shell()->web_contents())-> GetFrameTree()->root(); TestNavigationObserver observer(shell()->web_contents()); // Load same-site page into iframe. FrameTreeNode* child = root->child_at(0); GURL http_url(embedded_test_server()->GetURL("/title1.html")); NavigateFrameToURL(child, http_url); EXPECT_EQ(http_url, observer.last_navigation_url()); EXPECT_TRUE(observer.last_navigation_succeeded()); // Load cross-site page into iframe. TestNavigationObserver nav_observer(shell()->web_contents(), 1); GURL url = embedded_test_server()->GetURL("foo.com", "/title2.html"); NavigationController::LoadURLParams params(url); params.transition_type = ui::PAGE_TRANSITION_LINK; params.frame_tree_node_id = child->frame_tree_node_id(); child->navigator()->GetController()->LoadURLWithParams(params); nav_observer.Wait(); // Verify that the navigation succeeded and the expected URL was loaded. EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(url, observer.last_navigation_url()); } // Ensure that the renderer does not crash when navigating a frame that has a // sibling RemoteFrame. See https://crbug.com/426953. IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, NavigateWithSiblingRemoteFrame) { GURL main_url( embedded_test_server()->GetURL("/frame_tree/page_with_two_frames.html")); NavigateToURL(shell(), main_url); // It is safe to obtain the root frame tree node here, as it doesn't change. FrameTreeNode* root = static_cast(shell()->web_contents()) ->GetFrameTree() ->root(); TestNavigationObserver observer(shell()->web_contents()); // Make sure the first frame is out of process. ASSERT_EQ(2U, root->child_count()); FrameTreeNode* node2 = root->child_at(0); EXPECT_NE(root->current_frame_host()->GetSiteInstance(), node2->current_frame_host()->GetSiteInstance()); // Make sure the second frame is in the parent's process. FrameTreeNode* node3 = root->child_at(1); EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), node3->current_frame_host()->GetSiteInstance()); // Navigate the second iframe (node3) to a URL in its own process. GURL title_url = embedded_test_server()->GetURL("/title2.html"); NavigateFrameToURL(node3, title_url); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(title_url, observer.last_navigation_url()); EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), node3->current_frame_host()->GetSiteInstance()); EXPECT_TRUE(node3->current_frame_host()->IsRenderFrameLive()); } // Verify that load events for iframe elements work when the child frame is // out-of-process. In such cases, the load event is forwarded from the child // frame to the parent frame via the browser process. IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, LoadEventForwarding) { // Load a page with a cross-site frame. The parent page has an onload // handler in the iframe element that appends "LOADED" to the document title. { GURL main_url( embedded_test_server()->GetURL("/frame_with_load_event.html")); base::string16 expected_title(base::UTF8ToUTF16("LOADED")); TitleWatcher title_watcher(shell()->web_contents(), expected_title); EXPECT_TRUE(NavigateToURL(shell(), main_url)); EXPECT_EQ(title_watcher.WaitAndGetTitle(), expected_title); } // It is safe to obtain the root frame tree node here, as it doesn't change. FrameTreeNode* root = static_cast(shell()->web_contents()) ->GetFrameTree() ->root(); // Load another cross-site page into the iframe and check that the load event // is fired. { GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); base::string16 expected_title(base::UTF8ToUTF16("LOADEDLOADED")); TitleWatcher title_watcher(shell()->web_contents(), expected_title); TestNavigationObserver observer(shell()->web_contents()); NavigateFrameToURL(root->child_at(0), foo_url); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(foo_url, observer.last_navigation_url()); EXPECT_EQ(title_watcher.WaitAndGetTitle(), expected_title); } } // Check that postMessage can be routed between cross-site iframes. IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, SubframePostMessage) { GURL main_url(embedded_test_server()->GetURL( "/frame_tree/page_with_post_message_frames.html")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); // It is safe to obtain the root frame tree node here, as it doesn't change. FrameTreeNode* root = static_cast(shell()->web_contents()) ->GetFrameTree() ->root(); TestNavigationObserver observer(shell()->web_contents()); ASSERT_EQ(2U, root->child_count()); // Verify the frames start at correct URLs. First frame should be // same-site; second frame should be cross-site. GURL same_site_url(embedded_test_server()->GetURL("/post_message.html")); EXPECT_EQ(same_site_url, root->child_at(0)->current_url()); GURL foo_url(embedded_test_server()->GetURL("foo.com", "/post_message.html")); EXPECT_EQ(foo_url, root->child_at(1)->current_url()); EXPECT_NE(root->child_at(0)->current_frame_host()->GetSiteInstance(), root->child_at(1)->current_frame_host()->GetSiteInstance()); // Send a message from first, same-site frame to second, cross-site frame. // Expect the second frame to reply back to the first frame. // // TODO(alexmos): Also try sending from second to first frame. Currently, // this fails due to https://crbug.com/473518, which prevents // parent.frames[x] from working when "parent" is a remote frame. bool success = false; EXPECT_TRUE(ExecuteScriptAndExtractBool( root->child_at(0)->current_frame_host(), "window.domAutomationController.send(" " postToSibling('subframe-msg','subframe2'));", &success)); EXPECT_TRUE(success); // Wait for first frame to receive a reply from the second frame. It will // send "done-subframe1" from the DOMAutomationController when the reply // arrives. content::DOMMessageQueue msg_queue; std::string status; while (msg_queue.WaitForMessage(&status)) { if (status == "\"done-subframe1\"") break; } // Send a postMessage from second, cross-site frame to its parent. Expect // parent to send a reply to the frame. base::string16 expected_title(base::ASCIIToUTF16("subframe-msg")); TitleWatcher title_watcher(shell()->web_contents(), expected_title); success = false; EXPECT_TRUE(ExecuteScriptAndExtractBool( root->child_at(1)->current_frame_host(), "window.domAutomationController.send(postToParent('subframe-msg'));", &success)); EXPECT_TRUE(success); EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); // Wait for second frame to receive a reply from the parent. The frame will // return "done-subframe2" from the DOMAutomationController when the reply // arrives. while (msg_queue.WaitForMessage(&status)) { if (status == "\"done-subframe2\"") break; } // Verify the total number of received messages for each subframe. First // frame should have one message (reply from second frame), and second frame // should have two messages (message from first frame and reply from parent). int subframe1_received_messages = 0; int subframe2_received_messages = 0; EXPECT_TRUE(ExecuteScriptAndExtractInt( root->child_at(0)->current_frame_host(), "window.domAutomationController.send(window.receivedMessages);", &subframe1_received_messages)); EXPECT_EQ(1, subframe1_received_messages); EXPECT_TRUE(ExecuteScriptAndExtractInt( root->child_at(1)->current_frame_host(), "window.domAutomationController.send(window.receivedMessages);", &subframe2_received_messages)); EXPECT_EQ(2, subframe2_received_messages); } IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, RFPHDestruction) { GURL main_url(embedded_test_server()->GetURL("/site_per_process_main.html")); NavigateToURL(shell(), main_url); // It is safe to obtain the root frame tree node here, as it doesn't change. FrameTreeNode* root = static_cast(shell()->web_contents()) ->GetFrameTree() ->root(); TestNavigationObserver observer(shell()->web_contents()); // Load cross-site page into iframe. FrameTreeNode* child = root->child_at(0); GURL url = embedded_test_server()->GetURL("foo.com", "/title2.html"); NavigateFrameToURL(root->child_at(0), url); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(url, observer.last_navigation_url()); EXPECT_EQ( " Site A ------------ proxies for B\n" " |--Site B ------- proxies for A\n" " +--Site A ------- proxies for B\n" " |--Site A -- proxies for B\n" " +--Site A -- proxies for B\n" " +--Site A -- proxies for B\n" "Where A = http://127.0.0.1/\n" " B = http://foo.com/", DepictFrameTree(root)); // Load another cross-site page. url = embedded_test_server()->GetURL("bar.com", "/title3.html"); NavigateIframeToURL(shell()->web_contents(), "test", url); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(url, observer.last_navigation_url()); EXPECT_EQ( " Site A ------------ proxies for C\n" " |--Site C ------- proxies for A\n" " +--Site A ------- proxies for C\n" " |--Site A -- proxies for C\n" " +--Site A -- proxies for C\n" " +--Site A -- proxies for C\n" "Where A = http://127.0.0.1/\n" " C = http://bar.com/", DepictFrameTree(root)); // Navigate back to the parent's origin. url = embedded_test_server()->GetURL("/title1.html"); NavigateFrameToURL(child, url); EXPECT_EQ(url, observer.last_navigation_url()); EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ( " Site A\n" " |--Site A\n" " +--Site A\n" " |--Site A\n" " +--Site A\n" " +--Site A\n" "Where A = http://127.0.0.1/", DepictFrameTree(root)); } } // namespace content