Home | History | Annotate | Download | only in extensions
      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:
     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 
     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 
     92 void ActiveScriptControllerBrowserTest::CleanUpOnMainThread() {
     93   test_extension_dirs_.clear();
     94 }
     95 
     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 
    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 
    222 ActiveScriptTester::~ActiveScriptTester() {
    223 }
    224 
    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 
    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 
    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 
    312 ExtensionAction* ActiveScriptTester::GetAction() {
    313   ActiveScriptController* controller = GetActiveScriptController();
    314   return controller ? controller->GetActionForExtension(extension_) : NULL;
    315 }
    316 
    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.
    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.
    431   virtual void SetUpCommandLine(base::CommandLine* command_line) OVERRIDE {
    432     ExtensionBrowserTest::SetUpCommandLine(command_line);
    433   }
    434 };
    435 
    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