blob: b37577a034e7a1b3e29d570c026b8caf8c147b61 [file] [log] [blame]
// Copyright 2014 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/frame_host/navigation_controller_impl.h"
#include <stdint.h>
#include <algorithm>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/histogram_tester.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "build/build_config.h"
#include "content/browser/frame_host/frame_navigation_entry.h"
#include "content/browser/frame_host/frame_tree.h"
#include "content/browser/frame_host/navigation_entry_impl.h"
#include "content/browser/frame_host/navigation_handle_impl.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/loader/resource_dispatcher_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/frame_messages.h"
#include "content/common/page_state_serialization.h"
#include "content/common/site_isolation_policy.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/resource_dispatcher_host.h"
#include "content/public/browser/resource_dispatcher_host_delegate.h"
#include "content/public/browser/resource_throttle.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/bindings_policy.h"
#include "content/public/common/browser_side_navigation_policy.h"
#include "content/public/common/renderer_preferences.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.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/shell/common/shell_switches.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/did_commit_provisional_load_interceptor.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/url_request/url_request_failed_job.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace {
static const char kAddNamedFrameScript[] =
"var f = document.createElement('iframe');"
"f.name = 'foo-frame-name';"
"document.body.appendChild(f);";
static const char kAddFrameScript[] =
"var f = document.createElement('iframe');"
"document.body.appendChild(f);";
static const char kRemoveFrameScript[] =
"var f = document.querySelector('iframe');"
"f.parentNode.removeChild(f);";
} // namespace
namespace content {
class NavigationControllerBrowserTest : public ContentBrowserTest {
protected:
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
}
};
// Ensure that tests can navigate subframes cross-site in both default mode and
// --site-per-process, but that they only go cross-process in the latter.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, LoadCrossSiteSubframe) {
// Load a main frame with a subframe.
GURL main_url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_iframe.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
ASSERT_NE(nullptr, root->child_at(0));
// Use NavigateFrameToURL to go cross-site in the subframe.
GURL foo_url(embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/simple_page_1.html"));
NavigateFrameToURL(root->child_at(0), foo_url);
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
// We should only have swapped processes in --site-per-process.
bool cross_process = root->current_frame_host()->GetProcess() !=
root->child_at(0)->current_frame_host()->GetProcess();
EXPECT_EQ(AreAllSitesIsolatedForTesting(), cross_process);
}
// Verifies that the base, history, and data URLs for LoadDataWithBaseURL end up
// in the expected parts of the NavigationEntry in each stage of navigation, and
// that we don't kill the renderer on reload. See https://crbug.com/522567.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, LoadDataWithBaseURL) {
const GURL base_url("http://baseurl");
const GURL history_url("http://historyurl");
const std::string data = "<html><body>foo</body></html>";
const GURL data_url = GURL("data:text/html;charset=utf-8," + data);
const NavigationControllerImpl& controller =
static_cast<const NavigationControllerImpl&>(
shell()->web_contents()->GetController());
// Load data, but don't commit yet.
TestNavigationObserver same_tab_observer(shell()->web_contents(), 1);
shell()->LoadDataWithBaseURL(history_url, data, base_url);
// Verify the pending NavigationEntry.
NavigationEntryImpl* pending_entry = controller.GetPendingEntry();
EXPECT_EQ(base_url, pending_entry->GetBaseURLForDataURL());
EXPECT_EQ(history_url, pending_entry->GetVirtualURL());
EXPECT_EQ(history_url, pending_entry->GetHistoryURLForDataURL());
EXPECT_EQ(data_url, pending_entry->GetURL());
// Let the navigation commit.
same_tab_observer.Wait();
// Verify the last committed NavigationEntry.
NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
EXPECT_EQ(base_url, entry->GetBaseURLForDataURL());
EXPECT_EQ(history_url, entry->GetVirtualURL());
EXPECT_EQ(history_url, entry->GetHistoryURLForDataURL());
EXPECT_EQ(data_url, entry->GetURL());
// We should use data_url instead of the base_url as the original url of
// this navigation entry, because base_url is only used for resolving relative
// paths in the data, or enforcing same origin policy.
EXPECT_EQ(data_url, entry->GetOriginalRequestURL());
// Now reload and make sure the renderer isn't killed.
ReloadBlockUntilNavigationsComplete(shell(), 1);
EXPECT_TRUE(shell()->web_contents()->GetMainFrame()->IsRenderFrameLive());
// Verify the last committed NavigationEntry hasn't changed.
NavigationEntryImpl* reload_entry = controller.GetLastCommittedEntry();
EXPECT_EQ(entry, reload_entry);
EXPECT_EQ(base_url, reload_entry->GetBaseURLForDataURL());
EXPECT_EQ(history_url, reload_entry->GetVirtualURL());
EXPECT_EQ(history_url, reload_entry->GetHistoryURLForDataURL());
EXPECT_EQ(data_url, reload_entry->GetOriginalRequestURL());
EXPECT_EQ(data_url, reload_entry->GetURL());
}
// Verify which page loads when going back to a LoadDataWithBaseURL entry.
// See https://crbug.com/612196.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
LoadDataWithBaseURLTitleAfterBack) {
const GURL base_url("http://baseurl");
const GURL history_url(
embedded_test_server()->GetURL("/navigation_controller/form.html"));
const std::string data1 = "<html><title>One</title><body>foo</body></html>";
const GURL data_url1 = GURL("data:text/html;charset=utf-8," + data1);
NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
shell()->web_contents()->GetController());
{
TestNavigationObserver same_tab_observer(shell()->web_contents(), 1);
shell()->LoadDataWithBaseURL(history_url, data1, base_url);
same_tab_observer.Wait();
}
// Verify the last committed NavigationEntry.
NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
EXPECT_EQ(base_url, entry->GetBaseURLForDataURL());
EXPECT_EQ(history_url, entry->GetVirtualURL());
EXPECT_EQ(history_url, entry->GetHistoryURLForDataURL());
EXPECT_EQ(data_url1, entry->GetURL());
// Navigate again to a different data URL.
const std::string data2 = "<html><title>Two</title><body>bar</body></html>";
const GURL data_url2 = GURL("data:text/html;charset=utf-8," + data2);
{
TestNavigationObserver same_tab_observer(shell()->web_contents(), 1);
// Load data, not loaddatawithbaseurl.
EXPECT_TRUE(NavigateToURL(shell(), data_url2));
same_tab_observer.Wait();
}
// Go back.
TestNavigationObserver back_load_observer(shell()->web_contents());
controller.GoBack();
back_load_observer.Wait();
// Check title. We should load the data URL when going back.
EXPECT_EQ("One", base::UTF16ToUTF8(shell()->web_contents()->GetTitle()));
// Verify the last committed NavigationEntry.
NavigationEntryImpl* back_entry = controller.GetLastCommittedEntry();
EXPECT_EQ(base_url, back_entry->GetBaseURLForDataURL());
EXPECT_EQ(history_url, back_entry->GetVirtualURL());
EXPECT_EQ(history_url, back_entry->GetHistoryURLForDataURL());
EXPECT_EQ(data_url1, back_entry->GetOriginalRequestURL());
EXPECT_EQ(data_url1, back_entry->GetURL());
EXPECT_EQ(data_url1,
shell()->web_contents()->GetMainFrame()->GetLastCommittedURL());
}
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
CrossDomainResourceRequestLoadDataWithBaseUrl) {
const GURL base_url("foobar://");
const GURL history_url("http://historyurl");
const std::string data = "<html><body></body></html>";
const GURL data_url = GURL("data:text/html;charset=utf-8," + data);
const NavigationControllerImpl& controller =
static_cast<const NavigationControllerImpl&>(
shell()->web_contents()->GetController());
// Load data and commit.
{
TestNavigationObserver same_tab_observer(shell()->web_contents(), 1);
shell()->LoadDataWithBaseURL(history_url, data, base_url);
same_tab_observer.Wait();
EXPECT_EQ(1, controller.GetEntryCount());
NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
EXPECT_EQ(base_url, entry->GetBaseURLForDataURL());
EXPECT_EQ(history_url, entry->GetVirtualURL());
EXPECT_EQ(history_url, entry->GetHistoryURLForDataURL());
EXPECT_EQ(data_url, entry->GetURL());
}
// Now make an XHR request and check that the renderer isn't killed.
std::string script =
"var url = 'http://www.example.com';\n"
"var xhr = new XMLHttpRequest();\n"
"xhr.open('GET', url);\n"
"xhr.send();\n";
EXPECT_TRUE(ExecuteScript(shell()->web_contents(), script));
// The renderer may not be killed immediately (if it is indeed killed), so
// reload, block and verify its liveness.
ReloadBlockUntilNavigationsComplete(shell(), 1);
EXPECT_TRUE(shell()->web_contents()->GetMainFrame()->IsRenderFrameLive());
}
#if defined(OS_ANDROID)
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
LoadDataWithInvalidBaseURL) {
const GURL base_url("http://"); // Invalid.
const GURL history_url("http://historyurl");
const std::string title = "invalid_base_url";
const std::string data = base::StringPrintf(
"<html><head><title>%s</title></head><body>foo</body></html>",
title.c_str());
const GURL data_url = GURL("data:text/html;charset=utf-8," + data);
const NavigationControllerImpl& controller =
static_cast<const NavigationControllerImpl&>(
shell()->web_contents()->GetController());
TestNavigationObserver same_tab_observer(shell()->web_contents(), 1);
TitleWatcher title_watcher(shell()->web_contents(), base::UTF8ToUTF16(title));
shell()->LoadDataAsStringWithBaseURL(history_url, data, base_url);
same_tab_observer.Wait();
base::string16 actual_title = title_watcher.WaitAndGetTitle();
EXPECT_EQ(title, base::UTF16ToUTF8(actual_title));
NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
// What the base URL ends up being is really implementation defined, as
// using an invalid base URL is already undefined behavior.
EXPECT_EQ(base_url, entry->GetBaseURLForDataURL());
}
#endif // defined(OS_ANDROID)
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
NavigateFromLoadDataWithBaseURL) {
const GURL base_url("http://baseurl");
const GURL history_url("http://historyurl");
const std::string data = "<html><body></body></html>";
const GURL data_url = GURL("data:text/html;charset=utf-8," + data);
const NavigationControllerImpl& controller =
static_cast<const NavigationControllerImpl&>(
shell()->web_contents()->GetController());
// Load data and commit.
{
TestNavigationObserver same_tab_observer(shell()->web_contents(), 1);
shell()->LoadDataWithBaseURL(history_url, data, base_url);
same_tab_observer.Wait();
EXPECT_EQ(1, controller.GetEntryCount());
NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
EXPECT_EQ(base_url, entry->GetBaseURLForDataURL());
EXPECT_EQ(history_url, entry->GetVirtualURL());
EXPECT_EQ(history_url, entry->GetHistoryURLForDataURL());
EXPECT_EQ(data_url, entry->GetURL());
}
// TODO(boliu): Add test for same document fragment navigation. See
// crbug.com/561034.
// Navigate with Javascript.
{
GURL navigate_url = embedded_test_server()->base_url();
std::string script = "document.location = '" +
navigate_url.spec() + "';";
TestNavigationObserver same_tab_observer(shell()->web_contents(), 1);
EXPECT_TRUE(ExecuteScript(shell(), script));
same_tab_observer.Wait();
EXPECT_EQ(2, controller.GetEntryCount());
NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
EXPECT_TRUE(entry->GetBaseURLForDataURL().is_empty());
EXPECT_TRUE(entry->GetHistoryURLForDataURL().is_empty());
EXPECT_EQ(navigate_url, entry->GetVirtualURL());
EXPECT_EQ(navigate_url, entry->GetURL());
}
}
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FragmentNavigateFromLoadDataWithBaseURL) {
const GURL base_url("http://baseurl");
const GURL history_url("http://historyurl");
const std::string data =
"<html><body>"
" <p id=\"frag\">"
" <a id=\"fraglink\" href=\"#frag\">same document nav</a>"
" </p>"
"</body></html>";
const NavigationControllerImpl& controller =
static_cast<const NavigationControllerImpl&>(
shell()->web_contents()->GetController());
// Load data and commit.
TestNavigationObserver same_tab_observer(shell()->web_contents(), 1);
#if defined(OS_ANDROID)
shell()->LoadDataAsStringWithBaseURL(history_url, data, base_url);
#else
shell()->LoadDataWithBaseURL(history_url, data, base_url);
#endif
same_tab_observer.Wait();
EXPECT_EQ(1, controller.GetEntryCount());
const GURL data_url = controller.GetLastCommittedEntry()->GetURL();
// Perform a fragment navigation using a javascript: URL (which doesn't lead
// to a commit).
GURL js_url("javascript:document.location = '#frag';");
EXPECT_FALSE(NavigateToURL(shell(), js_url));
EXPECT_EQ(2, controller.GetEntryCount());
NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
EXPECT_EQ(base_url, entry->GetBaseURLForDataURL());
EXPECT_EQ(history_url, entry->GetHistoryURLForDataURL());
EXPECT_EQ(history_url, entry->GetVirtualURL());
EXPECT_EQ(data_url, entry->GetURL());
// Passes if renderer is still alive.
EXPECT_TRUE(ExecuteScript(shell(), "console.log('Success');"));
}
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, UniqueIDs) {
const NavigationControllerImpl& controller =
static_cast<const NavigationControllerImpl&>(
shell()->web_contents()->GetController());
GURL main_url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_link_to_load_iframe.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
ASSERT_EQ(1, controller.GetEntryCount());
// Use JavaScript to click the link and load the iframe.
std::string script = "document.getElementById('link').click()";
EXPECT_TRUE(ExecuteScript(shell(), script));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
ASSERT_EQ(2, controller.GetEntryCount());
// Unique IDs should... um... be unique.
ASSERT_NE(controller.GetEntryAtIndex(0)->GetUniqueID(),
controller.GetEntryAtIndex(1)->GetUniqueID());
}
// Ensures that RenderFrameHosts end up with the correct nav_entry_id() after
// navigations.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, UniqueIDsOnFrames) {
NavigationController& controller = shell()->web_contents()->GetController();
// Load a main frame with an about:blank subframe.
GURL main_url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_iframe.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
ASSERT_NE(nullptr, root->child_at(0));
// The main frame's nav_entry_id should match the last committed entry.
int unique_id = controller.GetLastCommittedEntry()->GetUniqueID();
EXPECT_EQ(unique_id, root->current_frame_host()->nav_entry_id());
// The about:blank iframe should have inherited the same nav_entry_id.
EXPECT_EQ(unique_id, root->child_at(0)->current_frame_host()->nav_entry_id());
// Use NavigateFrameToURL to go cross-site in the subframe.
GURL foo_url(embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/simple_page_1.html"));
NavigateFrameToURL(root->child_at(0), foo_url);
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
// The unique ID should have stayed the same for the auto-subframe navigation,
// since the new page replaces the initial about:blank page in the subframe.
EXPECT_EQ(unique_id, controller.GetLastCommittedEntry()->GetUniqueID());
EXPECT_EQ(unique_id, root->current_frame_host()->nav_entry_id());
EXPECT_EQ(unique_id, root->child_at(0)->current_frame_host()->nav_entry_id());
// Navigating in the subframe again should create a new entry.
GURL foo_url2(embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/simple_page_2.html"));
NavigateFrameToURL(root->child_at(0), foo_url2);
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
int unique_id2 = controller.GetLastCommittedEntry()->GetUniqueID();
EXPECT_NE(unique_id, unique_id2);
// The unique ID should have updated for the current RenderFrameHost in both
// frames, not just the subframe.
EXPECT_EQ(unique_id2, root->current_frame_host()->nav_entry_id());
EXPECT_EQ(unique_id2,
root->child_at(0)->current_frame_host()->nav_entry_id());
}
// This test used to make sure that a scheme used to prevent spoofs didn't ever
// interfere with navigations. We switched to a different scheme, so now this is
// just a test to make sure we can still navigate once we prune the history
// list.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
DontIgnoreBackAfterNavEntryLimit) {
NavigationController& controller =
shell()->web_contents()->GetController();
const int kMaxEntryCount =
static_cast<int>(NavigationControllerImpl::max_entry_count());
// Load up to the max count, all entries should be there.
for (int url_index = 0; url_index < kMaxEntryCount; ++url_index) {
GURL url(base::StringPrintf("data:text/html,page%d", url_index));
EXPECT_TRUE(NavigateToURL(shell(), url));
}
EXPECT_EQ(controller.GetEntryCount(), kMaxEntryCount);
// Navigate twice more more.
for (int url_index = kMaxEntryCount;
url_index < kMaxEntryCount + 2; ++url_index) {
GURL url(base::StringPrintf("data:text/html,page%d", url_index));
EXPECT_TRUE(NavigateToURL(shell(), url));
}
// We expect page0 and page1 to be gone.
EXPECT_EQ(kMaxEntryCount, controller.GetEntryCount());
EXPECT_EQ(GURL("data:text/html,page2"),
controller.GetEntryAtIndex(0)->GetURL());
// Now try to go back. This should not hang.
ASSERT_TRUE(controller.CanGoBack());
controller.GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
// This should have successfully gone back.
EXPECT_EQ(GURL(base::StringPrintf("data:text/html,page%d", kMaxEntryCount)),
controller.GetLastCommittedEntry()->GetURL());
}
namespace {
int RendererHistoryLength(Shell* shell) {
int value = 0;
EXPECT_TRUE(ExecuteScriptAndExtractInt(
shell, "domAutomationController.send(history.length)", &value));
return value;
}
// Does a renderer-initiated location.replace navigation to |url|, replacing the
// current entry.
bool RendererLocationReplace(Shell* shell, const GURL& url) {
WebContents* web_contents = shell->web_contents();
WaitForLoadStop(web_contents);
TestNavigationObserver same_tab_observer(web_contents, 1);
EXPECT_TRUE(
ExecuteScript(shell, "window.location.replace('" + url.spec() + "');"));
same_tab_observer.Wait();
if (!IsLastCommittedEntryOfPageType(web_contents, PAGE_TYPE_NORMAL))
return false;
return web_contents->GetLastCommittedURL() == url;
}
} // namespace
// When loading a new page to replace an old page in the history list, make sure
// that the browser and renderer agree, and that both get it right.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
CorrectLengthWithCurrentItemReplacement) {
NavigationController& controller =
shell()->web_contents()->GetController();
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/simple_page.html")));
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(1, RendererHistoryLength(shell()));
EXPECT_TRUE(RendererLocationReplace(
shell(), embedded_test_server()->GetURL("/title1.html")));
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(1, RendererHistoryLength(shell()));
// Now create two more entries and go back, to test replacing an entry without
// pruning the forward history.
EXPECT_TRUE(
NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html")));
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(2, RendererHistoryLength(shell()));
EXPECT_TRUE(
NavigateToURL(shell(), embedded_test_server()->GetURL("/title3.html")));
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(3, RendererHistoryLength(shell()));
controller.GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
controller.GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_TRUE(controller.CanGoForward());
EXPECT_TRUE(RendererLocationReplace(
shell(), embedded_test_server()->GetURL("/simple_page.html?page1b")));
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(3, RendererHistoryLength(shell()));
EXPECT_TRUE(controller.CanGoForward());
// Note that there's no way to access the renderer's notion of the history
// offset via JavaScript. Checking just the history length, though, is enough;
// if the replacement failed, there would be a new history entry and thus an
// incorrect length.
}
// When spawning a new page from a WebUI page, make sure that the browser and
// renderer agree about the length of the history list, and that both get it
// right.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
CorrectLengthWithNewTabNavigatingFromWebUI) {
GURL web_ui_page(std::string(kChromeUIScheme) + "://" +
std::string(kChromeUIGpuHost));
EXPECT_TRUE(NavigateToURL(shell(), web_ui_page));
EXPECT_EQ(BINDINGS_POLICY_WEB_UI,
shell()->web_contents()->GetMainFrame()->GetEnabledBindings());
ShellAddedObserver observer;
std::string page_url = embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html").spec();
EXPECT_TRUE(
ExecuteScript(shell(), "window.open('" + page_url + "', '_blank')"));
Shell* shell2 = observer.GetShell();
EXPECT_TRUE(WaitForLoadStop(shell2->web_contents()));
EXPECT_EQ(1, shell2->web_contents()->GetController().GetEntryCount());
EXPECT_EQ(1, RendererHistoryLength(shell2));
// Again, as above, there's no way to access the renderer's notion of the
// history offset via JavaScript. Checking just the history length, again,
// will have to suffice.
}
namespace {
class NoNavigationsObserver : public WebContentsObserver {
public:
// Observes navigation for the specified |web_contents|.
explicit NoNavigationsObserver(WebContents* web_contents)
: WebContentsObserver(web_contents) {}
private:
void DidFinishNavigation(NavigationHandle* navigation_handle) override {
if (!navigation_handle->HasCommitted())
return;
FAIL() << "No navigations should occur";
}
};
class FrameNavigateParamsCapturer : public WebContentsObserver {
public:
// Observes navigation for the specified |node|.
explicit FrameNavigateParamsCapturer(FrameTreeNode* node)
: WebContentsObserver(
node->current_frame_host()->delegate()->GetAsWebContents()),
frame_tree_node_id_(node->frame_tree_node_id()),
navigations_remaining_(1),
wait_for_load_(true),
message_loop_runner_(new MessageLoopRunner) {}
void set_navigations_remaining(int count) {
navigations_remaining_ = count;
}
void set_wait_for_load(bool ignore) {
wait_for_load_ = ignore;
}
void Wait() {
message_loop_runner_->Run();
}
ui::PageTransition transition() {
EXPECT_EQ(1U, transitions_.size());
return transitions_[0];
}
NavigationType navigation_type() {
EXPECT_EQ(1U, navigation_types_.size());
return navigation_types_[0];
}
bool is_same_document() {
EXPECT_EQ(1U, is_same_documents_.size());
return is_same_documents_[0];
}
const std::vector<ui::PageTransition>& transitions() { return transitions_; }
const std::vector<GURL>& urls() { return urls_; }
const std::vector<NavigationType>& navigation_types() {
return navigation_types_;
}
const std::vector<bool>& is_same_documents() { return is_same_documents_; }
private:
void DidFinishNavigation(NavigationHandle* navigation_handle) override {
if (!navigation_handle->HasCommitted())
return;
if (navigation_handle->GetFrameTreeNodeId() != frame_tree_node_id_)
return;
--navigations_remaining_;
transitions_.push_back(navigation_handle->GetPageTransition());
urls_.push_back(navigation_handle->GetURL());
navigation_types_.push_back(
static_cast<NavigationHandleImpl*>(navigation_handle)
->navigation_type());
is_same_documents_.push_back(navigation_handle->IsSameDocument());
if (!navigations_remaining_ &&
(!web_contents()->IsLoading() || !wait_for_load_))
message_loop_runner_->Quit();
}
void DidStopLoading() override {
if (!navigations_remaining_)
message_loop_runner_->Quit();
}
// The id of the FrameTreeNode whose navigations to observe.
int frame_tree_node_id_;
// How many navigations remain to capture.
int navigations_remaining_;
// Whether to also wait for the load to complete.
bool wait_for_load_;
std::vector<ui::PageTransition> transitions_;
std::vector<GURL> urls_;
std::vector<NavigationType> navigation_types_;
std::vector<bool> is_same_documents_;
// The MessageLoopRunner used to spin the message loop.
scoped_refptr<MessageLoopRunner> message_loop_runner_;
};
// Test that going back in a subframe on a loadDataWithBaseURL page doesn't
// crash. See https://crbug.com/768575.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
NavigateBackInChildOfLoadDataWithBaseURL) {
GURL iframe_url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_links.html"));
const GURL base_url("http://baseurl");
const GURL history_url("http://historyurl");
std::string data =
"<html><body>"
" <p>"
" <iframe src=\"";
data += iframe_url.spec();
data +=
"\" />"
" </p>"
"</body></html>";
// Load data and commit.
TestNavigationObserver same_tab_observer(shell()->web_contents(), 1);
#if defined(OS_ANDROID)
shell()->LoadDataAsStringWithBaseURL(history_url, data, base_url);
#else
shell()->LoadDataWithBaseURL(history_url, data, base_url);
#endif
same_tab_observer.Wait();
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1u, root->child_count());
FrameTreeNode* child = root->child_at(0u);
{
TestNavigationObserver observer(shell()->web_contents(), 1);
std::string script = "document.getElementById('thelink').click()";
EXPECT_TRUE(ExecuteScript(child, script));
observer.Wait();
}
{
TestNavigationObserver observer(shell()->web_contents(), 1);
shell()->web_contents()->GetController().GoBack();
observer.Wait();
}
// Passes if renderer is still alive.
EXPECT_TRUE(ExecuteScript(shell(), "console.log('Success');"));
}
class LoadCommittedCapturer : public WebContentsObserver {
public:
// Observes the load commit for the specified |node|.
explicit LoadCommittedCapturer(FrameTreeNode* node)
: WebContentsObserver(
node->current_frame_host()->delegate()->GetAsWebContents()),
frame_tree_node_id_(node->frame_tree_node_id()),
message_loop_runner_(new MessageLoopRunner) {}
// Observes the load commit for the next created frame in the specified
// |web_contents|.
explicit LoadCommittedCapturer(WebContents* web_contents)
: WebContentsObserver(web_contents),
frame_tree_node_id_(0),
message_loop_runner_(new MessageLoopRunner) {}
void Wait() {
message_loop_runner_->Run();
}
ui::PageTransition transition_type() const {
return transition_type_;
}
private:
void RenderFrameCreated(RenderFrameHost* render_frame_host) override {
RenderFrameHostImpl* rfh =
static_cast<RenderFrameHostImpl*>(render_frame_host);
// Don't pay attention to pending delete RenderFrameHosts in the main frame,
// which might happen in a race if a cross-process navigation happens
// quickly.
if (!rfh->is_active()) {
DLOG(INFO) << "Skipping pending delete RFH: "
<< rfh->GetSiteInstance()->GetSiteURL();
return;
}
// If this object was not created with a specified frame tree node, then use
// the first created active RenderFrameHost. Once a node is selected, there
// shouldn't be any other frames being created.
int frame_tree_node_id = rfh->frame_tree_node()->frame_tree_node_id();
DCHECK(frame_tree_node_id_ == 0 ||
frame_tree_node_id_ == frame_tree_node_id);
frame_tree_node_id_ = frame_tree_node_id;
}
void DidFinishNavigation(NavigationHandle* navigation_handle) override {
if (!navigation_handle->HasCommitted())
return;
DCHECK_NE(0, frame_tree_node_id_);
if (navigation_handle->GetRenderFrameHost()->GetFrameTreeNodeId() !=
frame_tree_node_id_) {
return;
}
transition_type_ = navigation_handle->GetPageTransition();
if (!web_contents()->IsLoading())
message_loop_runner_->Quit();
}
void DidStopLoading() override { message_loop_runner_->Quit(); }
// The id of the FrameTreeNode whose navigations to observe.
int frame_tree_node_id_;
// The transition_type of the last navigation.
ui::PageTransition transition_type_;
// The MessageLoopRunner used to spin the message loop.
scoped_refptr<MessageLoopRunner> message_loop_runner_;
};
} // namespace
// Some pages create a popup, then write an iframe into it. This causes a
// subframe navigation without having any committed entry. Such navigations
// just get thrown on the ground, but we shouldn't crash.
//
// This test actually hits NAVIGATION_TYPE_NAV_IGNORE four times. Two of them,
// the initial window.open() and the iframe creation, don't try to create
// navigation entries, and the third and fourth, the new navigations, try to.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, SubframeOnEmptyPage) {
// Navigate to a page to force the renderer process to start.
EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
// Pop open a new window with no last committed entry.
ShellAddedObserver new_shell_observer;
{
std::string script = "window.open()";
EXPECT_TRUE(ExecuteScript(root, script));
}
Shell* new_shell = new_shell_observer.GetShell();
ASSERT_NE(new_shell->web_contents(), shell()->web_contents());
FrameTreeNode* new_root =
static_cast<WebContentsImpl*>(new_shell->web_contents())
->GetFrameTree()
->root();
EXPECT_FALSE(
new_shell->web_contents()->GetController().GetLastCommittedEntry());
// Make a new iframe in it.
NoNavigationsObserver observer(new_shell->web_contents());
{
LoadCommittedCapturer capturer(new_shell->web_contents());
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = 'data:text/html,<p>some page</p>';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(new_root, script));
capturer.Wait();
}
ASSERT_EQ(1U, new_root->child_count());
ASSERT_NE(nullptr, new_root->child_at(0));
// Navigate it cross-site.
GURL frame_url = embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/simple_page_2.html");
{
LoadCommittedCapturer capturer(new_shell->web_contents());
std::string script = "location.assign('" + frame_url.spec() + "')";
EXPECT_TRUE(ExecuteScript(new_root->child_at(0), script));
capturer.Wait();
}
// Success is not crashing, and not navigating.
EXPECT_EQ(nullptr,
new_shell->web_contents()->GetController().GetLastCommittedEntry());
// A nested iframe with a cross-site URL should also be able to commit.
GURL grandchild_url(embedded_test_server()->GetURL(
"bar.com", "/navigation_controller/simple_page_1.html"));
{
LoadCommittedCapturer capturer(new_shell->web_contents());
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + grandchild_url.spec() + "';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(new_root->child_at(0), script));
capturer.Wait();
}
ASSERT_EQ(1U, new_root->child_at(0)->child_count());
EXPECT_EQ(grandchild_url, new_root->child_at(0)->child_at(0)->current_url());
}
// Test that the renderer is not killed after an auto subframe navigation if the
// main frame appears to change its origin due to a document.write on an
// about:blank page. See https://crbug.com/613732.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
OriginChangeAfterDocumentWrite) {
GURL url1 = embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html");
EXPECT_TRUE(NavigateToURL(shell(), url1));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
// Pop open a new window to about:blank.
ShellAddedObserver new_shell_observer;
EXPECT_TRUE(ExecuteScript(root, "var w = window.open('about:blank')"));
Shell* new_shell = new_shell_observer.GetShell();
ASSERT_NE(new_shell->web_contents(), shell()->web_contents());
FrameTreeNode* new_root =
static_cast<WebContentsImpl*>(new_shell->web_contents())
->GetFrameTree()
->root();
GURL blank_url(url::kAboutBlankURL);
EXPECT_EQ(blank_url, new_root->current_url());
// Make a new iframe in it using document.write from the opener.
{
LoadCommittedCapturer capturer(new_shell->web_contents());
std::string script = "w.document.write("
"\"<iframe src='" + url1.spec() + "'></iframe>\");"
"w.document.close();";
EXPECT_TRUE(ExecuteScript(root->current_frame_host(), script));
capturer.Wait();
}
ASSERT_EQ(1U, new_root->child_count());
EXPECT_EQ(blank_url, new_root->current_url());
EXPECT_EQ(url1, new_root->child_at(0)->current_url());
// Navigate the subframe.
GURL url2 = embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html");
{
LoadCommittedCapturer capturer(new_root->child_at(0));
std::string script = "location.href = '" + url2.spec() + "';";
EXPECT_TRUE(ExecuteScript(new_root->child_at(0), script));
capturer.Wait();
}
EXPECT_EQ(blank_url, new_root->current_url());
EXPECT_EQ(url2, new_root->child_at(0)->current_url());
EXPECT_EQ(2, new_shell->web_contents()->GetController().GetEntryCount());
// Do a replace state in the main frame, which changes the URL from
// about:blank to the opener's origin, due to the document.write() call.
{
LoadCommittedCapturer capturer(new_root);
std::string script = "history.replaceState({}, 'foo', 'foo');";
EXPECT_TRUE(ExecuteScript(new_root, script));
capturer.Wait();
}
EXPECT_EQ(embedded_test_server()->GetURL("/navigation_controller/foo"),
new_root->current_url());
EXPECT_EQ(url2, new_root->child_at(0)->current_url());
// Go back in the subframe. Note that the main frame's URL looks like a
// cross-origin change from a web URL to about:blank.
{
TestNavigationObserver observer(new_shell->web_contents(), 1);
new_shell->web_contents()->GetController().GoBack();
observer.Wait();
}
EXPECT_TRUE(new_root->current_frame_host()->IsRenderFrameLive());
// Go forward in the subframe. Note that the main frame's URL looks like a
// cross-origin change from about:blank to a web URL.
{
TestNavigationObserver observer(new_shell->web_contents(), 1);
new_shell->web_contents()->GetController().GoForward();
observer.Wait();
}
EXPECT_TRUE(new_root->current_frame_host()->IsRenderFrameLive());
}
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
ErrorPageReplacement) {
NavigationController& controller = shell()->web_contents()->GetController();
GURL error_url(
net::URLRequestFailedJob::GetMockHttpUrl(net::ERR_CONNECTION_RESET));
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&net::URLRequestFailedJob::AddUrlHandler));
EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
EXPECT_EQ(1, controller.GetEntryCount());
FrameTreeNode* root =
static_cast<WebContentsImpl*>(shell()->web_contents())->
GetFrameTree()->root();
// Navigate to a page that fails to load. It must result in an error page, the
// NEW_PAGE navigation type, and an addition to the history list.
{
FrameNavigateParamsCapturer capturer(root);
NavigateFrameToURL(root, error_url);
capturer.Wait();
EXPECT_EQ(NAVIGATION_TYPE_NEW_PAGE, capturer.navigation_type());
NavigationEntry* entry = controller.GetLastCommittedEntry();
EXPECT_EQ(PAGE_TYPE_ERROR, entry->GetPageType());
EXPECT_EQ(2, controller.GetEntryCount());
}
// Navigate again to the page that fails to load. It must result in an error
// page, the EXISTING_PAGE navigation type, and no addition to the history
// list. We do not use SAME_PAGE here; that case only differs in that it
// clears the pending entry, and there is no pending entry after a load
// failure.
{
FrameNavigateParamsCapturer capturer(root);
NavigateFrameToURL(root, error_url);
capturer.Wait();
EXPECT_EQ(NAVIGATION_TYPE_EXISTING_PAGE, capturer.navigation_type());
NavigationEntry* entry = controller.GetLastCommittedEntry();
EXPECT_EQ(PAGE_TYPE_ERROR, entry->GetPageType());
EXPECT_EQ(2, controller.GetEntryCount());
}
// Make a new entry ...
EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
EXPECT_EQ(3, controller.GetEntryCount());
// ... and replace it with a failed load.
// TODO(creis): Make this be NEW_PAGE along with the other location.replace
// cases. There isn't much impact to having this be EXISTING_PAGE for now.
// See https://crbug.com/596707.
{
FrameNavigateParamsCapturer capturer(root);
RendererLocationReplace(shell(), error_url);
capturer.Wait();
EXPECT_EQ(NAVIGATION_TYPE_EXISTING_PAGE, capturer.navigation_type());
NavigationEntry* entry = controller.GetLastCommittedEntry();
EXPECT_EQ(PAGE_TYPE_ERROR, entry->GetPageType());
EXPECT_EQ(3, controller.GetEntryCount());
}
// Make a new web ui page to force a process swap ...
GURL web_ui_page(std::string(kChromeUIScheme) + "://" +
std::string(kChromeUIGpuHost));
EXPECT_TRUE(NavigateToURL(shell(), web_ui_page));
EXPECT_EQ(4, controller.GetEntryCount());
// ... and replace it with a failed load. (It is NEW_PAGE for the reason noted
// above.)
{
FrameNavigateParamsCapturer capturer(root);
RendererLocationReplace(shell(), error_url);
capturer.Wait();
EXPECT_EQ(NAVIGATION_TYPE_NEW_PAGE, capturer.navigation_type());
NavigationEntry* entry = controller.GetLastCommittedEntry();
EXPECT_EQ(PAGE_TYPE_ERROR, entry->GetPageType());
EXPECT_EQ(4, controller.GetEntryCount());
}
}
// Various tests for navigation type classifications. TODO(avi): It's rather
// bogus that the same info is in two different enums; http://crbug.com/453555.
// Verify that navigations for NAVIGATION_TYPE_NEW_PAGE are correctly
// classified.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
NavigationTypeClassification_NewPage) {
EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
FrameTreeNode* root =
static_cast<WebContentsImpl*>(shell()->web_contents())->
GetFrameTree()->root();
{
// Simple load.
FrameNavigateParamsCapturer capturer(root);
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_links.html"));
NavigateFrameToURL(root, frame_url);
capturer.Wait();
// TODO(avi,creis): Why is this (and quite a few others below) a "link"
// transition? Lots of these transitions should be cleaned up.
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_LINK));
EXPECT_EQ(NAVIGATION_TYPE_NEW_PAGE, capturer.navigation_type());
EXPECT_FALSE(capturer.is_same_document());
}
{
// Load via a fragment link click.
FrameNavigateParamsCapturer capturer(root);
std::string script = "document.getElementById('fraglink').click()";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_LINK));
EXPECT_EQ(NAVIGATION_TYPE_NEW_PAGE, capturer.navigation_type());
EXPECT_TRUE(capturer.is_same_document());
}
{
// Load via link click.
FrameNavigateParamsCapturer capturer(root);
std::string script = "document.getElementById('thelink').click()";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_LINK));
EXPECT_EQ(NAVIGATION_TYPE_NEW_PAGE, capturer.navigation_type());
EXPECT_FALSE(capturer.is_same_document());
}
{
// location.assign().
FrameNavigateParamsCapturer capturer(root);
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
std::string script = "location.assign('" + frame_url.spec() + "')";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(),
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
ui::PAGE_TRANSITION_CLIENT_REDIRECT)));
EXPECT_EQ(NAVIGATION_TYPE_NEW_PAGE, capturer.navigation_type());
EXPECT_FALSE(capturer.is_same_document());
}
{
// history.pushState().
FrameNavigateParamsCapturer capturer(root);
std::string script =
"history.pushState({}, 'page 1', 'simple_page_1.html')";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(),
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
ui::PAGE_TRANSITION_CLIENT_REDIRECT)));
EXPECT_EQ(NAVIGATION_TYPE_NEW_PAGE, capturer.navigation_type());
EXPECT_TRUE(capturer.is_same_document());
}
if (AreAllSitesIsolatedForTesting()) {
// Cross-process location.replace().
FrameNavigateParamsCapturer capturer(root);
GURL frame_url(embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/simple_page_1.html"));
std::string script = "location.replace('" + frame_url.spec() + "')";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(),
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
ui::PAGE_TRANSITION_CLIENT_REDIRECT)));
EXPECT_EQ(NAVIGATION_TYPE_NEW_PAGE, capturer.navigation_type());
EXPECT_FALSE(capturer.is_same_document());
}
}
// Verify that navigations for NAVIGATION_TYPE_EXISTING_PAGE are correctly
// classified.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
NavigationTypeClassification_ExistingPage) {
GURL url1(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url1));
GURL url2(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
EXPECT_TRUE(NavigateToURL(shell(), url2));
FrameTreeNode* root =
static_cast<WebContentsImpl*>(shell()->web_contents())->
GetFrameTree()->root();
{
// Back from the browser side.
FrameNavigateParamsCapturer capturer(root);
shell()->web_contents()->GetController().GoBack();
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(),
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FORWARD_BACK |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)));
EXPECT_EQ(NAVIGATION_TYPE_EXISTING_PAGE, capturer.navigation_type());
EXPECT_FALSE(capturer.is_same_document());
}
{
// Forward from the browser side.
FrameNavigateParamsCapturer capturer(root);
shell()->web_contents()->GetController().GoForward();
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(),
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FORWARD_BACK |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)));
EXPECT_EQ(NAVIGATION_TYPE_EXISTING_PAGE, capturer.navigation_type());
EXPECT_FALSE(capturer.is_same_document());
}
{
// Back from the renderer side.
FrameNavigateParamsCapturer capturer(root);
EXPECT_TRUE(ExecuteScript(root, "history.back()"));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(),
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FORWARD_BACK |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)));
EXPECT_EQ(NAVIGATION_TYPE_EXISTING_PAGE, capturer.navigation_type());
EXPECT_FALSE(capturer.is_same_document());
}
{
// Forward from the renderer side.
FrameNavigateParamsCapturer capturer(root);
EXPECT_TRUE(ExecuteScript(root, "history.forward()"));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(),
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FORWARD_BACK |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)));
EXPECT_EQ(NAVIGATION_TYPE_EXISTING_PAGE, capturer.navigation_type());
EXPECT_FALSE(capturer.is_same_document());
}
{
// Back from the renderer side via history.go().
FrameNavigateParamsCapturer capturer(root);
EXPECT_TRUE(ExecuteScript(root, "history.go(-1)"));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(),
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FORWARD_BACK |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)));
EXPECT_EQ(NAVIGATION_TYPE_EXISTING_PAGE, capturer.navigation_type());
EXPECT_FALSE(capturer.is_same_document());
}
{
// Forward from the renderer side via history.go().
FrameNavigateParamsCapturer capturer(root);
EXPECT_TRUE(ExecuteScript(root, "history.go(1)"));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(),
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FORWARD_BACK |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)));
EXPECT_EQ(NAVIGATION_TYPE_EXISTING_PAGE, capturer.navigation_type());
EXPECT_FALSE(capturer.is_same_document());
}
{
// Reload from the browser side.
FrameNavigateParamsCapturer capturer(root);
shell()->web_contents()->GetController().Reload(ReloadType::NORMAL, false);
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_RELOAD));
EXPECT_EQ(NAVIGATION_TYPE_EXISTING_PAGE, capturer.navigation_type());
EXPECT_FALSE(capturer.is_same_document());
}
{
// Reload from the renderer side.
FrameNavigateParamsCapturer capturer(root);
EXPECT_TRUE(ExecuteScript(root, "location.reload()"));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(),
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
ui::PAGE_TRANSITION_CLIENT_REDIRECT)));
EXPECT_EQ(NAVIGATION_TYPE_EXISTING_PAGE, capturer.navigation_type());
EXPECT_FALSE(capturer.is_same_document());
}
{
// location.replace().
// TODO(creis): Change this to be NEW_PAGE with replacement in
// https://crbug.com/596707.
FrameNavigateParamsCapturer capturer(root);
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
std::string script = "location.replace('" + frame_url.spec() + "')";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(),
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
ui::PAGE_TRANSITION_CLIENT_REDIRECT)));
EXPECT_EQ(NAVIGATION_TYPE_EXISTING_PAGE, capturer.navigation_type());
EXPECT_FALSE(capturer.is_same_document());
}
// Now, various same document navigations.
{
// history.replaceState().
FrameNavigateParamsCapturer capturer(root);
std::string script =
"history.replaceState({}, 'page 2', 'simple_page_2.html')";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(),
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
ui::PAGE_TRANSITION_CLIENT_REDIRECT)));
EXPECT_EQ(NAVIGATION_TYPE_EXISTING_PAGE, capturer.navigation_type());
EXPECT_TRUE(capturer.is_same_document());
}
// Back and forward across a fragment navigation.
GURL url_links(embedded_test_server()->GetURL(
"/navigation_controller/page_with_links.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_links));
std::string script = "document.getElementById('fraglink').click()";
EXPECT_TRUE(ExecuteScript(root, script));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
{
// Back.
FrameNavigateParamsCapturer capturer(root);
shell()->web_contents()->GetController().GoBack();
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(),
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FORWARD_BACK |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)));
EXPECT_EQ(NAVIGATION_TYPE_EXISTING_PAGE, capturer.navigation_type());
EXPECT_TRUE(capturer.is_same_document());
}
{
// Forward.
FrameNavigateParamsCapturer capturer(root);
shell()->web_contents()->GetController().GoForward();
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(),
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
ui::PAGE_TRANSITION_FORWARD_BACK)));
EXPECT_EQ(NAVIGATION_TYPE_EXISTING_PAGE, capturer.navigation_type());
EXPECT_TRUE(capturer.is_same_document());
}
// Back and forward across a pushState-created navigation.
EXPECT_TRUE(NavigateToURL(shell(), url1));
script = "history.pushState({}, 'page 2', 'simple_page_2.html')";
EXPECT_TRUE(ExecuteScript(root, script));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
{
// Back.
FrameNavigateParamsCapturer capturer(root);
shell()->web_contents()->GetController().GoBack();
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(),
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FORWARD_BACK |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)));
EXPECT_EQ(NAVIGATION_TYPE_EXISTING_PAGE, capturer.navigation_type());
EXPECT_TRUE(capturer.is_same_document());
}
{
// Forward.
FrameNavigateParamsCapturer capturer(root);
shell()->web_contents()->GetController().GoForward();
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(),
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
ui::PAGE_TRANSITION_FORWARD_BACK)));
EXPECT_EQ(NAVIGATION_TYPE_EXISTING_PAGE, capturer.navigation_type());
EXPECT_TRUE(capturer.is_same_document());
}
}
// Verify that navigations for NAVIGATION_TYPE_SAME_PAGE are correctly
// classified.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
NavigationTypeClassification_SamePage) {
GURL url1(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url1));
FrameTreeNode* root =
static_cast<WebContentsImpl*>(shell()->web_contents())->
GetFrameTree()->root();
{
// Simple load.
FrameNavigateParamsCapturer capturer(root);
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
NavigateFrameToURL(root, frame_url);
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_LINK));
EXPECT_EQ(NAVIGATION_TYPE_SAME_PAGE, capturer.navigation_type());
}
}
// Verify that reloading a page with url anchor scrolls to correct position.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, ReloadWithUrlAnchor) {
GURL url1(embedded_test_server()->GetURL(
"/navigation_controller/reload-with-url-anchor.html#d2"));
EXPECT_TRUE(NavigateToURL(shell(), url1));
std::string script =
"domAutomationController.send(document.getElementById('div').scrollTop)";
int value = 0;
EXPECT_TRUE(ExecuteScriptAndExtractInt(shell(), script, &value));
EXPECT_EQ(100, value);
// Reload.
ReloadBlockUntilNavigationsComplete(shell(), 1);
EXPECT_TRUE(ExecuteScriptAndExtractInt(shell(), script, &value));
EXPECT_EQ(100, value);
}
// Verify that reloading a page with url anchor and scroll scrolls to correct
// position.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
ReloadWithUrlAnchorAndScroll) {
GURL url1(embedded_test_server()->GetURL(
"/navigation_controller/reload-with-url-anchor.html#d2"));
EXPECT_TRUE(NavigateToURL(shell(), url1));
std::string script_scroll_down = "window.scroll(0, 10)";
EXPECT_TRUE(ExecuteScript(shell(), script_scroll_down));
std::string get_div_scroll_top =
"domAutomationController.send(document.getElementById('div').scrollTop)";
std::string get_window_scroll_y =
"domAutomationController.send(window.scrollY)";
int div_scroll_top = 0;
int window_scroll_y = 0;
EXPECT_TRUE(
ExecuteScriptAndExtractInt(shell(), get_div_scroll_top, &div_scroll_top));
EXPECT_TRUE(ExecuteScriptAndExtractInt(shell(), get_window_scroll_y,
&window_scroll_y));
EXPECT_EQ(100, div_scroll_top);
EXPECT_EQ(10, window_scroll_y);
// Reload.
ReloadBlockUntilNavigationsComplete(shell(), 1);
EXPECT_TRUE(
ExecuteScriptAndExtractInt(shell(), get_div_scroll_top, &div_scroll_top));
EXPECT_TRUE(ExecuteScriptAndExtractInt(shell(), get_window_scroll_y,
&window_scroll_y));
EXPECT_EQ(100, div_scroll_top);
EXPECT_EQ(10, window_scroll_y);
}
// Verify that empty GURL navigations are not classified as SAME_PAGE.
// See https://crbug.com/534980.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
NavigationTypeClassification_EmptyGURL) {
GURL url1(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url1));
FrameTreeNode* root =
static_cast<WebContentsImpl*>(shell()->web_contents())->
GetFrameTree()->root();
{
// Load an (invalid) empty GURL. Blink will treat this as an inert commit,
// but we don't want it to show up as SAME_PAGE.
FrameNavigateParamsCapturer capturer(root);
NavigateFrameToURL(root, GURL());
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_LINK));
EXPECT_EQ(NAVIGATION_TYPE_NEW_PAGE, capturer.navigation_type());
}
}
// Verify that navigations for NAVIGATION_TYPE_NEW_SUBFRAME and
// NAVIGATION_TYPE_AUTO_SUBFRAME are properly classified.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
NavigationTypeClassification_NewAndAutoSubframe) {
GURL main_url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_iframe.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<WebContentsImpl*>(shell()->web_contents())->
GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
ASSERT_NE(nullptr, root->child_at(0));
{
// Initial load.
LoadCommittedCapturer capturer(root->child_at(0));
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
NavigateFrameToURL(root->child_at(0), frame_url);
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
{
// Simple load.
FrameNavigateParamsCapturer capturer(root->child_at(0));
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
NavigateFrameToURL(root->child_at(0), frame_url);
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_MANUAL_SUBFRAME));
EXPECT_EQ(NAVIGATION_TYPE_NEW_SUBFRAME, capturer.navigation_type());
}
{
// Back.
FrameNavigateParamsCapturer capturer(root->child_at(0));
shell()->web_contents()->GetController().GoBack();
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
EXPECT_EQ(NAVIGATION_TYPE_AUTO_SUBFRAME, capturer.navigation_type());
}
{
// Forward.
FrameNavigateParamsCapturer capturer(root->child_at(0));
shell()->web_contents()->GetController().GoForward();
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
EXPECT_EQ(NAVIGATION_TYPE_AUTO_SUBFRAME, capturer.navigation_type());
}
{
// Simple load.
FrameNavigateParamsCapturer capturer(root->child_at(0));
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_links.html"));
NavigateFrameToURL(root->child_at(0), frame_url);
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_MANUAL_SUBFRAME));
EXPECT_EQ(NAVIGATION_TYPE_NEW_SUBFRAME, capturer.navigation_type());
}
{
// Load via a fragment link click.
FrameNavigateParamsCapturer capturer(root->child_at(0));
std::string script = "document.getElementById('fraglink').click()";
EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_MANUAL_SUBFRAME));
EXPECT_EQ(NAVIGATION_TYPE_NEW_SUBFRAME, capturer.navigation_type());
}
{
// location.assign().
FrameNavigateParamsCapturer capturer(root->child_at(0));
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
std::string script = "location.assign('" + frame_url.spec() + "')";
EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_MANUAL_SUBFRAME));
EXPECT_EQ(NAVIGATION_TYPE_NEW_SUBFRAME, capturer.navigation_type());
}
{
// location.replace().
LoadCommittedCapturer capturer(root->child_at(0));
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
std::string script = "location.replace('" + frame_url.spec() + "')";
EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
{
// history.pushState().
FrameNavigateParamsCapturer capturer(root->child_at(0));
std::string script =
"history.pushState({}, 'page 1', 'simple_page_1.html')";
EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_MANUAL_SUBFRAME));
EXPECT_EQ(NAVIGATION_TYPE_NEW_SUBFRAME, capturer.navigation_type());
}
{
// history.replaceState().
LoadCommittedCapturer capturer(root->child_at(0));
std::string script =
"history.replaceState({}, 'page 2', 'simple_page_2.html')";
EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
{
// Reload.
LoadCommittedCapturer capturer(root->child_at(0));
EXPECT_TRUE(ExecuteScript(root->child_at(0), "location.reload()"));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
{
// Create an iframe.
LoadCommittedCapturer capturer(shell()->web_contents());
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + frame_url.spec() + "';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
}
// Verify that navigations caused by client-side redirects are correctly
// classified.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
NavigationTypeClassification_ClientSideRedirect) {
EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
FrameTreeNode* root =
static_cast<WebContentsImpl*>(shell()->web_contents())->
GetFrameTree()->root();
{
// Load the redirecting page.
FrameNavigateParamsCapturer capturer(root);
capturer.set_navigations_remaining(2);
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/client_redirect.html"));
NavigateFrameToURL(root, frame_url);
capturer.Wait();
ASSERT_EQ(2U, capturer.transitions().size());
ASSERT_EQ(2U, capturer.navigation_types().size());
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transitions()[0], ui::PAGE_TRANSITION_LINK));
EXPECT_EQ(NAVIGATION_TYPE_NEW_PAGE, capturer.navigation_types()[0]);
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transitions()[1],
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
ui::PAGE_TRANSITION_CLIENT_REDIRECT)));
EXPECT_EQ(NAVIGATION_TYPE_EXISTING_PAGE, capturer.navigation_types()[1]);
}
}
// Verify that the LoadCommittedDetails::is_same_document value is properly set
// for non same document navigations.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
LoadCommittedDetails_IsSameDocument) {
GURL links_url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_links.html"));
EXPECT_TRUE(NavigateToURL(shell(), links_url));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
FrameTreeNode* root =
static_cast<WebContentsImpl*>(shell()->web_contents())->
GetFrameTree()->root();
{
// Do a fragment link click.
FrameNavigateParamsCapturer capturer(root);
std::string script = "document.getElementById('fraglink').click()";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_LINK));
EXPECT_EQ(NAVIGATION_TYPE_NEW_PAGE, capturer.navigation_type());
EXPECT_TRUE(capturer.is_same_document());
}
{
// Do a non-fragment link click.
FrameNavigateParamsCapturer capturer(root);
std::string script = "document.getElementById('thelink').click()";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_LINK));
EXPECT_EQ(NAVIGATION_TYPE_NEW_PAGE, capturer.navigation_type());
EXPECT_FALSE(capturer.is_same_document());
}
// Second verse, same as the first. (But in a subframe.)
GURL iframe_url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_iframe.html"));
EXPECT_TRUE(NavigateToURL(shell(), iframe_url));
root = static_cast<WebContentsImpl*>(shell()->web_contents())->
GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
ASSERT_NE(nullptr, root->child_at(0));
NavigateFrameToURL(root->child_at(0), links_url);
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
{
// Do a fragment link click.
FrameNavigateParamsCapturer capturer(root->child_at(0));
std::string script = "document.getElementById('fraglink').click()";
EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_MANUAL_SUBFRAME));
EXPECT_EQ(NAVIGATION_TYPE_NEW_SUBFRAME, capturer.navigation_type());
EXPECT_TRUE(capturer.is_same_document());
}
{
// Do a non-fragment link click.
FrameNavigateParamsCapturer capturer(root->child_at(0));
std::string script = "document.getElementById('thelink').click()";
EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_MANUAL_SUBFRAME));
EXPECT_EQ(NAVIGATION_TYPE_NEW_SUBFRAME, capturer.navigation_type());
EXPECT_FALSE(capturer.is_same_document());
}
}
// Verify the tree of FrameNavigationEntries after initial about:blank commits
// in subframes, which should not count as real committed loads.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_BlankAutoSubframe) {
GURL about_blank_url(url::kAboutBlankURL);
GURL main_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
const NavigationControllerImpl& controller =
static_cast<const NavigationControllerImpl&>(
shell()->web_contents()->GetController());
FrameTreeNode* root =
static_cast<WebContentsImpl*>(shell()->web_contents())->
GetFrameTree()->root();
// 1. Create a iframe with no URL.
{
LoadCommittedCapturer capturer(shell()->web_contents());
std::string script = "var iframe = document.createElement('iframe');"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
// Check last committed NavigationEntry.
EXPECT_EQ(1, controller.GetEntryCount());
NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
EXPECT_EQ(main_url, entry->GetURL());
FrameNavigationEntry* root_entry = entry->root_node()->frame_entry.get();
EXPECT_EQ(main_url, root_entry->url());
// Verify subframe entries. The entry should now have one blank subframe
// FrameNavigationEntry, but this does not count as committing a real load.
ASSERT_EQ(1U, entry->root_node()->children.size());
FrameNavigationEntry* frame_entry =
entry->root_node()->children[0]->frame_entry.get();
EXPECT_EQ(about_blank_url, frame_entry->url());
EXPECT_FALSE(root->child_at(0)->has_committed_real_load());
// 1a. A nested iframe with no URL should also create a subframe entry but not
// count as a real load.
{
LoadCommittedCapturer capturer(shell()->web_contents());
std::string script = "var iframe = document.createElement('iframe');"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
// Verify subframe entries. The nested entry should have one blank subframe
// FrameNavigationEntry, but this does not count as committing a real load.
ASSERT_EQ(1U, entry->root_node()->children[0]->children.size());
frame_entry = entry->root_node()->children[0]->children[0]->frame_entry.get();
EXPECT_EQ(about_blank_url, frame_entry->url());
EXPECT_FALSE(root->child_at(0)->child_at(0)->has_committed_real_load());
// 2. Create another iframe with an explicit about:blank URL.
{
LoadCommittedCapturer capturer(shell()->web_contents());
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = 'about:blank';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
// Check last committed NavigationEntry.
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(entry, controller.GetLastCommittedEntry());
// Verify subframe entries. The new entry should have one blank subframe
// FrameNavigationEntry, but this does not count as committing a real load.
ASSERT_EQ(2U, entry->root_node()->children.size());
frame_entry = entry->root_node()->children[1]->frame_entry.get();
EXPECT_EQ(about_blank_url, frame_entry->url());
EXPECT_FALSE(root->child_at(1)->has_committed_real_load());
// 3. A real same-site navigation in the nested iframe should be AUTO.
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
{
LoadCommittedCapturer capturer(root->child_at(0)->child_at(0));
std::string script = "var frames = document.getElementsByTagName('iframe');"
"frames[0].src = '" + frame_url.spec() + "';";
EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
// Check last committed NavigationEntry. It should have replaced the previous
// frame entry in the original NavigationEntry.
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(entry, controller.GetLastCommittedEntry());
// The entry should still have one nested subframe FrameNavigationEntry.
ASSERT_EQ(1U, entry->root_node()->children[0]->children.size());
frame_entry = entry->root_node()->children[0]->children[0]->frame_entry.get();
EXPECT_EQ(frame_url, frame_entry->url());
EXPECT_FALSE(root->child_at(0)->has_committed_real_load());
EXPECT_TRUE(root->child_at(0)->child_at(0)->has_committed_real_load());
EXPECT_FALSE(root->child_at(1)->has_committed_real_load());
// 4. A real cross-site navigation in the second iframe should be AUTO.
GURL foo_url(embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/simple_page_2.html"));
{
LoadCommittedCapturer capturer(root->child_at(1));
std::string script = "var frames = document.getElementsByTagName('iframe');"
"frames[1].src = '" + foo_url.spec() + "';";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
// Check last committed NavigationEntry.
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(entry, controller.GetLastCommittedEntry());
// The entry should still have two subframe FrameNavigationEntries.
ASSERT_EQ(2U, entry->root_node()->children.size());
frame_entry = entry->root_node()->children[1]->frame_entry.get();
EXPECT_EQ(foo_url, frame_entry->url());
EXPECT_FALSE(root->child_at(0)->has_committed_real_load());
EXPECT_TRUE(root->child_at(0)->child_at(0)->has_committed_real_load());
EXPECT_TRUE(root->child_at(1)->has_committed_real_load());
// 5. A new navigation to about:blank in the nested frame should count as a
// real load, since that frame has already committed a real load and this is
// not the initial blank page.
{
LoadCommittedCapturer capturer(root->child_at(0)->child_at(0));
std::string script = "var frames = document.getElementsByTagName('iframe');"
"frames[0].src = 'about:blank';";
EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_MANUAL_SUBFRAME));
}
// This should have created a new NavigationEntry.
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_NE(entry, controller.GetLastCommittedEntry());
NavigationEntryImpl* entry2 = controller.GetLastCommittedEntry();
// Verify subframe entries.
ASSERT_EQ(2U, entry->root_node()->children.size());
frame_entry =
entry2->root_node()->children[0]->children[0]->frame_entry.get();
EXPECT_EQ(about_blank_url, frame_entry->url());
EXPECT_FALSE(root->child_at(0)->has_committed_real_load());
EXPECT_TRUE(root->child_at(0)->child_at(0)->has_committed_real_load());
EXPECT_TRUE(root->child_at(1)->has_committed_real_load());
// Check the end result of the frame tree.
if (AreAllSitesIsolatedForTesting()) {
FrameTreeVisualizer visualizer;
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" |--Site A ------- proxies for B\n"
" | +--Site A -- proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://127.0.0.1/\n"
" B = http://foo.com/",
visualizer.DepictFrameTree(root));
}
}
// Verify the tree of FrameNavigationEntries when a nested iframe commits inside
// the initial blank page of a loading iframe. Prevents regression of
// https://crbug.com/600743.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_SlowNestedAutoSubframe) {
GURL main_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
// 1. Create a iframe with a URL that doesn't commit.
GURL slow_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
TestNavigationManager subframe_delayer(shell()->web_contents(), slow_url);
{
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + slow_url.spec() + "';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root, script));
}
EXPECT_TRUE(subframe_delayer.WaitForRequestStart());
// Stop the request so that we can wait for load stop below, without ending up
// with a commit for this frame.
shell()->web_contents()->Stop();
// 2. A nested iframe with a cross-site URL should be able to commit.
GURL foo_url(embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/simple_page_1.html"));
{
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + foo_url.spec() + "';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
WaitForLoadStopWithoutSuccessCheck(shell()->web_contents());
}
// TODO(creis): Check subframe entries once we create them in this case.
// See https://crbug.com/608402.
EXPECT_EQ(foo_url, root->child_at(0)->child_at(0)->current_url());
}
// Verify the tree of FrameNavigationEntries when a nested iframe commits inside
// the initial blank page of an iframe with no committed entry. Prevents
// regression of https://crbug.com/600743.
// Flaky test: See https://crbug.com/610801
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
DISABLED_FrameNavigationEntry_NoCommitNestedAutoSubframe) {
GURL main_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
// 1. Create a iframe with a URL that doesn't commit.
GURL no_commit_url(embedded_test_server()->GetURL("/nocontent"));
{
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + no_commit_url.spec() + "';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root, script));
}
EXPECT_EQ(GURL(), root->child_at(0)->current_url());
// 2. A nested iframe with a cross-site URL should be able to commit.
GURL foo_url(embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/simple_page_1.html"));
{
LoadCommittedCapturer capturer(shell()->web_contents());
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + foo_url.spec() + "';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
// TODO(creis): Check subframe entries once we create them in this case.
// See https://crbug.com/608402.
EXPECT_EQ(foo_url, root->child_at(0)->child_at(0)->current_url());
}
// Verify the tree of FrameNavigationEntries when a nested iframe commits after
// doing same document back navigation, in which case its parent might not have
// been in the NavigationEntry. Prevents regression of
// https://crbug.com/600743.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_BackNestedAutoSubframe) {
GURL main_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
// 1. Perform same document navigation.
{
FrameNavigateParamsCapturer capturer(root);
std::string script = "history.pushState({}, 'foo', 'foo')";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_EQ(NAVIGATION_TYPE_NEW_PAGE, capturer.navigation_type());
EXPECT_TRUE(capturer.is_same_document());
}
// 2. Create an iframe.
GURL child_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
{
LoadCommittedCapturer capturer(shell()->web_contents());
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + child_url.spec() + "';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
// 3. Perform back same document navigation.
{
TestNavigationObserver back_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoBack();
back_load_observer.Wait();
}
// 4. A nested iframe with a cross-site URL should be able to commit.
GURL grandchild_url(embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/simple_page_1.html"));
{
LoadCommittedCapturer capturer(shell()->web_contents());
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + grandchild_url.spec() + "';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
// TODO(creis): Check subframe entries once we create them in this case.
// See https://crbug.com/608402.
EXPECT_EQ(grandchild_url, root->child_at(0)->child_at(0)->current_url());
}
// Verify the tree of FrameNavigationEntries when a nested iframe commits after
// its parent changes its name, in which case we might not find the parent
// FrameNavigationEntry. Prevents regression of https://crbug.com/600743.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_RenameNestedAutoSubframe) {
GURL main_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
// 1. Create an iframe.
GURL child_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
{
LoadCommittedCapturer capturer(shell()->web_contents());
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + child_url.spec() + "';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
// 2. Change the iframe's name.
EXPECT_TRUE(ExecuteScript(root->child_at(0), "window.name = 'foo';"));
// 3. A nested iframe with a cross-site URL should be able to commit.
GURL bar_url(embedded_test_server()->GetURL(
"bar.com", "/navigation_controller/simple_page_1.html"));
{
LoadCommittedCapturer capturer(shell()->web_contents());
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + bar_url.spec() + "';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
// TODO(creis): Check subframe entries once we create them in this case.
// See https://crbug.com/608402.
EXPECT_EQ(bar_url, root->child_at(0)->child_at(0)->current_url());
}
// Verify the tree of FrameNavigationEntries after NAVIGATION_TYPE_AUTO_SUBFRAME
// commits.
// TODO(creis): Test updating entries for history auto subframe navigations.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_AutoSubframe) {
GURL main_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
const NavigationControllerImpl& controller =
static_cast<const NavigationControllerImpl&>(
shell()->web_contents()->GetController());
FrameTreeNode* root =
static_cast<WebContentsImpl*>(shell()->web_contents())->
GetFrameTree()->root();
// 1. Create a same-site iframe.
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
{
LoadCommittedCapturer capturer(shell()->web_contents());
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + frame_url.spec() + "';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
// Check last committed NavigationEntry.
EXPECT_EQ(1, controller.GetEntryCount());
NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
EXPECT_EQ(main_url, entry->GetURL());
FrameNavigationEntry* root_entry = entry->root_node()->frame_entry.get();
EXPECT_EQ(main_url, root_entry->url());
EXPECT_FALSE(controller.GetPendingEntry());
// The entry should now have a subframe FrameNavigationEntry.
ASSERT_EQ(1U, entry->root_node()->children.size());
FrameNavigationEntry* frame_entry =
entry->root_node()->children[0]->frame_entry.get();
EXPECT_EQ(frame_url, frame_entry->url());
EXPECT_TRUE(root->child_at(0)->has_committed_real_load());
// 2. Create a second, initially cross-site iframe.
GURL foo_url(embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/simple_page_1.html"));
{
LoadCommittedCapturer capturer(shell()->web_contents());
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + foo_url.spec() + "';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
// The last committed NavigationEntry shouldn't have changed.
EXPECT_EQ(1, controller.GetEntryCount());
entry = controller.GetLastCommittedEntry();
EXPECT_EQ(main_url, entry->GetURL());
root_entry = entry->root_node()->frame_entry.get();
EXPECT_EQ(main_url, root_entry->url());
EXPECT_FALSE(controller.GetPendingEntry());
// The entry should now have 2 subframe FrameNavigationEntries.
ASSERT_EQ(2U, entry->root_node()->children.size());
frame_entry = entry->root_node()->children[1]->frame_entry.get();
EXPECT_EQ(foo_url, frame_entry->url());
EXPECT_TRUE(root->child_at(1)->has_committed_real_load());
// 3. Create a nested iframe in the second subframe.
{
LoadCommittedCapturer capturer(shell()->web_contents());
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + foo_url.spec() + "';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root->child_at(1), script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
// The last committed NavigationEntry shouldn't have changed.
EXPECT_EQ(1, controller.GetEntryCount());
entry = controller.GetLastCommittedEntry();
EXPECT_EQ(main_url, entry->GetURL());
root_entry = entry->root_node()->frame_entry.get();
EXPECT_EQ(main_url, root_entry->url());
// The entry should now have 2 subframe FrameNavigationEntries.
ASSERT_EQ(2U, entry->root_node()->children.size());
ASSERT_EQ(1U, entry->root_node()->children[1]->children.size());
frame_entry = entry->root_node()->children[1]->children[0]->frame_entry.get();
EXPECT_EQ(foo_url, frame_entry->url());
// 4. Create a third iframe on the same site as the second. This ensures that
// the commit type is correct even when the subframe process already exists.
{
LoadCommittedCapturer capturer(shell()->web_contents());
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + foo_url.spec() + "';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
// The last committed NavigationEntry shouldn't have changed.
EXPECT_EQ(1, controller.GetEntryCount());
entry = controller.GetLastCommittedEntry();
EXPECT_EQ(main_url, entry->GetURL());
root_entry = entry->root_node()->frame_entry.get();
EXPECT_EQ(main_url, root_entry->url());
// The entry should now have 3 subframe FrameNavigationEntries.
ASSERT_EQ(3U, entry->root_node()->children.size());
frame_entry = entry->root_node()->children[2]->frame_entry.get();
EXPECT_EQ(foo_url, frame_entry->url());
// 5. Create a nested iframe on the original site (A-B-A).
{
LoadCommittedCapturer capturer(shell()->web_contents());
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + frame_url.spec() + "';"
"document.body.appendChild(iframe);";
FrameTreeNode* child = root->child_at(2);
EXPECT_TRUE(ExecuteScript(child, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
// The last committed NavigationEntry shouldn't have changed.
EXPECT_EQ(1, controller.GetEntryCount());
entry = controller.GetLastCommittedEntry();
EXPECT_EQ(main_url, entry->GetURL());
root_entry = entry->root_node()->frame_entry.get();
EXPECT_EQ(main_url, root_entry->url());
// There should be a corresponding FrameNavigationEntry.
ASSERT_EQ(1U, entry->root_node()->children[2]->children.size());
frame_entry = entry->root_node()->children[2]->children[0]->frame_entry.get();
EXPECT_EQ(frame_url, frame_entry->url());
// Check the end result of the frame tree.
if (AreAllSitesIsolatedForTesting()) {
FrameTreeVisualizer visualizer;
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" |--Site A ------- proxies for B\n"
" |--Site B ------- proxies for A\n"
" | +--Site B -- proxies for A\n"
" +--Site B ------- proxies for A\n"
" +--Site A -- proxies for B\n"
"Where A = http://127.0.0.1/\n"
" B = http://foo.com/",
visualizer.DepictFrameTree(root));
}
}
// Verify the tree of FrameNavigationEntries after NAVIGATION_TYPE_NEW_SUBFRAME
// commits.
// Disabled due to flakes; see https://crbug.com/646836.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_NewSubframe) {
GURL main_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
const NavigationControllerImpl& controller =
static_cast<const NavigationControllerImpl&>(
shell()->web_contents()->GetController());
FrameTreeNode* root =
static_cast<WebContentsImpl*>(shell()->web_contents())->
GetFrameTree()->root();
// 1. Create a same-site iframe.
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
{
LoadCommittedCapturer capturer(shell()->web_contents());
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + frame_url.spec() + "';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
}
NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
// 2. Navigate in the subframe same-site.
GURL frame_url2(embedded_test_server()->GetURL(
"/navigation_controller/page_with_links.html"));
{
FrameNavigateParamsCapturer capturer(root->child_at(0));
NavigateFrameToURL(root->child_at(0), frame_url2);
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_MANUAL_SUBFRAME));
EXPECT_EQ(NAVIGATION_TYPE_NEW_SUBFRAME, capturer.navigation_type());
}
// We should have created a new NavigationEntry with the same main frame URL.
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
NavigationEntryImpl* entry2 = controller.GetLastCommittedEntry();
EXPECT_NE(entry, entry2);
EXPECT_EQ(main_url, entry2->GetURL());
FrameNavigationEntry* root_entry2 = entry2->root_node()->frame_entry.get();
EXPECT_EQ(main_url, root_entry2->url());
// The entry should have a new FrameNavigationEntries for the subframe.
ASSERT_EQ(1U, entry2->root_node()->children.size());
EXPECT_EQ(frame_url2, entry2->root_node()->children[0]->frame_entry->url());
// 3. Create a second, initially cross-site iframe.
GURL foo_url(embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/simple_page_1.html"));
{
LoadCommittedCapturer capturer(shell()->web_contents());
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + foo_url.spec() + "';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
}
// 4. Create a nested same-site iframe in the second subframe, wait for it to
// commit, then navigate it again cross-site.
{
LoadCommittedCapturer capturer(shell()->web_contents());
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + foo_url.spec() + "';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root->child_at(1), script));
capturer.Wait();
}
GURL bar_url(embedded_test_server()->GetURL(
"bar.com", "/navigation_controller/simple_page_1.html"));
{
FrameNavigateParamsCapturer capturer(root->child_at(1)->child_at(0));
RenderFrameDeletedObserver deleted_observer(
root->child_at(1)->child_at(0)->current_frame_host());
NavigateFrameToURL(root->child_at(1)->child_at(0), bar_url);
// Wait for the RenderFrame to go away, if this will be cross-process.
if (AreAllSitesIsolatedForTesting())
deleted_observer.WaitUntilDeleted();
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_MANUAL_SUBFRAME));
EXPECT_EQ(NAVIGATION_TYPE_NEW_SUBFRAME, capturer.navigation_type());
}
// We should have created a new NavigationEntry with the same main frame URL.
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
NavigationEntryImpl* entry3 = controller.GetLastCommittedEntry();
EXPECT_NE(entry, entry3);
EXPECT_EQ(main_url, entry3->GetURL());
FrameNavigationEntry* root_entry3 = entry3->root_node()->frame_entry.get();
EXPECT_EQ(main_url, root_entry3->url());
// The entry should still have FrameNavigationEntries for all 3 subframes.
ASSERT_EQ(2U, entry3->root_node()->children.size());
EXPECT_EQ(frame_url2, entry3->root_node()->children[0]->frame_entry->url());
EXPECT_EQ(foo_url, entry3->root_node()->children[1]->frame_entry->url());
ASSERT_EQ(1U, entry3->root_node()->children[1]->children.size());
EXPECT_EQ(bar_url,
entry3->root_node()->children[1]->children[0]->frame_entry->url());
// 6. Navigate the second subframe cross-site, clearing its existing subtree.
GURL baz_url(embedded_test_server()->GetURL(
"baz.com", "/navigation_controller/simple_page_1.html"));
{
FrameNavigateParamsCapturer capturer(root->child_at(1));
RenderFrameDeletedObserver deleted_observer(
root->child_at(1)->current_frame_host());
std::string script = "var frames = document.getElementsByTagName('iframe');"
"frames[1].src = '" + baz_url.spec() + "';";
EXPECT_TRUE(ExecuteScript(root, script));
// Wait for the RenderFrame to go away, if this will be cross-process.
if (AreAllSitesIsolatedForTesting())
deleted_observer.WaitUntilDeleted();
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_MANUAL_SUBFRAME));
EXPECT_EQ(NAVIGATION_TYPE_NEW_SUBFRAME, capturer.navigation_type());
}
// We should have created a new NavigationEntry with the same main frame URL.
EXPECT_EQ(4, controller.GetEntryCount());
EXPECT_EQ(3, controller.GetLastCommittedEntryIndex());
NavigationEntryImpl* entry4 = controller.GetLastCommittedEntry();
EXPECT_NE(entry, entry4);
EXPECT_EQ(main_url, entry4->GetURL());
FrameNavigationEntry* root_entry4 = entry4->root_node()->frame_entry.get();
EXPECT_EQ(main_url, root_entry4->url());
// The entry should still have FrameNavigationEntries for all 3 subframes.
ASSERT_EQ(2U, entry4->root_node()->children.size());
EXPECT_EQ(frame_url2, entry4->root_node()->children[0]->frame_entry->url());
EXPECT_EQ(baz_url, entry4->root_node()->children[1]->frame_entry->url());
ASSERT_EQ(0U, entry4->root_node()->children[1]->children.size());
// Check the end result of the frame tree.
if (AreAllSitesIsolatedForTesting()) {
FrameTreeVisualizer visualizer;
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" |--Site A ------- proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://127.0.0.1/\n"
" B = http://baz.com/",
visualizer.DepictFrameTree(root));
}
}
// Ensure that we don't crash when navigating subframes after same document
// navigations. See https://crbug.com/522193.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_SubframeAfterSameDocument) {
// 1. Start on a page with a subframe.
GURL main_url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_iframe.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
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_NE(nullptr, root->child_at(0));
// Navigate to a real page in the subframe, so that the next navigation will
// be MANUAL_SUBFRAME.
GURL subframe_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
{
LoadCommittedCapturer capturer(root->child_at(0));
NavigateFrameToURL(root->child_at(0), subframe_url);
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
// 2. Same document navigation in the main frame.
std::string push_script = "history.pushState({}, 'page 2', 'page_2.html')";
EXPECT_TRUE(ExecuteScript(root, push_script));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
// The entry should have a FrameNavigationEntry for the subframe.
NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
ASSERT_EQ(1U, entry->root_node()->children.size());
EXPECT_EQ(subframe_url, entry->root_node()->children[0]->frame_entry->url());
// 3. Add a nested subframe.
{
LoadCommittedCapturer capturer(shell()->web_contents());
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + subframe_url.spec() + "';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
// The entry should have a FrameNavigationEntry for the subframe.
entry = controller.GetLastCommittedEntry();
ASSERT_EQ(1U, entry->root_node()->children.size());
EXPECT_EQ(subframe_url, entry->root_node()->children[0]->frame_entry->url());
ASSERT_EQ(1U, entry->root_node()->children[0]->children.size());
EXPECT_EQ(subframe_url,
entry->root_node()->children[0]->children[0]->frame_entry->url());
}
// Verify the tree of FrameNavigationEntries after back/forward navigations in a
// cross-site subframe.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_SubframeBackForward) {
GURL main_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
const NavigationControllerImpl& controller =
static_cast<const NavigationControllerImpl&>(
shell()->web_contents()->GetController());
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
// 1. Create a same-site iframe.
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
{
LoadCommittedCapturer capturer(shell()->web_contents());
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + frame_url.spec() + "';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
}
NavigationEntryImpl* entry1 = controller.GetLastCommittedEntry();
// 2. Navigate in the subframe cross-site.
GURL frame_url2(embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/page_with_links.html"));
{
FrameNavigateParamsCapturer capturer(root->child_at(0));
NavigateFrameToURL(root->child_at(0), frame_url2);
capturer.Wait();
}
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
NavigationEntryImpl* entry2 = controller.GetLastCommittedEntry();
// 3. Navigate in the subframe cross-site again.
GURL frame_url3(embedded_test_server()->GetURL(
"bar.com", "/navigation_controller/page_with_links.html"));
{
FrameNavigateParamsCapturer capturer(root->child_at(0));
NavigateFrameToURL(root->child_at(0), frame_url3);
capturer.Wait();
}
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
NavigationEntryImpl* entry3 = controller.GetLastCommittedEntry();
// 4. Go back in the subframe.
{
FrameNavigateParamsCapturer capturer(root->child_at(0));
shell()->web_contents()->GetController().GoBack();
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
EXPECT_EQ(NAVIGATION_TYPE_AUTO_SUBFRAME, capturer.navigation_type());
}
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(entry2, controller.GetLastCommittedEntry());
// The entry should have a FrameNavigationEntry for the subframe.
ASSERT_EQ(1U, entry2->root_node()->children.size());
EXPECT_EQ(frame_url2, entry2->root_node()->children[0]->frame_entry->url());
// 5. Go back in the subframe again to the parent page's site.
{
FrameNavigateParamsCapturer capturer(root->child_at(0));
shell()->web_contents()->GetController().GoBack();
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
EXPECT_EQ(NAVIGATION_TYPE_AUTO_SUBFRAME, capturer.navigation_type());
}
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(entry1, controller.GetLastCommittedEntry());
// The entry should have a FrameNavigationEntry for the subframe.
ASSERT_EQ(1U, entry1->root_node()->children.size());
EXPECT_EQ(frame_url, entry1->root_node()->children[0]->frame_entry->url());
// 6. Go forward in the subframe cross-site.
{
FrameNavigateParamsCapturer capturer(root->child_at(0));
shell()->web_contents()->GetController().GoForward();
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
EXPECT_EQ(NAVIGATION_TYPE_AUTO_SUBFRAME, capturer.navigation_type());
}
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(entry2, controller.GetLastCommittedEntry());
// The entry should have a FrameNavigationEntry for the subframe.
ASSERT_EQ(1U, entry2->root_node()->children.size());
EXPECT_EQ(frame_url2, entry2->root_node()->children[0]->frame_entry->url());
// 7. Go forward in the subframe again, cross-site.
{
FrameNavigateParamsCapturer capturer(root->child_at(0));
shell()->web_contents()->GetController().GoForward();
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
EXPECT_EQ(NAVIGATION_TYPE_AUTO_SUBFRAME, capturer.navigation_type());
}
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(entry3, controller.GetLastCommittedEntry());
// The entry should have a FrameNavigationEntry for the subframe.
ASSERT_EQ(1U, entry3->root_node()->children.size());
EXPECT_EQ(frame_url3, entry3->root_node()->children[0]->frame_entry->url());
}
// Verify the tree of FrameNavigationEntries after subframes are recreated in
// history navigations, including nested frames. The history will look like:
// 1. initial_url
// 2. main_url_a (data_url)
// 3. main_url_a (frame_url_b (data_url))
// 4. main_url_a (frame_url_b (frame_url_c))
// 5. main_url_d
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_RecreatedSubframeBackForward) {
// 1. Start on a page with no frames.
GURL initial_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), initial_url));
const NavigationControllerImpl& controller =
static_cast<const NavigationControllerImpl&>(
shell()->web_contents()->GetController());
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
EXPECT_EQ(initial_url, root->current_url());
NavigationEntryImpl* entry1 = controller.GetLastCommittedEntry();
EXPECT_EQ(0U, entry1->root_node()->children.size());
// 2. Navigate to 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");
EXPECT_TRUE(NavigateToURL(shell(), main_url_a));
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(2, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
NavigationEntryImpl* entry2 = controller.GetLastCommittedEntry();
// The entry should have a FrameNavigationEntry for the data subframe.
ASSERT_EQ(1U, entry2->root_node()->children.size());
EXPECT_EQ(data_url, entry2->root_node()->children[0]->frame_entry->url());
// 3. Navigate the iframe cross-site to a page with a nested iframe.
GURL frame_url_b(embedded_test_server()->GetURL(
"b.com", "/navigation_controller/page_with_data_iframe.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(data_url, root->child_at(0)->child_at(0)->current_url());
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
NavigationEntryImpl* entry3 = controller.GetLastCommittedEntry();
// The entry should have a FrameNavigationEntry for the b.com subframe.
ASSERT_EQ(1U, entry3->root_node()->children.size());
ASSERT_EQ(1U, entry3->root_node()->children[0]->children.size());
EXPECT_EQ(frame_url_b, entry3->root_node()->children[0]->frame_entry->url());
EXPECT_EQ(data_url,
entry3->root_node()->children[0]->children[0]->frame_entry->url());
// 4. Navigate the nested iframe cross-site.
GURL frame_url_c(embedded_test_server()->GetURL(
"c.com", "/navigation_controller/simple_page_2.html"));
{
FrameNavigateParamsCapturer capturer(root->child_at(0)->child_at(0));
NavigateFrameToURL(root->child_at(0)->child_at(0), frame_url_c);
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(frame_url_c, root->child_at(0)->child_at(0)->current_url());
EXPECT_EQ(4, controller.GetEntryCount());
EXPECT_EQ(3, controller.GetLastCommittedEntryIndex());
NavigationEntryImpl* entry4 = controller.GetLastCommittedEntry();
// The entry should have FrameNavigationEntries for the subframes.
ASSERT_EQ(1U, entry4->root_node()->children.size());
ASSERT_EQ(1U, entry4->root_node()->children[0]->children.size());
EXPECT_EQ(frame_url_b, entry4->root_node()->children[0]->frame_entry->url());
EXPECT_EQ(frame_url_c,
entry4->root_node()->children[0]->children[0]->frame_entry->url());
// Remember the DSNs for later.
int64_t root_dsn =
entry4->root_node()->frame_entry->document_sequence_number();
int64_t frame_b_dsn =
entry4->root_node()->children[0]->frame_entry->document_sequence_number();
int64_t frame_c_dsn = entry4->root_node()
->children[0]
->children[0]
->frame_entry->document_sequence_number();
// 5. Navigate main frame cross-site, destroying the frames.
GURL main_url_d(embedded_test_server()->GetURL(
"d.com", "/navigation_controller/simple_page_2.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url_d));
ASSERT_EQ(0U, root->child_count());
EXPECT_EQ(main_url_d, root->current_url());
EXPECT_EQ(5, controller.GetEntryCount());
EXPECT_EQ(4, controller.GetLastCommittedEntryIndex());
NavigationEntryImpl* entry5 = controller.GetLastCommittedEntry();
EXPECT_EQ(0U, entry5->root_node()->children.size());
// 6. Go back, recreating the iframe and its nested iframe.
{
TestNavigationObserver back_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoBack();
back_load_observer.Wait();
}
ASSERT_EQ(1U, root->child_count());
ASSERT_EQ(1U, root->child_at(0)->child_count());
EXPECT_EQ(main_url_a, root->current_url());
EXPECT_EQ(frame_url_b, root->child_at(0)->current_url());
EXPECT_EQ(frame_url_c, root->child_at(0)->child_at(0)->current_url());
EXPECT_EQ(5, controller.GetEntryCount());
EXPECT_EQ(3, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(entry4, controller.GetLastCommittedEntry());
// The main frame should not have changed its DSN.
EXPECT_EQ(root_dsn,
entry4->root_node()->frame_entry->document_sequence_number());
// The entry should have FrameNavigationEntries for the subframes.
ASSERT_EQ(1U, entry4->root_node()->children.size());
ASSERT_EQ(1U, entry4->root_node()->children[0]->children.size());
EXPECT_EQ(frame_url_b, entry4->root_node()->children[0]->frame_entry->url());
EXPECT_EQ(frame_url_c,
entry4->root_node()->children[0]->children[0]->frame_entry->url());
// The subframes should not have changed their DSNs.
// See https://crbug.com/628286.
EXPECT_EQ(frame_b_dsn, entry4->root_node()
->children[0]
->frame_entry->document_sequence_number());
EXPECT_EQ(frame_c_dsn, entry4->root_node()
->children[0]
->children[0]
->frame_entry->document_sequence_number());
// Inject a JS value so that we can check for it later.
EXPECT_TRUE(content::ExecuteScript(root, "foo=3;"));
// 7. Go back again, to the data URL in the nested iframe.
{
TestNavigationObserver back_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoBack();
back_load_observer.Wait();
}
ASSERT_EQ(1U, root->child_count());
ASSERT_EQ(1U, root->child_at(0)->child_count());
EXPECT_EQ(main_url_a, root->current_url());
EXPECT_EQ(frame_url_b, root->child_at(0)->current_url());
EXPECT_EQ(data_url, root->child_at(0)->child_at(0)->current_url());
EXPECT_EQ(5, controller.GetEntryCount());
EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(entry3, controller.GetLastCommittedEntry());
// The entry should have FrameNavigationEntries for the subframes.
ASSERT_EQ(1U, entry3->root_node()->children.size());
ASSERT_EQ(1U, entry3->root_node()->children[0]->children.size());
EXPECT_EQ(frame_url_b, entry3->root_node()->children[0]->frame_entry->url());
EXPECT_EQ(data_url,
entry3->root_node()->children[0]->children[0]->frame_entry->url());
// Verify that we did not reload the main frame. See https://crbug.com/586234.
{
int value = 0;
EXPECT_TRUE(ExecuteScriptAndExtractInt(
root, "domAutomationController.send(foo)", &value));
EXPECT_EQ(3, value);
}
// 8. Go back again, to the data URL in the first subframe.
{
TestNavigationObserver back_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoBack();
back_load_observer.Wait();
}
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(5, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(entry2, controller.GetLastCommittedEntry());
// The entry should have a FrameNavigationEntry for the subframe.
ASSERT_EQ(1U, entry2->root_node()->children.size());
EXPECT_EQ(data_url, entry2->root_node()->children[0]->frame_entry->url());
// 9. Go back again, to the initial main frame page.
{
TestNavigationObserver back_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoBack();
back_load_observer.Wait();
}
ASSERT_EQ(0U, root->child_count());
EXPECT_EQ(initial_url, root->current_url());
EXPECT_EQ(5, controller.GetEntryCount());
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(entry1, controller.GetLastCommittedEntry());
EXPECT_EQ(0U, entry1->root_node()->children.size());
// 10. Go forward multiple entries and verify the correct subframe URLs load.
{
TestNavigationObserver back_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoToOffset(2);
back_load_observer.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(data_url, root->child_at(0)->child_at(0)->current_url());
EXPECT_EQ(5, controller.GetEntryCount());
EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(entry3, controller.GetLastCommittedEntry());
// The entry should have FrameNavigationEntries for the subframes.
ASSERT_EQ(1U, entry3->root_node()->children.size());
EXPECT_EQ(frame_url_b, entry3->root_node()->children[0]->frame_entry->url());
EXPECT_EQ(data_url,
entry3->root_node()->children[0]->children[0]->frame_entry->url());
}
// Verify that we navigate to the fallback (original) URL if a subframe's
// FrameNavigationEntry can't be found during a history navigation.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_SubframeHistoryFallback) {
// 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");
EXPECT_TRUE(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();
// 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());
// 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();
// 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());
// 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"));
EXPECT_TRUE(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());
// Force the subframe entry to have the wrong name, so that it isn't found
// when we go back.
entry2->root_node()->children[0]->frame_entry->set_frame_unique_name("wrong");
// 4. Go back, recreating the iframe. The subframe entry won't be found, and
// we should fall back to the default URL.
{
TestNavigationObserver back_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoBack();
back_load_observer.Wait();
}
ASSERT_EQ(1U, root->child_count());
EXPECT_EQ(main_url_a, root->current_url());
EXPECT_EQ(data_url, root->child_at(0)->current_url());
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(entry2, controller.GetLastCommittedEntry());
// The entry should have both the stale FrameNavigationEntry with the old
// name and the new FrameNavigationEntry for the fallback navigation.
ASSERT_EQ(2U, entry2->root_node()->children.size());
EXPECT_EQ(frame_url_b, entry2->root_node()->children[0]->frame_entry->url());
EXPECT_EQ(data_url, entry2->root_node()->children[1]->frame_entry->url());
}
// Allows waiting until an URL with a data scheme commits in any frame.
class DataUrlCommitObserver : public WebContentsObserver {
public:
explicit DataUrlCommitObserver(WebContents* web_contents)
: WebContentsObserver(web_contents),
message_loop_runner_(new MessageLoopRunner) {}
void Wait() { message_loop_runner_->Run(); }
private:
void DidFinishNavigation(NavigationHandle* navigation_handle) override {
if (navigation_handle->HasCommitted() &&
!navigation_handle->IsErrorPage() &&
navigation_handle->GetURL().scheme() == "data")
message_loop_runner_->Quit();
}
// The MessageLoopRunner used to spin the message loop.
scoped_refptr<MessageLoopRunner> message_loop_runner_;
};
// Verify that dynamically generated iframes load properly during a history
// navigation if no history item can be found for them.
// See https://crbug.com/649345.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_DynamicSubframeHistoryFallback) {
// 1. Start on a page with a script-generated iframe. The iframe has a
// dynamic name, starts at about:blank, and gets navigated to a dynamic data
// URL as the page is loading.
GURL main_url_a(embedded_test_server()->GetURL(
"a.com", "/navigation_controller/dynamic_iframe.html"));
{
// Wait until the data URL has committed, even if load stop happens after
// about:blank load.
DataUrlCommitObserver data_observer(shell()->web_contents());
EXPECT_TRUE(NavigateToURL(shell(), main_url_a));
data_observer.Wait();
}
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", root->child_at(0)->current_url().scheme());
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
NavigationEntryImpl* entry1 = controller.GetLastCommittedEntry();
// The entry should have a FrameNavigationEntry for the data subframe.
ASSERT_EQ(1U, entry1->root_node()->children.size());
EXPECT_EQ("data",
entry1->root_node()->children[0]->frame_entry->url().scheme());
// 2. Navigate main frame cross-site, destroying the frames.
GURL main_url_b(embedded_test_server()->GetURL(
"b.com", "/navigation_controller/simple_page_2.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url_b));
ASSERT_EQ(0U, root->child_count());
EXPECT_EQ(main_url_b, root->current_url());
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
NavigationEntryImpl* entry2 = controller.GetLastCommittedEntry();
EXPECT_EQ(0U, entry2->root_node()->children.size());
// 3. Go back, recreating the iframe. The subframe will have a new name this
// time, so we won't find a history item for it. We should let the new data
// URL be loaded into it, rather than clobbering it with an about:blank page.
{
// Wait until the data URL has committed, even if load stop happens first.
DataUrlCommitObserver back_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoBack();
back_load_observer.Wait();
}
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
ASSERT_EQ(1U, root->child_count());
EXPECT_EQ(main_url_a, root->current_url());
EXPECT_EQ("data", root->child_at(0)->current_url().scheme());
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(entry1, controller.GetLastCommittedEntry());
// The entry should have both the stale FrameNavigationEntry with the old
// name and the new FrameNavigationEntry for the fallback navigation.
ASSERT_EQ(2U, entry1->root_node()->children.size());
EXPECT_EQ("data",
entry1->root_node()->children[0]->frame_entry->url().scheme());
EXPECT_EQ("data",
entry1->root_node()->children[1]->frame_entry->url().scheme());
// The iframe commit should have been classified AUTO_SUBFRAME and not
// NEW_SUBFRAME, so we should still be able to go forward.
EXPECT_TRUE(shell()->web_contents()->GetController().CanGoForward());
}
// Verify that we don't clobber any content injected into the initial blank page
// if we go back to an about:blank subframe. See https://crbug.com/626416.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_RecreatedBlankSubframe) {
// 1. Start on a page that injects content into an about:blank iframe.
GURL main_url(embedded_test_server()->GetURL(
"/navigation_controller/inject_into_blank_iframe.html"));
GURL blank_url(url::kAboutBlankURL);
EXPECT_TRUE(NavigateToURL(shell(), main_url));
NavigationControllerImpl& controller = static_cast<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, root->current_url());
EXPECT_EQ(blank_url, root->child_at(0)->current_url());
// Verify that the parent was able to script the iframe.
std::string expected_text("Injected text");
{
std::string value;
EXPECT_TRUE(ExecuteScriptAndExtractString(
root->child_at(0),
"domAutomationController.send(document.body.innerHTML)", &value));
EXPECT_EQ(expected_text, value);
}
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
// The entry should have a FrameNavigationEntry for the blank subframe.
ASSERT_EQ(1U, entry->root_node()->children.size());
EXPECT_EQ(blank_url, entry->root_node()->children[0]->frame_entry->url());
// 2. Navigate the main frame, destroying the frames.
GURL main_url_2(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url_2));
ASSERT_EQ(0U, root->child_count());
EXPECT_EQ(main_url_2, root->current_url());
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
// 3. Go back, recreating the iframe.
{
TestNavigationObserver back_load_observer(shell()->web_contents());
controller.GoBack();
back_load_observer.Wait();
}
ASSERT_EQ(1U, root->child_count());
EXPECT_EQ(main_url, root->current_url());
EXPECT_EQ(blank_url, root->child_at(0)->current_url());
// Verify that the parent was able to script the iframe.
{
std::string value;
EXPECT_TRUE(ExecuteScriptAndExtractString(
root->child_at(0),
"domAutomationController.send(document.body.innerHTML)", &value));
EXPECT_EQ(expected_text, value);
}
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(entry, controller.GetLastCommittedEntry());
// The entry should have a FrameNavigationEntry for the blank subframe.
ASSERT_EQ(1U, entry->root_node()->children.size());
EXPECT_EQ(blank_url, entry->root_node()->children[0]->frame_entry->url());
}
// Verify that we correctly load nested iframes injected into a page if we go
// back and recreate them. Also confirm that form values are not restored for
// forms injected into about:blank pages. See https://crbug.com/657896.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_RecreatedInjectedBlankSubframe) {
// 1. Start on a page that injects a nested iframe into an injected
// about:blank iframe.
GURL main_url(embedded_test_server()->GetURL(
"/navigation_controller/inject_subframe_into_blank_iframe.html"));
GURL blank_url(url::kAboutBlankURL);
GURL inner_url(
embedded_test_server()->GetURL("/navigation_controller/form.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
shell()->web_contents()->GetController());
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
// Verify that the inner iframe was able to load.
ASSERT_EQ(1U, root->child_count());
ASSERT_EQ(1U, root->child_at(0)->child_count());
ASSERT_EQ(0U, root->child_at(0)->child_at(0)->child_count());
EXPECT_EQ(main_url, root->current_url());
EXPECT_EQ(blank_url, root->child_at(0)->current_url());
EXPECT_EQ(inner_url, root->child_at(0)->child_at(0)->current_url());
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
// The entry should have FrameNavigationEntries for the subframes.
ASSERT_EQ(1U, entry->root_node()->children.size());
EXPECT_EQ(blank_url, entry->root_node()->children[0]->frame_entry->url());
EXPECT_EQ(inner_url,
entry->root_node()->children[0]->children[0]->frame_entry->url());
// Set a value in the form which will be stored in the PageState.
EXPECT_TRUE(
ExecuteScript(root->child_at(0)->child_at(0),
"document.getElementById('itext').value = 'modified';"));
// 2. Navigate the main frame same-site, destroying the subframes.
GURL main_url_2(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url_2));
ASSERT_EQ(0U, root->child_count());
EXPECT_EQ(main_url_2, root->current_url());
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
// 3. Go back, recreating the subframes.
{
TestNavigationObserver back_load_observer(shell()->web_contents());
controller.GoBack();
back_load_observer.Wait();
}
ASSERT_EQ(1U, root->child_count());
EXPECT_EQ(main_url, root->current_url());
EXPECT_EQ(blank_url, root->child_at(0)->current_url());
// Verify that the inner iframe went to the correct URL.
EXPECT_EQ(inner_url, root->child_at(0)->child_at(0)->current_url());
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(entry, controller.GetLastCommittedEntry());
// The entry should have FrameNavigationEntries for the subframes.
ASSERT_EQ(1U, entry->root_node()->children.size());
EXPECT_EQ(blank_url, entry->root_node()->children[0]->frame_entry->url());
EXPECT_EQ(inner_url,
entry->root_node()->children[0]->children[0]->frame_entry->url());
// With injected about:blank iframes, we never restore form values from
// PageState.
std::string form_value = "fail";
EXPECT_TRUE(
ExecuteScriptAndExtractString(root->child_at(0)->child_at(0),
"window.domAutomationController.send("
"document.getElementById('itext').value);",
&form_value));
EXPECT_EQ("", form_value);
}
// Verify that we correctly load a nested iframe created by an injected iframe
// srcdoc if we go back and recreate the frames. Also verify that form values
// are correctly restored for forms within srcdoc frames, unlike forms injected
// into about:blank pages (as tested in
// FrameNavigationEntry_RecreatedInjectedBlankSubframe).
//
// This test worked before and after the fix for https://crbug.com/657896, but
// it failed with a preliminary version of the fix.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_RecreatedInjectedSrcdocSubframe) {
// 1. Start on a page that injects a nested iframe srcdoc which contains a
// nested iframe.
GURL main_url(embedded_test_server()->GetURL(
"/navigation_controller/inject_iframe_srcdoc_with_nested_frame.html"));
GURL srcdoc_url(content::kAboutSrcDocURL);
GURL inner_url(
embedded_test_server()->GetURL("/navigation_controller/form.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
shell()->web_contents()->GetController());
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
// Verify that the inner iframe was able to load.
ASSERT_EQ(1U, root->child_count());
ASSERT_EQ(1U, root->child_at(0)->child_count());
ASSERT_EQ(0U, root->child_at(0)->child_at(0)->child_count());
EXPECT_EQ(main_url, root->current_url());
EXPECT_EQ(srcdoc_url, root->child_at(0)->current_url());
EXPECT_EQ(inner_url, root->child_at(0)->child_at(0)->current_url());
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
// The entry should have FrameNavigationEntries for the subframes.
ASSERT_EQ(1U, entry->root_node()->children.size());
EXPECT_EQ(srcdoc_url, entry->root_node()->children[0]->frame_entry->url());
EXPECT_EQ(inner_url,
entry->root_node()->children[0]->children[0]->frame_entry->url());
// Set a value in the form which will be stored in the PageState.
EXPECT_TRUE(
ExecuteScript(root->child_at(0)->child_at(0),
"document.getElementById('itext').value = 'modified';"));
// 2. Navigate the main frame same-site, destroying the subframes.
GURL main_url_2(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url_2));
ASSERT_EQ(0U, root->child_count());
EXPECT_EQ(main_url_2, root->current_url());
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
// 3. Go back, recreating the subframes.
{
TestNavigationObserver back_load_observer(shell()->web_contents());
controller.GoBack();
back_load_observer.Wait();
}
ASSERT_EQ(1U, root->child_count());
ASSERT_EQ(1U, root->child_at(0)->child_count());
ASSERT_EQ(0U, root->child_at(0)->child_at(0)->child_count());
EXPECT_EQ(main_url, root->current_url());
EXPECT_EQ(srcdoc_url, root->child_at(0)->current_url());
// Verify that the inner iframe went to the correct URL.
EXPECT_EQ(inner_url, root->child_at(0)->child_at(0)->current_url());
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(entry, controller.GetLastCommittedEntry());
// The entry should have FrameNavigationEntries for the subframes.
ASSERT_EQ(1U, entry->root_node()->children.size());
EXPECT_EQ(srcdoc_url, entry->root_node()->children[0]->frame_entry->url());
EXPECT_EQ(inner_url,
entry->root_node()->children[0]->children[0]->frame_entry->url());
// With injected iframe srcdoc pages, we do restore form values from
// PageState.
std::string form_value;
EXPECT_TRUE(
ExecuteScriptAndExtractString(root->child_at(0)->child_at(0),
"window.domAutomationController.send("
"document.getElementById('itext').value);",
&form_value));
EXPECT_EQ("modified", form_value);
}
// Verify that we can load about:blank in an iframe when going back to a page,
// if that iframe did not originally have about:blank in it. See
// https://crbug.com/657896.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_RecreatedSubframeToBlank) {
// 1. Start on a page with a data iframe.
GURL main_url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_data_iframe.html"));
GURL data_url("data:text/html,Subframe");
EXPECT_TRUE(NavigateToURL(shell(), main_url));
NavigationControllerImpl& controller = static_cast<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, root->current_url());
EXPECT_EQ(data_url, root->child_at(0)->current_url());
// 2. Navigate the subframe to about:blank.
GURL blank_url(url::kAboutBlankURL);
NavigateFrameToURL(root->child_at(0), blank_url);
EXPECT_EQ(blank_url, root->child_at(0)->current_url());
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
// The entry should have a FrameNavigationEntry for the blank subframe.
ASSERT_EQ(1U, entry->root_node()->children.size());
EXPECT_EQ(blank_url, entry->root_node()->children[0]->frame_entry->url());
// 3. Navigate the main frame, destroying the frames.
GURL main_url_2(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url_2));
ASSERT_EQ(0U, root->child_count());
EXPECT_EQ(main_url_2, root->current_url());
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
// 3. Go back, recreating the iframe.
{
TestNavigationObserver back_load_observer(shell()->web_contents());
controller.GoBack();
back_load_observer.Wait();
}
ASSERT_EQ(1U, root->child_count());
EXPECT_EQ(main_url, root->current_url());
EXPECT_EQ(blank_url, root->child_at(0)->current_url());
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(entry, controller.GetLastCommittedEntry());
// The entry should have a FrameNavigationEntry for the blank subframe.
ASSERT_EQ(1U, entry->root_node()->children.size());
EXPECT_EQ(blank_url, entry->root_node()->children[0]->frame_entry->url());
}
// Ensure we don't crash if an onload handler removes an about:blank frame after
// recreating it on a back/forward. See https://crbug.com/638166.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_RemoveRecreatedBlankSubframe) {
// 1. Start on a page that removes its about:blank iframe during onload.
GURL main_url(embedded_test_server()->GetURL(
"/navigation_controller/remove_blank_iframe_on_load.html"));
GURL blank_url(url::kAboutBlankURL);
EXPECT_TRUE(NavigateToURL(shell(), main_url));
NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
shell()->web_contents()->GetController());
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
EXPECT_EQ(main_url, root->current_url());
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
// The entry should have a FrameNavigationEntry for the blank subframe, even
// though it is being removed from the page.
ASSERT_EQ(1U, entry->root_node()->children.size());
EXPECT_EQ(blank_url, entry->root_node()->children[0]->frame_entry->url());
// 2. Navigate the main frame, destroying the frames.
GURL main_url_2(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url_2));
ASSERT_EQ(0U, root->child_count());
EXPECT_EQ(main_url_2, root->current_url());
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
// 3. Go back, recreating the iframe (and removing it again).
{
TestNavigationObserver back_load_observer(shell()->web_contents());
controller.GoBack();
back_load_observer.Wait();
}
EXPECT_EQ(main_url, root->current_url());
// Check that the renderer is still alive.
EXPECT_TRUE(ExecuteScript(shell(), "console.log('Success');"));
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(entry, controller.GetLastCommittedEntry());
// The entry should have a FrameNavigationEntry for the blank subframe.
ASSERT_EQ(1U, entry->root_node()->children.size());
EXPECT_EQ(blank_url, entry->root_node()->children[0]->frame_entry->url());
}
// Verifies that we clear the children FrameNavigationEntries if a history
// navigation redirects, so that we don't try to load previous history items in
// frames of the new page. This should only clear the children of the frame
// that is redirecting. See https://crbug.com/585194.
//
// Specifically, this test covers the following interesting cases:
// - Subframe redirect when going back from a different main frame (step 4).
// - Subframe redirect without changing the main frame (step 6).
// - Main frame redirect, clearing the children (step 8).
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_BackWithRedirect) {
// 1. Start on a page with two frames.
GURL initial_url(
embedded_test_server()->GetURL("/frame_tree/page_with_two_frames.html"));
EXPECT_TRUE(NavigateToURL(shell(), initial_url));
const NavigationControllerImpl& controller =
static_cast<const NavigationControllerImpl&>(
shell()->web_contents()->GetController());
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
EXPECT_EQ(initial_url, root->current_url());
EXPECT_EQ(2U, root->child_count());
NavigationEntryImpl* entry1 = controller.GetLastCommittedEntry();
EXPECT_EQ(2U, entry1->root_node()->children.size());
// 2. Navigate both iframes to a page with a nested iframe.
GURL frame_url(embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/page_with_data_iframe.html"));
GURL data_url("data:text/html,Subframe");
NavigateFrameToURL(root->child_at(0), frame_url);
NavigateFrameToURL(root->child_at(1), frame_url);
EXPECT_EQ(initial_url, root->current_url());
EXPECT_EQ(frame_url, root->child_at(0)->current_url());
EXPECT_EQ(data_url, root->child_at(0)->child_at(0)->current_url());
EXPECT_EQ(frame_url, root->child_at(1)->current_url());
EXPECT_EQ(data_url, root->child_at(1)->child_at(0)->current_url());
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
NavigationEntryImpl* entry2 = controller.GetLastCommittedEntry();
// Verify subframe entries.
NavigationEntryImpl::TreeNode* root_node = entry2->root_node();
ASSERT_EQ(2U, root_node->children.size());
EXPECT_EQ(frame_url, root_node->children[0]->frame_entry->url());
EXPECT_EQ(data_url, root_node->children[0]->children[0]->frame_entry->url());
EXPECT_EQ(frame_url, root_node->children[1]->frame_entry->url());
EXPECT_EQ(data_url, root_node->children[1]->children[0]->frame_entry->url());
// Cause the first iframe to redirect when we come back later. It will go
// cross-site to a page with an about:blank iframe.
GURL frame_redirect_dest_url(embedded_test_server()->GetURL(
"bar.com", "/navigation_controller/page_with_iframe.html"));
GURL blank_url(url::kAboutBlankURL);
{
TestNavigationObserver observer(shell()->web_contents());
std::string script = "history.replaceState({}, '', '/server-redirect?" +
frame_redirect_dest_url.spec() + "')";
EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
observer.Wait();
}
// We should not have lost subframe entries for the nested frame.
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
FrameNavigationEntry* nested_entry =
entry2->GetFrameEntry(root->child_at(0)->child_at(0));
EXPECT_TRUE(nested_entry);
EXPECT_EQ(data_url, nested_entry->url());
// 3. Navigate the main frame to a different page. When we come back, we'll
// commit the main frame first and have no pending entry when navigating the
// subframes.
GURL url2(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url2));
EXPECT_EQ(4, controller.GetEntryCount());
EXPECT_EQ(3, controller.GetLastCommittedEntryIndex());
// 4. Go back. The first iframe should redirect to a cross-site page with a
// different nested iframe.
{
TestNavigationObserver back_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoBack();
back_load_observer.Wait();
}
EXPECT_EQ(initial_url, root->current_url());
EXPECT_EQ(frame_redirect_dest_url, root->child_at(0)->current_url());
EXPECT_EQ(blank_url, root->child_at(0)->child_at(0)->current_url());
EXPECT_EQ(frame_url, root->child_at(1)->current_url());
EXPECT_EQ(data_url, root->child_at(1)->child_at(0)->current_url());
// Check the FrameNavigationEntries as well.
EXPECT_EQ(4, controller.GetEntryCount());
EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(frame_redirect_dest_url,
entry2->GetFrameEntry(root->child_at(0))->url());
EXPECT_EQ(blank_url,
entry2->GetFrameEntry(root->child_at(0)->child_at(0))->url());
EXPECT_EQ(frame_url, entry2->GetFrameEntry(root->child_at(1))->url());
EXPECT_EQ(data_url,
entry2->GetFrameEntry(root->child_at(1)->child_at(0))->url());
// In --site-per-process, we're misclassifying the subframe redirect in step 6
// below. For now, skip the rest of the test in that mode.
// TODO(creis): Fix this in https://crbug.com/628782.
if (AreAllSitesIsolatedForTesting())
return;
// Now cause the second iframe to redirect when we come back to it.
{
TestNavigationObserver observer(shell()->web_contents());
std::string script = "history.replaceState({}, '', '/server-redirect?" +
frame_redirect_dest_url.spec() + "')";
EXPECT_TRUE(ExecuteScript(root->child_at(1), script));
observer.Wait();
}
// 5. Navigate the other iframe elsewhere, so that going back does not
// require a navigation in the main frame. This means there will be a
// pending entry when the subframe commits, exercising a different path than
// step 4.
{
FrameNavigateParamsCapturer capturer(root->child_at(1));
NavigateFrameToURL(root->child_at(1), url2);
capturer.Wait();
}
EXPECT_EQ(4, controller.GetEntryCount());
EXPECT_EQ(3, controller.GetLastCommittedEntryIndex());
// 6. As in step 4, go back but redirect, resetting the children.
{
TestNavigationObserver back_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoBack();
back_load_observer.Wait();
}
EXPECT_EQ(initial_url, root->current_url());
EXPECT_EQ(frame_redirect_dest_url, root->child_at(0)->current_url());
EXPECT_EQ(blank_url, root->child_at(0)->child_at(0)->current_url());
EXPECT_EQ(frame_redirect_dest_url, root->child_at(1)->current_url());
EXPECT_EQ(blank_url, root->child_at(1)->child_at(0)->current_url());
// Check the FrameNavigationEntries as well.
EXPECT_EQ(4, controller.GetEntryCount());
EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(frame_redirect_dest_url,
entry2->GetFrameEntry(root->child_at(0))->url());
EXPECT_EQ(blank_url,
entry2->GetFrameEntry(root->child_at(0)->child_at(0))->url());
EXPECT_EQ(frame_redirect_dest_url,
entry2->GetFrameEntry(root->child_at(1))->url());
EXPECT_EQ(blank_url,
entry2->GetFrameEntry(root->child_at(1)->child_at(0))->url());
// Now cause the main frame to redirect to a page with no frames when we come
// back to it.
GURL redirect_dest_url(embedded_test_server()->GetURL(
"bar.com", "/navigation_controller/simple_page_2.html"));
{
TestNavigationObserver observer(shell()->web_contents());
std::string script = "history.replaceState({}, '', '/server-redirect?" +
redirect_dest_url.spec() + "')";
EXPECT_TRUE(ExecuteScript(root, script));
observer.Wait();
}
// 7. Navigate the main frame to a different page.
EXPECT_TRUE(NavigateToURL(shell(), url2));
EXPECT_EQ(4, controller.GetEntryCount());
EXPECT_EQ(3, controller.GetLastCommittedEntryIndex());
// 8. Go back, causing the main frame to redirect to a page with no frames.
// All child items should be gone.
{
TestNavigationObserver back_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoBack();
back_load_observer.Wait();
}
EXPECT_EQ(redirect_dest_url, root->current_url());
EXPECT_EQ(0U, root->child_count());
EXPECT_EQ(0U, entry2->root_node()->children.size());
EXPECT_EQ(4, controller.GetEntryCount());
EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
}
// Similar to FrameNavigationEntry_BackWithRedirect but with same-origin frames.
// (This wasn't working initially).
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_SameOriginBackWithRedirect) {
// 1. Start on a page with an iframe.
GURL initial_url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_data_iframe.html"));
EXPECT_TRUE(NavigateToURL(shell(), initial_url));
NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
shell()->web_contents()->GetController());
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
EXPECT_EQ(initial_url, root->current_url());
EXPECT_EQ(1U, root->child_count());
NavigationEntryImpl* entry1 = controller.GetLastCommittedEntry();
EXPECT_EQ(1U, entry1->root_node()->children.size());
// 2. Navigate the iframe to a page with a nested iframe.
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_data_iframe.html"));
GURL data_url("data:text/html,Subframe");
NavigateFrameToURL(root->child_at(0), frame_url);
EXPECT_EQ(initial_url, root->current_url());
EXPECT_EQ(frame_url, root->child_at(0)->current_url());
EXPECT_EQ(data_url, root->child_at(0)->child_at(0)->current_url());
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
NavigationEntryImpl* entry2 = controller.GetLastCommittedEntry();
// Verify subframe entries.
NavigationEntryImpl::TreeNode* root_node = entry2->root_node();
ASSERT_EQ(1U, root_node->children.size());
EXPECT_EQ(frame_url, root_node->children[0]->frame_entry->url());
EXPECT_EQ(data_url, root_node->children[0]->children[0]->frame_entry->url());
// Cause the iframe to redirect when we come back later. It will go
// same-origin to a page with an about:blank iframe.
GURL frame_redirect_dest_url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_iframe.html"));
{
TestNavigationObserver observer(shell()->web_contents());
std::string script = "history.replaceState({}, '', '/server-redirect?" +
frame_redirect_dest_url.spec() + "')";
EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
observer.Wait();
}
// We should not have lost subframe entries for the nested frame.
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
FrameNavigationEntry* nested_entry =
entry2->GetFrameEntry(root->child_at(0)->child_at(0));
EXPECT_TRUE(nested_entry);
EXPECT_EQ(data_url, nested_entry->url());
// 3. Navigate the main frame to a different page. When we come back, we'll
// commit the main frame first and have no pending entry when navigating the
// subframes.
GURL url2(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url2));
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
// 4. Go back. The first iframe should redirect to a same-origin page with a
// different nested iframe.
{
TestNavigationObserver back_load_observer(shell()->web_contents());
controller.GoBack();
back_load_observer.Wait();
}
GURL blank_url(url::kAboutBlankURL);
EXPECT_EQ(initial_url, root->current_url());
EXPECT_EQ(frame_redirect_dest_url, root->child_at(0)->current_url());
EXPECT_EQ(blank_url, root->child_at(0)->child_at(0)->current_url());
// Check the FrameNavigationEntries as well.
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(frame_redirect_dest_url,
entry2->GetFrameEntry(root->child_at(0))->url());
EXPECT_EQ(blank_url,
entry2->GetFrameEntry(root->child_at(0)->child_at(0))->url());
// Now cause the main frame to redirect to a page with no frames when we come
// back to it.
GURL redirect_dest_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
{
TestNavigationObserver observer(shell()->web_contents());
std::string script = "history.replaceState({}, '', '/server-redirect?" +
redirect_dest_url.spec() + "')";
EXPECT_TRUE(ExecuteScript(root, script));
observer.Wait();
}
// 5. Navigate the main frame to a different page.
EXPECT_TRUE(NavigateToURL(shell(), url2));
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
// 6. Go back, causing the main frame to redirect to a page with no frames.
// All child items should be gone.
{
TestNavigationObserver back_load_observer(shell()->web_contents());
controller.GoBack();
back_load_observer.Wait();
}
EXPECT_EQ(redirect_dest_url, root->current_url());
EXPECT_EQ(0U, root->child_count());
EXPECT_EQ(0U, entry2->root_node()->children.size());
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
}
// 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");
EXPECT_TRUE(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();
// 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());
// 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();
// 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());
// 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"));
EXPECT_TRUE(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.
std::unique_ptr<NavigationEntryImpl> restored_entry =
NavigationEntryImpl::FromNavigationEntry(
NavigationControllerImpl::CreateNavigationEntry(
main_url_a, Referrer(), ui::PAGE_TRANSITION_RELOAD, false,
std::string(), controller.GetBrowserContext()));
EXPECT_EQ(0U, restored_entry->root_node()->children.size());
restored_entry->SetPageState(entry2->GetPageState());
// 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());
// 5. Restore the new entry in a new tab and verify the correct URLs load.
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_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();
// 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());
}
// Verify that we can finish loading a page on restore if the PageState is
// missing subframes. See https://crbug.com/638088.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_RestoreViaPartialPageState) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/navigation_controller/inject_into_blank_iframe.html"));
GURL blank_url(url::kAboutBlankURL);
NavigationControllerImpl& controller =
static_cast<NavigationControllerImpl&>(
shell()->web_contents()->GetController());
FrameTreeNode* root =
static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
// Create a NavigationEntry to restore, as if it had been loaded before. The
// page has an about:blank iframe and injects content into it, but the
// PageState lacks any subframe history items. This may happen during a
// restore of a bad session or if the page has changed since the last visit.
// Chrome should be robust to this and should be able to load the frame from
// its default URL.
std::unique_ptr<NavigationEntryImpl> restored_entry =
NavigationEntryImpl::FromNavigationEntry(
NavigationControllerImpl::CreateNavigationEntry(
main_url, Referrer(), ui::PAGE_TRANSITION_RELOAD, false,
std::string(), controller.GetBrowserContext()));
restored_entry->SetPageState(PageState::CreateFromURL(main_url));
EXPECT_EQ(0U, restored_entry->root_node()->children.size());
// Restore the new entry in a new tab and verify the iframe loads and has
// content injected into it.
std::vector<std::unique_ptr<NavigationEntry>> entries;
entries.push_back(std::move(restored_entry));
controller.Restore(entries.size() - 1,
RestoreType::LAST_SESSION_EXITED_CLEANLY, &entries);
ASSERT_EQ(0u, entries.size());
{
TestNavigationObserver restore_observer(shell()->web_contents());
controller.LoadIfNecessary();
restore_observer.Wait();
}
ASSERT_EQ(1U, root->child_count());
EXPECT_EQ(main_url, root->current_url());
EXPECT_EQ(blank_url, root->child_at(0)->current_url());
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
NavigationEntryImpl* new_entry = controller.GetLastCommittedEntry();
// The entry should have a FrameNavigationEntry for the blank subframe.
EXPECT_EQ(main_url, new_entry->root_node()->frame_entry->url());
ASSERT_EQ(1U, new_entry->root_node()->children.size());
EXPECT_EQ(blank_url, new_entry->root_node()->children[0]->frame_entry->url());
// Verify that the parent was able to script the iframe.
std::string expected_text("Injected text");
{
std::string value;
EXPECT_TRUE(ExecuteScriptAndExtractString(
root->child_at(0),
"domAutomationController.send(document.body.innerHTML)", &value));
EXPECT_EQ(expected_text, value);
}
}
// Verifies that the |frame_unique_name| is set to the correct frame, so that we
// can match subframe FrameNavigationEntries to newly created frames after
// back/forward and restore.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_FrameUniqueName) {
const NavigationControllerImpl& controller =
static_cast<const NavigationControllerImpl&>(
shell()->web_contents()->GetController());
// 1. Navigate the main frame.
GURL url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_links.html"));
EXPECT_TRUE(NavigateToURL(shell(), url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
SiteInstance* main_site_instance =
root->current_frame_host()->GetSiteInstance();
// The main frame defaults to an empty name.
FrameNavigationEntry* frame_entry =
controller.GetLastCommittedEntry()->GetFrameEntry(root);
EXPECT_EQ("", frame_entry->frame_unique_name());
// 2. Add an unnamed subframe, which does an AUTO_SUBFRAME navigation.
{
LoadCommittedCapturer capturer(shell()->web_contents());
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + url.spec() + "';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
// The root FrameNavigationEntry hasn't changed.
EXPECT_EQ(frame_entry,
controller.GetLastCommittedEntry()->GetFrameEntry(root));
// The subframe should have a generated name.
FrameTreeNode* subframe = root->child_at(0);
EXPECT_EQ(main_site_instance,
subframe->current_frame_host()->GetSiteInstance());
FrameNavigationEntry* subframe_entry =
controller.GetLastCommittedEntry()->GetFrameEntry(subframe);
std::string unnamed_subframe_name = "<!--framePath //<!--frame0-->-->";
EXPECT_EQ(unnamed_subframe_name, subframe_entry->frame_unique_name());
// 3. Add a named subframe.
{
LoadCommittedCapturer capturer(shell()->web_contents());
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + url.spec() + "';"
"iframe.name = 'foo';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
// The new subframe should have the specified name.
EXPECT_EQ(frame_entry,
controller.GetLastCommittedEntry()->GetFrameEntry(root));
FrameTreeNode* foo_subframe = root->child_at(1);
EXPECT_EQ(main_site_instance,
foo_subframe->current_frame_host()->GetSiteInstance());
FrameNavigationEntry* foo_subframe_entry =
controller.GetLastCommittedEntry()->GetFrameEntry(foo_subframe);
std::string named_subframe_name = "foo";
EXPECT_EQ(named_subframe_name, foo_subframe_entry->frame_unique_name());
// 4. Navigating in the subframes cross-process shouldn't change their names.
// TODO(creis): Fix the unnamed case in https://crbug.com/502317.
GURL bar_url(embedded_test_server()->GetURL(
"bar.com", "/navigation_controller/simple_page_1.html"));
NavigateFrameToURL(foo_subframe, bar_url);
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
// When run just with subframe navigation entries enabled and not in
// site-per-process-mode the subframe should be in the same SiteInstance as
// its parent.
if (!AreAllSitesIsolatedForTesting()) {
EXPECT_EQ(main_site_instance,
foo_subframe->current_frame_host()->GetSiteInstance());
} else {
EXPECT_NE(main_site_instance,
foo_subframe->current_frame_host()->GetSiteInstance());
}
foo_subframe_entry =
controller.GetLastCommittedEntry()->GetFrameEntry(foo_subframe);
EXPECT_EQ(named_subframe_name, foo_subframe_entry->frame_unique_name());
}
// Ensure we don't crash when cloning a named window. This happened in
// https://crbug.com/603245 because neither the FrameTreeNode ID nor the name of
// the cloned window matched the root FrameNavigationEntry.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, CloneNamedWindow) {
// Start on an initial page.
GURL url_1(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_1));
// Name the window.
EXPECT_TRUE(ExecuteScript(shell(), "window.name = 'foo';"));
// Navigate it.
GURL url_2(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_2));
// Clone the tab and load the page.
std::unique_ptr<WebContentsImpl> new_tab(
static_cast<WebContentsImpl*>(shell()->web_contents()->Clone()));
NavigationController& new_controller = new_tab->GetController();
EXPECT_TRUE(new_controller.IsInitialNavigation());
EXPECT_TRUE(new_controller.NeedsReload());
{
TestNavigationObserver clone_observer(new_tab.get());
new_controller.LoadIfNecessary();
clone_observer.Wait();
}
}
// Ensure we don't crash when going back in a cloned named window. This
// happened in https://crbug.com/603245 because neither the FrameTreeNode ID nor
// the name of the cloned window matched the root FrameNavigationEntry.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
CloneAndGoBackWithNamedWindow) {
// Start on an initial page.
GURL url_1(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_1));
// Name the window.
EXPECT_TRUE(ExecuteScript(shell(), "window.name = 'foo';"));
// Navigate it.
GURL url_2(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_2));
// Clear the name.
EXPECT_TRUE(ExecuteScript(shell(), "window.name = '';"));
// Navigate it again.
EXPECT_TRUE(NavigateToURL(shell(), url_1));
// Clone the tab and load the page.
std::unique_ptr<WebContentsImpl> new_tab(
static_cast<WebContentsImpl*>(shell()->web_contents()->Clone()));
NavigationController& new_controller = new_tab->GetController();
EXPECT_TRUE(new_controller.IsInitialNavigation());
EXPECT_TRUE(new_controller.NeedsReload());
{
TestNavigationObserver clone_observer(new_tab.get());
new_controller.LoadIfNecessary();
clone_observer.Wait();
}
// Go back.
{
TestNavigationObserver back_load_observer(new_tab.get());
new_controller.GoBack();
back_load_observer.Wait();
}
}
// Ensure that going back/forward to an apparently same document
// NavigationEntry works when the renderer process hasn't committed anything
// yet. This can happen when using Ctrl+Back or after a crash. See
// https://crbug.com/635403.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
BackSameDocumentInNewWindow) {
// Start on an initial page.
GURL url_1(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_1));
// Perform same document navigation.
GURL url_2(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html#foo"));
EXPECT_TRUE(NavigateToURL(shell(), url_2));
// Clone the tab but don't load last committed page.
std::unique_ptr<WebContentsImpl> new_tab(
static_cast<WebContentsImpl*>(shell()->web_contents()->Clone()));
NavigationController& new_controller = new_tab->GetController();
EXPECT_TRUE(new_controller.IsInitialNavigation());
EXPECT_TRUE(new_controller.NeedsReload());
// Go back in the new tab.
{
TestNavigationObserver back_load_observer(new_tab.get());
new_controller.GoBack();
back_load_observer.Wait();
}
// Make sure the new tab isn't still loading.
EXPECT_EQ(url_1, new_controller.GetLastCommittedEntry()->GetURL());
EXPECT_FALSE(new_tab->IsLoading());
// Also check going back in the original tab after a renderer crash.
NavigationController& controller = shell()->web_contents()->GetController();
RenderProcessHost* process =
shell()->web_contents()->GetMainFrame()->GetProcess();
RenderProcessHostWatcher crash_observer(
process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
process->Shutdown(0, false);
crash_observer.Wait();
{
TestNavigationObserver back_load_observer(shell()->web_contents());
controller.GoBack();
back_load_observer.Wait();
}
// Make sure the original tab isn't still loading.
EXPECT_EQ(url_1, controller.GetLastCommittedEntry()->GetURL());
EXPECT_FALSE(shell()->web_contents()->IsLoading());
}
// Ensures that FrameNavigationEntries for dynamically added iframes can be
// found correctly when cloning them during a transfer. If we don't look for
// them based on unique name in AddOrUpdateFrameEntry, the FrameTreeNode ID
// mismatch will cause us to create a second FrameNavigationEntry during the
// transfer. Later, we'll find the wrong FrameNavigationEntry (the earlier one
// from the clone which still has a PageState), and this will cause the renderer
// to crash in NavigateInternal because the PageState is present but the page_id
// is -1 (similar to https://crbug.com/568703). See https://crbug.com/568768.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_RepeatCreatedFrame) {
NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
shell()->web_contents()->GetController());
// 1. Navigate the main frame.
GURL url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_links.html"));
EXPECT_TRUE(NavigateToURL(shell(), url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
SiteInstance* main_site_instance =
root->current_frame_host()->GetSiteInstance();
// 2. Add a cross-site subframe.
GURL frame_url(embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/simple_page_1.html"));
std::string script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + frame_url.spec() + "';"
"document.body.appendChild(iframe);";
{
LoadCommittedCapturer capturer(shell()->web_contents());
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
FrameTreeNode* subframe = root->child_at(0);
if (AreAllSitesIsolatedForTesting()) {
EXPECT_NE(main_site_instance,
subframe->current_frame_host()->GetSiteInstance());
}
FrameNavigationEntry* subframe_entry =
controller.GetLastCommittedEntry()->GetFrameEntry(subframe);
EXPECT_EQ(frame_url, subframe_entry->url());
// 3. Reload the main frame.
{
FrameNavigateParamsCapturer capturer(root);
controller.Reload(ReloadType::NORMAL, false);
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_RELOAD));
EXPECT_EQ(NAVIGATION_TYPE_EXISTING_PAGE, capturer.navigation_type());
EXPECT_FALSE(capturer.is_same_document());
}
// 4. Add the iframe again.
{
LoadCommittedCapturer capturer(shell()->web_contents());
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
if (AreAllSitesIsolatedForTesting()) {
EXPECT_NE(main_site_instance,
root->child_at(0)->current_frame_host()->GetSiteInstance());
}
}
// Verifies that item sequence numbers and document sequence numbers update
// properly for main frames and subframes.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_SequenceNumbers) {
const NavigationControllerImpl& controller =
static_cast<const NavigationControllerImpl&>(
shell()->web_contents()->GetController());
// 1. Navigate the main frame.
GURL url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_links.html"));
EXPECT_TRUE(NavigateToURL(shell(), url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
FrameNavigationEntry* frame_entry =
controller.GetLastCommittedEntry()->GetFrameEntry(root);
int64_t isn_1 = frame_entry->item_sequence_number();
int64_t dsn_1 = frame_entry->document_sequence_number();
EXPECT_NE(-1, isn_1);
EXPECT_NE(-1, dsn_1);
// 2. Do a same document fragment navigation.
std::string script = "document.getElementById('fraglink').click()";
EXPECT_TRUE(ExecuteScript(root, script));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
frame_entry = controller.GetLastCommittedEntry()->GetFrameEntry(root);
int64_t isn_2 = frame_entry->item_sequence_number();
int64_t dsn_2 = frame_entry->document_sequence_number();
EXPECT_NE(-1, isn_2);
EXPECT_NE(isn_1, isn_2);
EXPECT_EQ(dsn_1, dsn_2);
// 3. Add a subframe, which does an AUTO_SUBFRAME navigation.
{
LoadCommittedCapturer capturer(shell()->web_contents());
std::string add_script = "var iframe = document.createElement('iframe');"
"iframe.src = '" + url.spec() + "';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root, add_script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
// The root FrameNavigationEntry hasn't changed.
EXPECT_EQ(frame_entry,
controller.GetLastCommittedEntry()->GetFrameEntry(root));
// We should have a unique ISN and DSN for the subframe entry.
FrameTreeNode* subframe = root->child_at(0);
FrameNavigationEntry* subframe_entry =
controller.GetLastCommittedEntry()->GetFrameEntry(subframe);
int64_t isn_3 = subframe_entry->item_sequence_number();
int64_t dsn_3 = subframe_entry->document_sequence_number();
EXPECT_NE(-1, isn_2);
EXPECT_NE(isn_2, isn_3);
EXPECT_NE(dsn_2, dsn_3);
// 4. Do a same document fragment navigation in the subframe.
EXPECT_TRUE(ExecuteScript(subframe, script));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
subframe_entry = controller.GetLastCommittedEntry()->GetFrameEntry(subframe);
int64_t isn_4 = subframe_entry->item_sequence_number();
int64_t dsn_4 = subframe_entry->document_sequence_number();
EXPECT_NE(-1, isn_4);
EXPECT_NE(isn_3, isn_4);
EXPECT_EQ(dsn_3, dsn_4);
}
// Verifies that the FrameNavigationEntry's redirect chain is created for the
// main frame.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_MainFrameRedirectChain) {
const NavigationControllerImpl& controller =
static_cast<const NavigationControllerImpl&>(
shell()->web_contents()->GetController());
// Navigate the main frame to a redirecting URL (server-side)
GURL final_url(embedded_test_server()->GetURL("/simple_page.html"));
GURL redirecting_url(
embedded_test_server()->GetURL("/server-redirect?/simple_page.html"));
NavigateToURLBlockUntilNavigationsComplete(shell(), redirecting_url, 1);
EXPECT_TRUE(IsLastCommittedEntryOfPageType(shell()->web_contents(),
PAGE_TYPE_NORMAL));
EXPECT_TRUE(shell()->web_contents()->GetLastCommittedURL() == final_url);
// Check last committed NavigationEntry's redirects.
EXPECT_EQ(1, controller.GetEntryCount());
content::NavigationEntry* entry = controller.GetLastCommittedEntry();
EXPECT_EQ(entry->GetRedirectChain().size(), 2u);
EXPECT_EQ(entry->GetRedirectChain()[0], redirecting_url);
EXPECT_EQ(entry->GetRedirectChain()[1], final_url);
}
// Verifies that FrameNavigationEntry's redirect chain is created and stored on
// the right subframe (AUTO_SUBFRAME navigation).
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_AutoSubFrameRedirectChain) {
const NavigationControllerImpl& controller =
static_cast<const NavigationControllerImpl&>(
shell()->web_contents()->GetController());
GURL main_url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_iframe_redirect.html"));
GURL iframe_redirect_url(
embedded_test_server()->GetURL("/server-redirect?/simple_page.html"));
GURL iframe_final_url(embedded_test_server()->GetURL("/simple_page.html"));
// Navigate to a page with an redirecting iframe.
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// Check that the main frame redirect chain contains only one url.
EXPECT_EQ(1, controller.GetEntryCount());
NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
EXPECT_EQ(entry->GetRedirectChain().size(), 1u);
EXPECT_EQ(entry->GetRedirectChain()[0], main_url);
// Check that the FrameNavigationEntry's redirect chain contains 2 urls.
ASSERT_EQ(1U, entry->root_node()->children.size());
FrameNavigationEntry* frame_entry =
entry->root_node()->children[0]->frame_entry.get();
EXPECT_EQ(frame_entry->redirect_chain().size(), 2u);
EXPECT_EQ(frame_entry->redirect_chain()[0], iframe_redirect_url);
EXPECT_EQ(frame_entry->redirect_chain()[1], iframe_final_url);
}
// Verifies that FrameNavigationEntry's redirect chain is created and stored on
// the right subframe (NEW_SUBFRAME navigation).
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
FrameNavigationEntry_NewSubFrameRedirectChain) {
const NavigationControllerImpl& controller =
static_cast<const NavigationControllerImpl&>(
shell()->web_contents()->GetController());
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
// 1. Navigate to a page with an iframe.
GURL main_url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_data_iframe.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
EXPECT_EQ(1, controller.GetEntryCount());
// 2. Navigate in the subframe with a redirection.
GURL frame_final_url(embedded_test_server()->GetURL("/simple_page.html"));
GURL frame_redirect_url(
embedded_test_server()->GetURL("/server-redirect?/simple_page.html"));
NavigateFrameToURL(root->child_at(0), frame_redirect_url);
// Check that the main frame redirect chain contains only the main_url.
EXPECT_EQ(2, controller.GetEntryCount());
NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
EXPECT_EQ(entry->GetRedirectChain().size(), 1u);
EXPECT_EQ(entry->GetRedirectChain()[0], main_url);
// Check that the FrameNavigationEntry's redirect chain contains 2 urls.
ASSERT_EQ(1U, entry->root_node()->children.size());
FrameNavigationEntry* frame_entry =
entry->root_node()->children[0]->frame_entry.get();
EXPECT_EQ(frame_entry->redirect_chain().size(), 2u);
EXPECT_EQ(frame_entry->redirect_chain()[0], frame_redirect_url);
EXPECT_EQ(frame_entry->redirect_chain()[1], frame_final_url);
}
// Support a set of tests that isolate only a subset of sites with
// out-of-process iframes (OOPIFs).
class NavigationControllerOopifBrowserTest
: public NavigationControllerBrowserTest {
public:
NavigationControllerOopifBrowserTest() {}
void SetUpCommandLine(base::CommandLine* command_line) override {
// Enable the OOPIF framework but only isolate sites from a single TLD.
command_line->AppendSwitchASCII(switches::kIsolateSitesForTesting, "*.is");
}
private:
DISALLOW_COPY_AND_ASSIGN(NavigationControllerOopifBrowserTest);
};
// Verify that restoring a NavigationEntry with cross-site subframes does not
// create out-of-process iframes unless the current SiteIsolationPolicy says to.
IN_PROC_BROWSER_TEST_F(NavigationControllerOopifBrowserTest,
RestoreWithoutExtraOopifs) {
// 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");
EXPECT_TRUE(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();
EXPECT_EQ(main_url_a, root->current_url());
EXPECT_EQ(data_url, root->child_at(0)->current_url());
// 2. Navigate the iframe cross-site.
GURL frame_url_b(embedded_test_server()->GetURL(
"b.com", "/navigation_controller/simple_page_1.html"));
NavigateFrameToURL(root->child_at(0), frame_url_b);
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();
// 3. Create a NavigationEntry with the same PageState as |entry2|.
std::unique_ptr<NavigationEntryImpl> restored_entry =
NavigationEntryImpl::FromNavigationEntry(
NavigationControllerImpl::CreateNavigationEntry(
main_url_a, Referrer(), ui::PAGE_TRANSITION_RELOAD, false,
std::string(), controller.GetBrowserContext()));
EXPECT_EQ(0U, restored_entry->root_node()->children.size());
restored_entry->SetPageState(entry2->GetPageState());
// The entry should have no SiteInstance in the FrameNavigationEntry for the
// b.com subframe.
EXPECT_FALSE(
restored_entry->root_node()->children[0]->frame_entry->site_instance());
// 4. Restore the new entry in a new tab and verify the correct URLs load.
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_a, new_root->current_url());
EXPECT_EQ(frame_url_b, new_root->child_at(0)->current_url());
// The subframe should only be in a different SiteInstance if OOPIFs are
// required for all sites.
if (AreAllSitesIsolatedForTesting()) {
EXPECT_NE(new_root->current_frame_host()->GetSiteInstance(),
new_root->child_at(0)->current_frame_host()->GetSiteInstance());
} else {
EXPECT_EQ(new_root->current_frame_host()->GetSiteInstance(),
new_root->child_at(0)->current_frame_host()->GetSiteInstance());
}
}
namespace {
// Loads |start_url|, then loads |stalled_url| which stalls. While the page is
// stalled, a same document navigation happens. Make sure that all the
// navigations are properly classified.
void DoReplaceStateWhilePending(Shell* shell,
const GURL& start_url,
const GURL& stalled_url,
const std::string& replace_state_filename) {
NavigationControllerImpl& controller =
static_cast<NavigationControllerImpl&>(
shell->web_contents()->GetController());
FrameTreeNode* root =
static_cast<WebContentsImpl*>(shell->web_contents())->
GetFrameTree()->root();
// Start with one page.
EXPECT_TRUE(NavigateToURL(shell, start_url));
// Have the user decide to go to a different page which is very slow.
NavigationStallDelegate stall_delegate(stalled_url);
ResourceDispatcherHost::Get()->SetDelegate(&stall_delegate);
controller.LoadURL(
stalled_url, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
// That should be the pending entry.
NavigationEntryImpl* entry = controller.GetPendingEntry();
ASSERT_NE(nullptr, entry);
EXPECT_EQ(stalled_url, entry->GetURL());
{
// Now the existing page uses history.replaceState().
FrameNavigateParamsCapturer capturer(root);
capturer.set_wait_for_load(false);
std::string script =
"history.replaceState({}, '', '" + replace_state_filename + "')";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
// The fact that there was a pending entry shouldn't interfere with the
// classification.
EXPECT_EQ(NAVIGATION_TYPE_EXISTING_PAGE, capturer.navigation_type());
EXPECT_TRUE(capturer.is_same_document());
}
ResourceDispatcherHost::Get()->SetDelegate(nullptr);
}
} // namespace
IN_PROC_BROWSER_TEST_F(
NavigationControllerBrowserTest,
NavigationTypeClassification_On1SameDocumentToXWhile2Pending) {
GURL url1(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
GURL url2(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
DoReplaceStateWhilePending(shell(), url1, url2, "x");
}
IN_PROC_BROWSER_TEST_F(
NavigationControllerBrowserTest,
NavigationTypeClassification_On1SameDocumentTo2While2Pending) {
GURL url1(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
GURL url2(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
DoReplaceStateWhilePending(shell(), url1, url2, "simple_page_2.html");
}
IN_PROC_BROWSER_TEST_F(
NavigationControllerBrowserTest,
NavigationTypeClassification_On1SameDocumentToXWhile1Pending) {
GURL url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
DoReplaceStateWhilePending(shell(), url, url, "x");
}
IN_PROC_BROWSER_TEST_F(
NavigationControllerBrowserTest,
NavigationTypeClassification_On1SameDocumentTo1While1Pending) {
GURL url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
DoReplaceStateWhilePending(shell(), url, url, "simple_page_1.html");
}
// Ensure that a pending NavigationEntry for a different navigation doesn't
// cause a commit to be incorrectly treated as a replacement.
// See https://crbug.com/593153.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
OtherCommitDuringPendingEntryWithReplacement) {
NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
shell()->web_contents()->GetController());
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
// Load an initial page.
GURL start_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
int entry_count = controller.GetEntryCount();
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(start_url, controller.GetLastCommittedEntry()->GetURL());
// Start a cross-process navigation with replacement, which never completes.
GURL foo_url(embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/page_with_links.html"));
NavigationStallDelegate stall_delegate(foo_url);
ResourceDispatcherHost::Get()->SetDelegate(&stall_delegate);
NavigationController::LoadURLParams params(foo_url);
params.should_replace_current_entry = true;
controller.LoadURLWithParams(params);
// That should be the pending entry.
NavigationEntryImpl* entry = controller.GetPendingEntry();
ASSERT_NE(nullptr, entry);
EXPECT_EQ(foo_url, entry->GetURL());
EXPECT_EQ(entry_count, controller.GetEntryCount());
{
// Now the existing page uses history.pushState() while the pending entry
// for the other navigation still exists.
FrameNavigateParamsCapturer capturer(root);
capturer.set_wait_for_load(false);
std::string script = "history.pushState({}, '', 'pushed')";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
EXPECT_EQ(NAVIGATION_TYPE_NEW_PAGE, capturer.navigation_type());
EXPECT_TRUE(capturer.is_same_document());
}
// The same document navigation should not have replaced the previous entry.
GURL push_state_url(
embedded_test_server()->GetURL("/navigation_controller/pushed"));
EXPECT_EQ(entry_count + 1, controller.GetEntryCount());
EXPECT_EQ(push_state_url, controller.GetLastCommittedEntry()->GetURL());
EXPECT_EQ(start_url, controller.GetEntryAtIndex(0)->GetURL());
ResourceDispatcherHost::Get()->SetDelegate(nullptr);
}
// This test ensures that if we go back from a page that has a replaceState()
// call in the window.beforeunload function, we commit to the proper navigation
// entry. https://crbug.com/597239
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
BackFromPageWithReplaceStateInBeforeUnload) {
NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
shell()->web_contents()->GetController());
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
// Load an initial page.
GURL start_url(embedded_test_server()->GetURL(
"/navigation_controller/beforeunload_replacestate_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(start_url, controller.GetLastCommittedEntry()->GetURL());
// Go to the second page.
std::string script = "document.getElementById('thelink').click()";
EXPECT_TRUE(ExecuteScript(root, script));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
// Go back to the first page, which never completes. The attempt to unload the
// second page, though, causes it to do a replaceState().
TestNavigationManager manager(shell()->web_contents(), start_url);
controller.GoBack();
EXPECT_TRUE(manager.WaitForRequestStart());
// The navigation that just happened was the replaceState(), which should not
// have changed the position into the navigation entry list. Make sure that
// the pending navigation didn't confuse anything.
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
}
// Ensure the renderer process does not get confused about the current entry
// due to subframes and replaced entries. See https://crbug.com/480201.
// TODO(creis): Re-enable for Site Isolation FYI bots: https://crbug.com/502317.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
PreventSpoofFromSubframeAndReplace) {
// Start at an initial URL.
GURL url1(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url1));
// Now go to a page with a real iframe.
GURL url2(embedded_test_server()->GetURL(
"/navigation_controller/page_with_data_iframe.html"));
EXPECT_TRUE(NavigateToURL(shell(), url2));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
ASSERT_NE(nullptr, root->child_at(0));
{
// Navigate in the iframe.
FrameNavigateParamsCapturer capturer(root->child_at(0));
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
NavigateFrameToURL(root->child_at(0), frame_url);
capturer.Wait();
EXPECT_EQ(NAVIGATION_TYPE_NEW_SUBFRAME, capturer.navigation_type());
}
{
// Go back in the iframe.
TestNavigationObserver back_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoBack();
back_load_observer.Wait();
}
{
// Go forward in the iframe.
TestNavigationObserver forward_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoForward();
forward_load_observer.Wait();
}
GURL url3(embedded_test_server()->GetURL(
"/navigation_controller/page_with_iframe.html"));
{
// location.replace() to cause an inert commit.
TestNavigationObserver replace_load_observer(shell()->web_contents());
std::string script = "location.replace('" + url3.spec() + "')";
EXPECT_TRUE(ExecuteScript(root, script));
replace_load_observer.Wait();
}
{
// Go back to url2.
TestNavigationObserver back_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoBack();
back_load_observer.Wait();
// Make sure the URL is correct for both the entry and the main frame, and
// that the process hasn't been killed for showing a spoof.
EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
EXPECT_EQ(url2, shell()->web_contents()->GetLastCommittedURL());
EXPECT_EQ(url2, root->current_url());
}
{
// Go back to reset main frame entirely.
TestNavigationObserver back_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoBack();
back_load_observer.Wait();
EXPECT_EQ(url1, shell()->web_contents()->GetLastCommittedURL());
EXPECT_EQ(url1, root->current_url());
}
{
// Go forward.
TestNavigationObserver back_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoForward();
back_load_observer.Wait();
EXPECT_EQ(url2, shell()->web_contents()->GetLastCommittedURL());
EXPECT_EQ(url2, root->current_url());
}
{
// Go forward to the replaced URL.
TestNavigationObserver forward_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoForward();
forward_load_observer.Wait();
// Make sure the URL is correct for both the entry and the main frame, and
// that the process hasn't been killed for showing a spoof.
EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
EXPECT_EQ(url3, shell()->web_contents()->GetLastCommittedURL());
EXPECT_EQ(url3, root->current_url());
}
}
// Ensure the renderer process does not get killed if the main frame URL's path
// changes when going back in a subframe, since this is currently possible after
// a replaceState in the main frame (thanks to https://crbug.com/373041).
// See https:///crbug.com/486916.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
SubframeBackFromReplaceState) {
// Start at a page with a real iframe.
GURL url1(embedded_test_server()->GetURL(
"/navigation_controller/page_with_data_iframe.html"));
EXPECT_TRUE(NavigateToURL(shell(), url1));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
ASSERT_NE(nullptr, root->child_at(0));
{
// Navigate in the iframe.
FrameNavigateParamsCapturer capturer(root->child_at(0));
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
NavigateFrameToURL(root->child_at(0), frame_url);
capturer.Wait();
EXPECT_EQ(NAVIGATION_TYPE_NEW_SUBFRAME, capturer.navigation_type());
}
{
// history.replaceState().
FrameNavigateParamsCapturer capturer(root);
std::string script =
"history.replaceState({}, 'replaced', 'replaced')";
EXPECT_TRUE(ExecuteScript(root, script));
capturer.Wait();
}
{
// Go back in the iframe.
TestNavigationObserver back_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoBack();
back_load_observer.Wait();
}
// For now, we expect the main frame's URL to revert. This won't happen once
// https://crbug.com/373041 is fixed.
EXPECT_EQ(url1, shell()->web_contents()->GetLastCommittedURL());
// Make sure the renderer process has not been killed.
EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
}
namespace {
class FailureWatcher : public WebContentsObserver {
public:
// Observes failure for the specified |node|.
explicit FailureWatcher(FrameTreeNode* node)
: WebContentsObserver(
node->current_frame_host()->delegate()->GetAsWebContents()),
frame_tree_node_id_(node->frame_tree_node_id()),
message_loop_runner_(new MessageLoopRunner) {}
void Wait() {
message_loop_runner_->Run();
}
private:
void DidFailLoad(RenderFrameHost* render_frame_host,
const GURL& validated_url,
int error_code,
const base::string16& error_description) override {
RenderFrameHostImpl* rfh =
static_cast<RenderFrameHostImpl*>(render_frame_host);
if (rfh->frame_tree_node()->frame_tree_node_id() != frame_tree_node_id_)
return;
message_loop_runner_->Quit();
}
void DidFinishNavigation(NavigationHandle* handle) override {
if (handle->HasCommitted() ||
handle->GetFrameTreeNodeId() != frame_tree_node_id_) {
return;
}
message_loop_runner_->Quit();
}
// The id of the FrameTreeNode whose navigations to observe.
int frame_tree_node_id_;
// The MessageLoopRunner used to spin the message loop.
scoped_refptr<MessageLoopRunner> message_loop_runner_;
};
} // namespace
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
DISABLED_StopCausesFailureDespiteJavaScriptURL) {
NavigationControllerImpl& controller =
static_cast<NavigationControllerImpl&>(
shell()->web_contents()->GetController());
FrameTreeNode* root =
static_cast<WebContentsImpl*>(shell()->web_contents())->
GetFrameTree()->root();
// Start with a normal page.
GURL url1(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url1));
// Have the user decide to go to a different page which will not commit.
GURL url2(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
NavigationStallDelegate stall_delegate(url2);
ResourceDispatcherHost::Get()->SetDelegate(&stall_delegate);
controller.LoadURL(url2, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
// That should be the pending entry.
NavigationEntryImpl* entry = controller.GetPendingEntry();
ASSERT_NE(nullptr, entry);
EXPECT_EQ(url2, entry->GetURL());
// Loading a JavaScript URL shouldn't affect the ability to stop.
{
FailureWatcher watcher(root);
GURL js("javascript:(function(){})()");
controller.LoadURL(js, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
// This LoadURL ends up purging the pending entry, which is why this is
// tricky.
EXPECT_EQ(nullptr, controller.GetPendingEntry());
EXPECT_TRUE(shell()->web_contents()->IsLoading());
shell()->web_contents()->Stop();
watcher.Wait();
EXPECT_FALSE(shell()->web_contents()->IsLoading());
}
ResourceDispatcherHost::Get()->SetDelegate(nullptr);
}
namespace {
class RenderProcessKilledObserver : public WebContentsObserver {
public:
explicit RenderProcessKilledObserver(WebContents* web_contents)
: WebContentsObserver(web_contents) {}
~RenderProcessKilledObserver() override {}
void RenderProcessGone(base::TerminationStatus status) override {
CHECK_NE(status,
base::TerminationStatus::TERMINATION_STATUS_PROCESS_WAS_KILLED);
}
};
} // namespace
// This tests a race in Reload with ReloadType::ORIGINAL_REQUEST_URL, where a
// cross-origin reload was causing an in-flight replaceState to look like a
// cross-origin navigation, even though it's same document. (The reload should
// not modify the underlying last committed entry.) Not crashing means that
// the test is successful.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, ReloadOriginalRequest) {
GURL original_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), original_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
RenderProcessKilledObserver kill_observer(shell()->web_contents());
// Redirect so that we can use Reload with ReloadType::ORIGINAL_REQUEST_URL.
GURL redirect_url(embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/simple_page_1.html"));
{
std::string script = "location.replace('" + redirect_url.spec() + "');";
FrameNavigateParamsCapturer capturer(root);
EXPECT_TRUE(ExecuteScript(shell(), script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(),
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
ui::PAGE_TRANSITION_CLIENT_REDIRECT)));
EXPECT_EQ(NAVIGATION_TYPE_EXISTING_PAGE, capturer.navigation_type());
}
// Modify an entry in the session history and reload the original request.
{
// We first send a replaceState() to the renderer, which will cause the
// renderer to send back a DidCommitProvisionalLoad. Immediately after,
// we send a Reload request with ReloadType::ORIGINAL_REQUEST_URL (which in
// this case is a different origin) and will also cause the renderer to
// commit the frame. In the end we verify that both navigations committed
// and that the URLs are correct.
std::string script = "history.replaceState({}, '', 'foo');";
root->render_manager()
->current_frame_host()
->ExecuteJavaScriptWithUserGestureForTests(base::UTF8ToUTF16(script));
EXPECT_FALSE(shell()->web_contents()->IsLoading());
shell()->web_contents()->GetController().Reload(
ReloadType::ORIGINAL_REQUEST_URL, false);
EXPECT_TRUE(shell()->web_contents()->IsLoading());
EXPECT_EQ(redirect_url, shell()->web_contents()->GetLastCommittedURL());
// Wait until there's no more navigations.
GURL modified_url(embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/foo"));
FrameNavigateParamsCapturer capturer(root);
capturer.set_wait_for_load(false);
capturer.set_navigations_remaining(2);
capturer.Wait();
EXPECT_EQ(2U, capturer.urls().size());
EXPECT_EQ(modified_url, capturer.urls()[0]);
EXPECT_EQ(original_url, capturer.urls()[1]);
EXPECT_EQ(original_url, shell()->web_contents()->GetLastCommittedURL());
}
// Make sure the renderer is still alive.
EXPECT_TRUE(ExecuteScript(shell(), "console.log('Success');"));
}
// This test shows that the initial "about:blank" URL is elided from the
// navigation history of a subframe when it is loaded.
//
// It also prevents regression for an same document navigation renderer kill
// when going back after an in-page navigation in the main frame is followed by
// an auto subframe navigation, due to a bug in HistoryEntry::CloneAndReplace.
// See https://crbug.com/612713.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
BackToAboutBlankIframe) {
GURL original_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), original_url));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
NavigationController& controller = shell()->web_contents()->GetController();
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(1, RendererHistoryLength(shell()));
// Add an iframe with no 'src'.
std::string script =
"var iframe = document.createElement('iframe');"
"iframe.id = 'frame';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root, script));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(1, RendererHistoryLength(shell()));
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* frame = root->child_at(0);
ASSERT_NE(nullptr, frame);
GURL blank_url(url::kAboutBlankURL);
EXPECT_EQ(blank_url, frame->current_url());
// Now create a new navigation entry. Note that the old navigation entry has
// "about:blank" as the URL in the iframe.
script = "history.pushState({}, '', 'notarealurl.html')";
EXPECT_TRUE(ExecuteScript(root, script));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(2, RendererHistoryLength(shell()));
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
// Load the iframe; the initial "about:blank" URL should be elided and thus we
// shouldn't get a new navigation entry.
GURL frame_url = embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/simple_page_2.html");
NavigateFrameToURL(frame, frame_url);
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(2, RendererHistoryLength(shell()));
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(frame_url, frame->current_url());
// Go back.
{
TestNavigationObserver observer(shell()->web_contents(), 1);
ASSERT_TRUE(controller.CanGoBack());
controller.GoBack();
observer.Wait();
}
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(2, RendererHistoryLength(shell()));
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
// There is some open discussion over whether this should send the iframe
// back to the blank page, but for now it stays in place to preserve
// compatibility with existing sites. See
// NavigationControllerImpl::FindFramesToNavigate for more information, as
// well as http://crbug.com/542299, https://crbug.com/598043 (for the
// regressions caused by going back), and
// https://github.com/whatwg/html/issues/546.
// TODO(avi, creis): Figure out the correct behavior to use here.
EXPECT_EQ(frame_url, frame->current_url());
// Now test for https://crbug.com/612713 to prevent an NC_IN_PAGE_NAVIGATION
// renderer kill.
// Do a same document navigation in the subframe.
std::string fragment_script = "location.href = \"#foo\";";
EXPECT_TRUE(ExecuteScript(frame->current_frame_host(), fragment_script));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(2, RendererHistoryLength(shell()));
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
GURL frame_url_2 = embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/simple_page_2.html#foo");
EXPECT_EQ(frame_url_2, frame->current_url());
// Go back.
{
TestNavigationObserver observer(shell()->web_contents(), 1);
controller.GoBack();
observer.Wait();
}
// Verify the process is still alive by running script. We can't just call
// IsRenderFrameLive after the navigation since it might not have disconnected
// yet.
EXPECT_TRUE(ExecuteScript(root->current_frame_host(), "true;"));
EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
// TODO(creis): We should probably go back to frame_url here instead of the
// initial blank page. That might require updating all relevant NavEntries to
// know what the first committed URL is, so that we really elide the initial
// blank page from history.
EXPECT_EQ(blank_url, frame->current_url());
}
// This test is similar to "BackToAboutBlankIframe" above, except that a
// fragment navigation is used rather than pushState (both create a same
// document navigation, so we need to test both), and an initial 'src' is given
// to the iframe to test proper restoration in that case.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
BackToIframeWithContent) {
GURL links_url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_links.html"));
EXPECT_TRUE(NavigateToURL(shell(), links_url));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
NavigationController& controller = shell()->web_contents()->GetController();
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(1, RendererHistoryLength(shell()));
// Add an iframe with a 'src'.
GURL frame_url_1 = embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html");
std::string script =
"var iframe = document.createElement('iframe');"
"iframe.src = '" + frame_url_1.spec() + "';"
"iframe.id = 'frame';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root, script));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(1, RendererHistoryLength(shell()));
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* frame = root->child_at(0);
ASSERT_NE(nullptr, frame);
EXPECT_EQ(frame_url_1, frame->current_url());
// Do a fragment navigation, creating a new navigation entry. Note that the
// old navigation entry has frame_url_1 as the URL in the iframe.
script = "document.getElementById('fraglink').click()";
EXPECT_TRUE(ExecuteScript(root, script));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(2, RendererHistoryLength(shell()));
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(frame_url_1, frame->current_url());
// Navigate the iframe; unlike the test "BackToAboutBlankIframe" above, this
// _will_ create a new navigation entry.
GURL frame_url_2 = embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/simple_page_2.html");
NavigateFrameToURL(frame, frame_url_2);
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(3, RendererHistoryLength(shell()));
EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(frame_url_2, frame->current_url());
// Go back two entries.
{
TestNavigationObserver observer(shell()->web_contents(), 1);
ASSERT_TRUE(controller.CanGoToOffset(-2));
controller.GoToOffset(-2);
observer.Wait();
}
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(3, RendererHistoryLength(shell()));
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
// There is some open discussion over whether this should send the iframe back
// to the original page, but for now it stays in place to preserve
// compatibility with existing sites. See
// NavigationControllerImpl::FindFramesToNavigate for more information, as
// well as http://crbug.com/542299, https://crbug.com/598043 (for the
// regressions caused by going back), and
// https://github.com/whatwg/html/issues/546.
// TODO(avi, creis): Figure out the correct behavior to use here.
EXPECT_EQ(frame_url_2, frame->current_url());
// Now test for https://crbug.com/612713 to prevent an NC_IN_PAGE_NAVIGATION
// renderer kill.
// Do a same document navigation in the subframe.
std::string fragment_script = "location.href = \"#foo\";";
EXPECT_TRUE(ExecuteScript(frame->current_frame_host(), fragment_script));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(2, RendererHistoryLength(shell()));
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
// Go back.
{
TestNavigationObserver observer(shell()->web_contents(), 1);
controller.GoBack();
observer.Wait();
}
// Verify the process is still alive by running script. We can't just call
// IsRenderFrameLive after the navigation since it might not have disconnected
// yet.
EXPECT_TRUE(ExecuteScript(root->current_frame_host(), "true;"));
EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
// TODO(creis): It's a bit surprising to go to frame_url_1 here instead of
// frame_url_2. Perhaps we should be going back to frame_url_1 when going
// back two entries above, since it's different than the initial blank case.
EXPECT_EQ(frame_url_1, frame->current_url());
}
// Test for same document navigation kills due to using the wrong history item
// in HistoryController::RecursiveGoToEntry and
// NavigationControllerImpl::FindFramesToNavigate.
// See https://crbug.com/612713.
//
// TODO(creis): Enable this test when https://crbug.com/618100 is fixed.
// Disabled for now while we switch to the new navigation path, since this kill
// is exceptionally rare in practice.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
DISABLED_BackTwiceToIframeWithContent) {
GURL links_url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_links.html"));
EXPECT_TRUE(NavigateToURL(shell(), links_url));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
NavigationController& controller = shell()->web_contents()->GetController();
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(1, RendererHistoryLength(shell()));
// Add an iframe with a 'src'.
GURL frame_url_1 = embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html");
std::string script =
"var iframe = document.createElement('iframe');"
"iframe.src = '" + frame_url_1.spec() + "';"
"iframe.id = 'frame';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root->current_frame_host(), script));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(1, RendererHistoryLength(shell()));
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* frame = root->child_at(0);
ASSERT_NE(nullptr, frame);
EXPECT_EQ(frame_url_1, frame->current_url());
// Do a same document navigation in the subframe.
GURL frame_url_2 = embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html#foo");
std::string fragment_script = "location.href = \"#foo\";";
EXPECT_TRUE(ExecuteScript(frame->current_frame_host(), fragment_script));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(2, RendererHistoryLength(shell()));
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(frame_url_2, frame->current_url());
// Do a fragment navigation at the top level.
std::string link_script = "document.getElementById('fraglink').click()";
EXPECT_TRUE(ExecuteScript(root->current_frame_host(), link_script));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(3, RendererHistoryLength(shell()));
EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(frame_url_2, frame->current_url());
// Go cross-site in the iframe.
GURL frame_url_3 = embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/simple_page_2.html");
NavigateFrameToURL(frame, frame_url_3);
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(4, controller.GetEntryCount());
EXPECT_EQ(4, RendererHistoryLength(shell()));
EXPECT_EQ(3, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(frame_url_3, frame->current_url());
// Go back two entries.
{
TestNavigationObserver observer(shell()->web_contents(), 1);
ASSERT_TRUE(controller.CanGoToOffset(-2));
controller.GoToOffset(-2);
observer.Wait();
}
EXPECT_EQ(4, controller.GetEntryCount());
EXPECT_EQ(4, RendererHistoryLength(shell()));
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(links_url, root->current_url());
// There is some open discussion over whether this should send the iframe back
// to the original page, but for now it stays in place to preserve
// compatibility with existing sites. See
// NavigationControllerImpl::FindFramesToNavigate for more information, as
// well as http://crbug.com/542299, https://crbug.com/598043 (for the
// regressions caused by going back), and
// https://github.com/whatwg/html/issues/546.
// TODO(avi, creis): Figure out the correct behavior to use here.
EXPECT_EQ(frame_url_3, frame->current_url());
// Now test for https://crbug.com/612713 to prevent an NC_IN_PAGE_NAVIGATION
// renderer kill.
// Go back.
{
TestNavigationObserver observer(shell()->web_contents(), 1);
controller.GoBack();
observer.Wait();
}
// Verify the process is still alive by running script. We can't just call
// IsRenderFrameLive after the navigation since it might not have disconnected
// yet.
EXPECT_TRUE(ExecuteScript(root->current_frame_host(), "true;"));
EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
// TODO(creis): It's a bit surprising to go to frame_url_1 here instead of
// frame_url_2. Perhaps we should be going back to frame_url_1 when going
// back two entries above, since it's different than the initial blank case.
EXPECT_EQ(frame_url_1, frame->current_url());
}
// Test for same document navigation kills when going back to about:blank after
// a document.write. See https://crbug.com/446959.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
BackAfterIframeDocumentWrite) {
GURL links_url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_links.html"));
EXPECT_TRUE(NavigateToURL(shell(), links_url));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
NavigationController& controller = shell()->web_contents()->GetController();
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(1, RendererHistoryLength(shell()));
// Add an iframe with no 'src'.
GURL blank_url(url::kAboutBlankURL);
std::string script =
"var iframe = document.createElement('iframe');"
"iframe.id = 'frame';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root->current_frame_host(), script));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(1, RendererHistoryLength(shell()));
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* frame = root->child_at(0);
ASSERT_NE(nullptr, frame);
EXPECT_EQ(blank_url, frame->current_url());
// Do a document.write in the subframe to create a link to click.
std::string document_write_script =
"var iframe = document.getElementById('frame');"
"iframe.contentWindow.document.write("
" \"<a id='fraglink' href='#frag'>fragment link</a>\");"
"iframe.contentWindow.document.close();";
EXPECT_TRUE(ExecuteScript(root->current_frame_host(), document_write_script));
// Click the link to do a same document navigation. Due to the
// document.write, the new URL matches the parent frame's URL.
GURL frame_url_2(embedded_test_server()->GetURL(
"/navigation_controller/page_with_links.html#frag"));
std::string link_script = "document.getElementById('fraglink').click()";
EXPECT_TRUE(ExecuteScript(frame->current_frame_host(), link_script));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(2, RendererHistoryLength(shell()));
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(frame_url_2, frame->current_url());
// Go back.
{
TestNavigationObserver observer(shell()->web_contents(), 1);
controller.GoBack();
observer.Wait();
}
// Verify the process is still alive by running script. We can't just call
// IsRenderFrameLive after the navigation since it might not have disconnected
// yet.
EXPECT_TRUE(ExecuteScript(root->current_frame_host(), "true;"));
EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
EXPECT_EQ(blank_url, frame->current_url());
}
// Test for same document navigation kills when going back to about:blank in an
// iframe of a data URL, after a document.write. This differs from
// BackAfterIframeDocumentWrite because both about:blank and the data URL are
// considered unique origins. See https://crbug.com/446959.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
BackAfterIframeDocumentWriteInDataURL) {
GURL data_url("data:text/html,Top level page");
EXPECT_TRUE(NavigateToURL(shell(), data_url));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
NavigationController& controller = shell()->web_contents()->GetController();
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(1, RendererHistoryLength(shell()));
// Add an iframe with no 'src'.
GURL blank_url(url::kAboutBlankURL);
std::string script =
"var iframe = document.createElement('iframe');"
"iframe.id = 'frame';"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecuteScript(root->current_frame_host(), script));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(1, RendererHistoryLength(shell()));
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* frame = root->child_at(0);
ASSERT_NE(nullptr, frame);
EXPECT_EQ(blank_url, frame->current_url());
// Do a document.write in the subframe to create a link to click.
std::string document_write_script =
"var iframe = document.getElementById('frame');"
"iframe.contentWindow.document.write("
" \"<a id='fraglink' href='#frag'>fragment link</a>\");"
"iframe.contentWindow.document.close();";
EXPECT_TRUE(ExecuteScript(root->current_frame_host(), document_write_script));
// Click the link to do a same document navigation. Due to the
// document.write, the new URL matches the parent frame's URL.
GURL frame_url_2("data:text/html,Top level page#frag");
std::string link_script = "document.getElementById('fraglink').click()";
EXPECT_TRUE(ExecuteScript(frame->current_frame_host(), link_script));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(2, RendererHistoryLength(shell()));
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(frame_url_2, frame->current_url());
// Go back.
{
TestNavigationObserver observer(shell()->web_contents(), 1);
controller.GoBack();
observer.Wait();
}
// Verify the process is still alive by running script. We can't just call
// IsRenderFrameLive after the navigation since it might not have disconnected
// yet.
EXPECT_TRUE(ExecuteScript(root->current_frame_host(), "true;"));
EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
EXPECT_EQ(blank_url, frame->current_url());
}
// Ensure that we do not corrupt a NavigationEntry's PageState if a subframe
// forward navigation commits after we've already started another forward
// navigation in the main frame. See https://crbug.com/597322.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
ForwardInSubframeWithPendingForward) {
// Navigate to a page with an iframe.
GURL url_a(embedded_test_server()->GetURL(
"/navigation_controller/page_with_data_iframe.html"));
GURL frame_url_a1("data:text/html,Subframe");
EXPECT_TRUE(NavigateToURL(shell(), url_a));
NavigationController& controller = shell()->web_contents()->GetController();
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
EXPECT_EQ(url_a, root->current_url());
FrameTreeNode* frame = root->child_at(0);
EXPECT_EQ(frame_url_a1, frame->current_url());
// Navigate the iframe to a second page.
GURL frame_url_a2 = embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html");
NavigateFrameToURL(frame, frame_url_a2);
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(url_a, root->current_url());
EXPECT_EQ(frame_url_a2, frame->current_url());
// Navigate the top-level frame to another page with an iframe.
GURL url_b(embedded_test_server()->GetURL(
"/navigation_controller/page_with_iframe.html"));
GURL frame_url_b1(url::kAboutBlankURL);
EXPECT_TRUE(NavigateToURL(shell(), url_b));
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(url_b, root->current_url());
EXPECT_EQ(frame_url_b1, root->child_at(0)->current_url());
// Go back two entries. The original frame URL should be back.
ASSERT_TRUE(controller.CanGoToOffset(-2));
controller.GoToOffset(-2);
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(url_a, root->current_url());
EXPECT_EQ(frame_url_a1, root->child_at(0)->current_url());
// Go forward two times in a row, being careful that the subframe commits
// after the second forward navigation begins but before the main frame
// commits.
FrameTestNavigationManager subframe_delayer(
root->child_at(0)->frame_tree_node_id(), shell()->web_contents(),
frame_url_a2);
TestNavigationManager mainframe_delayer(shell()->web_contents(), url_b);
controller.GoForward();
EXPECT_TRUE(subframe_delayer.WaitForRequestStart());
controller.GoForward();
EXPECT_TRUE(mainframe_delayer.WaitForRequestStart());
EXPECT_EQ(2, controller.GetPendingEntryIndex());
// Let the subframe commit.
subframe_delayer.WaitForNavigationFinished();
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(url_a, root->current_url());
EXPECT_EQ(frame_url_a2, root->child_at(0)->current_url());
// Let the main frame commit.
mainframe_delayer.WaitForNavigationFinished();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(url_b, root->current_url());
EXPECT_EQ(frame_url_b1, root->child_at(0)->current_url());
// Check the PageState of the previous entry to ensure it isn't corrupted.
NavigationEntry* entry = controller.GetEntryAtIndex(1);
EXPECT_EQ(url_a, entry->GetURL());
ExplodedPageState exploded_state;
EXPECT_TRUE(
DecodePageState(entry->GetPageState().ToEncodedData(), &exploded_state));
EXPECT_EQ(url_a,
GURL(exploded_state.top.url_string.value_or(base::string16())));
EXPECT_EQ(frame_url_a2,
GURL(exploded_state.top.children.at(0).url_string.value_or(
base::string16())));
}
// Start a provisional navigation, but abort it by going back before it commits.
// In crbug.com/631617 there was an issue which cleared the
// pending_navigation_params_ in RenderFrameImpl. This caused the interrupting
// navigation to lose important navigation data like its nav_entry_id, which
// could cause it to commit in-place instead of in the correct location in the
// browsing history.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
AbortProvisionalLoadRetainsNavigationParams) {
EXPECT_TRUE(
NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
EXPECT_TRUE(
NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html")));
TestNavigationManager delayer(shell()->web_contents(),
embedded_test_server()->GetURL("/title3.html"));
shell()->LoadURL(embedded_test_server()->GetURL("/title3.html"));
EXPECT_TRUE(delayer.WaitForRequestStart());
NavigationController& controller = shell()->web_contents()->GetController();
TestNavigationManager back_manager(
shell()->web_contents(), embedded_test_server()->GetURL("/title1.html"));
controller.GoBack();
back_manager.WaitForNavigationFinished();
EXPECT_TRUE(controller.CanGoForward());
EXPECT_EQ(0, controller.GetCurrentEntryIndex());
}
// Ensure that we do not corrupt a NavigationEntry's PageState if two forward
// navigations compete in different frames. See https://crbug.com/623319.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
PageStateAfterForwardInCompetingFrames) {
// Navigate to a page with an iframe.
GURL url_a(embedded_test_server()->GetURL(
"/navigation_controller/page_with_data_iframe.html"));
GURL frame_url_a1("data:text/html,Subframe");
EXPECT_TRUE(NavigateToURL(shell(), url_a));
NavigationController& controller = shell()->web_contents()->GetController();
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
EXPECT_EQ(url_a, root->current_url());
EXPECT_EQ(frame_url_a1, root->child_at(0)->current_url());
// Navigate the iframe to a second page.
GURL frame_url_a2 = embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html");
NavigateFrameToURL(root->child_at(0), frame_url_a2);
// Navigate the iframe to about:blank.
GURL blank_url(url::kAboutBlankURL);
NavigateFrameToURL(root->child_at(0), blank_url);
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(url_a, root->current_url());
EXPECT_EQ(blank_url, root->child_at(0)->current_url());
// Go back to the middle entry.
controller.GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
// Replace the entry with a cross-site top-level page. By doing a
// replacement, the main frame pages before and after have the same item
// sequence number, and thus going between them only requires loads in the
// subframe.
GURL url_b(embedded_test_server()->GetURL(
"b.com", "/navigation_controller/simple_page_2.html"));
std::string replace_script = "location.replace('" + url_b.spec() + "')";
TestNavigationObserver replace_observer(shell()->web_contents());
EXPECT_TRUE(ExecuteScript(shell()->web_contents(), replace_script));
replace_observer.Wait();
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(url_b, root->current_url());
// Go back to the original page.
controller.GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
// Navigate forward twice using script. In https://crbug.com/623319, this
// caused a mismatch between the NavigationEntry's URL and PageState.
EXPECT_TRUE(ExecuteScript(shell()->web_contents(),
"history.forward(); history.forward();"));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(url_b, root->current_url());
NavigationEntry* entry = controller.GetLastCommittedEntry();
EXPECT_EQ(url_b, entry->GetURL());
ExplodedPageState exploded_state;
EXPECT_TRUE(
DecodePageState(entry->GetPageState().ToEncodedData(), &exploded_state));
EXPECT_EQ(url_b,
GURL(exploded_state.top.url_string.value_or(base::string16())));
EXPECT_EQ(0U, exploded_state.top.children.size());
// Go back and then forward to see if the PageState loads correctly.
controller.GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
controller.GoForward();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
// We should be on url_b, and the renderer process shouldn't be killed.
ASSERT_TRUE(root->current_frame_host()->IsRenderFrameLive());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(url_b, shell()->web_contents()->GetVisibleURL());
EXPECT_EQ(url_b, root->current_url());
EXPECT_EQ(0U, root->child_count());
}
// Ensure that we do not corrupt a NavigationEntry's PageState if two forward
// navigations compete in different frames, and the main frame entry contains an
// iframe of its own. See https://crbug.com/623319.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
PageStateWithIframeAfterForwardInCompetingFrames) {
// Navigate to a page with an iframe.
GURL url_a(embedded_test_server()->GetURL(
"/navigation_controller/page_with_data_iframe.html"));
GURL data_url("data:text/html,Subframe");
EXPECT_TRUE(NavigateToURL(shell(), url_a));
NavigationController& controller = shell()->web_contents()->GetController();
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
EXPECT_EQ(url_a, root->current_url());
EXPECT_EQ(data_url, root->child_at(0)->current_url());
// Navigate the iframe to a first real page.
GURL frame_url_a1 = embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html");
NavigateFrameToURL(root->child_at(0), frame_url_a1);
// Navigate the iframe to a second real page.
GURL frame_url_a2 = embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html");
NavigateFrameToURL(root->child_at(0), frame_url_a2);
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(url_a, root->current_url());
EXPECT_EQ(frame_url_a2, root->child_at(0)->current_url());
// Go back to the middle entry.
controller.GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
// Replace the entry with a cross-site top-level page with an iframe. By
// doing a replacement, the main frame pages before and after have the same
// item sequence number, and thus going between them only requires loads in
// the subframe.
GURL url_b(embedded_test_server()->GetURL(
"b.com", "/navigation_controller/page_with_data_iframe.html"));
std::string replace_script = "location.replace('" + url_b.spec() + "')";
TestNavigationObserver replace_observer(shell()->web_contents());
EXPECT_TRUE(ExecuteScript(shell()->web_contents(), replace_script));
replace_observer.Wait();
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(url_b, root->current_url());
EXPECT_EQ(data_url, root->child_at(0)->current_url());
// Go back to the original page.
controller.GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
// Navigate forward twice using script. This will race, but in either outcome
// we want to ensure that the subframes target entry index 1 and not 2. In
// https://crbug.com/623319, the subframes targeted the wrong entry, leading
// to a URL spoof and renderer kill.
EXPECT_TRUE(ExecuteScript(shell()->web_contents(),
"history.forward(); history.forward();"));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
EXPECT_EQ(url_b, root->current_url());
EXPECT_EQ(data_url, root->child_at(0)->current_url());
NavigationEntry* entry = controller.GetLastCommittedEntry();
EXPECT_EQ(url_b, entry->GetURL());
ExplodedPageState exploded_state;
EXPECT_TRUE(
DecodePageState(entry->GetPageState().ToEncodedData(), &exploded_state));
EXPECT_EQ(url_b,
GURL(exploded_state.top.url_string.value_or(base::string16())));
// Go back and then forward to see if the PageState loads correctly.
controller.GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
controller.GoForward();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
// We should be on url_b, and the renderer process shouldn't be killed.
ASSERT_TRUE(root->current_frame_host()->IsRenderFrameLive());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(url_b, shell()->web_contents()->GetVisibleURL());
EXPECT_EQ(url_b, root->current_url());
EXPECT_EQ(data_url, root->child_at(0)->current_url());
}
// Ensure that forward navigations in cloned tabs can commit if they redirect to
// a different site than before. This causes the navigation's item sequence
// number to change, meaning that we can't use it for determining whether the
// commit matches the history item. See https://crbug.com/600238.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
ForwardRedirectWithNoCommittedEntry) {
NavigationController& controller = shell()->web_contents()->GetController();
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
// Put 2 pages in history.
GURL url_1(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_1));
GURL url_2(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_2));
EXPECT_EQ(url_2, root->current_url());
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
// Do a replaceState to a URL that will redirect when we come back to it via
// session history.
GURL url_3(embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/page_with_links.html"));
{
TestNavigationObserver observer(shell()->web_contents());
std::string script =
"history.replaceState({}, '', '/server-redirect?" + url_3.spec() + "')";
EXPECT_TRUE(ExecuteScript(root, script));
observer.Wait();
}
// Go back.
controller.GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(url_1, root->current_url());
// Clone the tab without navigating it.
std::unique_ptr<WebContentsImpl> new_tab(
static_cast<WebContentsImpl*>(shell()->web_contents()->Clone()));
NavigationController& new_controller = new_tab->GetController();
FrameTreeNode* new_root = new_tab->GetFrameTree()->root();
EXPECT_TRUE(new_controller.IsInitialNavigation());
EXPECT_TRUE(new_controller.NeedsReload());
// Go forward in the new tab.
{
TestNavigationObserver observer(new_tab.get());
new_controller.GoForward();
observer.Wait();
}
EXPECT_TRUE(new_root->current_frame_host()->IsRenderFrameLive());
EXPECT_EQ(url_3, new_root->current_url());
}
// Ensure that we can support cross-process navigations in subframes due to
// redirects.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
SubframeForwardRedirect) {
NavigationController& controller = shell()->web_contents()->GetController();
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
GURL url_1(embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/page_with_data_iframe.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_1));
GURL frame_url(embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/simple_page_1.html"));
NavigateFrameToURL(root->child_at(0), frame_url);
EXPECT_EQ(url_1, root->current_url());
EXPECT_EQ(frame_url, root->child_at(0)->current_url());
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
// Do a replaceState to a URL that will redirect cross-site when we come back
// to it via session history.
GURL frame_url2(embedded_test_server()->GetURL(
"bar.com", "/navigation_controller/simple_page_2.html"));
{
TestNavigationObserver observer(shell()->web_contents());
std::string script = "history.replaceState({}, '', '/server-redirect?" +
frame_url2.spec() + "')";
EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
observer.Wait();
}
// Go back.
{
TestNavigationObserver observer(shell()->web_contents());
controller.GoBack();
observer.Wait();
}
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(url_1, root->current_url());
// Go forward.
{
TestNavigationObserver observer(shell()->web_contents());
controller.GoForward();
observer.Wait();
}
EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
EXPECT_TRUE(root->child_at(0)->current_frame_host()->IsRenderFrameLive());
EXPECT_EQ(url_1, root->current_url());
EXPECT_EQ(frame_url2, root->child_at(0)->current_url());
if (AreAllSitesIsolatedForTesting()) {
EXPECT_EQ(GURL("http://bar.com"), root->child_at(0)
->current_frame_host()
->GetSiteInstance()
->GetSiteURL());
}
}
// Tests that when using FrameNavigationEntries, knowledge of POST navigations
// is recorded on a subframe level.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
PostInSubframe) {
GURL page_with_form_url = embedded_test_server()->GetURL(
"/navigation_controller/subframe_form.html");
EXPECT_TRUE(NavigateToURL(shell(), page_with_form_url));
NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
shell()->web_contents()->GetController());
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
FrameTreeNode* frame = root->child_at(0);
EXPECT_EQ(1, controller.GetEntryCount());
{
NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
FrameNavigationEntry* root_entry = entry->GetFrameEntry(root);
FrameNavigationEntry* frame_entry = entry->GetFrameEntry(frame);
EXPECT_NE(nullptr, root_entry);
EXPECT_NE(nullptr, frame_entry);
EXPECT_EQ("GET", root_entry->method());
EXPECT_EQ(-1, root_entry->post_id());
EXPECT_EQ("GET", frame_entry->method());
EXPECT_EQ(-1, frame_entry->post_id());
EXPECT_FALSE(entry->GetHasPostData());
EXPECT_EQ(-1, entry->GetPostID());
}
// Submit the form.
TestNavigationObserver observer(shell()->web_contents(), 1);
ExecuteScriptAsync(shell(), "submitForm('isubmit')");
observer.Wait();
EXPECT_EQ(2, controller.GetEntryCount());
{
NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
FrameNavigationEntry* root_entry = entry->GetFrameEntry(root);
FrameNavigationEntry* frame_entry = entry->GetFrameEntry(frame);
EXPECT_NE(nullptr, root_entry);
EXPECT_NE(nullptr, frame_entry);
EXPECT_EQ("GET", root_entry->method());
EXPECT_EQ(-1, root_entry->post_id());
EXPECT_EQ("POST", frame_entry->method());
EXPECT_NE(-1, frame_entry->post_id());
EXPECT_FALSE(entry->GetHasPostData());
EXPECT_EQ(-1, entry->GetPostID());
}
}
// Tests that POST body is not lost when decidePolicyForNavigation tells the
// renderer to route the request via FrameHostMsg_OpenURL sent to the browser.
// See also https://crbug.com/344348.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, PostViaOpenUrlMsg) {
GURL main_url(
embedded_test_server()->GetURL("/form_that_posts_to_echoall.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// Ask the renderer to go through OpenURL FrameHostMsg_OpenURL IPC message.
// Without this, the test wouldn't repro https://crbug.com/344348.
shell()
->web_contents()
->GetMutableRendererPrefs()
->browser_handles_all_top_level_requests = true;
shell()->web_contents()->GetRenderViewHost()->SyncRendererPrefs();
// Submit the form.
TestNavigationObserver form_post_observer(shell()->web_contents(), 1);
EXPECT_TRUE(ExecuteScript(shell()->web_contents(),
"document.getElementById('form').submit();"));
form_post_observer.Wait();
// Verify that we arrived at the expected location.
GURL target_url(embedded_test_server()->GetURL("/echoall"));
EXPECT_EQ(target_url, shell()->web_contents()->GetLastCommittedURL());
// Verify that POST body was correctly passed to the server and ended up in
// the body of the page.
std::string body;
EXPECT_TRUE(ExecuteScriptAndExtractString(
shell()->web_contents(),
"window.domAutomationController.send("
"document.getElementsByTagName('pre')[0].innerText);",
&body));
EXPECT_EQ("text=value\n", body);
}
// Tests that inserting a named subframe into the FrameTree clears any
// previously existing FrameNavigationEntry objects for the same name.
// See https://crbug.com/628677.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
EnsureFrameNavigationEntriesClearedOnMismatch) {
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
NavigationControllerImpl& controller = web_contents->GetController();
FrameTreeNode* root = web_contents->GetFrameTree()->root();
// Start by navigating to a page with complex frame hierarchy.
GURL start_url(embedded_test_server()->GetURL("/frame_tree/top.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
EXPECT_EQ(3U, root->child_count());
EXPECT_EQ(2U, root->child_at(0)->child_count());
NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
// Verify only the parts of the NavigationEntry affected by this test.
{
// * Main frame has 3 subframes.
FrameNavigationEntry* root_entry = entry->GetFrameEntry(root);
EXPECT_NE(nullptr, root_entry);
EXPECT_EQ("", root_entry->frame_unique_name());
EXPECT_EQ(3U, entry->root_node()->children.size());
// * The first child of the main frame is named and has two more children.
FrameTreeNode* frame = root->child_at(0);
NavigationEntryImpl::TreeNode* tree_node = entry->GetTreeNode(frame);
FrameNavigationEntry* frame_entry = entry->GetFrameEntry(frame);
EXPECT_NE(nullptr, tree_node);
EXPECT_NE(nullptr, frame_entry);
EXPECT_EQ("1-1-name", frame_entry->frame_unique_name());
EXPECT_EQ(frame_entry, tree_node->frame_entry);
EXPECT_EQ(2U, tree_node->children.size());
}
// Removing the first child of the main frame should remove the corresponding
// FrameTreeNode.
EXPECT_TRUE(ExecuteScript(root, kRemoveFrameScript));
EXPECT_EQ(2U, root->child_count());
// However, the FrameNavigationEntry objects for the frame that was removed
// should still be around.
{
FrameNavigationEntry* root_entry = entry->GetFrameEntry(root);
EXPECT_NE(nullptr, root_entry);
EXPECT_EQ(3U, entry->root_node()->children.size());
// Since child count is known only to the FrameNavigationEntry::TreeNode,
// traverse the root entry to find the correct one matching the
// frame_unique_name. The ordering of entries in the FrameNavigationEntry
// tree is not guaranteed to be the same as the order in the FrameTreeNode
// tree. The latter depends on the order of frames committing navigations,
// which is undefined and depends on responses from the network.
// Traverse the FrameNavigationEntry tree, since the FrameTreeNode has
// been deleted and cannot be used for looking up the TreeNode.
NavigationEntryImpl::TreeNode* tree_node = nullptr;
for (auto& node : entry->root_node()->children) {
if (node->frame_entry->frame_unique_name() == "1-1-name") {
tree_node = node.get();
break;
}
}
EXPECT_TRUE(tree_node);
EXPECT_EQ(2U, tree_node->children.size());
}
// Now, insert a frame with the same name as the previously removed one
// at a different layer of the frame tree.
FrameTreeNode* subframe = root->child_at(1)->child_at(1)->child_at(0);
EXPECT_EQ(2U, root->child_at(1)->child_count());
EXPECT_EQ(0U, subframe->child_count());
std::string add_matching_name_frame_script =
"var f = document.createElement('iframe');"
"f.name = '1-1-name';"
"document.body.appendChild(f);";
EXPECT_TRUE(ExecuteScript(subframe, add_matching_name_frame_script));
EXPECT_EQ(1U, subframe->child_count());
// Verify that the FrameNavigationEntry for the original frame is now gone.
{
FrameNavigationEntry* root_entry = entry->GetFrameEntry(root);
EXPECT_NE(nullptr, root_entry);
EXPECT_EQ(2U, entry->root_node()->children.size());
}
}
// Tests that sending a PageState update from a named subframe does not get
// incorrectly set on previously existing FrameNavigationEntry for the same
// name. It is similar to EnsureFrameNavigationEntriesClearedOnMismatch, but
// doesn't navigate the iframes to real URLs when added to the DOM.
// See https://crbug.com/628677.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
EnsureFrameNavigationEntriesClearedOnMismatchNoSrc) {
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
FrameTreeNode* root = web_contents->GetFrameTree()->root();
GURL start_url(embedded_test_server()->GetURL("/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
NavigationEntryImpl* nav_entry =
web_contents->GetController().GetLastCommittedEntry();
EXPECT_TRUE(ExecuteScript(root, kAddNamedFrameScript));
EXPECT_EQ(1U, root->child_count());
EXPECT_EQ("foo-frame-name", root->child_at(0)->frame_name());
EXPECT_TRUE(ExecuteScript(root, kRemoveFrameScript));
EXPECT_EQ(0U, root->child_count());
// When a frame is removed from the page, the corresponding
// FrameNavigationEntry is not removed. This is done intentionally to support
// back-forward navigations in subframes and more intuitive UX on tab restore.
EXPECT_EQ(1U, nav_entry->root_node()->children.size());
FrameNavigationEntry* frame_entry =
nav_entry->root_node()->children[0]->frame_entry.get();
EXPECT_EQ("foo-frame-name", frame_entry->frame_unique_name());
EXPECT_TRUE(ExecuteScript(root, kAddFrameScript));
EXPECT_EQ(1U, root->child_count());
EXPECT_NE("foo-frame-name", root->child_at(0)->frame_name());
// Add a nested frame with the previously used name.
EXPECT_TRUE(ExecuteScript(root->child_at(0), kAddNamedFrameScript));
EXPECT_EQ(1U, root->child_at(0)->child_count());
EXPECT_EQ("foo-frame-name", root->child_at(0)->child_at(0)->frame_name());
EXPECT_EQ(1U, nav_entry->root_node()->children.size());
EXPECT_EQ(1U, nav_entry->root_node()->children[0]->children.size());
const auto& tree_node = nav_entry->root_node()->children[0]->children[0];
EXPECT_EQ(0U, tree_node->children.size());
EXPECT_EQ("foo-frame-name", tree_node->frame_entry->frame_unique_name());
EXPECT_TRUE(ExecuteScript(root->child_at(0), kRemoveFrameScript));
EXPECT_EQ(0U, root->child_at(0)->child_count());
}
// This test ensures that the comparison of tree position between a
// FrameTreeNode and FrameNavigationEntry works correctly for matching
// first-level frames.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
EnsureFirstLevelFrameNavigationEntriesMatch) {
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
FrameTreeNode* root = web_contents->GetFrameTree()->root();
GURL start_url(embedded_test_server()->GetURL("/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
NavigationEntryImpl* nav_entry =
web_contents->GetController().GetLastCommittedEntry();
// Add, then remove a named frame. It will create a FrameNavigationEntry
// for the name and leave it around.
EXPECT_TRUE(ExecuteScript(root, kAddNamedFrameScript));
EXPECT_EQ(1U, root->child_count());
EXPECT_EQ(1U, nav_entry->root_node()->children.size());
auto* tree_node = nav_entry->root_node()->children[0].get();
EXPECT_TRUE(ExecuteScript(root, kRemoveFrameScript));
EXPECT_EQ(0U, root->child_count());
EXPECT_EQ(1U, nav_entry->root_node()->children.size());
// Add another frame with the same name as before. The matching logic
// should consider them the same and result in the FrameNavigationEntry
// being reused.
EXPECT_TRUE(ExecuteScript(root, kAddNamedFrameScript));
EXPECT_EQ(1U, root->child_count());
EXPECT_EQ(1U, nav_entry->root_node()->children.size());
EXPECT_EQ(tree_node, nav_entry->root_node()->children[0].get());
EXPECT_TRUE(ExecuteScript(root, kRemoveFrameScript));
EXPECT_EQ(0U, root->child_count());
}
// Test that navigations classified as SAME_PAGE properly update all the
// members of FrameNavigationEntry. If not, it is possible to get a mismatch
// between the origin and URL of a document as seen in
// https://crbug.com/630103.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
EnsureSamePageNavigationUpdatesFrameNavigationEntry) {
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
FrameTreeNode* root = web_contents->GetFrameTree()->root();
// Navigate to a simple page and then perform a fragment change navigation.
GURL start_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
GURL fragment_change_url(
embedded_test_server()->GetURL("a.com", "/title1.html#foo"));
EXPECT_TRUE(NavigateToURL(shell(), fragment_change_url));
EXPECT_EQ(2, web_contents->GetController().GetEntryCount());
// Replace the URL of the current NavigationEntry with one that will cause
// a server redirect when loaded.
{
GURL redirect_dest_url(
embedded_test_server()->GetURL("sub.a.com", "/simple_page.html"));
TestNavigationObserver observer(web_contents);
std::string script = "history.replaceState({}, '', '/server-redirect?" +
redirect_dest_url.spec() + "')";
EXPECT_TRUE(ExecuteScript(root, script));
observer.Wait();
}
// Simulate the user hitting Enter in the omnibox without changing the URL.
{
TestNavigationObserver observer(web_contents);
web_contents->GetController().LoadURL(web_contents->GetLastCommittedURL(),
Referrer(), ui::PAGE_TRANSITION_LINK,
std::string());
observer.Wait();
}
// Prior to fixing the issue, the above omnibox navigation (which is
// classified as SAME_PAGE) was leaving the FrameNavigationEntry with the
// same document sequence number as the previous entry but updates the URL.
// Doing a back session history navigation now will cause the browser to
// consider it as same document because of this matching document sequence
// number and lead to a mismatch of origin and URL in the renderer process.
{
TestNavigationObserver observer(web_contents);
web_contents->GetController().GoBack();
observer.Wait();
}
// Verify the expected origin through JavaScript. It also has the additional
// verification of the process also being still alive.
std::string origin;
EXPECT_TRUE(ExecuteScriptAndExtractString(
web_contents, "domAutomationController.send(document.origin)", &origin));
EXPECT_EQ(start_url.GetOrigin().spec(), origin + "/");
}
// Helper to trigger a history-back navigation in the WebContents after the
// renderer has committed a same-process and cross-origin navigation to the
// given |url|, but before the browser side has had a chance to process the
// DidCommitProvisionalLoad message.
class HistoryNavigationBeforeCommitInjector
: public DidCommitProvisionalLoadInterceptor {
public:
HistoryNavigationBeforeCommitInjector(WebContentsImpl* web_contents,
const GURL& url)
: DidCommitProvisionalLoadInterceptor(web_contents),
did_trigger_history_navigation_(false),
url_(url) {}
~HistoryNavigationBeforeCommitInjector() override {}
bool did_trigger_history_navigation() const {
return did_trigger_history_navigation_;
}
private:
// DidCommitProvisionalLoadInterceptor:
void WillDispatchDidCommitProvisionalLoad(
RenderFrameHost* render_frame_host,
::FrameHostMsg_DidCommitProvisionalLoad_Params* params) override {
if (!render_frame_host->GetParent() && params->url == url_) {
did_trigger_history_navigation_ = true;
web_contents()->GetController().GoBack();
}
}
bool did_trigger_history_navigation_;
GURL url_;
DISALLOW_COPY_AND_ASSIGN(HistoryNavigationBeforeCommitInjector);
};
// Test which simulates a race condition between a cross-origin, same-process
// navigation and a same document session history navigation. When such a race
// occurs, the renderer will commit the cross-origin navigation, updating its
// version of the current document sequence number, and will send an IPC to the
// browser process. The session history navigation comes after the commit for
// the cross-origin navigation and updates the URL, but not the origin of the
// document. This results in mismatch between the two and causes the renderer
// process to be killed. See https://crbug.com/630103.
IN_PROC_BROWSER_TEST_F(
NavigationControllerBrowserTest,
RaceCrossOriginNavigationAndSameDocumentHistoryNavigation) {
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
FrameTreeNode* root = web_contents->GetFrameTree()->root();
// Navigate to a simple page and then perform a same document navigation.
GURL start_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
GURL same_document_url(
embedded_test_server()->GetURL("a.com", "/title1.html#foo"));
EXPECT_TRUE(NavigateToURL(shell(), same_document_url));
EXPECT_EQ(2, web_contents->GetController().GetEntryCount());
// Create a HistoryNavigationBeforeCommitInjector, which will perform a
// GoBack() just before a cross-origin, same process navigation commits.
GURL cross_origin_url(
embedded_test_server()->GetURL("suborigin.a.com", "/title2.html"));
HistoryNavigationBeforeCommitInjector trigger(web_contents, cross_origin_url);
// Navigate cross-origin, waiting for the commit to occur.
UrlCommitObserver cross_origin_commit_observer(root, cross_origin_url);
UrlCommitObserver history_commit_observer(root, start_url);
shell()->LoadURL(cross_origin_url);
cross_origin_commit_observer.Wait();
EXPECT_EQ(cross_origin_url, web_contents->GetLastCommittedURL());
EXPECT_EQ(2, web_contents->GetController().GetLastCommittedEntryIndex());
EXPECT_TRUE(trigger.did_trigger_history_navigation());
if (IsBrowserSideNavigationEnabled()) {
// With browser-side-navigation, the history navigation is dropped.
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(2, web_contents->GetController().GetLastCommittedEntryIndex());
EXPECT_EQ(3, web_contents->GetController().GetEntryCount());
// Verify the expected origin through JavaScript. It also has the additional
// verification of the process also being still alive.
std::string origin;
EXPECT_TRUE(ExecuteScriptAndExtractString(
web_contents, "domAutomationController.send(document.origin)",
&origin));
EXPECT_EQ(cross_origin_url.GetOrigin().spec(), origin + "/");
// Navigate back again.
web_contents->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(1, web_contents->GetController().GetLastCommittedEntryIndex());
EXPECT_EQ(3, web_contents->GetController().GetEntryCount());
// Verify the expected origin through JavaScript. It also has the additional
// verification of the process also being still alive.
EXPECT_TRUE(ExecuteScriptAndExtractString(
web_contents, "domAutomationController.send(document.origin)",
&origin));
EXPECT_EQ(same_document_url.GetOrigin().spec(), origin + "/");
} else {
// Wait for the back navigation to commit as well.
history_commit_observer.Wait();
EXPECT_EQ(start_url, web_contents->GetLastCommittedURL());
EXPECT_EQ(0, web_contents->GetController().GetLastCommittedEntryIndex());
EXPECT_EQ(3, web_contents->GetController().GetEntryCount());
// Verify the expected origin through JavaScript. It also has the additional
// verification of the process also being still alive.
std::string origin;
EXPECT_TRUE(ExecuteScriptAndExtractString(
web_contents, "domAutomationController.send(document.origin)",
&origin));
EXPECT_EQ(start_url.GetOrigin().spec(), origin + "/");
}
}
// Test that verifies that Referer and Origin http headers are correctly sent
// to the final destination of a cross-site POST with a few redirects thrown in.
// This test is somewhat related to https://crbug.com/635400.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
RefererAndOriginHeadersAfterRedirects) {
// Navigate to the page with form that posts via 307 redirection to
// |redirect_target_url| (cross-site from |form_url|). Using 307 (rather than
// 302) redirection is important to preserve the HTTP method and POST body.
GURL form_url(embedded_test_server()->GetURL(
"a.com", "/form_that_posts_cross_site.html"));
GURL redirect_target_url(embedded_test_server()->GetURL("x.com", "/echoall"));
EXPECT_TRUE(NavigateToURL(shell(), form_url));
// Submit the form. The page submitting the form is at 0, and will
// go through 307 redirects from 1 -> 2 and 2 -> 3:
// 0. http://a.com:.../form_that_posts_cross_site.html
// 1. http://a.com:.../cross-site-307/i.com/cross-site-307/x.com/echoall
// 2. http://i.com:.../cross-site-307/x.com/echoall
// 3. http://x.com:.../echoall/
TestNavigationObserver form_post_observer(shell()->web_contents(), 1);
EXPECT_TRUE(
ExecuteScript(shell(), "document.getElementById('text-form').submit();"));
form_post_observer.Wait();
// Verify that we arrived at the expected, redirected location.
EXPECT_EQ(redirect_target_url,
shell()->web_contents()->GetLastCommittedURL());
// Get the http request headers.
std::string headers;
EXPECT_TRUE(ExecuteScriptAndExtractString(
shell(),
"window.domAutomationController.send("
"document.getElementsByTagName('pre')[1].innerText);",
&headers));
// Verify the Origin and Referer headers.
EXPECT_THAT(headers, ::testing::HasSubstr("Origin: null"));
EXPECT_THAT(headers,
::testing::ContainsRegex(
"Referer: http://a.com:.*/form_that_posts_cross_site.html"));
}
// Check that the favicon is not cleared for same document navigations.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
SameDocumentNavigationDoesNotClearFavicon) {
// Load a page and fake a favicon for it.
NavigationController& controller = shell()->web_contents()->GetController();
ASSERT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/simple_page.html")));
content::NavigationEntry* entry = controller.GetLastCommittedEntry();
ASSERT_TRUE(entry);
content::FaviconStatus& favicon_status = entry->GetFavicon();
favicon_status.valid = true;
ASSERT_TRUE(RendererLocationReplace(
shell(), embedded_test_server()->GetURL(
"/simple_page.html#same-document-navigation")));
entry = controller.GetLastCommittedEntry();
content::FaviconStatus& favicon_status2 = entry->GetFavicon();
EXPECT_TRUE(favicon_status2.valid);
ASSERT_TRUE(RendererLocationReplace(
shell(),
embedded_test_server()->GetURL("/simple_page.html?new-navigation")));
entry = controller.GetLastCommittedEntry();
content::FaviconStatus& favicon_status3 = entry->GetFavicon();
EXPECT_FALSE(favicon_status3.valid);
}
namespace {
// A BrowserMessageFilter that delays the FrameHostMsg_RunJavaScriptDialog IPC
// message until a commit happens on a given WebContents. This allows testing a
// race condition.
class AllowDialogIPCOnCommitFilter : public BrowserMessageFilter,
public WebContentsDelegate {
public:
explicit AllowDialogIPCOnCommitFilter(WebContents* web_contents)
: BrowserMessageFilter(FrameMsgStart),
render_frame_host_(web_contents->GetMainFrame()) {
web_contents_observer_.Observe(web_contents);
}
protected:
~AllowDialogIPCOnCommitFilter() override {}
private:
// BrowserMessageFilter:
bool OnMessageReceived(const IPC::Message& message) override {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (message.type() != FrameHostMsg_RunJavaScriptDialog::ID)
return false;
// Suspend the message.
web_contents_observer_.SetCallback(
base::Bind(&RenderFrameHost::OnMessageReceived,
base::Unretained(render_frame_host_), message));
return true;
}
// WebContentsDelegate:
JavaScriptDialogManager* GetJavaScriptDialogManager(
WebContents* source) override {
CHECK(false);
return nullptr; // agh compiler
}
// Separate because WebContentsObserver and BrowserMessageFilter each have an
// OnMessageReceived function; this is the simplest way to disambiguate.
class : public WebContentsObserver {
public:
using Callback = base::Callback<bool()>;
using WebContentsObserver::Observe;
void SetCallback(Callback callback) { callback_ = callback; }
private:
void DidFinishNavigation(NavigationHandle* navigation_handle) override {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!navigation_handle->HasCommitted())
return;
// Resume the message.
callback_.Run();
}
Callback callback_;
} web_contents_observer_;
RenderFrameHost* render_frame_host_;
DISALLOW_COPY_AND_ASSIGN(AllowDialogIPCOnCommitFilter);
};
} // namespace
// Check that swapped out frames cannot spawn JavaScript dialogs.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
NoDialogsFromSwappedOutFrames) {
// Start on a normal page.
GURL url1 = embedded_test_server()->GetURL(
"/navigation_controller/beforeunload_dialog.html");
EXPECT_TRUE(NavigateToURL(shell(), url1));
// Add a filter to allow us to force an IPC race.
WebContents* web_contents = shell()->web_contents();
scoped_refptr<AllowDialogIPCOnCommitFilter> filter =
new AllowDialogIPCOnCommitFilter(web_contents);
web_contents->SetDelegate(filter.get());
web_contents->GetMainFrame()->GetProcess()->AddFilter(filter.get());
// Use a chrome:// url to force the second page to be in a different process.
GURL url2(std::string(kChromeUIScheme) + url::kStandardSchemeSeparator +
kChromeUIGpuHost);
EXPECT_TRUE(NavigateToURL(shell(), url2));
// What happens now is that attempting to unload the first page will trigger a
// JavaScript alert but allow navigation. The alert IPC will be suspended by
// the message filter. The commit of the second page will unblock the IPC. If
// the dialog IPC is allowed to spawn a dialog, the call by the WebContents to
// its delegate to get the JavaScriptDialogManager will cause a CHECK and the
// test will fail.
}
namespace {
// Execute JavaScript without the user gesture flag set, and wait for the
// triggered load finished.
void ExecuteJavaScriptAndWaitForLoadStop(WebContents* web_contents,
const std::string script) {
// WaitForLoadStop() does not work to wait for loading that is triggered by
// JavaScript asynchronously.
TestNavigationObserver observer(web_contents);
// ExecuteScript() sets a user gesture flag internally for testing, but we
// want to run JavaScript without the flag. Call ExecuteJavaScriptForTests
// directory.
static_cast<WebContentsImpl*>(web_contents)
->GetMainFrame()
->ExecuteJavaScriptForTests(base::UTF8ToUTF16(script));
observer.Wait();
}
} // namespace
// Check if consecutive reloads can be correctly captured by metrics.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
ConsecutiveReloadMetrics) {
base::HistogramTester histogram;
const char kReloadToReloadMetricName[] =
"Navigation.Reload.ReloadToReloadDuration";
const char kReloadMainResourceToReloadMetricName[] =
"Navigation.Reload.ReloadMainResourceToReloadDuration";
// Navigate to a page, and check if metrics are initialized correctly.
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL(
"/navigation_controller/page_with_links.html")));
histogram.ExpectTotalCount(kReloadToReloadMetricName, 0);
histogram.ExpectTotalCount(kReloadMainResourceToReloadMetricName, 0);
NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
shell()->web_contents()->GetController());
// Reload triggers a reload of ReloadType::NORMAL. The first reload should
// not be counted.
controller.Reload(ReloadType::NORMAL, false);
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
histogram.ExpectTotalCount(kReloadToReloadMetricName, 0);
histogram.ExpectTotalCount(kReloadMainResourceToReloadMetricName, 0);
// Reload with ReloadType::BYPASSING_CACHE. Both metrics should count the
// consecutive reloads.
controller.Reload(ReloadType::BYPASSING_CACHE, false);
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
histogram.ExpectTotalCount(kReloadToReloadMetricName, 1);
histogram.ExpectTotalCount(kReloadMainResourceToReloadMetricName, 1);
// Triggers another reload with ReloadType::BYPASSING_CACHE.
// ReloadMainResourceToReload should not be counted here.
controller.Reload(ReloadType::BYPASSING_CACHE, false);
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
histogram.ExpectTotalCount(kReloadToReloadMetricName, 2);
histogram.ExpectTotalCount(kReloadMainResourceToReloadMetricName, 1);
// A browser-initiated navigation should reset the reload tracking
// information.
EXPECT_TRUE(
NavigateToURL(shell(), embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html")));
histogram.ExpectTotalCount(kReloadToReloadMetricName, 2);
histogram.ExpectTotalCount(kReloadMainResourceToReloadMetricName, 1);
// Then, the next reload should be assumed as the first reload. Metrics
// should not be changed for the first reload.
controller.Reload(ReloadType::NORMAL, false);
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
histogram.ExpectTotalCount(kReloadToReloadMetricName, 2);
histogram.ExpectTotalCount(kReloadMainResourceToReloadMetricName, 1);
// Another reload of ReloadType::NORMAL should be counted by both metrics
// again.
controller.Reload(ReloadType::NORMAL, false);
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
histogram.ExpectTotalCount(kReloadToReloadMetricName, 3);
histogram.ExpectTotalCount(kReloadMainResourceToReloadMetricName, 2);
// A renderer-initiated navigations with no user gesture don't reset reload
// tracking information, and the following reload will be counted by metrics.
ExecuteJavaScriptAndWaitForLoadStop(
shell()->web_contents(),
"history.pushState({}, 'page 1', 'simple_page_1.html')");
histogram.ExpectTotalCount(kReloadToReloadMetricName, 3);
histogram.ExpectTotalCount(kReloadMainResourceToReloadMetricName, 2);
ExecuteJavaScriptAndWaitForLoadStop(shell()->web_contents(),
"location.href='simple_page_2.html'");
histogram.ExpectTotalCount(kReloadToReloadMetricName, 3);
histogram.ExpectTotalCount(kReloadMainResourceToReloadMetricName, 2);
controller.Reload(ReloadType::NORMAL, false);
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
histogram.ExpectTotalCount(kReloadToReloadMetricName, 4);
histogram.ExpectTotalCount(kReloadMainResourceToReloadMetricName, 3);
// Go back to the first page. Reload tracking information should be reset.
shell()->web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
histogram.ExpectTotalCount(kReloadToReloadMetricName, 4);
histogram.ExpectTotalCount(kReloadMainResourceToReloadMetricName, 3);
controller.Reload(ReloadType::NORMAL, false);
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
histogram.ExpectTotalCount(kReloadToReloadMetricName, 4);
histogram.ExpectTotalCount(kReloadMainResourceToReloadMetricName, 3);
}
// Check that the referrer is stored inside FrameNavigationEntry for subframes.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
RefererStoredForSubFrame) {
const NavigationControllerImpl& controller =
static_cast<const NavigationControllerImpl&>(
shell()->web_contents()->GetController());
GURL url_simple(embedded_test_server()->GetURL(
"/navigation_controller/page_with_iframe_simple.html"));
GURL url_redirect(embedded_test_server()->GetURL(
"/navigation_controller/page_with_iframe_redirect.html"));
// Run this test twice: with and without a redirection.
for (const GURL& url : {url_simple, url_redirect}) {
// Navigate to a page with an iframe.
EXPECT_TRUE(NavigateToURL(shell(), url));
// Check the FrameNavigationEntry's referrer.
NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
ASSERT_EQ(1U, entry->root_node()->children.size());
FrameNavigationEntry* frame_entry =
entry->root_node()->children[0]->frame_entry.get();
EXPECT_EQ(frame_entry->referrer().url, url);
}
}
namespace {
class RequestMonitoringNavigationBrowserTest : public ContentBrowserTest {
public:
RequestMonitoringNavigationBrowserTest() : weak_factory_(this) {}
const net::test_server::HttpRequest* FindAccumulatedRequest(
const GURL& url_to_find) {
DCHECK(url_to_find.SchemeIsHTTPOrHTTPS());
auto it = std::find_if(
accumulated_requests_.begin(), accumulated_requests_.end(),
[&url_to_find](const net::test_server::HttpRequest& request) {
return request.GetURL() == url_to_find;
});
if (it == accumulated_requests_.end())
return nullptr;
return &*it;
}
protected:
void SetUpOnMainThread() override {
// Accumulate all http requests made to |embedded_test_server| into
// |accumulated_requests_| container.
embedded_test_server()->RegisterRequestMonitor(base::Bind(
&RequestMonitoringNavigationBrowserTest::MonitorRequestOnIoThread,
weak_factory_.GetWeakPtr(), base::SequencedTaskRunnerHandle::Get()));
ASSERT_TRUE(embedded_test_server()->Start());
}
void TearDown() override {
EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
}
private:
static void MonitorRequestOnIoThread(
const base::WeakPtr<RequestMonitoringNavigationBrowserTest>& weak_this,
const scoped_refptr<base::SequencedTaskRunner>& postback_task_runner,
const net::test_server::HttpRequest& request) {
postback_task_runner->PostTask(
FROM_HERE,
base::BindOnce(
&RequestMonitoringNavigationBrowserTest::MonitorRequestOnMainThread,
weak_this, request));
}
void MonitorRequestOnMainThread(
const net::test_server::HttpRequest& request) {
accumulated_requests_.push_back(request);
}
std::vector<net::test_server::HttpRequest> accumulated_requests_;
base::WeakPtrFactory<RequestMonitoringNavigationBrowserTest> weak_factory_;
};
// Helper for waiting until the main frame of |web_contents| has loaded
// |expected_url| (and all subresources have finished loading).
class WebContentsLoadFinishedWaiter : public WebContentsObserver {
public:
WebContentsLoadFinishedWaiter(WebContents* web_contents,
const GURL& expected_url)
: WebContentsObserver(web_contents),
expected_url_(expected_url),
message_loop_runner_(new MessageLoopRunner) {
EXPECT_TRUE(web_contents != NULL);
}
void Wait() { message_loop_runner_->Run(); }
private:
void DidFinishLoad(RenderFrameHost* render_frame_host,
const GURL& url) override {
bool is_main_frame = !render_frame_host->GetParent();
if (url == expected_url_ && is_main_frame)
message_loop_runner_->Quit();
}
GURL expected_url_;
scoped_refptr<MessageLoopRunner> message_loop_runner_;
};
} // namespace
// Check that NavigationController::LoadURLParams::extra_headers are not copied
// to subresource requests.
IN_PROC_BROWSER_TEST_F(RequestMonitoringNavigationBrowserTest,
ExtraHeadersVsSubresources) {
GURL page_url = embedded_test_server()->GetURL("/page_with_image.html");
GURL image_url = embedded_test_server()->GetURL("/blank.jpg");
// Navigate via LoadURLWithParams (setting |extra_headers| field).
WebContentsLoadFinishedWaiter waiter(shell()->web_contents(), page_url);
NavigationController::LoadURLParams load_url_params(page_url);
load_url_params.extra_headers =
"X-ExtraHeadersVsSubresources: 1\n"
"X-2ExtraHeadersVsSubresources: 2";
shell()->web_contents()->GetController().LoadURLWithParams(load_url_params);
waiter.Wait();
EXPECT_EQ(page_url, shell()->web_contents()->GetLastCommittedURL());
// Verify that the extra header was present for the page.
const net::test_server::HttpRequest* page_request =
FindAccumulatedRequest(page_url);
ASSERT_TRUE(page_request);
EXPECT_THAT(page_request->headers,
testing::Contains(testing::Key("X-ExtraHeadersVsSubresources")));
EXPECT_THAT(page_request->headers,
testing::Contains(testing::Key("X-2ExtraHeadersVsSubresources")));
// Verify that the extra header was NOT present for the subresource.
const net::test_server::HttpRequest* image_request =
FindAccumulatedRequest(image_url);
ASSERT_TRUE(image_request);
EXPECT_THAT(image_request->headers,
testing::Not(testing::Contains(
testing::Key("X-ExtraHeadersVsSubresources"))));
EXPECT_THAT(image_request->headers,
testing::Not(testing::Contains(
testing::Key("X-2ExtraHeadersVsSubresources"))));
}
// Test that a same document navigation does not lead to the deletion of the
// NavigationHandle for an ongoing different document navigation.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
SameDocumentNavigationDoesntDeleteNavigationHandle) {
const GURL kURL1 = embedded_test_server()->GetURL("/title1.html");
const GURL kPushStateURL =
embedded_test_server()->GetURL("/title1.html#fragment");
const GURL kURL2 = embedded_test_server()->GetURL("/title2.html");
// Navigate to the initial page.
EXPECT_TRUE(NavigateToURL(shell(), kURL1));
RenderFrameHostImpl* main_frame =
static_cast<WebContentsImpl*>(shell()->web_contents())->GetMainFrame();
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
EXPECT_FALSE(main_frame->navigation_handle());
EXPECT_FALSE(root->navigation_request());
// Start navigating to the second page.
TestNavigationManager manager(shell()->web_contents(), kURL2);
NavigationHandleCommitObserver navigation_observer(shell()->web_contents(),
kURL2);
shell()->web_contents()->GetController().LoadURL(
kURL2, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
EXPECT_TRUE(manager.WaitForRequestStart());
// This should create a NavigationHandle.
NavigationHandleImpl* handle = main_frame->navigation_handle();
NavigationRequest* request = root->navigation_request();
if (IsBrowserSideNavigationEnabled()) {
EXPECT_TRUE(request);
} else {
EXPECT_TRUE(handle);
}
// The current page does a PushState.
NavigationHandleCommitObserver push_state_observer(shell()->web_contents(),
kPushStateURL);
std::string push_state =
"history.pushState({}, \"title 1\", \"" + kPushStateURL.spec() + "\");";
EXPECT_TRUE(ExecuteScript(shell()->web_contents(), push_state));
NavigationEntry* last_committed =
shell()->web_contents()->GetController().GetLastCommittedEntry();
ASSERT_TRUE(last_committed);
EXPECT_EQ(kPushStateURL, last_committed->GetURL());
EXPECT_TRUE(push_state_observer.has_committed());
EXPECT_TRUE(push_state_observer.was_same_document());
EXPECT_TRUE(push_state_observer.was_renderer_initiated());
// This shouldn't affect the ongoing navigation.
if (IsBrowserSideNavigationEnabled()) {
EXPECT_TRUE(root->navigation_request());
EXPECT_EQ(request, root->navigation_request());
} else {
EXPECT_TRUE(main_frame->navigation_handle());
EXPECT_EQ(handle, main_frame->navigation_handle());
}
// Let the navigation finish. It should commit successfully.
manager.WaitForNavigationFinished();
last_committed =
shell()->web_contents()->GetController().GetLastCommittedEntry();
ASSERT_TRUE(last_committed);
EXPECT_EQ(kURL2, last_committed->GetURL());
EXPECT_TRUE(navigation_observer.has_committed());
EXPECT_FALSE(navigation_observer.was_same_document());
EXPECT_FALSE(navigation_observer.was_renderer_initiated());
}
// Tests that a same document browser-initiated navigation is properly reported
// by the NavigationHandle.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
SameDocumentBrowserInitiated) {
const GURL kURL = embedded_test_server()->GetURL("/title1.html");
const GURL kFragmentURL =
embedded_test_server()->GetURL("/title1.html#fragment");
// Navigate to the initial page.
EXPECT_TRUE(NavigateToURL(shell(), kURL));
// Do a browser-initiated fragment navigation.
NavigationHandleCommitObserver handle_observer(shell()->web_contents(),
kFragmentURL);
EXPECT_TRUE(NavigateToURL(shell(), kFragmentURL));
EXPECT_TRUE(handle_observer.has_committed());
EXPECT_TRUE(handle_observer.was_same_document());
EXPECT_FALSE(handle_observer.was_renderer_initiated());
}
// Tests that a 204 response to a browser-initiated navigation does not result
// in a new NavigationEntry being committed.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
204Navigation) {
const GURL kURL = embedded_test_server()->GetURL("/title1.html");
const GURL kURL204 = embedded_test_server()->GetURL("/page204.html");
// Navigate to the initial page.
EXPECT_TRUE(NavigateToURL(shell(), kURL));
const NavigationControllerImpl& controller =
static_cast<const NavigationControllerImpl&>(
shell()->web_contents()->GetController());
NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
EXPECT_EQ(kURL, entry->GetURL());
EXPECT_EQ(1, controller.GetEntryCount());
// Do a 204 navigation.
EXPECT_FALSE(NavigateToURL(shell(), kURL204));
entry = controller.GetLastCommittedEntry();
EXPECT_EQ(kURL, entry->GetURL());
EXPECT_EQ(1, controller.GetEntryCount());
}
// Tests that stopping a load clears the pending navigation entry.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, StopDuringLoad) {
// Load an initial page since the behavior differs for the first entry.
GURL start_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
TestNavigationObserver same_tab_observer(shell()->web_contents(), 1);
GURL slow_url = embedded_test_server()->GetURL("/slow?60");
shell()->LoadURL(slow_url);
shell()->web_contents()->Stop();
// For non-PlzNavigate case, this happens asynchronously.
if (!IsBrowserSideNavigationEnabled())
same_tab_observer.Wait();
const NavigationController& controller =
shell()->web_contents()->GetController();
ASSERT_EQ(controller.GetPendingEntry(), nullptr);
}
// Verify that session history navigations (back/forward) correctly hit the
// cache instead of going to the server. The test loads a page with no-cache
// header, stops the server, and goes back expecting successful navigation.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
HistoryNavigationUsesCache) {
GURL no_cache_url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_no_cache_header.html"));
GURL regular_url(embedded_test_server()->GetURL("/title2.html"));
NavigationController& controller = shell()->web_contents()->GetController();
EXPECT_TRUE(NavigateToURL(shell(), no_cache_url));
EXPECT_TRUE(NavigateToURL(shell(), regular_url));
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
TestNavigationObserver back_observer(shell()->web_contents());
controller.GoBack();
back_observer.Wait();
EXPECT_TRUE(back_observer.last_navigation_succeeded());
}
// Test to verify that navigating to a blocked URL does not result in a
// NavigationEntry that allows the navigation to succeed when using a history
// navigation. See https://crbug.com/723796.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
VerifyBlockedErrorPageURL_SessionHistory) {
NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
shell()->web_contents()->GetController());
GURL start_url(embedded_test_server()->GetURL("/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
// Navigate to an URL, which redirects to a data: URL, since it is an
// unsafe redirect and will result in a blocked navigation and error page.
// TODO(nasko): Find a different way to cause a blocked navigation, so
// we test a bit more generic case.
GURL redirect_to_unsafe_url(
embedded_test_server()->GetURL("/server-redirect?data:text/html,Hello!"));
EXPECT_FALSE(NavigateToURL(shell(), redirect_to_unsafe_url));
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(PAGE_TYPE_ERROR, controller.GetLastCommittedEntry()->GetPageType());
// Navigate to a new document, then go back in history trying to load the
// blocked URL.
EXPECT_TRUE(
NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
TestNavigationObserver back_load_observer(shell()->web_contents());
controller.GoBack();
back_load_observer.Wait();
// The expectation is that the blocked URL is present in the NavigationEntry,
// and shows up in both GetURL and GetVirtualURL.
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
EXPECT_FALSE(
controller.GetLastCommittedEntry()->GetURL().SchemeIs(url::kDataScheme));
EXPECT_EQ(redirect_to_unsafe_url,
controller.GetLastCommittedEntry()->GetURL());
EXPECT_EQ(redirect_to_unsafe_url,
controller.GetLastCommittedEntry()->GetVirtualURL());
}
// Verifies that redirecting to a blocked URL and going back does not allow a
// URL spoof. See https://crbug.com/777419.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
PreventSpoofFromBlockedRedirect) {
GURL url1 = embedded_test_server()->GetURL(
"a.com", "/navigation_controller/simple_page_1.html");
EXPECT_TRUE(NavigateToURL(shell(), url1));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
// Pop open a new window.
ShellAddedObserver new_shell_observer;
EXPECT_TRUE(ExecuteScript(root, "var w = window.open()"));
Shell* new_shell = new_shell_observer.GetShell();
ASSERT_NE(new_shell->web_contents(), shell()->web_contents());
EXPECT_FALSE(
new_shell->web_contents()->GetController().GetLastCommittedEntry());
// Navigate it to a cross-site URL that redirects to a data: URL. Since it is
// an unsafe redirect, it will result in a blocked navigation and error page.
GURL redirect_to_data_url(
embedded_test_server()->GetURL("/server-redirect?data:text/html,Hello!"));
TestNavigationObserver nav_observer(new_shell->web_contents(), 1);
EXPECT_TRUE(ExecuteScript(
root, "w.location.href = '" + redirect_to_data_url.spec() + "';"));
nav_observer.WaitForNavigationFinished();
EXPECT_FALSE(nav_observer.last_navigation_succeeded());
NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
new_shell->web_contents()->GetController());
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(redirect_to_data_url, controller.GetLastCommittedEntry()->GetURL());
EXPECT_EQ(PAGE_TYPE_ERROR, controller.GetLastCommittedEntry()->GetPageType());
// Navigate to a new document, then go back in history trying to load the
// blocked URL.
EXPECT_TRUE(NavigateToURL(new_shell, url1));
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(url1, controller.GetLastCommittedEntry()->GetURL());
TestNavigationObserver back_load_observer(new_shell->web_contents());
controller.GoBack();
back_load_observer.Wait();
EXPECT_EQ(redirect_to_data_url, controller.GetLastCommittedEntry()->GetURL());
// The opener should not be able to script the page, which should be another
// error message and not a blank page.
std::string result;
EXPECT_TRUE(ExecuteScriptAndExtractString(
shell(),
"domAutomationController.send((function() {\n"
" try {\n"
" return w.document.body.innerHTML;\n"
" } catch (e) {\n"
" return e.toString();\n"
" }\n"
"})())",
&result));
DLOG(INFO) << "Result: " << result;
EXPECT_THAT(result,
::testing::MatchesRegex("SecurityError: Blocked a frame with "
"origin \"http://a.com:\\d+\" from "
"accessing a cross-origin frame."));
}
// Same-document navigations can sometimes succeed but then later be blocked by
// policy (e.g., X-Frame-Options) after a page is restored or reloaded. Ensure
// that navigating back from a newly blocked URL in a subframe is not treated as
// same-document, even if it had been same-document originally.
// See https://crbug.com/765291.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
BackSameDocumentAfterBlockedSubframe) {
NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
shell()->web_contents()->GetController());
GURL start_url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_iframe_simple.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
// pushState to a URL that will be blocked by XFO if loaded from scratch.
{
FrameNavigateParamsCapturer capturer(root->child_at(0));
std::string pushStateToXfo =
"history.pushState({}, '', '/x-frame-options-deny.html')";
EXPECT_TRUE(ExecuteScript(root->child_at(0), pushStateToXfo));
capturer.Wait();
EXPECT_TRUE(capturer.is_same_document());
}
// Navigate the main frame to another page.
GURL new_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
EXPECT_TRUE(NavigateToURL(shell(), new_url));
// Go back, causing the subframe to be blocked by XFO.
{
TestNavigationObserver observer(shell()->web_contents());
controller.GoBack();
observer.Wait();
}
EXPECT_EQ(GURL("data:,"), root->child_at(0)->current_url());
// Go back again. This would have been same-document if the prior navigation
// had succeeded.
{
TestNavigationObserver observer(shell()->web_contents());
controller.GoBack();
observer.Wait();
}
// Check that the renderer is still alive.
EXPECT_TRUE(ExecuteScript(root->child_at(0), "console.log('Success');"));
}
// If the main frame does a load, it should not be reported as a subframe
// navigation. This used to occur in the following case:
// 1. You're on a site with frames.
// 2. You do a subframe navigation. This was stored with transition type
// MANUAL_SUBFRAME.
// 3. You navigate to some non-frame site.
// 4. You navigate back to the page from step 2. Since it was initially
// MANUAL_SUBFRAME, it will be that same transition type here.
// We don't want that, because any navigation that changes the toplevel frame
// should be tracked as a toplevel navigation (this allows us to update the URL
// bar, etc).
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
GoBackToManualSubFrame) {
GURL main_url(embedded_test_server()->GetURL(
"/navigation_controller/page_with_iframe.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
ASSERT_NE(nullptr, root->child_at(0));
{
// Iframe initial load.
LoadCommittedCapturer capturer(root->child_at(0));
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
NavigateFrameToURL(root->child_at(0), frame_url);
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
{
// Iframe manual navigation.
FrameNavigateParamsCapturer capturer(root->child_at(0));
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
NavigateFrameToURL(root->child_at(0), frame_url);
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_MANUAL_SUBFRAME));
EXPECT_EQ(NAVIGATION_TYPE_NEW_SUBFRAME, capturer.navigation_type());
}
{
// Main frame navigation.
FrameNavigateParamsCapturer capturer(root);
GURL main_url_2(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
NavigateFrameToURL(root, main_url_2);
capturer.Wait();
EXPECT_EQ(NAVIGATION_TYPE_NEW_PAGE, capturer.navigation_type());
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(), ui::PAGE_TRANSITION_LINK));
}
{
// Check the history before going back.
NavigationControllerImpl& controller =
static_cast<NavigationControllerImpl&>(
shell()->web_contents()->GetController());
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
controller.GetEntryAtIndex(0)->GetTransitionType(),
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)));
// TODO(creis, arthursonzogni): The correct PageTransition is still an open
// question. Maybe PAGE_TRANSITION_MANUAL_SUBFRAME is more appropriate.
// Please see https://crbug.com/740461.
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
controller.GetEntryAtIndex(1)->GetTransitionType(),
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)));
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
controller.GetEntryAtIndex(2)->GetTransitionType(),
ui::PAGE_TRANSITION_LINK));
}
{
// Back.
FrameNavigateParamsCapturer capturer(root);
shell()->web_contents()->GetController().GoBack();
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition(),
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FORWARD_BACK |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)));
}
{
// Check the history again.
NavigationControllerImpl& controller =
static_cast<NavigationControllerImpl&>(
shell()->web_contents()->GetController());
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
controller.GetEntryAtIndex(0)->GetTransitionType(),
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)));
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
controller.GetEntryAtIndex(1)->GetTransitionType(),
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FORWARD_BACK |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)));
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
controller.GetEntryAtIndex(2)->GetTransitionType(),
ui::PAGE_TRANSITION_LINK));
}
}
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
HashNavigationVsBeforeUnloadEvent) {
GURL main_url(embedded_test_server()->GetURL("/title1.html"));
GURL hash_url(embedded_test_server()->GetURL("/title1.html#hash"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
EXPECT_TRUE(
ExecuteScript(shell(),
R"( window.addEventListener("beforeunload", function(e) {
domAutomationController.send("beforeunload");
});
window.addEventListener("unload", function(e) {
domAutomationController.send("unload");
});
)"));
DOMMessageQueue message_queue;
std::vector<std::string> messages;
std::string message;
EXPECT_TRUE(NavigateToURL(shell(), hash_url));
while (message_queue.PopMessage(&message))
messages.push_back(message);
// Verify that none of "beforeunload", "unload" events fired.
EXPECT_THAT(messages, testing::IsEmpty());
}
} // namespace content