1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/command_line.h"
6 #include "content/browser/frame_host/frame_tree.h"
7 #include "content/browser/frame_host/frame_tree_node.h"
8 #include "content/browser/renderer_host/render_view_host_impl.h"
9 #include "content/browser/web_contents/web_contents_impl.h"
10 #include "content/public/browser/notification_service.h"
11 #include "content/public/browser/notification_types.h"
12 #include "content/public/common/content_switches.h"
13 #include "content/public/common/url_constants.h"
14 #include "content/public/test/browser_test_utils.h"
15 #include "content/public/test/content_browser_test.h"
16 #include "content/public/test/content_browser_test_utils.h"
17 #include "content/public/test/test_navigation_observer.h"
18 #include "content/public/test/test_utils.h"
19 #include "content/shell/browser/shell.h"
20 #include "content/test/content_browser_test_utils_internal.h"
21 #include "net/dns/mock_host_resolver.h"
22 #include "net/test/embedded_test_server/embedded_test_server.h"
23 #include "third_party/WebKit/public/web/WebSandboxFlags.h"
25 // For fine-grained suppression on flaky tests.
27 #include "base/win/windows_version.h"
32 class FrameTreeBrowserTest
: public ContentBrowserTest
{
34 FrameTreeBrowserTest() {}
36 void SetUpOnMainThread() override
{
37 host_resolver()->AddRule("*", "127.0.0.1");
38 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
39 SetupCrossSiteRedirector(embedded_test_server());
43 DISALLOW_COPY_AND_ASSIGN(FrameTreeBrowserTest
);
46 // Ensures FrameTree correctly reflects page structure during navigations.
47 IN_PROC_BROWSER_TEST_F(FrameTreeBrowserTest
, FrameTreeShape
) {
48 GURL base_url
= embedded_test_server()->GetURL("A.com", "/site_isolation/");
50 // Load doc without iframes. Verify FrameTree just has root.
53 NavigateToURL(shell(), base_url
.Resolve("blank.html"));
55 static_cast<WebContentsImpl
*>(shell()->web_contents())->
56 GetFrameTree()->root();
57 EXPECT_EQ(0U, root
->child_count());
59 // Add 2 same-site frames. Verify 3 nodes in tree with proper names.
61 // Site-A Root -- Site-A frame1
63 WindowedNotificationObserver
observer1(
64 content::NOTIFICATION_LOAD_STOP
,
65 content::Source
<NavigationController
>(
66 &shell()->web_contents()->GetController()));
67 NavigateToURL(shell(), base_url
.Resolve("frames-X-X.html"));
69 ASSERT_EQ(2U, root
->child_count());
70 EXPECT_EQ(0U, root
->child_at(0)->child_count());
71 EXPECT_EQ(0U, root
->child_at(1)->child_count());
74 // TODO(ajwong): Talk with nasko and merge this functionality with
76 IN_PROC_BROWSER_TEST_F(FrameTreeBrowserTest
, FrameTreeShape2
) {
77 NavigateToURL(shell(),
78 embedded_test_server()->GetURL("/frame_tree/top.html"));
80 WebContentsImpl
* wc
= static_cast<WebContentsImpl
*>(shell()->web_contents());
81 FrameTreeNode
* root
= wc
->GetFrameTree()->root();
83 // Check that the root node is properly created.
84 ASSERT_EQ(3UL, root
->child_count());
85 EXPECT_EQ(std::string(), root
->frame_name());
87 ASSERT_EQ(2UL, root
->child_at(0)->child_count());
88 EXPECT_STREQ("1-1-name", root
->child_at(0)->frame_name().c_str());
90 // Verify the deepest node exists and has the right name.
91 ASSERT_EQ(2UL, root
->child_at(2)->child_count());
92 EXPECT_EQ(1UL, root
->child_at(2)->child_at(1)->child_count());
93 EXPECT_EQ(0UL, root
->child_at(2)->child_at(1)->child_at(0)->child_count());
94 EXPECT_STREQ("3-1-name",
95 root
->child_at(2)->child_at(1)->child_at(0)->frame_name().c_str());
97 // Navigate to about:blank, which should leave only the root node of the frame
98 // tree in the browser process.
99 NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
101 root
= wc
->GetFrameTree()->root();
102 EXPECT_EQ(0UL, root
->child_count());
103 EXPECT_EQ(std::string(), root
->frame_name());
106 // Test that we can navigate away if the previous renderer doesn't clean up its
108 IN_PROC_BROWSER_TEST_F(FrameTreeBrowserTest
, FrameTreeAfterCrash
) {
109 NavigateToURL(shell(),
110 embedded_test_server()->GetURL("/frame_tree/top.html"));
112 // Ensure the view and frame are live.
113 RenderViewHost
* rvh
= shell()->web_contents()->GetRenderViewHost();
114 RenderFrameHostImpl
* rfh
=
115 static_cast<RenderFrameHostImpl
*>(rvh
->GetMainFrame());
116 EXPECT_TRUE(rvh
->IsRenderViewLive());
117 EXPECT_TRUE(rfh
->IsRenderFrameLive());
119 // Crash the renderer so that it doesn't send any FrameDetached messages.
120 RenderProcessHostWatcher
crash_observer(
121 shell()->web_contents(),
122 RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT
);
123 NavigateToURL(shell(), GURL(kChromeUICrashURL
));
124 crash_observer
.Wait();
126 // The frame tree should be cleared.
127 WebContentsImpl
* wc
= static_cast<WebContentsImpl
*>(shell()->web_contents());
128 FrameTreeNode
* root
= wc
->GetFrameTree()->root();
129 EXPECT_EQ(0UL, root
->child_count());
131 // Ensure the view and frame aren't live anymore.
132 EXPECT_FALSE(rvh
->IsRenderViewLive());
133 EXPECT_FALSE(rfh
->IsRenderFrameLive());
135 // Navigate to a new URL.
136 GURL
url(embedded_test_server()->GetURL("/title1.html"));
137 NavigateToURL(shell(), url
);
138 EXPECT_EQ(0UL, root
->child_count());
139 EXPECT_EQ(url
, root
->current_url());
141 // Ensure the view and frame are live again.
142 EXPECT_TRUE(rvh
->IsRenderViewLive());
143 EXPECT_TRUE(rfh
->IsRenderFrameLive());
146 // Test that we can navigate away if the previous renderer doesn't clean up its
148 // Flaky on Mac. http://crbug.com/452018
149 #if defined(OS_MACOSX)
150 #define MAYBE_NavigateWithLeftoverFrames DISABLED_NavigateWithLeftoverFrames
152 #define MAYBE_NavigateWithLeftoverFrames NavigateWithLeftoverFrames
154 IN_PROC_BROWSER_TEST_F(FrameTreeBrowserTest
, MAYBE_NavigateWithLeftoverFrames
) {
156 // Flaky on XP bot http://crbug.com/468713
157 if (base::win::GetVersion() <= base::win::VERSION_XP
)
160 GURL base_url
= embedded_test_server()->GetURL("A.com", "/site_isolation/");
162 NavigateToURL(shell(),
163 embedded_test_server()->GetURL("/frame_tree/top.html"));
165 // Hang the renderer so that it doesn't send any FrameDetached messages.
166 // (This navigation will never complete, so don't wait for it.)
167 shell()->LoadURL(GURL(kChromeUIHangURL
));
169 // Check that the frame tree still has children.
170 WebContentsImpl
* wc
= static_cast<WebContentsImpl
*>(shell()->web_contents());
171 FrameTreeNode
* root
= wc
->GetFrameTree()->root();
172 ASSERT_EQ(3UL, root
->child_count());
174 // Navigate to a new URL. We use LoadURL because NavigateToURL will try to
175 // wait for the previous navigation to stop.
176 TestNavigationObserver
tab_observer(wc
, 1);
177 shell()->LoadURL(base_url
.Resolve("blank.html"));
180 // The frame tree should now be cleared.
181 EXPECT_EQ(0UL, root
->child_count());
184 // Ensure that IsRenderFrameLive is true for main frames and same-site iframes.
185 IN_PROC_BROWSER_TEST_F(FrameTreeBrowserTest
, IsRenderFrameLive
) {
186 GURL
main_url(embedded_test_server()->GetURL("/frame_tree/top.html"));
187 NavigateToURL(shell(), main_url
);
189 // It is safe to obtain the root frame tree node here, as it doesn't change.
190 FrameTreeNode
* root
= static_cast<WebContentsImpl
*>(shell()->web_contents())
191 ->GetFrameTree()->root();
193 // The root and subframe should each have a live RenderFrame.
195 root
->current_frame_host()->render_view_host()->IsRenderViewLive());
196 EXPECT_TRUE(root
->current_frame_host()->IsRenderFrameLive());
197 EXPECT_TRUE(root
->child_at(0)->current_frame_host()->IsRenderFrameLive());
199 // Load a same-site page into iframe and it should still be live.
200 GURL
http_url(embedded_test_server()->GetURL("/title1.html"));
201 NavigateFrameToURL(root
->child_at(0), http_url
);
203 root
->current_frame_host()->render_view_host()->IsRenderViewLive());
204 EXPECT_TRUE(root
->current_frame_host()->IsRenderFrameLive());
205 EXPECT_TRUE(root
->child_at(0)->current_frame_host()->IsRenderFrameLive());
208 // Ensure that origins are correctly set on navigations.
209 IN_PROC_BROWSER_TEST_F(FrameTreeBrowserTest
, OriginSetOnNavigation
) {
210 GURL
main_url(embedded_test_server()->GetURL("/frame_tree/top.html"));
211 EXPECT_TRUE(NavigateToURL(shell(), main_url
));
213 // It is safe to obtain the root frame tree node here, as it doesn't change.
214 FrameTreeNode
* root
= static_cast<WebContentsImpl
*>(shell()->web_contents())
215 ->GetFrameTree()->root();
217 // Extra '/' is added because the replicated origin is serialized in RFC 6454
218 // format, which dictates no trailing '/', whereas GURL::GetOrigin does put a
220 EXPECT_EQ(root
->current_replication_state().origin
.string() + '/',
221 main_url
.GetOrigin().spec());
223 GURL
frame_url(embedded_test_server()->GetURL("/title1.html"));
224 NavigateFrameToURL(root
->child_at(0), frame_url
);
227 root
->child_at(0)->current_replication_state().origin
.string() + '/',
228 frame_url
.GetOrigin().spec());
230 GURL
data_url("data:text/html,foo");
231 EXPECT_TRUE(NavigateToURL(shell(), data_url
));
233 // Navigating to a data URL should set a unique origin. This is represented
234 // as "null" per RFC 6454.
235 EXPECT_EQ(root
->current_replication_state().origin
.string(), "null");
237 // Re-navigating to a normal URL should update the origin.
238 EXPECT_TRUE(NavigateToURL(shell(), main_url
));
239 EXPECT_EQ(root
->current_replication_state().origin
.string() + '/',
240 main_url
.GetOrigin().spec());
243 // Ensure that sandbox flags are correctly set when child frames are created.
244 IN_PROC_BROWSER_TEST_F(FrameTreeBrowserTest
, SandboxFlagsSetForChildFrames
) {
245 GURL
main_url(embedded_test_server()->GetURL("/sandboxed_frames.html"));
246 EXPECT_TRUE(NavigateToURL(shell(), main_url
));
248 // It is safe to obtain the root frame tree node here, as it doesn't change.
249 FrameTreeNode
* root
= static_cast<WebContentsImpl
*>(shell()->web_contents())
250 ->GetFrameTree()->root();
252 // Verify that sandbox flags are set properly for all FrameTreeNodes.
253 // First frame is completely sandboxed; second frame uses "allow-scripts",
254 // which resets both SandboxFlags::Scripts and
255 // SandboxFlags::AutomaticFeatures bits per blink::parseSandboxPolicy(), and
256 // third frame has "allow-scripts allow-same-origin".
257 EXPECT_EQ(root
->current_replication_state().sandbox_flags
,
258 blink::WebSandboxFlags::None
);
259 EXPECT_EQ(root
->child_at(0)->current_replication_state().sandbox_flags
,
260 blink::WebSandboxFlags::All
);
261 EXPECT_EQ(root
->child_at(1)->current_replication_state().sandbox_flags
,
262 blink::WebSandboxFlags::All
& ~blink::WebSandboxFlags::Scripts
&
263 ~blink::WebSandboxFlags::AutomaticFeatures
);
264 EXPECT_EQ(root
->child_at(2)->current_replication_state().sandbox_flags
,
265 blink::WebSandboxFlags::All
& ~blink::WebSandboxFlags::Scripts
&
266 ~blink::WebSandboxFlags::AutomaticFeatures
&
267 ~blink::WebSandboxFlags::Origin
);
269 // Sandboxed frames should set a unique origin unless they have the
270 // "allow-same-origin" directive.
271 EXPECT_EQ(root
->child_at(0)->current_replication_state().origin
.string(),
273 EXPECT_EQ(root
->child_at(1)->current_replication_state().origin
.string(),
276 root
->child_at(2)->current_replication_state().origin
.string() + "/",
277 main_url
.GetOrigin().spec());
279 // Navigating to a different URL should not clear sandbox flags.
280 GURL
frame_url(embedded_test_server()->GetURL("/title1.html"));
281 NavigateFrameToURL(root
->child_at(0), frame_url
);
282 EXPECT_EQ(root
->child_at(0)->current_replication_state().sandbox_flags
,
283 blink::WebSandboxFlags::All
);
286 // Ensure that a popup opened from a subframe sets its opener to the subframe's
287 // FrameTreeNode, and that the opener is cleared if the subframe is destroyed.
288 IN_PROC_BROWSER_TEST_F(FrameTreeBrowserTest
, SubframeOpenerSetForNewWindow
) {
289 GURL
main_url(embedded_test_server()->GetURL("/frame_tree/top.html"));
290 EXPECT_TRUE(NavigateToURL(shell(), main_url
));
292 // It is safe to obtain the root frame tree node here, as it doesn't change.
293 FrameTreeNode
* root
= static_cast<WebContentsImpl
*>(shell()->web_contents())
297 // Open a new window from a subframe.
298 ShellAddedObserver new_shell_observer
;
299 GURL
popup_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
300 EXPECT_TRUE(ExecuteScript(root
->child_at(0)->current_frame_host(),
301 "window.open('" + popup_url
.spec() + "');"));
302 Shell
* new_shell
= new_shell_observer
.GetShell();
303 WebContents
* new_contents
= new_shell
->web_contents();
304 WaitForLoadStop(new_contents
);
306 // Check that the new window's opener points to the correct subframe on
308 FrameTreeNode
* popup_root
=
309 static_cast<WebContentsImpl
*>(new_contents
)->GetFrameTree()->root();
310 EXPECT_EQ(root
->child_at(0), popup_root
->opener());
312 // Close the original window. This should clear the new window's opener.
314 EXPECT_EQ(nullptr, popup_root
->opener());
317 class CrossProcessFrameTreeBrowserTest
: public ContentBrowserTest
{
319 CrossProcessFrameTreeBrowserTest() {}
321 void SetUpCommandLine(base::CommandLine
* command_line
) override
{
322 command_line
->AppendSwitch(switches::kSitePerProcess
);
325 void SetUpOnMainThread() override
{
326 host_resolver()->AddRule("*", "127.0.0.1");
327 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
328 SetupCrossSiteRedirector(embedded_test_server());
332 DISALLOW_COPY_AND_ASSIGN(CrossProcessFrameTreeBrowserTest
);
335 // Ensure that we can complete a cross-process subframe navigation.
336 IN_PROC_BROWSER_TEST_F(CrossProcessFrameTreeBrowserTest
,
337 CreateCrossProcessSubframeProxies
) {
338 GURL
main_url(embedded_test_server()->GetURL("/site_per_process_main.html"));
339 NavigateToURL(shell(), main_url
);
341 // It is safe to obtain the root frame tree node here, as it doesn't change.
342 FrameTreeNode
* root
= static_cast<WebContentsImpl
*>(shell()->web_contents())
343 ->GetFrameTree()->root();
345 // There should not be a proxy for the root's own SiteInstance.
346 SiteInstance
* root_instance
= root
->current_frame_host()->GetSiteInstance();
347 EXPECT_FALSE(root
->render_manager()->GetRenderFrameProxyHost(root_instance
));
349 // Load same-site page into iframe.
350 GURL
http_url(embedded_test_server()->GetURL("/title1.html"));
351 NavigateFrameToURL(root
->child_at(0), http_url
);
353 // Load cross-site page into iframe.
355 embedded_test_server()->GetURL("foo.com", "/title2.html"));
356 NavigateFrameToURL(root
->child_at(0), cross_site_url
);
358 // Ensure that we have created a new process for the subframe.
359 ASSERT_EQ(2U, root
->child_count());
360 FrameTreeNode
* child
= root
->child_at(0);
361 SiteInstance
* child_instance
= child
->current_frame_host()->GetSiteInstance();
362 RenderViewHost
* rvh
= child
->current_frame_host()->render_view_host();
363 RenderProcessHost
* rph
= child
->current_frame_host()->GetProcess();
365 EXPECT_NE(shell()->web_contents()->GetRenderViewHost(), rvh
);
366 EXPECT_NE(shell()->web_contents()->GetSiteInstance(), child_instance
);
367 EXPECT_NE(shell()->web_contents()->GetRenderProcessHost(), rph
);
369 // Ensure that the root node has a proxy for the child node's SiteInstance.
370 EXPECT_TRUE(root
->render_manager()->GetRenderFrameProxyHost(child_instance
));
372 // Also ensure that the child has a proxy for the root node's SiteInstance.
373 EXPECT_TRUE(child
->render_manager()->GetRenderFrameProxyHost(root_instance
));
375 // The nodes should not have proxies for their own SiteInstance.
376 EXPECT_FALSE(root
->render_manager()->GetRenderFrameProxyHost(root_instance
));
378 child
->render_manager()->GetRenderFrameProxyHost(child_instance
));
380 // Ensure that the RenderViews and RenderFrames are all live.
382 root
->current_frame_host()->render_view_host()->IsRenderViewLive());
384 child
->current_frame_host()->render_view_host()->IsRenderViewLive());
385 EXPECT_TRUE(root
->current_frame_host()->IsRenderFrameLive());
386 EXPECT_TRUE(root
->child_at(0)->current_frame_host()->IsRenderFrameLive());
389 IN_PROC_BROWSER_TEST_F(CrossProcessFrameTreeBrowserTest
,
390 OriginSetOnCrossProcessNavigations
) {
391 GURL
main_url(embedded_test_server()->GetURL("/site_per_process_main.html"));
392 EXPECT_TRUE(NavigateToURL(shell(), main_url
));
394 // It is safe to obtain the root frame tree node here, as it doesn't change.
395 FrameTreeNode
* root
= static_cast<WebContentsImpl
*>(shell()->web_contents())
396 ->GetFrameTree()->root();
398 EXPECT_EQ(root
->current_replication_state().origin
.string() + '/',
399 main_url
.GetOrigin().spec());
401 // First frame is an about:blank frame. Check that its origin is correctly
402 // inherited from the parent.
404 root
->child_at(0)->current_replication_state().origin
.string() + '/',
405 main_url
.GetOrigin().spec());
407 // Second frame loads a same-site page. Its origin should also be the same
410 root
->child_at(1)->current_replication_state().origin
.string() + '/',
411 main_url
.GetOrigin().spec());
413 // Load cross-site page into the first frame.
415 embedded_test_server()->GetURL("foo.com", "/title2.html"));
416 NavigateFrameToURL(root
->child_at(0), cross_site_url
);
419 root
->child_at(0)->current_replication_state().origin
.string() + '/',
420 cross_site_url
.GetOrigin().spec());
422 // The root's origin shouldn't have changed.
423 EXPECT_EQ(root
->current_replication_state().origin
.string() + '/',
424 main_url
.GetOrigin().spec());
426 GURL
data_url("data:text/html,foo");
427 NavigateFrameToURL(root
->child_at(1), data_url
);
429 // Navigating to a data URL should set a unique origin. This is represented
430 // as "null" per RFC 6454.
431 EXPECT_EQ(root
->child_at(1)->current_replication_state().origin
.string(),
435 } // namespace content