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(¬_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