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