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