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 "chrome/browser/extensions/extension_browsertest.h"
9 #include "base/command_line.h"
10 #include "base/files/file_path.h"
11 #include "base/files/file_util.h"
12 #include "base/files/scoped_temp_dir.h"
13 #include "base/path_service.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "chrome/browser/extensions/browsertest_util.h"
18 #include "chrome/browser/extensions/component_loader.h"
19 #include "chrome/browser/extensions/crx_installer.h"
20 #include "chrome/browser/extensions/extension_creator.h"
21 #include "chrome/browser/extensions/extension_error_reporter.h"
22 #include "chrome/browser/extensions/extension_install_prompt.h"
23 #include "chrome/browser/extensions/extension_service.h"
24 #include "chrome/browser/extensions/extension_util.h"
25 #include "chrome/browser/extensions/unpacked_installer.h"
26 #include "chrome/browser/extensions/updater/extension_cache_fake.h"
27 #include "chrome/browser/extensions/updater/extension_updater.h"
28 #include "chrome/browser/profiles/profile.h"
29 #include "chrome/browser/profiles/profile_manager.h"
30 #include "chrome/browser/ui/browser.h"
31 #include "chrome/browser/ui/browser_window.h"
32 #include "chrome/browser/ui/tabs/tab_strip_model.h"
33 #include "chrome/common/chrome_paths.h"
34 #include "chrome/common/chrome_switches.h"
35 #include "chrome/common/chrome_version_info.h"
36 #include "chrome/test/base/ui_test_utils.h"
37 #include "content/public/browser/navigation_controller.h"
38 #include "content/public/browser/navigation_entry.h"
39 #include "content/public/browser/notification_registrar.h"
40 #include "content/public/browser/notification_service.h"
41 #include "content/public/browser/render_view_host.h"
42 #include "content/public/test/browser_test_utils.h"
43 #include "extensions/browser/extension_host.h"
44 #include "extensions/browser/extension_prefs.h"
45 #include "extensions/browser/extension_system.h"
46 #include "extensions/browser/notification_types.h"
47 #include "extensions/browser/uninstall_reason.h"
48 #include "extensions/common/constants.h"
49 #include "extensions/common/extension_set.h"
50 #include "sync/api/string_ordinal.h"
52 #if defined(OS_CHROMEOS)
53 #include "chromeos/chromeos_switches.h"
56 using extensions::Extension
;
57 using extensions::ExtensionCreator
;
58 using extensions::FeatureSwitch
;
59 using extensions::Manifest
;
61 ExtensionBrowserTest::ExtensionBrowserTest()
64 #if defined(OS_CHROMEOS)
65 set_chromeos_user_(true),
67 // Default channel is STABLE but override with UNKNOWN so that unlaunched
68 // or incomplete APIs can write tests.
69 current_channel_(chrome::VersionInfo::CHANNEL_UNKNOWN
),
70 override_prompt_for_external_extensions_(
71 FeatureSwitch::prompt_for_external_extensions(),
74 user_desktop_override_(base::DIR_USER_DESKTOP
),
75 common_desktop_override_(base::DIR_COMMON_DESKTOP
),
76 user_quick_launch_override_(base::DIR_USER_QUICK_LAUNCH
),
77 start_menu_override_(base::DIR_START_MENU
),
78 common_start_menu_override_(base::DIR_COMMON_START_MENU
),
81 EXPECT_TRUE(temp_dir_
.CreateUniqueTempDir());
84 ExtensionBrowserTest::~ExtensionBrowserTest() {
87 Profile
* ExtensionBrowserTest::profile() {
90 profile_
= browser()->profile();
92 profile_
= ProfileManager::GetActiveUserProfile();
98 const Extension
* ExtensionBrowserTest::GetExtensionByPath(
99 const extensions::ExtensionSet
* extensions
, const base::FilePath
& path
) {
100 base::FilePath extension_path
= base::MakeAbsoluteFilePath(path
);
101 EXPECT_TRUE(!extension_path
.empty());
102 for (extensions::ExtensionSet::const_iterator iter
= extensions
->begin();
103 iter
!= extensions
->end(); ++iter
) {
104 if ((*iter
)->path() == extension_path
) {
111 void ExtensionBrowserTest::SetUp() {
112 test_extension_cache_
.reset(new extensions::ExtensionCacheFake());
113 InProcessBrowserTest::SetUp();
116 void ExtensionBrowserTest::SetUpCommandLine(CommandLine
* command_line
) {
117 PathService::Get(chrome::DIR_TEST_DATA
, &test_data_dir_
);
118 test_data_dir_
= test_data_dir_
.AppendASCII("extensions");
119 observer_
.reset(new ExtensionTestNotificationObserver(browser()));
121 #if defined(OS_CHROMEOS)
122 if (set_chromeos_user_
) {
123 // This makes sure that we create the Default profile first, with no
124 // ExtensionService and then the real profile with one, as we do when
125 // running on chromeos.
126 command_line
->AppendSwitchASCII(chromeos::switches::kLoginUser
,
127 "TestUser@gmail.com");
128 command_line
->AppendSwitchASCII(chromeos::switches::kLoginProfile
, "user");
133 void ExtensionBrowserTest::SetUpOnMainThread() {
134 InProcessBrowserTest::SetUpOnMainThread();
135 observer_
.reset(new ExtensionTestNotificationObserver(browser()));
136 if (extension_service()->updater()) {
137 extension_service()->updater()->SetExtensionCacheForTesting(
138 test_extension_cache_
.get());
142 const Extension
* ExtensionBrowserTest::LoadExtension(
143 const base::FilePath
& path
) {
144 return LoadExtensionWithFlags(path
, kFlagEnableFileAccess
);
147 const Extension
* ExtensionBrowserTest::LoadExtensionIncognito(
148 const base::FilePath
& path
) {
149 return LoadExtensionWithFlags(path
,
150 kFlagEnableFileAccess
| kFlagEnableIncognito
);
153 const Extension
* ExtensionBrowserTest::LoadExtensionWithFlags(
154 const base::FilePath
& path
, int flags
) {
155 return LoadExtensionWithInstallParam(path
, flags
, std::string());
158 const extensions::Extension
*
159 ExtensionBrowserTest::LoadExtensionWithInstallParam(
160 const base::FilePath
& path
,
162 const std::string
& install_param
) {
163 ExtensionService
* service
= extensions::ExtensionSystem::Get(
164 profile())->extension_service();
166 observer_
->Watch(extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED
,
167 content::NotificationService::AllSources());
169 scoped_refptr
<extensions::UnpackedInstaller
> installer(
170 extensions::UnpackedInstaller::Create(service
));
171 installer
->set_prompt_for_plugins(false);
172 installer
->set_require_modern_manifest_version(
173 (flags
& kFlagAllowOldManifestVersions
) == 0);
174 installer
->Load(path
);
179 // Find the loaded extension by its path. See crbug.com/59531 for why
180 // we cannot just use last_loaded_extension_id().
181 const Extension
* extension
= GetExtensionByPath(service
->extensions(), path
);
185 if (!(flags
& kFlagIgnoreManifestWarnings
)) {
186 const std::vector
<extensions::InstallWarning
>& install_warnings
=
187 extension
->install_warnings();
188 if (!install_warnings
.empty()) {
189 std::string install_warnings_message
= base::StringPrintf(
190 "Unexpected warnings when loading test extension %s:\n",
191 path
.AsUTF8Unsafe().c_str());
193 for (std::vector
<extensions::InstallWarning
>::const_iterator it
=
194 install_warnings
.begin(); it
!= install_warnings
.end(); ++it
) {
195 install_warnings_message
+= " " + it
->message
+ "\n";
198 EXPECT_EQ(0u, extension
->install_warnings().size())
199 << install_warnings_message
;
204 const std::string extension_id
= extension
->id();
206 if (!install_param
.empty()) {
207 extensions::ExtensionPrefs::Get(profile())
208 ->SetInstallParam(extension_id
, install_param
);
209 // Re-enable the extension if needed.
210 if (service
->extensions()->Contains(extension_id
)) {
211 content::WindowedNotificationObserver
load_signal(
212 extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED
,
213 content::Source
<Profile
>(profile()));
214 // Reload the extension so that the
215 // NOTIFICATION_EXTENSION_LOADED_DEPRECATED
216 // observers may access |install_param|.
217 service
->ReloadExtension(extension_id
);
219 extension
= service
->GetExtensionById(extension_id
, false);
220 CHECK(extension
) << extension_id
<< " not found after reloading.";
224 // Toggling incognito or file access will reload the extension, so wait for
225 // the reload and grab the new extension instance. The default state is
226 // incognito disabled and file access enabled, so we don't wait in those
229 content::WindowedNotificationObserver
load_signal(
230 extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED
,
231 content::Source
<Profile
>(profile()));
232 CHECK(!extensions::util::IsIncognitoEnabled(extension_id
, profile()));
234 if (flags
& kFlagEnableIncognito
) {
235 extensions::util::SetIsIncognitoEnabled(extension_id
, profile(), true);
237 extension
= service
->GetExtensionById(extension_id
, false);
238 CHECK(extension
) << extension_id
<< " not found after reloading.";
243 content::WindowedNotificationObserver
load_signal(
244 extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED
,
245 content::Source
<Profile
>(profile()));
246 CHECK(extensions::util::AllowFileAccess(extension_id
, profile()));
247 if (!(flags
& kFlagEnableFileAccess
)) {
248 extensions::util::SetAllowFileAccess(extension_id
, profile(), false);
250 extension
= service
->GetExtensionById(extension_id
, false);
251 CHECK(extension
) << extension_id
<< " not found after reloading.";
255 if (!observer_
->WaitForExtensionViewsToLoad())
261 const Extension
* ExtensionBrowserTest::LoadExtensionAsComponentWithManifest(
262 const base::FilePath
& path
,
263 const base::FilePath::CharType
* manifest_relative_path
) {
264 ExtensionService
* service
= extensions::ExtensionSystem::Get(
265 profile())->extension_service();
267 std::string manifest
;
268 if (!base::ReadFileToString(path
.Append(manifest_relative_path
), &manifest
)) {
272 std::string extension_id
= service
->component_loader()->Add(manifest
, path
);
273 const Extension
* extension
= service
->extensions()->GetByID(extension_id
);
276 observer_
->set_last_loaded_extension_id(extension
->id());
280 const Extension
* ExtensionBrowserTest::LoadExtensionAsComponent(
281 const base::FilePath
& path
) {
282 return LoadExtensionAsComponentWithManifest(path
,
283 extensions::kManifestFilename
);
286 base::FilePath
ExtensionBrowserTest::PackExtension(
287 const base::FilePath
& dir_path
) {
288 base::FilePath crx_path
= temp_dir_
.path().AppendASCII("temp.crx");
289 if (!base::DeleteFile(crx_path
, false)) {
290 ADD_FAILURE() << "Failed to delete crx: " << crx_path
.value();
291 return base::FilePath();
294 // Look for PEM files with the same name as the directory.
295 base::FilePath pem_path
=
296 dir_path
.ReplaceExtension(FILE_PATH_LITERAL(".pem"));
297 base::FilePath pem_path_out
;
299 if (!base::PathExists(pem_path
)) {
300 pem_path
= base::FilePath();
301 pem_path_out
= crx_path
.DirName().AppendASCII("temp.pem");
302 if (!base::DeleteFile(pem_path_out
, false)) {
303 ADD_FAILURE() << "Failed to delete pem: " << pem_path_out
.value();
304 return base::FilePath();
308 return PackExtensionWithOptions(dir_path
, crx_path
, pem_path
, pem_path_out
);
311 base::FilePath
ExtensionBrowserTest::PackExtensionWithOptions(
312 const base::FilePath
& dir_path
,
313 const base::FilePath
& crx_path
,
314 const base::FilePath
& pem_path
,
315 const base::FilePath
& pem_out_path
) {
316 if (!base::PathExists(dir_path
)) {
317 ADD_FAILURE() << "Extension dir not found: " << dir_path
.value();
318 return base::FilePath();
321 if (!base::PathExists(pem_path
) && pem_out_path
.empty()) {
322 ADD_FAILURE() << "Must specify a PEM file or PEM output path";
323 return base::FilePath();
326 scoped_ptr
<ExtensionCreator
> creator(new ExtensionCreator());
327 if (!creator
->Run(dir_path
,
331 ExtensionCreator::kOverwriteCRX
)) {
332 ADD_FAILURE() << "ExtensionCreator::Run() failed: "
333 << creator
->error_message();
334 return base::FilePath();
337 if (!base::PathExists(crx_path
)) {
338 ADD_FAILURE() << crx_path
.value() << " was not created.";
339 return base::FilePath();
344 // This class is used to simulate an installation abort by the user.
345 class MockAbortExtensionInstallPrompt
: public ExtensionInstallPrompt
{
347 MockAbortExtensionInstallPrompt() : ExtensionInstallPrompt(NULL
) {
350 // Simulate a user abort on an extension installation.
351 void ConfirmInstall(Delegate
* delegate
,
352 const Extension
* extension
,
353 const ShowDialogCallback
& show_dialog_callback
) override
{
354 delegate
->InstallUIAbort(true);
355 base::MessageLoopForUI::current()->Quit();
358 void OnInstallSuccess(const Extension
* extension
, SkBitmap
* icon
) override
{}
360 void OnInstallFailure(const extensions::CrxInstallerError
& error
) override
{}
363 class MockAutoConfirmExtensionInstallPrompt
: public ExtensionInstallPrompt
{
365 explicit MockAutoConfirmExtensionInstallPrompt(
366 content::WebContents
* web_contents
)
367 : ExtensionInstallPrompt(web_contents
) {}
369 // Proceed without confirmation prompt.
370 void ConfirmInstall(Delegate
* delegate
,
371 const Extension
* extension
,
372 const ShowDialogCallback
& show_dialog_callback
) override
{
373 delegate
->InstallUIProceed();
377 const Extension
* ExtensionBrowserTest::UpdateExtensionWaitForIdle(
378 const std::string
& id
,
379 const base::FilePath
& path
,
380 int expected_change
) {
381 return InstallOrUpdateExtension(id
,
383 INSTALL_UI_TYPE_NONE
,
392 const Extension
* ExtensionBrowserTest::InstallExtensionFromWebstore(
393 const base::FilePath
& path
,
394 int expected_change
) {
395 return InstallOrUpdateExtension(std::string(),
397 INSTALL_UI_TYPE_NONE
,
401 Extension::FROM_WEBSTORE
,
406 const Extension
* ExtensionBrowserTest::InstallOrUpdateExtension(
407 const std::string
& id
,
408 const base::FilePath
& path
,
409 InstallUIType ui_type
,
410 int expected_change
) {
411 return InstallOrUpdateExtension(id
,
422 const Extension
* ExtensionBrowserTest::InstallOrUpdateExtension(
423 const std::string
& id
,
424 const base::FilePath
& path
,
425 InstallUIType ui_type
,
428 Extension::InitFromValueFlags creation_flags
) {
429 return InstallOrUpdateExtension(id
,
440 const Extension
* ExtensionBrowserTest::InstallOrUpdateExtension(
441 const std::string
& id
,
442 const base::FilePath
& path
,
443 InstallUIType ui_type
,
445 Manifest::Location install_source
) {
446 return InstallOrUpdateExtension(id
,
457 const Extension
* ExtensionBrowserTest::InstallOrUpdateExtension(
458 const std::string
& id
,
459 const base::FilePath
& path
,
460 InstallUIType ui_type
,
462 Manifest::Location install_source
,
464 Extension::InitFromValueFlags creation_flags
,
465 bool install_immediately
,
467 ExtensionService
* service
=
468 extensions::ExtensionSystem::Get(profile())->extension_service();
469 service
->set_show_extensions_prompts(false);
470 size_t num_before
= service
->extensions()->size();
473 scoped_ptr
<ExtensionInstallPrompt
> install_ui
;
474 if (ui_type
== INSTALL_UI_TYPE_CANCEL
) {
475 install_ui
.reset(new MockAbortExtensionInstallPrompt());
476 } else if (ui_type
== INSTALL_UI_TYPE_NORMAL
) {
477 install_ui
.reset(new ExtensionInstallPrompt(
478 browser
->tab_strip_model()->GetActiveWebContents()));
479 } else if (ui_type
== INSTALL_UI_TYPE_AUTO_CONFIRM
) {
480 install_ui
.reset(new MockAutoConfirmExtensionInstallPrompt(
481 browser
->tab_strip_model()->GetActiveWebContents()));
484 // TODO(tessamac): Update callers to always pass an unpacked extension
485 // and then always pack the extension here.
486 base::FilePath crx_path
= path
;
487 if (crx_path
.Extension() != FILE_PATH_LITERAL(".crx")) {
488 crx_path
= PackExtension(path
);
490 if (crx_path
.empty())
493 scoped_refptr
<extensions::CrxInstaller
> installer(
494 extensions::CrxInstaller::Create(service
, install_ui
.Pass()));
495 installer
->set_expected_id(id
);
496 installer
->set_creation_flags(creation_flags
);
497 installer
->set_install_source(install_source
);
498 installer
->set_install_immediately(install_immediately
);
499 installer
->set_is_ephemeral(is_ephemeral
);
500 if (!installer
->is_gallery_install()) {
501 installer
->set_off_store_install_allow_reason(
502 extensions::CrxInstaller::OffStoreInstallAllowedInTest
);
506 extensions::NOTIFICATION_CRX_INSTALLER_DONE
,
507 content::Source
<extensions::CrxInstaller
>(installer
.get()));
509 installer
->InstallCrx(crx_path
);
514 size_t num_after
= service
->extensions()->size();
515 EXPECT_EQ(num_before
+ expected_change
, num_after
);
516 if (num_before
+ expected_change
!= num_after
) {
517 VLOG(1) << "Num extensions before: " << base::IntToString(num_before
)
518 << " num after: " << base::IntToString(num_after
)
519 << " Installed extensions follow:";
521 for (extensions::ExtensionSet::const_iterator it
=
522 service
->extensions()->begin();
523 it
!= service
->extensions()->end(); ++it
)
524 VLOG(1) << " " << (*it
)->id();
526 VLOG(1) << "Errors follow:";
527 const std::vector
<base::string16
>* errors
=
528 ExtensionErrorReporter::GetInstance()->GetErrors();
529 for (std::vector
<base::string16
>::const_iterator iter
= errors
->begin();
530 iter
!= errors
->end(); ++iter
)
536 if (!observer_
->WaitForExtensionViewsToLoad())
538 return service
->GetExtensionById(last_loaded_extension_id(), false);
541 void ExtensionBrowserTest::ReloadExtension(const std::string extension_id
) {
542 observer_
->Watch(extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED
,
543 content::NotificationService::AllSources());
545 ExtensionService
* service
=
546 extensions::ExtensionSystem::Get(profile())->extension_service();
547 service
->ReloadExtension(extension_id
);
550 observer_
->WaitForExtensionViewsToLoad();
553 void ExtensionBrowserTest::UnloadExtension(const std::string
& extension_id
) {
554 ExtensionService
* service
= extensions::ExtensionSystem::Get(
555 profile())->extension_service();
556 service
->UnloadExtension(extension_id
,
557 extensions::UnloadedExtensionInfo::REASON_DISABLE
);
560 void ExtensionBrowserTest::UninstallExtension(const std::string
& extension_id
) {
561 ExtensionService
* service
= extensions::ExtensionSystem::Get(
562 profile())->extension_service();
563 service
->UninstallExtension(extension_id
,
564 extensions::UNINSTALL_REASON_FOR_TESTING
,
565 base::Bind(&base::DoNothing
),
569 void ExtensionBrowserTest::DisableExtension(const std::string
& extension_id
) {
570 ExtensionService
* service
= extensions::ExtensionSystem::Get(
571 profile())->extension_service();
572 service
->DisableExtension(extension_id
, Extension::DISABLE_USER_ACTION
);
575 void ExtensionBrowserTest::EnableExtension(const std::string
& extension_id
) {
576 ExtensionService
* service
= extensions::ExtensionSystem::Get(
577 profile())->extension_service();
578 service
->EnableExtension(extension_id
);
581 void ExtensionBrowserTest::OpenWindow(content::WebContents
* contents
,
583 bool newtab_process_should_equal_opener
,
584 content::WebContents
** newtab_result
) {
585 content::WindowedNotificationObserver
windowed_observer(
586 content::NOTIFICATION_LOAD_STOP
,
587 content::NotificationService::AllSources());
588 ASSERT_TRUE(content::ExecuteScript(contents
,
589 "window.open('" + url
.spec() + "');"));
591 // The above window.open call is not user-initiated, so it will create
592 // a popup window instead of a new tab in current window.
593 // The stop notification will come from the new tab.
594 windowed_observer
.Wait();
595 content::NavigationController
* controller
=
596 content::Source
<content::NavigationController
>(
597 windowed_observer
.source()).ptr();
598 content::WebContents
* newtab
= controller
->GetWebContents();
600 EXPECT_EQ(url
, controller
->GetLastCommittedEntry()->GetURL());
601 if (newtab_process_should_equal_opener
)
602 EXPECT_EQ(contents
->GetRenderProcessHost(), newtab
->GetRenderProcessHost());
604 EXPECT_NE(contents
->GetRenderProcessHost(), newtab
->GetRenderProcessHost());
607 *newtab_result
= newtab
;
610 void ExtensionBrowserTest::NavigateInRenderer(content::WebContents
* contents
,
613 content::WindowedNotificationObserver
windowed_observer(
614 content::NOTIFICATION_LOAD_STOP
,
615 content::NotificationService::AllSources());
616 ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
618 "window.addEventListener('unload', function() {"
619 " window.domAutomationController.send(true);"
621 "window.location = '" + url
.spec() + "';",
624 windowed_observer
.Wait();
625 EXPECT_EQ(url
, contents
->GetController().GetLastCommittedEntry()->GetURL());
628 extensions::ExtensionHost
* ExtensionBrowserTest::FindHostWithPath(
629 extensions::ProcessManager
* manager
,
630 const std::string
& path
,
631 int expected_hosts
) {
632 extensions::ExtensionHost
* host
= NULL
;
634 extensions::ProcessManager::ExtensionHostSet background_hosts
=
635 manager
->background_hosts();
636 for (extensions::ProcessManager::const_iterator iter
=
637 background_hosts
.begin();
638 iter
!= background_hosts
.end();
640 if ((*iter
)->GetURL().path() == path
) {
646 EXPECT_EQ(expected_hosts
, num_hosts
);
650 std::string
ExtensionBrowserTest::ExecuteScriptInBackgroundPage(
651 const std::string
& extension_id
,
652 const std::string
& script
) {
653 return extensions::browsertest_util::ExecuteScriptInBackgroundPage(
654 profile(), extension_id
, script
);
657 bool ExtensionBrowserTest::ExecuteScriptInBackgroundPageNoWait(
658 const std::string
& extension_id
,
659 const std::string
& script
) {
660 return extensions::browsertest_util::ExecuteScriptInBackgroundPageNoWait(
661 profile(), extension_id
, script
);