1 // 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.
5 #include "base/bind_helpers.h"
6 #include "base/strings/stringprintf.h"
7 #include "chrome/browser/extensions/extension_apitest.h"
8 #include "chrome/browser/extensions/extension_service.h"
9 #include "chrome/browser/ui/tabs/tab_strip_model.h"
10 #include "chrome/test/base/ui_test_utils.h"
11 #include "components/version_info/version_info.h"
12 #include "content/public/browser/navigation_controller.h"
13 #include "content/public/browser/navigation_entry.h"
14 #include "content/public/browser/web_contents.h"
15 #include "content/public/common/page_type.h"
16 #include "content/public/test/browser_test_utils.h"
17 #include "extensions/browser/extension_host.h"
18 #include "extensions/browser/process_manager.h"
19 #include "extensions/test/background_page_watcher.h"
20 #include "extensions/test/extension_test_message_listener.h"
22 namespace extensions
{
26 // Pass into ServiceWorkerTest::StartTestFromBackgroundPage to indicate that
27 // registration is expected to succeed.
28 std::string
* const kExpectSuccess
= nullptr;
30 void DoNothingWithBool(bool b
) {}
34 class ServiceWorkerTest
: public ExtensionApiTest
{
36 ServiceWorkerTest() : current_channel_(version_info::Channel::UNKNOWN
) {}
38 ~ServiceWorkerTest() override
{}
41 // Returns the ProcessManager for the test's profile.
42 ProcessManager
* process_manager() { return ProcessManager::Get(profile()); }
44 // Starts running a test from the background page test extension.
46 // This registers a service worker with |script_name|, and fetches the
47 // registration result.
49 // If |error_or_null| is null (kExpectSuccess), success is expected and this
50 // will fail if there is an error.
51 // If |error_or_null| is not null, nothing is assumed, and the error (which
52 // may be empty) is written to it.
53 const Extension
* StartTestFromBackgroundPage(const char* script_name
,
54 std::string
* error_or_null
) {
55 const Extension
* extension
=
56 LoadExtension(test_data_dir_
.AppendASCII("service_worker/background"));
58 ExtensionHost
* background_host
=
59 process_manager()->GetBackgroundHostForExtension(extension
->id());
60 CHECK(background_host
);
62 CHECK(content::ExecuteScriptAndExtractString(
63 background_host
->host_contents(),
64 base::StringPrintf("test.registerServiceWorker('%s')", script_name
),
67 *error_or_null
= error
;
68 else if (!error
.empty())
69 ADD_FAILURE() << "Got unexpected error " << error
;
73 // Navigates the browser to a new tab at |url|, waits for it to load, then
75 content::WebContents
* Navigate(const GURL
& url
) {
76 ui_test_utils::NavigateToURLWithDisposition(
77 browser(), url
, NEW_FOREGROUND_TAB
,
78 ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION
);
79 content::WebContents
* web_contents
=
80 browser()->tab_strip_model()->GetActiveWebContents();
81 content::WaitForLoadStop(web_contents
);
85 // Navigates the browser to |url| and returns the new tab's page type.
86 content::PageType
NavigateAndGetPageType(const GURL
& url
) {
87 return Navigate(url
)->GetController().GetActiveEntry()->GetPageType();
90 // Extracts the innerText from |contents|.
91 std::string
ExtractInnerText(content::WebContents
* contents
) {
92 std::string inner_text
;
93 if (!content::ExecuteScriptAndExtractString(
95 "window.domAutomationController.send(document.body.innerText)",
97 ADD_FAILURE() << "Failed to get inner text for "
98 << contents
->GetVisibleURL();
103 // Navigates the browser to |url|, then returns the innerText of the new
104 // tab's WebContents' main frame.
105 std::string
NavigateAndExtractInnerText(const GURL
& url
) {
106 return ExtractInnerText(Navigate(url
));
110 // Sets the channel to "trunk" since service workers are restricted to trunk.
111 ScopedCurrentChannel current_channel_
;
113 DISALLOW_COPY_AND_ASSIGN(ServiceWorkerTest
);
116 IN_PROC_BROWSER_TEST_F(ServiceWorkerTest
, RegisterSucceedsOnTrunk
) {
117 StartTestFromBackgroundPage("register.js", kExpectSuccess
);
120 // This feature is restricted to trunk, so on dev it should have existing
121 // behavior - which is for it to fail.
122 IN_PROC_BROWSER_TEST_F(ServiceWorkerTest
, RegisterFailsOnDev
) {
123 ScopedCurrentChannel
current_channel_override(
124 version_info::Channel::DEV
);
126 const Extension
* extension
=
127 StartTestFromBackgroundPage("register.js", &error
);
129 "Failed to register a ServiceWorker: The URL protocol of the current "
130 "origin ('chrome-extension://" +
131 extension
->id() + "') is not supported.",
135 IN_PROC_BROWSER_TEST_F(ServiceWorkerTest
, FetchArbitraryPaths
) {
136 const Extension
* extension
=
137 StartTestFromBackgroundPage("fetch.js", kExpectSuccess
);
139 // Open some arbirary paths. Their contents should be what the service worker
140 // responds with, which in this case is the path of the fetch.
142 "Caught a fetch for /index.html",
143 NavigateAndExtractInnerText(extension
->GetResourceURL("index.html")));
144 EXPECT_EQ("Caught a fetch for /path/to/other.html",
145 NavigateAndExtractInnerText(
146 extension
->GetResourceURL("path/to/other.html")));
147 EXPECT_EQ("Caught a fetch for /some/text/file.txt",
148 NavigateAndExtractInnerText(
149 extension
->GetResourceURL("some/text/file.txt")));
150 EXPECT_EQ("Caught a fetch for /no/file/extension",
151 NavigateAndExtractInnerText(
152 extension
->GetResourceURL("no/file/extension")));
153 EXPECT_EQ("Caught a fetch for /",
154 NavigateAndExtractInnerText(extension
->GetResourceURL("")));
157 IN_PROC_BROWSER_TEST_F(ServiceWorkerTest
,
158 LoadingBackgroundPageBypassesServiceWorker
) {
159 const Extension
* extension
=
160 StartTestFromBackgroundPage("fetch.js", kExpectSuccess
);
162 std::string kExpectedInnerText
= "background.html contents for testing.";
164 // Sanity check that the background page has the expected content.
165 ExtensionHost
* background_page
=
166 process_manager()->GetBackgroundHostForExtension(extension
->id());
167 ASSERT_TRUE(background_page
);
168 EXPECT_EQ(kExpectedInnerText
,
169 ExtractInnerText(background_page
->host_contents()));
171 // Close the background page.
172 background_page
->Close();
173 BackgroundPageWatcher(process_manager(), extension
).WaitForClose();
174 background_page
= nullptr;
177 process_manager()->WakeEventPage(extension
->id(),
178 base::Bind(&DoNothingWithBool
));
179 BackgroundPageWatcher(process_manager(), extension
).WaitForOpen();
181 // Content should not have been affected by the fetch, which would otherwise
182 // be "Caught fetch for...".
184 process_manager()->GetBackgroundHostForExtension(extension
->id());
185 ASSERT_TRUE(background_page
);
186 content::WaitForLoadStop(background_page
->host_contents());
188 // TODO(kalman): Everything you've read has been a LIE! It should be:
190 // EXPECT_EQ(kExpectedInnerText,
191 // ExtractInnerText(background_page->host_contents()));
193 // but there is a bug, and we're actually *not* bypassing the service worker
194 // for background page loads! For now, let it pass (assert wrong behavior)
195 // because it's not a regression, but this must be fixed eventually.
197 // Tracked in crbug.com/532720.
198 EXPECT_EQ("Caught a fetch for /background.html",
199 ExtractInnerText(background_page
->host_contents()));
202 IN_PROC_BROWSER_TEST_F(ServiceWorkerTest
,
203 ServiceWorkerPostsMessageToBackgroundClient
) {
204 const Extension
* extension
= StartTestFromBackgroundPage(
205 "post_message_to_background_client.js", kExpectSuccess
);
207 // The service worker in this test simply posts a message to the background
208 // client it receives from getBackgroundClient().
209 const char* kScript
=
210 "var messagePromise = null;\n"
211 "if (test.lastMessageFromServiceWorker) {\n"
212 " messagePromise = Promise.resolve(test.lastMessageFromServiceWorker);\n"
214 " messagePromise = test.waitForMessage(navigator.serviceWorker);\n"
216 "messagePromise.then(function(message) {\n"
217 " window.domAutomationController.send(String(message == 'success'));\n"
219 EXPECT_EQ("true", ExecuteScriptInBackgroundPage(extension
->id(), kScript
));
222 IN_PROC_BROWSER_TEST_F(ServiceWorkerTest
,
223 BackgroundPagePostsMessageToServiceWorker
) {
224 const Extension
* extension
=
225 StartTestFromBackgroundPage("post_message_to_sw.js", kExpectSuccess
);
227 // The service worker in this test waits for a message, then echoes it back
228 // by posting a message to the background page via getBackgroundClient().
229 const char* kScript
=
230 "var mc = new MessageChannel();\n"
231 "test.waitForMessage(mc.port1).then(function(message) {\n"
232 " window.domAutomationController.send(String(message == 'hello'));\n"
234 "test.registeredServiceWorker.postMessage(\n"
235 " {message: 'hello', port: mc.port2}, [mc.port2])\n";
236 EXPECT_EQ("true", ExecuteScriptInBackgroundPage(extension
->id(), kScript
));
239 IN_PROC_BROWSER_TEST_F(ServiceWorkerTest
,
240 ServiceWorkerSuspensionOnExtensionUnload
) {
241 // For this test, only hold onto the extension's ID and URL + a function to
242 // get a resource URL, because we're going to be disabling and uninstalling
243 // it, which will invalidate the pointer.
244 std::string extension_id
;
247 const Extension
* extension
=
248 StartTestFromBackgroundPage("fetch.js", kExpectSuccess
);
249 extension_id
= extension
->id();
250 extension_url
= extension
->url();
252 auto get_resource_url
= [&extension_url
](const std::string
& path
) {
253 return Extension::GetResourceURL(extension_url
, path
);
256 // Fetch should route to the service worker.
257 EXPECT_EQ("Caught a fetch for /index.html",
258 NavigateAndExtractInnerText(get_resource_url("index.html")));
260 // Disable the extension. Opening the page should fail.
261 extension_service()->DisableExtension(extension_id
,
262 Extension::DISABLE_USER_ACTION
);
263 base::RunLoop().RunUntilIdle();
265 EXPECT_EQ(content::PAGE_TYPE_ERROR
,
266 NavigateAndGetPageType(get_resource_url("index.html")));
267 EXPECT_EQ(content::PAGE_TYPE_ERROR
,
268 NavigateAndGetPageType(get_resource_url("other.html")));
270 // Re-enable the extension. Opening pages should immediately start to succeed
272 extension_service()->EnableExtension(extension_id
);
273 base::RunLoop().RunUntilIdle();
275 EXPECT_EQ("Caught a fetch for /index.html",
276 NavigateAndExtractInnerText(get_resource_url("index.html")));
277 EXPECT_EQ("Caught a fetch for /other.html",
278 NavigateAndExtractInnerText(get_resource_url("other.html")));
279 EXPECT_EQ("Caught a fetch for /another.html",
280 NavigateAndExtractInnerText(get_resource_url("another.html")));
282 // Uninstall the extension. Opening pages should fail again.
283 base::string16 error
;
284 extension_service()->UninstallExtension(
285 extension_id
, UninstallReason::UNINSTALL_REASON_FOR_TESTING
,
286 base::Bind(&base::DoNothing
), &error
);
287 base::RunLoop().RunUntilIdle();
289 EXPECT_EQ(content::PAGE_TYPE_ERROR
,
290 NavigateAndGetPageType(get_resource_url("index.html")));
291 EXPECT_EQ(content::PAGE_TYPE_ERROR
,
292 NavigateAndGetPageType(get_resource_url("other.html")));
293 EXPECT_EQ(content::PAGE_TYPE_ERROR
,
294 NavigateAndGetPageType(get_resource_url("anotherother.html")));
295 EXPECT_EQ(content::PAGE_TYPE_ERROR
,
296 NavigateAndGetPageType(get_resource_url("final.html")));
299 IN_PROC_BROWSER_TEST_F(ServiceWorkerTest
, BackgroundPageIsWokenIfAsleep
) {
300 const Extension
* extension
=
301 StartTestFromBackgroundPage("wake_on_fetch.js", kExpectSuccess
);
303 // Navigate to special URLs that this test's service worker recognises, each
304 // making a check then populating the response with either "true" or "false".
305 EXPECT_EQ("true", NavigateAndExtractInnerText(extension
->GetResourceURL(
306 "background-client-is-awake")));
307 EXPECT_EQ("true", NavigateAndExtractInnerText(
308 extension
->GetResourceURL("ping-background-client")));
309 // Ping more than once for good measure.
310 EXPECT_EQ("true", NavigateAndExtractInnerText(
311 extension
->GetResourceURL("ping-background-client")));
313 // Shut down the event page. The SW should detect that it's closed, but still
314 // be able to ping it.
315 ExtensionHost
* background_page
=
316 process_manager()->GetBackgroundHostForExtension(extension
->id());
317 ASSERT_TRUE(background_page
);
318 background_page
->Close();
319 BackgroundPageWatcher(process_manager(), extension
).WaitForClose();
321 EXPECT_EQ("false", NavigateAndExtractInnerText(extension
->GetResourceURL(
322 "background-client-is-awake")));
323 EXPECT_EQ("true", NavigateAndExtractInnerText(
324 extension
->GetResourceURL("ping-background-client")));
325 EXPECT_EQ("true", NavigateAndExtractInnerText(
326 extension
->GetResourceURL("ping-background-client")));
327 EXPECT_EQ("true", NavigateAndExtractInnerText(extension
->GetResourceURL(
328 "background-client-is-awake")));
331 IN_PROC_BROWSER_TEST_F(ServiceWorkerTest
,
332 GetBackgroundClientFailsWithNoBackgroundPage
) {
333 // This extension doesn't have a background page, only a tab at page.html.
334 // The service worker it registers tries to call getBackgroundClient() and
336 // Note that this also tests that service workers can be registered from tabs.
337 EXPECT_TRUE(RunExtensionSubtest("service_worker/no_background", "page.html"));
340 } // namespace extensions