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