blob: 524da3b6def4cfd3b68e2b338dbd554b547e2d2f [file] [log] [blame]
annekao38685502015-07-14 17:46:391// Copyright 2015 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.
4
kalman6f984ae2015-09-18 17:21:585#include "base/bind_helpers.h"
6#include "base/strings/stringprintf.h"
horo1eeddde2015-11-19 05:59:257#include "base/strings/utf_string_conversions.h"
annekao38685502015-07-14 17:46:398#include "chrome/browser/extensions/extension_apitest.h"
rdevlin.croninf5863da2015-09-10 19:21:459#include "chrome/browser/extensions/extension_service.h"
annekao1db36fd2015-07-29 17:09:1610#include "chrome/browser/ui/tabs/tab_strip_model.h"
rdevlin.croninf5863da2015-09-10 19:21:4511#include "chrome/test/base/ui_test_utils.h"
sdefresne9fb67692015-08-03 18:48:2212#include "components/version_info/version_info.h"
kalman6f984ae2015-09-18 17:21:5813#include "content/public/browser/navigation_controller.h"
rdevlin.croninf5863da2015-09-10 19:21:4514#include "content/public/browser/navigation_entry.h"
kalman6f984ae2015-09-18 17:21:5815#include "content/public/browser/web_contents.h"
lazyboybd325ae2015-11-18 21:35:2616#include "content/public/common/content_switches.h"
kalman6f984ae2015-09-18 17:21:5817#include "content/public/common/page_type.h"
lazyboybd325ae2015-11-18 21:35:2618#include "content/public/test/background_sync_test_util.h"
annekao1db36fd2015-07-29 17:09:1619#include "content/public/test/browser_test_utils.h"
kalman6f984ae2015-09-18 17:21:5820#include "extensions/browser/extension_host.h"
21#include "extensions/browser/process_manager.h"
22#include "extensions/test/background_page_watcher.h"
annekao38685502015-07-14 17:46:3923#include "extensions/test/extension_test_message_listener.h"
horo1eeddde2015-11-19 05:59:2524#include "net/test/embedded_test_server/embedded_test_server.h"
annekao38685502015-07-14 17:46:3925
26namespace extensions {
27
kalman6f984ae2015-09-18 17:21:5828namespace {
29
30// Pass into ServiceWorkerTest::StartTestFromBackgroundPage to indicate that
31// registration is expected to succeed.
32std::string* const kExpectSuccess = nullptr;
33
34void DoNothingWithBool(bool b) {}
35
36} // namespace
37
annekao38685502015-07-14 17:46:3938class ServiceWorkerTest : public ExtensionApiTest {
39 public:
kalman6f984ae2015-09-18 17:21:5840 ServiceWorkerTest() : current_channel_(version_info::Channel::UNKNOWN) {}
annekao38685502015-07-14 17:46:3941
42 ~ServiceWorkerTest() override {}
43
kalman6f984ae2015-09-18 17:21:5844 protected:
45 // Returns the ProcessManager for the test's profile.
46 ProcessManager* process_manager() { return ProcessManager::Get(profile()); }
47
48 // Starts running a test from the background page test extension.
49 //
50 // This registers a service worker with |script_name|, and fetches the
51 // registration result.
52 //
53 // If |error_or_null| is null (kExpectSuccess), success is expected and this
54 // will fail if there is an error.
55 // If |error_or_null| is not null, nothing is assumed, and the error (which
56 // may be empty) is written to it.
57 const Extension* StartTestFromBackgroundPage(const char* script_name,
58 std::string* error_or_null) {
59 const Extension* extension =
60 LoadExtension(test_data_dir_.AppendASCII("service_worker/background"));
61 CHECK(extension);
62 ExtensionHost* background_host =
63 process_manager()->GetBackgroundHostForExtension(extension->id());
64 CHECK(background_host);
65 std::string error;
66 CHECK(content::ExecuteScriptAndExtractString(
67 background_host->host_contents(),
68 base::StringPrintf("test.registerServiceWorker('%s')", script_name),
69 &error));
70 if (error_or_null)
71 *error_or_null = error;
72 else if (!error.empty())
73 ADD_FAILURE() << "Got unexpected error " << error;
74 return extension;
75 }
76
77 // Navigates the browser to a new tab at |url|, waits for it to load, then
78 // returns it.
79 content::WebContents* Navigate(const GURL& url) {
80 ui_test_utils::NavigateToURLWithDisposition(
81 browser(), url, NEW_FOREGROUND_TAB,
82 ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
83 content::WebContents* web_contents =
84 browser()->tab_strip_model()->GetActiveWebContents();
85 content::WaitForLoadStop(web_contents);
86 return web_contents;
87 }
88
89 // Navigates the browser to |url| and returns the new tab's page type.
90 content::PageType NavigateAndGetPageType(const GURL& url) {
91 return Navigate(url)->GetController().GetActiveEntry()->GetPageType();
92 }
93
94 // Extracts the innerText from |contents|.
95 std::string ExtractInnerText(content::WebContents* contents) {
96 std::string inner_text;
97 if (!content::ExecuteScriptAndExtractString(
98 contents,
99 "window.domAutomationController.send(document.body.innerText)",
100 &inner_text)) {
101 ADD_FAILURE() << "Failed to get inner text for "
102 << contents->GetVisibleURL();
103 }
104 return inner_text;
105 }
106
107 // Navigates the browser to |url|, then returns the innerText of the new
108 // tab's WebContents' main frame.
109 std::string NavigateAndExtractInnerText(const GURL& url) {
110 return ExtractInnerText(Navigate(url));
111 }
112
annekao38685502015-07-14 17:46:39113 private:
kalman6f984ae2015-09-18 17:21:58114 // Sets the channel to "trunk" since service workers are restricted to trunk.
annekao38685502015-07-14 17:46:39115 ScopedCurrentChannel current_channel_;
kalman6f984ae2015-09-18 17:21:58116
annekao38685502015-07-14 17:46:39117 DISALLOW_COPY_AND_ASSIGN(ServiceWorkerTest);
118};
119
lazyboybd325ae2015-11-18 21:35:26120class ServiceWorkerBackgroundSyncTest : public ServiceWorkerTest {
121 public:
122 ServiceWorkerBackgroundSyncTest() {}
123 ~ServiceWorkerBackgroundSyncTest() override {}
124
125 void SetUpCommandLine(base::CommandLine* command_line) override {
126 // ServiceWorkerRegistration.sync requires experimental flag.
127 command_line->AppendSwitch(
128 switches::kEnableExperimentalWebPlatformFeatures);
129 ServiceWorkerTest::SetUpCommandLine(command_line);
130 }
131
132 void SetUp() override {
133 content::background_sync_test_util::SetIgnoreNetworkChangeNotifier(true);
134 ServiceWorkerTest::SetUp();
135 }
136
137 private:
138 DISALLOW_COPY_AND_ASSIGN(ServiceWorkerBackgroundSyncTest);
139};
140
kalman6f984ae2015-09-18 17:21:58141IN_PROC_BROWSER_TEST_F(ServiceWorkerTest, RegisterSucceedsOnTrunk) {
142 StartTestFromBackgroundPage("register.js", kExpectSuccess);
annekao38685502015-07-14 17:46:39143}
144
145// This feature is restricted to trunk, so on dev it should have existing
146// behavior - which is for it to fail.
kalman6f984ae2015-09-18 17:21:58147IN_PROC_BROWSER_TEST_F(ServiceWorkerTest, RegisterFailsOnDev) {
annekao38685502015-07-14 17:46:39148 ScopedCurrentChannel current_channel_override(
sdefresne6e883e42015-07-30 08:05:54149 version_info::Channel::DEV);
kalman6f984ae2015-09-18 17:21:58150 std::string error;
151 const Extension* extension =
152 StartTestFromBackgroundPage("register.js", &error);
annekao1db36fd2015-07-29 17:09:16153 EXPECT_EQ(
kalman6f984ae2015-09-18 17:21:58154 "Failed to register a ServiceWorker: The URL protocol of the current "
155 "origin ('chrome-extension://" +
156 extension->id() + "') is not supported.",
157 error);
annekao38685502015-07-14 17:46:39158}
159
kalman6f984ae2015-09-18 17:21:58160IN_PROC_BROWSER_TEST_F(ServiceWorkerTest, FetchArbitraryPaths) {
161 const Extension* extension =
162 StartTestFromBackgroundPage("fetch.js", kExpectSuccess);
annekao1db36fd2015-07-29 17:09:16163
kalman6f984ae2015-09-18 17:21:58164 // Open some arbirary paths. Their contents should be what the service worker
165 // responds with, which in this case is the path of the fetch.
166 EXPECT_EQ(
167 "Caught a fetch for /index.html",
168 NavigateAndExtractInnerText(extension->GetResourceURL("index.html")));
169 EXPECT_EQ("Caught a fetch for /path/to/other.html",
170 NavigateAndExtractInnerText(
171 extension->GetResourceURL("path/to/other.html")));
172 EXPECT_EQ("Caught a fetch for /some/text/file.txt",
173 NavigateAndExtractInnerText(
174 extension->GetResourceURL("some/text/file.txt")));
175 EXPECT_EQ("Caught a fetch for /no/file/extension",
176 NavigateAndExtractInnerText(
177 extension->GetResourceURL("no/file/extension")));
178 EXPECT_EQ("Caught a fetch for /",
179 NavigateAndExtractInnerText(extension->GetResourceURL("")));
annekao1db36fd2015-07-29 17:09:16180}
181
kalman6f984ae2015-09-18 17:21:58182IN_PROC_BROWSER_TEST_F(ServiceWorkerTest,
183 LoadingBackgroundPageBypassesServiceWorker) {
184 const Extension* extension =
185 StartTestFromBackgroundPage("fetch.js", kExpectSuccess);
annekao49241182015-08-18 17:14:01186
kalman6f984ae2015-09-18 17:21:58187 std::string kExpectedInnerText = "background.html contents for testing.";
annekao49241182015-08-18 17:14:01188
kalman6f984ae2015-09-18 17:21:58189 // Sanity check that the background page has the expected content.
190 ExtensionHost* background_page =
191 process_manager()->GetBackgroundHostForExtension(extension->id());
192 ASSERT_TRUE(background_page);
193 EXPECT_EQ(kExpectedInnerText,
194 ExtractInnerText(background_page->host_contents()));
annekao49241182015-08-18 17:14:01195
kalman6f984ae2015-09-18 17:21:58196 // Close the background page.
197 background_page->Close();
198 BackgroundPageWatcher(process_manager(), extension).WaitForClose();
199 background_page = nullptr;
200
201 // Start it again.
202 process_manager()->WakeEventPage(extension->id(),
203 base::Bind(&DoNothingWithBool));
204 BackgroundPageWatcher(process_manager(), extension).WaitForOpen();
205
206 // Content should not have been affected by the fetch, which would otherwise
207 // be "Caught fetch for...".
208 background_page =
209 process_manager()->GetBackgroundHostForExtension(extension->id());
210 ASSERT_TRUE(background_page);
211 content::WaitForLoadStop(background_page->host_contents());
212
213 // TODO(kalman): Everything you've read has been a LIE! It should be:
214 //
215 // EXPECT_EQ(kExpectedInnerText,
216 // ExtractInnerText(background_page->host_contents()));
217 //
218 // but there is a bug, and we're actually *not* bypassing the service worker
219 // for background page loads! For now, let it pass (assert wrong behavior)
220 // because it's not a regression, but this must be fixed eventually.
221 //
222 // Tracked in crbug.com/532720.
223 EXPECT_EQ("Caught a fetch for /background.html",
224 ExtractInnerText(background_page->host_contents()));
annekao49241182015-08-18 17:14:01225}
226
kalman6f984ae2015-09-18 17:21:58227IN_PROC_BROWSER_TEST_F(ServiceWorkerTest,
228 ServiceWorkerPostsMessageToBackgroundClient) {
229 const Extension* extension = StartTestFromBackgroundPage(
230 "post_message_to_background_client.js", kExpectSuccess);
annekao533482222015-08-21 23:23:53231
kalman6f984ae2015-09-18 17:21:58232 // The service worker in this test simply posts a message to the background
233 // client it receives from getBackgroundClient().
234 const char* kScript =
235 "var messagePromise = null;\n"
236 "if (test.lastMessageFromServiceWorker) {\n"
237 " messagePromise = Promise.resolve(test.lastMessageFromServiceWorker);\n"
238 "} else {\n"
239 " messagePromise = test.waitForMessage(navigator.serviceWorker);\n"
240 "}\n"
241 "messagePromise.then(function(message) {\n"
242 " window.domAutomationController.send(String(message == 'success'));\n"
243 "})\n";
244 EXPECT_EQ("true", ExecuteScriptInBackgroundPage(extension->id(), kScript));
annekao533482222015-08-21 23:23:53245}
246
kalman6f984ae2015-09-18 17:21:58247IN_PROC_BROWSER_TEST_F(ServiceWorkerTest,
248 BackgroundPagePostsMessageToServiceWorker) {
249 const Extension* extension =
250 StartTestFromBackgroundPage("post_message_to_sw.js", kExpectSuccess);
annekao533482222015-08-21 23:23:53251
kalman6f984ae2015-09-18 17:21:58252 // The service worker in this test waits for a message, then echoes it back
253 // by posting a message to the background page via getBackgroundClient().
254 const char* kScript =
255 "var mc = new MessageChannel();\n"
256 "test.waitForMessage(mc.port1).then(function(message) {\n"
257 " window.domAutomationController.send(String(message == 'hello'));\n"
258 "});\n"
259 "test.registeredServiceWorker.postMessage(\n"
260 " {message: 'hello', port: mc.port2}, [mc.port2])\n";
261 EXPECT_EQ("true", ExecuteScriptInBackgroundPage(extension->id(), kScript));
annekao533482222015-08-21 23:23:53262}
263
rdevlin.croninf5863da2015-09-10 19:21:45264IN_PROC_BROWSER_TEST_F(ServiceWorkerTest,
265 ServiceWorkerSuspensionOnExtensionUnload) {
kalman6f984ae2015-09-18 17:21:58266 // For this test, only hold onto the extension's ID and URL + a function to
267 // get a resource URL, because we're going to be disabling and uninstalling
268 // it, which will invalidate the pointer.
269 std::string extension_id;
270 GURL extension_url;
271 {
272 const Extension* extension =
273 StartTestFromBackgroundPage("fetch.js", kExpectSuccess);
274 extension_id = extension->id();
275 extension_url = extension->url();
276 }
277 auto get_resource_url = [&extension_url](const std::string& path) {
278 return Extension::GetResourceURL(extension_url, path);
279 };
rdevlin.croninf5863da2015-09-10 19:21:45280
kalman6f984ae2015-09-18 17:21:58281 // Fetch should route to the service worker.
282 EXPECT_EQ("Caught a fetch for /index.html",
283 NavigateAndExtractInnerText(get_resource_url("index.html")));
rdevlin.croninf5863da2015-09-10 19:21:45284
kalman6f984ae2015-09-18 17:21:58285 // Disable the extension. Opening the page should fail.
286 extension_service()->DisableExtension(extension_id,
rdevlin.croninf5863da2015-09-10 19:21:45287 Extension::DISABLE_USER_ACTION);
288 base::RunLoop().RunUntilIdle();
rdevlin.croninf5863da2015-09-10 19:21:45289
kalman6f984ae2015-09-18 17:21:58290 EXPECT_EQ(content::PAGE_TYPE_ERROR,
291 NavigateAndGetPageType(get_resource_url("index.html")));
292 EXPECT_EQ(content::PAGE_TYPE_ERROR,
293 NavigateAndGetPageType(get_resource_url("other.html")));
294
295 // Re-enable the extension. Opening pages should immediately start to succeed
296 // again.
rdevlin.croninf5863da2015-09-10 19:21:45297 extension_service()->EnableExtension(extension_id);
298 base::RunLoop().RunUntilIdle();
299
kalman6f984ae2015-09-18 17:21:58300 EXPECT_EQ("Caught a fetch for /index.html",
301 NavigateAndExtractInnerText(get_resource_url("index.html")));
302 EXPECT_EQ("Caught a fetch for /other.html",
303 NavigateAndExtractInnerText(get_resource_url("other.html")));
304 EXPECT_EQ("Caught a fetch for /another.html",
305 NavigateAndExtractInnerText(get_resource_url("another.html")));
rdevlin.croninf5863da2015-09-10 19:21:45306
kalman6f984ae2015-09-18 17:21:58307 // Uninstall the extension. Opening pages should fail again.
308 base::string16 error;
309 extension_service()->UninstallExtension(
310 extension_id, UninstallReason::UNINSTALL_REASON_FOR_TESTING,
311 base::Bind(&base::DoNothing), &error);
312 base::RunLoop().RunUntilIdle();
313
314 EXPECT_EQ(content::PAGE_TYPE_ERROR,
315 NavigateAndGetPageType(get_resource_url("index.html")));
316 EXPECT_EQ(content::PAGE_TYPE_ERROR,
317 NavigateAndGetPageType(get_resource_url("other.html")));
318 EXPECT_EQ(content::PAGE_TYPE_ERROR,
319 NavigateAndGetPageType(get_resource_url("anotherother.html")));
320 EXPECT_EQ(content::PAGE_TYPE_ERROR,
321 NavigateAndGetPageType(get_resource_url("final.html")));
322}
323
324IN_PROC_BROWSER_TEST_F(ServiceWorkerTest, BackgroundPageIsWokenIfAsleep) {
325 const Extension* extension =
326 StartTestFromBackgroundPage("wake_on_fetch.js", kExpectSuccess);
327
328 // Navigate to special URLs that this test's service worker recognises, each
329 // making a check then populating the response with either "true" or "false".
330 EXPECT_EQ("true", NavigateAndExtractInnerText(extension->GetResourceURL(
331 "background-client-is-awake")));
332 EXPECT_EQ("true", NavigateAndExtractInnerText(
333 extension->GetResourceURL("ping-background-client")));
334 // Ping more than once for good measure.
335 EXPECT_EQ("true", NavigateAndExtractInnerText(
336 extension->GetResourceURL("ping-background-client")));
337
338 // Shut down the event page. The SW should detect that it's closed, but still
339 // be able to ping it.
340 ExtensionHost* background_page =
341 process_manager()->GetBackgroundHostForExtension(extension->id());
342 ASSERT_TRUE(background_page);
343 background_page->Close();
344 BackgroundPageWatcher(process_manager(), extension).WaitForClose();
345
346 EXPECT_EQ("false", NavigateAndExtractInnerText(extension->GetResourceURL(
347 "background-client-is-awake")));
348 EXPECT_EQ("true", NavigateAndExtractInnerText(
349 extension->GetResourceURL("ping-background-client")));
350 EXPECT_EQ("true", NavigateAndExtractInnerText(
351 extension->GetResourceURL("ping-background-client")));
352 EXPECT_EQ("true", NavigateAndExtractInnerText(extension->GetResourceURL(
353 "background-client-is-awake")));
354}
355
356IN_PROC_BROWSER_TEST_F(ServiceWorkerTest,
357 GetBackgroundClientFailsWithNoBackgroundPage) {
358 // This extension doesn't have a background page, only a tab at page.html.
359 // The service worker it registers tries to call getBackgroundClient() and
360 // should fail.
361 // Note that this also tests that service workers can be registered from tabs.
362 EXPECT_TRUE(RunExtensionSubtest("service_worker/no_background", "page.html"));
rdevlin.croninf5863da2015-09-10 19:21:45363}
364
lazyboy6ddb7d62015-11-10 23:15:27365IN_PROC_BROWSER_TEST_F(ServiceWorkerTest, NotificationAPI) {
366 EXPECT_TRUE(RunExtensionSubtest("service_worker/notifications/has_permission",
367 "page.html"));
368}
369
lazyboybd325ae2015-11-18 21:35:26370IN_PROC_BROWSER_TEST_F(ServiceWorkerBackgroundSyncTest, Sync) {
371 const Extension* extension = LoadExtensionWithFlags(
372 test_data_dir_.AppendASCII("service_worker/sync"), kFlagNone);
373 ASSERT_TRUE(extension);
374 ui_test_utils::NavigateToURL(browser(),
375 extension->GetResourceURL("page.html"));
376 content::WebContents* web_contents =
377 browser()->tab_strip_model()->GetActiveWebContents();
378
379 // Prevent firing by going offline.
380 content::background_sync_test_util::SetOnline(web_contents, false);
381
382 ExtensionTestMessageListener sync_listener("SYNC: send-chats", false);
383 sync_listener.set_failure_message("FAIL");
384
385 std::string result;
386 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
387 web_contents, "window.runServiceWorker()", &result));
388 ASSERT_EQ("SERVICE_WORKER_READY", result);
389
390 EXPECT_FALSE(sync_listener.was_satisfied());
391 // Resume firing by going online.
392 content::background_sync_test_util::SetOnline(web_contents, true);
393 EXPECT_TRUE(sync_listener.WaitUntilSatisfied());
394}
395
horo1eeddde2015-11-19 05:59:25396IN_PROC_BROWSER_TEST_F(ServiceWorkerTest,
397 FetchFromContentScriptShouldNotGoToServiceWorkerOfPage) {
398 ASSERT_TRUE(StartEmbeddedTestServer());
399 GURL page_url = embedded_test_server()->GetURL(
400 "/extensions/api_test/service_worker/content_script_fetch/"
401 "controlled_page/index.html");
402 content::WebContents* tab =
403 browser()->tab_strip_model()->GetActiveWebContents();
404 ui_test_utils::NavigateToURL(browser(), page_url);
405 content::WaitForLoadStop(tab);
406
407 std::string value;
408 ASSERT_TRUE(
409 content::ExecuteScriptAndExtractString(tab, "register();", &value));
410 EXPECT_EQ("SW controlled", value);
411
412 ASSERT_TRUE(RunExtensionTest("service_worker/content_script_fetch"))
413 << message_;
414}
415
annekao38685502015-07-14 17:46:39416} // namespace extensions