blob: a4786927c2e4d4b7960c05f10270aaa5f9322f8e [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"
annekao38685502015-07-14 17:46:397#include "chrome/browser/extensions/extension_apitest.h"
rdevlin.croninf5863da2015-09-10 19:21:458#include "chrome/browser/extensions/extension_service.h"
annekao1db36fd2015-07-29 17:09:169#include "chrome/browser/ui/tabs/tab_strip_model.h"
rdevlin.croninf5863da2015-09-10 19:21:4510#include "chrome/test/base/ui_test_utils.h"
sdefresne9fb67692015-08-03 18:48:2211#include "components/version_info/version_info.h"
kalman6f984ae2015-09-18 17:21:5812#include "content/public/browser/navigation_controller.h"
rdevlin.croninf5863da2015-09-10 19:21:4513#include "content/public/browser/navigation_entry.h"
kalman6f984ae2015-09-18 17:21:5814#include "content/public/browser/web_contents.h"
lazyboybd325ae2015-11-18 21:35:2615#include "content/public/common/content_switches.h"
kalman6f984ae2015-09-18 17:21:5816#include "content/public/common/page_type.h"
lazyboybd325ae2015-11-18 21:35:2617#include "content/public/test/background_sync_test_util.h"
annekao1db36fd2015-07-29 17:09:1618#include "content/public/test/browser_test_utils.h"
kalman6f984ae2015-09-18 17:21:5819#include "extensions/browser/extension_host.h"
20#include "extensions/browser/process_manager.h"
21#include "extensions/test/background_page_watcher.h"
annekao38685502015-07-14 17:46:3922#include "extensions/test/extension_test_message_listener.h"
23
24namespace extensions {
25
kalman6f984ae2015-09-18 17:21:5826namespace {
27
28// Pass into ServiceWorkerTest::StartTestFromBackgroundPage to indicate that
29// registration is expected to succeed.
30std::string* const kExpectSuccess = nullptr;
31
32void DoNothingWithBool(bool b) {}
33
34} // namespace
35
annekao38685502015-07-14 17:46:3936class ServiceWorkerTest : public ExtensionApiTest {
37 public:
kalman6f984ae2015-09-18 17:21:5838 ServiceWorkerTest() : current_channel_(version_info::Channel::UNKNOWN) {}
annekao38685502015-07-14 17:46:3939
40 ~ServiceWorkerTest() override {}
41
kalman6f984ae2015-09-18 17:21:5842 protected:
43 // Returns the ProcessManager for the test's profile.
44 ProcessManager* process_manager() { return ProcessManager::Get(profile()); }
45
46 // Starts running a test from the background page test extension.
47 //
48 // This registers a service worker with |script_name|, and fetches the
49 // registration result.
50 //
51 // If |error_or_null| is null (kExpectSuccess), success is expected and this
52 // will fail if there is an error.
53 // If |error_or_null| is not null, nothing is assumed, and the error (which
54 // may be empty) is written to it.
55 const Extension* StartTestFromBackgroundPage(const char* script_name,
56 std::string* error_or_null) {
57 const Extension* extension =
58 LoadExtension(test_data_dir_.AppendASCII("service_worker/background"));
59 CHECK(extension);
60 ExtensionHost* background_host =
61 process_manager()->GetBackgroundHostForExtension(extension->id());
62 CHECK(background_host);
63 std::string error;
64 CHECK(content::ExecuteScriptAndExtractString(
65 background_host->host_contents(),
66 base::StringPrintf("test.registerServiceWorker('%s')", script_name),
67 &error));
68 if (error_or_null)
69 *error_or_null = error;
70 else if (!error.empty())
71 ADD_FAILURE() << "Got unexpected error " << error;
72 return extension;
73 }
74
75 // Navigates the browser to a new tab at |url|, waits for it to load, then
76 // returns it.
77 content::WebContents* Navigate(const GURL& url) {
78 ui_test_utils::NavigateToURLWithDisposition(
79 browser(), url, NEW_FOREGROUND_TAB,
80 ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
81 content::WebContents* web_contents =
82 browser()->tab_strip_model()->GetActiveWebContents();
83 content::WaitForLoadStop(web_contents);
84 return web_contents;
85 }
86
87 // Navigates the browser to |url| and returns the new tab's page type.
88 content::PageType NavigateAndGetPageType(const GURL& url) {
89 return Navigate(url)->GetController().GetActiveEntry()->GetPageType();
90 }
91
92 // Extracts the innerText from |contents|.
93 std::string ExtractInnerText(content::WebContents* contents) {
94 std::string inner_text;
95 if (!content::ExecuteScriptAndExtractString(
96 contents,
97 "window.domAutomationController.send(document.body.innerText)",
98 &inner_text)) {
99 ADD_FAILURE() << "Failed to get inner text for "
100 << contents->GetVisibleURL();
101 }
102 return inner_text;
103 }
104
105 // Navigates the browser to |url|, then returns the innerText of the new
106 // tab's WebContents' main frame.
107 std::string NavigateAndExtractInnerText(const GURL& url) {
108 return ExtractInnerText(Navigate(url));
109 }
110
annekao38685502015-07-14 17:46:39111 private:
kalman6f984ae2015-09-18 17:21:58112 // Sets the channel to "trunk" since service workers are restricted to trunk.
annekao38685502015-07-14 17:46:39113 ScopedCurrentChannel current_channel_;
kalman6f984ae2015-09-18 17:21:58114
annekao38685502015-07-14 17:46:39115 DISALLOW_COPY_AND_ASSIGN(ServiceWorkerTest);
116};
117
lazyboybd325ae2015-11-18 21:35:26118class ServiceWorkerBackgroundSyncTest : public ServiceWorkerTest {
119 public:
120 ServiceWorkerBackgroundSyncTest() {}
121 ~ServiceWorkerBackgroundSyncTest() override {}
122
123 void SetUpCommandLine(base::CommandLine* command_line) override {
124 // ServiceWorkerRegistration.sync requires experimental flag.
125 command_line->AppendSwitch(
126 switches::kEnableExperimentalWebPlatformFeatures);
127 ServiceWorkerTest::SetUpCommandLine(command_line);
128 }
129
130 void SetUp() override {
131 content::background_sync_test_util::SetIgnoreNetworkChangeNotifier(true);
132 ServiceWorkerTest::SetUp();
133 }
134
135 private:
136 DISALLOW_COPY_AND_ASSIGN(ServiceWorkerBackgroundSyncTest);
137};
138
kalman6f984ae2015-09-18 17:21:58139IN_PROC_BROWSER_TEST_F(ServiceWorkerTest, RegisterSucceedsOnTrunk) {
140 StartTestFromBackgroundPage("register.js", kExpectSuccess);
annekao38685502015-07-14 17:46:39141}
142
143// This feature is restricted to trunk, so on dev it should have existing
144// behavior - which is for it to fail.
kalman6f984ae2015-09-18 17:21:58145IN_PROC_BROWSER_TEST_F(ServiceWorkerTest, RegisterFailsOnDev) {
annekao38685502015-07-14 17:46:39146 ScopedCurrentChannel current_channel_override(
sdefresne6e883e42015-07-30 08:05:54147 version_info::Channel::DEV);
kalman6f984ae2015-09-18 17:21:58148 std::string error;
149 const Extension* extension =
150 StartTestFromBackgroundPage("register.js", &error);
annekao1db36fd2015-07-29 17:09:16151 EXPECT_EQ(
kalman6f984ae2015-09-18 17:21:58152 "Failed to register a ServiceWorker: The URL protocol of the current "
153 "origin ('chrome-extension://" +
154 extension->id() + "') is not supported.",
155 error);
annekao38685502015-07-14 17:46:39156}
157
kalman6f984ae2015-09-18 17:21:58158IN_PROC_BROWSER_TEST_F(ServiceWorkerTest, FetchArbitraryPaths) {
159 const Extension* extension =
160 StartTestFromBackgroundPage("fetch.js", kExpectSuccess);
annekao1db36fd2015-07-29 17:09:16161
kalman6f984ae2015-09-18 17:21:58162 // Open some arbirary paths. Their contents should be what the service worker
163 // responds with, which in this case is the path of the fetch.
164 EXPECT_EQ(
165 "Caught a fetch for /index.html",
166 NavigateAndExtractInnerText(extension->GetResourceURL("index.html")));
167 EXPECT_EQ("Caught a fetch for /path/to/other.html",
168 NavigateAndExtractInnerText(
169 extension->GetResourceURL("path/to/other.html")));
170 EXPECT_EQ("Caught a fetch for /some/text/file.txt",
171 NavigateAndExtractInnerText(
172 extension->GetResourceURL("some/text/file.txt")));
173 EXPECT_EQ("Caught a fetch for /no/file/extension",
174 NavigateAndExtractInnerText(
175 extension->GetResourceURL("no/file/extension")));
176 EXPECT_EQ("Caught a fetch for /",
177 NavigateAndExtractInnerText(extension->GetResourceURL("")));
annekao1db36fd2015-07-29 17:09:16178}
179
kalman6f984ae2015-09-18 17:21:58180IN_PROC_BROWSER_TEST_F(ServiceWorkerTest,
181 LoadingBackgroundPageBypassesServiceWorker) {
182 const Extension* extension =
183 StartTestFromBackgroundPage("fetch.js", kExpectSuccess);
annekao49241182015-08-18 17:14:01184
kalman6f984ae2015-09-18 17:21:58185 std::string kExpectedInnerText = "background.html contents for testing.";
annekao49241182015-08-18 17:14:01186
kalman6f984ae2015-09-18 17:21:58187 // Sanity check that the background page has the expected content.
188 ExtensionHost* background_page =
189 process_manager()->GetBackgroundHostForExtension(extension->id());
190 ASSERT_TRUE(background_page);
191 EXPECT_EQ(kExpectedInnerText,
192 ExtractInnerText(background_page->host_contents()));
annekao49241182015-08-18 17:14:01193
kalman6f984ae2015-09-18 17:21:58194 // Close the background page.
195 background_page->Close();
196 BackgroundPageWatcher(process_manager(), extension).WaitForClose();
197 background_page = nullptr;
198
199 // Start it again.
200 process_manager()->WakeEventPage(extension->id(),
201 base::Bind(&DoNothingWithBool));
202 BackgroundPageWatcher(process_manager(), extension).WaitForOpen();
203
204 // Content should not have been affected by the fetch, which would otherwise
205 // be "Caught fetch for...".
206 background_page =
207 process_manager()->GetBackgroundHostForExtension(extension->id());
208 ASSERT_TRUE(background_page);
209 content::WaitForLoadStop(background_page->host_contents());
210
211 // TODO(kalman): Everything you've read has been a LIE! It should be:
212 //
213 // EXPECT_EQ(kExpectedInnerText,
214 // ExtractInnerText(background_page->host_contents()));
215 //
216 // but there is a bug, and we're actually *not* bypassing the service worker
217 // for background page loads! For now, let it pass (assert wrong behavior)
218 // because it's not a regression, but this must be fixed eventually.
219 //
220 // Tracked in crbug.com/532720.
221 EXPECT_EQ("Caught a fetch for /background.html",
222 ExtractInnerText(background_page->host_contents()));
annekao49241182015-08-18 17:14:01223}
224
kalman6f984ae2015-09-18 17:21:58225IN_PROC_BROWSER_TEST_F(ServiceWorkerTest,
226 ServiceWorkerPostsMessageToBackgroundClient) {
227 const Extension* extension = StartTestFromBackgroundPage(
228 "post_message_to_background_client.js", kExpectSuccess);
annekao533482222015-08-21 23:23:53229
kalman6f984ae2015-09-18 17:21:58230 // The service worker in this test simply posts a message to the background
231 // client it receives from getBackgroundClient().
232 const char* kScript =
233 "var messagePromise = null;\n"
234 "if (test.lastMessageFromServiceWorker) {\n"
235 " messagePromise = Promise.resolve(test.lastMessageFromServiceWorker);\n"
236 "} else {\n"
237 " messagePromise = test.waitForMessage(navigator.serviceWorker);\n"
238 "}\n"
239 "messagePromise.then(function(message) {\n"
240 " window.domAutomationController.send(String(message == 'success'));\n"
241 "})\n";
242 EXPECT_EQ("true", ExecuteScriptInBackgroundPage(extension->id(), kScript));
annekao533482222015-08-21 23:23:53243}
244
kalman6f984ae2015-09-18 17:21:58245IN_PROC_BROWSER_TEST_F(ServiceWorkerTest,
246 BackgroundPagePostsMessageToServiceWorker) {
247 const Extension* extension =
248 StartTestFromBackgroundPage("post_message_to_sw.js", kExpectSuccess);
annekao533482222015-08-21 23:23:53249
kalman6f984ae2015-09-18 17:21:58250 // The service worker in this test waits for a message, then echoes it back
251 // by posting a message to the background page via getBackgroundClient().
252 const char* kScript =
253 "var mc = new MessageChannel();\n"
254 "test.waitForMessage(mc.port1).then(function(message) {\n"
255 " window.domAutomationController.send(String(message == 'hello'));\n"
256 "});\n"
257 "test.registeredServiceWorker.postMessage(\n"
258 " {message: 'hello', port: mc.port2}, [mc.port2])\n";
259 EXPECT_EQ("true", ExecuteScriptInBackgroundPage(extension->id(), kScript));
annekao533482222015-08-21 23:23:53260}
261
rdevlin.croninf5863da2015-09-10 19:21:45262IN_PROC_BROWSER_TEST_F(ServiceWorkerTest,
263 ServiceWorkerSuspensionOnExtensionUnload) {
kalman6f984ae2015-09-18 17:21:58264 // For this test, only hold onto the extension's ID and URL + a function to
265 // get a resource URL, because we're going to be disabling and uninstalling
266 // it, which will invalidate the pointer.
267 std::string extension_id;
268 GURL extension_url;
269 {
270 const Extension* extension =
271 StartTestFromBackgroundPage("fetch.js", kExpectSuccess);
272 extension_id = extension->id();
273 extension_url = extension->url();
274 }
275 auto get_resource_url = [&extension_url](const std::string& path) {
276 return Extension::GetResourceURL(extension_url, path);
277 };
rdevlin.croninf5863da2015-09-10 19:21:45278
kalman6f984ae2015-09-18 17:21:58279 // Fetch should route to the service worker.
280 EXPECT_EQ("Caught a fetch for /index.html",
281 NavigateAndExtractInnerText(get_resource_url("index.html")));
rdevlin.croninf5863da2015-09-10 19:21:45282
kalman6f984ae2015-09-18 17:21:58283 // Disable the extension. Opening the page should fail.
284 extension_service()->DisableExtension(extension_id,
rdevlin.croninf5863da2015-09-10 19:21:45285 Extension::DISABLE_USER_ACTION);
286 base::RunLoop().RunUntilIdle();
rdevlin.croninf5863da2015-09-10 19:21:45287
kalman6f984ae2015-09-18 17:21:58288 EXPECT_EQ(content::PAGE_TYPE_ERROR,
289 NavigateAndGetPageType(get_resource_url("index.html")));
290 EXPECT_EQ(content::PAGE_TYPE_ERROR,
291 NavigateAndGetPageType(get_resource_url("other.html")));
292
293 // Re-enable the extension. Opening pages should immediately start to succeed
294 // again.
rdevlin.croninf5863da2015-09-10 19:21:45295 extension_service()->EnableExtension(extension_id);
296 base::RunLoop().RunUntilIdle();
297
kalman6f984ae2015-09-18 17:21:58298 EXPECT_EQ("Caught a fetch for /index.html",
299 NavigateAndExtractInnerText(get_resource_url("index.html")));
300 EXPECT_EQ("Caught a fetch for /other.html",
301 NavigateAndExtractInnerText(get_resource_url("other.html")));
302 EXPECT_EQ("Caught a fetch for /another.html",
303 NavigateAndExtractInnerText(get_resource_url("another.html")));
rdevlin.croninf5863da2015-09-10 19:21:45304
kalman6f984ae2015-09-18 17:21:58305 // Uninstall the extension. Opening pages should fail again.
306 base::string16 error;
307 extension_service()->UninstallExtension(
308 extension_id, UninstallReason::UNINSTALL_REASON_FOR_TESTING,
309 base::Bind(&base::DoNothing), &error);
310 base::RunLoop().RunUntilIdle();
311
312 EXPECT_EQ(content::PAGE_TYPE_ERROR,
313 NavigateAndGetPageType(get_resource_url("index.html")));
314 EXPECT_EQ(content::PAGE_TYPE_ERROR,
315 NavigateAndGetPageType(get_resource_url("other.html")));
316 EXPECT_EQ(content::PAGE_TYPE_ERROR,
317 NavigateAndGetPageType(get_resource_url("anotherother.html")));
318 EXPECT_EQ(content::PAGE_TYPE_ERROR,
319 NavigateAndGetPageType(get_resource_url("final.html")));
320}
321
322IN_PROC_BROWSER_TEST_F(ServiceWorkerTest, BackgroundPageIsWokenIfAsleep) {
323 const Extension* extension =
324 StartTestFromBackgroundPage("wake_on_fetch.js", kExpectSuccess);
325
326 // Navigate to special URLs that this test's service worker recognises, each
327 // making a check then populating the response with either "true" or "false".
328 EXPECT_EQ("true", NavigateAndExtractInnerText(extension->GetResourceURL(
329 "background-client-is-awake")));
330 EXPECT_EQ("true", NavigateAndExtractInnerText(
331 extension->GetResourceURL("ping-background-client")));
332 // Ping more than once for good measure.
333 EXPECT_EQ("true", NavigateAndExtractInnerText(
334 extension->GetResourceURL("ping-background-client")));
335
336 // Shut down the event page. The SW should detect that it's closed, but still
337 // be able to ping it.
338 ExtensionHost* background_page =
339 process_manager()->GetBackgroundHostForExtension(extension->id());
340 ASSERT_TRUE(background_page);
341 background_page->Close();
342 BackgroundPageWatcher(process_manager(), extension).WaitForClose();
343
344 EXPECT_EQ("false", NavigateAndExtractInnerText(extension->GetResourceURL(
345 "background-client-is-awake")));
346 EXPECT_EQ("true", NavigateAndExtractInnerText(
347 extension->GetResourceURL("ping-background-client")));
348 EXPECT_EQ("true", NavigateAndExtractInnerText(
349 extension->GetResourceURL("ping-background-client")));
350 EXPECT_EQ("true", NavigateAndExtractInnerText(extension->GetResourceURL(
351 "background-client-is-awake")));
352}
353
354IN_PROC_BROWSER_TEST_F(ServiceWorkerTest,
355 GetBackgroundClientFailsWithNoBackgroundPage) {
356 // This extension doesn't have a background page, only a tab at page.html.
357 // The service worker it registers tries to call getBackgroundClient() and
358 // should fail.
359 // Note that this also tests that service workers can be registered from tabs.
360 EXPECT_TRUE(RunExtensionSubtest("service_worker/no_background", "page.html"));
rdevlin.croninf5863da2015-09-10 19:21:45361}
362
lazyboy6ddb7d62015-11-10 23:15:27363IN_PROC_BROWSER_TEST_F(ServiceWorkerTest, NotificationAPI) {
364 EXPECT_TRUE(RunExtensionSubtest("service_worker/notifications/has_permission",
365 "page.html"));
366}
367
lazyboybd325ae2015-11-18 21:35:26368IN_PROC_BROWSER_TEST_F(ServiceWorkerBackgroundSyncTest, Sync) {
369 const Extension* extension = LoadExtensionWithFlags(
370 test_data_dir_.AppendASCII("service_worker/sync"), kFlagNone);
371 ASSERT_TRUE(extension);
372 ui_test_utils::NavigateToURL(browser(),
373 extension->GetResourceURL("page.html"));
374 content::WebContents* web_contents =
375 browser()->tab_strip_model()->GetActiveWebContents();
376
377 // Prevent firing by going offline.
378 content::background_sync_test_util::SetOnline(web_contents, false);
379
380 ExtensionTestMessageListener sync_listener("SYNC: send-chats", false);
381 sync_listener.set_failure_message("FAIL");
382
383 std::string result;
384 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
385 web_contents, "window.runServiceWorker()", &result));
386 ASSERT_EQ("SERVICE_WORKER_READY", result);
387
388 EXPECT_FALSE(sync_listener.was_satisfied());
389 // Resume firing by going online.
390 content::background_sync_test_util::SetOnline(web_contents, true);
391 EXPECT_TRUE(sync_listener.WaitUntilSatisfied());
392}
393
annekao38685502015-07-14 17:46:39394} // namespace extensions