diff options
Diffstat (limited to 'chrome/browser/extensions')
-rw-r--r-- | chrome/browser/extensions/app_process_apitest.cc | 3 | ||||
-rw-r--r-- | chrome/browser/extensions/crx_installer.cc | 42 | ||||
-rw-r--r-- | chrome/browser/extensions/crx_installer.h | 20 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_service.cc | 17 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_service.h | 3 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_service_unittest.cc | 114 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_system.cc | 5 | ||||
-rw-r--r-- | chrome/browser/extensions/requirements_checker.cc | 98 | ||||
-rw-r--r-- | chrome/browser/extensions/requirements_checker.h | 58 | ||||
-rw-r--r-- | chrome/browser/extensions/requirements_checker_browsertest.cc | 190 | ||||
-rw-r--r-- | chrome/browser/extensions/unpacked_installer.cc | 65 | ||||
-rw-r--r-- | chrome/browser/extensions/unpacked_installer.h | 19 |
12 files changed, 600 insertions, 34 deletions
diff --git a/chrome/browser/extensions/app_process_apitest.cc b/chrome/browser/extensions/app_process_apitest.cc index 4383fa0..472252a 100644 --- a/chrome/browser/extensions/app_process_apitest.cc +++ b/chrome/browser/extensions/app_process_apitest.cc @@ -253,7 +253,8 @@ IN_PROC_BROWSER_TEST_F(AppApiTest, BookmarkAppGetsNormalProcess) { Extension::FROM_BOOKMARK, &error)); service->OnExtensionInstalled(extension, false, - syncer::StringOrdinal::CreateInitialOrdinal()); + syncer::StringOrdinal::CreateInitialOrdinal(), + false /* no requirement errors */); ASSERT_TRUE(extension.get()); ASSERT_TRUE(extension->from_bookmark()); diff --git a/chrome/browser/extensions/crx_installer.cc b/chrome/browser/extensions/crx_installer.cc index d178204..3d4071b 100644 --- a/chrome/browser/extensions/crx_installer.cc +++ b/chrome/browser/extensions/crx_installer.cc @@ -14,7 +14,7 @@ #include "base/metrics/histogram.h" #include "base/path_service.h" #include "base/scoped_temp_dir.h" -#include "base/stl_util.h" +#include "base/string_util.h" #include "base/stringprintf.h" #include "base/threading/thread_restrictions.h" #include "base/time.h" @@ -29,6 +29,7 @@ #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_system.h" #include "chrome/browser/extensions/permissions_updater.h" +#include "chrome/browser/extensions/requirements_checker.h" #include "chrome/browser/extensions/webstore_installer.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/shell_integration.h" @@ -101,7 +102,10 @@ CrxInstaller::CrxInstaller( creation_flags_(Extension::NO_FLAGS), off_store_install_allow_reason_(OffStoreInstallDisallowed), did_handle_successfully_(true), - record_oauth2_grant_(false) { + record_oauth2_grant_(false), + error_on_unsupported_requirements_(false), + requirements_checker_(new extensions::RequirementsChecker()), + has_requirement_errors_(false) { if (!approval) return; @@ -385,11 +389,35 @@ void CrxInstaller::OnUnpackSuccess(const FilePath& temp_dir, } if (!BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(&CrxInstaller::ConfirmInstall, this))) + BrowserThread::UI, FROM_HERE, + base::Bind(&CrxInstaller::CheckRequirements, this))) NOTREACHED(); } +void CrxInstaller::CheckRequirements() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + AddRef(); // Balanced in OnRequirementsChecked(). + requirements_checker_->Check(extension_, + base::Bind(&CrxInstaller::OnRequirementsChecked, + this)); +} + +void CrxInstaller::OnRequirementsChecked( + std::vector<std::string> requirement_errors) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + Release(); // Balanced in CheckRequirements(). + if (!requirement_errors.empty()) { + if (error_on_unsupported_requirements_) { + ReportFailureFromUIThread(CrxInstallerError( + UTF8ToUTF16(JoinString(requirement_errors, ' ')))); + return; + } + has_requirement_errors_ = true; + } + + ConfirmInstall(); +} + void CrxInstaller::ConfirmInstall() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (!frontend_weak_.get()) @@ -598,8 +626,10 @@ void CrxInstaller::ReportSuccessFromUIThread() { // Tell the frontend about the installation and hand off ownership of // extension_ to it. - frontend_weak_->OnExtensionInstalled(extension_, is_gallery_install(), - page_ordinal_); + frontend_weak_->OnExtensionInstalled(extension_, + is_gallery_install(), + page_ordinal_, + has_requirement_errors_); NotifyCrxInstallComplete(extension_.get()); diff --git a/chrome/browser/extensions/crx_installer.h b/chrome/browser/extensions/crx_installer.h index 5c9a79e..e9541de 100644 --- a/chrome/browser/extensions/crx_installer.h +++ b/chrome/browser/extensions/crx_installer.h @@ -25,6 +25,7 @@ class SkBitmap; namespace extensions { class ExtensionUpdaterTest; +class RequirementsChecker; // This class installs a crx file into a profile. // @@ -165,6 +166,10 @@ class CrxInstaller page_ordinal_ = page_ordinal; } + void set_error_on_unsupported_requirements(bool val) { + error_on_unsupported_requirements_ = val; + } + bool did_handle_successfully() const { return did_handle_successfully_; } Profile* profile() { return profile_; } @@ -199,6 +204,12 @@ class CrxInstaller // whitelisted. bool CanSkipConfirmation(); + // Called on the UI thread to start the requirements check on the extension. + void CheckRequirements(); + + // Runs on the UI thread. Callback from RequirementsChecker. + void OnRequirementsChecked(std::vector<std::string> requirement_errors); + // Runs on the UI thread. Confirms with the user (via ExtensionInstallPrompt) // that it is OK to install this extension. void ConfirmInstall(); @@ -339,6 +350,15 @@ class CrxInstaller // Whether we should record an oauth2 grant upon successful install. bool record_oauth2_grant_; + // Whether we should produce an error if the manifest declares requirements + // that are not met. If false and there is an unmet requirement, the install + // will continue but the extension will be distabled. + bool error_on_unsupported_requirements_; + + scoped_ptr<RequirementsChecker> requirements_checker_; + + bool has_requirement_errors_; + DISALLOW_COPY_AND_ASSIGN(CrxInstaller); }; diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc index 11e7842..03c0563 100644 --- a/chrome/browser/extensions/extension_service.cc +++ b/chrome/browser/extensions/extension_service.cc @@ -2128,7 +2128,8 @@ void ExtensionService::UpdateActiveExtensionsInCrashReporter() { void ExtensionService::OnExtensionInstalled( const Extension* extension, bool from_webstore, - const syncer::StringOrdinal& page_ordinal) { + const syncer::StringOrdinal& page_ordinal, + bool has_requirement_errors) { CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); // Ensure extension is deleted unless we transfer ownership. @@ -2175,6 +2176,20 @@ void ExtensionService::OnExtensionInstalled( } } + // Unsupported requirements overrides the management policy. + if (has_requirement_errors) { + initial_enable = false; + extension_prefs_->AddDisableReason( + id, Extension::DISABLE_UNSUPPORTED_REQUIREMENT); + // If the extension was disabled because of unsupported requirements but + // now supports all requirements after an update and there are not other + // disable reasons, enable it. + } else if (extension_prefs_->GetDisableReasons(id) == + Extension::DISABLE_UNSUPPORTED_REQUIREMENT) { + initial_enable = true; + extension_prefs_->ClearDisableReasons(id); + } + int include_mask = INCLUDE_ENABLED; include_mask |= INCLUDE_DISABLED; // Do not record the install histograms for upgrades. diff --git a/chrome/browser/extensions/extension_service.h b/chrome/browser/extensions/extension_service.h index bc075b3..bb31d3d 100644 --- a/chrome/browser/extensions/extension_service.h +++ b/chrome/browser/extensions/extension_service.h @@ -406,7 +406,8 @@ class ExtensionService void OnExtensionInstalled( const extensions::Extension* extension, bool from_webstore, - const syncer::StringOrdinal& page_ordinal); + const syncer::StringOrdinal& page_ordinal, + bool has_requirement_errors); // Initializes the |extension|'s active permission set and disables the // extension if the privilege level has increased (e.g., due to an upgrade). diff --git a/chrome/browser/extensions/extension_service_unittest.cc b/chrome/browser/extensions/extension_service_unittest.cc index 9bf75f0..32adcc9 100644 --- a/chrome/browser/extensions/extension_service_unittest.cc +++ b/chrome/browser/extensions/extension_service_unittest.cc @@ -49,6 +49,7 @@ #include "chrome/browser/extensions/test_management_policy.h" #include "chrome/browser/extensions/unpacked_installer.h" #include "chrome/browser/extensions/updater/extension_updater.h" +#include "chrome/browser/plugin_prefs_factory.h" #include "chrome/browser/prefs/browser_prefs.h" #include "chrome/browser/prefs/pref_service_mock_builder.h" #include "chrome/browser/prefs/scoped_user_pref_update.h" @@ -66,6 +67,7 @@ #include "chrome/common/url_constants.h" #include "chrome/test/base/testing_profile.h" #include "content/public/browser/dom_storage_context.h" +#include "content/public/browser/gpu_data_manager.h" #include "content/public/browser/indexed_db_context.h" #include "content/public/browser/notification_registrar.h" #include "content/public/browser/notification_service.h" @@ -717,6 +719,21 @@ class ExtensionServiceTest ENABLED }; + void BlackListWebGL() { + static const std::string json_blacklist = + "{\n" + " \"name\": \"gpu blacklist\",\n" + " \"version\": \"1.0\",\n" + " \"entries\": [\n" + " {\n" + " \"id\": 1,\n" + " \"blacklist\": [\"webgl\"]\n" + " }\n" + " ]\n" + "}"; + content::GpuDataManager::GetInstance()->Initialize("0", json_blacklist); + } + void UpdateExtension(const std::string& id, const FilePath& in_path, UpdateState expected_state) { ASSERT_TRUE(file_util::PathExists(in_path)); @@ -2396,7 +2413,7 @@ TEST_F(ExtensionServiceTest, UpdateExtensionPreservesState) { path = data_dir_.AppendASCII("good2.crx"); UpdateExtension(good_crx, path, INSTALLED); - ASSERT_EQ(1u, service_->disabled_extensions()->size()); + ASSERT_EQ(1u, service_->disabled_extensions()->size());\ const Extension* good2 = service_->GetExtensionById(good_crx, true); ASSERT_EQ("1.0.0.1", good2->version()->GetString()); EXPECT_TRUE(service_->IsIncognitoEnabled(good2->id())); @@ -3432,6 +3449,101 @@ TEST_F(ExtensionServiceTest, UninstallExtensionHelperTerminated) { UninstallExtension(good_crx, true); } +// An extension disabled because of unsupported requirements should re-enabled +// if updated to a version with supported requirements as long as there are no +// other disable reasons. +TEST_F(ExtensionServiceTest, UpgradingRequirementsEnabled) { + InitializeEmptyExtensionService(); + BlackListWebGL(); + + FilePath path = data_dir_.AppendASCII("requirements"); + FilePath pem_path = data_dir_.AppendASCII("requirements") + .AppendASCII("v1_good.pem"); + const Extension* extension_v1 = PackAndInstallCRX(path.AppendASCII("v1_good"), + pem_path, + INSTALL_NEW); + std::string id = extension_v1->id(); + EXPECT_TRUE(service_->IsExtensionEnabled(id)); + + PackCRX(path.AppendASCII("v2_bad_requirements"), pem_path, + path.AppendASCII("v2_bad_requirements.crx")); + UpdateExtension(id, path.AppendASCII("v2_bad_requirements.crx"), INSTALLED); + EXPECT_FALSE(service_->IsExtensionEnabled(id)); + + PackCRX(path.AppendASCII("v3_good"), pem_path, + path.AppendASCII("v3_good.crx")); + UpdateExtension(id, path.AppendASCII("v3_good.crx"), ENABLED); + EXPECT_TRUE(service_->IsExtensionEnabled(id)); +} + +// Extensions disabled through user action should stay disabled. +TEST_F(ExtensionServiceTest, UpgradingRequirementsDisabled) { + InitializeEmptyExtensionService(); + BlackListWebGL(); + + FilePath path = data_dir_.AppendASCII("requirements"); + FilePath pem_path = data_dir_.AppendASCII("requirements") + .AppendASCII("v1_good.pem"); + const Extension* extension_v1 = PackAndInstallCRX(path.AppendASCII("v1_good"), + pem_path, + INSTALL_NEW); + std::string id = extension_v1->id(); + service_->DisableExtension(id, Extension::DISABLE_USER_ACTION); + EXPECT_FALSE(service_->IsExtensionEnabled(id)); + + PackCRX(path.AppendASCII("v2_bad_requirements"), pem_path, + path.AppendASCII("v2_bad_requirements.crx")); + UpdateExtension(id, path.AppendASCII("v2_bad_requirements.crx"), INSTALLED); + EXPECT_FALSE(service_->IsExtensionEnabled(id)); + + PackCRX(path.AppendASCII("v3_good"), pem_path, + path.AppendASCII("v3_good.crx")); + UpdateExtension(id, path.AppendASCII("v3_good.crx"), INSTALLED); + EXPECT_FALSE(service_->IsExtensionEnabled(id)); +} + +// The extension should not re-enabled because it was disabled from a +// permission increase. +TEST_F(ExtensionServiceTest, UpgradingRequirementsPermissions) { + InitializeEmptyExtensionService(); + BlackListWebGL(); + + FilePath path = data_dir_.AppendASCII("requirements"); + FilePath pem_path = data_dir_.AppendASCII("requirements") + .AppendASCII("v1_good.pem"); + const Extension* extension_v1 = PackAndInstallCRX(path.AppendASCII("v1_good"), + pem_path, + INSTALL_NEW); + std::string id = extension_v1->id(); + EXPECT_TRUE(service_->IsExtensionEnabled(id)); + + PackCRX(path.AppendASCII("v2_bad_requirements_and_permissions"), pem_path, + path.AppendASCII("v2_bad_requirements_and_permissions.crx")); + UpdateExtension( + id, + path.AppendASCII("v2_bad_requirements_and_permissions.crx"), INSTALLED); + EXPECT_FALSE(service_->IsExtensionEnabled(id)); + + PackCRX(path.AppendASCII("v3_bad_permissions"), pem_path, + path.AppendASCII("v3_bad_permissions.crx")); + UpdateExtension(id, path.AppendASCII("v3_bad_permissions.crx"), INSTALLED); + EXPECT_FALSE(service_->IsExtensionEnabled(id)); +} + +// Unpacked extensions are not allowed to be installed if they have unsupported +// requirements. +TEST_F(ExtensionServiceTest, UnpackedRequirements) { + InitializeEmptyExtensionService(); + BlackListWebGL(); + + FilePath path = data_dir_.AppendASCII("requirements") + .AppendASCII("v2_bad_requirements"); + extensions::UnpackedInstaller::Create(service_)->Load(path); + loop_.RunAllPending(); + EXPECT_EQ(1u, GetErrors().size()); + EXPECT_EQ(0u, service_->extensions()->size()); +} + class ExtensionCookieCallback { public: ExtensionCookieCallback() diff --git a/chrome/browser/extensions/extension_system.cc b/chrome/browser/extensions/extension_system.cc index 78d6277..fd89461 100644 --- a/chrome/browser/extensions/extension_system.cc +++ b/chrome/browser/extensions/extension_system.cc @@ -162,10 +162,9 @@ void ExtensionSystemImpl::Shared::Init(bool extensions_enabled) { StringTokenizerT<CommandLine::StringType, CommandLine::StringType::const_iterator> t(path_list, FILE_PATH_LITERAL(",")); - scoped_refptr<UnpackedInstaller> installer = - UnpackedInstaller::Create(extension_service_.get()); while (t.GetNext()) { - installer->LoadFromCommandLine(FilePath(t.token())); + UnpackedInstaller::Create(extension_service_.get())-> + LoadFromCommandLine(FilePath(t.token())); } } } diff --git a/chrome/browser/extensions/requirements_checker.cc b/chrome/browser/extensions/requirements_checker.cc new file mode 100644 index 0000000..516f5b0 --- /dev/null +++ b/chrome/browser/extensions/requirements_checker.cc @@ -0,0 +1,98 @@ +// Copyright (c) 2012 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/requirements_checker.h" + +#include "base/bind.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/gpu_feature_checker.h" +#include "chrome/common/extensions/extension_manifest_constants.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/manifest.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/common/gpu_feature_type.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +namespace extensions { + +RequirementsChecker::RequirementsChecker() + : pending_requirement_checks_(0) { +} + +RequirementsChecker::~RequirementsChecker() { +} + +void RequirementsChecker::Check(scoped_refptr<const Extension> extension, + base::Callback<void(std::vector<std::string> errors)> callback) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + + callback_ = callback; + const Extension::Requirements& requirements = extension->requirements(); + + if (requirements.npapi) { +#if defined(OS_CHROMEOS) + errors_.push_back( + l10n_util::GetStringUTF8(IDS_EXTENSION_NPAPI_NOT_SUPPORTED)); +#endif // defined(OS_CHROMEOS) + } + + if (requirements.webgl) { + ++pending_requirement_checks_; + webgl_checker_ = new GPUFeatureChecker( + content::GPU_FEATURE_TYPE_WEBGL, + base::Bind(&RequirementsChecker::SetWebGLAvailability, + AsWeakPtr())); + } + + if (requirements.css3d) { + ++pending_requirement_checks_; + css3d_checker_ = new GPUFeatureChecker( + content::GPU_FEATURE_TYPE_ACCELERATED_COMPOSITING, + base::Bind(&RequirementsChecker::SetCSS3DAvailability, + AsWeakPtr())); + } + + if (pending_requirement_checks_ == 0) { + content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, + base::Bind(callback_, errors_)); + // Reset the callback so any ref-counted bound parameters will get released. + callback_.Reset(); + return; + } + // Running the GPU checkers down here removes any race condition that arises + // from the use of pending_requirement_checks_. + if (webgl_checker_.get()) + webgl_checker_->CheckGPUFeatureAvailability(); + if (css3d_checker_.get()) + css3d_checker_->CheckGPUFeatureAvailability(); +} + +void RequirementsChecker::SetWebGLAvailability(bool available) { + if (!available) { + errors_.push_back( + l10n_util::GetStringUTF8(IDS_EXTENSION_WEBGL_NOT_SUPPORTED)); + } + MaybeRunCallback(); +} + +void RequirementsChecker::SetCSS3DAvailability(bool available) { + if (!available) { + errors_.push_back( + l10n_util::GetStringUTF8(IDS_EXTENSION_CSS3D_NOT_SUPPORTED)); + } + MaybeRunCallback(); +} + +void RequirementsChecker::MaybeRunCallback() { + if (--pending_requirement_checks_ == 0) { + content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, + base::Bind(callback_, errors_)); + // Reset the callback so any ref-counted bound parameters will get released. + callback_.Reset(); + errors_.clear(); + } +} + +} // namespace extensions diff --git a/chrome/browser/extensions/requirements_checker.h b/chrome/browser/extensions/requirements_checker.h new file mode 100644 index 0000000..cca3503 --- /dev/null +++ b/chrome/browser/extensions/requirements_checker.h @@ -0,0 +1,58 @@ +// Copyright (c) 2012 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. + +#ifndef CHROME_BROWSER_EXTENSIONS_REQUIREMENTS_CHECKER_H_ +#define CHROME_BROWSER_EXTENSIONS_REQUIREMENTS_CHECKER_H_ + +#include <vector> + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "chrome/browser/extensions/extension_service.h" + +class GPUFeatureChecker; + +namespace extensions { +class Extension; + +// Validates the 'requirements' extension manifest field. This is an +// asynchronous process that involves several threads, but the public interface +// of this class (including constructor and destructor) must only be used on +// the UI thread. +class RequirementsChecker : public base::SupportsWeakPtr<RequirementsChecker> { + public: + RequirementsChecker(); + ~RequirementsChecker(); + + // The vector passed to the callback are any localized errors describing + // requirement violations. If this vector is non-empty, requirements checking + // failed. This should only be called once. |callback| will always be invoked + // asynchronously on the UI thread. |callback| will only be called once, and + // will be reset after called. + void Check(scoped_refptr<const Extension> extension, + base::Callback<void(std::vector<std::string> requirement)> callback); + + private: + // Callbacks for the GPUFeatureChecker. + void SetWebGLAvailability(bool available); + void SetCSS3DAvailability(bool available); + + void MaybeRunCallback(); + + std::vector<std::string> errors_; + + // Every requirement that needs to be resolved asynchronously will add to + // this counter. When the counter is depleted, the callback will be run. + int pending_requirement_checks_; + + scoped_refptr<GPUFeatureChecker> webgl_checker_; + scoped_refptr<GPUFeatureChecker> css3d_checker_; + + base::Callback<void(std::vector<std::string> requirement_errorss)> callback_; +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_REQUIREMENTS_CHECKER_H_ diff --git a/chrome/browser/extensions/requirements_checker_browsertest.cc b/chrome/browser/extensions/requirements_checker_browsertest.cc new file mode 100644 index 0000000..b310dc9 --- /dev/null +++ b/chrome/browser/extensions/requirements_checker_browsertest.cc @@ -0,0 +1,190 @@ +// Copyright (c) 2012 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 <vector> + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "base/threading/sequenced_worker_pool.h" +#include "chrome/browser/extensions/extension_browsertest.h" +#include "chrome/browser/extensions/requirements_checker.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_file_util.h" +#include "chrome/test/base/test_launcher_utils.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/gpu_data_manager.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/gl/gl_switches.h" + +namespace extensions { + +class RequirementsCheckerBrowserTest : public ExtensionBrowserTest { + public: + void SetUpCommandLine(CommandLine* command_line) OVERRIDE { + // In linux, we need to launch GPU process to decide if WebGL is allowed. + // Run it on top of osmesa to avoid bot driver issues. +#if defined(OS_LINUX) + CHECK(test_launcher_utils::OverrideGLImplementation( + command_line, gfx::kGLImplementationOSMesaName)) << + "kUseGL must not be set multiple times!"; +#endif + } + + scoped_refptr<const Extension> LoadExtensionFromDirName( + const std::string& extension_dir_name) { + FilePath extension_path; + std::string load_error; + PathService::Get(chrome::DIR_TEST_DATA, &extension_path); + extension_path = extension_path.AppendASCII("requirements_checker") + .AppendASCII(extension_dir_name); + scoped_refptr<const Extension> extension = + extension_file_util::LoadExtension(extension_path, Extension::LOAD, + 0, &load_error); + CHECK(load_error.length() == 0u); + return extension; + } + + void ValidateRequirementErrors(std::vector<std::string> expected_errors, + std::vector<std::string> actual_errors) { + ASSERT_EQ(expected_errors, actual_errors); + requirement_errors_.swap(actual_errors); + } + + // This should only be called once per test instance. Calling more than once + // will result in stale information in the GPUDataManager which will throw off + // the RequirementsChecker. + void BlackListGPUFeatures(const std::vector<std::string>& features) { + static const std::string json_blacklist = + "{\n" + " \"name\": \"gpu blacklist\",\n" + " \"version\": \"1.0\",\n" + " \"entries\": [\n" + " {\n" + " \"id\": 1,\n" + " \"blacklist\": [\"" + JoinString(features, "\", \"") + "\"]\n" + " }\n" + " ]\n" + "}"; + content::GpuDataManager::GetInstance()->Initialize("0", json_blacklist); + } + + protected: + std::vector<std::string> requirement_errors_; + RequirementsChecker checker_; +}; + +IN_PROC_BROWSER_TEST_F(RequirementsCheckerBrowserTest, CheckEmptyExtension) { + scoped_refptr<const Extension> extension( + LoadExtensionFromDirName("no_requirements")); + ASSERT_TRUE(extension.get()); + checker_.Check(extension, base::Bind( + &RequirementsCheckerBrowserTest::ValidateRequirementErrors, + base::Unretained(this), std::vector<std::string>())); + content::BrowserThread::GetBlockingPool()->FlushForTesting(); +} + +IN_PROC_BROWSER_TEST_F(RequirementsCheckerBrowserTest, CheckNpapiExtension) { + scoped_refptr<const Extension> extension( + LoadExtensionFromDirName("require_npapi")); + ASSERT_TRUE(extension.get()); + + std::vector<std::string> expected_errors; + // npapi plugins are dissalowd on CROMEOS. +#if defined(OS_CHROMEOS) + expected_errors.push_back(l10n_util::GetStringUTF8( + IDS_EXTENSION_NPAPI_NOT_SUPPORTED)); +#endif // defined(OS_CHROMEOS) + + checker_.Check(extension, base::Bind( + &RequirementsCheckerBrowserTest::ValidateRequirementErrors, + base::Unretained(this), expected_errors)); + content::BrowserThread::GetBlockingPool()->FlushForTesting(); +} + +IN_PROC_BROWSER_TEST_F(RequirementsCheckerBrowserTest, DisallowCSS3D) { + scoped_refptr<const Extension> extension( + LoadExtensionFromDirName("require_3d")); + ASSERT_TRUE(extension.get()); + + + // Blacklist css3d + std::vector<std::string> blacklisted_features; + blacklisted_features.push_back("accelerated_compositing"); + BlackListGPUFeatures(blacklisted_features); + content::BrowserThread::GetBlockingPool()->FlushForTesting(); + + std::vector<std::string> expected_errors; + expected_errors.push_back(l10n_util::GetStringUTF8( + IDS_EXTENSION_CSS3D_NOT_SUPPORTED)); + + checker_.Check(extension, base::Bind( + &RequirementsCheckerBrowserTest::ValidateRequirementErrors, + base::Unretained(this), expected_errors)); + content::BrowserThread::GetBlockingPool()->FlushForTesting(); +} + +IN_PROC_BROWSER_TEST_F(RequirementsCheckerBrowserTest, DisallowWebGL) { + scoped_refptr<const Extension> extension( + LoadExtensionFromDirName("require_3d")); + ASSERT_TRUE(extension.get()); + + // Backlist webgl + std::vector<std::string> blacklisted_features; + blacklisted_features.push_back("webgl"); + BlackListGPUFeatures(blacklisted_features); + content::BrowserThread::GetBlockingPool()->FlushForTesting(); + + std::vector<std::string> expected_errors; + expected_errors.push_back(l10n_util::GetStringUTF8( + IDS_EXTENSION_WEBGL_NOT_SUPPORTED)); + + checker_.Check(extension, base::Bind( + &RequirementsCheckerBrowserTest::ValidateRequirementErrors, + base::Unretained(this), expected_errors)); + content::BrowserThread::GetBlockingPool()->FlushForTesting(); +} + +IN_PROC_BROWSER_TEST_F(RequirementsCheckerBrowserTest, DisallowGPUFeatures) { + scoped_refptr<const Extension> extension( + LoadExtensionFromDirName("require_3d")); + ASSERT_TRUE(extension.get()); + + // Backlist both webgl and css3d + std::vector<std::string> blacklisted_features; + blacklisted_features.push_back("webgl"); + blacklisted_features.push_back("accelerated_compositing"); + BlackListGPUFeatures(blacklisted_features); + content::BrowserThread::GetBlockingPool()->FlushForTesting(); + + std::vector<std::string> expected_errors; + expected_errors.push_back(l10n_util::GetStringUTF8( + IDS_EXTENSION_WEBGL_NOT_SUPPORTED)); + expected_errors.push_back(l10n_util::GetStringUTF8( + IDS_EXTENSION_CSS3D_NOT_SUPPORTED)); + + checker_.Check(extension, base::Bind( + &RequirementsCheckerBrowserTest::ValidateRequirementErrors, + base::Unretained(this), expected_errors)); + content::BrowserThread::GetBlockingPool()->FlushForTesting(); +} + +IN_PROC_BROWSER_TEST_F(RequirementsCheckerBrowserTest, Check3DExtension) { + scoped_refptr<const Extension> extension( + LoadExtensionFromDirName("require_3d")); + ASSERT_TRUE(extension.get()); + + checker_.Check(extension, base::Bind( + &RequirementsCheckerBrowserTest::ValidateRequirementErrors, + base::Unretained(this), std::vector<std::string>())); + content::BrowserThread::GetBlockingPool()->FlushForTesting(); +} + +} // extensions diff --git a/chrome/browser/extensions/unpacked_installer.cc b/chrome/browser/extensions/unpacked_installer.cc index 80307c0..298d3ba 100644 --- a/chrome/browser/extensions/unpacked_installer.cc +++ b/chrome/browser/extensions/unpacked_installer.cc @@ -7,12 +7,14 @@ #include "base/bind.h" #include "base/callback.h" #include "base/file_util.h" +#include "base/string_util.h" #include "base/threading/thread_restrictions.h" #include "chrome/browser/extensions/extension_install_prompt.h" #include "chrome/browser/extensions/extension_install_ui.h" #include "chrome/browser/extensions/extension_prefs.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/permissions_updater.h" +#include "chrome/browser/extensions/requirements_checker.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_file_util.h" #include "sync/api/string_ordinal.h" @@ -67,7 +69,10 @@ void SimpleExtensionLoadPrompt::InstallUIProceed() { extensions::PermissionsUpdater perms_updater(service_weak_->profile()); perms_updater.GrantActivePermissions(extension_, false); service_weak_->OnExtensionInstalled( - extension_, false, syncer::StringOrdinal()); // Not from web store. + extension_, + false, // Not from web store. + syncer::StringOrdinal(), + false /* no requirement errors */); } delete this; } @@ -90,6 +95,7 @@ scoped_refptr<UnpackedInstaller> UnpackedInstaller::Create( UnpackedInstaller::UnpackedInstaller(ExtensionService* extension_service) : service_weak_(extension_service->AsWeakPtr()), prompt_for_plugins_(true), + requirements_checker_(new RequirementsChecker()), require_modern_manifest_version_(true) { CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); } @@ -100,12 +106,16 @@ UnpackedInstaller::~UnpackedInstaller() { } void UnpackedInstaller::Load(const FilePath& path_in) { + DCHECK(extension_path_.empty()); extension_path_ = path_in; BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(&UnpackedInstaller::GetAbsolutePath, this)); } void UnpackedInstaller::LoadFromCommandLine(const FilePath& path_in) { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(extension_path_.empty()); + if (!service_weak_.get()) return; // Load extensions from the command line synchronously to avoid a race @@ -121,18 +131,37 @@ void UnpackedInstaller::LoadFromCommandLine(const FilePath& path_in) { } std::string error; - scoped_refptr<const Extension> extension(extension_file_util::LoadExtension( + extension_ = extension_file_util::LoadExtension( extension_path_, Extension::LOAD, GetFlags(), - &error)); + &error); - if (!extension) { + if (!extension_.get()) { ReportExtensionLoadError(error); return; } - OnLoaded(extension); + CheckRequirements(); +} + +void UnpackedInstaller::CheckRequirements() { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + requirements_checker_->Check( + extension_, + base::Bind(&UnpackedInstaller::OnRequirementsChecked, this)); +} + +void UnpackedInstaller::OnRequirementsChecked( + std::vector<std::string> requirement_errors) { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + if (!requirement_errors.empty()) { + ReportExtensionLoadError(JoinString(requirement_errors, ' ')); + return; + } + + OnLoaded(); } int UnpackedInstaller::GetFlags() { @@ -187,13 +216,13 @@ void UnpackedInstaller::LoadWithFileAccess(int flags) { CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); std::string error; - scoped_refptr<const Extension> extension(extension_file_util::LoadExtension( + extension_ = extension_file_util::LoadExtension( extension_path_, Extension::LOAD, flags, - &error)); + &error); - if (!extension) { + if (!extension_.get()) { BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( &UnpackedInstaller::ReportExtensionLoadError, @@ -202,9 +231,7 @@ void UnpackedInstaller::LoadWithFileAccess(int flags) { } BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind( - &UnpackedInstaller::OnLoaded, - this, extension)); + base::Bind(&UnpackedInstaller::CheckRequirements, this)); } void UnpackedInstaller::ReportExtensionLoadError(const std::string &error) { @@ -214,8 +241,7 @@ void UnpackedInstaller::ReportExtensionLoadError(const std::string &error) { service_weak_->ReportExtensionLoadError(extension_path_, error, true); } -void UnpackedInstaller::OnLoaded( - const scoped_refptr<const Extension>& extension) { +void UnpackedInstaller::OnLoaded() { CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (!service_weak_.get()) return; @@ -223,21 +249,22 @@ void UnpackedInstaller::OnLoaded( service_weak_->disabled_extensions(); if (service_weak_->show_extensions_prompts() && prompt_for_plugins_ && - !extension->plugins().empty() && - !disabled_extensions->Contains(extension->id())) { + !extension_->plugins().empty() && + !disabled_extensions->Contains(extension_->id())) { SimpleExtensionLoadPrompt* prompt = new SimpleExtensionLoadPrompt( service_weak_->profile(), service_weak_, - extension); + extension_); prompt->ShowPrompt(); return; // continues in SimpleExtensionLoadPrompt::InstallPrompt* } PermissionsUpdater perms_updater(service_weak_->profile()); - perms_updater.GrantActivePermissions(extension, false); - service_weak_->OnExtensionInstalled(extension, + perms_updater.GrantActivePermissions(extension_, false); + service_weak_->OnExtensionInstalled(extension_, false, // Not from web store. - syncer::StringOrdinal()); + syncer::StringOrdinal(), + false /* no requirement errors */); } } // namespace extensions diff --git a/chrome/browser/extensions/unpacked_installer.h b/chrome/browser/extensions/unpacked_installer.h index 0aa1f93..6b48857 100644 --- a/chrome/browser/extensions/unpacked_installer.h +++ b/chrome/browser/extensions/unpacked_installer.h @@ -6,9 +6,11 @@ #define CHROME_BROWSER_EXTENSIONS_UNPACKED_INSTALLER_H_ #include <string> +#include <vector> #include "base/file_path.h" #include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" class ExtensionService; @@ -16,8 +18,11 @@ class ExtensionService; namespace extensions { class Extension; +class RequirementsChecker; -// Installs and loads an unpacked extension. +// Installs and loads an unpacked extension. Because internal state needs to be +// held about the instalation process, only one call to Load*() should be made +// per UnpackedInstaller. // TODO(erikkay): It might be useful to be able to load a packed extension // (presumably into memory) without installing it. class UnpackedInstaller @@ -56,6 +61,12 @@ class UnpackedInstaller explicit UnpackedInstaller(ExtensionService* extension_service); virtual ~UnpackedInstaller(); + // Must be called from the UI thread. + void CheckRequirements(); + + // Callback from RequirementsChecker. + void OnRequirementsChecked(std::vector<std::string> requirement_errors); + // Verifies if loading unpacked extensions is allowed. bool IsLoadingUnpackedAllowed() const; @@ -74,7 +85,7 @@ class UnpackedInstaller void ReportExtensionLoadError(const std::string& error); // Called when an unpacked extension has been loaded and installed. - void OnLoaded(const scoped_refptr<const Extension>& extension); + void OnLoaded(); // Helper to get the Extension::CreateFlags for the installing extension. int GetFlags(); @@ -89,6 +100,10 @@ class UnpackedInstaller // loading. bool prompt_for_plugins_; + scoped_ptr<RequirementsChecker> requirements_checker_; + + scoped_refptr<const Extension> extension_; + // Whether to require the extension installed to have a modern manifest // version. bool require_modern_manifest_version_; |