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.
6 #include "base/callback.h"
7 #include "base/strings/stringprintf.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "chrome/browser/extensions/api/permissions/permissions_api.h"
10 #include "chrome/browser/extensions/extension_apitest.h"
11 #include "chrome/browser/extensions/extension_service.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/common/chrome_switches.h"
16 #include "chrome/test/base/ui_test_utils.h"
17 #include "components/app_modal/javascript_dialog_extensions_client.h"
18 #include "components/app_modal/javascript_dialog_manager.h"
19 #include "content/public/browser/javascript_dialog_manager.h"
20 #include "content/public/browser/web_contents.h"
21 #include "content/public/browser/web_contents_delegate.h"
22 #include "content/public/test/browser_test_utils.h"
23 #include "extensions/browser/notification_types.h"
24 #include "extensions/common/extension.h"
25 #include "extensions/test/extension_test_message_listener.h"
26 #include "extensions/test/result_catcher.h"
27 #include "net/dns/mock_host_resolver.h"
28 #include "net/test/embedded_test_server/embedded_test_server.h"
31 namespace extensions
{
35 // A fake webstore domain.
36 const char kWebstoreDomain
[] = "cws.com";
38 // Check whether or not style was injected, with |expected_injection| indicating
39 // the expected result. Also ensure that no CSS was added to the
40 // document.styleSheets array.
41 testing::AssertionResult
CheckStyleInjection(Browser
* browser
,
43 bool expected_injection
) {
44 ui_test_utils::NavigateToURL(browser
, url
);
46 bool css_injected
= false;
47 if (!content::ExecuteScriptAndExtractBool(
48 browser
->tab_strip_model()->GetActiveWebContents(),
49 "window.domAutomationController.send("
50 " document.defaultView.getComputedStyle(document.body, null)."
51 " getPropertyValue('display') == 'none');",
53 return testing::AssertionFailure()
54 << "Failed to execute script and extract bool for injection status.";
57 if (css_injected
!= expected_injection
) {
60 message
= "CSS injected when no injection was expected.";
62 message
= "CSS not injected when injection was expected.";
63 return testing::AssertionFailure() << message
;
66 bool css_doesnt_add_to_list
= false;
67 if (!content::ExecuteScriptAndExtractBool(
68 browser
->tab_strip_model()->GetActiveWebContents(),
69 "window.domAutomationController.send("
70 " document.styleSheets.length == 0);",
71 &css_doesnt_add_to_list
)) {
72 return testing::AssertionFailure()
73 << "Failed to execute script and extract bool for stylesheets length.";
75 if (!css_doesnt_add_to_list
) {
76 return testing::AssertionFailure()
77 << "CSS injection added to number of stylesheets.";
80 return testing::AssertionSuccess();
85 // A helper class to hijack the dialog manager's ExtensionsClient, so that we
86 // know when dialogs are being opened.
87 // NOTE: The default implementation of the JavaScriptDialogExtensionsClient
88 // doesn't do anything, so it's safe to override it. If, at some stage, this
89 // has behavior (like if we move this into app shell), we'll need to update
90 // this (by, e.g., making DialogClient a wrapper around the implementation).
93 explicit DialogHelper(content::WebContents
* web_contents
);
96 // Notifies the DialogHelper that a dialog was opened. Runs |quit_closure_|,
100 // Closes any active dialogs.
103 void set_quit_closure(const base::Closure
& quit_closure
) {
104 quit_closure_
= quit_closure
;
106 size_t dialog_count() const { return dialog_count_
; }
109 // The number of dialogs to appear.
110 size_t dialog_count_
;
112 // The WebContents this helper is associated with.
113 content::WebContents
* web_contents_
;
115 // The dialog manager for |web_contents_|.
116 content::JavaScriptDialogManager
* dialog_manager_
;
118 // The dialog client override.
119 DialogClient
* client_
;
121 // The quit closure to run when a dialog appears.
122 base::Closure quit_closure_
;
124 DISALLOW_COPY_AND_ASSIGN(DialogHelper
);
127 // The client override for the DialogHelper.
128 class DialogClient
: public app_modal::JavaScriptDialogExtensionsClient
{
130 explicit DialogClient(DialogHelper
* helper
) : helper_(helper
) {}
131 ~DialogClient() override
{}
133 void set_helper(DialogHelper
* helper
) { helper_
= helper
; }
136 // app_modal::JavaScriptDialogExtensionsClient:
137 void OnDialogOpened(content::WebContents
* web_contents
) override
{
139 helper_
->DialogOpened();
141 void OnDialogClosed(content::WebContents
* web_contents
) override
{}
142 bool GetExtensionName(content::WebContents
* web_contents
,
143 const GURL
& origin_url
,
144 std::string
* name_out
) override
{
148 // The dialog helper to notify of any open dialogs.
149 DialogHelper
* helper_
;
151 DISALLOW_COPY_AND_ASSIGN(DialogClient
);
154 DialogHelper::DialogHelper(content::WebContents
* web_contents
)
156 web_contents_(web_contents
),
157 dialog_manager_(nullptr),
159 app_modal::JavaScriptDialogManager
* dialog_manager_impl
=
160 app_modal::JavaScriptDialogManager::GetInstance();
162 web_contents_
->GetDelegate()->GetJavaScriptDialogManager(web_contents_
);
163 DCHECK_EQ(dialog_manager_impl
, dialog_manager_
);
165 client_
= new DialogClient(this);
166 dialog_manager_impl
->SetExtensionsClient(make_scoped_ptr(client_
));
169 DialogHelper::~DialogHelper() {
170 client_
->set_helper(nullptr);
173 void DialogHelper::CloseDialogs() {
174 dialog_manager_
->CancelActiveAndPendingDialogs(web_contents_
);
177 void DialogHelper::DialogOpened() {
179 if (!quit_closure_
.is_null()) {
181 quit_closure_
= base::Closure();
185 // Runs all pending tasks in the renderer associated with |web_contents|, and
186 // then all pending tasks in the browser process.
187 // Returns true on success.
188 bool RunAllPending(content::WebContents
* web_contents
) {
189 // This is slight hack to achieve a RunPendingInRenderer() method. Since IPCs
190 // are sent synchronously, anything started prior to this method will finish
191 // before this method returns (as content::ExecuteScript() is synchronous).
192 if (!content::ExecuteScript(web_contents
, "1 == 1;"))
194 base::RunLoop().RunUntilIdle();
198 // A simple extension manifest with content scripts on all pages.
199 const char kManifest
[] =
202 " \"version\": \"1.0\","
203 " \"manifest_version\": 2,"
204 " \"content_scripts\": [{"
205 " \"matches\": [\"*://*/*\"],"
206 " \"js\": [\"script.js\"],"
207 " \"run_at\": \"%s\""
211 // A (blocking) content script that pops up an alert.
212 const char kBlockingScript
[] = "alert('ALERT');";
214 // A (non-blocking) content script that sends a message.
215 const char kNonBlockingScript
[] = "chrome.test.sendMessage('done');";
219 IN_PROC_BROWSER_TEST_F(ExtensionApiTest
, ContentScriptAllFrames
) {
220 ASSERT_TRUE(StartEmbeddedTestServer());
221 ASSERT_TRUE(RunExtensionTest("content_scripts/all_frames")) << message_
;
224 IN_PROC_BROWSER_TEST_F(ExtensionApiTest
, ContentScriptAboutBlankIframes
) {
225 ASSERT_TRUE(StartEmbeddedTestServer());
227 RunExtensionTest("content_scripts/about_blank_iframes")) << message_
;
230 IN_PROC_BROWSER_TEST_F(ExtensionApiTest
, ContentScriptAboutBlankAndSrcdoc
) {
231 // The optional "*://*/*" permission is requested after verifying that
232 // content script insertion solely depends on content_scripts[*].matches.
233 // The permission is needed for chrome.tabs.executeScript tests.
234 PermissionsRequestFunction::SetAutoConfirmForTests(true);
235 PermissionsRequestFunction::SetIgnoreUserGestureForTests(true);
237 ASSERT_TRUE(StartEmbeddedTestServer());
238 ASSERT_TRUE(RunExtensionTest("content_scripts/about_blank_srcdoc"))
242 IN_PROC_BROWSER_TEST_F(ExtensionApiTest
, ContentScriptExtensionIframe
) {
243 ASSERT_TRUE(StartEmbeddedTestServer());
244 ASSERT_TRUE(RunExtensionTest("content_scripts/extension_iframe")) << message_
;
247 IN_PROC_BROWSER_TEST_F(ExtensionApiTest
, ContentScriptExtensionProcess
) {
248 ASSERT_TRUE(StartEmbeddedTestServer());
250 RunExtensionTest("content_scripts/extension_process")) << message_
;
253 IN_PROC_BROWSER_TEST_F(ExtensionApiTest
, ContentScriptFragmentNavigation
) {
254 ASSERT_TRUE(StartEmbeddedTestServer());
255 const char extension_name
[] = "content_scripts/fragment";
256 ASSERT_TRUE(RunExtensionTest(extension_name
)) << message_
;
259 // Times out on Linux: http://crbug.com/163097
260 #if defined(OS_LINUX)
261 #define MAYBE_ContentScriptIsolatedWorlds DISABLED_ContentScriptIsolatedWorlds
263 #define MAYBE_ContentScriptIsolatedWorlds ContentScriptIsolatedWorlds
265 IN_PROC_BROWSER_TEST_F(ExtensionApiTest
, MAYBE_ContentScriptIsolatedWorlds
) {
266 // This extension runs various bits of script and tests that they all run in
267 // the same isolated world.
268 ASSERT_TRUE(StartEmbeddedTestServer());
269 ASSERT_TRUE(RunExtensionTest("content_scripts/isolated_world1")) << message_
;
271 // Now load a different extension, inject into same page, verify worlds aren't
273 ASSERT_TRUE(RunExtensionTest("content_scripts/isolated_world2")) << message_
;
276 IN_PROC_BROWSER_TEST_F(ExtensionApiTest
, ContentScriptIgnoreHostPermissions
) {
277 host_resolver()->AddRule("a.com", "127.0.0.1");
278 host_resolver()->AddRule("b.com", "127.0.0.1");
279 ASSERT_TRUE(StartEmbeddedTestServer());
280 ASSERT_TRUE(RunExtensionTest(
281 "content_scripts/dont_match_host_permissions")) << message_
;
284 // crbug.com/39249 -- content scripts js should not run on view source.
285 IN_PROC_BROWSER_TEST_F(ExtensionApiTest
, ContentScriptViewSource
) {
286 ASSERT_TRUE(StartEmbeddedTestServer());
287 ASSERT_TRUE(RunExtensionTest("content_scripts/view_source")) << message_
;
290 // crbug.com/126257 -- content scripts should not get injected into other
292 IN_PROC_BROWSER_TEST_F(ExtensionApiTest
, ContentScriptOtherExtensions
) {
293 host_resolver()->AddRule("a.com", "127.0.0.1");
294 ASSERT_TRUE(StartEmbeddedTestServer());
295 // First, load extension that sets up content script.
296 ASSERT_TRUE(RunExtensionTest("content_scripts/other_extensions/injector"))
298 // Then load targeted extension to make sure its content isn't changed.
299 ASSERT_TRUE(RunExtensionTest("content_scripts/other_extensions/victim"))
303 class ContentScriptCssInjectionTest
: public ExtensionApiTest
{
305 // TODO(rdevlin.cronin): Make a testing switch that looks like FeatureSwitch,
306 // but takes in an optional value so that we don't have to do this.
307 void SetUpCommandLine(base::CommandLine
* command_line
) override
{
308 ExtensionApiTest::SetUpCommandLine(command_line
);
309 // We change the Webstore URL to be http://cws.com. We need to do this so
310 // we can check that css injection is not allowed on the webstore (which
311 // could lead to spoofing). Unfortunately, host_resolver seems to have
312 // problems with redirecting "chrome.google.com" to the test server, so we
313 // can't use the real Webstore's URL. If this changes, we could clean this
315 command_line
->AppendSwitchASCII(
316 switches::kAppsGalleryURL
,
317 base::StringPrintf("http://%s", kWebstoreDomain
));
321 IN_PROC_BROWSER_TEST_F(ContentScriptCssInjectionTest
,
322 ContentScriptInjectsStyles
) {
323 ASSERT_TRUE(StartEmbeddedTestServer());
324 host_resolver()->AddRule(kWebstoreDomain
, "127.0.0.1");
326 ASSERT_TRUE(LoadExtension(test_data_dir_
.AppendASCII("content_scripts")
327 .AppendASCII("css_injection")));
329 // CSS injection should be allowed on an aribitrary web page.
331 embedded_test_server()->GetURL("/extensions/test_file_with_body.html");
332 EXPECT_TRUE(CheckStyleInjection(browser(), url
, true));
334 // The loaded extension has an exclude match for "extensions/test_file.html",
335 // so no CSS should be injected.
336 url
= embedded_test_server()->GetURL("/extensions/test_file.html");
337 EXPECT_TRUE(CheckStyleInjection(browser(), url
, false));
339 // We disallow all injection on the webstore.
340 GURL::Replacements replacements
;
341 replacements
.SetHostStr(kWebstoreDomain
);
342 url
= embedded_test_server()->GetURL("/extensions/test_file_with_body.html")
343 .ReplaceComponents(replacements
);
344 EXPECT_TRUE(CheckStyleInjection(browser(), url
, false));
348 IN_PROC_BROWSER_TEST_F(
350 DISABLED_ContentScriptStylesInjectedIntoExistingRenderers
) {
351 ASSERT_TRUE(StartEmbeddedTestServer());
353 content::WindowedNotificationObserver
signal(
354 extensions::NOTIFICATION_USER_SCRIPTS_UPDATED
,
355 content::Source
<Profile
>(browser()->profile()));
357 // Start with a renderer already open at a URL.
358 GURL
url(test_server()->GetURL("file/extensions/test_file.html"));
359 ui_test_utils::NavigateToURL(browser(), url
);
362 test_data_dir_
.AppendASCII("content_scripts/existing_renderers"));
366 // And check that its styles were affected by the styles that just got loaded.
367 bool styles_injected
;
368 ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
369 browser()->tab_strip_model()->GetActiveWebContents(),
370 "window.domAutomationController.send("
371 " document.defaultView.getComputedStyle(document.body, null)."
372 " getPropertyValue('background-color') == 'rgb(255, 0, 0)')",
374 ASSERT_TRUE(styles_injected
);
377 IN_PROC_BROWSER_TEST_F(ExtensionApiTest
,
378 ContentScriptCSSLocalization
) {
379 ASSERT_TRUE(StartEmbeddedTestServer());
380 ASSERT_TRUE(RunExtensionTest("content_scripts/css_l10n")) << message_
;
383 IN_PROC_BROWSER_TEST_F(ExtensionApiTest
, ContentScriptExtensionAPIs
) {
384 ASSERT_TRUE(StartEmbeddedTestServer());
386 const extensions::Extension
* extension
= LoadExtension(
387 test_data_dir_
.AppendASCII("content_scripts/extension_api"));
389 ResultCatcher catcher
;
390 ui_test_utils::NavigateToURL(
392 embedded_test_server()->GetURL(
393 "/extensions/api_test/content_scripts/extension_api/functions.html"));
394 EXPECT_TRUE(catcher
.GetNextResult());
396 // Navigate to a page that will cause a content script to run that starts
397 // listening for an extension event.
398 ui_test_utils::NavigateToURL(
400 embedded_test_server()->GetURL(
401 "/extensions/api_test/content_scripts/extension_api/events.html"));
403 // Navigate to an extension page that will fire the event events.js is
405 ui_test_utils::NavigateToURLWithDisposition(
406 browser(), extension
->GetResourceURL("fire_event.html"),
407 NEW_FOREGROUND_TAB
, ui_test_utils::BROWSER_TEST_NONE
);
408 EXPECT_TRUE(catcher
.GetNextResult());
411 // Flaky on Windows. http://crbug.com/248418
413 #define MAYBE_ContentScriptPermissionsApi DISABLED_ContentScriptPermissionsApi
415 #define MAYBE_ContentScriptPermissionsApi ContentScriptPermissionsApi
417 IN_PROC_BROWSER_TEST_F(ExtensionApiTest
, MAYBE_ContentScriptPermissionsApi
) {
418 extensions::PermissionsRequestFunction::SetIgnoreUserGestureForTests(true);
419 extensions::PermissionsRequestFunction::SetAutoConfirmForTests(true);
420 host_resolver()->AddRule("*.com", "127.0.0.1");
421 ASSERT_TRUE(StartEmbeddedTestServer());
422 ASSERT_TRUE(RunExtensionTest("content_scripts/permissions")) << message_
;
425 IN_PROC_BROWSER_TEST_F(ExtensionApiTest
, ContentScriptBypassPageCSP
) {
426 ASSERT_TRUE(StartEmbeddedTestServer());
427 ASSERT_TRUE(RunExtensionTest("content_scripts/bypass_page_csp")) << message_
;
430 // Test that when injecting a blocking content script, other scripts don't run
431 // until the blocking script finishes.
432 IN_PROC_BROWSER_TEST_F(ExtensionApiTest
, ContentScriptBlockingScript
) {
433 ASSERT_TRUE(StartEmbeddedTestServer());
435 // Load up two extensions.
436 TestExtensionDir ext_dir1
;
437 ext_dir1
.WriteManifest(
438 base::StringPrintf(kManifest
, "ext1", "document_start"));
439 ext_dir1
.WriteFile(FILE_PATH_LITERAL("script.js"), kBlockingScript
);
440 const Extension
* ext1
= LoadExtension(ext_dir1
.unpacked_path());
443 TestExtensionDir ext_dir2
;
444 ext_dir2
.WriteManifest(base::StringPrintf(kManifest
, "ext2", "document_end"));
445 ext_dir2
.WriteFile(FILE_PATH_LITERAL("script.js"), kNonBlockingScript
);
446 const Extension
* ext2
= LoadExtension(ext_dir2
.unpacked_path());
449 content::WebContents
* web_contents
=
450 browser()->tab_strip_model()->GetActiveWebContents();
451 DialogHelper
dialog_helper(web_contents
);
452 base::RunLoop run_loop
;
453 dialog_helper
.set_quit_closure(run_loop
.QuitClosure());
455 ExtensionTestMessageListener
listener("done", false);
456 listener
.set_extension_id(ext2
->id());
458 // Navigate! Both extensions will try to inject.
459 ui_test_utils::NavigateToURLWithDisposition(
461 embedded_test_server()->GetURL("/empty.html"),
463 ui_test_utils::BROWSER_TEST_NONE
);
466 // Right now, the alert dialog is showing and blocking injection of anything
467 // after it, so the listener shouldn't be satisfied.
468 EXPECT_TRUE(RunAllPending(web_contents
));
469 EXPECT_FALSE(listener
.was_satisfied());
470 EXPECT_EQ(1u, dialog_helper
.dialog_count());
471 dialog_helper
.CloseDialogs();
473 // After closing the dialog, the rest of the scripts should be able to
475 EXPECT_TRUE(listener
.WaitUntilSatisfied());
478 // Test that closing a tab with a blocking script results in no further scripts
479 // running (and we don't crash).
480 IN_PROC_BROWSER_TEST_F(ExtensionApiTest
, ContentScriptBlockingScriptTabClosed
) {
481 ASSERT_TRUE(StartEmbeddedTestServer());
483 // We're going to close a tab in this test, so make a new one (to ensure
484 // we don't close the browser).
485 ui_test_utils::NavigateToURLWithDisposition(
487 embedded_test_server()->GetURL("/empty.html"),
489 ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION
);
491 // Set up the same as the previous test case.
492 TestExtensionDir ext_dir1
;
493 ext_dir1
.WriteManifest(
494 base::StringPrintf(kManifest
, "ext1", "document_start"));
495 ext_dir1
.WriteFile(FILE_PATH_LITERAL("script.js"), kBlockingScript
);
496 const Extension
* ext1
= LoadExtension(ext_dir1
.unpacked_path());
499 TestExtensionDir ext_dir2
;
500 ext_dir2
.WriteManifest(base::StringPrintf(kManifest
, "ext2", "document_end"));
501 ext_dir2
.WriteFile(FILE_PATH_LITERAL("script.js"), kNonBlockingScript
);
502 const Extension
* ext2
= LoadExtension(ext_dir2
.unpacked_path());
505 content::WebContents
* web_contents
=
506 browser()->tab_strip_model()->GetActiveWebContents();
507 DialogHelper
dialog_helper(web_contents
);
508 base::RunLoop run_loop
;
509 dialog_helper
.set_quit_closure(run_loop
.QuitClosure());
511 ExtensionTestMessageListener
listener("done", false);
512 listener
.set_extension_id(ext2
->id());
515 ui_test_utils::NavigateToURLWithDisposition(
517 embedded_test_server()->GetURL("/empty.html"),
519 ui_test_utils::BROWSER_TEST_NONE
);
521 // Now, instead of closing the dialog, just close the tab. Later scripts
522 // should never get a chance to run (and we shouldn't crash).
524 EXPECT_TRUE(RunAllPending(web_contents
));
525 EXPECT_FALSE(listener
.was_satisfied());
526 EXPECT_TRUE(browser()->tab_strip_model()->CloseWebContentsAt(
527 browser()->tab_strip_model()->active_index(), 0));
528 EXPECT_FALSE(listener
.was_satisfied());
531 // There was a bug by which content scripts that blocked and ran on
532 // document_idle could be injected twice (crbug.com/431263). Test for
534 IN_PROC_BROWSER_TEST_F(ExtensionApiTest
,
535 ContentScriptBlockingScriptsDontRunTwice
) {
536 ASSERT_TRUE(StartEmbeddedTestServer());
538 // Load up an extension.
539 TestExtensionDir ext_dir1
;
540 ext_dir1
.WriteManifest(
541 base::StringPrintf(kManifest
, "ext1", "document_idle"));
542 ext_dir1
.WriteFile(FILE_PATH_LITERAL("script.js"), kBlockingScript
);
543 const Extension
* ext1
= LoadExtension(ext_dir1
.unpacked_path());
546 content::WebContents
* web_contents
=
547 browser()->tab_strip_model()->GetActiveWebContents();
548 DialogHelper
dialog_helper(web_contents
);
549 base::RunLoop run_loop
;
550 dialog_helper
.set_quit_closure(run_loop
.QuitClosure());
553 ui_test_utils::NavigateToURLWithDisposition(
555 embedded_test_server()->GetURL("/empty.html"),
557 ui_test_utils::BROWSER_TEST_NONE
);
561 // The extension will have injected at idle, but it should only inject once.
562 EXPECT_TRUE(RunAllPending(web_contents
));
563 EXPECT_EQ(1u, dialog_helper
.dialog_count());
564 dialog_helper
.CloseDialogs();
565 EXPECT_TRUE(RunAllPending(web_contents
));
566 EXPECT_EQ(1u, dialog_helper
.dialog_count());
569 } // namespace extensions