blob: 881fdc6cab1b1a657370096ddc2f9a90f723764d [file] [log] [blame]
// Copyright 2013 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 <array>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/optional.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/pattern.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_timeouts.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/url_formatter/url_formatter.h"
#include "content/browser/frame_host/frame_tree.h"
#include "content/browser/frame_host/navigation_entry_impl.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_input_event_router.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/unfreezable_frame_messages.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/file_select_listener.h"
#include "content/public/browser/invalidate_type.h"
#include "content/public/browser/javascript_dialog_manager.h"
#include "content/public/browser/load_notification_details.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.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/content_client.h"
#include "content/public/common/content_paths.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/no_renderer_crashes_assertion.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/test/content_browser_test_utils_internal.h"
#include "content/test/test_content_browser_client.h"
#include "net/base/features.h"
#include "net/base/ip_endpoint.h"
#include "net/base/network_isolation_key.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 "testing/gmock/include/gmock/gmock.h"
#include "third_party/blink/public/common/features.h"
#include "url/gurl.h"
namespace content {
#define SCOPE_TRACED(statement) \
{ \
SCOPED_TRACE(#statement); \
statement; \
}
void ResizeWebContentsView(Shell* shell, const gfx::Size& size,
bool set_start_page) {
// Shell::SizeTo is not implemented on Aura; WebContentsView::SizeContents
// works on Win and ChromeOS but not Linux - we need to resize the shell
// window on Linux because if we don't, the next layout of the unchanged shell
// window will resize WebContentsView back to the previous size.
// SizeContents is a hack and should not be relied on.
#if defined(OS_MACOSX)
shell->SizeTo(size);
// If |set_start_page| is true, start with blank page to make sure resize
// takes effect.
if (set_start_page)
NavigateToURL(shell, GURL("about://blank"));
#else
static_cast<WebContentsImpl*>(shell->web_contents())->GetView()->
SizeContents(size);
#endif // defined(OS_MACOSX)
}
// Class to test that OverrideWebkitPrefs has been called for all relevant
// RenderViewHosts.
class NotifyPreferencesChangedTestContentBrowserClient
: public TestContentBrowserClient {
public:
NotifyPreferencesChangedTestContentBrowserClient() = default;
void OverrideWebkitPrefs(RenderViewHost* render_view_host,
WebPreferences* prefs) override {
override_webkit_prefs_rvh_set_.insert(render_view_host);
}
const std::unordered_set<RenderViewHost*>& override_webkit_prefs_rvh_set() {
return override_webkit_prefs_rvh_set_;
}
private:
std::unordered_set<RenderViewHost*> override_webkit_prefs_rvh_set_;
DISALLOW_COPY_AND_ASSIGN(NotifyPreferencesChangedTestContentBrowserClient);
};
class WebContentsImplBrowserTest : public ContentBrowserTest {
public:
WebContentsImplBrowserTest() {}
void SetUp() override {
RenderWidgetHostImpl::DisableResizeAckCheckForTesting();
ContentBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
// Setup the server to allow serving separate sites, so we can perform
// cross-process navigation.
host_resolver()->AddRule("*", "127.0.0.1");
}
bool IsInFullscreen() {
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
return web_contents->current_fullscreen_frame_;
}
void SetUpCommandLine(base::CommandLine* command_line) override {
ContentBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kAllowPreCommitInput);
}
private:
DISALLOW_COPY_AND_ASSIGN(WebContentsImplBrowserTest);
};
// Keeps track of data from LoadNotificationDetails so we can later verify that
// they are correct, after the LoadNotificationDetails object is deleted.
class LoadStopNotificationObserver : public WindowedNotificationObserver {
public:
explicit LoadStopNotificationObserver(NavigationController* controller)
: WindowedNotificationObserver(NOTIFICATION_LOAD_STOP,
Source<NavigationController>(controller)),
session_index_(-1),
controller_(nullptr) {}
void Observe(int type,
const NotificationSource& source,
const NotificationDetails& details) override {
if (type == NOTIFICATION_LOAD_STOP) {
const Details<LoadNotificationDetails> load_details(details);
url_ = load_details->url;
session_index_ = load_details->session_index;
controller_ = load_details->controller;
}
WindowedNotificationObserver::Observe(type, source, details);
}
GURL url_;
int session_index_;
NavigationController* controller_;
};
// Starts a new navigation as soon as the current one commits, but does not
// wait for it to complete. This allows us to observe DidStopLoading while
// a pending entry is present.
class NavigateOnCommitObserver : public WebContentsObserver {
public:
NavigateOnCommitObserver(Shell* shell, GURL url)
: WebContentsObserver(shell->web_contents()),
shell_(shell),
url_(url),
done_(false) {
}
// WebContentsObserver:
void NavigationEntryCommitted(
const LoadCommittedDetails& load_details) override {
if (!done_) {
done_ = true;
shell_->LoadURL(url_);
// There should be a pending entry.
CHECK(shell_->web_contents()->GetController().GetPendingEntry());
// Now that there is a pending entry, stop the load.
shell_->Stop();
}
}
Shell* shell_;
GURL url_;
bool done_;
};
class RenderViewSizeDelegate : public WebContentsDelegate {
public:
void set_size_insets(const gfx::Size& size_insets) {
size_insets_ = size_insets;
}
// WebContentsDelegate:
gfx::Size GetSizeForNewRenderView(WebContents* web_contents) override {
gfx::Size size(web_contents->GetContainerBounds().size());
size.Enlarge(size_insets_.width(), size_insets_.height());
return size;
}
private:
gfx::Size size_insets_;
};
class RenderViewSizeObserver : public WebContentsObserver {
public:
RenderViewSizeObserver(Shell* shell, const gfx::Size& wcv_new_size)
: WebContentsObserver(shell->web_contents()),
shell_(shell),
wcv_new_size_(wcv_new_size) {
}
// WebContentsObserver:
void RenderFrameCreated(RenderFrameHost* rfh) override {
if (!rfh->GetParent())
rwhv_create_size_ = rfh->GetView()->GetViewBounds().size();
}
void DidStartNavigation(NavigationHandle* navigation_handle) override {
ResizeWebContentsView(shell_, wcv_new_size_, false);
}
gfx::Size rwhv_create_size() const { return rwhv_create_size_; }
private:
Shell* shell_; // Weak ptr.
gfx::Size wcv_new_size_;
gfx::Size rwhv_create_size_;
};
class LoadingStateChangedDelegate : public WebContentsDelegate {
public:
LoadingStateChangedDelegate()
: loadingStateChangedCount_(0)
, loadingStateToDifferentDocumentCount_(0) {
}
// WebContentsDelegate:
void LoadingStateChanged(WebContents* contents,
bool to_different_document) override {
loadingStateChangedCount_++;
if (to_different_document)
loadingStateToDifferentDocumentCount_++;
}
int loadingStateChangedCount() const { return loadingStateChangedCount_; }
int loadingStateToDifferentDocumentCount() const {
return loadingStateToDifferentDocumentCount_;
}
private:
int loadingStateChangedCount_;
int loadingStateToDifferentDocumentCount_;
};
// Test that DidStopLoading includes the correct URL in the details.
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, DidStopLoadingDetails) {
ASSERT_TRUE(embedded_test_server()->Start());
LoadStopNotificationObserver load_observer(
&shell()->web_contents()->GetController());
NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
load_observer.Wait();
EXPECT_EQ("/title1.html", load_observer.url_.path());
EXPECT_EQ(0, load_observer.session_index_);
EXPECT_EQ(&shell()->web_contents()->GetController(),
load_observer.controller_);
}
// Test that DidStopLoading includes the correct URL in the details when a
// pending entry is present.
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
DidStopLoadingDetailsWithPending) {
ASSERT_TRUE(embedded_test_server()->Start());
// TODO(clamy): Add a cross-process navigation case as well once
// crbug.com/581024 is fixed.
GURL url1 = embedded_test_server()->GetURL("/title1.html");
GURL url2 = embedded_test_server()->GetURL("/title2.html");
// Listen for the first load to stop.
LoadStopNotificationObserver load_observer(
&shell()->web_contents()->GetController());
// Start a new pending navigation as soon as the first load commits.
// We will hear a DidStopLoading from the first load as the new load
// is started.
NavigateOnCommitObserver commit_observer(shell(), url2);
NavigateToURL(shell(), url1);
load_observer.Wait();
EXPECT_EQ(url1, load_observer.url_);
EXPECT_EQ(0, load_observer.session_index_);
EXPECT_EQ(&shell()->web_contents()->GetController(),
load_observer.controller_);
}
namespace {
// Class that waits for a particular load to finish in any frame. This happens
// after the commit event.
class LoadFinishedWaiter : public WebContentsObserver {
public:
LoadFinishedWaiter(WebContents* web_contents, const GURL& expected_url)
: WebContentsObserver(web_contents),
expected_url_(expected_url),
run_loop_(new base::RunLoop()) {
EXPECT_TRUE(web_contents != nullptr);
}
void Wait() { run_loop_->Run(); }
private:
void DidFinishLoad(RenderFrameHost* render_frame_host,
const GURL& url) override {
if (url == expected_url_)
run_loop_->Quit();
}
GURL expected_url_;
std::unique_ptr<base::RunLoop> run_loop_;
};
} // namespace
// Ensure that cross-site subframes always notify their parents when they finish
// loading, so that the page eventually reaches DidStopLoading. There was a bug
// where an OOPIF would not notify its parent if (1) it finished loading, but
// (2) later added a subframe that kept the main frame in the loading state, and
// (3) all subframes then finished loading.
// Note that this test makes sense to run with and without OOPIFs.
// See https://crbug.com/822013#c12.
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
DidStopLoadingWithNestedFrames) {
ASSERT_TRUE(embedded_test_server()->Start());
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// Navigate to an A(B, C) page where B is slow to load. Wait for C to reach
// load stop. A will still be loading due to B.
GURL url_a = embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b,c)");
GURL url_b = embedded_test_server()->GetURL(
"b.com", "/cross_site_iframe_factory.html?b()");
GURL url_c = embedded_test_server()->GetURL(
"c.com", "/cross_site_iframe_factory.html?c()");
TestNavigationManager delayer_b(web_contents, url_b);
LoadFinishedWaiter load_waiter_c(web_contents, url_c);
shell()->LoadURL(url_a);
EXPECT_TRUE(delayer_b.WaitForRequestStart());
load_waiter_c.Wait();
EXPECT_TRUE(web_contents->IsLoading());
// At this point, C has finished loading and B is stalled. Add a slow D frame
// within C.
GURL url_d = embedded_test_server()->GetURL("d.com", "/title1.html");
FrameTreeNode* subframe_c = web_contents->GetFrameTree()->root()->child_at(1);
EXPECT_EQ(url_c, subframe_c->current_url());
TestNavigationManager delayer_d(web_contents, url_d);
const std::string add_d_script = base::StringPrintf(
"var f = document.createElement('iframe');"
"f.src='%s';"
"document.body.appendChild(f);",
url_d.spec().c_str());
EXPECT_TRUE(content::ExecuteScript(subframe_c, add_d_script));
EXPECT_TRUE(delayer_d.WaitForRequestStart());
EXPECT_TRUE(web_contents->IsLoading());
// Let B finish and wait for another load stop. A will still be loading due
// to D.
LoadFinishedWaiter load_waiter_b(web_contents, url_b);
delayer_b.WaitForNavigationFinished();
load_waiter_b.Wait();
EXPECT_TRUE(web_contents->IsLoading());
// Let D finish. We should get a load stop in the main frame.
LoadFinishedWaiter load_waiter_d(web_contents, url_d);
delayer_d.WaitForNavigationFinished();
load_waiter_d.Wait();
EXPECT_TRUE(WaitForLoadStop(web_contents));
EXPECT_FALSE(web_contents->IsLoading());
}
// Test that a renderer-initiated navigation to an invalid URL does not leave
// around a pending entry that could be used in a URL spoof. We test this in
// a browser test because our unit test framework incorrectly calls
// DidStartProvisionalLoadForFrame for in-page navigations.
// See http://crbug.com/280512.
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
ClearNonVisiblePendingOnFail) {
ASSERT_TRUE(embedded_test_server()->Start());
NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
// Navigate to an invalid URL and make sure it doesn't leave a pending entry.
LoadStopNotificationObserver load_observer1(
&shell()->web_contents()->GetController());
ASSERT_TRUE(
ExecuteScript(shell(), "window.location.href=\"nonexistent:12121\";"));
load_observer1.Wait();
EXPECT_FALSE(shell()->web_contents()->GetController().GetPendingEntry());
LoadStopNotificationObserver load_observer2(
&shell()->web_contents()->GetController());
ASSERT_TRUE(ExecuteScript(shell(), "window.location.href=\"#foo\";"));
load_observer2.Wait();
EXPECT_EQ(embedded_test_server()->GetURL("/title1.html#foo"),
shell()->web_contents()->GetVisibleURL());
}
// Crashes under ThreadSanitizer, http://crbug.com/356758.
#if defined(OS_WIN) || defined(OS_ANDROID) \
|| defined(THREAD_SANITIZER)
#define MAYBE_GetSizeForNewRenderView DISABLED_GetSizeForNewRenderView
#else
#define MAYBE_GetSizeForNewRenderView DISABLED_GetSizeForNewRenderView
#endif
// Test that RenderViewHost is created and updated at the size specified by
// WebContentsDelegate::GetSizeForNewRenderView().
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
MAYBE_GetSizeForNewRenderView) {
ASSERT_TRUE(embedded_test_server()->Start());
// Create a new server with a different site.
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.ServeFilesFromSourceDirectory(GetTestDataFilePath());
ASSERT_TRUE(https_server.Start());
std::unique_ptr<RenderViewSizeDelegate> delegate(
new RenderViewSizeDelegate());
shell()->web_contents()->SetDelegate(delegate.get());
ASSERT_TRUE(shell()->web_contents()->GetDelegate() == delegate.get());
// When no size is set, RenderWidgetHostView adopts the size of
// WebContentsView.
NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html"));
EXPECT_EQ(shell()->web_contents()->GetContainerBounds().size(),
shell()->web_contents()->GetRenderWidgetHostView()->GetViewBounds().
size());
// When a size is set, RenderWidgetHostView and WebContentsView honor this
// size.
gfx::Size size(300, 300);
gfx::Size size_insets(10, 15);
ResizeWebContentsView(shell(), size, true);
delegate->set_size_insets(size_insets);
NavigateToURL(shell(), https_server.GetURL("/"));
size.Enlarge(size_insets.width(), size_insets.height());
EXPECT_EQ(size,
shell()->web_contents()->GetRenderWidgetHostView()->GetViewBounds().
size());
// The web_contents size is set by the embedder, and should not depend on the
// rwhv size. The behavior is correct on OSX, but incorrect on other
// platforms.
gfx::Size exp_wcv_size(300, 300);
#if !defined(OS_MACOSX)
exp_wcv_size.Enlarge(size_insets.width(), size_insets.height());
#endif
EXPECT_EQ(exp_wcv_size,
shell()->web_contents()->GetContainerBounds().size());
// If WebContentsView is resized after RenderWidgetHostView is created but
// before pending navigation entry is committed, both RenderWidgetHostView and
// WebContentsView use the new size of WebContentsView.
gfx::Size init_size(200, 200);
gfx::Size new_size(100, 100);
size_insets = gfx::Size(20, 30);
ResizeWebContentsView(shell(), init_size, true);
delegate->set_size_insets(size_insets);
RenderViewSizeObserver observer(shell(), new_size);
NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
// RenderWidgetHostView is created at specified size.
init_size.Enlarge(size_insets.width(), size_insets.height());
EXPECT_EQ(init_size, observer.rwhv_create_size());
// Once again, the behavior is correct on OSX. The embedder explicitly sets
// the size to (100,100) during navigation. Both the wcv and the rwhv should
// take on that size.
#if !defined(OS_MACOSX)
new_size.Enlarge(size_insets.width(), size_insets.height());
#endif
gfx::Size actual_size = shell()->web_contents()->GetRenderWidgetHostView()->
GetViewBounds().size();
EXPECT_EQ(new_size, actual_size);
EXPECT_EQ(new_size, shell()->web_contents()->GetContainerBounds().size());
}
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, SetTitleOnUnload) {
GURL url(
"data:text/html,"
"<title>A</title>"
"<body onunload=\"document.title = 'B'\"></body>");
NavigateToURL(shell(), url);
ASSERT_EQ(1, shell()->web_contents()->GetController().GetEntryCount());
NavigationEntryImpl* entry1 = NavigationEntryImpl::FromNavigationEntry(
shell()->web_contents()->GetController().GetLastCommittedEntry());
SiteInstance* site_instance1 = entry1->site_instance();
EXPECT_EQ(base::ASCIIToUTF16("A"), entry1->GetTitle());
// Force a process switch by going to a privileged page.
GURL web_ui_page(std::string(kChromeUIScheme) + "://" +
std::string(kChromeUIGpuHost));
NavigateToURL(shell(), web_ui_page);
NavigationEntryImpl* entry2 = NavigationEntryImpl::FromNavigationEntry(
shell()->web_contents()->GetController().GetLastCommittedEntry());
SiteInstance* site_instance2 = entry2->site_instance();
EXPECT_NE(site_instance1, site_instance2);
EXPECT_EQ(2, shell()->web_contents()->GetController().GetEntryCount());
EXPECT_EQ(base::ASCIIToUTF16("B"), entry1->GetTitle());
}
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, OpenURLSubframe) {
// Navigate to a page with frames and grab a subframe's FrameTreeNode ID.
ASSERT_TRUE(embedded_test_server()->Start());
NavigateToURL(shell(),
embedded_test_server()->GetURL("/frame_tree/top.html"));
WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
FrameTreeNode* root = wc->GetFrameTree()->root();
ASSERT_EQ(3UL, root->child_count());
int frame_tree_node_id = root->child_at(0)->frame_tree_node_id();
EXPECT_NE(-1, frame_tree_node_id);
// Navigate with the subframe's FrameTreeNode ID.
const GURL url(embedded_test_server()->GetURL("/title1.html"));
OpenURLParams params(url, Referrer(), frame_tree_node_id,
WindowOpenDisposition::CURRENT_TAB,
ui::PAGE_TRANSITION_LINK, true);
params.initiator_origin = wc->GetMainFrame()->GetLastCommittedOrigin();
shell()->web_contents()->OpenURL(params);
// Make sure the NavigationEntry ends up with the FrameTreeNode ID.
NavigationController* controller = &shell()->web_contents()->GetController();
EXPECT_TRUE(controller->GetPendingEntry());
EXPECT_EQ(frame_tree_node_id,
NavigationEntryImpl::FromNavigationEntry(
controller->GetPendingEntry())->frame_tree_node_id());
}
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
AppendingFrameInWebUIDoesNotCrash) {
const GURL kWebUIUrl(GetWebUIURL("tracing"));
const char kJSCodeForAppendingFrame[] =
"document.body.appendChild(document.createElement('iframe'));";
NavigateToURL(shell(), kWebUIUrl);
EXPECT_TRUE(content::ExecuteScript(shell(), kJSCodeForAppendingFrame));
}
// Observer class to track the creation of RenderFrameHost objects. It is used
// in subsequent tests.
class RenderFrameCreatedObserver : public WebContentsObserver {
public:
explicit RenderFrameCreatedObserver(Shell* shell)
: WebContentsObserver(shell->web_contents()), last_rfh_(nullptr) {}
void RenderFrameCreated(RenderFrameHost* render_frame_host) override {
last_rfh_ = render_frame_host;
}
RenderFrameHost* last_rfh() const { return last_rfh_; }
private:
RenderFrameHost* last_rfh_;
DISALLOW_COPY_AND_ASSIGN(RenderFrameCreatedObserver);
};
// Test that creation of new RenderFrameHost objects sends the correct object
// to the WebContentObservers. See http://crbug.com/347339.
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
RenderFrameCreatedCorrectProcessForObservers) {
static const char kFooCom[] = "foo.com";
GURL::Replacements replace_host;
net::HostPortPair foo_host_port;
GURL cross_site_url;
ASSERT_TRUE(embedded_test_server()->Start());
foo_host_port = embedded_test_server()->host_port_pair();
foo_host_port.set_host(kFooCom);
GURL initial_url(embedded_test_server()->GetURL("/title1.html"));
cross_site_url = embedded_test_server()->GetURL("/title2.html");
replace_host.SetHostStr(kFooCom);
cross_site_url = cross_site_url.ReplaceComponents(replace_host);
// Navigate to the initial URL and capture the RenderFrameHost for later
// comparison.
NavigateToURL(shell(), initial_url);
RenderFrameHost* orig_rfh = shell()->web_contents()->GetMainFrame();
// Install the observer and navigate cross-site.
RenderFrameCreatedObserver observer(shell());
NavigateToURL(shell(), cross_site_url);
// The observer should've seen a RenderFrameCreated call for the new frame
// and not the old one.
EXPECT_NE(observer.last_rfh(), orig_rfh);
EXPECT_EQ(observer.last_rfh(), shell()->web_contents()->GetMainFrame());
}
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
LoadingStateChangedForSameDocumentNavigation) {
ASSERT_TRUE(embedded_test_server()->Start());
std::unique_ptr<LoadingStateChangedDelegate> delegate(
new LoadingStateChangedDelegate());
shell()->web_contents()->SetDelegate(delegate.get());
LoadStopNotificationObserver load_observer(
&shell()->web_contents()->GetController());
TitleWatcher title_watcher(shell()->web_contents(),
base::ASCIIToUTF16("pushState"));
NavigateToURL(shell(), embedded_test_server()->GetURL("/push_state.html"));
load_observer.Wait();
base::string16 title = title_watcher.WaitAndGetTitle();
ASSERT_EQ(title, base::ASCIIToUTF16("pushState"));
// LoadingStateChanged should be called 5 times: start and stop for the
// initial load of push_state.html, once for the switch from
// IsWaitingForResponse() to !IsWaitingForResponse(), and start and stop for
// the "navigation" triggered by history.pushState(). However, the start
// notification for the history.pushState() navigation should set
// to_different_document to false.
EXPECT_EQ("pushState", shell()->web_contents()->GetLastCommittedURL().ref());
EXPECT_EQ(5, delegate->loadingStateChangedCount());
EXPECT_EQ(4, delegate->loadingStateToDifferentDocumentCount());
}
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
RenderViewCreatedForChildWindow) {
ASSERT_TRUE(embedded_test_server()->Start());
NavigateToURL(shell(),
embedded_test_server()->GetURL("/title1.html"));
WebContentsAddedObserver new_web_contents_observer;
ASSERT_TRUE(ExecuteScript(shell(),
"var a = document.createElement('a');"
"a.href='./title2.html';"
"a.target = '_blank';"
"document.body.appendChild(a);"
"a.click();"));
WebContents* new_web_contents = new_web_contents_observer.GetWebContents();
WaitForLoadStop(new_web_contents);
EXPECT_TRUE(new_web_contents_observer.RenderViewCreatedCalled());
}
// Observer class to track resource loads.
class ResourceLoadObserver : public WebContentsObserver {
public:
explicit ResourceLoadObserver(Shell* shell)
: WebContentsObserver(shell->web_contents()) {}
const std::vector<mojom::ResourceLoadInfoPtr>& resource_load_infos() const {
return resource_load_infos_;
}
const std::vector<bool>& resource_is_associated_with_main_frame() const {
return resource_is_associated_with_main_frame_;
}
const std::vector<GURL>& memory_cached_loaded_urls() const {
return memory_cached_loaded_urls_;
}
// Use this method with the SCOPED_TRACE macro, so it shows the caller context
// if it fails.
void CheckResourceLoaded(
const GURL& url,
const GURL& referrer,
const std::string& load_method,
content::ResourceType resource_type,
const base::FilePath::StringPieceType& served_file_name,
const std::string& mime_type,
const std::string& ip_address,
bool was_cached,
bool first_network_request,
const base::TimeTicks& before_request,
const base::TimeTicks& after_request) {
bool resource_load_info_found = false;
for (const auto& resource_load_info : resource_load_infos_) {
if (resource_load_info->url != url)
continue;
resource_load_info_found = true;
int64_t file_size = -1;
if (!served_file_name.empty()) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::FilePath test_dir;
ASSERT_TRUE(base::PathService::Get(content::DIR_TEST_DATA, &test_dir));
base::FilePath served_file = test_dir.Append(served_file_name);
ASSERT_TRUE(GetFileSize(served_file, &file_size));
}
EXPECT_EQ(referrer, resource_load_info->referrer);
EXPECT_EQ(load_method, resource_load_info->method);
EXPECT_EQ(resource_type, resource_load_info->resource_type);
if (!first_network_request)
EXPECT_GT(resource_load_info->request_id, 0);
EXPECT_EQ(mime_type, resource_load_info->mime_type);
ASSERT_TRUE(resource_load_info->network_info->remote_endpoint);
EXPECT_EQ(ip_address, resource_load_info->network_info->remote_endpoint
->ToStringWithoutPort());
EXPECT_EQ(was_cached, resource_load_info->was_cached);
// Simple sanity check of the load timing info.
auto CheckTime = [before_request, after_request](auto actual) {
EXPECT_LE(before_request, actual);
EXPECT_GT(after_request, actual);
};
const net::LoadTimingInfo& timing = resource_load_info->load_timing_info;
CheckTime(timing.request_start);
CheckTime(timing.receive_headers_end);
CheckTime(timing.send_start);
CheckTime(timing.send_end);
if (!was_cached) {
CheckTime(timing.connect_timing.dns_start);
CheckTime(timing.connect_timing.dns_end);
CheckTime(timing.connect_timing.connect_start);
CheckTime(timing.connect_timing.connect_end);
}
if (file_size != -1) {
EXPECT_EQ(file_size, resource_load_info->raw_body_bytes);
EXPECT_LT(file_size, resource_load_info->total_received_bytes);
}
}
EXPECT_TRUE(resource_load_info_found);
}
// Returns the resource with the given url if found, otherwise nullptr.
mojom::ResourceLoadInfoPtr* FindResource(const GURL& url) {
for (auto& resource : resource_load_infos_) {
if (resource->url == url)
return &resource;
}
return nullptr;
}
void Reset() {
resource_load_infos_.clear();
memory_cached_loaded_urls_.clear();
resource_is_associated_with_main_frame_.clear();
}
void WaitForResourceCompletion(const GURL& url) {
// If we've already seen the resource, return immediately.
for (const auto& load_info : resource_load_infos_) {
if (load_info->url == url)
return;
}
// Otherwise wait for it.
base::RunLoop loop;
waiting_url_ = url;
waiting_callback_ = loop.QuitClosure();
loop.Run();
}
private:
// WebContentsObserver implementation:
void ResourceLoadComplete(
content::RenderFrameHost* render_frame_host,
const GlobalRequestID& request_id,
const mojom::ResourceLoadInfo& resource_load_info) override {
EXPECT_NE(nullptr, render_frame_host);
resource_load_infos_.push_back(resource_load_info.Clone());
resource_is_associated_with_main_frame_.push_back(
render_frame_host->GetParent() == nullptr);
// Have we been waiting for this resource? If so, run the callback.
if (waiting_url_.is_valid() && resource_load_info.url == waiting_url_) {
waiting_url_ = GURL();
std::move(waiting_callback_).Run();
}
}
void DidLoadResourceFromMemoryCache(const GURL& url,
const std::string& mime_type,
ResourceType resource_type) override {
memory_cached_loaded_urls_.push_back(url);
}
std::vector<GURL> memory_cached_loaded_urls_;
std::vector<mojom::ResourceLoadInfoPtr> resource_load_infos_;
std::vector<bool> resource_is_associated_with_main_frame_;
GURL waiting_url_;
base::OnceClosure waiting_callback_;
DISALLOW_COPY_AND_ASSIGN(ResourceLoadObserver);
};
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, ResourceLoadComplete) {
ResourceLoadObserver observer(shell());
ASSERT_TRUE(embedded_test_server()->Start());
// Load a page with an image and an image.
GURL page_url(embedded_test_server()->GetURL("/page_with_iframe.html"));
base::TimeTicks before = base::TimeTicks::Now();
NavigateToURL(shell(), page_url);
base::TimeTicks after = base::TimeTicks::Now();
ASSERT_EQ(3U, observer.resource_load_infos().size());
SCOPE_TRACED(observer.CheckResourceLoaded(
page_url, /*referrer=*/GURL(), "GET", content::ResourceType::kMainFrame,
FILE_PATH_LITERAL("page_with_iframe.html"), "text/html", "127.0.0.1",
/*was_cached=*/false, /*first_network_request=*/true, before, after));
SCOPE_TRACED(observer.CheckResourceLoaded(
embedded_test_server()->GetURL("/image.jpg"),
/*referrer=*/page_url, "GET", content::ResourceType::kImage,
FILE_PATH_LITERAL("image.jpg"), "image/jpeg", "127.0.0.1",
/*was_cached=*/false, /*first_network_request=*/false, before, after));
SCOPE_TRACED(observer.CheckResourceLoaded(
embedded_test_server()->GetURL("/title1.html"),
/*referrer=*/page_url, "GET", content::ResourceType::kSubFrame,
FILE_PATH_LITERAL("title1.html"), "text/html", "127.0.0.1",
/*was_cached=*/false, /*first_network_request=*/false, before, after));
}
// Same as WebContentsImplBrowserTest.ResourceLoadComplete but with resources
// retrieved from the network cache.
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
ResourceLoadCompleteFromNetworkCache) {
ResourceLoadObserver observer(shell());
ASSERT_TRUE(embedded_test_server()->Start());
GURL page_url(
embedded_test_server()->GetURL("/page_with_cached_subresource.html"));
base::TimeTicks before = base::TimeTicks::Now();
NavigateToURL(shell(), page_url);
base::TimeTicks after = base::TimeTicks::Now();
GURL resource_url = embedded_test_server()->GetURL("/cachetime");
ASSERT_EQ(2U, observer.resource_load_infos().size());
SCOPE_TRACED(observer.CheckResourceLoaded(
page_url, /*referrer=*/GURL(), "GET", content::ResourceType::kMainFrame,
/*served_file_name=*/FILE_PATH_LITERAL(""), "text/html", "127.0.0.1",
/*was_cached=*/false,
/*first_network_request=*/true, before, after));
SCOPE_TRACED(observer.CheckResourceLoaded(
resource_url, /*referrer=*/page_url, "GET",
content::ResourceType::kScript,
/*served_file_name=*/FILE_PATH_LITERAL(""), "text/html", "127.0.0.1",
/*was_cached=*/false, /*first_network_request=*/false, before, after));
EXPECT_TRUE(
observer.resource_load_infos()[1]->network_info->network_accessed);
EXPECT_TRUE(observer.memory_cached_loaded_urls().empty());
observer.Reset();
// Loading again should serve the request out of the in-memory cache.
before = base::TimeTicks::Now();
NavigateToURL(shell(), page_url);
after = base::TimeTicks::Now();
ASSERT_EQ(1U, observer.resource_load_infos().size());
SCOPE_TRACED(observer.CheckResourceLoaded(
page_url, /*referrer=*/GURL(), "GET", content::ResourceType::kMainFrame,
/*served_file_name=*/FILE_PATH_LITERAL(""), "text/html", "127.0.0.1",
/*was_cached=*/false, /*first_network_request=*/false, before, after));
ASSERT_EQ(1U, observer.memory_cached_loaded_urls().size());
EXPECT_EQ(resource_url, observer.memory_cached_loaded_urls()[0]);
observer.Reset();
// Kill the renderer process so when the navigate again, it will be a fresh
// renderer with an empty in-memory cache.
ScopedAllowRendererCrashes scoped_allow_renderer_crashes(shell());
NavigateToURL(shell(), GetWebUIURL("crash"));
// Reload that URL, the subresource should be served from the network cache.
before = base::TimeTicks::Now();
NavigateToURL(shell(), page_url);
after = base::TimeTicks::Now();
ASSERT_EQ(2U, observer.resource_load_infos().size());
SCOPE_TRACED(observer.CheckResourceLoaded(
page_url, /*referrer=*/GURL(), "GET", content::ResourceType::kMainFrame,
/*served_file_name=*/FILE_PATH_LITERAL(""), "text/html", "127.0.0.1",
/*was_cached=*/false, /*first_network_request=*/true, before, after));
SCOPE_TRACED(observer.CheckResourceLoaded(
resource_url, /*referrer=*/page_url, "GET",
content::ResourceType::kScript,
/*served_file_name=*/FILE_PATH_LITERAL(""), "text/html", "127.0.0.1",
/*was_cached=*/true, /*first_network_request=*/false, before, after));
EXPECT_TRUE(observer.memory_cached_loaded_urls().empty());
EXPECT_FALSE(
observer.resource_load_infos()[1]->network_info->network_accessed);
}
class WebContentsSplitCacheBrowserTest : public WebContentsImplBrowserTest {
public:
enum class Context { kMainFrame, kSameOriginFrame, kCrossOriginFrame };
WebContentsSplitCacheBrowserTest() {}
// WebContentsImplBrowserTest:
void SetUpOnMainThread() override {
WebContentsImplBrowserTest::SetUpOnMainThread();
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
&WebContentsSplitCacheBrowserTest::CachedScriptHandler,
base::Unretained(this)));
ASSERT_TRUE(embedded_test_server()->Start());
}
std::unique_ptr<net::test_server::HttpResponse> CachedScriptHandler(
const net::test_server::HttpRequest& request) {
GURL absolute_url = embedded_test_server()->GetURL(request.relative_url);
// Return a page that redirects to d.com/title1.html.
if (absolute_url.path() == "/redirect_to_d") {
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_SEE_OTHER);
http_response->AddCustomHeader(
"Location",
embedded_test_server()->GetURL("d.com", "/title1.html").spec());
return http_response;
}
// Return valid cacheable script.
if (absolute_url.path() == "/script") {
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
http_response->set_content("console.log(\"Hello World\");");
http_response->set_content_type("application/javascript");
http_response->AddCustomHeader("Cache-Control", "max-age=1000");
return http_response;
}
// A basic cacheable worker that loads 3p.com/script
if (absolute_url.path() == "/worker.js") {
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
GURL resource = GenURL("3p.com", "/script");
std::string content =
base::StringPrintf("importScripts('%s');", resource.spec().c_str());
http_response->set_content(content);
http_response->set_content_type("application/javascript");
http_response->AddCustomHeader("Cache-Control", "max-age=100000");
return http_response;
}
// Make the document resource cacheable.
if (absolute_url.path() == "/title1.html") {
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
http_response->AddCustomHeader("Cache-Control", "max-age=100000");
return http_response;
}
// A cacheable worker that loads a nested /worker.js on an origin provided
// as a query param.
if (absolute_url.path() == "/embedding_worker.js") {
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
GURL resource =
GenURL(base::StringPrintf("%s.com", absolute_url.query().c_str()),
"/worker.js");
const char kLoadWorkerScript[] = "let w = new Worker('%s');";
std::string content =
base::StringPrintf(kLoadWorkerScript, resource.spec().c_str());
http_response->set_content(content);
http_response->set_content_type("application/javascript");
http_response->AddCustomHeader("Cache-Control", "max-age=100000");
return http_response;
}
return std::unique_ptr<net::test_server::HttpResponse>();
}
protected:
// Loads 3p.com/script on page |url|, optionally from |sub_frame| if it's
// valid, and returns whether the script was cached or not.
bool TestResourceLoad(const GURL& url, const GURL& sub_frame) {
return TestResourceLoadHelper(url, sub_frame, GURL());
}
// Loads 3p.com/script on page |url| from |worker| and returns whether
// the script was cached or not.
bool TestResourceLoadFromDedicatedWorker(const GURL& url,
const GURL& worker) {
DCHECK(worker.is_valid());
return TestResourceLoadHelper(url, GURL(), worker);
}
// Loads 3p.com/script on page |url| from |worker| inside |sub_frame|
// and returns whether the script was cached or not.
bool TestResourceLoadFromDedicatedWorkerInIframe(const GURL& url,
const GURL& sub_frame,
const GURL& worker) {
DCHECK(sub_frame.is_valid());
DCHECK(worker.is_valid());
return TestResourceLoadHelper(url, sub_frame, worker);
}
bool TestResourceLoadHelper(const GURL& url,
const GURL& sub_frame,
const GURL& worker) {
DCHECK(url.is_valid());
// Do a cross-process navigation to clear the in-memory cache.
// We assume that we don't start this call from "chrome://gpu", as
// otherwise it won't be a cross-process navigation. We are relying
// on this navigation to discard the old process.
EXPECT_TRUE(NavigateToURL(shell(), GetWebUIURL("gpu")));
// Observe network requests.
ResourceLoadObserver observer(shell());
// In the case of a redirect, the observed URL will be different from
// what NavigateToURL(...) expects.
if (base::StartsWith(url.path(), "/redirect", base::CompareCase::SENSITIVE))
EXPECT_FALSE(NavigateToURL(shell(), url));
else
EXPECT_TRUE(NavigateToURL(shell(), url));
RenderFrameHost* host_to_load_resource =
shell()->web_contents()->GetMainFrame();
RenderFrameHostImpl* main_frame =
static_cast<RenderFrameHostImpl*>(host_to_load_resource);
// If there is supposed to be a subframe, create it.
if (sub_frame.is_valid()) {
host_to_load_resource = CreateSubframe(sub_frame);
}
GURL resource = GenURL("3p.com", "/script");
// If there is supposed to be a worker to load this resource, create it.
// Otherwise, load the resource directly.
if (worker.is_valid()) {
EXPECT_TRUE(
ExecuteScript(host_to_load_resource, GetWorkerScript(worker)));
} else {
EXPECT_TRUE(ExecuteScript(host_to_load_resource,
GetLoadResourceScript(resource)));
}
observer.WaitForResourceCompletion(resource);
// Test the network isolation key.
url::Origin top_frame_origin =
main_frame->frame_tree_node()->current_origin();
RenderFrameHostImpl* frame_host =
static_cast<RenderFrameHostImpl*>(host_to_load_resource);
url::Origin frame_origin;
if (sub_frame.is_empty()) {
frame_origin = top_frame_origin;
} else {
frame_origin = url::Origin::Create(sub_frame);
// TODO(crbug.com/888079) in about:blank currently committed origin is
// different from the origin at the time of CommitNavigation.
if (!frame_origin.opaque()) {
// Modify to take redirects into account.
frame_origin = frame_host->GetLastCommittedOrigin();
}
}
if (!top_frame_origin.opaque() && !frame_origin.opaque()) {
EXPECT_EQ(net::NetworkIsolationKey(top_frame_origin, frame_origin),
frame_host->network_isolation_key_);
} else {
EXPECT_TRUE(frame_host->network_isolation_key_.IsTransient());
}
return (*observer.FindResource(resource))->was_cached;
}
// Navigates to |url| and returns if the navigation resource was fetched from
// the cache or not.
bool NavigationResourceCached(const GURL& url,
const GURL& sub_frame,
bool subframe_navigation_resource_cached) {
// Do a cross-process navigation to clear the in-memory cache.
// We assume that we don't start this call from "chrome://gpu", as
// otherwise it won't be a cross-process navigation. We are relying
// on this navigation to discard the old process.
EXPECT_TRUE(NavigateToURL(shell(), GetWebUIURL("gpu")));
// Observe network requests.
ResourceLoadObserver observer(shell());
NavigateToURL(shell(), url);
RenderFrameHostImpl* main_frame = static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetMainFrame());
GURL main_url = main_frame->frame_tree_node()->current_url();
observer.WaitForResourceCompletion(main_url);
if (sub_frame.is_valid()) {
EXPECT_EQ(1U, main_frame->frame_tree_node()->child_count());
NavigateFrameToURL(main_frame->frame_tree_node()->child_at(0), sub_frame);
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
GURL sub_frame_url =
main_frame->frame_tree_node()->child_at(0)->current_url();
observer.WaitForResourceCompletion(sub_frame_url);
EXPECT_EQ(subframe_navigation_resource_cached,
(*observer.FindResource(sub_frame_url))->was_cached);
}
return (*observer.FindResource(main_url))->was_cached;
}
// Loads a dedicated worker script and checks to see whether or not the
// script was cached.
bool DedicatedWorkerScriptCached(const GURL& url,
const GURL& sub_frame,
const GURL& worker) {
DCHECK(url.is_valid());
DCHECK(worker.is_valid());
// Do a cross-process navigation to clear the in-memory cache.
// We assume that we don't start this call from "chrome://gpu", as
// otherwise it won't be a cross-process navigation. We are relying
// on this navigation to discard the old process.
EXPECT_TRUE(NavigateToURL(shell(), GetWebUIURL("gpu")));
// Observe network requests.
ResourceLoadObserver observer(shell());
EXPECT_TRUE(NavigateToURL(shell(), url));
RenderFrameHost* host_to_load_resource =
shell()->web_contents()->GetMainFrame();
// If there is supposed to be a subframe, create it.
if (sub_frame.is_valid()) {
host_to_load_resource = CreateSubframe(sub_frame);
}
EXPECT_TRUE(ExecuteScript(host_to_load_resource, GetWorkerScript(worker)));
observer.WaitForResourceCompletion(GenURL("3p.com", "/script"));
return (*observer.FindResource(worker))->was_cached;
}
// Gets script to create subframe.
std::string GetSubframeScript(const GURL& sub_frame) {
const char kLoadIframeScript[] = R"(
let iframe = document.createElement('iframe');
iframe.src = $1;
document.body.appendChild(iframe);
)";
return JsReplace(kLoadIframeScript, sub_frame);
}
// Gets script to create worker.
std::string GetWorkerScript(const GURL& worker) {
const char kLoadWorkerScript[] = "let w = new Worker($1);";
return JsReplace(kLoadWorkerScript, worker);
}
// Gets script to load resource.
std::string GetLoadResourceScript(const GURL& resource) {
const char kLoadResourceScript[] = R"(
let script = document.createElement('script');
script.src = $1;
document.body.appendChild(script);
)";
return JsReplace(kLoadResourceScript, resource);
}
// Creates and loads subframe, waits for load to stop, and then returns
// subframe from the web contents frame tree.
RenderFrameHost* CreateSubframe(const GURL& sub_frame) {
EXPECT_TRUE(ExecuteScript(shell(), GetSubframeScript(sub_frame)));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
return static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root()
->child_at(0)
->current_frame_host();
}
GURL GenURL(const std::string& host, const std::string& path) {
return embedded_test_server()->GetURL(host, path);
}
private:
DISALLOW_COPY_AND_ASSIGN(WebContentsSplitCacheBrowserTest);
};
class WebContentsSplitCacheWithFrameOriginBrowserTest
: public WebContentsSplitCacheBrowserTest {
public:
WebContentsSplitCacheWithFrameOriginBrowserTest() {
feature_list.InitWithFeatures(
{net::features::kSplitCacheByNetworkIsolationKey,
net::features::kAppendFrameOriginToNetworkIsolationKey},
{});
}
private:
base::test::ScopedFeatureList feature_list;
};
class WebContentsSplitCacheBrowserTestEnabled
: public WebContentsSplitCacheBrowserTest,
public ::testing::WithParamInterface<bool> {
public:
WebContentsSplitCacheBrowserTestEnabled() {
std::vector<base::Feature> enabled_features;
enabled_features.push_back(net::features::kSplitCacheByNetworkIsolationKey);
// When the test parameter is true, we test the split cache with
// PlzDedicatedWorker enabled.
if (GetParam())
enabled_features.push_back(blink::features::kPlzDedicatedWorker);
feature_list.InitWithFeatures(enabled_features, {});
}
private:
base::test::ScopedFeatureList feature_list;
};
class WebContentsSplitCacheBrowserTestDisabled
: public WebContentsSplitCacheBrowserTest {
public:
WebContentsSplitCacheBrowserTestDisabled() {
feature_list.InitAndDisableFeature(
net::features::kSplitCacheByNetworkIsolationKey);
}
private:
base::test::ScopedFeatureList feature_list;
};
#if defined(THREAD_SANITIZER)
// Flaky under TSan: https://crbug.com/995181
#define MAYBE_SplitCache DISABLED_SplitCache
#else
#define MAYBE_SplitCache SplitCache
#endif
IN_PROC_BROWSER_TEST_P(WebContentsSplitCacheBrowserTestEnabled,
MAYBE_SplitCache) {
// Load a cacheable resource for the first time, and it's not cached.
EXPECT_FALSE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));
// The second time, it's cached.
EXPECT_TRUE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));
// Now load it from a different site, and the resource isn't cached because
// the top frame origin is different.
EXPECT_FALSE(TestResourceLoad(GenURL("b.com", "/title1.html"), GURL()));
// Now load it from 3p.com, which is same-site to the cacheable
// resource. Still not supposed to be cached.
EXPECT_FALSE(TestResourceLoad(GenURL("3p.com", "/title1.html"), GURL()));
// Load it from a a.com/redirect_to_d which redirects to d.com/title1.html and
// the resource shouldn't be cached because now we're on d.com.
EXPECT_FALSE(TestResourceLoad(GenURL("a.com", "/redirect_to_d"), GURL()));
// Navigate to d.com directly. The main resource should be cached due to the
// earlier navigation.
EXPECT_TRUE(TestResourceLoad(GenURL("d.com", "/title1.html"), GURL()));
// Load the resource from a same-origin iframe on a page where it's already
// cached. It should still be cached.
EXPECT_TRUE(TestResourceLoad(GenURL("a.com", "/title1.html"),
GenURL("a.com", "/title1.html")));
// Load the resource from a cross-origin iframe on a page where it's already
// cached. It should still be cached.
EXPECT_TRUE(TestResourceLoad(GenURL("a.com", "/title1.html"),
GenURL("d.com", "/title1.html")));
// Load the resource from a same-origin iframe on a page where it's not
// cached. It should not be cached.
EXPECT_FALSE(TestResourceLoad(GenURL("e.com", "/title1.html"),
GenURL("e.com", "/title1.html")));
// Load the resource from a cross-origin iframe where the iframe's origin has
// seen the object before but the top frame hasn't. It should not be cached.
EXPECT_FALSE(TestResourceLoad(GenURL("f.com", "/title1.html"),
GenURL("a.com", "/title1.html")));
// Load the resource from a data url which has an opaque origin. It shouldn't
// be cached.
GURL data_url("data:text/html,<body>Hello World</body>");
EXPECT_FALSE(TestResourceLoad(data_url, GURL()));
// Load the same resource from the same data url, it shouldn't be cached
// because the origin should be unique.
EXPECT_FALSE(TestResourceLoad(data_url, GURL()));
// Load the resource from a document that points to about:blank.
GURL blank_url("about:blank");
EXPECT_FALSE(TestResourceLoad(blank_url, GURL()));
// Load the same resource from about:blank url again, it shouldn't be cached
// because the origin is unique. TODO(crbug.com/888079) will change this
// behavior and about:blank main frame pages will inherit the origin of the
// page that opened it.
EXPECT_FALSE(TestResourceLoad(blank_url, GURL()));
}
IN_PROC_BROWSER_TEST_F(WebContentsSplitCacheWithFrameOriginBrowserTest,
SplitCache) {
// Load a cacheable resource for the first time, and it's not cached.
EXPECT_FALSE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));
// The second time, it's cached.
EXPECT_TRUE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));
// Load it from a a.com/redirect_to_d which redirects to d.com/title1.html and
// the resource shouldn't be cached because now we're on d.com.
EXPECT_FALSE(TestResourceLoad(GenURL("a.com", "/title1.html"),
GenURL("a.com", "/redirect_to_d")));
// Now load it from the d.com iframe directly. It should be cached.
EXPECT_TRUE(TestResourceLoad(GenURL("a.com", "/title1.html"),
GenURL("d.com", "/title1.html")));
// Load the resource from a same-origin iframe on a page where it's already
// cached. It should still be cached.
EXPECT_TRUE(TestResourceLoad(GenURL("a.com", "/title1.html"),
GenURL("a.com", "/title1.html")));
// Load the resource from a cross-origin iframe on a page where the
// iframe hasn't been cached previously. It should not be cached.
EXPECT_FALSE(TestResourceLoad(GenURL("a.com", "/title1.html"),
GenURL("e.com", "/title1.html")));
EXPECT_TRUE(TestResourceLoad(GenURL("a.com", "/title1.html"),
GenURL("e.com", "/title1.html")));
// Load the resource from a same-origin iframe on a page where it's not
// cached. It should not be cached.
EXPECT_FALSE(TestResourceLoad(GenURL("e.com", "/title1.html"),
GenURL("e.com", "/title1.html")));
EXPECT_TRUE(TestResourceLoad(GenURL("e.com", "/title1.html"),
GenURL("e.com", "/title1.html")));
// Load the resource from a cross-origin iframe where the iframe's origin has
// seen the object before but the top frame hasn't. It should not be cached.
EXPECT_FALSE(TestResourceLoad(GenURL("f.com", "/title1.html"),
GenURL("a.com", "/title1.html")));
// Load the resource from a data url which has an opaque origin. It shouldn't
// be cached.
GURL data_url("data:text/html,<body>Hello World</body>");
EXPECT_FALSE(TestResourceLoad(GenURL("a.com", "/title1.html"), data_url));
// Load the same resource from the same data url, it shouldn't be cached
// because the origin should be unique.
EXPECT_FALSE(TestResourceLoad(GenURL("a.com", "/title1.html"), data_url));
// Load the resource from a subframe document that points to about:blank. The
// resource is cached because the resource load is using the main frame's
// URLLoaderFactory and main frame's factory has the NIK set to
// (a.com, a.com) which is already in the cache.
GURL blank_url(url::kAboutBlankURL);
EXPECT_TRUE(TestResourceLoad(GenURL("a.com", "/title1.html"), blank_url));
}
IN_PROC_BROWSER_TEST_F(WebContentsSplitCacheBrowserTestDisabled,
NonSplitCache) {
// Load a cacheable resource for the first time, and it's not cached.
EXPECT_FALSE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));
// The second time, it's cached.
EXPECT_TRUE(TestResourceLoad(GenURL("a.com", "/title1.html"), GURL()));
// Now load it from a different site, and the resource is cached.
EXPECT_TRUE(TestResourceLoad(GenURL("b.com", "/title1.html"), GURL()));
// Load it from a cross-origin iframe, and it's still cached.
EXPECT_TRUE(TestResourceLoad(GenURL("b.com", "/title1.html"),
GenURL("c.com", "/title1.html")));
}
IN_PROC_BROWSER_TEST_F(WebContentsSplitCacheWithFrameOriginBrowserTest,
SplitCacheDedicatedWorkers) {
// Load 3p.com/script from a.com's worker. The first time it's loaded from the
// network and the second it's cached.
EXPECT_FALSE(TestResourceLoadFromDedicatedWorker(
GenURL("a.com", "/title1.html"), GenURL("a.com", "/worker.js")));
EXPECT_TRUE(TestResourceLoadFromDedicatedWorker(
GenURL("a.com", "/title1.html"), GenURL("a.com", "/worker.js")));
// Load 3p.com/script from a worker with a new top frame origin. Due to split
// caching it's a cache miss.
EXPECT_FALSE(TestResourceLoadFromDedicatedWorker(
GenURL("b.com", "/title1.html"), GenURL("b.com", "/worker.js")));
EXPECT_TRUE(TestResourceLoadFromDedicatedWorker(
GenURL("b.com", "/title1.html"), GenURL("b.com", "/worker.js")));
// Cross origin workers.
EXPECT_FALSE(TestResourceLoadFromDedicatedWorkerInIframe(
GenURL("d.com", "/title1.html"), GenURL("e.com", "/title1.html"),
GenURL("e.com", "/worker.js")));
EXPECT_TRUE(TestResourceLoadFromDedicatedWorkerInIframe(
GenURL("d.com", "/title1.html"), GenURL("e.com", "/title1.html"),
GenURL("e.com", "/worker.js")));
// Load 3p.com/script from a nested worker with a new top-frame origin. Due to
// split caching it's a cache miss.
EXPECT_FALSE(TestResourceLoadFromDedicatedWorker(
GenURL("c.com", "/title1.html"),
GenURL("c.com", "/embedding_worker.js?c")));
EXPECT_TRUE(TestResourceLoadFromDedicatedWorker(
GenURL("c.com", "/title1.html"),
GenURL("c.com", "/embedding_worker.js?c")));
}
IN_PROC_BROWSER_TEST_P(WebContentsSplitCacheBrowserTestEnabled,
NavigationResources) {
// Navigate for the first time, and it's not cached.
EXPECT_FALSE(
NavigationResourceCached(GenURL("a.com", "/title1.html"), GURL(), false));
// The second time, it's cached.
EXPECT_TRUE(
NavigationResourceCached(GenURL("a.com", "/title1.html"), GURL(), false));
// Navigate to a.com/redirect_to_d which redirects to d.com/title1.html.
EXPECT_FALSE(NavigationResourceCached(GenURL("a.com", "/redirect_to_d"),
GURL(), false));
// Navigate to d.com directly. The main resource should be cached due to the
// earlier redirected navigation.
EXPECT_TRUE(
NavigationResourceCached(GenURL("d.com", "/title1.html"), GURL(), false));
// Navigate to a subframe with the same top frame origin as in an earlier
// navigation and same url as already navigated to earlier in a main frame
// navigation. It should be a cache hit for the subframe resource.
EXPECT_FALSE(NavigationResourceCached(
GenURL("a.com", "/navigation_controller/page_with_iframe.html"),
GenURL("a.com", "/title1.html"), true));
// Navigate to the same subframe document from a different top frame origin.
// It should be a cache miss.
EXPECT_FALSE(NavigationResourceCached(
GenURL("b.com", "/navigation_controller/page_with_iframe.html"),
GenURL("a.com", "/title1.html"), false));
}
IN_PROC_BROWSER_TEST_F(WebContentsSplitCacheWithFrameOriginBrowserTest,
SubframeNavigationResources) {
// Navigate for the first time, and it's not cached.
NavigationResourceCached(
GenURL("a.com", "/navigation_controller/page_with_iframe.html"),
GenURL("a.com", "/title1.html"), false);
// The second time it should be a cache hit.
NavigationResourceCached(
GenURL("a.com", "/navigation_controller/page_with_iframe.html"),
GenURL("a.com", "/title1.html"), true);
// Navigate to the same subframe document from a different top frame origin.
// It should be a cache miss.
NavigationResourceCached(
GenURL("b.com", "/navigation_controller/page_with_iframe.html"),
GenURL("a.com", "/title1.html"), false);
// Navigate the subframe to a.com/redirect_to_d which redirects to
// d.com/title1.html.
NavigationResourceCached(
GenURL("a.com", "/navigation_controller/page_with_iframe.html"),
GenURL("a.com", "/redirect_to_d"), false);
// Navigate to d.com directly. The resource should be cached due to the
// earlier redirected navigation.
NavigationResourceCached(
GenURL("a.com", "/navigation_controller/page_with_iframe.html"),
GenURL("d.com", "/title1.html"), true);
}
// TODO(http://crbug.com/997808): Flaky on Linux ASAN.
#if defined(OS_LINUX) && defined(ADDRESS_SANITIZER)
#define MAYBE_SplitCacheDedicatedWorkers DISABLED_SplitCacheDedicatedWorkers
#else
#define MAYBE_SplitCacheDedicatedWorkers SplitCacheDedicatedWorkers
#endif
IN_PROC_BROWSER_TEST_P(WebContentsSplitCacheBrowserTestEnabled,
MAYBE_SplitCacheDedicatedWorkers) {
// Load 3p.com/script from a.com's worker. The first time it's loaded from the
// network and the second it's cached.
EXPECT_FALSE(TestResourceLoadFromDedicatedWorker(
GenURL("a.com", "/title1.html"), GenURL("a.com", "/worker.js")));
EXPECT_TRUE(TestResourceLoadFromDedicatedWorker(
GenURL("a.com", "/title1.html"), GenURL("a.com", "/worker.js")));
// Load 3p.com/script from a worker with a new top-frame origin. Due to split
// caching it's a cache miss.
EXPECT_FALSE(TestResourceLoadFromDedicatedWorker(
GenURL("b.com", "/title1.html"), GenURL("b.com", "/worker.js")));
EXPECT_TRUE(TestResourceLoadFromDedicatedWorker(
GenURL("b.com", "/title1.html"), GenURL("b.com", "/worker.js")));
// Load 3p.com/script from a nested worker with a new top-frame origin. Due to
// split caching it's a cache miss.
EXPECT_FALSE(TestResourceLoadFromDedicatedWorker(
GenURL("c.com", "/title1.html"),
GenURL("c.com", "/embedding_worker.js?c")));
EXPECT_TRUE(TestResourceLoadFromDedicatedWorker(
GenURL("c.com", "/title1.html"),
GenURL("c.com", "/embedding_worker.js?c")));
// Load 3p.com/script from a worker with a new top-frame origin and nested in
// a cross-origin iframe. Due to split caching it's a cache miss.
EXPECT_FALSE(TestResourceLoadFromDedicatedWorkerInIframe(
GenURL("d.com", "/title1.html"), GenURL("e.com", "/title1.html"),
GenURL("e.com", "/worker.js")));
EXPECT_TRUE(TestResourceLoadFromDedicatedWorkerInIframe(
GenURL("d.com", "/title1.html"), GenURL("e.com", "/title1.html"),
GenURL("e.com", "/worker.js")));
// Load 3p.com/script from a worker with a new top-frame origin and nested in
// a cross-origin iframe whose URL has previously been loaded.
EXPECT_FALSE(TestResourceLoadFromDedicatedWorkerInIframe(
GenURL("f.com", "/title1.html"), GenURL("e.com", "/title1.html"),
GenURL("e.com", "/worker.js")));
EXPECT_TRUE(TestResourceLoadFromDedicatedWorkerInIframe(
GenURL("f.com", "/title1.html"), GenURL("e.com", "/title1.html"),
GenURL("e.com", "/worker.js")));
}
// TODO(http://crbug.com/997732): Flaky on Linux.
#if defined(OS_LINUX)
#define MAYBE_SplitCacheDedicatedWorkerScripts \
DISABLED_SplitCacheDedicatedWorkersScripts
#else
#define MAYBE_SplitCacheDedicatedWorkerScripts \
SplitCacheDedicatedWorkersScripts
#endif
IN_PROC_BROWSER_TEST_P(WebContentsSplitCacheBrowserTestEnabled,
MAYBE_SplitCacheDedicatedWorkerScripts) {
// Load a.com's worker. The first time the worker script is loaded from the
// network and the second it's cached.
EXPECT_FALSE(DedicatedWorkerScriptCached(
GenURL("a.com", "/title1.html"), GURL(), GenURL("a.com", "/worker.js")));
EXPECT_TRUE(DedicatedWorkerScriptCached(
GenURL("a.com", "/title1.html"), GURL(), GenURL("a.com", "/worker.js")));
// Load a nested worker with a new top-frame origin. It's a cache miss for
// the embedding worker the first time, as it hasn't been loaded yet, and
// then the second time it's cached.
EXPECT_FALSE(
DedicatedWorkerScriptCached(GenURL("c.com", "/title1.html"), GURL(),
GenURL("c.com", "/embedding_worker.js?c")));
EXPECT_TRUE(
DedicatedWorkerScriptCached(GenURL("c.com", "/title1.html"), GURL(),
GenURL("c.com", "/embedding_worker.js?c")));
// Load a worker with a new top-frame origin and nested in a cross-origin
// iframe. It's a cache miss for the worker script the first time, then
// the second time it's cached.
EXPECT_FALSE(DedicatedWorkerScriptCached(GenURL("d.com", "/title1.html"),
GenURL("e.com", "/title1.html"),
GenURL("e.com", "/worker.js")));
EXPECT_TRUE(DedicatedWorkerScriptCached(GenURL("d.com", "/title1.html"),
GenURL("e.com", "/title1.html"),
GenURL("e.com", "/worker.js")));
// Load a worker with a new top-frame origin and nested in a cross-origin
// iframe whose URL has previously been loaded. Due to split caching it's a
// cache miss for the worker script the first time.
EXPECT_FALSE(DedicatedWorkerScriptCached(GenURL("f.com", "/title1.html"),
GenURL("e.com", "/title1.html"),
GenURL("e.com", "/worker.js")));
EXPECT_TRUE(DedicatedWorkerScriptCached(GenURL("f.com", "/title1.html"),
GenURL("e.com", "/title1.html"),
GenURL("e.com", "/worker.js")));
}
IN_PROC_BROWSER_TEST_F(WebContentsSplitCacheBrowserTestDisabled,
MAYBE_SplitCacheDedicatedWorkers) {
// Load 3p.com/script from a.com's worker. The first time it's loaded from the
// network and the second it's cached.
EXPECT_FALSE(TestResourceLoadFromDedicatedWorker(
GenURL("a.com", "/title1.html"), GenURL("a.com", "/worker.js")));
EXPECT_TRUE(TestResourceLoadFromDedicatedWorker(
GenURL("a.com", "/title1.html"), GenURL("a.com", "/worker.js")));
// Load 3p.com/script from b.com's worker. The cache isn't split by top-frame
// origin so the resource is already cached.
EXPECT_TRUE(TestResourceLoadFromDedicatedWorker(
GenURL("b.com", "/title1.html"), GenURL("b.com", "/worker.js")));
// Load 3p.com/script from a nested worker with a new top-frame origin. The
// cache isn't split by top-frame origin so the resource is already cached.
EXPECT_TRUE(TestResourceLoadFromDedicatedWorker(
GenURL("c.com", "/title1.html"),
GenURL("c.com", "/embedding_worker.js?c")));
// Load 3p.com/script from a worker with a new top-frame origin and nested in
// a cross-origin iframe. The cache isn't split by top-frame origin so the
// resource is already cached.
EXPECT_TRUE(TestResourceLoadFromDedicatedWorkerInIframe(
GenURL("d.com", "/title1.html"), GenURL("e.com", "/title1.html"),
GenURL("e.com", "/worker.js")));
EXPECT_TRUE(TestResourceLoadFromDedicatedWorkerInIframe(
GenURL("f.com", "/title1.html"), GenURL("e.com", "/title1.html"),
GenURL("e.com", "/worker.js")));
}
INSTANTIATE_TEST_SUITE_P(/* no prefix */,
WebContentsSplitCacheBrowserTestEnabled,
::testing::Values(true, false));
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
ResourceLoadCompleteFromLocalResource) {
ResourceLoadObserver observer(shell());
ASSERT_TRUE(embedded_test_server()->Start());
NavigateToURL(shell(),
GURL(embedded_test_server()->GetURL("/page_with_image.html")));
ASSERT_EQ(2U, observer.resource_load_infos().size());
EXPECT_TRUE(
observer.resource_load_infos()[0]->network_info->network_accessed);
EXPECT_TRUE(
observer.resource_load_infos()[1]->network_info->network_accessed);
observer.Reset();
NavigateToURL(shell(), GetWebUIURL("gpu"));
ASSERT_LE(1U, observer.resource_load_infos().size());
for (const mojom::ResourceLoadInfoPtr& resource_load_info :
observer.resource_load_infos()) {
EXPECT_FALSE(resource_load_info->network_info->network_accessed);
}
}
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
ResourceLoadCompleteWithRedirect) {
ResourceLoadObserver observer(shell());
ASSERT_TRUE(embedded_test_server()->Start());
GURL page_destination_url(
embedded_test_server()->GetURL("/page_with_image_redirect.html"));
GURL page_original_url(embedded_test_server()->GetURL(
"/server-redirect?" + page_destination_url.spec()));
NavigateToURL(shell(), page_original_url);
ASSERT_EQ(2U, observer.resource_load_infos().size());
const mojom::ResourceLoadInfoPtr& page_load_info =
observer.resource_load_infos()[0];
EXPECT_EQ(page_destination_url, page_load_info->url);
EXPECT_EQ(page_original_url, page_load_info->original_url);
GURL image_destination_url(embedded_test_server()->GetURL("/blank.jpg"));
GURL image_original_url(
embedded_test_server()->GetURL("/server-redirect?blank.jpg"));
const mojom::ResourceLoadInfoPtr& image_load_info =
observer.resource_load_infos()[1];
EXPECT_EQ(image_destination_url, image_load_info->url);
EXPECT_EQ(image_original_url, image_load_info->original_url);
}
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
ResourceLoadCompleteNetError) {
ResourceLoadObserver observer(shell());
ASSERT_TRUE(embedded_test_server()->Start());
GURL page_url(embedded_test_server()->GetURL("/page_with_image.html"));
GURL image_url(embedded_test_server()->GetURL("/blank.jpg"));
// Load the page without errors.
NavigateToURL(shell(), page_url);
ASSERT_EQ(2U, observer.resource_load_infos().size());
EXPECT_EQ(net::OK, observer.resource_load_infos()[0]->net_error);
EXPECT_EQ(net::OK, observer.resource_load_infos()[1]->net_error);
observer.Reset();
// Load the page and simulate a network error.
content::URLLoaderInterceptor url_interceptor(base::BindRepeating(
[](const GURL& url,
content::URLLoaderInterceptor::RequestParams* params) {
if (params->url_request.url != url)
return false;
network::URLLoaderCompletionStatus status;
status.error_code = net::ERR_ADDRESS_UNREACHABLE;
params->client->OnComplete(status);
return true;
},
image_url));
NavigateToURL(shell(), page_url);
ASSERT_EQ(2U, observer.resource_load_infos().size());
EXPECT_EQ(net::OK, observer.resource_load_infos()[0]->net_error);
EXPECT_EQ(net::ERR_ADDRESS_UNREACHABLE,
observer.resource_load_infos()[1]->net_error);
}
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
ResourceLoadCompleteAlwaysAccessNetwork) {
ResourceLoadObserver observer(shell());
ASSERT_TRUE(embedded_test_server()->Start());
GURL cacheable_url(embedded_test_server()->GetURL("/set-header"));
NavigateToURL(shell(), cacheable_url);
ASSERT_EQ(1U, observer.resource_load_infos().size());
EXPECT_FALSE(
observer.resource_load_infos()[0]->network_info->always_access_network);
observer.Reset();
std::array<std::string, 3> headers = {
"cache-control: no-cache", "cache-control: no-store", "pragma: no-cache"};
for (const std::string& header : headers) {
GURL no_cache_url(embedded_test_server()->GetURL("/set-header?" + header));
NavigateToURL(shell(), no_cache_url);
ASSERT_EQ(1U, observer.resource_load_infos().size());
EXPECT_TRUE(
observer.resource_load_infos()[0]->network_info->always_access_network);
observer.Reset();
}
}
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
ResourceLoadCompleteWithRedirects) {
ResourceLoadObserver observer(shell());
ASSERT_TRUE(embedded_test_server()->Start());
GURL target_url(embedded_test_server()->GetURL("/title1.html"));
GURL intermediate_url(
embedded_test_server()->GetURL("/server-redirect?" + target_url.spec()));
GURL start_url(embedded_test_server()->GetURL("/server-redirect?" +
intermediate_url.spec()));
NavigateToURL(shell(), start_url);
ASSERT_EQ(1U, observer.resource_load_infos().size());
EXPECT_EQ(target_url, observer.resource_load_infos()[0]->url);
ASSERT_EQ(2U, observer.resource_load_infos()[0]->redirect_info_chain.size());
EXPECT_EQ(intermediate_url,
observer.resource_load_infos()[0]->redirect_info_chain[0]->url);
EXPECT_TRUE(observer.resource_load_infos()[0]
->redirect_info_chain[0]
->network_info->network_accessed);
EXPECT_FALSE(observer.resource_load_infos()[0]
->redirect_info_chain[0]
->network_info->always_access_network);
EXPECT_EQ(target_url,
observer.resource_load_infos()[0]->redirect_info_chain[1]->url);
EXPECT_TRUE(observer.resource_load_infos()[0]
->redirect_info_chain[1]
->network_info->network_accessed);
EXPECT_FALSE(observer.resource_load_infos()[0]
->redirect_info_chain[1]
->network_info->always_access_network);
}
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
ResourceLoadCompleteIsMainFrame) {
ResourceLoadObserver observer(shell());
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL("/page_with_image.html"));
NavigateToURL(shell(), url);
ASSERT_EQ(2U, observer.resource_load_infos().size());
EXPECT_EQ(url, observer.resource_load_infos()[0]->url);
EXPECT_TRUE(observer.resource_is_associated_with_main_frame()[0]);
EXPECT_TRUE(observer.resource_is_associated_with_main_frame()[1]);
observer.Reset();
// Load that same page inside an iframe.
GURL data_url("data:text/html,<iframe src='" + url.spec() + "'></iframe>");
NavigateToURL(shell(), data_url);
ASSERT_EQ(3U, observer.resource_load_infos().size());
EXPECT_EQ(data_url, observer.resource_load_infos()[0]->url);
EXPECT_EQ(url, observer.resource_load_infos()[1]->url);
EXPECT_TRUE(observer.resource_is_associated_with_main_frame()[0]);
EXPECT_FALSE(observer.resource_is_associated_with_main_frame()[1]);
EXPECT_FALSE(observer.resource_is_associated_with_main_frame()[2]);
}
struct LoadProgressDelegateAndObserver : public WebContentsDelegate,
public WebContentsObserver {
explicit LoadProgressDelegateAndObserver(Shell* shell)
: WebContentsObserver(shell->web_contents()),
did_start_loading(false),
did_stop_loading(false) {
web_contents()->SetDelegate(this);
}
// WebContentsDelegate:
void LoadProgressChanged(WebContents* source, double progress) override {
EXPECT_TRUE(did_start_loading);
EXPECT_FALSE(did_stop_loading);
progresses.push_back(progress);
}
// WebContentsObserver:
void DidStartLoading() override {
EXPECT_FALSE(did_start_loading);
EXPECT_EQ(0U, progresses.size());
EXPECT_FALSE(did_stop_loading);
did_start_loading = true;
}
void DidStopLoading() override {
EXPECT_TRUE(did_start_loading);
EXPECT_GE(progresses.size(), 1U);
EXPECT_FALSE(did_stop_loading);
did_stop_loading = true;
}
bool did_start_loading;
std::vector<double> progresses;
bool did_stop_loading;
};
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, LoadProgress) {
ASSERT_TRUE(embedded_test_server()->Start());
std::unique_ptr<LoadProgressDelegateAndObserver> delegate(
new LoadProgressDelegateAndObserver(shell()));
NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
const std::vector<double>& progresses = delegate->progresses;
// All updates should be in order ...
if (std::adjacent_find(progresses.begin(),
progresses.end(),
std::greater<double>()) != progresses.end()) {
ADD_FAILURE() << "Progress values should be in order: "
<< ::testing::PrintToString(progresses);
}
// ... and the last one should be 1.0, meaning complete.
ASSERT_GE(progresses.size(), 1U)
<< "There should be at least one progress update";
EXPECT_EQ(1.0, *progresses.rbegin());
}
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, LoadProgressWithFrames) {
ASSERT_TRUE(embedded_test_server()->Start());
std::unique_ptr<LoadProgressDelegateAndObserver> delegate(
new LoadProgressDelegateAndObserver(shell()));
NavigateToURL(shell(),
embedded_test_server()->GetURL("/frame_tree/top.html"));
const std::vector<double>& progresses = delegate->progresses;
// All updates should be in order ...
if (std::adjacent_find(progresses.begin(),
progresses.end(),
std::greater<double>()) != progresses.end()) {
ADD_FAILURE() << "Progress values should be in order: "
<< ::testing::PrintToString(progresses);
}
// ... and the last one should be 1.0, meaning complete.
ASSERT_GE(progresses.size(), 1U)
<< "There should be at least one progress update";
EXPECT_EQ(1.0, *progresses.rbegin());
}
// Ensure that a new navigation that interrupts a pending one will still fire
// a DidStopLoading. See http://crbug.com/429399.
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
LoadProgressAfterInterruptedNav) {
ASSERT_TRUE(embedded_test_server()->Start());
// Start at a real page.
NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
// Simulate a navigation that has not completed.
const GURL kURL2 = embedded_test_server()->GetURL("/title2.html");
TestNavigationManager navigation(shell()->web_contents(), kURL2);
std::unique_ptr<LoadProgressDelegateAndObserver> delegate(
new LoadProgressDelegateAndObserver(shell()));
shell()->LoadURL(kURL2);
EXPECT_TRUE(navigation.WaitForResponse());
EXPECT_TRUE(delegate->did_start_loading);
EXPECT_FALSE(delegate->did_stop_loading);
// Also simulate a DidChangeLoadProgress, but not a DidStopLoading.
RenderFrameHostImpl* main_frame = static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetMainFrame());
FrameHostMsg_DidChangeLoadProgress progress_msg(main_frame->GetRoutingID(),
1.0);
main_frame->OnMessageReceived(progress_msg);
EXPECT_TRUE(delegate->did_start_loading);
EXPECT_FALSE(delegate->did_stop_loading);
// Now interrupt with a new cross-process navigation.
TestNavigationObserver tab_observer(shell()->web_contents(), 1);
GURL url(embedded_test_server()->GetURL("foo.com", "/title2.html"));
shell()->LoadURL(url);
tab_observer.Wait();
EXPECT_EQ(url, shell()->web_contents()->GetLastCommittedURL());
// We should have gotten to DidStopLoading.
EXPECT_TRUE(delegate->did_stop_loading);
}
struct FirstVisuallyNonEmptyPaintObserver : public WebContentsObserver {
explicit FirstVisuallyNonEmptyPaintObserver(Shell* shell)
: WebContentsObserver(shell->web_contents()),
did_fist_visually_non_empty_paint_(false) {}
void DidFirstVisuallyNonEmptyPaint() override {
did_fist_visually_non_empty_paint_ = true;
on_did_first_visually_non_empty_paint_.Run();
}
void WaitForDidFirstVisuallyNonEmptyPaint() {
if (did_fist_visually_non_empty_paint_)
return;
base::RunLoop run_loop;
on_did_first_visually_non_empty_paint_ = run_loop.QuitClosure();
run_loop.Run();
}
base::Closure on_did_first_visually_non_empty_paint_;
bool did_fist_visually_non_empty_paint_;
};
// See: http://crbug.com/395664
#if defined(OS_ANDROID)
#define MAYBE_FirstVisuallyNonEmptyPaint DISABLED_FirstVisuallyNonEmptyPaint
#else
// http://crbug.com/398471
#define MAYBE_FirstVisuallyNonEmptyPaint DISABLED_FirstVisuallyNonEmptyPaint
#endif
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
MAYBE_FirstVisuallyNonEmptyPaint) {
ASSERT_TRUE(embedded_test_server()->Start());
std::unique_ptr<FirstVisuallyNonEmptyPaintObserver> observer(
new FirstVisuallyNonEmptyPaintObserver(shell()));
NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
observer->WaitForDidFirstVisuallyNonEmptyPaint();
ASSERT_TRUE(observer->did_fist_visually_non_empty_paint_);
}
namespace {
class WebDisplayModeDelegate : public WebContentsDelegate {
public:
explicit WebDisplayModeDelegate(blink::WebDisplayMode mode) : mode_(mode) { }
~WebDisplayModeDelegate() override { }
blink::WebDisplayMode GetDisplayMode(const WebContents* source) override {
return mode_;
}
void set_mode(blink::WebDisplayMode mode) { mode_ = mode; }
private:
blink::WebDisplayMode mode_;
DISALLOW_COPY_AND_ASSIGN(WebDisplayModeDelegate);
};
} // namespace
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, ChangeDisplayMode) {
ASSERT_TRUE(embedded_test_server()->Start());
WebDisplayModeDelegate delegate(blink::kWebDisplayModeMinimalUi);
shell()->web_contents()->SetDelegate(&delegate);
NavigateToURL(shell(), GURL("about://blank"));
ASSERT_TRUE(ExecuteScript(shell(),
"document.title = "
" window.matchMedia('(display-mode:"
" minimal-ui)').matches"));
EXPECT_EQ(base::ASCIIToUTF16("true"), shell()->web_contents()->GetTitle());
delegate.set_mode(blink::kWebDisplayModeFullscreen);
// Simulate widget is entering fullscreen (changing size is enough).
shell()
->web_contents()
->GetRenderViewHost()
->GetWidget()
->SynchronizeVisualProperties();
ASSERT_TRUE(ExecuteScript(shell(),
"document.title = "
" window.matchMedia('(display-mode:"
" fullscreen)').matches"));
EXPECT_EQ(base::ASCIIToUTF16("true"), shell()->web_contents()->GetTitle());
}
// Observer class used to verify that WebContentsObservers are notified
// when the page scale factor changes.
// See WebContentsImplBrowserTest.ChangePageScale.
class MockPageScaleObserver : public WebContentsObserver {
public:
explicit MockPageScaleObserver(Shell* shell)
: WebContentsObserver(shell->web_contents()),
got_page_scale_update_(false) {
// Once OnPageScaleFactorChanged is called, quit the run loop.
ON_CALL(*this, OnPageScaleFactorChanged(::testing::_)).WillByDefault(
::testing::InvokeWithoutArgs(
this, &MockPageScaleObserver::GotPageScaleUpdate));
}
MOCK_METHOD1(OnPageScaleFactorChanged, void(float page_scale_factor));
void WaitForPageScaleUpdate() {
if (!got_page_scale_update_) {
base::RunLoop run_loop;
on_page_scale_update_ = run_loop.QuitClosure();
run_loop.Run();
}
got_page_scale_update_ = false;
}
private:
void GotPageScaleUpdate() {
got_page_scale_update_ = true;
on_page_scale_update_.Run();
}
base::Closure on_page_scale_update_;
bool got_page_scale_update_;
};
// When the page scale factor is set in the renderer it should send
// a notification to the browser so that WebContentsObservers are notified.
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, ChangePageScale) {
ASSERT_TRUE(embedded_test_server()->Start());
NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
MockPageScaleObserver observer(shell());
::testing::InSequence expect_call_sequence;
shell()->web_contents()->SetPageScale(1.5);
EXPECT_CALL(observer, OnPageScaleFactorChanged(::testing::FloatEq(1.5)));
observer.WaitForPageScaleUpdate();
// Navigate to reset the page scale factor.
shell()->LoadURL(embedded_test_server()->GetURL("/title2.html"));
EXPECT_CALL(observer, OnPageScaleFactorChanged(::testing::_));
observer.WaitForPageScaleUpdate();
}
// Test that a direct navigation to a view-source URL works.
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, ViewSourceDirectNavigation) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kUrl(embedded_test_server()->GetURL("/simple_page.html"));
const GURL kViewSourceURL(kViewSourceScheme + std::string(":") + kUrl.spec());
NavigateToURL(shell(), kViewSourceURL);
// Displayed view-source URLs don't include the scheme of the effective URL if
// the effective URL is HTTP. (e.g. view-source:example.com is displayed
// instead of view-source:http://example.com).
EXPECT_EQ(base::ASCIIToUTF16(std::string("view-source:") + kUrl.host() + ":" +
kUrl.port() + kUrl.path()),
shell()->web_contents()->GetTitle());
EXPECT_TRUE(shell()
->web_contents()
->GetController()
.GetLastCommittedEntry()
->IsViewSourceMode());
}
// Test that window.open to a view-source URL is blocked.
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
ViewSourceWindowOpen_ShouldBeBlocked) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kUrl(embedded_test_server()->GetURL("/simple_page.html"));
const GURL kViewSourceURL(kViewSourceScheme + std::string(":") + kUrl.spec());
NavigateToURL(shell(), kUrl);
auto console_delegate = std::make_unique<ConsoleObserverDelegate>(
shell()->web_contents(),
"Not allowed to load local resource: view-source:*");
shell()->web_contents()->SetDelegate(console_delegate.get());
EXPECT_TRUE(ExecuteScript(shell()->web_contents(),
"window.open('" + kViewSourceURL.spec() + "');"));
console_delegate->Wait();
// Original page shouldn't navigate away, no new tab should be opened.
EXPECT_EQ(kUrl, shell()->web_contents()->GetURL());
EXPECT_EQ(1u, Shell::windows().size());
}
// Test that a content initiated navigation to a view-source URL is blocked.
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
ViewSourceRedirect_ShouldBeBlocked) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kUrl(embedded_test_server()->GetURL("/simple_page.html"));
const GURL kViewSourceURL(kViewSourceScheme + std::string(":") + kUrl.spec());
NavigateToURL(shell(), kUrl);
std::unique_ptr<ConsoleObserverDelegate> console_delegate(
new ConsoleObserverDelegate(
shell()->web_contents(),
"Not allowed to load local resource: view-source:*"));
shell()->web_contents()->SetDelegate(console_delegate.get());
EXPECT_TRUE(
ExecuteScript(shell()->web_contents(),
"window.location = '" + kViewSourceURL.spec() + "';"));
console_delegate->Wait();
// Original page shouldn't navigate away.
EXPECT_EQ(kUrl, shell()->web_contents()->GetURL());
EXPECT_FALSE(shell()
->web_contents()
->GetController()
.GetLastCommittedEntry()
->IsViewSourceMode());
}
// Test that view source mode for a webui page can be opened.
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, ViewSourceWebUI) {
const std::string kUrl = "view-source:" + GetWebUIURLString(kChromeUIGpuHost);
const GURL kGURL(kUrl);
NavigateToURL(shell(), kGURL);
EXPECT_EQ(base::ASCIIToUTF16(kUrl), shell()->web_contents()->GetTitle());
EXPECT_TRUE(shell()
->web_contents()
->GetController()
.GetLastCommittedEntry()
->IsViewSourceMode());
}
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, NewNamedWindow) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url = embedded_test_server()->GetURL("/click-noreferrer-links.html");
EXPECT_TRUE(NavigateToURL(shell(), url));
{
ShellAddedObserver new_shell_observer;
// Open a new, named window.
EXPECT_TRUE(
ExecuteScript(shell(), "window.open('about:blank','new_window');"));
Shell* new_shell = new_shell_observer.GetShell();
WaitForLoadStop(new_shell->web_contents());
EXPECT_EQ("new_window",
static_cast<WebContentsImpl*>(new_shell->web_contents())
->GetFrameTree()->root()->frame_name());
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
new_shell,
"window.domAutomationController.send(window.name == 'new_window');",
&success));
EXPECT_TRUE(success);
}
{
ShellAddedObserver new_shell_observer;
// Test clicking a target=foo link.
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(),
"window.domAutomationController.send(clickSameSiteTargetedLink());",
&success));
EXPECT_TRUE(success);
Shell* new_shell = new_shell_observer.GetShell();
WaitForLoadStop(new_shell->web_contents());
EXPECT_EQ("foo",
static_cast<WebContentsImpl*>(new_shell->web_contents())
->GetFrameTree()->root()->frame_name());
}
}
// Test that HasOriginalOpener() tracks provenance through closed WebContentses.
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
HasOriginalOpenerTracksThroughClosedWebContents) {
const GURL blank_url = GURL("about:blank");
Shell* shell1 = shell();
EXPECT_TRUE(NavigateToURL(shell1, blank_url));
Shell* shell2 = OpenPopup(shell1, blank_url, "window2");
Shell* shell3 = OpenPopup(shell2, blank_url, "window3");
EXPECT_EQ(shell2->web_contents(),
WebContents::FromRenderFrameHost(
shell3->web_contents()->GetOriginalOpener()));
EXPECT_EQ(shell1->web_contents(),
WebContents::FromRenderFrameHost(
shell2->web_contents()->GetOriginalOpener()));
shell2->Close();
EXPECT_EQ(shell1->web_contents(),
WebContents::FromRenderFrameHost(
shell3->web_contents()->GetOriginalOpener()));
}
// TODO(clamy): Make the test work on Windows and on Mac. On Mac and Windows,
// there seem to be an issue with the ShellJavascriptDialogManager.
// Flaky on all platforms: https://crbug.com/655628
// Test that if a BeforeUnload dialog is destroyed due to the commit of a
// cross-site navigation, it will not reset the loading state.
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
DISABLED_NoResetOnBeforeUnloadCanceledOnCommit) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kStartURL(
embedded_test_server()->GetURL("/hang_before_unload.html"));
const GURL kCrossSiteURL(
embedded_test_server()->GetURL("bar.com", "/title1.html"));
// Navigate to a first web page with a BeforeUnload event listener.
EXPECT_TRUE(NavigateToURL(shell(), kStartURL));
// Start a cross-site navigation that will not commit for the moment.
TestNavigationManager cross_site_delayer(shell()->web_contents(),
kCrossSiteURL);
shell()->LoadURL(kCrossSiteURL);
EXPECT_TRUE(cross_site_delayer.WaitForRequestStart());
// Click on a link in the page. This will show the BeforeUnload dialog.
// Ensure the dialog is not dismissed, which will cause it to still be
// present when the cross-site navigation later commits.
// Note: the javascript function executed will not do the link click but
// schedule it for afterwards. Since the BeforeUnload event is synchronous,
// clicking on the link right away would cause the ExecuteScript to never
// return.
SetShouldProceedOnBeforeUnload(shell(), false, false);
EXPECT_TRUE(ExecuteScript(shell(), "clickLinkSoon()"));
WaitForAppModalDialog(shell());
// Have the cross-site navigation commit. The main RenderFrameHost should
// still be loading after that.
cross_site_delayer.WaitForNavigationFinished();
EXPECT_TRUE(shell()->web_contents()->IsLoading());
}
namespace {
void NavigateToDataURLAndCheckForTerminationDisabler(
Shell* shell,
const std::string& html,
bool expect_onunload,
bool expect_onbeforeunload) {
NavigateToURL(shell, GURL("data:text/html," + html));
RenderFrameHostImpl* rfh =
static_cast<RenderFrameHostImpl*>(shell->web_contents()->GetMainFrame());
EXPECT_EQ(expect_onunload || expect_onbeforeunload,
shell->web_contents()->NeedToFireBeforeUnload());
EXPECT_EQ(expect_onunload,
rfh->GetSuddenTerminationDisablerState(blink::kUnloadHandler));
EXPECT_EQ(expect_onbeforeunload, rfh->GetSuddenTerminationDisablerState(
blink::kBeforeUnloadHandler));
}
} // namespace
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
SuddenTerminationDisablerNone) {
const std::string NO_HANDLERS_HTML = "<html><body>foo</body></html>";
NavigateToDataURLAndCheckForTerminationDisabler(shell(), NO_HANDLERS_HTML,
false, false);
}
IN_PROC_BROWSER_TEST_F(
WebContentsImplBrowserTest,
SuddenTerminationDisablerNoneProcessTerminationDisallowed) {
const std::string NO_HANDLERS_HTML = "<html><body>foo</body></html>";
// The WebContents termination disabler should be independent of the
// RenderProcessHost termination disabler, as process termination can depend
// on more than the presence of a beforeunload/unload handler.
shell()
->web_contents()
->GetMainFrame()
->GetProcess()
->SetSuddenTerminationAllowed(false);
NavigateToDataURLAndCheckForTerminationDisabler(shell(), NO_HANDLERS_HTML,
false, false);
}
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
SuddenTerminationDisablerOnUnload) {
const std::string UNLOAD_HTML =
"<html><body><script>window.onunload=function(e) {}</script>"
"</body></html>";
NavigateToDataURLAndCheckForTerminationDisabler(shell(), UNLOAD_HTML, true,
false);
}
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
SuddenTerminationDisablerOnBeforeUnload) {
const std::string BEFORE_UNLOAD_HTML =
"<html><body><script>window.onbeforeunload=function(e) {}</script>"
"</body></html>";
NavigateToDataURLAndCheckForTerminationDisabler(shell(), BEFORE_UNLOAD_HTML,
false, true);
}
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
SuddenTerminationDisablerOnUnloadAndBeforeUnload) {
const std::string UNLOAD_AND_BEFORE_UNLOAD_HTML =
"<html><body><script>window.onunload=function(e) {};"
"window.onbeforeunload=function(e) {}</script>"
"</body></html>";
NavigateToDataURLAndCheckForTerminationDisabler(
shell(), UNLOAD_AND_BEFORE_UNLOAD_HTML, true, true);
}
class TestWCDelegateForDialogsAndFullscreen : public JavaScriptDialogManager,
public WebContentsDelegate {
public:
explicit TestWCDelegateForDialogsAndFullscreen(WebContentsImpl* web_contents)
: web_contents_(web_contents) {
old_delegate_ = web_contents_->GetDelegate();
web_contents_->SetDelegate(this);
}
~TestWCDelegateForDialogsAndFullscreen() override {
web_contents_->SetJavaScriptDialogManagerForTesting(nullptr);
web_contents_->SetDelegate(old_delegate_);
}
void WillWaitForDialog() { waiting_for_ = kDialog; }
void WillWaitForNewContents() { waiting_for_ = kNewContents; }
void WillWaitForFullscreenExit() { waiting_for_ = kFullscreenExit; }
void Wait() {
run_loop_->Run();
run_loop_ = std::make_unique<base::RunLoop>();
}
std::string last_message() { return last_message_; }
WebContents* last_popup() { return popup_.get(); }
// WebContentsDelegate
JavaScriptDialogManager* GetJavaScriptDialogManager(
WebContents* source) override {
return this;
}
void EnterFullscreenModeForTab(
WebContents* web_contents,
const GURL& origin,
const blink::WebFullscreenOptions& options) override {
is_fullscreen_ = true;
}
void ExitFullscreenModeForTab(WebContents*) override {
is_fullscreen_ = false;
if (waiting_for_ == kFullscreenExit) {
waiting_for_ = kNothing;
run_loop_->Quit();
}
}
bool IsFullscreenForTabOrPending(const WebContents* web_contents) override {
return is_fullscreen_;
}
void AddNewContents(WebContents* source,
std::unique_ptr<WebContents> new_contents,
WindowOpenDisposition disposition,
const gfx::Rect& initial_rect,
bool user_gesture,
bool* was_blocked) override {
popup_ = std::move(new_contents);
if (waiting_for_ == kNewContents) {
waiting_for_ = kNothing;
run_loop_->Quit();
}
}
// JavaScriptDialogManager
void RunJavaScriptDialog(WebContents* web_contents,
RenderFrameHost* render_frame_host,
JavaScriptDialogType dialog_type,
const base::string16& message_text,
const base::string16& default_prompt_text,
DialogClosedCallback callback,
bool* did_suppress_message) override {
last_message_ = base::UTF16ToUTF8(message_text);
*did_suppress_message = true;
if (waiting_for_ == kDialog) {
waiting_for_ = kNothing;
run_loop_->Quit();
}
}
void RunBeforeUnloadDialog(WebContents* web_contents,
RenderFrameHost* render_frame_host,
bool is_reload,
DialogClosedCallback callback) override {
std::move(callback).Run(true, base::string16());
if (waiting_for_ == kDialog) {
waiting_for_ = kNothing;
run_loop_->Quit();
}
}
bool HandleJavaScriptDialog(WebContents* web_contents,
bool accept,
const base::string16* prompt_override) override {
return true;
}
void CancelDialogs(WebContents* web_contents,
bool reset_state) override {}
private:
WebContentsImpl* web_contents_;
WebContentsDelegate* old_delegate_;
enum {
kNothing,
kDialog,
kNewContents,
kFullscreenExit
} waiting_for_ = kNothing;
std::string last_message_;
bool is_fullscreen_ = false;
std::unique_ptr<WebContents> popup_;
std::unique_ptr<base::RunLoop> run_loop_ = std::make_unique<base::RunLoop>();
DISALLOW_COPY_AND_ASSIGN(TestWCDelegateForDialogsAndFullscreen);
};
class MockFileSelectListener : public FileSelectListener {
public:
MockFileSelectListener() {}
void FileSelected(std::vector<blink::mojom::FileChooserFileInfoPtr> files,
const base::FilePath& base_dir,
blink::mojom::FileChooserParams::Mode mode) override {}
void FileSelectionCanceled() override {}
};
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
JavaScriptDialogsInMainAndSubframes) {
WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
TestWCDelegateForDialogsAndFullscreen test_delegate(wc);
ASSERT_TRUE(embedded_test_server()->Start());
NavigateToURL(shell(),
embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(WaitForLoadStop(wc));
FrameTreeNode* root = wc->GetFrameTree()->root();
ASSERT_EQ(0U, root->child_count());
std::string script =
"var iframe = document.createElement('iframe');"
"document.body.appendChild(iframe);";
EXPECT_TRUE(content::ExecuteScript(root->current_frame_host(), script));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* frame = root->child_at(0);
ASSERT_NE(nullptr, frame);
url::Replacements<char> clear_port;
clear_port.ClearPort();
// A dialog from the main frame.
std::string alert_location = "alert(document.location)";
test_delegate.WillWaitForDialog();
EXPECT_TRUE(
content::ExecuteScript(root->current_frame_host(), alert_location));
test_delegate.Wait();
EXPECT_EQ(GURL("http://a.com/title1.html"),
GURL(test_delegate.last_message()).ReplaceComponents(clear_port));
// A dialog from the subframe.
test_delegate.WillWaitForDialog();
EXPECT_TRUE(
content::ExecuteScript(frame->current_frame_host(), alert_location));
test_delegate.Wait();
EXPECT_EQ("about:blank", test_delegate.last_message());
// Navigate the subframe cross-site.
NavigateFrameToURL(frame,
embedded_test_server()->GetURL("b.com", "/title2.html"));
EXPECT_TRUE(WaitForLoadStop(wc));
// A dialog from the subframe.
test_delegate.WillWaitForDialog();
EXPECT_TRUE(
content::ExecuteScript(frame->current_frame_host(), alert_location));
test_delegate.Wait();
EXPECT_EQ(GURL("http://b.com/title2.html"),
GURL(test_delegate.last_message()).ReplaceComponents(clear_port));
// A dialog from the main frame.
test_delegate.WillWaitForDialog();
EXPECT_TRUE(
content::ExecuteScript(root->current_frame_host(), alert_location));
test_delegate.Wait();
EXPECT_EQ(GURL("http://a.com/title1.html"),
GURL(test_delegate.last_message()).ReplaceComponents(clear_port));
// Navigate the top frame cross-site; ensure that dialogs work.
NavigateToURL(shell(),
embedded_test_server()->GetURL("c.com", "/title3.html"));
EXPECT_TRUE(WaitForLoadStop(wc));
test_delegate.WillWaitForDialog();
EXPECT_TRUE(
content::ExecuteScript(root->current_frame_host(), alert_location));
test_delegate.Wait();
EXPECT_EQ(GURL("http://c.com/title3.html"),
GURL(test_delegate.last_message()).ReplaceComponents(clear_port));
// Navigate back; ensure that dialogs work.
wc->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(wc));
test_delegate.WillWaitForDialog();
EXPECT_TRUE(
content::ExecuteScript(root->current_frame_host(), alert_location));
test_delegate.Wait();
EXPECT_EQ(GURL("http://a.com/title1.html"),
GURL(test_delegate.last_message()).ReplaceComponents(clear_port));
}
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
JavaScriptDialogsNormalizeText) {
WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
TestWCDelegateForDialogsAndFullscreen test_delegate(wc);
GURL url("about:blank");
EXPECT_TRUE(NavigateToURL(shell(), url));
// A dialog with mixed linebreaks.
std::string alert = "alert('1\\r2\\r\\n3\\n4')";
test_delegate.WillWaitForDialog();
EXPECT_TRUE(content::ExecuteScript(wc, alert));
test_delegate.Wait();
EXPECT_EQ("1\n2\n3\n4", test_delegate.last_message());
}
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
CreateWebContentsWithRendererProcess) {
ASSERT_TRUE(embedded_test_server()->Start());
WebContents* base_web_contents = shell()->web_contents();
ASSERT_TRUE(base_web_contents);
WebContents::CreateParams create_params(
base_web_contents->GetBrowserContext());
create_params.desired_renderer_state =
WebContents::CreateParams::kInitializeAndWarmupRendererProcess;
std::unique_ptr<WebContents> web_contents(WebContents::Create(create_params));
ASSERT_TRUE(web_contents);
// There is no navigation (to about:blank or something like that).
EXPECT_FALSE(web_contents->IsLoading());
// The WebContents have an associated main frame and a renderer process that
// has either already launched (or is in the process of being launched).
ASSERT_TRUE(web_contents->GetMainFrame());
EXPECT_TRUE(web_contents->GetMainFrame()->IsRenderFrameLive());
EXPECT_TRUE(web_contents->GetController().IsInitialBlankNavigation());
RenderProcessHost* process = web_contents->GetMainFrame()->GetProcess();
int renderer_id = process->GetID();
ASSERT_TRUE(process);
EXPECT_TRUE(process->IsInitializedAndNotDead());
// Navigate the WebContents.
GURL url(embedded_test_server()->GetURL("c.com", "/title3.html"));
TestNavigationObserver same_tab_observer(web_contents.get());
NavigationController::LoadURLParams params(url);
params.transition_type = ui::PageTransitionFromInt(
ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
web_contents->GetController().LoadURLWithParams(params);
same_tab_observer.Wait();
EXPECT_TRUE(same_tab_observer.last_navigation_succeeded());
// Check that pre-warmed process is used.
EXPECT_EQ(process, web_contents->GetMainFrame()->GetProcess());
EXPECT_EQ(renderer_id, web_contents->GetMainFrame()->GetProcess()->GetID());
EXPECT_EQ(1, web_contents->GetController().GetEntryCount());
NavigationEntry* entry =
web_contents->GetController().GetLastCommittedEntry();
ASSERT_TRUE(entry);
EXPECT_EQ(url, entry->GetURL());
}
// Regression test for https://crbug.com/840409.
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
CreateWebContentsWithoutRendererProcess) {
ASSERT_TRUE(embedded_test_server()->Start());
WebContents* base_web_contents = shell()->web_contents();
ASSERT_TRUE(base_web_contents);
for (int i = 1; i <= 2; i++) {
SCOPED_TRACE(testing::Message() << "Iteration #" << i);
WebContents::CreateParams create_params(
base_web_contents->GetBrowserContext());
create_params.desired_renderer_state =
WebContents::CreateParams::kNoRendererProcess;
std::unique_ptr<WebContents> web_contents(
WebContents::Create(create_params));
ASSERT_TRUE(web_contents);
base::RunLoop().RunUntilIdle();
// There is no navigation (to about:blank or something like that) yet.
EXPECT_FALSE(web_contents->IsLoading());
// The WebContents have an associated main frame and a RenderProcessHost
// object, but no actual OS process has been launched yet.
ASSERT_TRUE(web_contents->GetMainFrame());
EXPECT_FALSE(web_contents->GetMainFrame()->IsRenderFrameLive());
EXPECT_TRUE(web_contents->GetController().IsInitialBlankNavigation());
RenderProcessHost* process = web_contents->GetMainFrame()->GetProcess();
int renderer_id = process->GetID();
ASSERT_TRUE(process);
EXPECT_FALSE(process->IsInitializedAndNotDead());
EXPECT_EQ(base::kNullProcessHandle, process->GetProcess().Handle());
// Navigate the WebContents.
GURL url(embedded_test_server()->GetURL("c.com", "/title3.html"));
TestNavigationObserver same_tab_observer(web_contents.get());
NavigationController::LoadURLParams params(url);
params.transition_type = ui::PageTransitionFromInt(
ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
web_contents->GetController().LoadURLWithParams(params);
same_tab_observer.Wait();
EXPECT_TRUE(same_tab_observer.last_navigation_succeeded());
// The process should be launched now.
EXPECT_TRUE(process->IsInitializedAndNotDead());
EXPECT_NE(base::kNullProcessHandle, process->GetProcess().Handle());
// Check that the RenderProcessHost and its ID didn't change.
EXPECT_EQ(process, web_contents->GetMainFrame()->GetProcess());
EXPECT_EQ(renderer_id, web_contents->GetMainFrame()->GetProcess()->GetID());
// Verify that the navigation succeeded.
EXPECT_EQ(1, web_contents->GetController().GetEntryCount());
NavigationEntry* entry =
web_contents->GetController().GetLastCommittedEntry();