summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2018-09-06 13:12:18 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2018-09-12 07:44:09 +0000
commitae14d10af68cd18452ff9f970bc97aaac7a7eb88 (patch)
treea89b572d50cd2904bddf89cb8aeee32d00edf7c9
parentc952ab2ef5eb141cd594788f30e36f084908ad2e (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>
-rw-r--r--chromium/content/browser/frame_host/render_frame_host_manager.cc32
-rw-r--r--chromium/content/browser/frame_host/render_frame_host_manager.h3
-rw-r--r--chromium/content/browser/site_instance_impl.cc23
-rw-r--r--chromium/content/browser/site_instance_impl_unittest.cc35
-rw-r--r--chromium/content/browser/site_per_process_browsertest.cc286
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.