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