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
24 namespace extensions {
25
26 namespace {
27
28 const char kAllHostsScheme[] = "*://*/*";
29 const char kExplicitHostsScheme[] = "http://127.0.0.1/*";
30 const char kBackgroundScript[] =
31 "\"background\": {\"scripts\": [\"script.js\"]}";
32 const 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 " chrome.test.sendMessage('inject attempted');\n"
39 "};\n"
40 "chrome.tabs.onUpdated.addListener(listener);";
41 const char kContentScriptSource[] =
42 "chrome.test.sendMessage('inject succeeded');";
43
44 const char kInjectAttempted[] = "inject attempted";
45 const char kInjectSucceeded[] = "inject succeeded";
46
47 enum InjectionType {
48 CONTENT_SCRIPT,
49 EXECUTE_SCRIPT
50 };
51
52 enum HostType {
53 ALL_HOSTS,
54 EXPLICIT_HOSTS
55 };
56
57 enum RequiresConsent {
58 REQUIRES_CONSENT,
59 DOES_NOT_REQUIRE_CONSENT
60 };
61
62 } // namespace
63
64 class ActiveScriptControllerBrowserTest : public ExtensionBrowserTest {
65 public:
ActiveScriptControllerBrowserTest()66 ActiveScriptControllerBrowserTest() {}
67
68 virtual void SetUpCommandLine(base::CommandLine* command_line) OVERRIDE;
69 virtual void CleanUpOnMainThread() OVERRIDE;
70
71 // Returns an extension with the given |host_type| and |injection_type|. If
72 // one already exists, the existing extension will be returned. Othewrwise,
73 // one will be created.
74 // This could potentially return NULL if LoadExtension() fails.
75 const Extension* CreateExtension(HostType host_type,
76 InjectionType injection_type);
77
78 private:
79 ScopedVector<TestExtensionDir> test_extension_dirs_;
80 std::vector<const Extension*> extensions_;
81 };
82
SetUpCommandLine(base::CommandLine * command_line)83 void ActiveScriptControllerBrowserTest::SetUpCommandLine(
84 base::CommandLine* command_line) {
85 ExtensionBrowserTest::SetUpCommandLine(command_line);
86 // We append the actual switch to the commandline because it needs to be
87 // passed over to the renderer, which a FeatureSwitch::ScopedOverride will
88 // not do.
89 command_line->AppendSwitch(switches::kEnableScriptsRequireAction);
90 }
91
CleanUpOnMainThread()92 void ActiveScriptControllerBrowserTest::CleanUpOnMainThread() {
93 test_extension_dirs_.clear();
94 }
95
CreateExtension(HostType host_type,InjectionType injection_type)96 const Extension* ActiveScriptControllerBrowserTest::CreateExtension(
97 HostType host_type, InjectionType injection_type) {
98 std::string name =
99 base::StringPrintf(
100 "%s %s",
101 injection_type == CONTENT_SCRIPT ?
102 "content_script" : "execute_script",
103 host_type == ALL_HOSTS ? "all_hosts" : "explicit_hosts");
104
105 const char* permission_scheme =
106 host_type == ALL_HOSTS ? kAllHostsScheme : kExplicitHostsScheme;
107
108 std::string permissions = base::StringPrintf(
109 "\"permissions\": [\"tabs\", \"%s\"]", permission_scheme);
110
111 std::string scripts;
112 std::string script_source;
113 if (injection_type == CONTENT_SCRIPT) {
114 scripts = base::StringPrintf(
115 "\"content_scripts\": ["
116 " {"
117 " \"matches\": [\"%s\"],"
118 " \"js\": [\"script.js\"],"
119 " \"run_at\": \"document_start\""
120 " }"
121 "]",
122 permission_scheme);
123 } else {
124 scripts = kBackgroundScript;
125 }
126
127 std::string manifest = base::StringPrintf(
128 "{"
129 " \"name\": \"%s\","
130 " \"version\": \"1.0\","
131 " \"manifest_version\": 2,"
132 " %s,"
133 " %s"
134 "}",
135 name.c_str(),
136 permissions.c_str(),
137 scripts.c_str());
138
139 scoped_ptr<TestExtensionDir> dir(new TestExtensionDir);
140 dir->WriteManifest(manifest);
141 dir->WriteFile(FILE_PATH_LITERAL("script.js"),
142 injection_type == CONTENT_SCRIPT ? kContentScriptSource :
143 kBackgroundScriptSource);
144
145 const Extension* extension = LoadExtension(dir->unpacked_path());
146 if (extension) {
147 test_extension_dirs_.push_back(dir.release());
148 extensions_.push_back(extension);
149 }
150
151 // If extension is NULL here, it will be caught later in the test.
152 return extension;
153 }
154
155 class ActiveScriptTester {
156 public:
157 ActiveScriptTester(const std::string& name,
158 const Extension* extension,
159 Browser* browser,
160 RequiresConsent requires_consent,
161 InjectionType type);
162 ~ActiveScriptTester();
163
164 testing::AssertionResult Verify();
165
166 private:
167 // Returns the location bar controller, or NULL if one does not exist.
168 LocationBarController* GetLocationBarController();
169
170 // Returns the active script controller, or NULL if one does not exist.
171 ActiveScriptController* GetActiveScriptController();
172
173 // Get the ExtensionAction for this extension, or NULL if one does not exist.
174 ExtensionAction* GetAction();
175
176 // The name of the extension, and also the message it sends.
177 std::string name_;
178
179 // The extension associated with this tester.
180 const Extension* extension_;
181
182 // The browser the tester is running in.
183 Browser* browser_;
184
185 // Whether or not the extension has permission to run the script without
186 // asking the user.
187 RequiresConsent requires_consent_;
188
189 // The type of injection this tester uses.
190 InjectionType type_;
191
192 // All of these extensions should inject a script (either through content
193 // scripts or through chrome.tabs.executeScript()) that sends a message with
194 // the |kInjectSucceeded| message.
195 linked_ptr<ExtensionTestMessageListener> inject_attempt_listener_;
196
197 // After trying to inject the script, extensions sending the script via
198 // chrome.tabs.executeScript() send a |kInjectAttempted| message.
199 linked_ptr<ExtensionTestMessageListener> inject_success_listener_;
200 };
201
ActiveScriptTester(const std::string & name,const Extension * extension,Browser * browser,RequiresConsent requires_consent,InjectionType type)202 ActiveScriptTester::ActiveScriptTester(const std::string& name,
203 const Extension* extension,
204 Browser* browser,
205 RequiresConsent requires_consent,
206 InjectionType type)
207 : name_(name),
208 extension_(extension),
209 browser_(browser),
210 requires_consent_(requires_consent),
211 type_(type),
212 inject_attempt_listener_(
213 new ExtensionTestMessageListener(kInjectAttempted,
214 false /* won't reply */)),
215 inject_success_listener_(
216 new ExtensionTestMessageListener(kInjectSucceeded,
217 false /* won't reply */)) {
218 inject_attempt_listener_->set_extension_id(extension->id());
219 inject_success_listener_->set_extension_id(extension->id());
220 }
221
~ActiveScriptTester()222 ActiveScriptTester::~ActiveScriptTester() {
223 }
224
Verify()225 testing::AssertionResult ActiveScriptTester::Verify() {
226 if (!extension_)
227 return testing::AssertionFailure() << "Could not load extension: " << name_;
228
229 // If it's not a content script, the Extension lets us know when it has
230 // attempted to inject the script.
231 // This means there is a potential for a race condition with content scripts;
232 // however, since they are all injected at document_start, this shouldn't be
233 // a problem. If these tests start failing, though, that might be it.
234 if (type_ == EXECUTE_SCRIPT)
235 inject_attempt_listener_->WaitUntilSatisfied();
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
GetLocationBarController()295 LocationBarController* 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
GetActiveScriptController()306 ActiveScriptController* ActiveScriptTester::GetActiveScriptController() {
307 LocationBarController* location_bar_controller = GetLocationBarController();
308 return location_bar_controller ?
309 location_bar_controller->active_script_controller() : NULL;
310 }
311
GetAction()312 ExtensionAction* ActiveScriptTester::GetAction() {
313 ActiveScriptController* controller = GetActiveScriptController();
314 return controller ? controller->GetActionForExtension(extension_) : NULL;
315 }
316
IN_PROC_BROWSER_TEST_F(ActiveScriptControllerBrowserTest,ActiveScriptsAreDisplayedAndDelayExecution)317 IN_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.
IN_PROC_BROWSER_TEST_F(ActiveScriptControllerBrowserTest,RemoveExtensionWithPendingInjections)377 IN_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 // This is slight hack to achieve a RunPendingInRenderer() method. Since IPCs
406 // are sent synchronously, the renderer will be notified of the extension
407 // being unloaded before the script is executed, and, since ExecuteScript() is
408 // synchronous, the renderer is guaranteed to be done updating scripts.
409 EXPECT_TRUE(content::ExecuteScript(web_contents, "1 == 1;"));
410
411 // We should have pending requests for extension1, but not the removed
412 // extension2.
413 EXPECT_TRUE(active_script_controller->GetActionForExtension(extension1));
414 EXPECT_FALSE(active_script_controller->GetActionForExtension(extension2));
415
416 // We should still be able to run the request for extension1.
417 ExtensionTestMessageListener inject_success_listener(
418 new ExtensionTestMessageListener(kInjectSucceeded,
419 false /* won't reply */));
420 inject_success_listener.set_extension_id(extension1->id());
421 active_script_controller->OnClicked(extension1);
422 inject_success_listener.WaitUntilSatisfied();
423 }
424
425 // A version of the test with the flag off, in order to test that everything
426 // still works as expected.
427 class FlagOffActiveScriptControllerBrowserTest
428 : public ActiveScriptControllerBrowserTest {
429 private:
430 // Simply don't append the flag.
SetUpCommandLine(base::CommandLine * command_line)431 virtual void SetUpCommandLine(base::CommandLine* command_line) OVERRIDE {
432 ExtensionBrowserTest::SetUpCommandLine(command_line);
433 }
434 };
435
IN_PROC_BROWSER_TEST_F(FlagOffActiveScriptControllerBrowserTest,ScriptsExecuteWhenFlagAbsent)436 IN_PROC_BROWSER_TEST_F(FlagOffActiveScriptControllerBrowserTest,
437 ScriptsExecuteWhenFlagAbsent) {
438 const char* kExtensionNames[] = {
439 "content_scripts_all_hosts",
440 "inject_scripts_all_hosts",
441 };
442 ActiveScriptTester testers[] = {
443 ActiveScriptTester(
444 kExtensionNames[0],
445 CreateExtension(ALL_HOSTS, CONTENT_SCRIPT),
446 browser(),
447 DOES_NOT_REQUIRE_CONSENT,
448 CONTENT_SCRIPT),
449 ActiveScriptTester(
450 kExtensionNames[1],
451 CreateExtension(ALL_HOSTS, EXECUTE_SCRIPT),
452 browser(),
453 DOES_NOT_REQUIRE_CONSENT,
454 EXECUTE_SCRIPT),
455 };
456
457 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
458 ui_test_utils::NavigateToURL(
459 browser(), embedded_test_server()->GetURL("/extensions/test_file.html"));
460
461 for (size_t i = 0u; i < arraysize(testers); ++i)
462 EXPECT_TRUE(testers[i].Verify()) << kExtensionNames[i];
463 }
464
465 } // namespace extensions
466