Commit 7115a704 authored by creis's avatar creis Committed by Commit bot

OOPIF: Support session restore by combining/splitting frame PageStates.

In OOPIF-enabled modes, each FrameNavigationEntry has a per-frame
PageState object.  The states for each frame in the tree need to be
combined into a single PageState for GetPageState, and a single
PageState needs to be split into individual FrameNavigationEntries for
SetPageState.

Because GetPageState is const and returns a const ref, we need to
cache the combined PageState each time it could change.

BUG=545219
TEST=Close and restore a tab or session with OOPIFs.

Review URL: https://codereview.chromium.org/1496483002

Cr-Commit-Position: refs/heads/master@{#363025}
parent 0cc26bbb
...@@ -1847,11 +1847,12 @@ void NavigationControllerImpl::FindFramesToNavigate( ...@@ -1847,11 +1847,12 @@ void NavigationControllerImpl::FindFramesToNavigate(
return; return;
// Schedule a load in this frame if the new item isn't for the same item // Schedule a load in this frame if the new item isn't for the same item
// sequence number in the same SiteInstance. // sequence number in the same SiteInstance. Newly restored items may not have
// TODO(creis): Handle null SiteInstances during session restore. // a SiteInstance yet, in which case it will be assigned on first commit.
if (!old_item || if (!old_item ||
new_item->item_sequence_number() != old_item->item_sequence_number() || new_item->item_sequence_number() != old_item->item_sequence_number() ||
new_item->site_instance() != old_item->site_instance()) { (new_item->site_instance() != nullptr &&
new_item->site_instance() != old_item->site_instance())) {
if (old_item && if (old_item &&
new_item->document_sequence_number() == new_item->document_sequence_number() ==
old_item->document_sequence_number()) { old_item->document_sequence_number()) {
......
...@@ -2320,7 +2320,7 @@ IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, ...@@ -2320,7 +2320,7 @@ IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
ASSERT_EQ(1U, entry1->root_node()->children.size()); ASSERT_EQ(1U, entry1->root_node()->children.size());
EXPECT_EQ(data_url, entry1->root_node()->children[0]->frame_entry->url()); EXPECT_EQ(data_url, entry1->root_node()->children[0]->frame_entry->url());
// 2. Navigate the iframe cross-site to a page with a nested iframe. // 2. Navigate the iframe cross-site.
GURL frame_url_b(embedded_test_server()->GetURL( GURL frame_url_b(embedded_test_server()->GetURL(
"b.com", "/navigation_controller/simple_page_1.html")); "b.com", "/navigation_controller/simple_page_1.html"));
{ {
...@@ -2378,6 +2378,144 @@ IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, ...@@ -2378,6 +2378,144 @@ IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
EXPECT_EQ(data_url, entry2->root_node()->children[1]->frame_entry->url()); EXPECT_EQ(data_url, entry2->root_node()->children[1]->frame_entry->url());
} }
// Verify that subframes can be restored in a new NavigationController using the
// PageState of an existing NavigationEntry.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_RestoreViaPageState) {
// 1. Start on a page with a data URL iframe.
GURL main_url_a(embedded_test_server()->GetURL(
"a.com", "/navigation_controller/page_with_data_iframe.html"));
GURL data_url("data:text/html,Subframe");
NavigateToURL(shell(), main_url_a);
const NavigationControllerImpl& controller =
static_cast<const NavigationControllerImpl&>(
shell()->web_contents()->GetController());
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
ASSERT_EQ(0U, root->child_at(0)->child_count());
EXPECT_EQ(main_url_a, root->current_url());
EXPECT_EQ(data_url, root->child_at(0)->current_url());
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
NavigationEntryImpl* entry1 = controller.GetLastCommittedEntry();
// Verify subframe entries if they're enabled (e.g. in --site-per-process).
if (SiteIsolationPolicy::UseSubframeNavigationEntries()) {
// The entry should have a FrameNavigationEntry for the data subframe.
ASSERT_EQ(1U, entry1->root_node()->children.size());
EXPECT_EQ(data_url, entry1->root_node()->children[0]->frame_entry->url());
} else {
// There are no subframe FrameNavigationEntries by default.
EXPECT_EQ(0U, entry1->root_node()->children.size());
}
// 2. Navigate the iframe cross-site.
GURL frame_url_b(embedded_test_server()->GetURL(
"b.com", "/navigation_controller/simple_page_1.html"));
{
FrameNavigateParamsCapturer capturer(root->child_at(0));
NavigateFrameToURL(root->child_at(0), frame_url_b);
capturer.Wait();
}
ASSERT_EQ(1U, root->child_count());
EXPECT_EQ(main_url_a, root->current_url());
EXPECT_EQ(frame_url_b, root->child_at(0)->current_url());
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
NavigationEntryImpl* entry2 = controller.GetLastCommittedEntry();
// Verify subframe entries if they're enabled (e.g. in --site-per-process).
if (SiteIsolationPolicy::UseSubframeNavigationEntries()) {
// The entry should have a FrameNavigationEntry for the b.com subframe.
ASSERT_EQ(1U, entry2->root_node()->children.size());
EXPECT_EQ(frame_url_b,
entry2->root_node()->children[0]->frame_entry->url());
} else {
// There are no subframe FrameNavigationEntries by default.
EXPECT_EQ(0U, entry2->root_node()->children.size());
}
// 3. Navigate main frame cross-site, destroying the frames.
GURL main_url_c(embedded_test_server()->GetURL(
"c.com", "/navigation_controller/simple_page_2.html"));
NavigateToURL(shell(), main_url_c);
ASSERT_EQ(0U, root->child_count());
EXPECT_EQ(main_url_c, root->current_url());
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
NavigationEntryImpl* entry3 = controller.GetLastCommittedEntry();
EXPECT_EQ(0U, entry3->root_node()->children.size());
// 4. Create a NavigationEntry with the same PageState as |entry2| and verify
// it has the same FrameNavigationEntry structure.
scoped_ptr<NavigationEntryImpl> restored_entry =
NavigationEntryImpl::FromNavigationEntry(
NavigationControllerImpl::CreateNavigationEntry(
main_url_a, Referrer(), ui::PAGE_TRANSITION_RELOAD, false,
std::string(), controller.GetBrowserContext()));
restored_entry->SetPageID(0);
EXPECT_EQ(0U, restored_entry->root_node()->children.size());
restored_entry->SetPageState(entry2->GetPageState());
// Verify subframe entries if they're enabled (e.g. in --site-per-process).
if (SiteIsolationPolicy::UseSubframeNavigationEntries()) {
// The entry should have a FrameNavigationEntry for the b.com subframe.
EXPECT_EQ(main_url_a, restored_entry->root_node()->frame_entry->url());
ASSERT_EQ(1U, restored_entry->root_node()->children.size());
EXPECT_EQ(frame_url_b,
restored_entry->root_node()->children[0]->frame_entry->url());
} else {
// There are no subframe FrameNavigationEntries by default.
EXPECT_EQ(0U, restored_entry->root_node()->children.size());
}
// 5. Restore the new entry in a new tab and verify the correct URLs load.
std::vector<scoped_ptr<NavigationEntry>> entries;
entries.push_back(restored_entry.Pass());
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,
NavigationController::RESTORE_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_a, new_root->current_url());
EXPECT_EQ(frame_url_b, new_root->child_at(0)->current_url());
EXPECT_EQ(1, new_controller.GetEntryCount());
EXPECT_EQ(0, new_controller.GetLastCommittedEntryIndex());
NavigationEntryImpl* new_entry = new_controller.GetLastCommittedEntry();
// Verify subframe entries if they're enabled (e.g. in --site-per-process).
if (SiteIsolationPolicy::UseSubframeNavigationEntries()) {
// The entry should have a FrameNavigationEntry for the b.com subframe.
EXPECT_EQ(main_url_a, new_entry->root_node()->frame_entry->url());
ASSERT_EQ(1U, new_entry->root_node()->children.size());
EXPECT_EQ(frame_url_b,
new_entry->root_node()->children[0]->frame_entry->url());
} else {
// There are no subframe FrameNavigationEntries by default.
EXPECT_EQ(0U, new_entry->root_node()->children.size());
}
}
// Verifies that the |frame_unique_name| is set to the correct frame, so that we // Verifies that the |frame_unique_name| is set to the correct frame, so that we
// can match subframe FrameNavigationEntries to newly created frames after // can match subframe FrameNavigationEntries to newly created frames after
// back/forward and restore. // back/forward and restore.
......
...@@ -17,14 +17,72 @@ ...@@ -17,14 +17,72 @@
#include "content/public/common/url_constants.h" #include "content/public/common/url_constants.h"
#include "ui/gfx/text_elider.h" #include "ui/gfx/text_elider.h"
using base::UTF16ToUTF8;
namespace content {
namespace {
// Use this to get a new unique ID for a NavigationEntry during construction. // Use this to get a new unique ID for a NavigationEntry during construction.
// The returned ID is guaranteed to be nonzero (which is the "no ID" indicator). // The returned ID is guaranteed to be nonzero (which is the "no ID" indicator).
static int GetUniqueIDInConstructor() { int GetUniqueIDInConstructor() {
static int unique_id_counter = 0; static int unique_id_counter = 0;
return ++unique_id_counter; return ++unique_id_counter;
} }
namespace content { void RecursivelyGenerateFrameEntries(const ExplodedFrameState& state,
NavigationEntryImpl::TreeNode* node) {
node->frame_entry = new FrameNavigationEntry(
-1, UTF16ToUTF8(state.target.string()), state.item_sequence_number,
state.document_sequence_number, nullptr, GURL(state.url_string.string()),
Referrer(GURL(state.referrer.string()), state.referrer_policy));
// Set a single-frame PageState on the entry.
ExplodedPageState page_state;
page_state.top = state;
std::string data;
if (EncodePageState(page_state, &data))
node->frame_entry->set_page_state(PageState::CreateFromEncodedData(data));
for (const ExplodedFrameState& child_state : state.children) {
NavigationEntryImpl::TreeNode* child_node =
new NavigationEntryImpl::TreeNode(nullptr);
node->children.push_back(child_node);
RecursivelyGenerateFrameEntries(child_state, child_node);
}
}
void RecursivelyGenerateFrameState(
NavigationEntryImpl::TreeNode* node,
ExplodedFrameState* state,
std::vector<base::NullableString16>* referenced_files) {
// The FrameNavigationEntry's PageState contains just the ExplodedFrameState
// for that particular frame.
ExplodedPageState exploded_page_state;
if (!DecodePageState(node->frame_entry->page_state().ToEncodedData(),
&exploded_page_state)) {
NOTREACHED();
return;
}
ExplodedFrameState frame_state = exploded_page_state.top;
// Copy the FrameNavigationEntry's frame state into the destination state.
*state = frame_state;
// Copy the frame's files into the PageState's |referenced_files|.
referenced_files->reserve(referenced_files->size() +
exploded_page_state.referenced_files.size());
for (auto& file : exploded_page_state.referenced_files)
referenced_files->push_back(file);
state->children.resize(node->children.size());
for (size_t i = 0; i < node->children.size(); ++i) {
RecursivelyGenerateFrameState(node->children[i], &state->children[i],
referenced_files);
}
}
} // namespace
int NavigationEntryImpl::kInvalidBindings = -1; int NavigationEntryImpl::kInvalidBindings = -1;
...@@ -180,22 +238,47 @@ const base::string16& NavigationEntryImpl::GetTitle() const { ...@@ -180,22 +238,47 @@ const base::string16& NavigationEntryImpl::GetTitle() const {
} }
void NavigationEntryImpl::SetPageState(const PageState& state) { void NavigationEntryImpl::SetPageState(const PageState& state) {
frame_tree_->frame_entry->set_page_state(state); if (!SiteIsolationPolicy::UseSubframeNavigationEntries()) {
frame_tree_->frame_entry->set_page_state(state);
return;
}
if (SiteIsolationPolicy::UseSubframeNavigationEntries()) { // This should only be called when restoring a NavigationEntry, so there
// Also get the root ISN and DSN out of the PageState. // should be no subframe FrameNavigationEntries yet.
ExplodedPageState exploded_state; DCHECK_EQ(0U, frame_tree_->children.size());
if (!DecodePageState(state.ToEncodedData(), &exploded_state))
return; // If the PageState can't be parsed or has no children, just store it on the
frame_tree_->frame_entry->set_item_sequence_number( // main frame's FrameNavigationEntry without recursively creating subframe
exploded_state.top.item_sequence_number); // entries.
frame_tree_->frame_entry->set_document_sequence_number( ExplodedPageState exploded_state;
exploded_state.top.document_sequence_number); if (!DecodePageState(state.ToEncodedData(), &exploded_state) ||
exploded_state.top.children.size() == 0U) {
frame_tree_->frame_entry->set_page_state(state);
return;
} }
RecursivelyGenerateFrameEntries(exploded_state.top, frame_tree_.get());
} }
const PageState& NavigationEntryImpl::GetPageState() const { PageState NavigationEntryImpl::GetPageState() const {
return frame_tree_->frame_entry->page_state(); // Just return the main frame's PageState in default Chrome, or if there are
// no subframe FrameNavigationEntries.
if (!SiteIsolationPolicy::UseSubframeNavigationEntries() ||
frame_tree_->children.size() == 0U)
return frame_tree_->frame_entry->page_state();
// When we're using subframe entries, each FrameNavigationEntry has a
// frame-specific PageState. We combine these into an ExplodedPageState tree
// and generate a full PageState from it.
ExplodedPageState exploded_state;
RecursivelyGenerateFrameState(frame_tree_.get(), &exploded_state.top,
&exploded_state.referenced_files);
std::string encoded_data;
if (!EncodePageState(exploded_state, &encoded_data))
return frame_tree_->frame_entry->page_state();
return PageState::CreateFromEncodedData(encoded_data);
} }
void NavigationEntryImpl::SetPageID(int page_id) { void NavigationEntryImpl::SetPageID(int page_id) {
......
...@@ -89,7 +89,7 @@ class CONTENT_EXPORT NavigationEntryImpl ...@@ -89,7 +89,7 @@ class CONTENT_EXPORT NavigationEntryImpl
void SetTitle(const base::string16& title) override; void SetTitle(const base::string16& title) override;
const base::string16& GetTitle() const override; const base::string16& GetTitle() const override;
void SetPageState(const PageState& state) override; void SetPageState(const PageState& state) override;
const PageState& GetPageState() const override; PageState GetPageState() const override;
void SetPageID(int page_id) override; void SetPageID(int page_id) override;
int32 GetPageID() const override; int32 GetPageID() const override;
const base::string16& GetTitleForDisplay( const base::string16& GetTitleForDisplay(
......
...@@ -85,7 +85,7 @@ class NavigationEntry { ...@@ -85,7 +85,7 @@ class NavigationEntry {
// the format is modified in the future, we should still be able to deal with // the format is modified in the future, we should still be able to deal with
// older versions. // older versions.
virtual void SetPageState(const PageState& state) = 0; virtual void SetPageState(const PageState& state) = 0;
virtual const PageState& GetPageState() const = 0; virtual PageState GetPageState() const = 0;
// Describes the current page that the tab represents. This is the ID that the // Describes the current page that the tab represents. This is the ID that the
// renderer generated for the page and is how we can tell new versus // renderer generated for the page and is how we can tell new versus
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment