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