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.
4
5 #include <memory>
6
7 #include "base/run_loop.h"
8 #include "chrome/browser/apps/platform_apps/app_browsertest_util.h"
9 #include "chrome/browser/extensions/extension_apitest.h"
10 #include "chrome/browser/extensions/extension_function_test_utils.h"
11 #include "chrome/browser/extensions/extension_service.h"
12 #include "chrome/test/base/ui_test_utils.h"
13 #include "content/public/test/browser_test.h"
14 #include "content/public/test/browser_test_utils.h"
15 #include "extensions/browser/api/runtime/runtime_api.h"
16 #include "extensions/browser/blocklist_state.h"
17 #include "extensions/browser/extension_dialog_auto_confirm.h"
18 #include "extensions/browser/extension_function.h"
19 #include "extensions/browser/extension_prefs.h"
20 #include "extensions/browser/extension_registry.h"
21 #include "extensions/browser/test_extension_registry_observer.h"
22 #include "extensions/common/scoped_worker_based_extensions_channel.h"
23 #include "extensions/test/extension_test_message_listener.h"
24 #include "extensions/test/result_catcher.h"
25 #include "extensions/test/test_extension_dir.h"
26 #include "net/test/embedded_test_server/embedded_test_server.h"
27 #include "url/url_constants.h"
28
29 namespace extensions {
30
31 using ContextType = ExtensionBrowserTest::ContextType;
32
33 class RuntimeApiTest : public ExtensionApiTest,
34 public testing::WithParamInterface<ContextType> {
35 public:
RuntimeApiTest()36 RuntimeApiTest() {
37 // Service Workers are currently only available on certain channels, so set
38 // the channel for those tests.
39 if (GetParam() == ContextType::kServiceWorker)
40 current_channel_ = std::make_unique<ScopedWorkerBasedExtensionsChannel>();
41 }
42
43 RuntimeApiTest(const RuntimeApiTest&) = delete;
44 RuntimeApiTest& operator=(const RuntimeApiTest&) = delete;
45
LoadExtensionWithParamFlag(const base::FilePath & path)46 const Extension* LoadExtensionWithParamFlag(const base::FilePath& path) {
47 int flags = kFlagEnableFileAccess;
48 if (GetParam() == ContextType::kServiceWorker)
49 flags |= ExtensionBrowserTest::kFlagRunAsServiceWorkerBasedExtension;
50
51 return LoadExtensionWithFlags(path, flags);
52 }
53
RunTestWithParamFlag(const std::string & extension_name)54 bool RunTestWithParamFlag(const std::string& extension_name) {
55 int flags = kFlagEnableFileAccess;
56 if (GetParam() == ContextType::kServiceWorker)
57 flags |= ExtensionBrowserTest::kFlagRunAsServiceWorkerBasedExtension;
58
59 return RunExtensionTestWithFlags(extension_name, flags, kFlagNone);
60 }
61
62 private:
63 std::unique_ptr<extensions::ScopedWorkerBasedExtensionsChannel>
64 current_channel_;
65 };
66
67 INSTANTIATE_TEST_SUITE_P(PersistentBackground,
68 RuntimeApiTest,
69 ::testing::Values(ContextType::kPersistentBackground));
70
71 INSTANTIATE_TEST_SUITE_P(ServiceWorker,
72 RuntimeApiTest,
73 ::testing::Values(ContextType::kServiceWorker));
74
75 // Tests the privileged components of chrome.runtime.
IN_PROC_BROWSER_TEST_P(RuntimeApiTest,ChromeRuntimePrivileged)76 IN_PROC_BROWSER_TEST_P(RuntimeApiTest, ChromeRuntimePrivileged) {
77 ASSERT_TRUE(RunTestWithParamFlag("runtime/privileged")) << message_;
78 }
79
80 // Tests the unprivileged components of chrome.runtime.
IN_PROC_BROWSER_TEST_P(RuntimeApiTest,ChromeRuntimeUnprivileged)81 IN_PROC_BROWSER_TEST_P(RuntimeApiTest, ChromeRuntimeUnprivileged) {
82 ASSERT_TRUE(StartEmbeddedTestServer());
83 ASSERT_TRUE(
84 LoadExtension(test_data_dir_.AppendASCII("runtime/content_script")));
85
86 // The content script runs on this page.
87 extensions::ResultCatcher catcher;
88 ui_test_utils::NavigateToURL(browser(),
89 embedded_test_server()->GetURL("/title1.html"));
90 EXPECT_TRUE(catcher.GetNextResult()) << message_;
91 }
92
IN_PROC_BROWSER_TEST_P(RuntimeApiTest,ChromeRuntimeUninstallURL)93 IN_PROC_BROWSER_TEST_P(RuntimeApiTest, ChromeRuntimeUninstallURL) {
94 // TODO(https://crbug.com/977629): Currently, chrome.test.runWithUserGesture()
95 // doesn't support Service Worker-based extensions, so this is a workaround.
96 using ScopedUserGestureForTests =
97 ExtensionFunction::ScopedUserGestureForTests;
98 std::unique_ptr<ScopedUserGestureForTests> scoped_user_gesture;
99 if (GetParam() == ContextType::kServiceWorker)
100 scoped_user_gesture = std::make_unique<ScopedUserGestureForTests>();
101
102 // Auto-confirm the uninstall dialog.
103 extensions::ScopedTestDialogAutoConfirm auto_confirm(
104 extensions::ScopedTestDialogAutoConfirm::ACCEPT);
105 ExtensionTestMessageListener ready_listener("ready", false);
106 ASSERT_TRUE(
107 LoadExtensionWithParamFlag(test_data_dir_.AppendASCII("runtime")
108 .AppendASCII("uninstall_url")
109 .AppendASCII("sets_uninstall_url")));
110 EXPECT_TRUE(ready_listener.WaitUntilSatisfied());
111 ASSERT_TRUE(RunTestWithParamFlag("runtime/uninstall_url")) << message_;
112 }
113
IN_PROC_BROWSER_TEST_P(RuntimeApiTest,GetPlatformInfo)114 IN_PROC_BROWSER_TEST_P(RuntimeApiTest, GetPlatformInfo) {
115 ASSERT_TRUE(RunTestWithParamFlag("runtime/get_platform_info")) << message_;
116 }
117
118 namespace {
119
120 const char kUninstallUrl[] = "http://www.google.com/";
121
GetActiveUrl(Browser * browser)122 std::string GetActiveUrl(Browser* browser) {
123 return browser->tab_strip_model()
124 ->GetActiveWebContents()
125 ->GetLastCommittedURL()
126 .spec();
127 }
128
129 class RuntimeAPIUpdateTest : public ExtensionApiTest {
130 public:
RuntimeAPIUpdateTest()131 RuntimeAPIUpdateTest() {}
132
133 protected:
SetUpOnMainThread()134 void SetUpOnMainThread() override {
135 ExtensionApiTest::SetUpOnMainThread();
136 EXPECT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
137 }
138
139 struct ExtensionCRXData {
140 std::string unpacked_relative_path;
141 base::FilePath crx_path;
ExtensionCRXDataextensions::__anonc9de17110111::RuntimeAPIUpdateTest::ExtensionCRXData142 explicit ExtensionCRXData(const std::string& unpacked_relative_path)
143 : unpacked_relative_path(unpacked_relative_path) {}
144 };
145
SetUpCRX(const std::string & root_dir,const std::string & pem_filename,std::vector<ExtensionCRXData> * crx_data_list)146 void SetUpCRX(const std::string& root_dir,
147 const std::string& pem_filename,
148 std::vector<ExtensionCRXData>* crx_data_list) {
149 const base::FilePath test_dir = test_data_dir_.AppendASCII(root_dir);
150 const base::FilePath pem_path = test_dir.AppendASCII(pem_filename);
151 for (ExtensionCRXData& crx_data : *crx_data_list) {
152 crx_data.crx_path = PackExtensionWithOptions(
153 test_dir.AppendASCII(crx_data.unpacked_relative_path),
154 scoped_temp_dir_.GetPath().AppendASCII(
155 crx_data.unpacked_relative_path + ".crx"),
156 pem_path, base::FilePath());
157 }
158 }
159
CrashEnabledExtension(const std::string & extension_id)160 bool CrashEnabledExtension(const std::string& extension_id) {
161 ExtensionHost* background_host =
162 ProcessManager::Get(browser()->profile())
163 ->GetBackgroundHostForExtension(extension_id);
164 if (!background_host)
165 return false;
166 content::CrashTab(background_host->host_contents());
167 return true;
168 }
169
170 private:
171 base::ScopedTempDir scoped_temp_dir_;
172
173 DISALLOW_COPY_AND_ASSIGN(RuntimeAPIUpdateTest);
174 };
175
176 } // namespace
177
IN_PROC_BROWSER_TEST_F(ExtensionApiTest,ChromeRuntimeOpenOptionsPage)178 IN_PROC_BROWSER_TEST_F(ExtensionApiTest, ChromeRuntimeOpenOptionsPage) {
179 ASSERT_TRUE(RunExtensionTest("runtime/open_options_page"));
180 }
181
IN_PROC_BROWSER_TEST_F(ExtensionApiTest,ChromeRuntimeOpenOptionsPageError)182 IN_PROC_BROWSER_TEST_F(ExtensionApiTest, ChromeRuntimeOpenOptionsPageError) {
183 ASSERT_TRUE(RunExtensionTest("runtime/open_options_page_error"));
184 }
185
IN_PROC_BROWSER_TEST_F(ExtensionApiTest,ChromeRuntimeGetPlatformInfo)186 IN_PROC_BROWSER_TEST_F(ExtensionApiTest, ChromeRuntimeGetPlatformInfo) {
187 std::unique_ptr<base::Value> result(
188 extension_function_test_utils::RunFunctionAndReturnSingleResult(
189 new RuntimeGetPlatformInfoFunction(), "[]", browser()));
190 ASSERT_TRUE(result.get() != NULL);
191 base::DictionaryValue* dict =
192 extension_function_test_utils::ToDictionary(result.get());
193 ASSERT_TRUE(dict != NULL);
194 EXPECT_TRUE(dict->HasKey("os"));
195 EXPECT_TRUE(dict->HasKey("arch"));
196 EXPECT_TRUE(dict->HasKey("nacl_arch"));
197 }
198
199 // Tests chrome.runtime.getPackageDirectory with an app.
IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest,ChromeRuntimeGetPackageDirectoryEntryApp)200 IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest,
201 ChromeRuntimeGetPackageDirectoryEntryApp) {
202 ASSERT_TRUE(RunPlatformAppTest("api_test/runtime/get_package_directory/app"))
203 << message_;
204 }
205
206 // Tests chrome.runtime.getPackageDirectory with an extension.
IN_PROC_BROWSER_TEST_F(ExtensionApiTest,ChromeRuntimeGetPackageDirectoryEntryExtension)207 IN_PROC_BROWSER_TEST_F(ExtensionApiTest,
208 ChromeRuntimeGetPackageDirectoryEntryExtension) {
209 ASSERT_TRUE(RunExtensionTest("runtime/get_package_directory/extension"))
210 << message_;
211 }
212
213 // Tests that an extension calling chrome.runtime.reload() repeatedly
214 // will eventually be terminated.
IN_PROC_BROWSER_TEST_F(ExtensionApiTest,ExtensionTerminatedForRapidReloads)215 IN_PROC_BROWSER_TEST_F(ExtensionApiTest, ExtensionTerminatedForRapidReloads) {
216 ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
217 static constexpr char kManifest[] = R"(
218 {
219 "name": "reload",
220 "version": "1.0",
221 "background": {
222 "scripts": ["background.js"]
223 },
224 "manifest_version": 2
225 })";
226
227 TestExtensionDir dir;
228 dir.WriteManifest(kManifest);
229 dir.WriteFile(FILE_PATH_LITERAL("background.js"),
230 "chrome.test.sendMessage('ready');");
231
232 // Use a packed extension, since this is the scenario we are interested in
233 // testing. Unpacked extensions are allowed more reloads within the allotted
234 // time, to avoid interfering with the developer work flow.
235 const Extension* extension = LoadExtension(dir.Pack());
236 ASSERT_TRUE(extension);
237 const std::string extension_id = extension->id();
238
239 // The current limit for fast reload is 5, so the loop limit of 10
240 // be enough to trigger termination. If the extension manages to
241 // reload itself that often without being terminated, the test fails
242 // anyway.
243 for (int i = 0; i < RuntimeAPI::kFastReloadCount + 1; i++) {
244 ExtensionTestMessageListener ready_listener_reload("ready", false);
245 TestExtensionRegistryObserver unload_observer(registry, extension_id);
246 ASSERT_TRUE(ExecuteScriptInBackgroundPageNoWait(
247 extension_id, "chrome.runtime.reload();"));
248 unload_observer.WaitForExtensionUnloaded();
249 base::RunLoop().RunUntilIdle();
250
251 if (registry->GetExtensionById(extension_id,
252 ExtensionRegistry::TERMINATED)) {
253 break;
254 } else {
255 EXPECT_TRUE(ready_listener_reload.WaitUntilSatisfied());
256 }
257 }
258 ASSERT_TRUE(
259 registry->GetExtensionById(extension_id, ExtensionRegistry::TERMINATED));
260 }
261
262 // Tests chrome.runtime.reload
IN_PROC_BROWSER_TEST_F(ExtensionApiTest,ChromeRuntimeReload)263 IN_PROC_BROWSER_TEST_F(ExtensionApiTest, ChromeRuntimeReload) {
264 static constexpr char kManifest[] = R"(
265 {
266 "name": "reload",
267 "version": "1.0",
268 "background": {
269 "scripts": ["background.js"]
270 },
271 "manifest_version": 2
272 })";
273
274 static constexpr char kScript[] = R"(
275 chrome.test.sendMessage('ready', function(response) {
276 if (response == 'reload') {
277 chrome.runtime.reload();
278 } else if (response == 'done') {
279 chrome.test.notifyPass();
280 }
281 });
282 )";
283
284 TestExtensionDir dir;
285 dir.WriteManifest(kManifest);
286 dir.WriteFile(FILE_PATH_LITERAL("background.js"), kScript);
287
288 // This listener will respond to the initial load of the extension
289 // and tell the script to do the reload.
290 ExtensionTestMessageListener ready_listener_reload("ready", true);
291 const Extension* extension = LoadExtension(dir.UnpackedPath());
292 ASSERT_TRUE(extension);
293 const std::string extension_id = extension->id();
294 EXPECT_TRUE(ready_listener_reload.WaitUntilSatisfied());
295
296 // This listener will respond to the ready message from the
297 // reloaded extension and tell the script to finish the test.
298 ExtensionTestMessageListener ready_listener_done("ready", true);
299 ResultCatcher reload_catcher;
300 ready_listener_reload.Reply("reload");
301 EXPECT_TRUE(ready_listener_done.WaitUntilSatisfied());
302 ready_listener_done.Reply("done");
303 EXPECT_TRUE(reload_catcher.GetNextResult());
304 }
305
306 // Tests that updating a terminated extension sends runtime.onInstalled event
307 // with correct previousVersion.
308 // Regression test for https://crbug.com/724563.
IN_PROC_BROWSER_TEST_F(RuntimeAPIUpdateTest,TerminatedExtensionUpdateHasCorrectPreviousVersion)309 IN_PROC_BROWSER_TEST_F(RuntimeAPIUpdateTest,
310 TerminatedExtensionUpdateHasCorrectPreviousVersion) {
311 std::vector<ExtensionCRXData> data;
312 data.emplace_back("v1");
313 data.emplace_back("v2");
314 SetUpCRX("runtime/update_terminated_extension", "pem.pem", &data);
315
316 ExtensionId extension_id;
317 {
318 // Install version 1 of the extension.
319 ResultCatcher catcher;
320 const int expected_change = 1;
321 const Extension* extension_v1 =
322 InstallExtension(data[0].crx_path, expected_change);
323 extension_id = extension_v1->id();
324 ASSERT_TRUE(extension_v1);
325 EXPECT_TRUE(catcher.GetNextResult());
326 }
327
328 ASSERT_TRUE(CrashEnabledExtension(extension_id));
329
330 // The process-terminated notification may be received immediately before
331 // the task that will actually update the active-extensions count, so spin
332 // the message loop to ensure we are up-to-date.
333 base::RunLoop().RunUntilIdle();
334
335 {
336 // Update to version 2, expect runtime.onInstalled with
337 // previousVersion = '1'.
338 ResultCatcher catcher;
339 const int expected_change = 1;
340 const Extension* extension_v2 =
341 UpdateExtension(extension_id, data[1].crx_path, expected_change);
342 ASSERT_TRUE(extension_v2);
343 EXPECT_TRUE(catcher.GetNextResult());
344 }
345 }
346
347 // Tests that when a blocklisted extension with a set uninstall url is
348 // uninstalled, its uninstall url does not open.
IN_PROC_BROWSER_TEST_P(RuntimeApiTest,DoNotOpenUninstallUrlForBlocklistedExtensions)349 IN_PROC_BROWSER_TEST_P(RuntimeApiTest,
350 DoNotOpenUninstallUrlForBlocklistedExtensions) {
351 ExtensionTestMessageListener ready_listener("ready", false);
352 // Load an extension that has set an uninstall url.
353 scoped_refptr<const extensions::Extension> extension =
354 LoadExtensionWithParamFlag(test_data_dir_.AppendASCII("runtime")
355 .AppendASCII("uninstall_url")
356 .AppendASCII("sets_uninstall_url"));
357 EXPECT_TRUE(ready_listener.WaitUntilSatisfied());
358 ASSERT_TRUE(extension.get());
359 extension_service()->AddExtension(extension.get());
360 ASSERT_TRUE(extension_service()->IsExtensionEnabled(extension->id()));
361
362 // Uninstall the extension and expect its uninstall url to open.
363 extension_service()->UninstallExtension(
364 extension->id(), extensions::UNINSTALL_REASON_USER_INITIATED, NULL);
365 TabStripModel* tabs = browser()->tab_strip_model();
366
367 EXPECT_EQ(2, tabs->count());
368 content::WaitForLoadStop(tabs->GetActiveWebContents());
369 // Verify the uninstall url
370 EXPECT_EQ(kUninstallUrl, GetActiveUrl(browser()));
371
372 // Close the tab pointing to the uninstall url.
373 tabs->CloseWebContentsAt(tabs->active_index(), 0);
374 EXPECT_EQ(1, tabs->count());
375 EXPECT_EQ("about:blank", GetActiveUrl(browser()));
376
377 // Load the same extension again, except blocklist it after installation.
378 ExtensionTestMessageListener ready_listener_reload("ready", false);
379 extension =
380 LoadExtensionWithParamFlag(test_data_dir_.AppendASCII("runtime")
381 .AppendASCII("uninstall_url")
382 .AppendASCII("sets_uninstall_url"));
383 EXPECT_TRUE(ready_listener_reload.WaitUntilSatisfied());
384 extension_service()->AddExtension(extension.get());
385 ASSERT_TRUE(extension_service()->IsExtensionEnabled(extension->id()));
386
387 // Blocklist extension.
388 extensions::ExtensionPrefs::Get(profile())->SetExtensionBlocklistState(
389 extension->id(), extensions::BlocklistState::BLOCKLISTED_MALWARE);
390
391 // Uninstalling a blocklisted extension should not open its uninstall url.
392 TestExtensionRegistryObserver observer(ExtensionRegistry::Get(profile()),
393 extension->id());
394 extension_service()->UninstallExtension(
395 extension->id(), extensions::UNINSTALL_REASON_USER_INITIATED, NULL);
396 observer.WaitForExtensionUninstalled();
397
398 EXPECT_EQ(1, tabs->count());
399 EXPECT_TRUE(content::WaitForLoadStop(tabs->GetActiveWebContents()));
400 EXPECT_EQ(url::kAboutBlankURL, GetActiveUrl(browser()));
401 }
402
403 } // namespace extensions
404