1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // This file tests that Service Workers (a Content feature) work in the Chromium
9 #include "base/command_line.h"
10 #include "base/files/scoped_temp_dir.h"
11 #include "base/numerics/safe_conversions.h"
12 #include "base/path_service.h"
13 #include "base/run_loop.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/test/metrics/histogram_tester.h"
17 #include "base/test/scoped_feature_list.h"
18 #include "base/threading/thread_restrictions.h"
19 #include "build/build_config.h"
20 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/ui/browser.h"
23 #include "chrome/browser/ui/browser_window.h"
24 #include "chrome/browser/ui/tabs/tab_strip_model.h"
25 #include "chrome/common/chrome_paths.h"
26 #include "chrome/common/chrome_switches.h"
27 #include "chrome/test/base/in_process_browser_test.h"
28 #include "chrome/test/base/test_chrome_web_ui_controller_factory.h"
29 #include "chrome/test/base/ui_test_utils.h"
30 #include "components/content_settings/browser/tab_specific_content_settings.h"
31 #include "components/content_settings/core/browser/host_content_settings_map.h"
32 #include "components/content_settings/core/common/pref_names.h"
33 #include "components/favicon/content/content_favicon_driver.h"
34 #include "components/favicon/core/favicon_driver_observer.h"
35 #include "components/nacl/common/buildflags.h"
36 #include "content/public/browser/browser_context.h"
37 #include "content/public/browser/browser_task_traits.h"
38 #include "content/public/browser/browser_thread.h"
39 #include "content/public/browser/render_frame_host.h"
40 #include "content/public/browser/service_worker_context.h"
41 #include "content/public/browser/storage_partition.h"
42 #include "content/public/browser/url_data_source.h"
43 #include "content/public/browser/web_contents.h"
44 #include "content/public/browser/web_ui_controller.h"
45 #include "content/public/test/browser_test.h"
46 #include "content/public/test/browser_test_utils.h"
47 #include "net/dns/mock_host_resolver.h"
48 #include "net/test/embedded_test_server/embedded_test_server.h"
49 #include "net/test/embedded_test_server/http_request.h"
50 #include "net/test/embedded_test_server/http_response.h"
51 #include "ppapi/shared_impl/ppapi_switches.h"
52 #include "third_party/blink/public/common/messaging/string_message_codec.h"
53 #include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"
55 namespace chrome_service_worker_browser_test {
57 const char kInstallAndWaitForActivatedPage[] =
59 "navigator.serviceWorker.register('./sw.js', {scope: './scope/'})"
60 " .then(function(reg) {"
61 " reg.addEventListener('updatefound', function() {"
62 " var worker = reg.installing;"
63 " worker.addEventListener('statechange', function() {"
64 " if (worker.state == 'activated')"
65 " document.title = 'READY';"
71 class ChromeServiceWorkerTest : public InProcessBrowserTest {
73 ChromeServiceWorkerTest() {
74 EXPECT_TRUE(service_worker_dir_.CreateUniqueTempDir());
75 EXPECT_TRUE(base::CreateDirectoryAndGetError(
76 service_worker_dir_.GetPath().Append(
77 FILE_PATH_LITERAL("scope")), nullptr));
79 ~ChromeServiceWorkerTest() override {}
81 void WriteFile(const base::FilePath::StringType& filename,
82 base::StringPiece contents) {
83 base::ScopedAllowBlockingForTesting allow_blocking;
84 EXPECT_EQ(base::checked_cast<int>(contents.size()),
85 base::WriteFile(service_worker_dir_.GetPath().Append(filename),
86 contents.data(), contents.size()));
89 void NavigateToPageAndWaitForReadyTitle(const std::string path) {
90 const base::string16 expected_title1 = base::ASCIIToUTF16("READY");
91 content::TitleWatcher title_watcher1(
92 browser()->tab_strip_model()->GetActiveWebContents(), expected_title1);
93 ui_test_utils::NavigateToURL(browser(),
94 embedded_test_server()->GetURL(path));
95 EXPECT_EQ(expected_title1, title_watcher1.WaitAndGetTitle());
98 void InitializeServer() {
99 embedded_test_server()->ServeFilesFromDirectory(
100 service_worker_dir_.GetPath());
101 ASSERT_TRUE(embedded_test_server()->Start());
104 content::ServiceWorkerContext* GetServiceWorkerContext() {
105 return content::BrowserContext::GetDefaultStoragePartition(
106 browser()->profile())
107 ->GetServiceWorkerContext();
110 base::ScopedTempDir service_worker_dir_;
113 DISALLOW_COPY_AND_ASSIGN(ChromeServiceWorkerTest);
116 template <typename T>
117 static void ExpectResultAndRun(T expected,
118 const base::Closure& continuation,
120 EXPECT_EQ(expected, actual);
124 // http://crbug.com/368570
125 IN_PROC_BROWSER_TEST_F(ChromeServiceWorkerTest,
126 CanShutDownWithRegisteredServiceWorker) {
127 WriteFile(FILE_PATH_LITERAL("service_worker.js"), "");
129 embedded_test_server()->ServeFilesFromDirectory(
130 service_worker_dir_.GetPath());
131 ASSERT_TRUE(embedded_test_server()->Start());
133 base::RunLoop run_loop;
134 blink::mojom::ServiceWorkerRegistrationOptions options(
135 embedded_test_server()->GetURL("/"), blink::mojom::ScriptType::kClassic,
136 blink::mojom::ServiceWorkerUpdateViaCache::kImports);
137 GetServiceWorkerContext()->RegisterServiceWorker(
138 embedded_test_server()->GetURL("/service_worker.js"), options,
139 base::BindOnce(&ExpectResultAndRun<bool>, true, run_loop.QuitClosure()));
142 // Leave the Service Worker registered, and make sure that the browser can
143 // shut down without DCHECK'ing. It'd be nice to check here that the SW is
144 // actually occupying a process, but we don't yet have the public interface to
148 // http://crbug.com/419290
149 IN_PROC_BROWSER_TEST_F(ChromeServiceWorkerTest,
150 CanCloseIncognitoWindowWithServiceWorkerController) {
151 WriteFile(FILE_PATH_LITERAL("service_worker.js"), "");
152 WriteFile(FILE_PATH_LITERAL("service_worker.js.mock-http-headers"),
153 "HTTP/1.1 200 OK\nContent-Type: text/javascript");
154 WriteFile(FILE_PATH_LITERAL("test.html"), "");
157 Browser* incognito = CreateIncognitoBrowser();
159 base::RunLoop run_loop;
160 blink::mojom::ServiceWorkerRegistrationOptions options(
161 embedded_test_server()->GetURL("/"), blink::mojom::ScriptType::kClassic,
162 blink::mojom::ServiceWorkerUpdateViaCache::kImports);
163 GetServiceWorkerContext()->RegisterServiceWorker(
164 embedded_test_server()->GetURL("/service_worker.js"), options,
165 base::BindOnce(&ExpectResultAndRun<bool>, true, run_loop.QuitClosure()));
168 ui_test_utils::NavigateToURL(incognito,
169 embedded_test_server()->GetURL("/test.html"));
171 CloseBrowserSynchronously(incognito);
173 // Test passes if we don't crash.
176 IN_PROC_BROWSER_TEST_F(ChromeServiceWorkerTest,
177 FailRegisterServiceWorkerWhenJSDisabled) {
178 WriteFile(FILE_PATH_LITERAL("service_worker.js"), "");
181 HostContentSettingsMapFactory::GetForProfile(browser()->profile())
182 ->SetDefaultContentSetting(ContentSettingsType::JAVASCRIPT,
183 CONTENT_SETTING_BLOCK);
185 base::RunLoop run_loop;
186 blink::mojom::ServiceWorkerRegistrationOptions options(
187 embedded_test_server()->GetURL("/"), blink::mojom::ScriptType::kClassic,
188 blink::mojom::ServiceWorkerUpdateViaCache::kImports);
189 GetServiceWorkerContext()->RegisterServiceWorker(
190 embedded_test_server()->GetURL("/service_worker.js"), options,
191 base::BindOnce(&ExpectResultAndRun<bool>, false, run_loop.QuitClosure()));
195 IN_PROC_BROWSER_TEST_F(ChromeServiceWorkerTest,
196 FallbackMainResourceRequestWhenJSDisabled) {
198 FILE_PATH_LITERAL("sw.js"),
199 "self.onfetch = function(e) {"
200 " e.respondWith(new Response('<title>Fail</title>',"
201 " {headers: {'Content-Type': 'text/html'}}));"
203 WriteFile(FILE_PATH_LITERAL("scope/done.html"), "<title>Done</title>");
204 WriteFile(FILE_PATH_LITERAL("test.html"), kInstallAndWaitForActivatedPage);
206 NavigateToPageAndWaitForReadyTitle("/test.html");
208 GetServiceWorkerContext()->StopAllServiceWorkersForOrigin(
209 embedded_test_server()->base_url());
210 HostContentSettingsMapFactory::GetForProfile(browser()->profile())
211 ->SetDefaultContentSetting(ContentSettingsType::JAVASCRIPT,
212 CONTENT_SETTING_BLOCK);
214 const base::string16 expected_title2 = base::ASCIIToUTF16("Done");
215 content::TitleWatcher title_watcher2(
216 browser()->tab_strip_model()->GetActiveWebContents(), expected_title2);
217 ui_test_utils::NavigateToURL(
219 embedded_test_server()->GetURL("/scope/done.html"));
220 EXPECT_EQ(expected_title2, title_watcher2.WaitAndGetTitle());
222 content::WebContents* web_contents =
223 browser()->tab_strip_model()->GetActiveWebContents();
224 EXPECT_TRUE(content_settings::TabSpecificContentSettings::FromWebContents(
226 ->IsContentBlocked(ContentSettingsType::JAVASCRIPT));
229 IN_PROC_BROWSER_TEST_F(ChromeServiceWorkerTest,
230 StartServiceWorkerAndDispatchMessage) {
231 base::RunLoop run_loop;
232 blink::TransferableMessage msg;
233 const base::string16 message_data = base::UTF8ToUTF16("testMessage");
235 WriteFile(FILE_PATH_LITERAL("sw.js"), "self.onfetch = function(e) {};");
236 WriteFile(FILE_PATH_LITERAL("test.html"), kInstallAndWaitForActivatedPage);
238 NavigateToPageAndWaitForReadyTitle("/test.html");
239 msg.owned_encoded_message = blink::EncodeStringMessage(message_data);
240 msg.encoded_message = msg.owned_encoded_message;
242 content::GetIOThreadTaskRunner({})->PostTask(
245 &content::ServiceWorkerContext::StartServiceWorkerAndDispatchMessage,
246 base::Unretained(GetServiceWorkerContext()),
247 embedded_test_server()->GetURL("/scope/"), std::move(msg),
248 base::BindRepeating(&ExpectResultAndRun<bool>, true,
249 run_loop.QuitClosure())));
254 class ChromeServiceWorkerFetchTest : public ChromeServiceWorkerTest {
256 ChromeServiceWorkerFetchTest() {}
257 ~ChromeServiceWorkerFetchTest() override {}
259 void SetUpOnMainThread() override {
260 WriteServiceWorkerFetchTestFiles();
261 embedded_test_server()->ServeFilesFromDirectory(
262 service_worker_dir_.GetPath());
263 ASSERT_TRUE(embedded_test_server()->Start());
264 InitializeServiceWorkerFetchTestPage();
267 std::string ExecuteScriptAndExtractString(const std::string& js) {
269 EXPECT_TRUE(content::ExecuteScriptAndExtractString(
270 browser()->tab_strip_model()->GetActiveWebContents(), js, &result));
274 std::string RequestString(const std::string& url,
275 const std::string& mode,
276 const std::string& credentials) const {
277 return base::StringPrintf("url:%s, mode:%s, credentials:%s\n", url.c_str(),
278 mode.c_str(), credentials.c_str());
281 std::string GetURL(const std::string& relative_url) const {
282 return embedded_test_server()->GetURL(relative_url).spec();
286 void WriteServiceWorkerFetchTestFiles() {
287 WriteFile(FILE_PATH_LITERAL("sw.js"),
288 "this.onactivate = function(event) {"
289 " event.waitUntil(self.clients.claim());"
291 "this.onfetch = function(event) {"
292 // Ignore the default favicon request. The default favicon request
293 // is sent after the page loading is finished, and we can't
294 // control the timing of the request. If the request is sent after
295 // clients.claim() is called, fetch event for the default favicon
296 // request is triggered and the tests become flaky. See
297 // https://crbug.com/912543.
298 " if (event.request.url.endsWith('/favicon.ico')) {"
301 " event.respondWith("
302 " self.clients.matchAll().then(function(clients) {"
303 " clients.forEach(function(client) {"
304 " client.postMessage("
305 " 'url:' + event.request.url + ', ' +"
306 " 'mode:' + event.request.mode + ', ' +"
307 " 'credentials:' + event.request.credentials"
310 " return fetch(event.request);"
313 WriteFile(FILE_PATH_LITERAL("test.html"),
315 "navigator.serviceWorker.register('./sw.js', {scope: './'})"
316 " .then(function(reg) {"
317 " reg.addEventListener('updatefound', function() {"
318 " var worker = reg.installing;"
319 " worker.addEventListener('statechange', function() {"
320 " if (worker.state == 'activated')"
321 " document.title = 'READY';"
325 "var reportOnFetch = true;"
326 "var issuedRequests = [];"
327 "function reportRequests() {"
329 " issuedRequests.forEach(function(data) {"
330 " str += data + '\\n';"
332 " window.domAutomationController.send(str);"
334 "navigator.serviceWorker.addEventListener("
337 " issuedRequests.push(event.data);"
338 " if (reportOnFetch) {"
345 void InitializeServiceWorkerFetchTestPage() {
346 // The message "READY" will be sent when the service worker is activated.
347 const base::string16 expected_title = base::ASCIIToUTF16("READY");
348 content::TitleWatcher title_watcher(
349 browser()->tab_strip_model()->GetActiveWebContents(), expected_title);
350 ui_test_utils::NavigateToURL(browser(),
351 embedded_test_server()->GetURL("/test.html"));
352 EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
355 DISALLOW_COPY_AND_ASSIGN(ChromeServiceWorkerFetchTest);
358 class FaviconUpdateWaiter : public favicon::FaviconDriverObserver {
360 explicit FaviconUpdateWaiter(content::WebContents* web_contents) {
361 scoped_observer_.Add(
362 favicon::ContentFaviconDriver::FromWebContents(web_contents));
364 ~FaviconUpdateWaiter() override = default;
370 base::RunLoop run_loop;
371 quit_closure_ = run_loop.QuitClosure();
376 void OnFaviconUpdated(favicon::FaviconDriver* favicon_driver,
377 NotificationIconType notification_icon_type,
378 const GURL& icon_url,
379 bool icon_url_changed,
380 const gfx::Image& image) override {
382 if (!quit_closure_.is_null())
383 std::move(quit_closure_).Run();
386 bool updated_ = false;
387 ScopedObserver<favicon::FaviconDriver, favicon::FaviconDriverObserver>
388 scoped_observer_{this};
389 base::OnceClosure quit_closure_;
391 DISALLOW_COPY_AND_ASSIGN(FaviconUpdateWaiter);
394 class ChromeServiceWorkerLinkFetchTest : public ChromeServiceWorkerFetchTest {
396 ChromeServiceWorkerLinkFetchTest() {}
397 ~ChromeServiceWorkerLinkFetchTest() override {}
398 void SetUpOnMainThread() override {
399 // Map all hosts to localhost and setup the EmbeddedTestServer for
401 host_resolver()->AddRule("*", "127.0.0.1");
402 ChromeServiceWorkerFetchTest::SetUpOnMainThread();
404 std::string ExecuteManifestFetchTest(const std::string& url,
405 const std::string& cross_origin) {
407 base::StringPrintf("reportOnFetch = false;"
408 "var link = document.createElement('link');"
409 "link.rel = 'manifest';"
412 if (!cross_origin.empty()) {
414 base::StringPrintf("link.crossOrigin = '%s';", cross_origin.c_str());
416 js += "document.head.appendChild(link);";
417 ExecuteJavaScriptForTests(js);
418 return GetManifestAndIssuedRequests();
421 std::string ExecuteFaviconFetchTest(const std::string& url) {
422 FaviconUpdateWaiter waiter(
423 browser()->tab_strip_model()->GetActiveWebContents());
425 base::StringPrintf("reportOnFetch = false;"
426 "var link = document.createElement('link');"
429 "document.head.appendChild(link);",
431 ExecuteJavaScriptForTests(js);
433 return ExecuteScriptAndExtractString("reportRequests();");
436 void CopyTestFile(const std::string& src, const std::string& dst) {
437 base::ScopedAllowBlockingForTesting allow_blocking;
438 base::FilePath test_data_dir;
439 base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir);
440 EXPECT_TRUE(base::CopyFile(test_data_dir.AppendASCII(src),
441 service_worker_dir_.GetPath().AppendASCII(dst)));
445 void ExecuteJavaScriptForTests(const std::string& js) {
446 base::RunLoop run_loop;
449 ->GetActiveWebContents()
451 ->ExecuteJavaScriptForTests(
452 base::ASCIIToUTF16(js),
453 base::BindOnce([](const base::Closure& quit_callback,
454 base::Value result) { quit_callback.Run(); },
455 run_loop.QuitClosure()));
459 std::string GetManifestAndIssuedRequests() {
460 base::RunLoop run_loop;
461 browser()->tab_strip_model()->GetActiveWebContents()->GetManifest(
462 base::BindOnce(&ManifestCallbackAndRun, run_loop.QuitClosure()));
464 return ExecuteScriptAndExtractString(
465 "if (issuedRequests.length != 0) reportRequests();"
466 "else reportOnFetch = true;");
469 static void ManifestCallbackAndRun(const base::Closure& continuation,
471 const blink::Manifest&) {
475 DISALLOW_COPY_AND_ASSIGN(ChromeServiceWorkerLinkFetchTest);
478 IN_PROC_BROWSER_TEST_F(ChromeServiceWorkerLinkFetchTest, ManifestSameOrigin) {
479 // <link rel="manifest" href="manifest.json">
480 EXPECT_EQ(RequestString(GetURL("/manifest.json"), "cors", "omit"),
481 ExecuteManifestFetchTest("manifest.json", ""));
484 IN_PROC_BROWSER_TEST_F(ChromeServiceWorkerLinkFetchTest,
485 ManifestSameOriginUseCredentials) {
486 // <link rel="manifest" href="manifest.json" crossorigin="use-credentials">
487 EXPECT_EQ(RequestString(GetURL("/manifest.json"), "cors", "include"),
488 ExecuteManifestFetchTest("manifest.json", "use-credentials"));
491 IN_PROC_BROWSER_TEST_F(ChromeServiceWorkerLinkFetchTest, ManifestOtherOrigin) {
492 // <link rel="manifest" href="http://www.example.com:PORT/manifest.json">
493 const std::string url = embedded_test_server()
494 ->GetURL("www.example.com", "/manifest.json")
496 EXPECT_EQ(RequestString(url, "cors", "omit"),
497 ExecuteManifestFetchTest(url, ""));
500 IN_PROC_BROWSER_TEST_F(ChromeServiceWorkerLinkFetchTest,
501 ManifestOtherOriginUseCredentials) {
502 // <link rel="manifest" href="http://www.example.com:PORT/manifest.json"
503 // crossorigin="use-credentials">
504 const std::string url = embedded_test_server()
505 ->GetURL("www.example.com", "/manifest.json")
507 EXPECT_EQ(RequestString(url, "cors", "include"),
508 ExecuteManifestFetchTest(url, "use-credentials"));
511 IN_PROC_BROWSER_TEST_F(ChromeServiceWorkerLinkFetchTest, FaviconSameOrigin) {
512 // <link rel="favicon" href="fav.png">
513 CopyTestFile("favicon/icon.png", "fav.png");
514 EXPECT_EQ(RequestString(GetURL("/fav.png"), "no-cors", "include"),
515 ExecuteFaviconFetchTest("/fav.png"));
518 IN_PROC_BROWSER_TEST_F(ChromeServiceWorkerLinkFetchTest, FaviconOtherOrigin) {
519 // <link rel="favicon" href="http://www.example.com:PORT/fav.png">
520 CopyTestFile("favicon/icon.png", "fav.png");
521 const std::string url =
522 embedded_test_server()->GetURL("www.example.com", "/fav.png").spec();
523 EXPECT_EQ("", ExecuteFaviconFetchTest(url));
526 #if BUILDFLAG(ENABLE_NACL)
527 // This test registers a service worker and then loads a controlled iframe that
528 // creates a PNaCl plugin in an <embed> element. Once loaded, the PNaCl plugin
529 // is ordered to do a resource request for "/echo". The service worker records
530 // all the fetch events it sees. Since requests for plug-ins and requests
531 // initiated by plug-ins should not be interecepted by service workers, we
532 // expect that the the service worker only see the navigation request for the
534 class ChromeServiceWorkerFetchPPAPITest : public ChromeServiceWorkerFetchTest {
536 ChromeServiceWorkerFetchPPAPITest() {}
537 ~ChromeServiceWorkerFetchPPAPITest() override {}
539 void SetUpCommandLine(base::CommandLine* command_line) override {
540 ChromeServiceWorkerFetchTest::SetUpCommandLine(command_line);
541 // Use --enable-nacl flag to ensure the PNaCl module can load (without
542 // needing to use an OT token)
543 command_line->AppendSwitch(switches::kEnableNaCl);
546 void SetUpOnMainThread() override {
547 base::FilePath document_root;
548 ASSERT_TRUE(ui_test_utils::GetRelativeBuildDirectory(&document_root));
549 embedded_test_server()->AddDefaultHandlers(
550 document_root.Append(FILE_PATH_LITERAL("nacl_test_data"))
551 .Append(FILE_PATH_LITERAL("pnacl")));
552 ChromeServiceWorkerFetchTest::SetUpOnMainThread();
553 test_page_url_ = GetURL("/pnacl_url_loader.html");
556 std::string GetNavigationRequestString(const std::string& fragment) const {
557 return RequestString(test_page_url_ + fragment, "navigate", "include");
560 std::string ExecutePNACLUrlLoaderTest(const std::string& mode) {
561 std::string result(ExecuteScriptAndExtractString(
562 base::StringPrintf("reportOnFetch = false;"
563 "var iframe = document.createElement('iframe');"
564 "iframe.src='%s#%s';"
565 "document.body.appendChild(iframe);",
566 test_page_url_.c_str(), mode.c_str())));
567 EXPECT_EQ(base::StringPrintf("OnOpen%s", mode.c_str()), result);
568 return ExecuteScriptAndExtractString("reportRequests();");
572 std::string test_page_url_;
574 DISALLOW_COPY_AND_ASSIGN(ChromeServiceWorkerFetchPPAPITest);
577 IN_PROC_BROWSER_TEST_F(ChromeServiceWorkerFetchPPAPITest,
578 NotInterceptedByServiceWorker) {
579 // Only the navigation to the iframe should be intercepted by the service
580 // worker. The request for the PNaCl manifest ("/pnacl_url_loader.nmf"),
581 // the request for the compiled code ("/pnacl_url_loader_newlib_pnacl.pexe"),
582 // and any other requests initiated by the plug-in ("/echo") should not be
583 // seen by the service worker.
584 const std::string fragment =
585 "NotIntercepted"; // this string is not important.
586 EXPECT_EQ(GetNavigationRequestString("#" + fragment),
587 ExecutePNACLUrlLoaderTest(fragment));
589 #endif // BUILDFLAG(ENABLE_NACL)
591 class ChromeServiceWorkerNavigationHintTest : public ChromeServiceWorkerTest {
593 void RunNavigationHintTest(
595 content::StartServiceWorkerForNavigationHintResult expected_result,
596 bool expected_started) {
597 base::RunLoop run_loop;
598 GetServiceWorkerContext()->StartServiceWorkerForNavigationHint(
599 embedded_test_server()->GetURL(scope),
600 base::BindOnce(&ExpectResultAndRun<
601 content::StartServiceWorkerForNavigationHintResult>,
602 expected_result, run_loop.QuitClosure()));
604 if (expected_started) {
605 histogram_tester_.ExpectBucketCount(
606 "ServiceWorker.StartWorker.Purpose",
607 27 /* ServiceWorkerMetrics::EventType::NAVIGATION_HINT */, 1);
608 histogram_tester_.ExpectBucketCount(
609 "ServiceWorker.StartWorker.StatusByPurpose_NAVIGATION_HINT",
610 0 /* SERVICE_WORKER_OK */, 1);
612 histogram_tester_.ExpectTotalCount("ServiceWorker.StartWorker.Purpose",
614 histogram_tester_.ExpectTotalCount(
615 "ServiceWorker.StartWorker.StatusByPurpose_NAVIGATION_HINT", 0);
617 histogram_tester_.ExpectBucketCount(
618 "ServiceWorker.StartForNavigationHint.Result",
619 static_cast<int>(expected_result), 1);
622 base::HistogramTester histogram_tester_;
625 IN_PROC_BROWSER_TEST_F(ChromeServiceWorkerNavigationHintTest, Started) {
626 WriteFile(FILE_PATH_LITERAL("sw.js"), "self.onfetch = function(e) {};");
627 WriteFile(FILE_PATH_LITERAL("test.html"), kInstallAndWaitForActivatedPage);
629 NavigateToPageAndWaitForReadyTitle("/test.html");
630 GetServiceWorkerContext()->StopAllServiceWorkersForOrigin(
631 embedded_test_server()->base_url());
632 RunNavigationHintTest(
633 "/scope/", content::StartServiceWorkerForNavigationHintResult::STARTED,
637 IN_PROC_BROWSER_TEST_F(ChromeServiceWorkerNavigationHintTest, AlreadyRunning) {
638 WriteFile(FILE_PATH_LITERAL("sw.js"), "self.onfetch = function(e) {};");
639 WriteFile(FILE_PATH_LITERAL("test.html"), kInstallAndWaitForActivatedPage);
641 NavigateToPageAndWaitForReadyTitle("/test.html");
642 RunNavigationHintTest(
644 content::StartServiceWorkerForNavigationHintResult::ALREADY_RUNNING,
648 IN_PROC_BROWSER_TEST_F(ChromeServiceWorkerNavigationHintTest,
649 NoServiceWorkerRegistration) {
651 RunNavigationHintTest("/scope/",
652 content::StartServiceWorkerForNavigationHintResult::
653 NO_SERVICE_WORKER_REGISTRATION,
657 IN_PROC_BROWSER_TEST_F(ChromeServiceWorkerNavigationHintTest,
658 NoActiveServiceWorkerVersion) {
659 WriteFile(FILE_PATH_LITERAL("sw.js"),
660 "self.oninstall = function(e) {\n"
661 " e.waitUntil(new Promise(r => { /* never resolve */ }));\n"
663 "self.onfetch = function(e) {};");
665 base::RunLoop run_loop;
666 blink::mojom::ServiceWorkerRegistrationOptions options(
667 embedded_test_server()->GetURL("/scope/"),
668 blink::mojom::ScriptType::kClassic,
669 blink::mojom::ServiceWorkerUpdateViaCache::kImports);
670 GetServiceWorkerContext()->RegisterServiceWorker(
671 embedded_test_server()->GetURL("/sw.js"), options,
672 base::BindOnce(&ExpectResultAndRun<bool>, true, run_loop.QuitClosure()));
674 RunNavigationHintTest("/scope/",
675 content::StartServiceWorkerForNavigationHintResult::
676 NO_ACTIVE_SERVICE_WORKER_VERSION,
680 IN_PROC_BROWSER_TEST_F(ChromeServiceWorkerNavigationHintTest, NoFetchHandler) {
681 WriteFile(FILE_PATH_LITERAL("sw.js"), "/* empty */");
682 WriteFile(FILE_PATH_LITERAL("test.html"), kInstallAndWaitForActivatedPage);
684 NavigateToPageAndWaitForReadyTitle("/test.html");
685 GetServiceWorkerContext()->StopAllServiceWorkersForOrigin(
686 embedded_test_server()->base_url());
687 RunNavigationHintTest(
689 content::StartServiceWorkerForNavigationHintResult::NO_FETCH_HANDLER,
693 // Copied from devtools_sanity_browsertest.cc.
694 class StaticURLDataSource : public content::URLDataSource {
696 StaticURLDataSource(const std::string& source, const std::string& content)
697 : source_(source), content_(content) {}
698 ~StaticURLDataSource() override = default;
700 // content::URLDataSource:
701 std::string GetSource() override { return source_; }
702 void StartDataRequest(const GURL& url,
703 const content::WebContents::Getter& wc_getter,
704 GotDataCallback callback) override {
705 std::string data(content_);
706 std::move(callback).Run(base::RefCountedString::TakeString(&data));
708 std::string GetMimeType(const std::string& path) override {
709 return "application/javascript";
711 bool ShouldAddContentSecurityPolicy() override { return false; }
714 const std::string source_;
715 const std::string content_;
717 DISALLOW_COPY_AND_ASSIGN(StaticURLDataSource);
720 // Copied from devtools_sanity_browsertest.cc.
721 class MockWebUIProvider
722 : public TestChromeWebUIControllerFactory::WebUIProvider {
724 MockWebUIProvider(const std::string& source, const std::string& content)
725 : source_(source), content_(content) {}
726 ~MockWebUIProvider() override = default;
728 std::unique_ptr<content::WebUIController> NewWebUI(content::WebUI* web_ui,
729 const GURL& url) override {
730 content::URLDataSource::Add(
731 Profile::FromWebUI(web_ui),
732 std::make_unique<StaticURLDataSource>(source_, content_));
733 return std::make_unique<content::WebUIController>(web_ui);
737 const std::string source_;
738 const std::string content_;
739 DISALLOW_COPY_AND_ASSIGN(MockWebUIProvider);
742 // Tests that registering a service worker with a chrome:// URL fails.
743 IN_PROC_BROWSER_TEST_F(ChromeServiceWorkerTest, DisallowChromeScheme) {
744 const GURL kScript("chrome://dummyurl/sw.js");
745 const GURL kScope("chrome://dummyurl");
747 // Make chrome://dummyurl/sw.js serve a service worker script.
748 TestChromeWebUIControllerFactory test_factory;
749 MockWebUIProvider mock_provider("serviceworker", "// empty service worker");
750 test_factory.AddFactoryOverride(kScript.host(), &mock_provider);
751 content::WebUIControllerFactory::RegisterFactory(&test_factory);
753 // Try to register the service worker.
754 base::RunLoop run_loop;
756 blink::mojom::ServiceWorkerRegistrationOptions options(
757 kScope, blink::mojom::ScriptType::kClassic,
758 blink::mojom::ServiceWorkerUpdateViaCache::kImports);
759 GetServiceWorkerContext()->RegisterServiceWorker(
762 [](base::OnceClosure quit_closure, bool* out_result, bool result) {
763 *out_result = result;
764 std::move(quit_closure).Run();
766 run_loop.QuitClosure(), &result));
769 // Registration should fail. This is the desired behavior. At the time of this
770 // writing, there are a few reasons the registration fails:
771 // * OriginCanAccessServiceWorkers() returns false for the "chrome" scheme.
772 // * Even if that returned true, the URL loader factory bundle used to make
773 // the resource request in ServiceWorkerNewScriptLoader doesn't support
774 // the "chrome" scheme. This is because:
775 // * The call to RegisterNonNetworkSubresourceURLLoaderFactories() from
776 // CreateFactoryBundle() in embedded_worker_instance.cc doesn't register
777 // the "chrome" scheme, because there is no frame/web_contents.
778 // * Even if that registered a factory, CreateFactoryBundle() would
779 // skip it because GetServiceWorkerSchemes() doesn't include "chrome".
781 // It's difficult to change all these, so the test author hasn't actually
782 // changed Chrome in a way that makes this test fail, to prove that the test
783 // would be effective at catching a regression.
784 EXPECT_FALSE(result);
787 enum class ServicifiedFeatures { kNone, kServiceWorker, kNetwork };
789 // A simple fixture used for navigation preload tests so far. The fixture
790 // stashes the HttpRequest to a certain URL, useful for inspecting the headers
791 // to see if it was a navigation preload request and if it contained cookies.
793 // This is in //chrome instead of //content since the tests exercise the
794 // kBlockThirdPartyCookies preference which is not a //content concept.
795 class ChromeServiceWorkerNavigationPreloadTest : public InProcessBrowserTest {
797 ChromeServiceWorkerNavigationPreloadTest() = default;
799 void SetUp() override {
800 embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
801 &ChromeServiceWorkerNavigationPreloadTest::HandleRequest,
802 base::Unretained(this)));
803 ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
805 InProcessBrowserTest::SetUp();
808 void SetUpOnMainThread() override {
809 // Make all hosts resolve to 127.0.0.1 so the same embedded test server can
810 // be used for cross-origin URLs.
811 host_resolver()->AddRule("*", "127.0.0.1");
813 embedded_test_server()->StartAcceptingConnections();
816 std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
817 const net::test_server::HttpRequest& request) {
818 // Intercept requests to the "test" endpoint.
819 GURL url = request.base_url;
820 url = url.Resolve(request.relative_url);
821 if (url.path() != "/service_worker/test")
824 // Stash the request for testing. We'd typically prefer to echo back the
825 // request and test the resulting page contents, but that becomes
826 // cumbersome if the test involves cross-origin frames.
827 EXPECT_FALSE(received_request_);
828 received_request_ = request;
831 std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
832 new net::test_server::BasicHttpResponse());
833 http_response->set_code(net::HTTP_OK);
834 http_response->set_content("OK");
835 http_response->set_content_type("text/plain");
836 return http_response;
839 bool HasHeader(const net::test_server::HttpRequest& request,
840 const std::string& name) const {
841 return request.headers.find(name) != request.headers.end();
844 std::string GetHeader(const net::test_server::HttpRequest& request,
845 const std::string& name) const {
846 const auto& iter = request.headers.find(name);
847 EXPECT_TRUE(iter != request.headers.end());
848 if (iter == request.headers.end())
849 return std::string();
853 bool has_received_request() const { return received_request_.has_value(); }
855 const net::test_server::HttpRequest& received_request() const {
856 return *received_request_;
860 base::test::ScopedFeatureList scoped_feature_list_;
862 // The request that hit the "test" endpoint.
863 base::Optional<net::test_server::HttpRequest> received_request_;
865 DISALLOW_COPY_AND_ASSIGN(ChromeServiceWorkerNavigationPreloadTest);
868 // Tests navigation preload during a navigation in the top-level frame
869 // when third-party cookies are blocked. The navigation preload request
870 // should be sent with cookies as normal. Regression test for
871 // https://crbug.com/913220.
872 IN_PROC_BROWSER_TEST_F(ChromeServiceWorkerNavigationPreloadTest,
873 TopFrameWithThirdPartyBlocking) {
874 // Enable third-party cookie blocking.
875 browser()->profile()->GetPrefs()->SetBoolean(prefs::kBlockThirdPartyCookies,
878 // Load a page that registers a service worker.
879 ui_test_utils::NavigateToURL(
880 browser(), embedded_test_server()->GetURL(
881 "/service_worker/create_service_worker.html"));
882 EXPECT_EQ("DONE", EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
883 "register('navigation_preload_worker.js');"));
887 EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
888 "document.cookie = 'foo=bar'; document.cookie;"));
890 // Load the test page.
891 ui_test_utils::NavigateToURL(
892 browser(), embedded_test_server()->GetURL("/service_worker/test"));
894 // The navigation preload request should have occurred and included cookies.
895 ASSERT_TRUE(has_received_request());
897 GetHeader(received_request(), "Service-Worker-Navigation-Preload"));
898 EXPECT_EQ("foo=bar", GetHeader(received_request(), "Cookie"));
901 // Tests navigation preload during a navigation in a third-party iframe
902 // when third-party cookies are blocked. This blocks service worker as well,
903 // so the navigation preload request should not be sent. And the navigation
904 // request should not include cookies.
905 IN_PROC_BROWSER_TEST_F(ChromeServiceWorkerNavigationPreloadTest,
906 SubFrameWithThirdPartyBlocking) {
907 // Enable third-party cookie blocking.
908 browser()->profile()->GetPrefs()->SetBoolean(prefs::kBlockThirdPartyCookies,
911 // Load a page that registers a service worker.
912 ui_test_utils::NavigateToURL(
913 browser(), embedded_test_server()->GetURL(
914 "/service_worker/create_service_worker.html"));
915 EXPECT_EQ("DONE", EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
916 "register('navigation_preload_worker.js');"));
920 EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
921 "document.cookie = 'foo=bar'; document.cookie;"));
923 // Generate a cross-origin URL.
924 GURL top_frame_url = embedded_test_server()->GetURL(
925 "/service_worker/page_with_third_party_iframe.html");
926 GURL::Replacements replacements;
927 replacements.SetHostStr("cross-origin.example.com");
928 top_frame_url = top_frame_url.ReplaceComponents(replacements);
930 // Navigate to the page and embed a third-party iframe to the test
932 ui_test_utils::NavigateToURL(browser(), top_frame_url);
933 GURL iframe_url = embedded_test_server()->GetURL("/service_worker/test");
934 EXPECT_EQ(true, EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
935 "addIframe('" + iframe_url.spec() + "');"));
937 // The request should have been received. Because the navigation was for a
938 // third-party iframe with cookies blocked, the service worker should not have
939 // handled the request so navigation preload should not have occurred.
940 // Likewise, the cookies should not have been sent.
941 ASSERT_TRUE(has_received_request());
943 HasHeader(received_request(), "Service-Worker-Navigation-Preload"));
944 EXPECT_FALSE(HasHeader(received_request(), "Cookie"));
947 } // namespace chrome_service_worker_browser_test