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