blob: e69f948ee4765e42e3774791a34f5e045de70645 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/public/browser/resource_dispatcher_host.h"
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "content/browser/download/download_manager_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/resource_dispatcher_host_delegate.h"
#include "content/public/browser/resource_request_info.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/shell/browser/shell_content_browser_client.h"
#include "content/shell/browser/shell_network_delegate.h"
#include "net/base/net_errors.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/test/url_request/url_request_failed_job.h"
#include "net/test/url_request/url_request_mock_http_job.h"
#include "net/url_request/url_request.h"
#include "url/gurl.h"
using base::ASCIIToUTF16;
namespace content {
class ResourceDispatcherHostBrowserTest : public ContentBrowserTest,
public DownloadManager::Observer {
public:
ResourceDispatcherHostBrowserTest() : got_downloads_(false) {}
protected:
void SetUpOnMainThread() override {
base::FilePath path = GetTestFilePath("", "");
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(
&net::URLRequestMockHTTPJob::AddUrlHandlers, path,
make_scoped_refptr(content::BrowserThread::GetBlockingPool())));
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&net::URLRequestFailedJob::AddUrlHandler));
}
void OnDownloadCreated(DownloadManager* manager,
DownloadItem* item) override {
if (!got_downloads_)
got_downloads_ = !!manager->InProgressCount();
}
void CheckTitleTest(const GURL& url,
const std::string& expected_title) {
base::string16 expected_title16(ASCIIToUTF16(expected_title));
TitleWatcher title_watcher(shell()->web_contents(), expected_title16);
NavigateToURL(shell(), url);
EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle());
}
bool GetPopupTitle(const GURL& url, base::string16* title) {
NavigateToURL(shell(), url);
ShellAddedObserver new_shell_observer;
// Create dynamic popup.
if (!ExecuteScript(shell(), "OpenPopup();"))
return false;
Shell* new_shell = new_shell_observer.GetShell();
*title = new_shell->web_contents()->GetTitle();
return true;
}
std::string GetCookies(const GURL& url) {
return content::GetCookies(
shell()->web_contents()->GetBrowserContext(), url);
}
bool got_downloads() const { return got_downloads_; }
private:
bool got_downloads_;
};
// Test title for content created by javascript window.open().
// See http://crbug.com/5988
IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest, DynamicTitle1) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL("/dynamic1.html"));
base::string16 title;
ASSERT_TRUE(GetPopupTitle(url, &title));
EXPECT_TRUE(base::StartsWith(title, ASCIIToUTF16("My Popup Title"),
base::CompareCase::SENSITIVE))
<< "Actual title: " << title;
}
// Test title for content created by javascript window.open().
// See http://crbug.com/5988
IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest, DynamicTitle2) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL("/dynamic2.html"));
base::string16 title;
ASSERT_TRUE(GetPopupTitle(url, &title));
EXPECT_TRUE(base::StartsWith(title, ASCIIToUTF16("My Dynamic Title"),
base::CompareCase::SENSITIVE))
<< "Actual title: " << title;
}
IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
SniffHTMLWithNoContentType) {
CheckTitleTest(
net::URLRequestMockHTTPJob::GetMockUrl("content-sniffer-test0.html"),
"Content Sniffer Test 0");
}
IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
RespectNoSniffDirective) {
CheckTitleTest(net::URLRequestMockHTTPJob::GetMockUrl("nosniff-test.html"),
"mock.http/nosniff-test.html");
}
IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
DoNotSniffHTMLFromTextPlain) {
CheckTitleTest(
net::URLRequestMockHTTPJob::GetMockUrl("content-sniffer-test1.html"),
"mock.http/content-sniffer-test1.html");
}
IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
DoNotSniffHTMLFromImageGIF) {
CheckTitleTest(
net::URLRequestMockHTTPJob::GetMockUrl("content-sniffer-test2.html"),
"mock.http/content-sniffer-test2.html");
}
IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
SniffNoContentTypeNoData) {
// Make sure no downloads start.
BrowserContext::GetDownloadManager(
shell()->web_contents()->GetBrowserContext())->AddObserver(this);
CheckTitleTest(
net::URLRequestMockHTTPJob::GetMockUrl("content-sniffer-test3.html"),
"Content Sniffer Test 3");
EXPECT_EQ(1u, Shell::windows().size());
ASSERT_FALSE(got_downloads());
}
IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
ContentDispositionEmpty) {
CheckTitleTest(
net::URLRequestMockHTTPJob::GetMockUrl("content-disposition-empty.html"),
"success");
}
IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
ContentDispositionInline) {
CheckTitleTest(
net::URLRequestMockHTTPJob::GetMockUrl("content-disposition-inline.html"),
"success");
}
// Test for bug #1091358.
IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest, SyncXMLHttpRequest) {
ASSERT_TRUE(embedded_test_server()->Start());
NavigateToURL(
shell(), embedded_test_server()->GetURL("/sync_xmlhttprequest.html"));
// Let's check the XMLHttpRequest ran successfully.
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(), "window.domAutomationController.send(DidSyncRequestSucceed());",
&success));
EXPECT_TRUE(success);
}
// If this flakes, use http://crbug.com/62776.
IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
SyncXMLHttpRequest_Disallowed) {
ASSERT_TRUE(embedded_test_server()->Start());
NavigateToURL(
shell(),
embedded_test_server()->GetURL("/sync_xmlhttprequest_disallowed.html"));
// Let's check the XMLHttpRequest ran successfully.
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(), "window.domAutomationController.send(DidSucceed());", &success));
EXPECT_TRUE(success);
}
// Test for bug #1159553 -- A synchronous xhr (whose content-type is
// downloadable) would trigger download and hang the renderer process,
// if executed while navigating to a new page.
// Disabled on Mac: see http://crbug.com/56264
#if defined(OS_MACOSX)
#define MAYBE_SyncXMLHttpRequest_DuringUnload \
DISABLED_SyncXMLHttpRequest_DuringUnload
#else
#define MAYBE_SyncXMLHttpRequest_DuringUnload SyncXMLHttpRequest_DuringUnload
#endif
IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
MAYBE_SyncXMLHttpRequest_DuringUnload) {
ASSERT_TRUE(embedded_test_server()->Start());
BrowserContext::GetDownloadManager(
shell()->web_contents()->GetBrowserContext())->AddObserver(this);
CheckTitleTest(
embedded_test_server()->GetURL("/sync_xmlhttprequest_during_unload.html"),
"sync xhr on unload");
// Navigate to a new page, to dispatch unload event and trigger xhr.
// (the bug would make this step hang the renderer).
CheckTitleTest(
embedded_test_server()->GetURL("/title2.html"), "Title Of Awesomeness");
ASSERT_FALSE(got_downloads());
}
// Flaky everywhere. http://crbug.com/130404
// Tests that onunload is run for cross-site requests. (Bug 1114994)
IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
DISABLED_CrossSiteOnunloadCookie) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url = embedded_test_server()->GetURL("/onunload_cookie.html");
CheckTitleTest(url, "set cookie on unload");
// Navigate to a new cross-site page, to dispatch unload event and set the
// cookie.
CheckTitleTest(
net::URLRequestMockHTTPJob::GetMockUrl("content-sniffer-test0.html"),
"Content Sniffer Test 0");
// Check that the cookie was set.
EXPECT_EQ("onunloadCookie=foo", GetCookies(url));
}
// If this flakes, use http://crbug.com/130404
// Tests that onunload is run for cross-site requests to URLs that complete
// without network loads (e.g., about:blank, data URLs).
IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
DISABLED_CrossSiteImmediateLoadOnunloadCookie) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url = embedded_test_server()->GetURL("/onunload_cookie.html");
CheckTitleTest(url, "set cookie on unload");
// Navigate to a cross-site page that loads immediately without making a
// network request. The unload event should still be run.
NavigateToURL(shell(), GURL(url::kAboutBlankURL));
// Check that the cookie was set.
EXPECT_EQ("onunloadCookie=foo", GetCookies(url));
}
namespace {
// Handles |request| by serving a redirect response.
std::unique_ptr<net::test_server::HttpResponse> NoContentResponseHandler(
const std::string& path,
const net::test_server::HttpRequest& request) {
if (!base::StartsWith(path, request.relative_url,
base::CompareCase::SENSITIVE))
return std::unique_ptr<net::test_server::HttpResponse>();
std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
new net::test_server::BasicHttpResponse);
http_response->set_code(net::HTTP_NO_CONTENT);
return std::move(http_response);
}
} // namespace
// Tests that the unload handler is not run for 204 responses.
// If this flakes use http://crbug.com/80596.
IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
CrossSiteNoUnloadOn204) {
ASSERT_TRUE(embedded_test_server()->Start());
// Start with a URL that sets a cookie in its unload handler.
GURL url = embedded_test_server()->GetURL("/onunload_cookie.html");
CheckTitleTest(url, "set cookie on unload");
// Navigate to a cross-site URL that returns a 204 No Content response.
const char kNoContentPath[] = "/nocontent";
embedded_test_server()->RegisterRequestHandler(
base::Bind(&NoContentResponseHandler, kNoContentPath));
NavigateToURL(shell(), embedded_test_server()->GetURL(kNoContentPath));
// Check that the unload cookie was not set.
EXPECT_EQ("", GetCookies(url));
}
// Tests that the onbeforeunload and onunload logic is short-circuited if the
// old renderer is gone. In that case, we don't want to wait for the old
// renderer to run the handlers.
// We need to disable this on Mac because the crash causes the OS CrashReporter
// process to kick in to analyze the poor dead renderer. Unfortunately, if the
// app isn't stripped of debug symbols, this takes about five minutes to
// complete and isn't conducive to quick turnarounds. As we don't currently
// strip the app on the build bots, this is bad times.
#if defined(OS_MACOSX)
#define MAYBE_CrossSiteAfterCrash DISABLED_CrossSiteAfterCrash
#else
#define MAYBE_CrossSiteAfterCrash CrossSiteAfterCrash
#endif
IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
MAYBE_CrossSiteAfterCrash) {
// Make sure we have a live process before trying to kill it.
NavigateToURL(shell(), GURL("about:blank"));
// Cause the renderer to crash.
RenderProcessHostWatcher crash_observer(
shell()->web_contents(),
RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
NavigateToURL(shell(), GURL(kChromeUICrashURL));
// Wait for browser to notice the renderer crash.
crash_observer.Wait();
// Navigate to a new cross-site page. The browser should not wait around for
// the old renderer's on{before}unload handlers to run.
CheckTitleTest(
net::URLRequestMockHTTPJob::GetMockUrl("content-sniffer-test0.html"),
"Content Sniffer Test 0");
}
// Tests that cross-site navigations work when the new page does not go through
// the BufferedEventHandler (e.g., non-http{s} URLs). (Bug 1225872)
IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
CrossSiteNavigationNonBuffered) {
// Start with an HTTP page.
CheckTitleTest(
net::URLRequestMockHTTPJob::GetMockUrl("content-sniffer-test0.html"),
"Content Sniffer Test 0");
// Now load a file:// page, which does not use the BufferedEventHandler.
// Make sure that the page loads and displays a title, and doesn't get stuck.
GURL url = GetTestUrl("", "title2.html");
CheckTitleTest(url, "Title Of Awesomeness");
}
// Flaky everywhere. http://crbug.com/130404
// Tests that a cross-site navigation to an error page (resulting in the link
// doctor page) still runs the onunload handler and can support navigations
// away from the link doctor page. (Bug 1235537)
IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
DISABLED_CrossSiteNavigationErrorPage) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL("/onunload_cookie.html"));
CheckTitleTest(url, "set cookie on unload");
// Navigate to a new cross-site URL that results in an error.
// TODO(creis): If this causes crashes or hangs, it might be for the same
// reason as ErrorPageTest::DNSError. See bug 1199491 and
// http://crbug.com/22877.
GURL failed_url = net::URLRequestFailedJob::GetMockHttpUrl(
net::ERR_NAME_NOT_RESOLVED);
NavigateToURL(shell(), failed_url);
EXPECT_NE(ASCIIToUTF16("set cookie on unload"),
shell()->web_contents()->GetTitle());
// Check that the cookie was set, meaning that the onunload handler ran.
EXPECT_EQ("onunloadCookie=foo", GetCookies(url));
// Check that renderer-initiated navigations still work. In a previous bug,
// the ResourceDispatcherHost would think that such navigations were
// cross-site, because we didn't clean up from the previous request. Since
// WebContentsImpl was in the NORMAL state, it would ignore the attempt to run
// the onunload handler, and the navigation would fail. We can't test by
// redirecting to javascript:window.location='someURL', since javascript:
// URLs are prohibited by policy from interacting with sensitive chrome
// pages of which the error page is one. Instead, use automation to kick
// off the navigation, and wait to see that the tab loads.
base::string16 expected_title16(ASCIIToUTF16("Title Of Awesomeness"));
TitleWatcher title_watcher(shell()->web_contents(), expected_title16);
bool success;
GURL test_url(embedded_test_server()->GetURL("/title2.html"));
std::string redirect_script = "window.location='" +
test_url.possibly_invalid_spec() + "';" +
"window.domAutomationController.send(true);";
EXPECT_TRUE(ExecuteScriptAndExtractBool(shell(), redirect_script, &success));
EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle());
}
IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
CrossSiteNavigationErrorPage2) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL("/title2.html"));
CheckTitleTest(url, "Title Of Awesomeness");
// Navigate to a new cross-site URL that results in an error.
// TODO(creis): If this causes crashes or hangs, it might be for the same
// reason as ErrorPageTest::DNSError. See bug 1199491 and
// http://crbug.com/22877.
GURL failed_url = net::URLRequestFailedJob::GetMockHttpUrl(
net::ERR_NAME_NOT_RESOLVED);
NavigateToURL(shell(), failed_url);
EXPECT_NE(ASCIIToUTF16("Title Of Awesomeness"),
shell()->web_contents()->GetTitle());
// Repeat navigation. We are testing that this completes.
NavigateToURL(shell(), failed_url);
EXPECT_NE(ASCIIToUTF16("Title Of Awesomeness"),
shell()->web_contents()->GetTitle());
}
IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
CrossOriginRedirectBlocked) {
// We expect the following URL requests from this test:
// 1- http://mock.http/cross-origin-redirect-blocked.html
// 2- http://mock.http/redirect-to-title2.html
// 3- http://mock.http/title2.html
//
// If the redirect in #2 were not blocked, we'd also see a request
// for http://mock.http:4000/title2.html, and the title would be different.
CheckTitleTest(net::URLRequestMockHTTPJob::GetMockUrl(
"cross-origin-redirect-blocked.html"),
"Title Of More Awesomeness");
}
// Tests that ResourceRequestInfoImpl is updated correctly on failed
// requests, to prevent calling Read on a request that has already failed.
// See bug 40250.
IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
CrossSiteFailedRequest) {
// Visit another URL first to trigger a cross-site navigation.
NavigateToURL(shell(), GetTestUrl("", "simple_page.html"));
// Visit a URL that fails without calling ResourceDispatcherHost::Read.
GURL broken_url("chrome://theme");
NavigateToURL(shell(), broken_url);
}
namespace {
std::unique_ptr<net::test_server::HttpResponse> HandleRedirectRequest(
const std::string& request_path,
const net::test_server::HttpRequest& request) {
if (!base::StartsWith(request.relative_url, request_path,
base::CompareCase::SENSITIVE))
return std::unique_ptr<net::test_server::HttpResponse>();
std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
new net::test_server::BasicHttpResponse);
http_response->set_code(net::HTTP_FOUND);
http_response->AddCustomHeader(
"Location", request.relative_url.substr(request_path.length()));
return std::move(http_response);
}
} // namespace
// Test that we update the cookie policy URLs correctly when transferring
// navigations.
IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest, CookiePolicy) {
ASSERT_TRUE(embedded_test_server()->Start());
embedded_test_server()->RegisterRequestHandler(
base::Bind(&HandleRedirectRequest, "/redirect?"));
std::string set_cookie_url(base::StringPrintf(
"http://localhost:%u/set_cookie.html", embedded_test_server()->port()));
GURL url(embedded_test_server()->GetURL("/redirect?" + set_cookie_url));
ShellContentBrowserClient::SetSwapProcessesForRedirect(true);
ShellNetworkDelegate::SetBlockThirdPartyCookies(true);
CheckTitleTest(url, "cookie set");
}
class PageTransitionResourceDispatcherHostDelegate
: public ResourceDispatcherHostDelegate {
public:
PageTransitionResourceDispatcherHostDelegate(GURL watch_url)
: watch_url_(watch_url) {}
// ResourceDispatcherHostDelegate implementation:
void RequestBeginning(net::URLRequest* request,
ResourceContext* resource_context,
AppCacheService* appcache_service,
ResourceType resource_type,
ScopedVector<ResourceThrottle>* throttles) override {
if (request->url() == watch_url_) {
const ResourceRequestInfo* info =
ResourceRequestInfo::ForRequest(request);
page_transition_ = info->GetPageTransition();
}
}
ui::PageTransition page_transition() { return page_transition_; }
private:
GURL watch_url_;
ui::PageTransition page_transition_;
};
// Test that ui::PAGE_TRANSITION_CLIENT_REDIRECT is correctly set
// when encountering a meta refresh tag.
IN_PROC_BROWSER_TEST_F(ResourceDispatcherHostBrowserTest,
PageTransitionClientRedirect) {
ASSERT_TRUE(embedded_test_server()->Start());
PageTransitionResourceDispatcherHostDelegate delegate(
embedded_test_server()->GetURL("/title1.html"));
ResourceDispatcherHost::Get()->SetDelegate(&delegate);
NavigateToURLBlockUntilNavigationsComplete(
shell(),
embedded_test_server()->GetURL("/client_redirect.html"),
2);
EXPECT_TRUE(
delegate.page_transition() & ui::PAGE_TRANSITION_CLIENT_REDIRECT);
}
namespace {
// Checks whether the given urls are requested, and that IsUsingLofi() returns
// the appropriate value when the Lo-Fi state is set.
class LoFiModeResourceDispatcherHostDelegate
: public ResourceDispatcherHostDelegate {
public:
LoFiModeResourceDispatcherHostDelegate(const GURL& main_frame_url,
const GURL& subresource_url,
const GURL& iframe_url)
: main_frame_url_(main_frame_url),
subresource_url_(subresource_url),
iframe_url_(iframe_url),
main_frame_url_seen_(false),
subresource_url_seen_(false),
iframe_url_seen_(false),
use_lofi_(false),
should_enable_lofi_mode_called_(false) {}
~LoFiModeResourceDispatcherHostDelegate() override {}
// ResourceDispatcherHostDelegate implementation:
void RequestBeginning(net::URLRequest* request,
ResourceContext* resource_context,
AppCacheService* appcache_service,
ResourceType resource_type,
ScopedVector<ResourceThrottle>* throttles) override {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request);
if (request->url() != main_frame_url_ && request->url() != subresource_url_
&& request->url() != iframe_url_)
return;
if (request->url() == main_frame_url_) {
EXPECT_FALSE(main_frame_url_seen_);
main_frame_url_seen_ = true;
} else if (request->url() == subresource_url_) {
EXPECT_TRUE(main_frame_url_seen_);
EXPECT_FALSE(subresource_url_seen_);
subresource_url_seen_ = true;
} else if (request->url() == iframe_url_) {
EXPECT_TRUE(main_frame_url_seen_);
EXPECT_FALSE(iframe_url_seen_);
iframe_url_seen_ = true;
}
EXPECT_EQ(use_lofi_, info->IsUsingLoFi());
}
void SetDelegate() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
ResourceDispatcherHost::Get()->SetDelegate(this);
}
bool ShouldEnableLoFiMode(
const net::URLRequest& request,
content::ResourceContext* resource_context) override {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
EXPECT_FALSE(should_enable_lofi_mode_called_);
should_enable_lofi_mode_called_ = true;
EXPECT_EQ(main_frame_url_, request.url());
return use_lofi_;
}
void Reset(bool use_lofi) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
main_frame_url_seen_ = false;
subresource_url_seen_ = false;
iframe_url_seen_ = false;
use_lofi_ = use_lofi;
should_enable_lofi_mode_called_ = false;
}
void CheckResourcesRequested(bool should_enable_lofi_mode_called) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
EXPECT_EQ(should_enable_lofi_mode_called, should_enable_lofi_mode_called_);
EXPECT_TRUE(main_frame_url_seen_);
EXPECT_TRUE(subresource_url_seen_);
EXPECT_TRUE(iframe_url_seen_);
}
private:
const GURL main_frame_url_;
const GURL subresource_url_;
const GURL iframe_url_;
bool main_frame_url_seen_;
bool subresource_url_seen_;
bool iframe_url_seen_;
bool use_lofi_;
bool should_enable_lofi_mode_called_;
DISALLOW_COPY_AND_ASSIGN(LoFiModeResourceDispatcherHostDelegate);
};
} // namespace
class LoFiResourceDispatcherHostBrowserTest : public ContentBrowserTest {
public:
~LoFiResourceDispatcherHostBrowserTest() override {}
protected:
void SetUpOnMainThread() override {
ContentBrowserTest::SetUpOnMainThread();
ASSERT_TRUE(embedded_test_server()->Start());
delegate_.reset(new LoFiModeResourceDispatcherHostDelegate(
embedded_test_server()->GetURL("/page_with_iframe.html"),
embedded_test_server()->GetURL("/image.jpg"),
embedded_test_server()->GetURL("/title1.html")));
content::BrowserThread::PostTask(
content::BrowserThread::IO,
FROM_HERE,
base::Bind(&LoFiModeResourceDispatcherHostDelegate::SetDelegate,
base::Unretained(delegate_.get())));
}
void Reset(bool use_lofi) {
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&LoFiModeResourceDispatcherHostDelegate::Reset,
base::Unretained(delegate_.get()), use_lofi));
}
void CheckResourcesRequested(
bool should_enable_lofi_mode_called) {
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(
&LoFiModeResourceDispatcherHostDelegate::CheckResourcesRequested,
base::Unretained(delegate_.get()), should_enable_lofi_mode_called));
}
private:
std::unique_ptr<LoFiModeResourceDispatcherHostDelegate> delegate_;
};
// Test that navigating with ShouldEnableLoFiMode returning true fetches the
// resources with LOFI_ON.
IN_PROC_BROWSER_TEST_F(LoFiResourceDispatcherHostBrowserTest,
ShouldEnableLoFiModeOn) {
// Navigate with ShouldEnableLoFiMode returning true.
Reset(true);
NavigateToURLBlockUntilNavigationsComplete(
shell(), embedded_test_server()->GetURL("/page_with_iframe.html"), 1);
CheckResourcesRequested(true);
}
// Test that navigating with ShouldEnableLoFiMode returning false fetches the
// resources with LOFI_OFF.
IN_PROC_BROWSER_TEST_F(LoFiResourceDispatcherHostBrowserTest,
ShouldEnableLoFiModeOff) {
// Navigate with ShouldEnableLoFiMode returning false.
NavigateToURLBlockUntilNavigationsComplete(
shell(), embedded_test_server()->GetURL("/page_with_iframe.html"), 1);
CheckResourcesRequested(true);
}
// Test that reloading calls ShouldEnableLoFiMode again and changes the Lo-Fi
// state.
IN_PROC_BROWSER_TEST_F(LoFiResourceDispatcherHostBrowserTest,
ShouldEnableLoFiModeReload) {
// Navigate with ShouldEnableLoFiMode returning false.
NavigateToURLBlockUntilNavigationsComplete(
shell(), embedded_test_server()->GetURL("/page_with_iframe.html"), 1);
CheckResourcesRequested(true);
// Reload. ShouldEnableLoFiMode should be called.
Reset(true);
ReloadBlockUntilNavigationsComplete(shell(), 1);
CheckResourcesRequested(true);
}
// Test that navigating backwards calls ShouldEnableLoFiMode again and changes
// the Lo-Fi state.
IN_PROC_BROWSER_TEST_F(LoFiResourceDispatcherHostBrowserTest,
ShouldEnableLoFiModeNavigateBackThenForward) {
// Navigate with ShouldEnableLoFiMode returning false.
NavigateToURLBlockUntilNavigationsComplete(
shell(), embedded_test_server()->GetURL("/page_with_iframe.html"), 1);
CheckResourcesRequested(true);
// Go to a different page.
NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
// Go back with ShouldEnableLoFiMode returning true.
Reset(true);
TestNavigationObserver tab_observer(shell()->web_contents(), 1);
shell()->GoBackOrForward(-1);
tab_observer.Wait();
CheckResourcesRequested(true);
}
// Test that reloading with Lo-Fi disabled doesn't call ShouldEnableLoFiMode and
// already has LOFI_OFF.
IN_PROC_BROWSER_TEST_F(LoFiResourceDispatcherHostBrowserTest,
ShouldEnableLoFiModeReloadDisableLoFi) {
// Navigate with ShouldEnableLoFiMode returning true.
Reset(true);
NavigateToURLBlockUntilNavigationsComplete(
shell(), embedded_test_server()->GetURL("/page_with_iframe.html"), 1);
CheckResourcesRequested(true);
// Reload with Lo-Fi disabled.
Reset(false);
TestNavigationObserver tab_observer(shell()->web_contents(), 1);
shell()->web_contents()->GetController().ReloadDisableLoFi(true);
tab_observer.Wait();
CheckResourcesRequested(false);
}
namespace {
struct RequestDataForDelegate {
const GURL url;
const GURL first_party;
const url::Origin initiator;
RequestDataForDelegate(const GURL& url,
const GURL& first_party,
const url::Origin initiator)
: url(url), first_party(first_party), initiator(initiator) {}
};
// Captures calls to 'RequestBeginning' and records the URL, first-party for
// cookies, and initiator.
class RequestDataResourceDispatcherHostDelegate
: public ResourceDispatcherHostDelegate {
public:
RequestDataResourceDispatcherHostDelegate() {}
const ScopedVector<RequestDataForDelegate>& data() { return requests_; }
// ResourceDispatcherHostDelegate implementation:
void RequestBeginning(net::URLRequest* request,
ResourceContext* resource_context,
AppCacheService* appcache_service,
ResourceType resource_type,
ScopedVector<ResourceThrottle>* throttles) override {
requests_.push_back(new RequestDataForDelegate(
request->url(), request->first_party_for_cookies(),
request->initiator()));
}
void SetDelegate() { ResourceDispatcherHost::Get()->SetDelegate(this); }
private:
ScopedVector<RequestDataForDelegate> requests_;
DISALLOW_COPY_AND_ASSIGN(RequestDataResourceDispatcherHostDelegate);
};
const GURL kURLWithUniqueOrigin("data:,");
} // namespace
class RequestDataResourceDispatcherHostBrowserTest : public ContentBrowserTest {
public:
~RequestDataResourceDispatcherHostBrowserTest() override {}
protected:
void SetUpOnMainThread() override {
ContentBrowserTest::SetUpOnMainThread();
ASSERT_TRUE(embedded_test_server()->Start());
delegate_.reset(new RequestDataResourceDispatcherHostDelegate());
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&RequestDataResourceDispatcherHostDelegate::SetDelegate,
base::Unretained(delegate_.get())));
}
protected:
std::unique_ptr<RequestDataResourceDispatcherHostDelegate> delegate_;
};
IN_PROC_BROWSER_TEST_F(RequestDataResourceDispatcherHostBrowserTest, Basic) {
GURL top_url(embedded_test_server()->GetURL("/page_with_subresources.html"));
url::Origin top_origin(top_url);
NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1);
EXPECT_EQ(8u, delegate_->data().size());
// All resources loaded directly by the top-level document (including the
// top-level document itself) should have a |first_party| and |initiator|
// that match the URL of the top-level document.
for (auto* request : delegate_->data()) {
SCOPED_TRACE(request->url);
EXPECT_EQ(top_url, request->first_party);
EXPECT_EQ(top_origin, request->initiator);
}
}
IN_PROC_BROWSER_TEST_F(RequestDataResourceDispatcherHostBrowserTest,
BasicCrossSite) {
host_resolver()->AddRule("*", "127.0.0.1");
GURL top_url(embedded_test_server()->GetURL(
"a.com", "/nested_page_with_subresources.html"));
GURL nested_url(embedded_test_server()->GetURL(
"not-a.com", "/page_with_subresources.html"));
url::Origin top_origin(top_url);
url::Origin nested_origin(nested_url);
NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1);
EXPECT_EQ(9u, delegate_->data().size());
// The first items loaded are the top-level and nested documents. These should
// both have a |first_party| and |initiator| that match the URL of the
// top-level document:
EXPECT_EQ(top_url, delegate_->data()[0]->url);
EXPECT_EQ(top_url, delegate_->data()[0]->first_party);
EXPECT_EQ(top_origin, delegate_->data()[0]->initiator);
EXPECT_EQ(nested_url, delegate_->data()[1]->url);
EXPECT_EQ(top_url, delegate_->data()[1]->first_party);
EXPECT_EQ(top_origin, delegate_->data()[1]->initiator);
// The remaining items are loaded as subresources in the nested document, and
// should have a unique first-party, and an initiator that matches the
// document in which they're embedded.
for (size_t i = 2; i < delegate_->data().size(); i++) {
SCOPED_TRACE(delegate_->data()[i]->url);
EXPECT_EQ(kURLWithUniqueOrigin, delegate_->data()[i]->first_party);
EXPECT_EQ(nested_origin, delegate_->data()[i]->initiator);
}
}
IN_PROC_BROWSER_TEST_F(RequestDataResourceDispatcherHostBrowserTest,
SameOriginNested) {
GURL top_url(embedded_test_server()->GetURL("/page_with_iframe.html"));
GURL image_url(embedded_test_server()->GetURL("/image.jpg"));
GURL nested_url(embedded_test_server()->GetURL("/title1.html"));
url::Origin top_origin(top_url);
NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1);
EXPECT_EQ(3u, delegate_->data().size());
// User-initiated top-level navigations have a first-party and initiator that
// matches the URL to which they navigate.
EXPECT_EQ(top_url, delegate_->data()[0]->url);
EXPECT_EQ(top_url, delegate_->data()[0]->first_party);
EXPECT_EQ(top_origin, delegate_->data()[0]->initiator);
// Subresource requests have a first-party and initiator that matches the
// document in which they're embedded.
EXPECT_EQ(image_url, delegate_->data()[1]->url);
EXPECT_EQ(top_url, delegate_->data()[1]->first_party);
EXPECT_EQ(top_origin, delegate_->data()[1]->initiator);
// Same-origin nested frames have a first-party and initiator that matches
// the document in which they're embedded.
EXPECT_EQ(nested_url, delegate_->data()[2]->url);
EXPECT_EQ(top_url, delegate_->data()[2]->first_party);
EXPECT_EQ(top_origin, delegate_->data()[2]->initiator);
}
IN_PROC_BROWSER_TEST_F(RequestDataResourceDispatcherHostBrowserTest,
SameOriginAuxiliary) {
GURL top_url(embedded_test_server()->GetURL("/simple_links.html"));
GURL auxiliary_url(embedded_test_server()->GetURL("/title2.html"));
url::Origin top_origin(top_url);
NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1);
ShellAddedObserver new_shell_observer;
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(),
"window.domAutomationController.send(clickSameSiteNewWindowLink());",
&success));
EXPECT_TRUE(success);
Shell* new_shell = new_shell_observer.GetShell();
WaitForLoadStop(new_shell->web_contents());
EXPECT_EQ(2u, delegate_->data().size());
// User-initiated top-level navigations have a first-party and initiator that
// matches the URL to which they navigate, even if they fail to load.
EXPECT_EQ(top_url, delegate_->data()[0]->url);
EXPECT_EQ(top_url, delegate_->data()[0]->first_party);
EXPECT_EQ(top_origin, delegate_->data()[0]->initiator);
// Auxiliary navigations have a first-party that matches the URL to which they
// navigate, and an initiator that matches the document that triggered them.
EXPECT_EQ(auxiliary_url, delegate_->data()[1]->url);
EXPECT_EQ(auxiliary_url, delegate_->data()[1]->first_party);
EXPECT_EQ(top_origin, delegate_->data()[1]->initiator);
}
IN_PROC_BROWSER_TEST_F(RequestDataResourceDispatcherHostBrowserTest,
CrossOriginAuxiliary) {
GURL top_url(embedded_test_server()->GetURL("/simple_links.html"));
GURL auxiliary_url(embedded_test_server()->GetURL("foo.com", "/title2.html"));
url::Origin top_origin(top_url);
NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1);
const char kReplacePortNumber[] =
"window.domAutomationController.send(setPortNumber(%d));";
uint16_t port_number = embedded_test_server()->port();
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(), base::StringPrintf(kReplacePortNumber, port_number), &success));
success = false;
ShellAddedObserver new_shell_observer;
success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
shell(),
"window.domAutomationController.send(clickCrossSiteNewWindowLink());",
&success));
EXPECT_TRUE(success);
Shell* new_shell = new_shell_observer.GetShell();
WaitForLoadStop(new_shell->web_contents());
EXPECT_EQ(2u, delegate_->data().size());
// User-initiated top-level navigations have a first-party and initiator that
// matches the URL to which they navigate, even if they fail to load.
EXPECT_EQ(top_url, delegate_->data()[0]->url);
EXPECT_EQ(top_url, delegate_->data()[0]->first_party);
EXPECT_EQ(top_origin, delegate_->data()[0]->initiator);
// Auxiliary navigations have a first-party that matches the URL to which they
// navigate, and an initiator that matches the document that triggered them.
EXPECT_EQ(auxiliary_url, delegate_->data()[1]->url);
EXPECT_EQ(auxiliary_url, delegate_->data()[1]->first_party);
EXPECT_EQ(top_origin, delegate_->data()[1]->initiator);
}
IN_PROC_BROWSER_TEST_F(RequestDataResourceDispatcherHostBrowserTest,
FailedNavigation) {
// Navigating to this URL will fail, as we haven't taught the host resolver
// about 'a.com'.
GURL top_url(embedded_test_server()->GetURL("a.com", "/simple_page.html"));
url::Origin top_origin(top_url);
NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1);
EXPECT_EQ(1u, delegate_->data().size());
// User-initiated top-level navigations have a first-party and initiator that
// matches the URL to which they navigate, even if they fail to load.
EXPECT_EQ(top_url, delegate_->data()[0]->url);
EXPECT_EQ(top_url, delegate_->data()[0]->first_party);
EXPECT_EQ(top_origin, delegate_->data()[0]->initiator);
}
IN_PROC_BROWSER_TEST_F(RequestDataResourceDispatcherHostBrowserTest,
CrossOriginNested) {
host_resolver()->AddRule("*", "127.0.0.1");
GURL top_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
GURL top_js_url(
embedded_test_server()->GetURL("a.com", "/tree_parser_util.js"));
GURL nested_url(embedded_test_server()->GetURL(
"b.com", "/cross_site_iframe_factory.html?b()"));
GURL nested_js_url(
embedded_test_server()->GetURL("b.com", "/tree_parser_util.js"));
url::Origin top_origin(top_url);
url::Origin nested_origin(nested_url);
NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1);
EXPECT_EQ(4u, delegate_->data().size());
// User-initiated top-level navigations have a first-party and initiator that
// matches the URL to which they navigate.
EXPECT_EQ(top_url, delegate_->data()[0]->url);
EXPECT_EQ(top_url, delegate_->data()[0]->first_party);
EXPECT_EQ(top_origin, delegate_->data()[0]->initiator);
EXPECT_EQ(top_js_url, delegate_->data()[1]->url);
EXPECT_EQ(top_url, delegate_->data()[1]->first_party);
EXPECT_EQ(top_origin, delegate_->data()[1]->initiator);
// Cross-origin frames have a first-party and initiator that matches the URL
// in which they're embedded.
EXPECT_EQ(nested_url, delegate_->data()[2]->url);
EXPECT_EQ(top_url, delegate_->data()[2]->first_party);
EXPECT_EQ(top_origin, delegate_->data()[2]->initiator);
// Cross-origin subresource requests have a unique first-party, and an
// initiator that matches the document in which they're embedded.
EXPECT_EQ(nested_js_url, delegate_->data()[3]->url);
EXPECT_EQ(kURLWithUniqueOrigin, delegate_->data()[3]->first_party);
EXPECT_EQ(nested_origin, delegate_->data()[3]->initiator);
}
} // namespace content