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/tab_helper.h"
     12 #include "chrome/browser/extensions/test_extension_dir.h"
     13 #include "chrome/browser/ui/browser.h"
     14 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     15 #include "chrome/test/base/ui_test_utils.h"
     16 #include "content/public/test/browser_test_utils.h"
     17 #include "extensions/common/feature_switch.h"
     18 #include "extensions/common/switches.h"
     19 #include "extensions/test/extension_test_message_listener.h"
     20 #include "net/test/embedded_test_server/embedded_test_server.h"
     21 #include "testing/gtest/include/gtest/gtest.h"
     22 
     23 namespace extensions {
     24 
     25 namespace {
     26 
     27 const char kAllHostsScheme[] = "*://*/*";
     28 const char kExplicitHostsScheme[] = "http://127.0.0.1/*";
     29 const char kBackgroundScript[] =
     30     "\"background\": {\"scripts\": [\"script.js\"]}";
     31 const 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     "};\n"
     38     "chrome.tabs.onUpdated.addListener(listener);";
     39 const char kContentScriptSource[] =
     40     "chrome.test.sendMessage('inject succeeded');";
     41 
     42 const char kInjectSucceeded[] = "inject succeeded";
     43 
     44 enum InjectionType {
     45   CONTENT_SCRIPT,
     46   EXECUTE_SCRIPT
     47 };
     48 
     49 enum HostType {
     50   ALL_HOSTS,
     51   EXPLICIT_HOSTS
     52 };
     53 
     54 enum RequiresConsent {
     55   REQUIRES_CONSENT,
     56   DOES_NOT_REQUIRE_CONSENT
     57 };
     58 
     59 // Runs all pending tasks in the renderer associated with |web_contents|.
     60 // Returns true on success.
     61 bool RunAllPendingInRenderer(content::WebContents* web_contents) {
     62   // This is slight hack to achieve a RunPendingInRenderer() method. Since IPCs
     63   // are sent synchronously, anything started prior to this method will finish
     64   // before this method returns (as content::ExecuteScript() is synchronous).
     65   return content::ExecuteScript(web_contents, "1 == 1;");
     66 }
     67 
     68 }  // namespace
     69 
     70 class ActiveScriptControllerBrowserTest : public ExtensionBrowserTest {
     71  public:
     72   ActiveScriptControllerBrowserTest() {}
     73 
     74   virtual void SetUpCommandLine(base::CommandLine* command_line) OVERRIDE;
     75   virtual void TearDownOnMainThread() OVERRIDE;
     76 
     77   // Returns an extension with the given |host_type| and |injection_type|. If
     78   // one already exists, the existing extension will be returned. Othewrwise,
     79   // one will be created.
     80   // This could potentially return NULL if LoadExtension() fails.
     81   const Extension* CreateExtension(HostType host_type,
     82                                    InjectionType injection_type);
     83 
     84  private:
     85   ScopedVector<TestExtensionDir> test_extension_dirs_;
     86   std::vector<const Extension*> extensions_;
     87 };
     88 
     89 void ActiveScriptControllerBrowserTest::SetUpCommandLine(
     90     base::CommandLine* command_line) {
     91   ExtensionBrowserTest::SetUpCommandLine(command_line);
     92   // We append the actual switch to the commandline because it needs to be
     93   // passed over to the renderer, which a FeatureSwitch::ScopedOverride will
     94   // not do.
     95   command_line->AppendSwitch(switches::kEnableScriptsRequireAction);
     96 }
     97 
     98 void ActiveScriptControllerBrowserTest::TearDownOnMainThread() {
     99   test_extension_dirs_.clear();
    100 }
    101 
    102 const Extension* ActiveScriptControllerBrowserTest::CreateExtension(
    103     HostType host_type, InjectionType injection_type) {
    104   std::string name =
    105       base::StringPrintf(
    106           "%s %s",
    107           injection_type == CONTENT_SCRIPT ?
    108               "content_script" : "execute_script",
    109           host_type == ALL_HOSTS ? "all_hosts" : "explicit_hosts");
    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 
    161 class 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 active script controller, or NULL if one does not exist.
    174   ActiveScriptController* GetActiveScriptController();
    175 
    176   // Returns true if the extension has a pending request with the active script
    177   // controller.
    178   bool WantsToRun();
    179 
    180   // The name of the extension, and also the message it sends.
    181   std::string name_;
    182 
    183   // The extension associated with this tester.
    184   const Extension* extension_;
    185 
    186   // The browser the tester is running in.
    187   Browser* browser_;
    188 
    189   // Whether or not the extension has permission to run the script without
    190   // asking the user.
    191   RequiresConsent requires_consent_;
    192 
    193   // The type of injection this tester uses.
    194   InjectionType type_;
    195 
    196   // All of these extensions should inject a script (either through content
    197   // scripts or through chrome.tabs.executeScript()) that sends a message with
    198   // the |kInjectSucceeded| 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_success_listener_(
    213           new ExtensionTestMessageListener(kInjectSucceeded,
    214                                            false /* won't reply */)) {
    215   inject_success_listener_->set_extension_id(extension->id());
    216 }
    217 
    218 ActiveScriptTester::~ActiveScriptTester() {
    219 }
    220 
    221 testing::AssertionResult ActiveScriptTester::Verify() {
    222   if (!extension_)
    223     return testing::AssertionFailure() << "Could not load extension: " << name_;
    224 
    225   content::WebContents* web_contents =
    226       browser_ ? browser_->tab_strip_model()->GetActiveWebContents() : NULL;
    227   if (!web_contents)
    228     return testing::AssertionFailure() << "No web contents.";
    229 
    230   // Give the extension plenty of time to inject.
    231   if (!RunAllPendingInRenderer(web_contents))
    232     return testing::AssertionFailure() << "Could not run pending in renderer.";
    233 
    234   // Make sure all running tasks are complete.
    235   content::RunAllPendingInMessageLoop();
    236 
    237   ActiveScriptController* controller = GetActiveScriptController();
    238   if (!controller)
    239     return testing::AssertionFailure() << "Could not find controller.";
    240 
    241   bool wants_to_run = WantsToRun();
    242 
    243   // An extension should have an action displayed iff it requires user consent.
    244   if ((requires_consent_ == REQUIRES_CONSENT && !wants_to_run) ||
    245       (requires_consent_ == DOES_NOT_REQUIRE_CONSENT && wants_to_run)) {
    246     return testing::AssertionFailure()
    247         << "Improper wants to run for " << name_ << ": expected "
    248         << (requires_consent_ == REQUIRES_CONSENT) << ", found "
    249         << wants_to_run;
    250   }
    251 
    252   // If the extension has permission, we should be able to simply wait for it
    253   // to execute.
    254   if (requires_consent_ == DOES_NOT_REQUIRE_CONSENT) {
    255     inject_success_listener_->WaitUntilSatisfied();
    256     return testing::AssertionSuccess();
    257   }
    258 
    259   // Otherwise, we don't have permission, and have to grant it. Ensure the
    260   // script has *not* already executed.
    261   if (inject_success_listener_->was_satisfied()) {
    262     return testing::AssertionFailure() <<
    263         name_ << "'s script ran without permission.";
    264   }
    265 
    266   // If we reach this point, we should always want to run.
    267   DCHECK(wants_to_run);
    268 
    269   // Grant permission by clicking on the extension action.
    270   controller->OnClicked(extension_);
    271 
    272   // Now, the extension should be able to inject the script.
    273   inject_success_listener_->WaitUntilSatisfied();
    274 
    275   // The extension should no longer want to run.
    276   wants_to_run = WantsToRun();
    277   if (wants_to_run) {
    278     return testing::AssertionFailure()
    279         << "Extension " << name_ << " still wants to run after injecting.";
    280   }
    281 
    282   return testing::AssertionSuccess();
    283 }
    284 
    285 ActiveScriptController* ActiveScriptTester::GetActiveScriptController() {
    286   content::WebContents* web_contents =
    287       browser_ ? browser_->tab_strip_model()->GetActiveWebContents() : NULL;
    288 
    289   if (!web_contents)
    290     return NULL;
    291 
    292   TabHelper* tab_helper = TabHelper::FromWebContents(web_contents);
    293   return tab_helper ? tab_helper->active_script_controller() : NULL;
    294 }
    295 
    296 bool ActiveScriptTester::WantsToRun() {
    297   ActiveScriptController* controller = GetActiveScriptController();
    298   return controller ? controller->WantsToRun(extension_) : false;
    299 }
    300 
    301 IN_PROC_BROWSER_TEST_F(ActiveScriptControllerBrowserTest,
    302                        ActiveScriptsAreDisplayedAndDelayExecution) {
    303   base::FilePath active_script_path =
    304       test_data_dir_.AppendASCII("active_script");
    305 
    306   const char* kExtensionNames[] = {
    307       "inject_scripts_all_hosts",
    308       "inject_scripts_explicit_hosts",
    309       "content_scripts_all_hosts",
    310       "content_scripts_explicit_hosts"
    311   };
    312 
    313   // First, we load up three extensions:
    314   // - An extension that injects scripts into all hosts,
    315   // - An extension that injects scripts into explicit hosts,
    316   // - An extension with a content script that runs on all hosts,
    317   // - An extension with a content script that runs on explicit hosts.
    318   // The extensions that operate on explicit hosts have permission; the ones
    319   // that request all hosts require user consent.
    320   ActiveScriptTester testers[] = {
    321       ActiveScriptTester(
    322           kExtensionNames[0],
    323           CreateExtension(ALL_HOSTS, EXECUTE_SCRIPT),
    324           browser(),
    325           REQUIRES_CONSENT,
    326           EXECUTE_SCRIPT),
    327       ActiveScriptTester(
    328           kExtensionNames[1],
    329           CreateExtension(EXPLICIT_HOSTS, EXECUTE_SCRIPT),
    330           browser(),
    331           DOES_NOT_REQUIRE_CONSENT,
    332           EXECUTE_SCRIPT),
    333       ActiveScriptTester(
    334           kExtensionNames[2],
    335           CreateExtension(ALL_HOSTS, CONTENT_SCRIPT),
    336           browser(),
    337           REQUIRES_CONSENT,
    338           CONTENT_SCRIPT),
    339       ActiveScriptTester(
    340           kExtensionNames[3],
    341           CreateExtension(EXPLICIT_HOSTS, CONTENT_SCRIPT),
    342           browser(),
    343           DOES_NOT_REQUIRE_CONSENT,
    344           CONTENT_SCRIPT),
    345   };
    346 
    347   // Navigate to an URL (which matches the explicit host specified in the
    348   // extension content_scripts_explicit_hosts). All four extensions should
    349   // inject the script.
    350   ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
    351   ui_test_utils::NavigateToURL(
    352       browser(), embedded_test_server()->GetURL("/extensions/test_file.html"));
    353 
    354   for (size_t i = 0u; i < arraysize(testers); ++i)
    355     EXPECT_TRUE(testers[i].Verify()) << kExtensionNames[i];
    356 }
    357 
    358 // Test that removing an extension with pending injections a) removes the
    359 // pending injections for that extension, and b) does not affect pending
    360 // injections for other extensions.
    361 IN_PROC_BROWSER_TEST_F(ActiveScriptControllerBrowserTest,
    362                        RemoveExtensionWithPendingInjections) {
    363   // Load up two extensions, each with content scripts.
    364   const Extension* extension1 = CreateExtension(ALL_HOSTS, CONTENT_SCRIPT);
    365   ASSERT_TRUE(extension1);
    366   const Extension* extension2 = CreateExtension(ALL_HOSTS, CONTENT_SCRIPT);
    367   ASSERT_TRUE(extension2);
    368 
    369   ASSERT_NE(extension1->id(), extension2->id());
    370 
    371   content::WebContents* web_contents =
    372       browser()->tab_strip_model()->GetActiveWebContents();
    373   ASSERT_TRUE(web_contents);
    374   ActiveScriptController* active_script_controller =
    375       ActiveScriptController::GetForWebContents(web_contents);
    376   ASSERT_TRUE(active_script_controller);
    377 
    378   ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
    379   ui_test_utils::NavigateToURL(
    380       browser(), embedded_test_server()->GetURL("/extensions/test_file.html"));
    381 
    382   // Both extensions should have pending requests.
    383   EXPECT_TRUE(active_script_controller->WantsToRun(extension1));
    384   EXPECT_TRUE(active_script_controller->WantsToRun(extension2));
    385 
    386   // Unload one of the extensions.
    387   UnloadExtension(extension2->id());
    388 
    389   EXPECT_TRUE(RunAllPendingInRenderer(web_contents));
    390 
    391   // We should have pending requests for extension1, but not the removed
    392   // extension2.
    393   EXPECT_TRUE(active_script_controller->WantsToRun(extension1));
    394   EXPECT_FALSE(active_script_controller->WantsToRun(extension2));
    395 
    396   // We should still be able to run the request for extension1.
    397   ExtensionTestMessageListener inject_success_listener(
    398       new ExtensionTestMessageListener(kInjectSucceeded,
    399                                        false /* won't reply */));
    400   inject_success_listener.set_extension_id(extension1->id());
    401   active_script_controller->OnClicked(extension1);
    402   inject_success_listener.WaitUntilSatisfied();
    403 }
    404 
    405 // A version of the test with the flag off, in order to test that everything
    406 // still works as expected.
    407 class FlagOffActiveScriptControllerBrowserTest
    408     : public ActiveScriptControllerBrowserTest {
    409  private:
    410   // Simply don't append the flag.
    411   virtual void SetUpCommandLine(base::CommandLine* command_line) OVERRIDE {
    412     ExtensionBrowserTest::SetUpCommandLine(command_line);
    413   }
    414 };
    415 
    416 IN_PROC_BROWSER_TEST_F(FlagOffActiveScriptControllerBrowserTest,
    417                        ScriptsExecuteWhenFlagAbsent) {
    418   const char* kExtensionNames[] = {
    419     "content_scripts_all_hosts",
    420     "inject_scripts_all_hosts",
    421   };
    422   ActiveScriptTester testers[] = {
    423     ActiveScriptTester(
    424           kExtensionNames[0],
    425           CreateExtension(ALL_HOSTS, CONTENT_SCRIPT),
    426           browser(),
    427           DOES_NOT_REQUIRE_CONSENT,
    428           CONTENT_SCRIPT),
    429       ActiveScriptTester(
    430           kExtensionNames[1],
    431           CreateExtension(ALL_HOSTS, EXECUTE_SCRIPT),
    432           browser(),
    433           DOES_NOT_REQUIRE_CONSENT,
    434           EXECUTE_SCRIPT),
    435   };
    436 
    437   ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
    438   ui_test_utils::NavigateToURL(
    439       browser(), embedded_test_server()->GetURL("/extensions/test_file.html"));
    440 
    441   for (size_t i = 0u; i < arraysize(testers); ++i)
    442     EXPECT_TRUE(testers[i].Verify()) << kExtensionNames[i];
    443 }
    444 
    445 }  // namespace extensions
    446