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.
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"
29 namespace extensions
{
32 class MessageSender
: public content::NotificationObserver
{
35 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING
,
36 content::NotificationService::AllSources());
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();
51 static scoped_ptr
<Event
> BuildEvent(scoped_ptr
<base::ListValue
> event_args
,
54 scoped_ptr
<Event
> event(new Event("test.onMessage", event_args
.Pass()));
55 event
->restrict_to_profile
= profile
;
56 event
->event_url
= event_url
;
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();
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(),
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(),
86 content::NotificationRegistrar registrar_
;
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_
;
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")));
103 ASSERT_TRUE(RunExtensionTest("messaging/connect_external")) << message_
;
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_
;
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
);
121 IN_PROC_BROWSER_TEST_F(PanelMessagingTest
, MessagingPanel
) {
122 ASSERT_TRUE(RunExtensionTest("messaging/connect_panel")) << message_
;
125 // Tests externally_connectable between a web page and an extension.
127 // TODO(kalman): Test between extensions. This is already tested in this file,
128 // but not with externally_connectable set in the manifest.
130 // TODO(kalman): Test with host permissions.
131 class ExternallyConnectableMessagingTest
: public ExtensionApiTest
{
133 // Result codes from the test. These must match up with |results| in
134 // c/t/d/extensions/api_test/externally_connectable/assertions.json.
137 NAMESPACE_NOT_DEFINED
= 1,
138 FUNCTION_NOT_DEFINED
= 2,
139 COULD_NOT_ESTABLISH_CONNECTION_ERROR
= 3,
141 INCORRECT_RESPONSE_SENDER
= 5,
142 INCORRECT_RESPONSE_MESSAGE
= 6,
145 bool AppendIframe(const GURL
& src
) {
147 CHECK(content::ExecuteScriptAndExtractBool(
148 browser()->tab_strip_model()->GetActiveWebContents(),
149 "actions.appendIframe('" + src
.spec() + "');", &result
));
153 Result
CanConnectAndSendMessages(const std::string
& extension_id
) {
154 return CanConnectAndSendMessages(browser(), extension_id
, "");
157 Result
CanConnectAndSendMessages(const std::string
& extension_id
,
158 const char* frame_xpath
,
159 const char* message
) {
160 return CanConnectAndSendMessages(browser(), extension_id
, frame_xpath
,
164 Result
CanConnectAndSendMessages(Browser
* browser
,
165 const std::string
& extension_id
) {
166 return CanConnectAndSendMessages(browser
, extension_id
, "");
169 Result
CanConnectAndSendMessages(const std::string
& extension_id
,
170 const char* frame_xpath
) {
171 return CanConnectAndSendMessages(browser(), extension_id
, frame_xpath
);
174 Result
CanConnectAndSendMessages(Browser
* browser
,
175 const std::string
& extension_id
,
176 const char* frame_xpath
,
177 const char* message
= NULL
) {
179 std::string args
= "'" + extension_id
+ "'";
181 args
+= std::string(", '") + message
+ "'";
182 CHECK(content::ExecuteScriptInFrameAndExtractInt(
183 browser
->tab_strip_model()->GetActiveWebContents(),
185 base::StringPrintf("assertions.canConnectAndSendMessages(%s)",
186 args
.c_str()).c_str(),
188 return static_cast<Result
>(result
);
191 testing::AssertionResult
AreAnyNonWebApisDefined() {
192 return AreAnyNonWebApisDefined("");
195 testing::AssertionResult
AreAnyNonWebApisDefined(const char* frame_xpath
) {
196 // All runtime API methods are non-web except for sendRequest and connect.
197 const char* non_messaging_apis
[] = {
202 "requestUpdateCheck",
210 "onBrowserUpdateAvailable",
219 // Turn the array into a JS array, which effectively gets eval()ed.
220 std::string as_js_array
;
221 for (size_t i
= 0; i
< arraysize(non_messaging_apis
); ++i
) {
222 as_js_array
+= as_js_array
.empty() ? "[" : ",";
223 as_js_array
+= base::StringPrintf("'%s'", non_messaging_apis
[i
]);
228 CHECK(content::ExecuteScriptInFrameAndExtractBool(
229 browser()->tab_strip_model()->GetActiveWebContents(),
231 "assertions.areAnyRuntimePropertiesDefined(" + as_js_array
+ ")",
234 testing::AssertionSuccess() : testing::AssertionFailure();
237 GURL
GetURLForPath(const std::string
& host
, const std::string
& path
) {
238 std::string port
= base::IntToString(embedded_test_server()->port());
239 GURL::Replacements replacements
;
240 replacements
.SetHostStr(host
);
241 replacements
.SetPortStr(port
);
242 return embedded_test_server()->GetURL(path
).ReplaceComponents(replacements
);
245 GURL
chromium_org_url() {
246 return GetURLForPath("www.chromium.org", "/chromium.org.html");
249 GURL
google_com_url() {
250 return GetURLForPath("www.google.com", "/google.com.html");
253 const Extension
* LoadChromiumConnectableExtension() {
254 const Extension
* extension
=
255 LoadExtensionIntoDir(&web_connectable_dir_
, base::StringPrintf(
257 " \"name\": \"chromium_connectable\","
259 " \"externally_connectable\": {"
260 " \"matches\": [\"*://*.chromium.org:*/*\"]"
268 const Extension
* LoadNotConnectableExtension() {
269 const Extension
* extension
=
270 LoadExtensionIntoDir(¬_connectable_dir_
, base::StringPrintf(
272 " \"name\": \"not_connectable\","
280 void InitializeTestServer() {
281 base::FilePath test_data
;
282 EXPECT_TRUE(PathService::Get(chrome::DIR_TEST_DATA
, &test_data
));
283 embedded_test_server()->ServeFilesFromDirectory(test_data
.AppendASCII(
284 "extensions/api_test/messaging/externally_connectable/sites"));
285 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
286 host_resolver()->AddRule("*", embedded_test_server()->base_url().host());
289 const char* close_background_message() {
290 return "closeBackgroundPage";
294 const Extension
* LoadExtensionIntoDir(TestExtensionDir
* dir
,
295 const std::string
& manifest
) {
296 dir
->WriteManifest(manifest
);
297 dir
->WriteFile(FILE_PATH_LITERAL("background.js"),
299 "function maybeClose(message) {\n"
300 " if (message.indexOf('%s') >= 0)\n"
301 " window.setTimeout(function() { window.close() }, 0);\n"
303 "chrome.runtime.onMessageExternal.addListener(\n"
304 " function(message, sender, reply) {\n"
305 " reply({ message: message, sender: sender });\n"
306 " maybeClose(message);\n"
308 "chrome.runtime.onConnectExternal.addListener(function(port) {\n"
309 " port.onMessage.addListener(function(message) {\n"
310 " port.postMessage({ message: message, sender: port.sender });\n"
311 " maybeClose(message);\n"
314 close_background_message()));
315 return LoadExtension(dir
->unpacked_path());
318 const char* common_manifest() {
319 return "\"version\": \"1.0\","
321 " \"scripts\": [\"background.js\"],"
322 " \"persistent\": false"
324 "\"manifest_version\": 2";
327 TestExtensionDir web_connectable_dir_
;
328 TestExtensionDir not_connectable_dir_
;
331 IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest
, NotInstalled
) {
332 InitializeTestServer();
334 const char kFakeId
[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
336 ui_test_utils::NavigateToURL(browser(), chromium_org_url());
337 EXPECT_EQ(NAMESPACE_NOT_DEFINED
, CanConnectAndSendMessages(kFakeId
));
338 EXPECT_FALSE(AreAnyNonWebApisDefined());
340 ui_test_utils::NavigateToURL(browser(), google_com_url());
341 EXPECT_EQ(NAMESPACE_NOT_DEFINED
, CanConnectAndSendMessages(kFakeId
));
342 EXPECT_FALSE(AreAnyNonWebApisDefined());
345 // Tests two extensions on the same sites: one web connectable, one not.
346 IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest
,
347 WebConnectableAndNotConnectable
) {
348 InitializeTestServer();
350 // Install the web connectable extension. chromium.org can connect to it,
352 const Extension
* chromium_connectable
= LoadChromiumConnectableExtension();
354 ui_test_utils::NavigateToURL(browser(), chromium_org_url());
355 EXPECT_EQ(OK
, CanConnectAndSendMessages(chromium_connectable
->id()));
356 EXPECT_FALSE(AreAnyNonWebApisDefined());
358 ui_test_utils::NavigateToURL(browser(), google_com_url());
359 EXPECT_EQ(NAMESPACE_NOT_DEFINED
,
360 CanConnectAndSendMessages(chromium_connectable
->id()));
361 EXPECT_FALSE(AreAnyNonWebApisDefined());
363 // Install the non-connectable extension. Nothing can connect to it.
364 const Extension
* not_connectable
= LoadNotConnectableExtension();
366 ui_test_utils::NavigateToURL(browser(), chromium_org_url());
367 // Namespace will be defined here because |chromium_connectable| can connect
368 // to it - so this will be the "cannot establish connection" error.
369 EXPECT_EQ(COULD_NOT_ESTABLISH_CONNECTION_ERROR
,
370 CanConnectAndSendMessages(not_connectable
->id()));
371 EXPECT_FALSE(AreAnyNonWebApisDefined());
373 ui_test_utils::NavigateToURL(browser(), google_com_url());
374 EXPECT_EQ(NAMESPACE_NOT_DEFINED
,
375 CanConnectAndSendMessages(not_connectable
->id()));
376 EXPECT_FALSE(AreAnyNonWebApisDefined());
379 IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest
,
380 BackgroundPageClosesOnMessageReceipt
) {
381 InitializeTestServer();
383 // Install the web connectable extension.
384 const Extension
* chromium_connectable
= LoadChromiumConnectableExtension();
386 ui_test_utils::NavigateToURL(browser(), chromium_org_url());
387 // If the background page closes after receipt of the message, it will still
388 // reply to this message...
389 EXPECT_EQ(OK
, CanConnectAndSendMessages(chromium_connectable
->id(),
391 close_background_message()));
392 // and be re-opened by receipt of a subsequent message.
393 EXPECT_EQ(OK
, CanConnectAndSendMessages(chromium_connectable
->id()));
396 // Tests that enabling and disabling an extension makes the runtime bindings
397 // appear and disappear.
399 // TODO(kalman): Test with multiple extensions that can be accessed by the same
401 IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest
,
402 EnablingAndDisabling
) {
403 InitializeTestServer();
405 const Extension
* chromium_connectable
= LoadChromiumConnectableExtension();
406 const Extension
* not_connectable
= LoadNotConnectableExtension();
408 ui_test_utils::NavigateToURL(browser(), chromium_org_url());
409 EXPECT_EQ(OK
, CanConnectAndSendMessages(chromium_connectable
->id()));
410 EXPECT_EQ(COULD_NOT_ESTABLISH_CONNECTION_ERROR
,
411 CanConnectAndSendMessages(not_connectable
->id()));
413 DisableExtension(chromium_connectable
->id());
414 EXPECT_EQ(COULD_NOT_ESTABLISH_CONNECTION_ERROR
,
415 CanConnectAndSendMessages(chromium_connectable
->id()));
417 EnableExtension(chromium_connectable
->id());
418 EXPECT_EQ(OK
, CanConnectAndSendMessages(chromium_connectable
->id()));
419 EXPECT_EQ(COULD_NOT_ESTABLISH_CONNECTION_ERROR
,
420 CanConnectAndSendMessages(not_connectable
->id()));
423 // Tests connection from incognito tabs. Spanning mode only.
425 // TODO(kalman): ensure that we exercise split vs spanning incognito logic
426 // somewhere. This is a test that should be shared with the content script logic
427 // so it's not really our specific concern for web connectable.
428 IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest
, FromIncognito
) {
429 InitializeTestServer();
431 const Extension
* chromium_connectable
= LoadChromiumConnectableExtension();
433 Browser
* incognito_browser
= ui_test_utils::OpenURLOffTheRecord(
434 profile()->GetOffTheRecordProfile(),
437 // No connection because incognito enabled hasn't been set.
438 const std::string
& id
= chromium_connectable
->id();
439 EXPECT_EQ(COULD_NOT_ESTABLISH_CONNECTION_ERROR
,
440 CanConnectAndSendMessages(incognito_browser
, id
));
443 ExtensionPrefs::Get(profile())->SetIsIncognitoEnabled(id
, true);
444 EXPECT_EQ(OK
, CanConnectAndSendMessages(incognito_browser
, id
));
447 // Tests a connection from an iframe within a tab which doesn't have
448 // permission. Iframe should work.
449 IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest
,
450 FromIframeWithPermission
) {
451 InitializeTestServer();
453 const Extension
* extension
= LoadChromiumConnectableExtension();
455 ui_test_utils::NavigateToURL(browser(), google_com_url());
456 EXPECT_EQ(NAMESPACE_NOT_DEFINED
, CanConnectAndSendMessages(extension
->id()));
457 EXPECT_FALSE(AreAnyNonWebApisDefined());
459 ASSERT_TRUE(AppendIframe(chromium_org_url()));
461 const char* frame_xpath
= "//iframe[1]";
462 EXPECT_EQ(OK
, CanConnectAndSendMessages(extension
->id(), frame_xpath
));
463 EXPECT_FALSE(AreAnyNonWebApisDefined(frame_xpath
));
466 // Tests connection from an iframe without permission within a tab that does.
467 // Iframe shouldn't work.
468 IN_PROC_BROWSER_TEST_F(ExternallyConnectableMessagingTest
,
469 FromIframeWithoutPermission
) {
470 InitializeTestServer();
472 const Extension
* extension
= LoadChromiumConnectableExtension();
474 ui_test_utils::NavigateToURL(browser(), chromium_org_url());
475 EXPECT_EQ(OK
, CanConnectAndSendMessages(extension
->id()));
476 EXPECT_FALSE(AreAnyNonWebApisDefined());
478 ASSERT_TRUE(AppendIframe(google_com_url()));
480 const char* frame_xpath
= "//iframe[1]";
481 EXPECT_EQ(NAMESPACE_NOT_DEFINED
,
482 CanConnectAndSendMessages(extension
->id(), frame_xpath
));
483 EXPECT_FALSE(AreAnyNonWebApisDefined(frame_xpath
));
487 }; // namespace extensions