[email protected] | bcabac76 | 2013-05-29 23:33:24 | [diff] [blame] | 1 | // Copyright 2013 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 | // This file contains tests for extension loading, reloading, and |
| 6 | // unloading behavior. |
| 7 | |
[email protected] | bcabac76 | 2013-05-29 23:33:24 | [diff] [blame] | 8 | #include "base/run_loop.h" |
[email protected] | 4458084 | 2013-07-09 21:36:53 | [diff] [blame] | 9 | #include "base/strings/stringprintf.h" |
[email protected] | ca97594 | 2014-01-07 12:06:47 | [diff] [blame] | 10 | #include "base/version.h" |
avi | a2f4804a | 2015-12-24 23:11:13 | [diff] [blame] | 11 | #include "build/build_config.h" |
rockot | a34fed22 | 2015-09-25 21:56:31 | [diff] [blame] | 12 | #include "chrome/browser/extensions/devtools_util.h" |
[email protected] | bcabac76 | 2013-05-29 23:33:24 | [diff] [blame] | 13 | #include "chrome/browser/extensions/extension_browsertest.h" |
rockot | a34fed22 | 2015-09-25 21:56:31 | [diff] [blame] | 14 | #include "chrome/browser/extensions/extension_service.h" |
[email protected] | 4458084 | 2013-07-09 21:36:53 | [diff] [blame] | 15 | #include "chrome/browser/extensions/test_extension_dir.h" |
[email protected] | ca97594 | 2014-01-07 12:06:47 | [diff] [blame] | 16 | #include "chrome/browser/profiles/profile.h" |
[email protected] | bcabac76 | 2013-05-29 23:33:24 | [diff] [blame] | 17 | #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| 18 | #include "chrome/test/base/in_process_browser_test.h" |
| 19 | #include "chrome/test/base/ui_test_utils.h" |
rockot | a34fed22 | 2015-09-25 21:56:31 | [diff] [blame] | 20 | #include "content/public/browser/devtools_agent_host.h" |
rob | 1166ab3 | 2016-02-29 20:26:02 | [diff] [blame] | 21 | #include "content/public/test/browser_test_utils.h" |
[email protected] | ca97594 | 2014-01-07 12:06:47 | [diff] [blame] | 22 | #include "extensions/browser/extension_registry.h" |
rockot | a34fed22 | 2015-09-25 21:56:31 | [diff] [blame] | 23 | #include "extensions/browser/process_manager.h" |
robertshield | 9cf4246 | 2017-07-19 23:44:08 | [diff] [blame^] | 24 | #include "extensions/common/permissions/permissions_data.h" |
rob | 1166ab3 | 2016-02-29 20:26:02 | [diff] [blame] | 25 | #include "extensions/test/extension_test_message_listener.h" |
[email protected] | bcabac76 | 2013-05-29 23:33:24 | [diff] [blame] | 26 | #include "net/test/embedded_test_server/embedded_test_server.h" |
| 27 | #include "testing/gmock/include/gmock/gmock.h" |
| 28 | |
wychen | 7b07e7b | 2017-01-10 17:48:29 | [diff] [blame] | 29 | #if defined(OS_WIN) |
| 30 | #include "base/win/windows_version.h" |
| 31 | #endif |
| 32 | |
[email protected] | bcabac76 | 2013-05-29 23:33:24 | [diff] [blame] | 33 | namespace extensions { |
| 34 | namespace { |
| 35 | |
[email protected] | bcabac76 | 2013-05-29 23:33:24 | [diff] [blame] | 36 | class ExtensionLoadingTest : public ExtensionBrowserTest { |
| 37 | }; |
| 38 | |
| 39 | // Check the fix for http://crbug.com/178542. |
| 40 | IN_PROC_BROWSER_TEST_F(ExtensionLoadingTest, |
| 41 | UpgradeAfterNavigatingFromOverriddenNewTabPage) { |
| 42 | embedded_test_server()->ServeFilesFromDirectory( |
| 43 | base::FilePath(FILE_PATH_LITERAL("chrome/test/data"))); |
svaldez | a01f7d9 | 2015-11-18 17:47:56 | [diff] [blame] | 44 | ASSERT_TRUE(embedded_test_server()->Start()); |
[email protected] | bcabac76 | 2013-05-29 23:33:24 | [diff] [blame] | 45 | |
| 46 | TestExtensionDir extension_dir; |
robertshield | 9cf4246 | 2017-07-19 23:44:08 | [diff] [blame^] | 47 | const char kManifestTemplate[] = |
[email protected] | 4458084 | 2013-07-09 21:36:53 | [diff] [blame] | 48 | "{" |
rockot | a34fed22 | 2015-09-25 21:56:31 | [diff] [blame] | 49 | " 'name': 'Overrides New Tab'," |
| 50 | " 'version': '%d'," |
| 51 | " 'description': 'Overrides New Tab'," |
| 52 | " 'manifest_version': 2," |
| 53 | " 'background': {" |
| 54 | " 'persistent': false," |
| 55 | " 'scripts': ['event.js']" |
[email protected] | 4458084 | 2013-07-09 21:36:53 | [diff] [blame] | 56 | " }," |
rockot | a34fed22 | 2015-09-25 21:56:31 | [diff] [blame] | 57 | " 'chrome_url_overrides': {" |
| 58 | " 'newtab': 'newtab.html'" |
[email protected] | 4458084 | 2013-07-09 21:36:53 | [diff] [blame] | 59 | " }" |
| 60 | "}"; |
rockot | a34fed22 | 2015-09-25 21:56:31 | [diff] [blame] | 61 | extension_dir.WriteManifestWithSingleQuotes( |
robertshield | 9cf4246 | 2017-07-19 23:44:08 | [diff] [blame^] | 62 | base::StringPrintf(kManifestTemplate, 1)); |
[email protected] | bcabac76 | 2013-05-29 23:33:24 | [diff] [blame] | 63 | extension_dir.WriteFile(FILE_PATH_LITERAL("event.js"), ""); |
| 64 | extension_dir.WriteFile(FILE_PATH_LITERAL("newtab.html"), |
| 65 | "<h1>Overridden New Tab Page</h1>"); |
| 66 | |
| 67 | const Extension* new_tab_extension = |
| 68 | InstallExtension(extension_dir.Pack(), 1 /*new install*/); |
| 69 | ASSERT_TRUE(new_tab_extension); |
| 70 | |
| 71 | // Visit the New Tab Page to get a renderer using the extension into history. |
| 72 | ui_test_utils::NavigateToURL(browser(), GURL("chrome://newtab")); |
| 73 | |
| 74 | // Navigate that tab to a non-extension URL to swap out the extension's |
| 75 | // renderer. |
| 76 | const GURL test_link_from_NTP = |
| 77 | embedded_test_server()->GetURL("/README.chromium"); |
| 78 | EXPECT_THAT(test_link_from_NTP.spec(), testing::EndsWith("/README.chromium")) |
| 79 | << "Check that the test server started."; |
| 80 | NavigateInRenderer(browser()->tab_strip_model()->GetActiveWebContents(), |
| 81 | test_link_from_NTP); |
| 82 | |
| 83 | // Increase the extension's version. |
rockot | a34fed22 | 2015-09-25 21:56:31 | [diff] [blame] | 84 | extension_dir.WriteManifestWithSingleQuotes( |
robertshield | 9cf4246 | 2017-07-19 23:44:08 | [diff] [blame^] | 85 | base::StringPrintf(kManifestTemplate, 2)); |
[email protected] | bcabac76 | 2013-05-29 23:33:24 | [diff] [blame] | 86 | |
| 87 | // Upgrade the extension. |
| 88 | new_tab_extension = UpdateExtension( |
| 89 | new_tab_extension->id(), extension_dir.Pack(), 0 /*expected upgrade*/); |
| 90 | EXPECT_THAT(new_tab_extension->version()->components(), |
| 91 | testing::ElementsAre(2)); |
| 92 | |
| 93 | // The extension takes a couple round-trips to the renderer in order |
| 94 | // to crash, so open a new tab to wait long enough. |
| 95 | AddTabAtIndex(browser()->tab_strip_model()->count(), |
| 96 | GURL("http://www.google.com/"), |
Sylvain Defresne | c6ccc77d | 2014-09-19 10:19:35 | [diff] [blame] | 97 | ui::PAGE_TRANSITION_TYPED); |
[email protected] | bcabac76 | 2013-05-29 23:33:24 | [diff] [blame] | 98 | |
| 99 | // Check that the extension hasn't crashed. |
[email protected] | ca97594 | 2014-01-07 12:06:47 | [diff] [blame] | 100 | ExtensionRegistry* registry = ExtensionRegistry::Get(profile()); |
| 101 | EXPECT_EQ(0U, registry->terminated_extensions().size()); |
| 102 | EXPECT_TRUE(registry->enabled_extensions().Contains(new_tab_extension->id())); |
[email protected] | bcabac76 | 2013-05-29 23:33:24 | [diff] [blame] | 103 | } |
| 104 | |
robertshield | 9cf4246 | 2017-07-19 23:44:08 | [diff] [blame^] | 105 | IN_PROC_BROWSER_TEST_F(ExtensionLoadingTest, |
| 106 | UpgradeAddingNewTabPagePermissionNoPrompt) { |
| 107 | embedded_test_server()->ServeFilesFromDirectory( |
| 108 | base::FilePath(FILE_PATH_LITERAL("chrome/test/data"))); |
| 109 | ASSERT_TRUE(embedded_test_server()->Start()); |
| 110 | |
| 111 | TestExtensionDir extension_dir; |
| 112 | const char kManifestTemplate[] = |
| 113 | "{" |
| 114 | " 'name': 'Overrides New Tab'," |
| 115 | " 'version': '%d'," |
| 116 | " 'description': 'Will override New Tab soon'," |
| 117 | " %s" // Placeholder for future NTP url override block. |
| 118 | " 'manifest_version': 2" |
| 119 | "}"; |
| 120 | extension_dir.WriteManifestWithSingleQuotes( |
| 121 | base::StringPrintf(kManifestTemplate, 1, "")); |
| 122 | extension_dir.WriteFile(FILE_PATH_LITERAL("event.js"), ""); |
| 123 | extension_dir.WriteFile(FILE_PATH_LITERAL("newtab.html"), |
| 124 | "<h1>Overridden New Tab Page</h1>"); |
| 125 | |
| 126 | const Extension* new_tab_extension = |
| 127 | InstallExtension(extension_dir.Pack(), 1 /*new install*/); |
| 128 | ASSERT_TRUE(new_tab_extension); |
| 129 | |
| 130 | EXPECT_FALSE(new_tab_extension->permissions_data()->HasAPIPermission( |
| 131 | APIPermission::kNewTabPageOverride)); |
| 132 | |
| 133 | // Navigate that tab to a non-extension URL to swap out the extension's |
| 134 | // renderer. |
| 135 | const GURL test_link_from_ntp = |
| 136 | embedded_test_server()->GetURL("/README.chromium"); |
| 137 | EXPECT_THAT(test_link_from_ntp.spec(), testing::EndsWith("/README.chromium")) |
| 138 | << "Check that the test server started."; |
| 139 | NavigateInRenderer(browser()->tab_strip_model()->GetActiveWebContents(), |
| 140 | test_link_from_ntp); |
| 141 | |
| 142 | // Increase the extension's version and add the NTP url override which will |
| 143 | // add the kNewTabPageOverride permission. |
| 144 | const char ntp_override_string[] = |
| 145 | " 'chrome_url_overrides': {" |
| 146 | " 'newtab': 'newtab.html'" |
| 147 | " },"; |
| 148 | extension_dir.WriteManifestWithSingleQuotes( |
| 149 | base::StringPrintf(kManifestTemplate, 2, ntp_override_string)); |
| 150 | |
| 151 | // Upgrade the extension, ensure that the upgrade 'worked' in the sense that |
| 152 | // the extension is still present and not disabled and that it now has the |
| 153 | // new API permission. |
| 154 | // TODO(robertshield): Update this once most of the population is on M62+ |
| 155 | // and adding NTP permissions implies a permission upgrade. |
| 156 | new_tab_extension = UpdateExtension( |
| 157 | new_tab_extension->id(), extension_dir.Pack(), 0 /*expected upgrade*/); |
| 158 | ASSERT_NE(nullptr, new_tab_extension); |
| 159 | |
| 160 | EXPECT_TRUE(new_tab_extension->permissions_data()->HasAPIPermission( |
| 161 | APIPermission::kNewTabPageOverride)); |
| 162 | EXPECT_THAT(new_tab_extension->version()->components(), |
| 163 | testing::ElementsAre(2)); |
| 164 | } |
| 165 | |
rockot | a34fed22 | 2015-09-25 21:56:31 | [diff] [blame] | 166 | // Tests the behavior described in http://crbug.com/532088. |
| 167 | IN_PROC_BROWSER_TEST_F(ExtensionLoadingTest, |
| 168 | KeepAliveWithDevToolsOpenOnReload) { |
| 169 | embedded_test_server()->ServeFilesFromDirectory( |
| 170 | base::FilePath(FILE_PATH_LITERAL("chrome/test/data"))); |
svaldez | a01f7d9 | 2015-11-18 17:47:56 | [diff] [blame] | 171 | ASSERT_TRUE(embedded_test_server()->Start()); |
rockot | a34fed22 | 2015-09-25 21:56:31 | [diff] [blame] | 172 | |
| 173 | TestExtensionDir extension_dir; |
| 174 | const char manifest_contents[] = |
| 175 | "{" |
| 176 | " 'name': 'Test With Lazy Background Page'," |
| 177 | " 'version': '0'," |
| 178 | " 'manifest_version': 2," |
| 179 | " 'app': {" |
| 180 | " 'background': {" |
| 181 | " 'scripts': ['event.js']" |
| 182 | " }" |
| 183 | " }" |
| 184 | "}"; |
| 185 | extension_dir.WriteManifestWithSingleQuotes(manifest_contents); |
| 186 | extension_dir.WriteFile(FILE_PATH_LITERAL("event.js"), ""); |
| 187 | |
| 188 | const Extension* extension = |
| 189 | InstallExtension(extension_dir.Pack(), 1 /*new install*/); |
| 190 | ASSERT_TRUE(extension); |
| 191 | std::string extension_id = extension->id(); |
| 192 | |
| 193 | ProcessManager* process_manager = ProcessManager::Get(profile()); |
| 194 | EXPECT_EQ(0, process_manager->GetLazyKeepaliveCount(extension)); |
| 195 | |
| 196 | devtools_util::InspectBackgroundPage(extension, profile()); |
| 197 | EXPECT_EQ(1, process_manager->GetLazyKeepaliveCount(extension)); |
| 198 | |
| 199 | // Opening DevTools will cause the background page to load. Wait for it. |
| 200 | WaitForExtensionViewsToLoad(); |
| 201 | |
| 202 | ReloadExtension(extension_id); |
| 203 | |
| 204 | // Flush the MessageLoop to ensure that DevTools has a chance to be reattached |
| 205 | // and the background page has a chance to begin reloading. |
| 206 | base::RunLoop().RunUntilIdle(); |
| 207 | |
| 208 | // And wait for the background page to finish loading again. |
| 209 | WaitForExtensionViewsToLoad(); |
| 210 | |
| 211 | // Ensure that our DevtoolsAgentHost is actually connected to the new |
| 212 | // background WebContents. |
| 213 | content::WebContents* background_contents = |
| 214 | process_manager->GetBackgroundHostForExtension(extension_id) |
| 215 | ->host_contents(); |
| 216 | EXPECT_TRUE(content::DevToolsAgentHost::HasFor(background_contents)); |
| 217 | |
| 218 | // The old Extension object is no longer valid. |
| 219 | extension = ExtensionRegistry::Get(profile()) |
| 220 | ->enabled_extensions().GetByID(extension_id); |
| 221 | |
| 222 | // Keepalive count should stabilize back to 1, because DevTools is still open. |
| 223 | EXPECT_EQ(1, process_manager->GetLazyKeepaliveCount(extension)); |
| 224 | } |
| 225 | |
rob | 1166ab3 | 2016-02-29 20:26:02 | [diff] [blame] | 226 | // Tests whether the extension runtime stays valid when an extension reloads |
| 227 | // while a devtools extension is hammering the frame with eval requests. |
| 228 | // Regression test for https://crbug.com/544182 |
| 229 | IN_PROC_BROWSER_TEST_F(ExtensionLoadingTest, RuntimeValidWhileDevToolsOpen) { |
| 230 | TestExtensionDir devtools_dir; |
| 231 | TestExtensionDir inspect_dir; |
| 232 | |
| 233 | const char kDevtoolsManifest[] = |
| 234 | "{" |
| 235 | " 'name': 'Devtools'," |
| 236 | " 'version': '1'," |
| 237 | " 'manifest_version': 2," |
| 238 | " 'devtools_page': 'devtools.html'" |
| 239 | "}"; |
| 240 | |
| 241 | const char kDevtoolsJs[] = |
| 242 | "setInterval(function() {" |
| 243 | " chrome.devtools.inspectedWindow.eval('1', function() {" |
| 244 | " });" |
| 245 | "}, 4);" |
| 246 | "chrome.test.sendMessage('devtools_page_ready');"; |
| 247 | |
| 248 | const char kTargetManifest[] = |
| 249 | "{" |
| 250 | " 'name': 'Inspect target'," |
| 251 | " 'version': '1'," |
| 252 | " 'manifest_version': 2," |
| 253 | " 'background': {" |
| 254 | " 'scripts': ['background.js']" |
| 255 | " }" |
| 256 | "}"; |
| 257 | |
| 258 | // A script to duck-type whether it runs in a background page. |
| 259 | const char kTargetJs[] = |
| 260 | "var is_valid = !!(chrome.tabs && chrome.tabs.create);"; |
| 261 | |
| 262 | devtools_dir.WriteManifestWithSingleQuotes(kDevtoolsManifest); |
| 263 | devtools_dir.WriteFile(FILE_PATH_LITERAL("devtools.js"), kDevtoolsJs); |
| 264 | devtools_dir.WriteFile(FILE_PATH_LITERAL("devtools.html"), |
| 265 | "<script src='devtools.js'></script>"); |
| 266 | |
| 267 | inspect_dir.WriteManifestWithSingleQuotes(kTargetManifest); |
| 268 | inspect_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kTargetJs); |
vabr | 9142fe2 | 2016-09-08 13:19:22 | [diff] [blame] | 269 | const Extension* devtools_ext = LoadExtension(devtools_dir.UnpackedPath()); |
rob | 1166ab3 | 2016-02-29 20:26:02 | [diff] [blame] | 270 | ASSERT_TRUE(devtools_ext); |
| 271 | |
vabr | 9142fe2 | 2016-09-08 13:19:22 | [diff] [blame] | 272 | const Extension* inspect_ext = LoadExtension(inspect_dir.UnpackedPath()); |
rob | 1166ab3 | 2016-02-29 20:26:02 | [diff] [blame] | 273 | ASSERT_TRUE(inspect_ext); |
| 274 | const std::string inspect_ext_id = inspect_ext->id(); |
| 275 | |
| 276 | // Open the devtools and wait until the devtools_page is ready. |
| 277 | ExtensionTestMessageListener devtools_ready("devtools_page_ready", false); |
| 278 | devtools_util::InspectBackgroundPage(inspect_ext, profile()); |
| 279 | ASSERT_TRUE(devtools_ready.WaitUntilSatisfied()); |
| 280 | |
| 281 | // Reload the extension. The devtools window will stay open, but temporarily |
| 282 | // be detached. As soon as the background is attached again, the devtools |
| 283 | // continues with spamming eval requests. |
| 284 | ReloadExtension(inspect_ext_id); |
| 285 | WaitForExtensionViewsToLoad(); |
| 286 | |
| 287 | content::WebContents* bg_contents = |
| 288 | ProcessManager::Get(profile()) |
| 289 | ->GetBackgroundHostForExtension(inspect_ext_id) |
| 290 | ->host_contents(); |
| 291 | ASSERT_TRUE(bg_contents); |
| 292 | |
| 293 | // Now check whether the extension runtime is valid (see kTargetJs). |
| 294 | bool is_valid = false; |
| 295 | ASSERT_TRUE(content::ExecuteScriptAndExtractBool( |
| 296 | bg_contents, "domAutomationController.send(is_valid);", &is_valid)); |
| 297 | EXPECT_TRUE(is_valid); |
| 298 | } |
| 299 | |
[email protected] | bcabac76 | 2013-05-29 23:33:24 | [diff] [blame] | 300 | } // namespace |
| 301 | } // namespace extensions |