Home | History | Annotate | Download | only in extensions
      1 // Copyright (c) 2012 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/path_service.h"
      7 #include "base/strings/string_number_conversions.h"
      8 #include "base/strings/stringprintf.h"
      9 #include "base/values.h"
     10 #include "chrome/browser/chrome_notification_types.h"
     11 #include "chrome/browser/extensions/event_router.h"
     12 #include "chrome/browser/extensions/extension_apitest.h"
     13 #include "chrome/browser/extensions/extension_prefs.h"
     14 #include "chrome/browser/extensions/extension_system.h"
     15 #include "chrome/browser/extensions/test_extension_dir.h"
     16 #include "chrome/browser/profiles/profile.h"
     17 #include "chrome/browser/ui/browser.h"
     18 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     19 #include "chrome/common/chrome_paths.h"
     20 #include "chrome/common/chrome_switches.h"
     21 #include "chrome/test/base/ui_test_utils.h"
     22 #include "content/public/browser/notification_registrar.h"
     23 #include "content/public/browser/notification_service.h"
     24 #include "content/public/test/browser_test_utils.h"
     25 #include "net/dns/mock_host_resolver.h"
     26 #include "net/test/embedded_test_server/embedded_test_server.h"
     27 #include "url/gurl.h"
     28 
     29 namespace extensions {
     30 namespace {
     31 
     32 class MessageSender : public content::NotificationObserver {
     33  public:
     34   MessageSender() {
     35     registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING,
     36                    content::NotificationService::AllSources());
     37   }
     38 
     39  private:
     40   static scoped_ptr<base::ListValue> BuildEventArguments(
     41       const bool last_message,
     42       const std::string& data) {
     43     DictionaryValue* event = new DictionaryValue();
     44     event->SetBoolean("lastMessage", last_message);
     45     event->SetString("data", data);
     46     scoped_ptr<base::ListValue> arguments(new base::ListValue());
     47     arguments->Append(event);
     48     return arguments.Pass();
     49   }
     50 
     51   static scoped_ptr<Event> BuildEvent(scoped_ptr<base::ListValue> event_args,
     52                                       Profile* profile,
     53                                       GURL event_url) {
     54     scoped_ptr<Event> event(new Event("test.onMessage", event_args.Pass()));
     55     event->restrict_to_profile = profile;
     56     event->event_url = event_url;
     57     return event.Pass();
     58   }
     59 
     60   virtual void Observe(int type,
     61                        const content::NotificationSource& source,
     62                        const content::NotificationDetails& details) OVERRIDE {
     63     EventRouter* event_router = ExtensionSystem::Get(
     64         content::Source<Profile>(source).ptr())->event_router();
     65 
     66     // Sends four messages to the extension. All but the third message sent
     67     // from the origin http://b.com/ are supposed to arrive.
     68     event_router->BroadcastEvent(BuildEvent(
     69         BuildEventArguments(false, "no restriction"),
     70         content::Source<Profile>(source).ptr(),
     71         GURL()));
     72     event_router->BroadcastEvent(BuildEvent(
     73         BuildEventArguments(false, "http://a.com/"),
     74         content::Source<Profile>(source).ptr(),
     75         GURL("http://a.com/")));
     76     event_router->BroadcastEvent(BuildEvent(
     77         BuildEventArguments(false, "http://b.com/"),
     78         content::Source<Profile>(source).ptr(),
     79         GURL("http://b.com/")));
     80     event_router->BroadcastEvent(BuildEvent(
     81         BuildEventArguments(true, "last message"),
     82         content::Source<Profile>(source).ptr(),
     83         GURL()));
     84   }
     85 
     86   content::NotificationRegistrar registrar_;
     87 };
     88 
     89 // Tests that message passing between extensions and content scripts works.
     90 IN_PROC_BROWSER_TEST_F(ExtensionApiTest, Messaging) {
     91   ASSERT_TRUE(StartEmbeddedTestServer());
     92   ASSERT_TRUE(RunExtensionTest("messaging/connect")) << message_;
     93 }
     94 
     95 // Tests that message passing from one extension to another works.
     96 IN_PROC_BROWSER_TEST_F(ExtensionApiTest, MessagingExternal) {
     97   ASSERT_TRUE(LoadExtension(
     98       test_data_dir_.AppendASCII("..").AppendASCII("good")
     99                     .AppendASCII("Extensions")
    100                     .AppendASCII("bjafgdebaacbbbecmhlhpofkepfkgcpa")
    101                     .AppendASCII("1.0")));
    102 
    103   ASSERT_TRUE(RunExtensionTest("messaging/connect_external")) << message_;
    104 }
    105 
    106 // Tests that messages with event_urls are only passed to extensions with
    107 // appropriate permissions.
    108 IN_PROC_BROWSER_TEST_F(ExtensionApiTest, MessagingEventURL) {
    109   MessageSender sender;
    110   ASSERT_TRUE(RunExtensionTest("messaging/event_url")) << message_;
    111 }
    112 
    113 // Tests connecting from a panel to its extension.
    114 class PanelMessagingTest : public ExtensionApiTest {
    115   virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
    116     ExtensionApiTest::SetUpCommandLine(command_line);
    117     command_line->AppendSwitch(switches::kEnablePanels);
    118   }
    119 };
    120 
    121 IN_PROC_BROWSER_TEST_F(PanelMessagingTest, MessagingPanel) {
    122   ASSERT_TRUE(RunExtensionTest("messaging/connect_panel")) << message_;
    123 }
    124 
    125 // Tests externally_connectable between a web page and an extension.
    126 //
    127 // TODO(kalman): Test between extensions. This is already tested in this file,
    128 // but not with externally_connectable set in the manifest.
    129 //
    130 // TODO(kalman): Test with host permissions.
    131 class ExternallyConnectableMessagingTest : public ExtensionApiTest {
    132  protected:
    133   // Result codes from the test. These must match up with |results| in
    134   // c/t/d/extensions/api_test/externally_connectable/assertions.json.
    135   enum Result {
    136     OK = 0,
    137     NAMESPACE_NOT_DEFINED = 1,
    138     FUNCTION_NOT_DEFINED = 2,
    139     COULD_NOT_ESTABLISH_CONNECTION_ERROR = 3,
    140     OTHER_ERROR = 4,
    141     INCORRECT_RESPONSE_SENDER = 5,
    142     INCORRECT_RESPONSE_MESSAGE = 6,
    143   };
    144 
    145   Result CanConnectAndSendMessages(const std::string& extension_id) {
    146     return CanConnectAndSendMessages(browser(), extension_id);
    147   }
    148 
    149   Result CanConnectAndSendMessages(Browser* browser,
    150                                    const std::string& extension_id) {
    151     int result;
    152     CHECK(content::ExecuteScriptAndExtractInt(
    153         browser->tab_strip_model()->GetActiveWebContents(),
    154         "assertions.canConnectAndSendMessages('" + extension_id + "')",
    155         &result));
    156     return static_cast<Result>(result);
    157   }
    158 
    159   testing::AssertionResult AreAnyNonWebApisDefined() {
    160     // All runtime API methods are non-web except for sendRequest and connect.
    161     const char* non_messaging_apis[] = {
    162         "getBackgroundPage",
    163         "getManifest",
    164         "getURL",
    165         "reload",
    166         "requestUpdateCheck",
    167         "connectNative",
    168         "sendNativeMessage",
    169         "onStartup",
    170         "onInstalled",
    171         "onSuspend",
    172         "onSuspendCanceled",
    173         "onUpdateAvailable",
    174         "onBrowserUpdateAvailable",
    175         "onConnect",
    176         "onConnectExternal",
    177         "onMessage",
    178         "onMessageExternal",
    179         "onRestartRequired",
    180         "id",
    181     };
    182 
    183     // Turn the array into a JS array, which effectively gets eval()ed.
    184     std::string as_js_array;
    185     for (size_t i = 0; i < arraysize(non_messaging_apis); ++i) {
    186       as_js_array += as_js_array.empty() ? "[" : ",";
    187       as_js_array += base::StringPrintf("'%s'", non_messaging_apis[i]);
    188     }
    189     as_js_array += "]";
    190 
    191     bool any_defined;
    192     CHECK(content::ExecuteScriptAndExtractBool(
    193         browser()->tab_strip_model()->GetActiveWebContents(),
    194         "assertions.areAnyRuntimePropertiesDefined(" + as_js_array + ")",
    195         &any_defined));
    196     return any_defined ?
    197         testing::AssertionSuccess() : testing::AssertionFailure();
    198   }
    199 
    200   GURL GetURLForPath(const std::string& host, const std::string& path) {
    201     std::string port = base::IntToString(embedded_test_server()->port());
    202     GURL::Replacements replacements;
    203     replacements.SetHostStr(host);
    204     replacements.SetPortStr(port);
    205     return embedded_test_server()->GetURL(path).ReplaceComponents(replacements);
    206   }
    207 
    208   GURL chromium_org_url() {
    209     return GetURLForPath("www.chromium.org", "/chromium.org.html");
    210   }
    211 
    212   GURL google_com_url() {
    213     return GetURLForPath("www.google.com", "/google.com.html");
    214   }
    215 
    216   const Extension* LoadChromiumConnectableExtension() {
    217     return LoadExtensionIntoDir(&web_connectable_dir_, base::StringPrintf(
    218         "{"
    219         "  \"name\": \"chromium_connectable\","
    220         "  %s,"
    221         "  \"externally_connectable\": {"
    222         "    \"matches\": [\"*://*.chromium.org:*/*\"]"
    223         "  }"
    224         "}",
    225         common_manifest()));
    226   }
    227 
    228   scoped_refptr<const Extension> LoadNotConnectableExtension() {
    229     return LoadExtensionIntoDir(&not_connectable_dir_, base::StringPrintf(
    230         "{"
    231         "  \"name\": \"not_connectable\","
    232         "  %s"
    233         "}",
    234         common_manifest()));
    235   }
    236 
    237   void InitializeTestServer() {
    238     base::FilePath test_data;
    239     EXPECT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data));
    240     embedded_test_server()->ServeFilesFromDirectory(test_data.AppendASCII(
    241         "extensions/api_test/messaging/externally_connectable/sites"));
    242     ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
    243     host_resolver()->AddRule("*", embedded_test_server()->base_url().host());
    244   }
    245 
    246  private:
    247   const Extension* LoadExtensionIntoDir(TestExtensionDir* dir,
    248                                         const std::string& manifest) {
    249     dir->WriteManifest(manifest);
    250     dir->WriteFile(FILE_PATH_LITERAL("background.js"),
    251         "chrome.runtime.onMessageExternal.addListener(\n"
    252         "    function(message, sender, reply) {\n"
    253         "  reply({ message: message, sender: sender });\n"
    254         "});\n"
    255         "chrome.runtime.onConnectExternal.addListener(function(port) {\n"
    256         "  port.onMessage.addListener(function(message) {\n"
    257         "    port.postMessage({ message: message, sender: port.sender });\n"
    258         "  });\n"
    259         "});\n");
    260     return LoadExtension(dir->unpacked_path());
    261   }
    262 
    263   const char* common_manifest() {
    264     return "\"version\": \"1.0\","
    265            "\"background\": {"
    266            "    \"scripts\": [\"background.js\"],"
    267            "    \"persistent\": false"
    268            "},"
    269            "\"manifest_version\": 2";
    270   }
    271 
    272   TestExtensionDir web_connectable_dir_;
    273   TestExtensionDir not_connectable_dir_;
    274 };
    275 
    276 IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest, NotInstalled) {
    277   InitializeTestServer();
    278 
    279   const char kFakeId[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
    280 
    281   ui_test_utils::NavigateToURL(browser(), chromium_org_url());
    282   EXPECT_EQ(NAMESPACE_NOT_DEFINED, CanConnectAndSendMessages(kFakeId));
    283   EXPECT_FALSE(AreAnyNonWebApisDefined());
    284 
    285   ui_test_utils::NavigateToURL(browser(), google_com_url());
    286   EXPECT_EQ(NAMESPACE_NOT_DEFINED, CanConnectAndSendMessages(kFakeId));
    287   EXPECT_FALSE(AreAnyNonWebApisDefined());
    288 }
    289 
    290 // Tests two extensions on the same sites: one web connectable, one not.
    291 IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest,
    292                        WebConnectableAndNotConnectable) {
    293   InitializeTestServer();
    294 
    295   // Install the web connectable extension. chromium.org can connect to it,
    296   // google.com can't.
    297   const Extension* chromium_connectable = LoadChromiumConnectableExtension();
    298   ASSERT_TRUE(chromium_connectable);
    299 
    300   ui_test_utils::NavigateToURL(browser(), chromium_org_url());
    301   EXPECT_EQ(OK, CanConnectAndSendMessages(chromium_connectable->id()));
    302   EXPECT_FALSE(AreAnyNonWebApisDefined());
    303 
    304   ui_test_utils::NavigateToURL(browser(), google_com_url());
    305   EXPECT_EQ(NAMESPACE_NOT_DEFINED,
    306             CanConnectAndSendMessages(chromium_connectable->id()));
    307   EXPECT_FALSE(AreAnyNonWebApisDefined());
    308 
    309   // Install the non-connectable extension. Nothing can connect to it.
    310   const Extension* not_connectable = LoadNotConnectableExtension();
    311   ASSERT_TRUE(not_connectable);
    312 
    313   ui_test_utils::NavigateToURL(browser(), chromium_org_url());
    314   // Namespace will be defined here because |chromium_connectable| can connect
    315   // to it - so this will be the "cannot establish connection" error.
    316   EXPECT_EQ(COULD_NOT_ESTABLISH_CONNECTION_ERROR,
    317             CanConnectAndSendMessages(not_connectable->id()));
    318   EXPECT_FALSE(AreAnyNonWebApisDefined());
    319 
    320   ui_test_utils::NavigateToURL(browser(), google_com_url());
    321   EXPECT_EQ(NAMESPACE_NOT_DEFINED,
    322             CanConnectAndSendMessages(not_connectable->id()));
    323   EXPECT_FALSE(AreAnyNonWebApisDefined());
    324 }
    325 
    326 // Tests that enabling and disabling an extension makes the runtime bindings
    327 // appear and disappear.
    328 //
    329 // TODO(kalman): Test with multiple extensions that can be accessed by the same
    330 // host.
    331 IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest,
    332                        EnablingAndDisabling) {
    333   InitializeTestServer();
    334 
    335   const Extension* chromium_connectable = LoadChromiumConnectableExtension();
    336   ASSERT_TRUE(chromium_connectable);
    337   const Extension* not_connectable = LoadNotConnectableExtension();
    338   ASSERT_TRUE(not_connectable);
    339 
    340   ui_test_utils::NavigateToURL(browser(), chromium_org_url());
    341   EXPECT_EQ(OK, CanConnectAndSendMessages(chromium_connectable->id()));
    342   EXPECT_EQ(COULD_NOT_ESTABLISH_CONNECTION_ERROR,
    343             CanConnectAndSendMessages(not_connectable->id()));
    344 
    345   DisableExtension(chromium_connectable->id());
    346   EXPECT_EQ(COULD_NOT_ESTABLISH_CONNECTION_ERROR,
    347             CanConnectAndSendMessages(chromium_connectable->id()));
    348 
    349   EnableExtension(chromium_connectable->id());
    350   EXPECT_EQ(OK, CanConnectAndSendMessages(chromium_connectable->id()));
    351   EXPECT_EQ(COULD_NOT_ESTABLISH_CONNECTION_ERROR,
    352             CanConnectAndSendMessages(not_connectable->id()));
    353 }
    354 
    355 // Tests connection from incognito tabs. Spanning mode only.
    356 //
    357 // TODO(kalman): ensure that we exercise split vs spanning incognito logic
    358 // somewhere. This is a test that should be shared with the content script logic
    359 // so it's not really our specific concern for web connectable.
    360 IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest, FromIncognito) {
    361   InitializeTestServer();
    362 
    363   const Extension* chromium_connectable = LoadChromiumConnectableExtension();
    364   ASSERT_TRUE(chromium_connectable);
    365 
    366   Browser* incognito_browser = ui_test_utils::OpenURLOffTheRecord(
    367       profile()->GetOffTheRecordProfile(),
    368       chromium_org_url());
    369 
    370   // No connection because incognito enabled hasn't been set.
    371   const std::string& id = chromium_connectable->id();
    372   EXPECT_EQ(COULD_NOT_ESTABLISH_CONNECTION_ERROR,
    373             CanConnectAndSendMessages(incognito_browser, id));
    374 
    375   // Then yes.
    376   ExtensionPrefs::Get(profile())->SetIsIncognitoEnabled(id, true);
    377   EXPECT_EQ(OK, CanConnectAndSendMessages(incognito_browser, id));
    378 }
    379 
    380 }  // namespace
    381 }  // namespace extensions
    382