summaryrefslogtreecommitdiffstats
path: root/chrome/browser/extensions
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/extensions')
-rw-r--r--chrome/browser/extensions/app_process_apitest.cc3
-rw-r--r--chrome/browser/extensions/crx_installer.cc42
-rw-r--r--chrome/browser/extensions/crx_installer.h20
-rw-r--r--chrome/browser/extensions/extension_service.cc17
-rw-r--r--chrome/browser/extensions/extension_service.h3
-rw-r--r--chrome/browser/extensions/extension_service_unittest.cc114
-rw-r--r--chrome/browser/extensions/extension_system.cc5
-rw-r--r--chrome/browser/extensions/requirements_checker.cc98
-rw-r--r--chrome/browser/extensions/requirements_checker.h58
-rw-r--r--chrome/browser/extensions/requirements_checker_browsertest.cc190
-rw-r--r--chrome/browser/extensions/unpacked_installer.cc65
-rw-r--r--chrome/browser/extensions/unpacked_installer.h19
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_;