diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-09-06 13:12:18 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-09-12 07:44:09 +0000 |
commit | ae14d10af68cd18452ff9f970bc97aaac7a7eb88 (patch) | |
tree | a89b572d50cd2904bddf89cb8aeee32d00edf7c9 | |
parent | c952ab2ef5eb141cd594788f30e36f084908ad2e (diff) |
[Backport] CVE-2018-16073
[Merge to M69] Use unique processes for data URLs on restore.
Data URLs are usually put into the process that created them, but this
info is not tracked after a tab restore. Ensure that they do not end up
in the parent frame's process (or each other's process), in case they
are malicious.
BUG=863069
Change-Id: I899a3da54ea15c922092e02b7c152c5c7c2e342f
Reviewed-on: https://chromium-review.googlesource.com/1150767
Reviewed-on: https://chromium-review.googlesource.com/1167771
Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
5 files changed, 346 insertions, 33 deletions
diff --git a/chromium/content/browser/frame_host/render_frame_host_manager.cc b/chromium/content/browser/frame_host/render_frame_host_manager.cc index 3866f423e1b..e757cbfc7b0 100644 --- a/chromium/content/browser/frame_host/render_frame_host_manager.cc +++ b/chromium/content/browser/frame_host/render_frame_host_manager.cc @@ -1853,7 +1853,7 @@ RenderFrameHostManager::GetSiteInstanceForNavigationRequest( // allowed to swap processes. no_renderer_swap_allowed |= !CanSubframeSwapProcess( request.common_params().url, request.source_site_instance(), - request.dest_site_instance(), was_server_redirect); + request.dest_site_instance()); } if (no_renderer_swap_allowed) @@ -2386,8 +2386,7 @@ void RenderFrameHostManager::SendPageMessage(IPC::Message* msg, bool RenderFrameHostManager::CanSubframeSwapProcess( const GURL& dest_url, SiteInstance* source_instance, - SiteInstance* dest_instance, - bool was_server_redirect) { + SiteInstance* dest_instance) { // On renderer-initiated navigations, when the frame initiating the navigation // and the frame being navigated differ, |source_instance| is set to the // SiteInstance of the initiating frame. |dest_instance| is present on session @@ -2404,22 +2403,25 @@ bool RenderFrameHostManager::CanSubframeSwapProcess( resolved_url = dest_instance->GetSiteURL(); } else { // If there is no SiteInstance this unique origin can be associated with, - // there are two cases: - // (1) If there was a server redirect, allow a process swap. Normally, - // redirects to data: or about: URLs are disallowed as + // then check whether it is safe to put into the parent frame's process. + // This is the case for about:blank URLs (with or without fragments), + // since they contain no active data. This is also the case for + // about:srcdoc, since such URLs only get active content from their parent + // frame. Using the parent frame's process avoids putting blank frames + // into OOPIFs and preserves scripting for about:srcdoc. + // + // Allow a process swap for other unique origin URLs, such as data: URLs. + // These have active content and may have come from an untrusted source, + // such as a restored frame from a different site or a redirect. + // (Normally, redirects to data: or about: URLs are disallowed as // net::ERR_UNSAFE_REDIRECT. However, extensions can still redirect // arbitary requests to those URLs using the chrome.webRequest or // chrome.declarativeWebRequest API, which will end up here (for an - // example, see ExtensionWebRequestApiTest.WebRequestDeclarative1). It's - // safest to swap processes for those redirects if we are in an - // appropriate OOPIF-enabled mode. - // - // (2) Otherwise, avoid a process swap. We can get here during session - // restore, and this avoids putting all data: and about:blank subframes - // in OOPIFs. We can also get here in tests with browser-initiated - // subframe navigations (NavigateFrameToURL). - if (!was_server_redirect) + // example, see ExtensionWebRequestApiTest.WebRequestDeclarative1).) + if (resolved_url.IsAboutBlank() || + resolved_url == GURL(content::kAboutSrcDocURL)) { return false; + } } } diff --git a/chromium/content/browser/frame_host/render_frame_host_manager.h b/chromium/content/browser/frame_host/render_frame_host_manager.h index e18692368ec..d71f0dd2988 100644 --- a/chromium/content/browser/frame_host/render_frame_host_manager.h +++ b/chromium/content/browser/frame_host/render_frame_host_manager.h @@ -696,8 +696,7 @@ class CONTENT_EXPORT RenderFrameHostManager // Returns true if a subframe can navigate cross-process. bool CanSubframeSwapProcess(const GURL& dest_url, SiteInstance* source_instance, - SiteInstance* dest_instance, - bool was_server_redirect); + SiteInstance* dest_instance); // After a renderer process crash we'd have marked the host as invisible, so // we need to set the visibility of the new View to the correct value here diff --git a/chromium/content/browser/site_instance_impl.cc b/chromium/content/browser/site_instance_impl.cc index f4584ea8c32..14a57a71b2a 100644 --- a/chromium/content/browser/site_instance_impl.cc +++ b/chromium/content/browser/site_instance_impl.cc @@ -433,16 +433,27 @@ GURL SiteInstance::GetSiteForURL(BrowserContext* browser_context, return GURL(origin.scheme() + ":"); } else if (url.has_scheme()) { // In some cases, it is not safe to use just the scheme as a site URL, as - // that might allow two URLs created by different sites to to share a - // process. See https://crbug.com/863623. + // that might allow two URLs created by different sites to share a process. + // See https://crbug.com/863623 and https://crbug.com/863069. // // TODO(alexmos,creis): This should eventually be expanded to certain other - // schemes, such as data: and file:. - if (url.SchemeIsBlob()) { + // schemes, such as file:. + // TODO(creis): This currently causes problems with tests on Android and + // Android WebView. For now, skip it when Site Isolation is not enabled, + // since there's no need to isolate data and blob URLs from each other in + // that case. + bool is_site_isolation_enabled = + SiteIsolationPolicy::UseDedicatedProcessesForAllSites() || + SiteIsolationPolicy::AreIsolatedOriginsEnabled(); + if (is_site_isolation_enabled && + (url.SchemeIsBlob() || url.scheme() == url::kDataScheme)) { // We get here for blob URLs of form blob:null/guid. Use the full URL // with the guid in that case, which isolates all blob URLs with unique - // origins from each other. Remove hash from the URL, since - // same-document navigations shouldn't use a different site URL. + // origins from each other. We also get here for browser-initiated + // navigations to data URLs, which have a unique origin and should only + // share a process when they are identical. Remove hash from the URL in + // either case, since same-document navigations shouldn't use a different + // site URL. if (url.has_ref()) { GURL::Replacements replacements; replacements.ClearRef(); diff --git a/chromium/content/browser/site_instance_impl_unittest.cc b/chromium/content/browser/site_instance_impl_unittest.cc index a31de99b666..64a957c9e25 100644 --- a/chromium/content/browser/site_instance_impl_unittest.cc +++ b/chromium/content/browser/site_instance_impl_unittest.cc @@ -340,12 +340,25 @@ TEST_F(SiteInstanceTest, GetSiteForURL) { EXPECT_EQ("file", site_url.scheme()); EXPECT_FALSE(site_url.has_host()); - // Data URLs should include the scheme. + // Data URLs should include the whole URL, except for the hash, when Site + // Isolation is enabled. Otherwise they just include the scheme. test_url = GURL("data:text/html,foo"); site_url = SiteInstanceImpl::GetSiteForURL(nullptr, test_url); - EXPECT_EQ(GURL("data:"), site_url); + if (AreAllSitesIsolatedForTesting()) + EXPECT_EQ(test_url, site_url); + else + EXPECT_EQ(GURL("data:"), site_url); EXPECT_EQ("data", site_url.scheme()); EXPECT_FALSE(site_url.has_host()); + test_url = GURL("data:text/html,foo#bar"); + site_url = SiteInstanceImpl::GetSiteForURL(nullptr, test_url); + EXPECT_FALSE(site_url.has_ref()); + if (AreAllSitesIsolatedForTesting()) { + EXPECT_NE(test_url, site_url); + EXPECT_TRUE(site_url.EqualsIgnoringRef(test_url)); + } else { + EXPECT_EQ(GURL("data:"), site_url); + } // Javascript URLs should include the scheme. test_url = GURL("javascript:foo();"); @@ -368,16 +381,24 @@ TEST_F(SiteInstanceTest, GetSiteForURL) { EXPECT_EQ("file", site_url.scheme()); EXPECT_FALSE(site_url.has_host()); - // Blob URLs created from a unique origin use the full URL as the site URL, - // except for the hash. + // Blob URLs created from a unique origin use the full URL as the site URL + // when Site Isolation is enabled, except for the hash. Otherwise they just + // include the scheme. test_url = GURL("blob:null/1029e5a4-2983-4b90-a585-ed217563acfeb"); site_url = SiteInstanceImpl::GetSiteForURL(nullptr, test_url); - EXPECT_EQ(site_url, test_url); + if (AreAllSitesIsolatedForTesting()) + EXPECT_EQ(test_url, site_url); + else + EXPECT_EQ(GURL("blob:"), site_url); test_url = GURL("blob:null/1029e5a4-2983-4b90-a585-ed217563acfeb#foo"); site_url = SiteInstanceImpl::GetSiteForURL(nullptr, test_url); - EXPECT_NE(site_url, test_url); EXPECT_FALSE(site_url.has_ref()); - EXPECT_TRUE(site_url.EqualsIgnoringRef(test_url)); + if (AreAllSitesIsolatedForTesting()) { + EXPECT_NE(test_url, site_url); + EXPECT_TRUE(site_url.EqualsIgnoringRef(test_url)); + } else { + EXPECT_EQ(GURL("blob:"), site_url); + } // Private domains are preserved, appspot being such a site. test_url = GURL( diff --git a/chromium/content/browser/site_per_process_browsertest.cc b/chromium/content/browser/site_per_process_browsertest.cc index c4095dce2ec..201af0992cf 100644 --- a/chromium/content/browser/site_per_process_browsertest.cc +++ b/chromium/content/browser/site_per_process_browsertest.cc @@ -9765,8 +9765,8 @@ IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, } // Ensures that navigating to data: URLs present in session history will -// correctly commit the navigation in the same process as the parent frame. -// See https://crbug.com/606996. +// correctly commit the navigation in the same process as the one used for the +// original navigation. See https://crbug.com/606996. IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, NavigateSubframeToDataUrlInSessionHistory) { GURL main_url(embedded_test_server()->GetURL( @@ -9792,7 +9792,7 @@ IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, EXPECT_TRUE(observer.last_navigation_succeeded()); EXPECT_EQ(data_url, observer.last_navigation_url()); scoped_refptr<SiteInstanceImpl> orig_site_instance = - child->current_frame_host()->GetSiteInstance(); + child->current_frame_host()->GetSiteInstance(); EXPECT_NE(root->current_frame_host()->GetSiteInstance(), orig_site_instance); // Navigate it to another cross-site url. @@ -9812,6 +9812,286 @@ IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, EXPECT_EQ(orig_site_instance, child->current_frame_host()->GetSiteInstance()); } +// Ensures that subframes navigated to data: URLs start in a process based on +// their creator, but end up in unique processes after a restore (since +// SiteInstance relationships are not preserved on restore, until +// https://crbug.com/14987 is fixed). This is better than restoring into the +// parent process, per https://crbug.com/863069. +IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, + SubframeDataUrlsAfterRestore) { + // We must use a page that has iframes in the HTML here, unlike + // cross_site_iframe_factory.html which loads them dynamically. In the latter + // case, Chrome will not restore subframe URLs from history, which is needed + // for this test. + GURL main_url(embedded_test_server()->GetURL( + "a.com", "/frame_tree/page_with_two_iframes.html")); + EXPECT_TRUE(NavigateToURL(shell(), main_url)); + + FrameTreeNode* root = web_contents()->GetFrameTree()->root(); + EXPECT_EQ(2U, root->child_count()); + 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://a.com/\n" + " B = http://bar.com/\n" + " C = http://baz.com/", + DepictFrameTree(root)); + + FrameTreeNode* child_0 = root->child_at(0); + FrameTreeNode* child_1 = root->child_at(1); + scoped_refptr<SiteInstanceImpl> child_site_instance_0 = + child_0->current_frame_host()->GetSiteInstance(); + scoped_refptr<SiteInstanceImpl> child_site_instance_1 = + child_1->current_frame_host()->GetSiteInstance(); + + // Navigate the iframes to data URLs via renderer initiated navigations, which + // will commit in the existing SiteInstances. + TestNavigationObserver observer(shell()->web_contents()); + GURL data_url_0("data:text/html,dataurl_0"); + { + TestFrameNavigationObserver commit_observer(child_0); + EXPECT_TRUE( + ExecuteScript(child_0, "location.href = '" + data_url_0.spec() + "';")); + commit_observer.WaitForCommit(); + } + EXPECT_TRUE(observer.last_navigation_succeeded()); + EXPECT_EQ(data_url_0, observer.last_navigation_url()); + EXPECT_EQ(child_site_instance_0, + child_0->current_frame_host()->GetSiteInstance()); + + GURL data_url_1("data:text/html,dataurl_1"); + { + TestFrameNavigationObserver commit_observer(child_1); + EXPECT_TRUE( + ExecuteScript(child_1, "location.href = '" + data_url_1.spec() + "';")); + commit_observer.WaitForCommit(); + } + EXPECT_TRUE(observer.last_navigation_succeeded()); + EXPECT_EQ(data_url_1, observer.last_navigation_url()); + EXPECT_EQ(child_site_instance_1, + child_1->current_frame_host()->GetSiteInstance()); + + // Grab the NavigationEntry and clone its PageState into a new entry for + // restoring into a new tab. + const NavigationControllerImpl& controller = + static_cast<const NavigationControllerImpl&>( + shell()->web_contents()->GetController()); + NavigationEntryImpl* entry = controller.GetLastCommittedEntry(); + std::unique_ptr<NavigationEntryImpl> restored_entry = + NavigationEntryImpl::FromNavigationEntry( + NavigationController::CreateNavigationEntry( + main_url, Referrer(), ui::PAGE_TRANSITION_RELOAD, false, + std::string(), controller.GetBrowserContext(), + nullptr /* blob_url_loader_factory */)); + EXPECT_EQ(0U, restored_entry->root_node()->children.size()); + restored_entry->SetPageState(entry->GetPageState()); + ASSERT_EQ(2U, restored_entry->root_node()->children.size()); + + // Restore the NavigationEntry into a new tab and check that the data URLs are + // not loaded into the parent's SiteInstance. + std::vector<std::unique_ptr<NavigationEntry>> entries; + entries.push_back(std::move(restored_entry)); + Shell* new_shell = Shell::CreateNewWindow( + controller.GetBrowserContext(), GURL::EmptyGURL(), nullptr, gfx::Size()); + FrameTreeNode* new_root = + static_cast<WebContentsImpl*>(new_shell->web_contents()) + ->GetFrameTree() + ->root(); + NavigationControllerImpl& new_controller = + static_cast<NavigationControllerImpl&>( + new_shell->web_contents()->GetController()); + new_controller.Restore(entries.size() - 1, + RestoreType::LAST_SESSION_EXITED_CLEANLY, &entries); + ASSERT_EQ(0u, entries.size()); + { + TestNavigationObserver restore_observer(new_shell->web_contents()); + new_controller.LoadIfNecessary(); + restore_observer.Wait(); + } + ASSERT_EQ(2U, new_root->child_count()); + EXPECT_EQ(main_url, new_root->current_url()); + EXPECT_EQ("data", new_root->child_at(0)->current_url().scheme()); + EXPECT_EQ("data", new_root->child_at(1)->current_url().scheme()); + + EXPECT_NE(new_root->current_frame_host()->GetSiteInstance(), + new_root->child_at(0)->current_frame_host()->GetSiteInstance()); + EXPECT_NE(new_root->current_frame_host()->GetSiteInstance(), + new_root->child_at(1)->current_frame_host()->GetSiteInstance()); + EXPECT_NE(new_root->child_at(0)->current_frame_host()->GetSiteInstance(), + new_root->child_at(1)->current_frame_host()->GetSiteInstance()); +} + +// Similar to SubframeDataUrlsAfterRestore, but ensures that about:blank frames +// do get put into their parent process after restore, even if they weren't +// originally. This is safe because they do not contain active content (even +// when there's a fragment in the URL), and it avoids unnecessary OOPIFs. +IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, + SubframeBlankUrlsAfterRestore) { + // We must use a page that has iframes in the HTML here, unlike + // cross_site_iframe_factory.html which loads them dynamically. In the latter + // case, Chrome will not restore subframe URLs from history, which is needed + // for this test. + GURL main_url(embedded_test_server()->GetURL( + "a.com", "/frame_tree/page_with_two_iframes.html")); + EXPECT_TRUE(NavigateToURL(shell(), main_url)); + + FrameTreeNode* root = web_contents()->GetFrameTree()->root(); + EXPECT_EQ(2U, root->child_count()); + 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://a.com/\n" + " B = http://bar.com/\n" + " C = http://baz.com/", + DepictFrameTree(root)); + + FrameTreeNode* child_0 = root->child_at(0); + FrameTreeNode* child_1 = root->child_at(1); + scoped_refptr<SiteInstanceImpl> child_site_instance_0 = + child_0->current_frame_host()->GetSiteInstance(); + scoped_refptr<SiteInstanceImpl> child_site_instance_1 = + child_1->current_frame_host()->GetSiteInstance(); + + // Navigate the iframes to about:blank URLs via renderer initiated + // navigations, which will commit in the existing SiteInstances. + TestNavigationObserver observer(shell()->web_contents()); + GURL blank_url("about:blank"); + { + TestFrameNavigationObserver commit_observer(child_0); + EXPECT_TRUE( + ExecuteScript(child_0, "location.href = '" + blank_url.spec() + "';")); + commit_observer.WaitForCommit(); + } + EXPECT_TRUE(observer.last_navigation_succeeded()); + EXPECT_EQ(blank_url, observer.last_navigation_url()); + EXPECT_EQ(child_site_instance_0, + child_0->current_frame_host()->GetSiteInstance()); + + GURL blank_url_ref("about:blank#1"); + { + TestFrameNavigationObserver commit_observer(child_1); + EXPECT_TRUE(ExecuteScript( + child_1, "location.href = '" + blank_url_ref.spec() + "';")); + commit_observer.WaitForCommit(); + } + EXPECT_TRUE(observer.last_navigation_succeeded()); + EXPECT_EQ(blank_url_ref, observer.last_navigation_url()); + EXPECT_EQ(child_site_instance_1, + child_1->current_frame_host()->GetSiteInstance()); + + // Grab the NavigationEntry and clone its PageState into a new entry for + // restoring into a new tab. + const NavigationControllerImpl& controller = + static_cast<const NavigationControllerImpl&>( + shell()->web_contents()->GetController()); + NavigationEntryImpl* entry = controller.GetLastCommittedEntry(); + std::unique_ptr<NavigationEntryImpl> restored_entry = + NavigationEntryImpl::FromNavigationEntry( + NavigationController::CreateNavigationEntry( + main_url, Referrer(), ui::PAGE_TRANSITION_RELOAD, false, + std::string(), controller.GetBrowserContext(), + nullptr /* blob_url_loader_factory */)); + EXPECT_EQ(0U, restored_entry->root_node()->children.size()); + restored_entry->SetPageState(entry->GetPageState()); + ASSERT_EQ(2U, restored_entry->root_node()->children.size()); + + // Restore the NavigationEntry into a new tab and check that the data URLs are + // not loaded into the parent's SiteInstance. + std::vector<std::unique_ptr<NavigationEntry>> entries; + entries.push_back(std::move(restored_entry)); + Shell* new_shell = Shell::CreateNewWindow( + controller.GetBrowserContext(), GURL::EmptyGURL(), nullptr, gfx::Size()); + FrameTreeNode* new_root = + static_cast<WebContentsImpl*>(new_shell->web_contents()) + ->GetFrameTree() + ->root(); + NavigationControllerImpl& new_controller = + static_cast<NavigationControllerImpl&>( + new_shell->web_contents()->GetController()); + new_controller.Restore(entries.size() - 1, + RestoreType::LAST_SESSION_EXITED_CLEANLY, &entries); + ASSERT_EQ(0u, entries.size()); + { + TestNavigationObserver restore_observer(new_shell->web_contents()); + new_controller.LoadIfNecessary(); + restore_observer.Wait(); + } + ASSERT_EQ(2U, new_root->child_count()); + EXPECT_EQ(main_url, new_root->current_url()); + EXPECT_TRUE(new_root->child_at(0)->current_url().IsAboutBlank()); + EXPECT_TRUE(new_root->child_at(1)->current_url().IsAboutBlank()); + + EXPECT_EQ(new_root->current_frame_host()->GetSiteInstance(), + new_root->child_at(0)->current_frame_host()->GetSiteInstance()); + EXPECT_EQ(new_root->current_frame_host()->GetSiteInstance(), + new_root->child_at(1)->current_frame_host()->GetSiteInstance()); +} + +// Similar to SubframeBlankUrlsAfterRestore, but ensures that about:srcdoc ends +// up in its parent's process after restore, since that's where its content +// comes from. +IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, + SubframeSrcdocUrlAfterRestore) { + // Load a page that uses iframe srcdoc. + GURL main_url(embedded_test_server()->GetURL( + "a.com", "/frame_tree/page_with_srcdoc_frame.html")); + EXPECT_TRUE(NavigateToURL(shell(), main_url)); + + FrameTreeNode* root = web_contents()->GetFrameTree()->root(); + EXPECT_EQ(1U, root->child_count()); + FrameTreeNode* child = root->child_at(0); + scoped_refptr<SiteInstanceImpl> child_site_instance = + child->current_frame_host()->GetSiteInstance(); + EXPECT_EQ(child_site_instance, root->current_frame_host()->GetSiteInstance()); + + // Grab the NavigationEntry and clone its PageState into a new entry for + // restoring into a new tab. + const NavigationControllerImpl& controller = + static_cast<const NavigationControllerImpl&>( + shell()->web_contents()->GetController()); + NavigationEntryImpl* entry = controller.GetLastCommittedEntry(); + std::unique_ptr<NavigationEntryImpl> restored_entry = + NavigationEntryImpl::FromNavigationEntry( + NavigationController::CreateNavigationEntry( + main_url, Referrer(), ui::PAGE_TRANSITION_RELOAD, false, + std::string(), controller.GetBrowserContext(), + nullptr /* blob_url_loader_factory */)); + EXPECT_EQ(0U, restored_entry->root_node()->children.size()); + restored_entry->SetPageState(entry->GetPageState()); + ASSERT_EQ(1U, restored_entry->root_node()->children.size()); + + // Restore the NavigationEntry into a new tab and check that the srcdoc URLs + // are still loaded into the parent's SiteInstance. + std::vector<std::unique_ptr<NavigationEntry>> entries; + entries.push_back(std::move(restored_entry)); + Shell* new_shell = Shell::CreateNewWindow( + controller.GetBrowserContext(), GURL::EmptyGURL(), nullptr, gfx::Size()); + FrameTreeNode* new_root = + static_cast<WebContentsImpl*>(new_shell->web_contents()) + ->GetFrameTree() + ->root(); + NavigationControllerImpl& new_controller = + static_cast<NavigationControllerImpl&>( + new_shell->web_contents()->GetController()); + new_controller.Restore(entries.size() - 1, + RestoreType::LAST_SESSION_EXITED_CLEANLY, &entries); + ASSERT_EQ(0u, entries.size()); + { + TestNavigationObserver restore_observer(new_shell->web_contents()); + new_controller.LoadIfNecessary(); + restore_observer.Wait(); + } + ASSERT_EQ(1U, new_root->child_count()); + EXPECT_EQ(main_url, new_root->current_url()); + EXPECT_EQ(GURL(content::kAboutSrcDocURL), + new_root->child_at(0)->current_url()); + + EXPECT_EQ(new_root->current_frame_host()->GetSiteInstance(), + new_root->child_at(0)->current_frame_host()->GetSiteInstance()); +} + // Ensures that navigating to about:blank URLs present in session history will // correctly commit the navigation in the same process as the one used for // the original navigation. |