|  | // Copyright 2018 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 "base/barrier_closure.h" | 
|  | #include "base/bind.h" | 
|  | #include "base/strings/utf_string_conversions.h" | 
|  | #include "build/build_config.h" | 
|  | #include "content/public/browser/content_browser_client.h" | 
|  | #include "content/public/browser/web_contents.h" | 
|  | #include "content/public/common/content_client.h" | 
|  | #include "content/public/test/browser_test.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/shell/browser/shell.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 "third_party/blink/public/mojom/renderer_preferences.mojom.h" | 
|  |  | 
|  | #if defined(OS_ANDROID) | 
|  | #include "base/system/sys_info.h" | 
|  | #endif | 
|  |  | 
|  | namespace content { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | class MockContentBrowserClient final : public ContentBrowserClient { | 
|  | public: | 
|  | void UpdateRendererPreferencesForWorker( | 
|  | BrowserContext*, | 
|  | blink::mojom::RendererPreferences* prefs) override { | 
|  | if (do_not_track_enabled_) { | 
|  | prefs->enable_do_not_track = true; | 
|  | prefs->enable_referrers = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | void EnableDoNotTrack() { do_not_track_enabled_ = true; } | 
|  |  | 
|  | private: | 
|  | bool do_not_track_enabled_ = false; | 
|  | }; | 
|  |  | 
|  | class DoNotTrackTest : public ContentBrowserTest { | 
|  | protected: | 
|  | void SetUpOnMainThread() override { | 
|  | #if defined(OS_ANDROID) | 
|  | // TODO(crbug.com/864403): It seems that we call unsupported Android APIs on | 
|  | // KitKat when we set a ContentBrowserClient. Don't call such APIs and make | 
|  | // this test available on KitKat. | 
|  | int32_t major_version = 0, minor_version = 0, bugfix_version = 0; | 
|  | base::SysInfo::OperatingSystemVersionNumbers(&major_version, &minor_version, | 
|  | &bugfix_version); | 
|  | if (major_version < 5) | 
|  | return; | 
|  | #endif | 
|  |  | 
|  | original_client_ = SetBrowserClientForTesting(&client_); | 
|  | } | 
|  | void TearDownOnMainThread() override { | 
|  | if (original_client_) | 
|  | SetBrowserClientForTesting(original_client_); | 
|  | } | 
|  |  | 
|  | // Returns false if we cannot enable do not track. It happens only when | 
|  | // Android Kitkat or older systems. | 
|  | bool EnableDoNotTrack() { | 
|  | if (!original_client_) | 
|  | return false; | 
|  | client_.EnableDoNotTrack(); | 
|  | blink::mojom::RendererPreferences* prefs = | 
|  | shell()->web_contents()->GetMutableRendererPrefs(); | 
|  | EXPECT_FALSE(prefs->enable_do_not_track); | 
|  | prefs->enable_do_not_track = true; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void ExpectPageTextEq(const std::string& expected_content) { | 
|  | std::string text; | 
|  | ASSERT_TRUE(ExecuteScriptAndExtractString( | 
|  | shell(), | 
|  | "window.domAutomationController.send(document.body.innerText);", | 
|  | &text)); | 
|  | EXPECT_EQ(expected_content, text); | 
|  | } | 
|  |  | 
|  | std::string GetDOMDoNotTrackProperty() { | 
|  | std::string value; | 
|  | EXPECT_TRUE(ExecuteScriptAndExtractString( | 
|  | shell(), | 
|  | "window.domAutomationController.send(" | 
|  | "    navigator.doNotTrack === null ? '' : navigator.doNotTrack)", | 
|  | &value)); | 
|  | return value; | 
|  | } | 
|  |  | 
|  | GURL GetURL(std::string relative_url) { | 
|  | return embedded_test_server()->GetURL(relative_url); | 
|  | } | 
|  |  | 
|  | private: | 
|  | ContentBrowserClient* original_client_ = nullptr; | 
|  | MockContentBrowserClient client_; | 
|  | }; | 
|  |  | 
|  | std::unique_ptr<net::test_server::HttpResponse> | 
|  | CaptureHeaderHandlerAndReturnScript( | 
|  | const std::string& path, | 
|  | net::test_server::HttpRequest::HeaderMap* header_map, | 
|  | const std::string& script, | 
|  | base::OnceClosure done_callback, | 
|  | const net::test_server::HttpRequest& request) { | 
|  | GURL request_url = request.GetURL(); | 
|  | if (request_url.path() != path) | 
|  | return nullptr; | 
|  |  | 
|  | *header_map = request.headers; | 
|  | std::move(done_callback).Run(); | 
|  | auto response = std::make_unique<net::test_server::BasicHttpResponse>(); | 
|  | response->set_content_type("text/javascript"); | 
|  | response->set_content(script); | 
|  | return response; | 
|  | } | 
|  |  | 
|  | // Checks that the DNT header is not sent by default. | 
|  | IN_PROC_BROWSER_TEST_F(DoNotTrackTest, NotEnabled) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | EXPECT_TRUE(NavigateToURL(shell(), GetURL("/echoheader?DNT"))); | 
|  | ExpectPageTextEq("None"); | 
|  | // And the DOM property is not set. | 
|  | EXPECT_EQ("", GetDOMDoNotTrackProperty()); | 
|  | } | 
|  |  | 
|  | // Checks that the DNT header is sent when the corresponding preference is set. | 
|  | IN_PROC_BROWSER_TEST_F(DoNotTrackTest, Simple) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | if (!EnableDoNotTrack()) | 
|  | return; | 
|  | EXPECT_TRUE(NavigateToURL(shell(), GetURL("/echoheader?DNT"))); | 
|  | ExpectPageTextEq("1"); | 
|  | } | 
|  |  | 
|  | // Checks that the DNT header is preserved during redirects. | 
|  | IN_PROC_BROWSER_TEST_F(DoNotTrackTest, Redirect) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | GURL final_url = GetURL("/echoheader?DNT"); | 
|  | GURL url = GetURL("/server-redirect?" + final_url.spec()); | 
|  | if (!EnableDoNotTrack()) | 
|  | return; | 
|  | // We don't check the result NavigateToURL as it returns true only if the | 
|  | // final URL is equal to the passed URL. | 
|  | EXPECT_TRUE(NavigateToURL(shell(), url, final_url /* expected_commit_url */)); | 
|  | ExpectPageTextEq("1"); | 
|  | } | 
|  |  | 
|  | // Checks that the DOM property is set when the corresponding preference is set. | 
|  | IN_PROC_BROWSER_TEST_F(DoNotTrackTest, DOMProperty) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | if (!EnableDoNotTrack()) | 
|  | return; | 
|  | EXPECT_TRUE(NavigateToURL(shell(), GetURL("/echo"))); | 
|  | EXPECT_EQ("1", GetDOMDoNotTrackProperty()); | 
|  | } | 
|  |  | 
|  | // Checks that the DNT header is sent in a request for a dedicated worker | 
|  | // script. | 
|  | IN_PROC_BROWSER_TEST_F(DoNotTrackTest, Worker) { | 
|  | const std::string kWorkerScript = R"(postMessage('DONE');)"; | 
|  | net::test_server::HttpRequest::HeaderMap header_map; | 
|  | base::RunLoop loop; | 
|  | embedded_test_server()->RegisterRequestHandler( | 
|  | base::BindRepeating(&CaptureHeaderHandlerAndReturnScript, "/capture", | 
|  | &header_map, kWorkerScript, loop.QuitClosure())); | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | if (!EnableDoNotTrack()) | 
|  | return; | 
|  | EXPECT_TRUE(NavigateToURL( | 
|  | shell(), GetURL("/workers/create_worker.html?worker_url=/capture"))); | 
|  | loop.Run(); | 
|  |  | 
|  | EXPECT_TRUE(header_map.find("DNT") != header_map.end()); | 
|  | EXPECT_EQ("1", header_map["DNT"]); | 
|  |  | 
|  | // Wait until the worker script is loaded to stop the test from crashing | 
|  | // during destruction. | 
|  | EXPECT_EQ("DONE", EvalJs(shell(), "waitForMessage();")); | 
|  | } | 
|  |  | 
|  | // Checks that the DNT header is sent in a request for shared worker script. | 
|  | // Disabled on Android since a shared worker is not available on Android: | 
|  | // crbug.com/869745. | 
|  | #if defined(OS_ANDROID) | 
|  | #define MAYBE_SharedWorker DISABLED_SharedWorker | 
|  | #else | 
|  | #define MAYBE_SharedWorker SharedWorker | 
|  | #endif | 
|  | IN_PROC_BROWSER_TEST_F(DoNotTrackTest, MAYBE_SharedWorker) { | 
|  | const std::string kWorkerScript = | 
|  | R"(self.onconnect = e => { e.ports[0].postMessage('DONE'); };)"; | 
|  | net::test_server::HttpRequest::HeaderMap header_map; | 
|  | base::RunLoop loop; | 
|  | embedded_test_server()->RegisterRequestHandler( | 
|  | base::BindRepeating(&CaptureHeaderHandlerAndReturnScript, "/capture", | 
|  | &header_map, kWorkerScript, loop.QuitClosure())); | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | if (!EnableDoNotTrack()) | 
|  | return; | 
|  | EXPECT_TRUE(NavigateToURL( | 
|  | shell(), | 
|  | GetURL("/workers/create_shared_worker.html?worker_url=/capture"))); | 
|  | loop.Run(); | 
|  |  | 
|  | EXPECT_TRUE(header_map.find("DNT") != header_map.end()); | 
|  | EXPECT_EQ("1", header_map["DNT"]); | 
|  |  | 
|  | // Wait until the worker script is loaded to stop the test from crashing | 
|  | // during destruction. | 
|  | EXPECT_EQ("DONE", EvalJs(shell(), "waitForMessage();")); | 
|  | } | 
|  |  | 
|  | // Checks that the DNT header is sent in a request for a service worker | 
|  | // script. | 
|  | IN_PROC_BROWSER_TEST_F(DoNotTrackTest, ServiceWorker_Register) { | 
|  | const std::string kWorkerScript = "// empty"; | 
|  | net::test_server::HttpRequest::HeaderMap header_map; | 
|  | base::RunLoop loop; | 
|  | embedded_test_server()->RegisterRequestHandler( | 
|  | base::BindRepeating(&CaptureHeaderHandlerAndReturnScript, "/capture", | 
|  | &header_map, kWorkerScript, loop.QuitClosure())); | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | if (!EnableDoNotTrack()) | 
|  | return; | 
|  | EXPECT_TRUE(NavigateToURL( | 
|  | shell(), GetURL("/service_worker/create_service_worker.html"))); | 
|  |  | 
|  | EXPECT_EQ("DONE", EvalJs(shell(), "register('/capture');")); | 
|  | loop.Run(); | 
|  |  | 
|  | EXPECT_TRUE(header_map.find("DNT") != header_map.end()); | 
|  | EXPECT_EQ("1", header_map["DNT"]); | 
|  |  | 
|  | // Service worker doesn't have to wait for onmessage event because | 
|  | // navigator.serviceWorker.ready can ensure that the script load has | 
|  | // been completed. | 
|  | } | 
|  |  | 
|  | // Checks that the DNT header is sent in a request for a service worker | 
|  | // script during update checking. | 
|  | IN_PROC_BROWSER_TEST_F(DoNotTrackTest, ServiceWorker_Update) { | 
|  | const std::string kWorkerScript = "// empty"; | 
|  | net::test_server::HttpRequest::HeaderMap header_map; | 
|  | base::RunLoop loop; | 
|  | // Wait for two requests to capture the request header for updating. | 
|  | embedded_test_server()->RegisterRequestHandler(base::BindRepeating( | 
|  | &CaptureHeaderHandlerAndReturnScript, "/capture", &header_map, | 
|  | kWorkerScript, base::BarrierClosure(2, loop.QuitClosure()))); | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | if (!EnableDoNotTrack()) | 
|  | return; | 
|  |  | 
|  | // Register a service worker, trigger update, then wait until the handler sees | 
|  | // the second request. | 
|  | EXPECT_TRUE(NavigateToURL( | 
|  | shell(), GetURL("/service_worker/create_service_worker.html"))); | 
|  | EXPECT_EQ("DONE", EvalJs(shell(), "register('/capture');")); | 
|  | EXPECT_EQ("DONE", EvalJs(shell(), "update();")); | 
|  | loop.Run(); | 
|  |  | 
|  | EXPECT_TRUE(header_map.find("DNT") != header_map.end()); | 
|  | EXPECT_EQ("1", header_map["DNT"]); | 
|  |  | 
|  | // Service worker doesn't have to wait for onmessage event because | 
|  | // waiting for a promise by registration.update() can ensure that the script | 
|  | // load has been completed. | 
|  | } | 
|  |  | 
|  | // Checks that the DNT header is preserved when fetching from a dedicated | 
|  | // worker. | 
|  | IN_PROC_BROWSER_TEST_F(DoNotTrackTest, FetchFromWorker) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | if (!EnableDoNotTrack()) | 
|  | return; | 
|  | EXPECT_TRUE( | 
|  | NavigateToURL(shell(), GetURL("/workers/fetch_from_worker.html"))); | 
|  | EXPECT_EQ("1", EvalJs(shell(), "fetch_from_worker('/echoheader?DNT');")); | 
|  | } | 
|  |  | 
|  | // Checks that the DNT header is preserved when fetching from a shared worker. | 
|  | // | 
|  | // Disabled on Android since a shared worker is not available on Android: | 
|  | // crbug.com/869745. | 
|  | #if defined(OS_ANDROID) | 
|  | #define MAYBE_FetchFromSharedWorker DISABLED_FetchFromSharedWorker | 
|  | #else | 
|  | #define MAYBE_FetchFromSharedWorker FetchFromSharedWorker | 
|  | #endif | 
|  | IN_PROC_BROWSER_TEST_F(DoNotTrackTest, MAYBE_FetchFromSharedWorker) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | if (!EnableDoNotTrack()) | 
|  | return; | 
|  | EXPECT_TRUE( | 
|  | NavigateToURL(shell(), GetURL("/workers/fetch_from_shared_worker.html"))); | 
|  |  | 
|  | EXPECT_EQ("1", | 
|  | EvalJs(shell(), "fetch_from_shared_worker('/echoheader?DNT');")); | 
|  | } | 
|  |  | 
|  | // Checks that the DNT header is preserved when fetching from a service worker. | 
|  | IN_PROC_BROWSER_TEST_F(DoNotTrackTest, FetchFromServiceWorker) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | if (!EnableDoNotTrack()) | 
|  | return; | 
|  | EXPECT_TRUE(NavigateToURL( | 
|  | shell(), GetURL("/service_worker/fetch_from_service_worker.html"))); | 
|  |  | 
|  | EXPECT_EQ("ready", EvalJs(shell(), "setup();")); | 
|  | EXPECT_EQ("1", | 
|  | EvalJs(shell(), "fetch_from_service_worker('/echoheader?DNT');")); | 
|  | } | 
|  |  | 
|  | // Checks that the DNT header is preserved when fetching from a page controlled | 
|  | // by a service worker which doesn't have a fetch handler and falls back to the | 
|  | // network. | 
|  | IN_PROC_BROWSER_TEST_F(DoNotTrackTest, | 
|  | FetchFromServiceWorkerControlledPage_NoFetchHandler) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | if (!EnableDoNotTrack()) | 
|  | return; | 
|  |  | 
|  | // Register a service worker which controls /service_worker. | 
|  | EXPECT_TRUE(NavigateToURL( | 
|  | shell(), GetURL("/service_worker/create_service_worker.html"))); | 
|  | EXPECT_EQ("DONE", EvalJs(shell(), "register('empty.js');")); | 
|  |  | 
|  | // Issue a request from a controlled page. | 
|  | EXPECT_TRUE( | 
|  | NavigateToURL(shell(), GetURL("/service_worker/fetch_from_page.html"))); | 
|  | EXPECT_EQ("1", EvalJs(shell(), "fetch_from_page('/echoheader?DNT');")); | 
|  | } | 
|  |  | 
|  | // Checks that the DNT header is preserved when fetching from a page controlled | 
|  | // by a service worker which has a fetch handler but falls back to the network. | 
|  | IN_PROC_BROWSER_TEST_F(DoNotTrackTest, | 
|  | FetchFromServiceWorkerControlledPage_PassThrough) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | if (!EnableDoNotTrack()) | 
|  | return; | 
|  |  | 
|  | // Register a service worker which controls /service_worker. | 
|  | EXPECT_TRUE(NavigateToURL( | 
|  | shell(), GetURL("/service_worker/create_service_worker.html"))); | 
|  | EXPECT_EQ("DONE", | 
|  | EvalJs(shell(), "register('fetch_event_pass_through.js');")); | 
|  |  | 
|  | // Issue a request from a controlled page. | 
|  | EXPECT_TRUE( | 
|  | NavigateToURL(shell(), GetURL("/service_worker/fetch_from_page.html"))); | 
|  | EXPECT_EQ("1", EvalJs(shell(), "fetch_from_page('/echoheader?DNT');")); | 
|  | } | 
|  |  | 
|  | // Checks that the DNT header is preserved when fetching from a page controlled | 
|  | // by a service worker which has a fetch handler and responds with fetch(). | 
|  | IN_PROC_BROWSER_TEST_F(DoNotTrackTest, | 
|  | FetchFromServiceWorkerControlledPage_RespondWithFetch) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | if (!EnableDoNotTrack()) | 
|  | return; | 
|  |  | 
|  | // Register a service worker which controls /service_worker. | 
|  | EXPECT_TRUE(NavigateToURL( | 
|  | shell(), GetURL("/service_worker/create_service_worker.html"))); | 
|  | EXPECT_EQ("DONE", | 
|  | EvalJs(shell(), "register('fetch_event_respond_with_fetch.js');")); | 
|  |  | 
|  | // Issue a request from a controlled page. | 
|  | EXPECT_TRUE( | 
|  | NavigateToURL(shell(), GetURL("/service_worker/fetch_from_page.html"))); | 
|  | EXPECT_EQ("1", EvalJs(shell(), "fetch_from_page('/echoheader?DNT');")); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | }  // namespace content |