| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/extensions/extension_browsertest.h" |
| |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/file_path.h" |
| #include "base/file_util.h" |
| #include "base/path_service.h" |
| #include "base/string_number_conversions.h" |
| #include "base/memory/scoped_temp_dir.h" |
| #include "chrome/browser/extensions/crx_installer.h" |
| #include "chrome/browser/extensions/extension_creator.h" |
| #include "chrome/browser/extensions/extension_error_reporter.h" |
| #include "chrome/browser/extensions/extension_host.h" |
| #include "chrome/browser/extensions/extension_install_ui.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/omnibox/location_bar.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/test/ui_test_utils.h" |
| #include "content/common/notification_registrar.h" |
| #include "content/common/notification_service.h" |
| #include "content/common/notification_type.h" |
| |
| ExtensionBrowserTest::ExtensionBrowserTest() |
| : loaded_(false), |
| installed_(false), |
| extension_installs_observed_(0), |
| target_page_action_count_(-1), |
| target_visible_page_action_count_(-1) { |
| EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| } |
| |
| void ExtensionBrowserTest::SetUpCommandLine(CommandLine* command_line) { |
| // This enables DOM automation for tab contentses. |
| EnableDOMAutomation(); |
| |
| // This enables it for extension hosts. |
| ExtensionHost::EnableDOMAutomation(); |
| |
| PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_); |
| test_data_dir_ = test_data_dir_.AppendASCII("extensions"); |
| |
| #if defined(OS_CHROMEOS) |
| // This makes sure that we create the Default profile first, with no |
| // ExtensionService and then the real profile with one, as we do when |
| // running on chromeos. |
| command_line->AppendSwitchASCII(switches::kLoginUser, |
| "TestUser@gmail.com"); |
| command_line->AppendSwitchASCII(switches::kLoginProfile, "user"); |
| command_line->AppendSwitch(switches::kNoFirstRun); |
| #endif |
| } |
| |
| const Extension* ExtensionBrowserTest::LoadExtensionImpl( |
| const FilePath& path, bool incognito_enabled, bool fileaccess_enabled) { |
| ExtensionService* service = browser()->profile()->GetExtensionService(); |
| { |
| NotificationRegistrar registrar; |
| registrar.Add(this, NotificationType::EXTENSION_LOADED, |
| NotificationService::AllSources()); |
| service->LoadExtension(path); |
| ui_test_utils::RunMessageLoop(); |
| } |
| |
| // Find the extension by iterating backwards since it is likely last. |
| FilePath extension_path = path; |
| file_util::AbsolutePath(&extension_path); |
| const Extension* extension = NULL; |
| for (ExtensionList::const_reverse_iterator iter = |
| service->extensions()->rbegin(); |
| iter != service->extensions()->rend(); ++iter) { |
| if ((*iter)->path() == extension_path) { |
| extension = *iter; |
| break; |
| } |
| } |
| if (!extension) |
| return NULL; |
| |
| // The call to OnExtensionInstalled ensures the other extension prefs |
| // are set up with the defaults. |
| service->extension_prefs()->OnExtensionInstalled( |
| extension, Extension::ENABLED, false); |
| service->SetIsIncognitoEnabled(extension->id(), incognito_enabled); |
| service->SetAllowFileAccess(extension, fileaccess_enabled); |
| |
| if (!WaitForExtensionHostsToLoad()) |
| return NULL; |
| |
| return extension; |
| } |
| |
| const Extension* ExtensionBrowserTest::LoadExtension(const FilePath& path) { |
| return LoadExtensionImpl(path, false, true); |
| } |
| |
| const Extension* ExtensionBrowserTest::LoadExtensionIncognito( |
| const FilePath& path) { |
| return LoadExtensionImpl(path, true, true); |
| } |
| |
| const Extension* ExtensionBrowserTest::LoadExtensionNoFileAccess( |
| const FilePath& path) { |
| return LoadExtensionImpl(path, false, false); |
| } |
| |
| const Extension* ExtensionBrowserTest::LoadExtensionIncognitoNoFileAccess( |
| const FilePath& path) { |
| return LoadExtensionImpl(path, true, false); |
| } |
| |
| bool ExtensionBrowserTest::LoadExtensionAsComponent(const FilePath& path) { |
| ExtensionService* service = browser()->profile()->GetExtensionService(); |
| |
| std::string manifest; |
| if (!file_util::ReadFileToString(path.Append(Extension::kManifestFilename), |
| &manifest)) |
| return false; |
| |
| service->LoadComponentExtension( |
| ExtensionService::ComponentExtensionInfo(manifest, path)); |
| |
| return true; |
| } |
| |
| FilePath ExtensionBrowserTest::PackExtension(const FilePath& dir_path) { |
| FilePath crx_path = temp_dir_.path().AppendASCII("temp.crx"); |
| if (!file_util::Delete(crx_path, false)) { |
| ADD_FAILURE() << "Failed to delete crx: " << crx_path.value(); |
| return FilePath(); |
| } |
| |
| FilePath pem_path = crx_path.DirName().AppendASCII("temp.pem"); |
| if (!file_util::Delete(pem_path, false)) { |
| ADD_FAILURE() << "Failed to delete pem: " << pem_path.value(); |
| return FilePath(); |
| } |
| |
| if (!file_util::PathExists(dir_path)) { |
| ADD_FAILURE() << "Extension dir not found: " << dir_path.value(); |
| return FilePath(); |
| } |
| |
| scoped_ptr<ExtensionCreator> creator(new ExtensionCreator()); |
| if (!creator->Run(dir_path, |
| crx_path, |
| FilePath(), // no existing pem, use empty path |
| pem_path)) { |
| ADD_FAILURE() << "ExtensionCreator::Run() failed."; |
| return FilePath(); |
| } |
| |
| if (!file_util::PathExists(crx_path)) { |
| ADD_FAILURE() << crx_path.value() << " was not created."; |
| return FilePath(); |
| } |
| return crx_path; |
| } |
| |
| // This class is used to simulate an installation abort by the user. |
| class MockAbortExtensionInstallUI : public ExtensionInstallUI { |
| public: |
| MockAbortExtensionInstallUI() : ExtensionInstallUI(NULL) {} |
| |
| // Simulate a user abort on an extension installation. |
| virtual void ConfirmInstall(Delegate* delegate, const Extension* extension) { |
| delegate->InstallUIAbort(); |
| MessageLoopForUI::current()->Quit(); |
| } |
| |
| virtual void OnInstallSuccess(const Extension* extension, SkBitmap* icon) {} |
| |
| virtual void OnInstallFailure(const std::string& error) {} |
| }; |
| |
| class MockAutoConfirmExtensionInstallUI : public ExtensionInstallUI { |
| public: |
| explicit MockAutoConfirmExtensionInstallUI(Profile* profile) : |
| ExtensionInstallUI(profile) {} |
| |
| // Proceed without confirmation prompt. |
| virtual void ConfirmInstall(Delegate* delegate, const Extension* extension) { |
| delegate->InstallUIProceed(); |
| } |
| }; |
| |
| bool ExtensionBrowserTest::InstallOrUpdateExtension(const std::string& id, |
| const FilePath& path, |
| InstallUIType ui_type, |
| int expected_change) { |
| return InstallOrUpdateExtension(id, path, ui_type, expected_change, |
| browser()->profile()); |
| } |
| |
| bool ExtensionBrowserTest::InstallOrUpdateExtension(const std::string& id, |
| const FilePath& path, |
| InstallUIType ui_type, |
| int expected_change, |
| Profile* profile) { |
| ExtensionService* service = profile->GetExtensionService(); |
| service->set_show_extensions_prompts(false); |
| size_t num_before = service->extensions()->size(); |
| |
| { |
| NotificationRegistrar registrar; |
| registrar.Add(this, NotificationType::EXTENSION_LOADED, |
| NotificationService::AllSources()); |
| registrar.Add(this, NotificationType::EXTENSION_UPDATE_DISABLED, |
| NotificationService::AllSources()); |
| registrar.Add(this, NotificationType::EXTENSION_INSTALL_ERROR, |
| NotificationService::AllSources()); |
| |
| ExtensionInstallUI* install_ui = NULL; |
| if (ui_type == INSTALL_UI_TYPE_CANCEL) |
| install_ui = new MockAbortExtensionInstallUI(); |
| else if (ui_type == INSTALL_UI_TYPE_NORMAL) |
| install_ui = new ExtensionInstallUI(profile); |
| else if (ui_type == INSTALL_UI_TYPE_AUTO_CONFIRM) |
| install_ui = new MockAutoConfirmExtensionInstallUI(profile); |
| |
| // TODO(tessamac): Update callers to always pass an unpacked extension |
| // and then always pack the extension here. |
| FilePath crx_path = path; |
| if (crx_path.Extension() != FILE_PATH_LITERAL(".crx")) { |
| crx_path = PackExtension(path); |
| } |
| if (crx_path.empty()) |
| return false; |
| |
| scoped_refptr<CrxInstaller> installer( |
| new CrxInstaller(service, install_ui)); |
| installer->set_expected_id(id); |
| installer->InstallCrx(crx_path); |
| |
| ui_test_utils::RunMessageLoop(); |
| } |
| |
| size_t num_after = service->extensions()->size(); |
| if (num_after != (num_before + expected_change)) { |
| VLOG(1) << "Num extensions before: " << base::IntToString(num_before) |
| << " num after: " << base::IntToString(num_after) |
| << " Installed extensions follow:"; |
| |
| for (size_t i = 0; i < service->extensions()->size(); ++i) |
| VLOG(1) << " " << (*service->extensions())[i]->id(); |
| |
| VLOG(1) << "Errors follow:"; |
| const std::vector<std::string>* errors = |
| ExtensionErrorReporter::GetInstance()->GetErrors(); |
| for (std::vector<std::string>::const_iterator iter = errors->begin(); |
| iter != errors->end(); ++iter) |
| VLOG(1) << *iter; |
| |
| return false; |
| } |
| |
| return WaitForExtensionHostsToLoad(); |
| } |
| |
| void ExtensionBrowserTest::ReloadExtension(const std::string& extension_id) { |
| ExtensionService* service = browser()->profile()->GetExtensionService(); |
| service->ReloadExtension(extension_id); |
| ui_test_utils::RegisterAndWait(this, |
| NotificationType::EXTENSION_LOADED, |
| NotificationService::AllSources()); |
| } |
| |
| void ExtensionBrowserTest::UnloadExtension(const std::string& extension_id) { |
| ExtensionService* service = browser()->profile()->GetExtensionService(); |
| service->UnloadExtension(extension_id, UnloadedExtensionInfo::DISABLE); |
| } |
| |
| void ExtensionBrowserTest::UninstallExtension(const std::string& extension_id) { |
| ExtensionService* service = browser()->profile()->GetExtensionService(); |
| service->UninstallExtension(extension_id, false, NULL); |
| } |
| |
| void ExtensionBrowserTest::DisableExtension(const std::string& extension_id) { |
| ExtensionService* service = browser()->profile()->GetExtensionService(); |
| service->DisableExtension(extension_id); |
| } |
| |
| void ExtensionBrowserTest::EnableExtension(const std::string& extension_id) { |
| ExtensionService* service = browser()->profile()->GetExtensionService(); |
| service->EnableExtension(extension_id); |
| } |
| |
| bool ExtensionBrowserTest::WaitForPageActionCountChangeTo(int count) { |
| LocationBarTesting* location_bar = |
| browser()->window()->GetLocationBar()->GetLocationBarForTesting(); |
| if (location_bar->PageActionCount() != count) { |
| target_page_action_count_ = count; |
| ui_test_utils::RegisterAndWait(this, |
| NotificationType::EXTENSION_PAGE_ACTION_COUNT_CHANGED, |
| NotificationService::AllSources()); |
| } |
| return location_bar->PageActionCount() == count; |
| } |
| |
| bool ExtensionBrowserTest::WaitForPageActionVisibilityChangeTo(int count) { |
| LocationBarTesting* location_bar = |
| browser()->window()->GetLocationBar()->GetLocationBarForTesting(); |
| if (location_bar->PageActionVisibleCount() != count) { |
| target_visible_page_action_count_ = count; |
| ui_test_utils::RegisterAndWait(this, |
| NotificationType::EXTENSION_PAGE_ACTION_VISIBILITY_CHANGED, |
| NotificationService::AllSources()); |
| } |
| return location_bar->PageActionVisibleCount() == count; |
| } |
| |
| bool ExtensionBrowserTest::WaitForExtensionHostsToLoad() { |
| // Wait for all the extension hosts that exist to finish loading. |
| NotificationRegistrar registrar; |
| registrar.Add(this, NotificationType::EXTENSION_HOST_DID_STOP_LOADING, |
| NotificationService::AllSources()); |
| |
| ExtensionProcessManager* manager = |
| browser()->profile()->GetExtensionProcessManager(); |
| for (ExtensionProcessManager::const_iterator iter = manager->begin(); |
| iter != manager->end();) { |
| if ((*iter)->did_stop_loading()) { |
| ++iter; |
| } else { |
| ui_test_utils::RunMessageLoop(); |
| |
| // Test activity may have modified the set of extension processes during |
| // message processing, so re-start the iteration to catch added/removed |
| // processes. |
| iter = manager->begin(); |
| } |
| } |
| return true; |
| } |
| |
| bool ExtensionBrowserTest::WaitForExtensionInstall() { |
| int before = extension_installs_observed_; |
| ui_test_utils::RegisterAndWait(this, NotificationType::EXTENSION_INSTALLED, |
| NotificationService::AllSources()); |
| return extension_installs_observed_ == (before + 1); |
| } |
| |
| bool ExtensionBrowserTest::WaitForExtensionInstallError() { |
| int before = extension_installs_observed_; |
| ui_test_utils::RegisterAndWait(this, |
| NotificationType::EXTENSION_INSTALL_ERROR, |
| NotificationService::AllSources()); |
| return extension_installs_observed_ == before; |
| } |
| |
| void ExtensionBrowserTest::WaitForExtensionLoad() { |
| ui_test_utils::RegisterAndWait(this, NotificationType::EXTENSION_LOADED, |
| NotificationService::AllSources()); |
| WaitForExtensionHostsToLoad(); |
| } |
| |
| bool ExtensionBrowserTest::WaitForExtensionCrash( |
| const std::string& extension_id) { |
| ExtensionService* service = browser()->profile()->GetExtensionService(); |
| |
| if (!service->GetExtensionById(extension_id, true)) { |
| // The extension is already unloaded, presumably due to a crash. |
| return true; |
| } |
| ui_test_utils::RegisterAndWait(this, |
| NotificationType::EXTENSION_PROCESS_TERMINATED, |
| NotificationService::AllSources()); |
| return (service->GetExtensionById(extension_id, true) == NULL); |
| } |
| |
| void ExtensionBrowserTest::Observe(NotificationType type, |
| const NotificationSource& source, |
| const NotificationDetails& details) { |
| switch (type.value) { |
| case NotificationType::EXTENSION_LOADED: |
| last_loaded_extension_id_ = Details<const Extension>(details).ptr()->id(); |
| VLOG(1) << "Got EXTENSION_LOADED notification."; |
| MessageLoopForUI::current()->Quit(); |
| break; |
| |
| case NotificationType::EXTENSION_UPDATE_DISABLED: |
| VLOG(1) << "Got EXTENSION_UPDATE_DISABLED notification."; |
| MessageLoopForUI::current()->Quit(); |
| break; |
| |
| case NotificationType::EXTENSION_HOST_DID_STOP_LOADING: |
| VLOG(1) << "Got EXTENSION_HOST_DID_STOP_LOADING notification."; |
| MessageLoopForUI::current()->Quit(); |
| break; |
| |
| case NotificationType::EXTENSION_INSTALLED: |
| VLOG(1) << "Got EXTENSION_INSTALLED notification."; |
| ++extension_installs_observed_; |
| MessageLoopForUI::current()->Quit(); |
| break; |
| |
| case NotificationType::EXTENSION_INSTALL_ERROR: |
| VLOG(1) << "Got EXTENSION_INSTALL_ERROR notification."; |
| MessageLoopForUI::current()->Quit(); |
| break; |
| |
| case NotificationType::EXTENSION_PROCESS_CREATED: |
| VLOG(1) << "Got EXTENSION_PROCESS_CREATED notification."; |
| MessageLoopForUI::current()->Quit(); |
| break; |
| |
| case NotificationType::EXTENSION_PROCESS_TERMINATED: |
| VLOG(1) << "Got EXTENSION_PROCESS_TERMINATED notification."; |
| MessageLoopForUI::current()->Quit(); |
| break; |
| |
| case NotificationType::EXTENSION_PAGE_ACTION_COUNT_CHANGED: { |
| LocationBarTesting* location_bar = |
| browser()->window()->GetLocationBar()->GetLocationBarForTesting(); |
| VLOG(1) << "Got EXTENSION_PAGE_ACTION_COUNT_CHANGED notification. Number " |
| "of page actions: " << location_bar->PageActionCount(); |
| if (location_bar->PageActionCount() == |
| target_page_action_count_) { |
| target_page_action_count_ = -1; |
| MessageLoopForUI::current()->Quit(); |
| } |
| break; |
| } |
| |
| case NotificationType::EXTENSION_PAGE_ACTION_VISIBILITY_CHANGED: { |
| LocationBarTesting* location_bar = |
| browser()->window()->GetLocationBar()->GetLocationBarForTesting(); |
| VLOG(1) << "Got EXTENSION_PAGE_ACTION_VISIBILITY_CHANGED notification. " |
| "Number of visible page actions: " |
| << location_bar->PageActionVisibleCount(); |
| if (location_bar->PageActionVisibleCount() == |
| target_visible_page_action_count_) { |
| target_visible_page_action_count_ = -1; |
| MessageLoopForUI::current()->Quit(); |
| } |
| break; |
| } |
| |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |