• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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