active_script_controller_browsertest.cc revision 5f1c94371a64b3196d4be9466099bb892df9b88e
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 "base/files/file_path.h" 6#include "base/macros.h" 7#include "base/strings/stringprintf.h" 8#include "chrome/browser/extensions/active_script_controller.h" 9#include "chrome/browser/extensions/extension_action.h" 10#include "chrome/browser/extensions/extension_browsertest.h" 11#include "chrome/browser/extensions/extension_test_message_listener.h" 12#include "chrome/browser/extensions/location_bar_controller.h" 13#include "chrome/browser/extensions/tab_helper.h" 14#include "chrome/browser/extensions/test_extension_dir.h" 15#include "chrome/browser/ui/browser.h" 16#include "chrome/browser/ui/tabs/tab_strip_model.h" 17#include "chrome/test/base/ui_test_utils.h" 18#include "content/public/test/browser_test_utils.h" 19#include "extensions/common/feature_switch.h" 20#include "extensions/common/switches.h" 21#include "net/test/embedded_test_server/embedded_test_server.h" 22#include "testing/gtest/include/gtest/gtest.h" 23 24namespace extensions { 25 26namespace { 27 28const char kAllHostsScheme[] = "*://*/*"; 29const char kExplicitHostsScheme[] = "http://127.0.0.1/*"; 30const char kBackgroundScript[] = 31 "\"background\": {\"scripts\": [\"script.js\"]}"; 32const char kBackgroundScriptSource[] = 33 "var listener = function(tabId) {\n" 34 " chrome.tabs.onUpdated.removeListener(listener);\n" 35 " chrome.tabs.executeScript(tabId, {\n" 36 " code: \"chrome.test.sendMessage('inject succeeded');\"\n" 37 " });" 38 "};\n" 39 "chrome.tabs.onUpdated.addListener(listener);"; 40const char kContentScriptSource[] = 41 "chrome.test.sendMessage('inject succeeded');"; 42 43const char kInjectSucceeded[] = "inject succeeded"; 44 45enum InjectionType { 46 CONTENT_SCRIPT, 47 EXECUTE_SCRIPT 48}; 49 50enum HostType { 51 ALL_HOSTS, 52 EXPLICIT_HOSTS 53}; 54 55enum RequiresConsent { 56 REQUIRES_CONSENT, 57 DOES_NOT_REQUIRE_CONSENT 58}; 59 60// Runs all pending tasks in the renderer associated with |web_contents|. 61// Returns true on success. 62bool RunAllPendingInRenderer(content::WebContents* web_contents) { 63 // This is slight hack to achieve a RunPendingInRenderer() method. Since IPCs 64 // are sent synchronously, anything started prior to this method will finish 65 // before this method returns (as content::ExecuteScript() is synchronous). 66 return content::ExecuteScript(web_contents, "1 == 1;"); 67} 68 69} // namespace 70 71class ActiveScriptControllerBrowserTest : public ExtensionBrowserTest { 72 public: 73 ActiveScriptControllerBrowserTest() {} 74 75 virtual void SetUpCommandLine(base::CommandLine* command_line) OVERRIDE; 76 virtual void TearDownOnMainThread() OVERRIDE; 77 78 // Returns an extension with the given |host_type| and |injection_type|. If 79 // one already exists, the existing extension will be returned. Othewrwise, 80 // one will be created. 81 // This could potentially return NULL if LoadExtension() fails. 82 const Extension* CreateExtension(HostType host_type, 83 InjectionType injection_type); 84 85 private: 86 ScopedVector<TestExtensionDir> test_extension_dirs_; 87 std::vector<const Extension*> extensions_; 88}; 89 90void ActiveScriptControllerBrowserTest::SetUpCommandLine( 91 base::CommandLine* command_line) { 92 ExtensionBrowserTest::SetUpCommandLine(command_line); 93 // We append the actual switch to the commandline because it needs to be 94 // passed over to the renderer, which a FeatureSwitch::ScopedOverride will 95 // not do. 96 command_line->AppendSwitch(switches::kEnableScriptsRequireAction); 97} 98 99void ActiveScriptControllerBrowserTest::TearDownOnMainThread() { 100 test_extension_dirs_.clear(); 101} 102 103const Extension* ActiveScriptControllerBrowserTest::CreateExtension( 104 HostType host_type, InjectionType injection_type) { 105 std::string name = 106 base::StringPrintf( 107 "%s %s", 108 injection_type == CONTENT_SCRIPT ? 109 "content_script" : "execute_script", 110 host_type == ALL_HOSTS ? "all_hosts" : "explicit_hosts"); 111 112 const char* permission_scheme = 113 host_type == ALL_HOSTS ? kAllHostsScheme : kExplicitHostsScheme; 114 115 std::string permissions = base::StringPrintf( 116 "\"permissions\": [\"tabs\", \"%s\"]", permission_scheme); 117 118 std::string scripts; 119 std::string script_source; 120 if (injection_type == CONTENT_SCRIPT) { 121 scripts = base::StringPrintf( 122 "\"content_scripts\": [" 123 " {" 124 " \"matches\": [\"%s\"]," 125 " \"js\": [\"script.js\"]," 126 " \"run_at\": \"document_start\"" 127 " }" 128 "]", 129 permission_scheme); 130 } else { 131 scripts = kBackgroundScript; 132 } 133 134 std::string manifest = base::StringPrintf( 135 "{" 136 " \"name\": \"%s\"," 137 " \"version\": \"1.0\"," 138 " \"manifest_version\": 2," 139 " %s," 140 " %s" 141 "}", 142 name.c_str(), 143 permissions.c_str(), 144 scripts.c_str()); 145 146 scoped_ptr<TestExtensionDir> dir(new TestExtensionDir); 147 dir->WriteManifest(manifest); 148 dir->WriteFile(FILE_PATH_LITERAL("script.js"), 149 injection_type == CONTENT_SCRIPT ? kContentScriptSource : 150 kBackgroundScriptSource); 151 152 const Extension* extension = LoadExtension(dir->unpacked_path()); 153 if (extension) { 154 test_extension_dirs_.push_back(dir.release()); 155 extensions_.push_back(extension); 156 } 157 158 // If extension is NULL here, it will be caught later in the test. 159 return extension; 160} 161 162class ActiveScriptTester { 163 public: 164 ActiveScriptTester(const std::string& name, 165 const Extension* extension, 166 Browser* browser, 167 RequiresConsent requires_consent, 168 InjectionType type); 169 ~ActiveScriptTester(); 170 171 testing::AssertionResult Verify(); 172 173 private: 174 // Returns the location bar controller, or NULL if one does not exist. 175 LocationBarController* GetLocationBarController(); 176 177 // Returns the active script controller, or NULL if one does not exist. 178 ActiveScriptController* GetActiveScriptController(); 179 180 // Get the ExtensionAction for this extension, or NULL if one does not exist. 181 ExtensionAction* GetAction(); 182 183 // The name of the extension, and also the message it sends. 184 std::string name_; 185 186 // The extension associated with this tester. 187 const Extension* extension_; 188 189 // The browser the tester is running in. 190 Browser* browser_; 191 192 // Whether or not the extension has permission to run the script without 193 // asking the user. 194 RequiresConsent requires_consent_; 195 196 // The type of injection this tester uses. 197 InjectionType type_; 198 199 // All of these extensions should inject a script (either through content 200 // scripts or through chrome.tabs.executeScript()) that sends a message with 201 // the |kInjectSucceeded| message. 202 linked_ptr<ExtensionTestMessageListener> inject_success_listener_; 203}; 204 205ActiveScriptTester::ActiveScriptTester(const std::string& name, 206 const Extension* extension, 207 Browser* browser, 208 RequiresConsent requires_consent, 209 InjectionType type) 210 : name_(name), 211 extension_(extension), 212 browser_(browser), 213 requires_consent_(requires_consent), 214 type_(type), 215 inject_success_listener_( 216 new ExtensionTestMessageListener(kInjectSucceeded, 217 false /* won't reply */)) { 218 inject_success_listener_->set_extension_id(extension->id()); 219} 220 221ActiveScriptTester::~ActiveScriptTester() { 222} 223 224testing::AssertionResult ActiveScriptTester::Verify() { 225 if (!extension_) 226 return testing::AssertionFailure() << "Could not load extension: " << name_; 227 228 content::WebContents* web_contents = 229 browser_ ? browser_->tab_strip_model()->GetActiveWebContents() : NULL; 230 if (!web_contents) 231 return testing::AssertionFailure() << "No web contents."; 232 233 // Give the extension plenty of time to inject. 234 if (!RunAllPendingInRenderer(web_contents)) 235 return testing::AssertionFailure() << "Could not run pending in renderer."; 236 237 // Make sure all running tasks are complete. 238 content::RunAllPendingInMessageLoop(); 239 240 LocationBarController* location_bar_controller = GetLocationBarController(); 241 if (!location_bar_controller) { 242 return testing::AssertionFailure() 243 << "Could not find location bar controller"; 244 } 245 246 ActiveScriptController* controller = 247 location_bar_controller->active_script_controller(); 248 if (!controller) 249 return testing::AssertionFailure() << "Could not find controller."; 250 251 ExtensionAction* action = GetAction(); 252 bool has_action = action != NULL; 253 254 // An extension should have an action displayed iff it requires user consent. 255 if ((requires_consent_ == REQUIRES_CONSENT && !has_action) || 256 (requires_consent_ == DOES_NOT_REQUIRE_CONSENT && has_action)) { 257 return testing::AssertionFailure() 258 << "Improper action status for " << name_ << ": expected " 259 << (requires_consent_ == REQUIRES_CONSENT) << ", found " << has_action; 260 } 261 262 // If the extension has permission, we should be able to simply wait for it 263 // to execute. 264 if (requires_consent_ == DOES_NOT_REQUIRE_CONSENT) { 265 inject_success_listener_->WaitUntilSatisfied(); 266 return testing::AssertionSuccess(); 267 } 268 269 // Otherwise, we don't have permission, and have to grant it. Ensure the 270 // script has *not* already executed. 271 if (inject_success_listener_->was_satisfied()) { 272 return testing::AssertionFailure() << 273 name_ << "'s script ran without permission."; 274 } 275 276 // If we reach this point, we should always have an action. 277 DCHECK(action); 278 279 // Grant permission by clicking on the extension action. 280 location_bar_controller->OnClicked(action); 281 282 // Now, the extension should be able to inject the script. 283 inject_success_listener_->WaitUntilSatisfied(); 284 285 // The Action should have disappeared. 286 has_action = GetAction() != NULL; 287 if (has_action) { 288 return testing::AssertionFailure() 289 << "Extension " << name_ << " has lingering action."; 290 } 291 292 return testing::AssertionSuccess(); 293} 294 295LocationBarController* ActiveScriptTester::GetLocationBarController() { 296 content::WebContents* web_contents = 297 browser_ ? browser_->tab_strip_model()->GetActiveWebContents() : NULL; 298 299 if (!web_contents) 300 return NULL; 301 302 TabHelper* tab_helper = TabHelper::FromWebContents(web_contents); 303 return tab_helper ? tab_helper->location_bar_controller() : NULL; 304} 305 306ActiveScriptController* ActiveScriptTester::GetActiveScriptController() { 307 LocationBarController* location_bar_controller = GetLocationBarController(); 308 return location_bar_controller ? 309 location_bar_controller->active_script_controller() : NULL; 310} 311 312ExtensionAction* ActiveScriptTester::GetAction() { 313 ActiveScriptController* controller = GetActiveScriptController(); 314 return controller ? controller->GetActionForExtension(extension_) : NULL; 315} 316 317IN_PROC_BROWSER_TEST_F(ActiveScriptControllerBrowserTest, 318 ActiveScriptsAreDisplayedAndDelayExecution) { 319 base::FilePath active_script_path = 320 test_data_dir_.AppendASCII("active_script"); 321 322 const char* kExtensionNames[] = { 323 "inject_scripts_all_hosts", 324 "inject_scripts_explicit_hosts", 325 "content_scripts_all_hosts", 326 "content_scripts_explicit_hosts" 327 }; 328 329 // First, we load up three extensions: 330 // - An extension that injects scripts into all hosts, 331 // - An extension that injects scripts into explicit hosts, 332 // - An extension with a content script that runs on all hosts, 333 // - An extension with a content script that runs on explicit hosts. 334 // The extensions that operate on explicit hosts have permission; the ones 335 // that request all hosts require user consent. 336 ActiveScriptTester testers[] = { 337 ActiveScriptTester( 338 kExtensionNames[0], 339 CreateExtension(ALL_HOSTS, EXECUTE_SCRIPT), 340 browser(), 341 REQUIRES_CONSENT, 342 EXECUTE_SCRIPT), 343 ActiveScriptTester( 344 kExtensionNames[1], 345 CreateExtension(EXPLICIT_HOSTS, EXECUTE_SCRIPT), 346 browser(), 347 DOES_NOT_REQUIRE_CONSENT, 348 EXECUTE_SCRIPT), 349 ActiveScriptTester( 350 kExtensionNames[2], 351 CreateExtension(ALL_HOSTS, CONTENT_SCRIPT), 352 browser(), 353 REQUIRES_CONSENT, 354 CONTENT_SCRIPT), 355 ActiveScriptTester( 356 kExtensionNames[3], 357 CreateExtension(EXPLICIT_HOSTS, CONTENT_SCRIPT), 358 browser(), 359 DOES_NOT_REQUIRE_CONSENT, 360 CONTENT_SCRIPT), 361 }; 362 363 // Navigate to an URL (which matches the explicit host specified in the 364 // extension content_scripts_explicit_hosts). All four extensions should 365 // inject the script. 366 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 367 ui_test_utils::NavigateToURL( 368 browser(), embedded_test_server()->GetURL("/extensions/test_file.html")); 369 370 for (size_t i = 0u; i < arraysize(testers); ++i) 371 EXPECT_TRUE(testers[i].Verify()) << kExtensionNames[i]; 372} 373 374// Test that removing an extension with pending injections a) removes the 375// pending injections for that extension, and b) does not affect pending 376// injections for other extensions. 377IN_PROC_BROWSER_TEST_F(ActiveScriptControllerBrowserTest, 378 RemoveExtensionWithPendingInjections) { 379 // Load up two extensions, each with content scripts. 380 const Extension* extension1 = CreateExtension(ALL_HOSTS, CONTENT_SCRIPT); 381 ASSERT_TRUE(extension1); 382 const Extension* extension2 = CreateExtension(ALL_HOSTS, CONTENT_SCRIPT); 383 ASSERT_TRUE(extension2); 384 385 ASSERT_NE(extension1->id(), extension2->id()); 386 387 content::WebContents* web_contents = 388 browser()->tab_strip_model()->GetActiveWebContents(); 389 ASSERT_TRUE(web_contents); 390 ActiveScriptController* active_script_controller = 391 ActiveScriptController::GetForWebContents(web_contents); 392 ASSERT_TRUE(active_script_controller); 393 394 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 395 ui_test_utils::NavigateToURL( 396 browser(), embedded_test_server()->GetURL("/extensions/test_file.html")); 397 398 // Both extensions should have pending requests. 399 EXPECT_TRUE(active_script_controller->GetActionForExtension(extension1)); 400 EXPECT_TRUE(active_script_controller->GetActionForExtension(extension2)); 401 402 // Unload one of the extensions. 403 UnloadExtension(extension2->id()); 404 405 EXPECT_TRUE(RunAllPendingInRenderer(web_contents)); 406 407 // We should have pending requests for extension1, but not the removed 408 // extension2. 409 EXPECT_TRUE(active_script_controller->GetActionForExtension(extension1)); 410 EXPECT_FALSE(active_script_controller->GetActionForExtension(extension2)); 411 412 // We should still be able to run the request for extension1. 413 ExtensionTestMessageListener inject_success_listener( 414 new ExtensionTestMessageListener(kInjectSucceeded, 415 false /* won't reply */)); 416 inject_success_listener.set_extension_id(extension1->id()); 417 active_script_controller->OnClicked(extension1); 418 inject_success_listener.WaitUntilSatisfied(); 419} 420 421// A version of the test with the flag off, in order to test that everything 422// still works as expected. 423class FlagOffActiveScriptControllerBrowserTest 424 : public ActiveScriptControllerBrowserTest { 425 private: 426 // Simply don't append the flag. 427 virtual void SetUpCommandLine(base::CommandLine* command_line) OVERRIDE { 428 ExtensionBrowserTest::SetUpCommandLine(command_line); 429 } 430}; 431 432IN_PROC_BROWSER_TEST_F(FlagOffActiveScriptControllerBrowserTest, 433 ScriptsExecuteWhenFlagAbsent) { 434 const char* kExtensionNames[] = { 435 "content_scripts_all_hosts", 436 "inject_scripts_all_hosts", 437 }; 438 ActiveScriptTester testers[] = { 439 ActiveScriptTester( 440 kExtensionNames[0], 441 CreateExtension(ALL_HOSTS, CONTENT_SCRIPT), 442 browser(), 443 DOES_NOT_REQUIRE_CONSENT, 444 CONTENT_SCRIPT), 445 ActiveScriptTester( 446 kExtensionNames[1], 447 CreateExtension(ALL_HOSTS, EXECUTE_SCRIPT), 448 browser(), 449 DOES_NOT_REQUIRE_CONSENT, 450 EXECUTE_SCRIPT), 451 }; 452 453 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 454 ui_test_utils::NavigateToURL( 455 browser(), embedded_test_server()->GetURL("/extensions/test_file.html")); 456 457 for (size_t i = 0u; i < arraysize(testers); ++i) 458 EXPECT_TRUE(testers[i].Verify()) << kExtensionNames[i]; 459} 460 461} // namespace extensions 462