blob: 4cbc72de1c5a8e8f317bdb99ca4e225a4e251d49 [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 <stdint.h>
#include <algorithm>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/files/scoped_temp_dir.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/task/post_task.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/frame_host/frame_navigation_entry.h"
#include "content/browser/frame_host/frame_tree.h"
#include "content/browser/frame_host/navigation_controller_impl.h"
#include "content/browser/frame_host/navigation_entry_impl.h"
#include "content/browser/frame_host/navigation_handle_impl.h"
#include "content/browser/frame_host/navigation_request.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/display_util.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/web_contents/web_contents_view.h"
#include "content/common/frame_messages.h"
#include "content/common/page_state_serialization.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/site_isolation_policy.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/content_features.h"
#include "content/public/common/navigation_policy.h"
#include "content/public/common/screen_info.h"
#include "content/public/common/url_constants.h"
#include "content/public/common/use_zoom_for_dsf_policy.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/download_test_observer.h"
#include "content/public/test/navigation_handle_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/url_loader_interceptor.h"
#include "content/shell/browser/shell.h"
#include "content/shell/browser/shell_download_manager_delegate.h"
#include "content/shell/common/shell_switches.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/did_commit_navigation_interceptor.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/controllable_http_response.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"
#include "third_party/blink/public/mojom/renderer_preferences.mojom.h"
namespace {
static const char kAddNamedFrameScript[] =
"var f = document.createElement('iframe');"
"f.name = 'foo-frame-name';"
"document.body.appendChild(f);";
static const char kRemoveFrameScript[] =
"var f = document.querySelector('iframe');"
"f.parentNode.removeChild(f);";
using testing::ElementsAre;
using testing::IsEmpty;
} // 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());
}
void SetUpCommandLine(base::CommandLine* command_line) override {
ContentBrowserTest::SetUpCommandLine(command_line);
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kExposeInternalsForTesting);
}
// TODO(bokan): There's one test whose result depends on whether the
// FractionalScrollOffsets feature is enabled in Blink's
// RuntimeEnabledFeatures. Since there's just one, we can determine the
// status of the feature using script, rather than plumbing this state across
// the browser/renderer boundary. This can be removed once the feature is
// shipped to stable and the flag removed. https://crbug.com/414283.
bool IsFractionalScrollOffsetsEnabled() {
std::string script =
"internals.runtimeFlags.fractionalScrollOffsetsEnabled";
return EvalJs(shell(), script).ExtractBool();
}
};
// Base class for tests that need to supply modifications to EmbeddedTestServer
// which are required to be complete before it is started.
class NavigationControllerBrowserTestNoServer : public ContentBrowserTest {
protected:
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
content::SetupCrossSiteRedirector(embedded_test_server());
}
};
// 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) {
// LoadDataWithBaseURL is never subject to --site-per-process policy today
// (this API is only used by Android WebView [where OOPIFs have not shipped
// yet] and GuestView cases [which always hosts guests inside a renderer
// without an origin lock]). Therefore, skip the test in --site-per-process
// mode to avoid renderer kills which won't happen in practice as described
// above.
//
// TODO(https://crbug.com/962643): Consider enabling this test once Android
// Webview or WebView guests support OOPIFs and/or origin locks.
if (AreAllSitesIsolatedForTesting())
return;
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);
NavigationControllerImpl& controller = static_cast<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) {
// LoadDataWithBaseURL is never subject to --site-per-process policy today
// (this API is only used by Android WebView [where OOPIFs have not shipped
// yet] and GuestView cases [which always hosts guests inside a renderer
// without an origin lock]). Therefore, skip the test in --site-per-process
// mode to avoid renderer kills which won't happen in practice as described
// above.
//
// TODO(https://crbug.com/962643): Consider enabling this test once Android
// Webview or WebView guests support OOPIFs and/or origin locks.
if (AreAllSitesIsolatedForTesting())
return;
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());
EXPECT_EQ("http://baseurl", EvalJs(shell(), "self.origin"));
EXPECT_EQ(url::Origin::Create(base_url),
shell()->web_contents()->GetMainFrame()->GetLastCommittedOrigin());
// 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();
}
url::Origin data_origin =
shell()->web_contents()->GetMainFrame()->GetLastCommittedOrigin();
EXPECT_TRUE(data_origin.opaque());
EXPECT_EQ(url::SchemeHostPort(),
data_origin.GetTupleOrPrecursorTupleIfOpaque());
// 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());
EXPECT_EQ(url::Origin::Create(base_url),
shell()->web_contents()->GetMainFrame()->GetLastCommittedOrigin());
}
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);
NavigationControllerImpl& controller = static_cast<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(ExecJs(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) {
// LoadDataWithBaseURL is never subject to --site-per-process policy today
// (this API is only used by Android WebView [where OOPIFs have not shipped
// yet] and GuestView cases [which always hosts guests inside a renderer
// without an origin lock]). Therefore, skip the test in --site-per-process
// mode to avoid renderer kills which won't happen in practice as described
// above.
//
// TODO(https://crbug.com/962643): Consider enabling this test once Android
// Webview or WebView guests support OOPIFs and/or origin locks.
if (AreAllSitesIsolatedForTesting())
return;
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);
NavigationControllerImpl& controller = static_cast<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) {
// LoadDataWithBaseURL is never subject to --site-per-process policy today
// (this API is only used by Android WebView [where OOPIFs have not shipped
// yet] and GuestView cases [which always hosts guests inside a renderer
// without an origin lock]). Therefore, skip the test in --site-per-process
// mode to avoid renderer kills which won't happen in practice as described
// above.
//
// TODO(https://crbug.com/962643): Consider enabling this test once Android
// Webview or WebView guests support OOPIFs and/or origin locks.
if (AreAllSitesIsolatedForTesting())
return;
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);
NavigationControllerImpl& controller = static_cast<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 = JsReplace("document.location = $1", navigate_url);
TestNavigationObserver same_tab_observer(shell()->web_contents(), 1);
EXPECT_TRUE(ExecJs(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) {
// LoadDataWithBaseURL is never subject to --site-per-process policy today
// (this API is only used by Android WebView [where OOPIFs have not shipped
// yet] and GuestView cases [which always hosts guests inside a renderer
// without an origin lock]). Therefore, skip the test in --site-per-process
// mode to avoid renderer kills which won't happen in practice as described
// above.
//
// TODO(https://crbug.com/962643): Consider enabling this test once Android
// Webview or WebView guests support OOPIFs and/or origin locks.
if (AreAllSitesIsolatedForTesting())
return;
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>";
NavigationControllerImpl& controller = static_cast<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(ExecJs(shell(), "console.log('Success');"));
}
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, UniqueIDs) {
NavigationControllerImpl& controller = static_cast<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(ExecJs(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 {
// 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(ExecJs(shell, JsReplace("window.location.replace($1)", url)));
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, EvalJs(shell(), "history.length"));
EXPECT_TRUE(RendererLocationReplace(
shell(), embedded_test_server()->GetURL("/title1.html")));
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(1, EvalJs(shell(), "history.length"));
// 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, EvalJs(shell(), "history.length"));
EXPECT_TRUE(
NavigateToURL(shell(), embedded_test_server()->GetURL("/title3.html")));
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(3, EvalJs(shell(), "history.length"));
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, EvalJs(shell(), "history.length"));
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;
GURL page_url = embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html");
EXPECT_TRUE(
ExecJs(shell(), JsReplace("window.open($1, '_blank');", page_url)));
Shell* shell2 = observer.GetShell();
EXPECT_TRUE(WaitForLoadStop(shell2->web_contents()));
EXPECT_EQ(1, shell2->web_contents()->GetController().GetEntryCount());
EXPECT_EQ(1, EvalJs(shell2, "history.length"));
// 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];
}
bool did_replace_entry() {
EXPECT_EQ(1U, did_replace_entries_.size());
return did_replace_entries_[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_; }
const std::vector<bool>& did_replace_entries() {
return did_replace_entries_;
}
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());
did_replace_entries_.push_back(navigation_handle->DidReplaceEntry());
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_;
std::vector<bool> did_replace_entries_;
// 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) {
// LoadDataWithBaseURL is never subject to --site-per-process policy today
// (this API is only used by Android WebView [where OOPIFs have not shipped
// yet] and GuestView cases [which always hosts guests inside a renderer
// without an origin lock]). Therefore, skip the test in --site-per-process
// mode to avoid renderer kills which won't happen in practice as described
// above.
//
// TODO(https://crbug.com/962643): Consider enabling this test once Android
// Webview or WebView guests support OOPIFs and/or origin locks.
if (AreAllSitesIsolatedForTesting())
return;
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(ExecJs(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(ExecJs(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 = JsReplace("location.assign($1);", frame_url);
EXPECT_TRUE(ExecJs(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(ExecJs(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(ExecJs(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 html = "<iframe src='" + url1.spec() + "'></iframe>";
std::string script = JsReplace(
"w.document.write($1);"
"w.document.close();",
html);
EXPECT_TRUE(ExecJs(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(ExecJs(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(ExecJs(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 = embedded_test_server()->GetURL("/close-socket");
base::PostTask(FROM_HERE, {BrowserThread::IO},
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 results in an error page,
// the NEW_PAGE navigation type with replacement, and no addition to the
// history list.
{
FrameNavigateParamsCapturer capturer(root);
NavigateFrameToURL(root, error_url);
capturer.Wait();
EXPECT_EQ(NAVIGATION_TYPE_NEW_PAGE, capturer.navigation_type());
EXPECT_TRUE(capturer.did_replace_entry());
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.
{
FrameNavigateParamsCapturer capturer(root);
RendererLocationReplace(shell(), error_url);
capturer.Wait();
EXPECT_EQ(NAVIGATION_TYPE_NEW_PAGE, capturer.navigation_type());
EXPECT_TRUE(capturer.did_replace_entry());
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());
EXPECT_TRUE(capturer.did_replace_entry());
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(ExecJs(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(ExecJs(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 = JsReplace("location.assign($1);", frame_url);
EXPECT_TRUE(ExecJs(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(ExecJs(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());
}
// location.replace().
FrameNavigateParamsCapturer capturer(root);
GURL frame_url(embedded_test_server()->GetURL(
"foo.com", "/navigation_controller/simple_page_1.html"));
std::string script = JsReplace("location.replace($1);", frame_url);
EXPECT_TRUE(ExecJs(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.did_replace_entry());
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(ExecJs(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(ExecJs(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(ExecJs(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(ExecJs(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(ExecJs(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().
FrameNavigateParamsCapturer capturer(root);
GURL frame_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_1.html"));
std::string script = JsReplace("location.replace($1);", frame_url);
EXPECT_TRUE(ExecJs(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.did_replace_entry());
EXPECT_FALSE(capturer.is_same_document());
}
// Now, various same document navigations.
{
// Same-document location.replace().
FrameNavigateParamsCapturer capturer(root);
std::string script = "location.replace('#foo')";
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.did_replace_entry());
EXPECT_TRUE(capturer.is_same_document());
}
{
// history.replaceState().
FrameNavigateParamsCapturer capturer(root);
std::string script =
"history.replaceState({}, 'page 2', 'simple_page_2.html')";
EXPECT_TRUE(ExecJs(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(ExecJs(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(ExecJs(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 url(embedded_test_server()->GetURL(
"/navigation_controller/reload-with-url-anchor.html#center-element"));
EXPECT_TRUE(NavigateToURL(shell(), url));
double window_scroll_y = EvalJs(shell(), "window.scrollY").ExtractDouble();
// TODO(bokan): The floor hack below required when ZoomForDSFEnabled can go
// away once FractionalScrolLOffsets ships. The reason it's required is that,
// at certain device scale factors, the given CSS pixel scroll value may land
// between physical pixels. Without the feature Blink will truncate to the
// nearest physical pixel so the expectation must account for that. When the
// feature is enabled, Blink stores the fractional offset so the truncation
// is unnecessary. https://crbug.com/414283.
bool fractional_scroll_offsets_enabled = IsFractionalScrollOffsetsEnabled();
// The 'center-element' y-position is 2000px. 2000px is an arbitrary value.
double expected_window_scroll_y = 2000;
if (IsUseZoomForDSFEnabled() && !fractional_scroll_offsets_enabled) {
float device_scale_factor = shell()
->web_contents()
->GetRenderWidgetHostView()
->GetDeviceScaleFactor();
expected_window_scroll_y =
floor(device_scale_factor * expected_window_scroll_y) /
device_scale_factor;
}
EXPECT_FLOAT_EQ(expected_window_scroll_y, window_scroll_y);
// Reload.
ReloadBlockUntilNavigationsComplete(shell(), 1);
window_scroll_y = EvalJs(shell(), "window.scrollY").ExtractDouble();
EXPECT_FLOAT_EQ(expected_window_scroll_y, window_scroll_y);
}
// Verify that reloading a page with url anchor and scroll scrolls to correct
// position.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
ReloadWithUrlAnchorAndScroll) {
GURL url(embedded_test_server()->GetURL(
"/navigation_controller/reload-with-url-anchor.html#center-element"));
EXPECT_TRUE(NavigateToURL(shell(), url));
// The 'center-element' y-position is 2000px. This script scrolls the view
// 100px below this element. 2000px and 100px are arbitrary values.
std::string script_scroll_down = "window.scroll(0, 2100)";
EXPECT_TRUE(ExecuteScript(shell(), script_scroll_down));
double window_scroll_y = EvalJs(shell(), "window.scrollY").ExtractDouble();
// TODO(bokan): The floor hack below required when ZoomForDSFEnabled can go
// away once FractionalScrolLOffsets ships. The reason it's required is that,
// at certain device scale factors, the given CSS pixel scroll value may land
// between physical pixels. Without the feature Blink will truncate to the
// nearest physical pixel so the expectation must account for that. When the
// feature is enabled, Blink stores the fractional offset so the truncation
// is unnecessary. https://crbug.com/414283.
bool fractional_scroll_offsets_enabled = IsFractionalScrollOffsetsEnabled();
double expected_window_scroll_y = 2100;
if (IsUseZoomForDSFEnabled() && !fractional_scroll_offsets_enabled) {
float device_scale_factor = shell()
->web_contents()
->GetRenderWidgetHostView()
->GetDeviceScaleFactor();
expected_window_scroll_y =
floor(device_scale_factor * expected_window_scroll_y) /
device_scale_factor;
}
EXPECT_FLOAT_EQ(expected_window_scroll_y, window_scroll_y);
// Reload.
ReloadBlockUntilNavigationsComplete(shell(), 1);
window_scroll_y = EvalJs(shell(), "window.scrollY").ExtractDouble();
EXPECT_FLOAT_EQ(expected_window_scroll_y, 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(ExecJs(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 = JsReplace("location.assign($1);", frame_url);
EXPECT_TRUE(ExecJs(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 = JsReplace("location.replace($1);", frame_url);
EXPECT_TRUE(ExecJs(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(ExecJs(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(ExecJs(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(ExecJs(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(ExecJs(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_NEW_PAGE, capturer.navigation_types()[1]);
EXPECT_TRUE(capturer.did_replace_entries()[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(ExecJs(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(ExecJs(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(ExecJs(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(ExecJs(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));
NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
shell()->web_contents()->GetController());
FrameTreeNode* root =
static_cast<WebContentsImpl*>(shell()->web_contents())->
GetFrameTree()->root();
url::Origin main_origin =
root->current_frame_host()->GetLastCommittedOrigin();
EXPECT_EQ(url::Origin::Create(main_url), main_origin);
// 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(ExecJs(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_EQ(main_origin, root_entry->committed_origin());
EXPECT_FALSE(root_entry->initiator_origin().has_value());
// 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_EQ(main_origin, frame_entry->committed_origin());
ASSERT_TRUE(frame_entry->initiator_origin().has_value());
EXPECT_EQ(main_origin, frame_entry->initiator_origin().value());
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(ExecJs(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_EQ(main_origin, frame_entry->committed_origin());
ASSERT_TRUE(frame_entry->initiator_origin().has_value());
EXPECT_EQ(main_origin, frame_entry->initiator_origin().value());
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(ExecJs(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_EQ(main_origin, frame_entry->committed_origin());
ASSERT_TRUE(frame_entry->initiator_origin().has_value());
EXPECT_EQ(main_origin, frame_entry->initiator_origin().value());
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 = JsReplace(
"var frames = document.getElementsByTagName('iframe');"
"frames[0].src = $1;",
frame_url);
EXPECT_TRUE(ExecJs(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_EQ(url::Origin::Create(frame_url), frame_entry->committed_origin());
ASSERT_TRUE(frame_entry->initiator_origin().has_value());
EXPECT_EQ(main_origin, frame_entry->initiator_origin().value());
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 = JsReplace(
"var frames = document.getElementsByTagName('iframe');"
"frames[1].src = $1;",
foo_url);
EXPECT_TRUE(ExecJs(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_EQ(url::Origin::Create(foo_url), frame_entry->committed_origin());
ASSERT_TRUE(frame_entry->initiator_origin().has_value());
EXPECT_EQ(main_origin, frame_entry->initiator_origin().value());
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());
EXPECT_EQ(frame_entry->committed_origin(),
root->child_at(1)->current_frame_host()->GetLastCommittedOrigin());
// 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(ExecJs(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_EQ(main_origin, frame_entry->committed_origin());
ASSERT_TRUE(frame_entry->initiator_origin().has_value());
EXPECT_EQ(main_origin, frame_entry->initiator_origin().value());
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_template =
"var iframe = document.createElement('iframe');"
"iframe.src = $1;"
"document.body.appendChild(iframe);";
EXPECT_TRUE(ExecJs(root, JsReplace(script_template, slow_url)));
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"));
EXPECT_TRUE(ExecJs(root->child_at(0), JsReplace(script_template, foo_url)));
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 that history.pushState() does not replace the pending entry.
// https://crbug.com/900036.
// TODO(crbug.com/926009): Fix and re-enable this test.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
DISABLED_PushStatePreservesPendingEntry) {
NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
shell()->web_contents()->GetController());
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. Start loading an URL that doesn't commit.
GURL stalled_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
// Have the user decide to go to a different page which is very slow.
TestNavigationManager stalled_navigation(shell()->web_contents(),
stalled_url);
controller.LoadURL(stalled_url, Referrer(), ui::PAGE_TRANSITION_LINK,
std::string());
EXPECT_TRUE(stalled_navigation.WaitForRequestStart());
// 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.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(ExecJs(root, script));
capturer.Wait();
EXPECT_EQ(NAVIGATION_TYPE_NEW_PAGE, capturer.navigation_type());
EXPECT_TRUE(capturer.is_same_document());
}
// This should not replace the pending entry.
entry = controller.GetPendingEntry();
ASSERT_NE(nullptr, entry);
EXPECT_EQ(stalled_url, entry->GetURL());
}
// Verify that history.replaceState() does not replace the pending entry.
// https://crbug.com/900036.
IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
ReplaceStatePreservesPendingEntry) {
NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
shell()->web_contents()->GetController());
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. Start loading an URL that doesn't commit.
GURL stalled_url(embedded_test_server()->GetURL(
"/navigation_controller/simple_page_2.html"));
// Have the user decide to go to a different page which is very slow.
TestNavigationManager stalled_navigation(shell()->web_contents(),
stalled_url);
controller.LoadURL(stalled_url, Referrer(), ui::PAGE_TRANSITION_LINK,
std::string());
EXPECT_TRUE(stalled_navigation.WaitForRequestStart());
// 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() while the pending entry
// for the other navigation still exists.
FrameNavigateParamsCapturer capturer(root);
capturer.set_wait_for_load(false);
std::string script = "history.replaceState({}, '', 'replaced')";
EXPECT_TRUE(ExecJs(root, script));
capturer.Wait();
EXPECT_EQ(NAVIGATION_TYPE_EXISTING_PAGE, capturer.navigation_type());
EXPECT_TRUE(capturer.is_same_document());
}
// This should not replace the pending entry.
entry = controller.GetPendingEntry();
ASSERT_NE(nullptr, entry);
EXPECT_EQ(stalled_url, entry->GetURL());
}
// 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(ExecJs(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(ExecJs(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(ExecJs(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(ExecJs(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(ExecJs(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(ExecJs(root, script));
capturer.Wait();
EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
}
// 2. Change the iframe's name.
EXPECT_TRUE(ExecJs(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(ExecJs(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));
NavigationControllerImpl& controller = static_cast<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(ExecJs(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(root_entry->initiator_origin().has_value());
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());
ASSERT_TRUE(frame_entry->initiator_origin().has_value());
EXPECT_EQ(url::Origin::Create(main_url),
frame_entry->initiator_origin().value());
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(ExecJs(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(root_entry->initiator_origin().has_value());
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());
ASSERT_TRUE(frame_entry->initiator_origin().has_value());
EXPECT_EQ(url::Origin::Create(main_url),
frame_entry->initiator_origin().value());
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(ExecJs(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());
EXPECT_FALSE(root_entry->initiator_origin().has_value());
// 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());
ASSERT_TRUE(frame_entry->initiator_origin().has_value());
EXPECT_EQ(url::Origin::Create(foo_url),
frame_entry->initiator_origin().value());
// 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(ExecJs(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(root_entry->initiator_origin().has_value());
// 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());
ASSERT_TRUE(frame_entry->initiator_origin().has_value());
EXPECT_EQ(url::Origin::Create(main_url),
frame_entry->initiator_origin().value());
// 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(ExecJs(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());
EXPECT_FALSE(root_entry->initiator_origin().has_value());
// 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());
ASSERT_TRUE(frame_entry->initiator_origin().has_value());
EXPECT_EQ(url::Origin::Create(foo_url),
frame_entry->initiator_origin().value());
// 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));
NavigationControllerImpl& controller = static_cast<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(ExecJs(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());
EXPECT_FALSE(root_entry2->initiator_origin().has_value());
// 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(ExecJs(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(ExecJs(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