Move ChromeRequirementsChecker to //extensions as a PreloadCheck

Remove the "Chrome" from ChromeRequirementsChecker. Make it implement
PreloadCheck and simplify its async logic a bit. Transform
RequirementsCheckerBrowserTest to a unit test.

This lets AppShell and other modules use RequirementsChecker. (This
wasn't possible until GPUFeatureChecker was moved to //content in
http://crrev.com/2666243002.)

BUG=679971
[email protected]
[email protected]

Review-Url: https://codereview.chromium.org/2783813002
Cr-Commit-Position: refs/heads/master@{#462607}
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index ade7ac6..65abf852 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -4041,17 +4041,6 @@
       <message name="IDS_APP_CANT_DOWNGRADE_VERSION" desc="Error message when a user tries to install an app with a lower version than a version that it already installed.">
         Attempted to downgrade app.
       </message>
-      <message name="IDS_EXTENSION_WEBGL_NOT_SUPPORTED" desc="Error message when an extension has a requirement for WebGL that the system does not support.">
-        WebGL is not supported.
-      </message>
-      <message name="IDS_EXTENSION_NPAPI_NOT_SUPPORTED" desc="Error message when an extension has a requirement for plugins that the system does not support.">
-        NPAPI plugins are not supported.
-      </message>
-      <if expr="not use_aura">
-        <message name="IDS_EXTENSION_WINDOW_SHAPE_NOT_SUPPORTED" desc="Error message when an extension has a requirement for shaped windows that the system does not support.">
-          Shaped windows are not supported.
-        </message>
-      </if>
       <if expr="chromeos">
         <message name="IDS_EXTENSION_CANT_INSTALL_IN_DEVICE_LOCAL_ACCOUNT" desc="Error message when a user tries to install or the administrator tries to force-install through policy an extension that is not allowed in a device-local account.">
           <ph name="EXTENSION_NAME">$1<ex>Google Talk</ex></ph> (extension ID "<ph name="EXTENSION_ID">$2<ex>nckgahadagoaajjgafhacjanaoiihapd</ex></ph>") is not allowed in this type of session.
@@ -4108,9 +4097,6 @@
       <message name="IDS_EXTENSION_BAD_FILE_ENCODING" desc="">
         Could not load file '<ph name="RELATIVE_PATH">$1<ex>file.js</ex></ph>' for content script. It isn't UTF-8 encoded.
       </message>
-      <message name="IDS_EXTENSION_LOAD_PLUGIN_PATH_FAILED" desc="">
-        Could not load '<ph name="PLUGIN_PATH">$1<ex>/path/to/file</ex></ph>' for plugin.
-      </message>
       <message name="IDS_EXTENSION_LOAD_ICON_FOR_PAGE_ACTION_FAILED" desc="">
         Could not load icon '<ph name="ICON">$1<ex>icon.png</ex></ph>' for page action.
       </message>
diff --git a/chrome/browser/content_settings/content_settings_internal_extension_provider.cc b/chrome/browser/content_settings/content_settings_internal_extension_provider.cc
index d153e10..bd58d07 100644
--- a/chrome/browser/content_settings/content_settings_internal_extension_provider.cc
+++ b/chrome/browser/content_settings/content_settings_internal_extension_provider.cc
@@ -10,7 +10,6 @@
 #include "chrome/browser/pdf/pdf_extension_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/chrome_content_client.h"
-#include "chrome/common/extensions/api/plugins/plugins_handler.h"
 #include "components/content_settings/core/browser/content_settings_rule.h"
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/content_settings/core/common/content_settings_pattern.h"
@@ -23,6 +22,7 @@
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_set.h"
 #include "extensions/common/features/simple_feature.h"
+#include "extensions/common/manifest_handlers/plugins_handler.h"
 
 using extensions::UnloadedExtensionInfo;
 
diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn
index 6ef51a2..cc6a1797 100644
--- a/chrome/browser/extensions/BUILD.gn
+++ b/chrome/browser/extensions/BUILD.gn
@@ -487,8 +487,6 @@
     "chrome_mojo_service_registration.h",
     "chrome_process_manager_delegate.cc",
     "chrome_process_manager_delegate.h",
-    "chrome_requirements_checker.cc",
-    "chrome_requirements_checker.h",
     "chrome_url_request_util.cc",
     "chrome_url_request_util.h",
     "clipboard_extension_helper_chromeos.cc",
diff --git a/chrome/browser/extensions/api/management/chrome_management_api_delegate.cc b/chrome/browser/extensions/api/management/chrome_management_api_delegate.cc
index 9d52108..8fd3eff 100644
--- a/chrome/browser/extensions/api/management/chrome_management_api_delegate.cc
+++ b/chrome/browser/extensions/api/management/chrome_management_api_delegate.cc
@@ -10,7 +10,6 @@
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/extensions/bookmark_app_helper.h"
 #include "chrome/browser/extensions/chrome_extension_function_details.h"
-#include "chrome/browser/extensions/chrome_requirements_checker.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_util.h"
 #include "chrome/browser/extensions/launch_util.h"
@@ -225,11 +224,6 @@
           web_contents, browser_context, extension, callback));
 }
 
-std::unique_ptr<extensions::RequirementsChecker>
-ChromeManagementAPIDelegate::CreateRequirementsChecker() const {
-  return base::MakeUnique<extensions::ChromeRequirementsChecker>();
-}
-
 std::unique_ptr<extensions::UninstallDialogDelegate>
 ChromeManagementAPIDelegate::UninstallFunctionDelegate(
     extensions::ManagementUninstallFunctionBase* function,
diff --git a/chrome/browser/extensions/api/management/chrome_management_api_delegate.h b/chrome/browser/extensions/api/management/chrome_management_api_delegate.h
index 6c12736..dec9423 100644
--- a/chrome/browser/extensions/api/management/chrome_management_api_delegate.h
+++ b/chrome/browser/extensions/api/management/chrome_management_api_delegate.h
@@ -31,8 +31,6 @@
       content::BrowserContext* browser_context,
       const extensions::Extension* extension,
       const base::Callback<void(bool)>& callback) const override;
-  std::unique_ptr<extensions::RequirementsChecker> CreateRequirementsChecker()
-      const override;
   std::unique_ptr<extensions::UninstallDialogDelegate>
   UninstallFunctionDelegate(
       extensions::ManagementUninstallFunctionBase* function,
diff --git a/chrome/browser/extensions/chrome_requirements_checker.cc b/chrome/browser/extensions/chrome_requirements_checker.cc
deleted file mode 100644
index 3aeacef..0000000
--- a/chrome/browser/extensions/chrome_requirements_checker.cc
+++ /dev/null
@@ -1,89 +0,0 @@
-// 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/chrome_requirements_checker.h"
-
-#include "base/bind.h"
-#include "build/build_config.h"
-#include "chrome/grit/generated_resources.h"
-#include "content/public/browser/browser_thread.h"
-#include "content/public/browser/gpu_feature_checker.h"
-#include "extensions/common/extension.h"
-#include "extensions/common/manifest.h"
-#include "extensions/common/manifest_handlers/requirements_info.h"
-#include "gpu/config/gpu_feature_type.h"
-#include "ui/base/l10n/l10n_util.h"
-
-namespace extensions {
-
-ChromeRequirementsChecker::ChromeRequirementsChecker()
-    : pending_requirement_checks_(0), weak_ptr_factory_(this) {
-}
-
-ChromeRequirementsChecker::~ChromeRequirementsChecker() {
-}
-
-void ChromeRequirementsChecker::Check(
-    const scoped_refptr<const Extension>& extension,
-    const RequirementsCheckedCallback& callback) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  callback_ = callback;
-  const RequirementsInfo& requirements =
-      RequirementsInfo::GetRequirements(extension.get());
-
-  if (requirements.npapi) {
-#if defined(OS_POSIX) && !defined(OS_MACOSX)
-    errors_.push_back(
-        l10n_util::GetStringUTF8(IDS_EXTENSION_NPAPI_NOT_SUPPORTED));
-#endif
-  }
-
-  if (requirements.window_shape) {
-#if !defined(USE_AURA)
-    errors_.push_back(
-        l10n_util::GetStringUTF8(IDS_EXTENSION_WINDOW_SHAPE_NOT_SUPPORTED));
-#endif
-  }
-
-  if (requirements.webgl) {
-    ++pending_requirement_checks_;
-    webgl_checker_ = content::GpuFeatureChecker::Create(
-        gpu::GPU_FEATURE_TYPE_ACCELERATED_WEBGL,
-        base::Bind(&ChromeRequirementsChecker::SetWebGLAvailability,
-                   weak_ptr_factory_.GetWeakPtr()));
-  }
-
-  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();
-}
-
-void ChromeRequirementsChecker::SetWebGLAvailability(bool available) {
-  if (!available) {
-    errors_.push_back(
-        l10n_util::GetStringUTF8(IDS_EXTENSION_WEBGL_NOT_SUPPORTED));
-  }
-  MaybeRunCallback();
-}
-
-void ChromeRequirementsChecker::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/chrome_requirements_checker.h b/chrome/browser/extensions/chrome_requirements_checker.h
deleted file mode 100644
index 07304e9..0000000
--- a/chrome/browser/extensions/chrome_requirements_checker.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// 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_CHROME_REQUIREMENTS_CHECKER_H_
-#define CHROME_BROWSER_EXTENSIONS_CHROME_REQUIREMENTS_CHECKER_H_
-
-#include <vector>
-
-#include "base/callback.h"
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/weak_ptr.h"
-#include "extensions/browser/requirements_checker.h"
-
-namespace content {
-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 ChromeRequirementsChecker : public RequirementsChecker {
- public:
-  ChromeRequirementsChecker();
-  ~ChromeRequirementsChecker() override;
-
- private:
-  // RequirementsChecker:
-  void Check(const scoped_refptr<const Extension>& extension,
-             const RequirementsCheckedCallback& callback) override;
-
-  // Callbacks for the GpuFeatureChecker.
-  void SetWebGLAvailability(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<content::GpuFeatureChecker> webgl_checker_;
-
-  RequirementsCheckedCallback callback_;
-
-  base::WeakPtrFactory<ChromeRequirementsChecker> weak_ptr_factory_;
-
-  DISALLOW_COPY_AND_ASSIGN(ChromeRequirementsChecker);
-};
-
-}  // namespace extensions
-
-#endif  // CHROME_BROWSER_EXTENSIONS_CHROME_REQUIREMENTS_CHECKER_H_
diff --git a/chrome/browser/extensions/crx_installer.cc b/chrome/browser/extensions/crx_installer.cc
index beb79a7..05462ce 100644
--- a/chrome/browser/extensions/crx_installer.cc
+++ b/chrome/browser/extensions/crx_installer.cc
@@ -526,12 +526,11 @@
     return;
 
   // Check for requirement errors.
-  if (!install_checker_->requirement_errors().empty()) {
+  if (!install_checker_->requirements_error_message().empty()) {
     if (error_on_unsupported_requirements_) {
       ReportFailureFromUIThread(
           CrxInstallError(CrxInstallError::ERROR_DECLINED,
-                          base::UTF8ToUTF16(base::JoinString(
-                              install_checker_->requirement_errors(), " "))));
+                          install_checker_->requirements_error_message()));
       return;
     }
     install_flags_ |= kInstallFlagHasRequirementErrors;
@@ -569,9 +568,8 @@
     // Note: |client_| can be NULL in unit_tests!
     if (extension()->from_webstore() && client_)
       client_->install_ui()->SetSkipPostInstallUI(true);
-    ReportFailureFromUIThread(
-        CrxInstallError(CrxInstallError::ERROR_DECLINED,
-                        base::UTF8ToUTF16(install_checker_->policy_error())));
+    ReportFailureFromUIThread(CrxInstallError(
+        CrxInstallError::ERROR_DECLINED, install_checker_->policy_error()));
     return;
   }
 
diff --git a/chrome/browser/extensions/extension_install_checker.cc b/chrome/browser/extensions/extension_install_checker.cc
index 8117973..d529647e 100644
--- a/chrome/browser/extensions/extension_install_checker.cc
+++ b/chrome/browser/extensions/extension_install_checker.cc
@@ -10,11 +10,11 @@
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/extensions/blacklist.h"
 #include "chrome/browser/extensions/blacklist_check.h"
-#include "chrome/browser/extensions/chrome_requirements_checker.h"
 #include "chrome/browser/profiles/profile.h"
 #include "content/public/browser/browser_thread.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/browser/policy_check.h"
+#include "extensions/browser/requirements_checker.h"
 
 namespace extensions {
 
@@ -72,7 +72,7 @@
 void ExtensionInstallChecker::CheckManagementPolicy() {
   // In tests, this check may already be stubbed.
   if (!policy_check_)
-    policy_check_ = base::MakeUnique<PolicyCheck>(profile_, extension_.get());
+    policy_check_ = base::MakeUnique<PolicyCheck>(profile_, extension_);
   policy_check_->Start(
       base::BindOnce(&ExtensionInstallChecker::OnManagementPolicyCheckDone,
                      weak_ptr_factory_.GetWeakPtr()));
@@ -82,7 +82,7 @@
     PreloadCheck::Errors errors) {
   if (!errors.empty()) {
     DCHECK_EQ(1u, errors.count(PreloadCheck::DISALLOWED_BY_POLICY));
-    policy_error_ = base::UTF16ToUTF8(policy_check_->GetErrorMessage());
+    policy_error_ = policy_check_->GetErrorMessage();
   }
 
   running_checks_ &= ~CHECK_MANAGEMENT_POLICY;
@@ -90,17 +90,19 @@
 }
 
 void ExtensionInstallChecker::CheckRequirements() {
-  requirements_checker_ = base::MakeUnique<ChromeRequirementsChecker>();
-  requirements_checker_->Check(
-      extension_, base::Bind(&ExtensionInstallChecker::OnRequirementsCheckDone,
-                             weak_ptr_factory_.GetWeakPtr()));
+  // In tests, this check may already be stubbed.
+  if (!requirements_check_)
+    requirements_check_ = base::MakeUnique<RequirementsChecker>(extension_);
+  requirements_check_->Start(
+      base::BindOnce(&ExtensionInstallChecker::OnRequirementsCheckDone,
+                     weak_ptr_factory_.GetWeakPtr()));
 }
 
 void ExtensionInstallChecker::OnRequirementsCheckDone(
-    const std::vector<std::string>& errors) {
+    PreloadCheck::Errors errors) {
   DCHECK(is_running());
 
-  requirement_errors_ = errors;
+  requirements_error_message_ = requirements_check_->GetErrorMessage();
 
   running_checks_ &= ~CHECK_REQUIREMENTS;
   MaybeInvokeCallback();
@@ -109,8 +111,8 @@
 void ExtensionInstallChecker::CheckBlacklistState() {
   // In tests, this check may already be stubbed.
   if (!blacklist_check_) {
-    blacklist_check_ = base::MakeUnique<BlacklistCheck>(
-        Blacklist::Get(profile_), extension_.get());
+    blacklist_check_ =
+        base::MakeUnique<BlacklistCheck>(Blacklist::Get(profile_), extension_);
   }
   blacklist_check_->Start(
       base::BindOnce(&ExtensionInstallChecker::OnBlacklistStateCheckDone,
@@ -140,7 +142,7 @@
   int failed_mask = 0;
   if (blacklist_error_ == PreloadCheck::BLACKLISTED_ID)
     failed_mask |= CHECK_BLACKLIST;
-  if (!requirement_errors_.empty())
+  if (!requirements_error_message_.empty())
     failed_mask |= CHECK_REQUIREMENTS;
   if (!policy_error_.empty())
     failed_mask |= CHECK_MANAGEMENT_POLICY;
@@ -153,7 +155,7 @@
     // If we are failing fast, discard any pending results.
     blacklist_check_.reset();
     policy_check_.reset();
-    requirements_checker_.reset();
+    requirements_check_.reset();
     weak_ptr_factory_.InvalidateWeakPtrs();
     base::ResetAndReturn(&callback_).Run(failed_mask);
   }
diff --git a/chrome/browser/extensions/extension_install_checker.h b/chrome/browser/extensions/extension_install_checker.h
index 17da2427..767c466 100644
--- a/chrome/browser/extensions/extension_install_checker.h
+++ b/chrome/browser/extensions/extension_install_checker.h
@@ -20,8 +20,6 @@
 
 namespace extensions {
 
-class RequirementsChecker;
-
 // Performs common checks for validating whether an extension may be installed.
 // This class should be Start()-ed at most once.
 class ExtensionInstallChecker {
@@ -60,10 +58,9 @@
   // Returns true if any checks are currently running.
   bool is_running() const { return running_checks_ != 0; }
 
-  // Returns the requirement violations. A non-empty list is considered to be
-  // a check failure.
-  const std::vector<std::string>& requirement_errors() const {
-    return requirement_errors_;
+  // Returns the error message for requirement violations, if any were found.
+  const base::string16& requirements_error_message() const {
+    return requirements_error_message_;
   }
 
   // Returns the blacklist error of the extension. Note that there is only an
@@ -71,7 +68,7 @@
   PreloadCheck::Error blacklist_error() const { return blacklist_error_; }
 
   // Returns whether management policy permits installation of the extension.
-  const std::string& policy_error() const { return policy_error_; }
+  const base::string16& policy_error() const { return policy_error_; }
 
   void SetBlacklistCheckForTesting(std::unique_ptr<PreloadCheck> policy_check) {
     blacklist_check_ = std::move(policy_check);
@@ -79,13 +76,17 @@
   void SetPolicyCheckForTesting(std::unique_ptr<PreloadCheck> policy_check) {
     policy_check_ = std::move(policy_check);
   }
+  void SetRequirementsCheckForTesting(
+      std::unique_ptr<PreloadCheck> requirements_check) {
+    requirements_check_ = std::move(requirements_check);
+  }
 
  protected:
   virtual void CheckManagementPolicy();
   void OnManagementPolicyCheckDone(PreloadCheck::Errors errors);
 
   virtual void CheckRequirements();
-  void OnRequirementsCheckDone(const std::vector<std::string>& errors);
+  void OnRequirementsCheckDone(PreloadCheck::Errors errors);
 
   virtual void CheckBlacklistState();
   void OnBlacklistStateCheckDone(PreloadCheck::Errors errors);
@@ -100,8 +101,8 @@
   scoped_refptr<const Extension> extension_;
 
   // Checks requirements specified in the manifest.
-  std::unique_ptr<RequirementsChecker> requirements_checker_;
-  std::vector<std::string> requirement_errors_;
+  std::unique_ptr<PreloadCheck> requirements_check_;
+  base::string16 requirements_error_message_;
 
   // Checks if the extension is blacklisted.
   std::unique_ptr<PreloadCheck> blacklist_check_;
@@ -109,7 +110,7 @@
 
   // Checks whether management policies allow the extension to be installed.
   std::unique_ptr<PreloadCheck> policy_check_;
-  std::string policy_error_;
+  base::string16 policy_error_;
 
   // Bitmask of enabled checks.
   int enabled_checks_;
diff --git a/chrome/browser/extensions/extension_install_checker_unittest.cc b/chrome/browser/extensions/extension_install_checker_unittest.cc
index 36307ff..5c4238a4 100644
--- a/chrome/browser/extensions/extension_install_checker_unittest.cc
+++ b/chrome/browser/extensions/extension_install_checker_unittest.cc
@@ -34,41 +34,13 @@
 
   ~ExtensionInstallCheckerForTest() override {}
 
-  void set_requirements_error(const std::string& error) {
-    requirements_error_ = error;
-  }
-
   bool is_async() const { return is_async_; }
   void set_is_async(bool is_async) { is_async_ = is_async; }
 
- protected:
-  void MockCheckRequirements() {
-    if (!is_running())
-      return;
-    std::vector<std::string> errors;
-    if (!requirements_error_.empty())
-      errors.push_back(requirements_error_);
-    OnRequirementsCheckDone(errors);
-  }
-
-  void CheckRequirements() override {
-    if (is_async_) {
-      base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE,
-          base::Bind(&ExtensionInstallCheckerForTest::MockCheckRequirements,
-                     base::Unretained(this)));
-    } else {
-      MockCheckRequirements();
-    }
-  }
-
  private:
   // Whether to run the requirements and blacklist checks asynchronously, as
   // they often do in ExtensionInstallChecker.
   bool is_async_ = false;
-
-  // Dummy error for testing.
-  std::string requirements_error_;
 };
 
 class CheckObserver {
@@ -121,29 +93,41 @@
     checker->SetPolicyCheckForTesting(std::move(policy_check));
   }
 
+  void SetRequirementsError(ExtensionInstallCheckerForTest* checker,
+                            PreloadCheck::Error error,
+                            const std::string& message) {
+    auto requirements_check = base::MakeUnique<PreloadCheckStub>();
+    requirements_check->set_is_async(checker->is_async());
+    if (error != PreloadCheck::NONE) {
+      requirements_check->AddError(error);
+      requirements_check->set_error_message(base::UTF8ToUTF16(message));
+    }
+    checker->SetRequirementsCheckForTesting(std::move(requirements_check));
+  }
+
  protected:
   void SetAllPass(ExtensionInstallCheckerForTest* checker) {
     SetBlacklistError(checker, PreloadCheck::NONE);
     SetPolicyError(checker, PreloadCheck::NONE, "");
-    checker->set_requirements_error("");
+    SetRequirementsError(checker, PreloadCheck::NONE, "");
   }
 
   void SetAllErrors(ExtensionInstallCheckerForTest* checker) {
     SetBlacklistError(checker, kBlacklistError);
     SetPolicyError(checker, PreloadCheck::DISALLOWED_BY_POLICY,
                    kDummyPolicyError);
-    checker->set_requirements_error(kDummyRequirementsError);
+    SetRequirementsError(checker, PreloadCheck::NPAPI_NOT_SUPPORTED,
+                         kDummyRequirementsError);
   }
 
   void ExpectRequirementsPass(const ExtensionInstallCheckerForTest& checker) {
-    EXPECT_TRUE(checker.requirement_errors().empty());
+    EXPECT_EQ(base::string16(), checker.requirements_error_message());
   }
 
   void ExpectRequirementsError(const char* expected_error,
                                const ExtensionInstallCheckerForTest& checker) {
-    EXPECT_FALSE(checker.requirement_errors().empty());
-    EXPECT_EQ(std::string(expected_error),
-              checker.requirement_errors().front());
+    EXPECT_EQ(base::UTF8ToUTF16(expected_error),
+              checker.requirements_error_message());
   }
 
   void ExpectRequirementsError(const ExtensionInstallCheckerForTest& checker) {
@@ -165,7 +149,7 @@
   void ExpectPolicyError(const char* expected_error,
                          const ExtensionInstallCheckerForTest& checker) {
     EXPECT_FALSE(checker.policy_error().empty());
-    EXPECT_EQ(std::string(expected_error), checker.policy_error());
+    EXPECT_EQ(base::UTF8ToUTF16(expected_error), checker.policy_error());
   }
 
   void ExpectPolicyError(const ExtensionInstallCheckerForTest& checker) {
diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc
index 06617e6d..9338f19 100644
--- a/chrome/browser/extensions/extension_service.cc
+++ b/chrome/browser/extensions/extension_service.cc
@@ -654,7 +654,9 @@
       extensions::UnpackedInstaller::Create(this)->LoadFromCommandLine(
           base::FilePath(t.token()), &extension_id, false /*only-allow-apps*/);
       // Extension id is added to whitelist after its extension is loaded
-      // because code is executed asynchronously.
+      // because code is executed asynchronously. TODO(michaelpg): Remove this
+      // assumption so loading extensions does not have to be asynchronous:
+      // crbug.com/708354.
       if (switch_name == switches::kDisableExtensionsExcept)
         disable_flag_exempted_extensions_.insert(extension_id);
     }
diff --git a/chrome/browser/extensions/extension_service_unittest.cc b/chrome/browser/extensions/extension_service_unittest.cc
index ee94ed9..d1e8c5e4 100644
--- a/chrome/browser/extensions/extension_service_unittest.cc
+++ b/chrome/browser/extensions/extension_service_unittest.cc
@@ -78,7 +78,6 @@
 #include "chrome/browser/ui/global_error/global_error_service_factory.h"
 #include "chrome/common/chrome_constants.h"
 #include "chrome/common/chrome_switches.h"
-#include "chrome/common/extensions/api/plugins/plugins_handler.h"
 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
 #include "chrome/common/extensions/manifest_handlers/content_scripts_handler.h"
 #include "chrome/common/pref_names.h"
@@ -123,6 +122,7 @@
 #include "extensions/common/manifest_constants.h"
 #include "extensions/common/manifest_handlers/background_info.h"
 #include "extensions/common/manifest_handlers/permissions_parser.h"
+#include "extensions/common/manifest_handlers/plugins_handler.h"
 #include "extensions/common/manifest_url_handlers.h"
 #include "extensions/common/permissions/permission_set.h"
 #include "extensions/common/permissions/permissions_data.h"
diff --git a/chrome/browser/extensions/plugin_manager.cc b/chrome/browser/extensions/plugin_manager.cc
index 0b9d50e..688f79f 100644
--- a/chrome/browser/extensions/plugin_manager.cc
+++ b/chrome/browser/extensions/plugin_manager.cc
@@ -2,21 +2,22 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "chrome/browser/extensions/plugin_manager.h"
+
 #include "base/files/file_path.h"
 #include "base/lazy_instance.h"
 #include "base/path_service.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/extensions/plugin_manager.h"
 #include "chrome/browser/plugins/chrome_plugin_service_filter.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/chrome_paths.h"
-#include "chrome/common/extensions/api/plugins/plugins_handler.h"
 #include "content/public/browser/plugin_service.h"
 #include "content/public/common/pepper_plugin_info.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/manifest_handlers/mime_types_handler.h"
+#include "extensions/common/manifest_handlers/plugins_handler.h"
 #include "url/gurl.h"
 
 #if !defined(DISABLE_NACL)
diff --git a/chrome/browser/extensions/requirements_checker_browsertest.cc b/chrome/browser/extensions/requirements_checker_browsertest.cc
deleted file mode 100644
index d8d40e4a..0000000
--- a/chrome/browser/extensions/requirements_checker_browsertest.cc
+++ /dev/null
@@ -1,136 +0,0 @@
-// 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/files/file_path.h"
-#include "base/memory/ref_counted.h"
-#include "base/message_loop/message_loop.h"
-#include "base/path_service.h"
-#include "base/strings/string_util.h"
-#include "build/build_config.h"
-#include "chrome/browser/extensions/chrome_requirements_checker.h"
-#include "chrome/browser/extensions/extension_browsertest.h"
-#include "chrome/common/chrome_paths.h"
-#include "chrome/grit/generated_resources.h"
-#include "content/public/browser/browser_thread.h"
-#include "content/public/browser/gpu_data_manager.h"
-#include "content/public/test/test_utils.h"
-#include "extensions/common/extension.h"
-#include "extensions/common/file_util.h"
-#include "ui/base/l10n/l10n_util.h"
-
-namespace extensions {
-
-class RequirementsCheckerBrowserTest : public ExtensionBrowserTest {
- public:
-  RequirementsCheckerBrowserTest()
-      : checker_(new ChromeRequirementsChecker()) {}
-
-  scoped_refptr<const Extension> LoadExtensionFromDirName(
-      const std::string& extension_dir_name) {
-    base::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 = file_util::LoadExtension(
-        extension_path, Manifest::UNPACKED, 0, &load_error);
-    CHECK_EQ(0U, load_error.length());
-    return extension;
-  }
-
-  void ValidateRequirementErrors(
-      const std::vector<std::string>& expected_errors,
-      const std::vector<std::string>& actual_errors) {
-    ASSERT_EQ(expected_errors, actual_errors);
-  }
-
- protected:
-  std::unique_ptr<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::RunAllBlockingPoolTasksUntilIdle();
-}
-
-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;
-#if defined(OS_POSIX) && !defined(OS_MACOSX)
-  expected_errors.push_back(l10n_util::GetStringUTF8(
-      IDS_EXTENSION_NPAPI_NOT_SUPPORTED));
-#endif
-
-  checker_->Check(extension, base::Bind(
-      &RequirementsCheckerBrowserTest::ValidateRequirementErrors,
-      base::Unretained(this), expected_errors));
-  content::RunAllBlockingPoolTasksUntilIdle();
-}
-
-IN_PROC_BROWSER_TEST_F(RequirementsCheckerBrowserTest,
-                       CheckWindowShapeExtension) {
-  scoped_refptr<const Extension> extension(
-      LoadExtensionFromDirName("require_window_shape"));
-  ASSERT_TRUE(extension.get());
-
-  std::vector<std::string> expected_errors;
-#if !defined(USE_AURA)
-  expected_errors.push_back(l10n_util::GetStringUTF8(
-      IDS_EXTENSION_WINDOW_SHAPE_NOT_SUPPORTED));
-#endif  // !defined(USE_AURA)
-
-  checker_->Check(extension, base::Bind(
-      &RequirementsCheckerBrowserTest::ValidateRequirementErrors,
-      base::Unretained(this), expected_errors));
-  content::RunAllBlockingPoolTasksUntilIdle();
-}
-
-IN_PROC_BROWSER_TEST_F(RequirementsCheckerBrowserTest, DisallowWebGL) {
-  scoped_refptr<const Extension> extension(
-      LoadExtensionFromDirName("require_3d"));
-  ASSERT_TRUE(extension.get());
-
-  content::GpuDataManager::GetInstance()->BlacklistWebGLForTesting();
-  content::RunAllBlockingPoolTasksUntilIdle();
-
-  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::RunAllBlockingPoolTasksUntilIdle();
-}
-
-IN_PROC_BROWSER_TEST_F(RequirementsCheckerBrowserTest, Check3DExtension) {
-  scoped_refptr<const Extension> extension(
-      LoadExtensionFromDirName("require_3d"));
-  ASSERT_TRUE(extension.get());
-
-  std::vector<std::string> expected_errors;
-
-  if (!content::GpuDataManager::GetInstance()->GpuAccessAllowed(NULL)) {
-    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::RunAllBlockingPoolTasksUntilIdle();
-}
-
-}  // namespace extensions
diff --git a/chrome/browser/extensions/unpacked_installer.cc b/chrome/browser/extensions/unpacked_installer.cc
index 148113f3..0d93fcc 100644
--- a/chrome/browser/extensions/unpacked_installer.cc
+++ b/chrome/browser/extensions/unpacked_installer.cc
@@ -8,7 +8,9 @@
 #include "base/callback.h"
 #include "base/files/file_util.h"
 #include "base/memory/ptr_util.h"
+#include "base/strings/string16.h"
 #include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
 #include "base/threading/thread_restrictions.h"
 #include "chrome/browser/extensions/extension_error_reporter.h"
 #include "chrome/browser/extensions/extension_install_checker.h"
@@ -18,7 +20,6 @@
 #include "chrome/browser/extensions/permissions_updater.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/extensions/extension_install_ui_factory.h"
-#include "chrome/common/extensions/api/plugins/plugins_handler.h"
 #include "components/crx_file/id_util.h"
 #include "components/sync/model/string_ordinal.h"
 #include "content/public/browser/browser_thread.h"
@@ -30,6 +31,7 @@
 #include "extensions/common/extension_l10n_util.h"
 #include "extensions/common/file_util.h"
 #include "extensions/common/manifest.h"
+#include "extensions/common/manifest_handlers/plugins_handler.h"
 #include "extensions/common/manifest_handlers/shared_module_info.h"
 #include "extensions/common/permissions/permissions_data.h"
 
@@ -253,14 +255,12 @@
 void UnpackedInstaller::OnInstallChecksComplete(int failed_checks) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  if (!install_checker_->policy_error().empty()) {
-    ReportExtensionLoadError(install_checker_->policy_error());
-    return;
-  }
+  base::string16 error_message = install_checker_->policy_error();
+  if (error_message.empty())
+    error_message = install_checker_->requirements_error_message();
 
-  if (!install_checker_->requirement_errors().empty()) {
-    ReportExtensionLoadError(
-        base::JoinString(install_checker_->requirement_errors(), " "));
+  if (!error_message.empty()) {
+    ReportExtensionLoadError(base::UTF16ToUTF8(error_message));
     return;
   }
 
diff --git a/chrome/common/BUILD.gn b/chrome/common/BUILD.gn
index b9be3b0..79e8128d 100644
--- a/chrome/common/BUILD.gn
+++ b/chrome/common/BUILD.gn
@@ -252,8 +252,6 @@
       "extensions/api/notifications/notification_style.h",
       "extensions/api/omnibox/omnibox_handler.cc",
       "extensions/api/omnibox/omnibox_handler.h",
-      "extensions/api/plugins/plugins_handler.cc",
-      "extensions/api/plugins/plugins_handler.h",
       "extensions/api/speech/tts_engine_manifest_handler.cc",
       "extensions/api/speech/tts_engine_manifest_handler.h",
       "extensions/api/spellcheck/spellcheck_handler.cc",
diff --git a/chrome/common/extensions/chrome_manifest_handlers.cc b/chrome/common/extensions/chrome_manifest_handlers.cc
index ca7ecf5..a047f72 100644
--- a/chrome/common/extensions/chrome_manifest_handlers.cc
+++ b/chrome/common/extensions/chrome_manifest_handlers.cc
@@ -7,7 +7,6 @@
 #include "build/build_config.h"
 #include "chrome/common/extensions/api/commands/commands_handler.h"
 #include "chrome/common/extensions/api/omnibox/omnibox_handler.h"
-#include "chrome/common/extensions/api/plugins/plugins_handler.h"
 #include "chrome/common/extensions/api/speech/tts_engine_manifest_handler.h"
 #include "chrome/common/extensions/api/spellcheck/spellcheck_handler.h"
 #include "chrome/common/extensions/api/storage/storage_schema_manifest_handler.h"
@@ -26,7 +25,6 @@
 #include "chrome/common/extensions/manifest_handlers/ui_overrides_handler.h"
 #include "extensions/common/manifest_handlers/app_isolation_info.h"
 #include "extensions/common/manifest_handlers/options_page_info.h"
-#include "extensions/common/manifest_handlers/requirements_info.h"
 #include "extensions/common/manifest_url_handlers.h"
 
 #if defined(OS_CHROMEOS)
@@ -53,8 +51,6 @@
   (new MinimumChromeVersionChecker)->Register();
   (new OmniboxHandler)->Register();
   (new OptionsPageManifestHandler)->Register();
-  (new PluginsHandler)->Register();
-  (new RequirementsHandler)->Register();  // Depends on plugins.
   (new SettingsOverridesHandler)->Register();
   (new SpellcheckHandler)->Register();
   (new StorageSchemaManifestHandler)->Register();
diff --git a/chrome/common/extensions/sync_helper.cc b/chrome/common/extensions/sync_helper.cc
index 5e216f91..9152450f 100644
--- a/chrome/common/extensions/sync_helper.cc
+++ b/chrome/common/extensions/sync_helper.cc
@@ -5,7 +5,6 @@
 #include "chrome/common/extensions/sync_helper.h"
 
 #include "base/logging.h"
-#include "chrome/common/extensions/api/plugins/plugins_handler.h"
 #include "chrome/common/extensions/extension_constants.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension.h"
@@ -13,6 +12,7 @@
 #include "extensions/common/features/feature.h"
 #include "extensions/common/features/feature_provider.h"
 #include "extensions/common/manifest.h"
+#include "extensions/common/manifest_handlers/plugins_handler.h"
 #include "extensions/common/manifest_url_handlers.h"
 #include "extensions/common/permissions/permissions_data.h"
 
diff --git a/chrome/common/extensions/sync_type_unittest.cc b/chrome/common/extensions/sync_type_unittest.cc
index 2ac283b4..512ad30 100644
--- a/chrome/common/extensions/sync_type_unittest.cc
+++ b/chrome/common/extensions/sync_type_unittest.cc
@@ -4,13 +4,13 @@
 
 #include "base/files/file_path.h"
 #include "build/build_config.h"
-#include "chrome/common/extensions/api/plugins/plugins_handler.h"
 #include "chrome/common/extensions/sync_helper.h"
 #include "extensions/common/error_utils.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/features/simple_feature.h"
 #include "extensions/common/manifest.h"
 #include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/plugins_handler.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index ac39ffe9..778daa9 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1545,7 +1545,6 @@
       "../browser/extensions/process_management_browsertest.cc",
       "../browser/extensions/process_manager_browsertest.cc",
       "../browser/extensions/renderer_initialization_browsertest.cc",
-      "../browser/extensions/requirements_checker_browsertest.cc",
       "../browser/extensions/sandboxed_pages_apitest.cc",
       "../browser/extensions/service_worker_apitest.cc",
       "../browser/extensions/shared_module_apitest.cc",
diff --git a/chrome/test/data/requirements_checker/no_requirements/manifest.json b/chrome/test/data/requirements_checker/no_requirements/manifest.json
deleted file mode 100644
index a464131..0000000
--- a/chrome/test/data/requirements_checker/no_requirements/manifest.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-  "name": "RequirementsChecker Browser Test",
-  "version": "1.0",
-  "manifest_version": 2
-}
diff --git a/chrome/test/data/requirements_checker/require_3d/manifest.json b/chrome/test/data/requirements_checker/require_3d/manifest.json
deleted file mode 100644
index c5cbf5d..0000000
--- a/chrome/test/data/requirements_checker/require_3d/manifest.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-  "name": "RequirementsChecker Browser Test",
-  "version": "1.0",
-  "manifest_version": 2,
-  "requirements": {
-    "3D": {
-      "features": ["webgl", "css3d"]
-    }
-  }
-}
diff --git a/chrome/test/data/requirements_checker/require_npapi/manifest.json b/chrome/test/data/requirements_checker/require_npapi/manifest.json
deleted file mode 100644
index 90835ac2b..0000000
--- a/chrome/test/data/requirements_checker/require_npapi/manifest.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-  "name": "RequirementsChecker Browser Test",
-  "version": "1.0",
-  "manifest_version": 2,
-  "requirements": {
-    "plugins": {
-      "npapi": true
-    }
-  }
-}
diff --git a/chrome/test/data/requirements_checker/require_window_shape/manifest.json b/chrome/test/data/requirements_checker/require_window_shape/manifest.json
deleted file mode 100644
index 0e145dc..0000000
--- a/chrome/test/data/requirements_checker/require_window_shape/manifest.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-  "name": "RequirementsChecker Browser Test",
-  "version": "1.0",
-  "manifest_version": 2,
-  "requirements": {
-    "window": {
-      "shape": true
-    }
-  }
-}
diff --git a/extensions/browser/BUILD.gn b/extensions/browser/BUILD.gn
index 02a49b2..aee589d 100644
--- a/extensions/browser/BUILD.gn
+++ b/extensions/browser/BUILD.gn
@@ -244,6 +244,7 @@
       "quota_service.h",
       "renderer_startup_helper.cc",
       "renderer_startup_helper.h",
+      "requirements_checker.cc",
       "requirements_checker.h",
       "runtime_data.cc",
       "runtime_data.h",
@@ -452,6 +453,7 @@
     "process_map_unittest.cc",
     "quota_service_unittest.cc",
     "renderer_startup_helper_unittest.cc",
+    "requirements_checker_unittest.cc",
     "runtime_data_unittest.cc",
     "sandboxed_unpacker_unittest.cc",
     "updater/update_service_unittest.cc",
diff --git a/extensions/browser/DEPS b/extensions/browser/DEPS
index 99095ce4..bc6d14a 100644
--- a/extensions/browser/DEPS
+++ b/extensions/browser/DEPS
@@ -19,6 +19,7 @@
   "+device/serial",
   "+device/usb",
   "+google_apis/gaia",
+  "+gpu/config",
   "+grit/extensions_strings.h",
   "+net",
   # This directory contains build flags and does not pull all of PPAPI in.
diff --git a/extensions/browser/api/management/management_api.cc b/extensions/browser/api/management/management_api.cc
index c723341f..8f4df2a34 100644
--- a/extensions/browser/api/management/management_api.cc
+++ b/extensions/browser/api/management/management_api.cc
@@ -447,9 +447,8 @@
     if (prefs->GetDisableReasons(extension_id_) &
             Extension::DISABLE_UNSUPPORTED_REQUIREMENT) {
       // Recheck the requirements.
-      requirements_checker_ = delegate->CreateRequirementsChecker();
-      requirements_checker_->Check(
-          extension,
+      requirements_checker_ = base::MakeUnique<RequirementsChecker>(extension);
+      requirements_checker_->Start(
           base::Bind(&ManagementSetEnabledFunction::OnRequirementsChecked,
                      this));  // This bind creates a reference.
       return RespondLater();
@@ -478,15 +477,15 @@
 }
 
 void ManagementSetEnabledFunction::OnRequirementsChecked(
-    const std::vector<std::string>& requirements_errors) {
-  if (requirements_errors.empty()) {
+    PreloadCheck::Errors errors) {
+  if (errors.empty()) {
     ManagementAPI::GetFactoryInstance()->Get(browser_context())->GetDelegate()->
         EnableExtension(browser_context(), extension_id_);
     Respond(NoArguments());
   } else {
     // TODO(devlin): Should we really be noisy here all the time?
     Respond(Error(keys::kMissingRequirementsError,
-                  base::JoinString(requirements_errors, " ")));
+                  base::UTF16ToUTF8(requirements_checker_->GetErrorMessage())));
   }
 }
 
diff --git a/extensions/browser/api/management/management_api.h b/extensions/browser/api/management/management_api.h
index 785af73..e699ece8 100644
--- a/extensions/browser/api/management/management_api.h
+++ b/extensions/browser/api/management/management_api.h
@@ -16,6 +16,7 @@
 #include "extensions/browser/extension_event_histogram_value.h"
 #include "extensions/browser/extension_function.h"
 #include "extensions/browser/extension_registry_observer.h"
+#include "extensions/browser/preload_check.h"
 
 struct WebApplicationInfo;
 
@@ -112,7 +113,7 @@
  private:
   void OnInstallPromptDone(bool did_accept);
 
-  void OnRequirementsChecked(const std::vector<std::string>& requirements);
+  void OnRequirementsChecked(PreloadCheck::Errors errors);
 
   std::string extension_id_;
 
diff --git a/extensions/browser/api/management/management_api_delegate.h b/extensions/browser/api/management/management_api_delegate.h
index b221907..7a0e461c 100644
--- a/extensions/browser/api/management/management_api_delegate.h
+++ b/extensions/browser/api/management/management_api_delegate.h
@@ -25,7 +25,6 @@
 class ManagementGenerateAppForLinkFunction;
 class ManagementGetPermissionWarningsByManifestFunction;
 class ManagementUninstallFunctionBase;
-class RequirementsChecker;
 
 // Manages the lifetime of the install prompt.
 class InstallPromptDelegate {
@@ -83,10 +82,6 @@
       const Extension* extension,
       const base::Callback<void(bool)>& callback) const = 0;
 
-  // Returns a new RequirementsChecker.
-  virtual std::unique_ptr<RequirementsChecker> CreateRequirementsChecker()
-      const = 0;
-
   // Enables the extension identified by |extension_id|.
   virtual void EnableExtension(content::BrowserContext* context,
                                const std::string& extension_id) const = 0;
diff --git a/extensions/browser/preload_check.h b/extensions/browser/preload_check.h
index 5050d54..d3445ff 100644
--- a/extensions/browser/preload_check.h
+++ b/extensions/browser/preload_check.h
@@ -27,6 +27,9 @@
     BLACKLISTED_ID,
     BLACKLISTED_UNKNOWN,
     DISALLOWED_BY_POLICY,
+    NPAPI_NOT_SUPPORTED,
+    WEBGL_NOT_SUPPORTED,
+    WINDOW_SHAPE_NOT_SUPPORTED,
   };
 
   using Errors = std::set<Error>;
diff --git a/extensions/browser/requirements_checker.cc b/extensions/browser/requirements_checker.cc
new file mode 100644
index 0000000..a70b043
--- /dev/null
+++ b/extensions/browser/requirements_checker.cc
@@ -0,0 +1,99 @@
+// Copyright 2017 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 "extensions/browser/requirements_checker.h"
+
+#include "base/bind.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/gpu_feature_checker.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handlers/requirements_info.h"
+#include "extensions/strings/grit/extensions_strings.h"
+#include "gpu/config/gpu_feature_type.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace extensions {
+
+RequirementsChecker::RequirementsChecker(
+    scoped_refptr<const Extension> extension)
+    : PreloadCheck(extension), weak_ptr_factory_(this) {}
+
+RequirementsChecker::~RequirementsChecker() {}
+
+void RequirementsChecker::Start(ResultCallback callback) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  const RequirementsInfo& requirements =
+      RequirementsInfo::GetRequirements(extension());
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+  if (requirements.npapi)
+    errors_.insert(NPAPI_NOT_SUPPORTED);
+#endif
+
+#if !defined(USE_AURA)
+  if (requirements.window_shape)
+    errors_.insert(WINDOW_SHAPE_NOT_SUPPORTED);
+#endif
+
+  callback_ = std::move(callback);
+  if (requirements.webgl) {
+    webgl_checker_ = content::GpuFeatureChecker::Create(
+        gpu::GPU_FEATURE_TYPE_ACCELERATED_WEBGL,
+        base::Bind(&RequirementsChecker::VerifyWebGLAvailability,
+                   weak_ptr_factory_.GetWeakPtr()));
+    webgl_checker_->CheckGpuFeatureAvailability();
+  } else {
+    PostRunCallback();
+  }
+}
+
+base::string16 RequirementsChecker::GetErrorMessage() const {
+  // Join the error messages into one string.
+  std::vector<std::string> messages;
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+  if (errors_.count(NPAPI_NOT_SUPPORTED)) {
+    messages.push_back(
+        l10n_util::GetStringUTF8(IDS_EXTENSION_NPAPI_NOT_SUPPORTED));
+  }
+#endif
+  if (errors_.count(WEBGL_NOT_SUPPORTED)) {
+    messages.push_back(
+        l10n_util::GetStringUTF8(IDS_EXTENSION_WEBGL_NOT_SUPPORTED));
+  }
+#if !defined(USE_AURA)
+  if (errors_.count(WINDOW_SHAPE_NOT_SUPPORTED)) {
+    messages.push_back(
+        l10n_util::GetStringUTF8(IDS_EXTENSION_WINDOW_SHAPE_NOT_SUPPORTED));
+  }
+#endif
+
+  return base::UTF8ToUTF16(base::JoinString(messages, " "));
+}
+
+void RequirementsChecker::VerifyWebGLAvailability(bool available) {
+  if (!available)
+    errors_.insert(WEBGL_NOT_SUPPORTED);
+  PostRunCallback();
+}
+
+void RequirementsChecker::PostRunCallback() {
+  // TODO(michaelpg): This always forces the callback to run asynchronously
+  // to maintain the assumption in
+  // ExtensionService::LoadExtensionsFromCommandLineFlag(). Remove these helper
+  // functions after crbug.com/708354 is addressed.
+  content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
+                                   base::Bind(&RequirementsChecker::RunCallback,
+                                              weak_ptr_factory_.GetWeakPtr()));
+}
+
+void RequirementsChecker::RunCallback() {
+  DCHECK(callback_);
+  std::move(callback_).Run(errors_);
+}
+
+}  // namespace extensions
diff --git a/extensions/browser/requirements_checker.h b/extensions/browser/requirements_checker.h
index 8844157e..3118325 100644
--- a/extensions/browser/requirements_checker.h
+++ b/extensions/browser/requirements_checker.h
@@ -5,10 +5,14 @@
 #ifndef EXTENSIONS_BROWSER_REQUIREMENTS_CHECKER_H_
 #define EXTENSIONS_BROWSER_REQUIREMENTS_CHECKER_H_
 
-#include <vector>
-
-#include "base/callback.h"
+#include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "extensions/browser/preload_check.h"
+
+namespace content {
+class GpuFeatureChecker;
+}
 
 namespace extensions {
 class Extension;
@@ -17,20 +21,34 @@
 // 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 {
+class RequirementsChecker : public PreloadCheck {
  public:
-  virtual ~RequirementsChecker() {}
+  explicit RequirementsChecker(scoped_refptr<const Extension> extension);
+  ~RequirementsChecker() override;
 
-  using RequirementsCheckedCallback =
-      base::Callback<void(const std::vector<std::string>& /* requirements */)>;
+  // PreloadCheck:
+  void Start(ResultCallback callback) override;
+  // Joins multiple errors into a space-separated string.
+  base::string16 GetErrorMessage() const override;
 
-  // 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.
-  virtual void Check(const scoped_refptr<const Extension>& extension,
-                     const RequirementsCheckedCallback& callback) = 0;
+ private:
+  // Callback for the GpuFeatureChecker.
+  void VerifyWebGLAvailability(bool available);
+
+  // Helper function to post a task on the UI thread to call RunCallback().
+  void PostRunCallback();
+
+  // Helper function to run the callback.
+  void RunCallback();
+
+  scoped_refptr<content::GpuFeatureChecker> webgl_checker_;
+
+  ResultCallback callback_;
+  Errors errors_;
+
+  base::WeakPtrFactory<RequirementsChecker> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(RequirementsChecker);
 };
 
 }  // namespace extensions
diff --git a/extensions/browser/requirements_checker_unittest.cc b/extensions/browser/requirements_checker_unittest.cc
new file mode 100644
index 0000000..c4532f74
--- /dev/null
+++ b/extensions/browser/requirements_checker_unittest.cc
@@ -0,0 +1,176 @@
+// Copyright 2017 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 "extensions/browser/requirements_checker.h"
+
+#include <memory>
+#include <vector>
+
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "content/public/browser/gpu_data_manager.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/extensions_test.h"
+#include "extensions/browser/preload_check.h"
+#include "extensions/browser/preload_check_test_util.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace extensions {
+
+namespace {
+
+// Whether this build supports the window.shape requirement.
+const bool kSupportsWindowShape =
+#if defined(USE_AURA)
+    true;
+#else
+    false;
+#endif
+
+// Whether this build supports the plugins.npapi requirement.
+const bool kSupportsNPAPI =
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+    false;
+#else
+    true;
+#endif
+
+// Returns true if a WebGL check might not fail immediately.
+bool MightSupportWebGL() {
+  return content::GpuDataManager::GetInstance()->GpuAccessAllowed(nullptr);
+}
+
+const char kFeaturesKey[] = "requirements.3D.features";
+const char kFeatureWebGL[] = "webgl";
+const char kFeatureCSS3d[] = "css3d";
+
+}  // namespace
+
+class RequirementsCheckerTest : public ExtensionsTest {
+ public:
+  RequirementsCheckerTest() {
+    manifest_dict_ = base::MakeUnique<base::DictionaryValue>();
+  }
+
+  ~RequirementsCheckerTest() override {}
+
+  void CreateExtension() {
+    manifest_dict_->SetString("name", "dummy name");
+    manifest_dict_->SetString("version", "1");
+
+    std::string error;
+    extension_ =
+        Extension::Create(base::FilePath(), Manifest::UNPACKED, *manifest_dict_,
+                          Extension::NO_FLAGS, &error);
+    ASSERT_TRUE(extension_.get()) << error;
+  }
+
+ protected:
+  void StartChecker() {
+    checker_ = base::MakeUnique<RequirementsChecker>(extension_);
+    // TODO(michaelpg): This should normally not have to be async. Use Run()
+    // instead of RunUntilComplete() after crbug.com/708354 is addressed.
+    runner_.RunUntilComplete(checker_.get());
+  }
+
+  void RequireWindowShape() {
+    manifest_dict_->SetBoolean("requirements.window.shape", true);
+  }
+
+  void RequireNPAPI() {
+    manifest_dict_->SetBoolean("requirements.plugins.npapi", true);
+  }
+
+  void RequireFeature(const char feature[]) {
+    if (!manifest_dict_->HasKey(kFeaturesKey))
+      manifest_dict_->Set(kFeaturesKey, base::MakeUnique<base::ListValue>());
+    base::ListValue* features_list = nullptr;
+    ASSERT_TRUE(manifest_dict_->GetList(kFeaturesKey, &features_list));
+    features_list->AppendString(feature);
+  }
+
+  std::unique_ptr<RequirementsChecker> checker_;
+  PreloadCheckRunner runner_;
+
+ private:
+  content::TestBrowserThreadBundle bundle_;
+  scoped_refptr<Extension> extension_;
+  std::unique_ptr<base::DictionaryValue> manifest_dict_;
+};
+
+// Tests no requirements.
+TEST_F(RequirementsCheckerTest, RequirementsEmpty) {
+  CreateExtension();
+  StartChecker();
+  EXPECT_TRUE(runner_.called());
+  EXPECT_EQ(0u, runner_.errors().size());
+  EXPECT_TRUE(checker_->GetErrorMessage().empty());
+}
+
+// Tests fulfilled requirements.
+TEST_F(RequirementsCheckerTest, RequirementsSuccess) {
+  if (kSupportsWindowShape)
+    RequireWindowShape();
+  if (kSupportsNPAPI)
+    RequireNPAPI();
+  RequireFeature(kFeatureCSS3d);
+
+  CreateExtension();
+  StartChecker();
+  EXPECT_TRUE(runner_.called());
+  EXPECT_EQ(0u, runner_.errors().size());
+  EXPECT_TRUE(checker_->GetErrorMessage().empty());
+}
+
+// Tests multiple requirements failing (on some builds).
+TEST_F(RequirementsCheckerTest, RequirementsFailMultiple) {
+  size_t expected_errors = 0u;
+  if (!kSupportsWindowShape) {
+    RequireWindowShape();
+    expected_errors++;
+  }
+  if (!kSupportsNPAPI) {
+    RequireNPAPI();
+    expected_errors++;
+  }
+  if (!MightSupportWebGL()) {
+    RequireFeature(kFeatureWebGL);
+    expected_errors++;
+  }
+  // css3d should always succeed.
+  RequireFeature(kFeatureCSS3d);
+
+  CreateExtension();
+  StartChecker();
+  EXPECT_TRUE(runner_.called());
+  EXPECT_EQ(expected_errors, runner_.errors().size());
+  EXPECT_EQ(expected_errors == 0, checker_->GetErrorMessage().empty());
+}
+
+// Tests a requirement that might fail asynchronously.
+TEST_F(RequirementsCheckerTest, RequirementsFailWebGL) {
+  content::GpuDataManager::GetInstance()->BlacklistWebGLForTesting();
+  RequireFeature(kFeatureWebGL);
+  CreateExtension();
+  StartChecker();
+
+  // TODO(michaelpg): Check that the runner actually finishes, which requires
+  // waiting for the GPU check to succeed: crbug.com/706204.
+  runner_.WaitForIdle();
+  if (runner_.errors().size()) {
+    EXPECT_THAT(runner_.errors(), testing::UnorderedElementsAre(
+                                      PreloadCheck::WEBGL_NOT_SUPPORTED));
+    EXPECT_FALSE(checker_->GetErrorMessage().empty());
+  }
+}
+
+}  // namespace extensions
diff --git a/extensions/common/BUILD.gn b/extensions/common/BUILD.gn
index b711c37f..8af643d 100644
--- a/extensions/common/BUILD.gn
+++ b/extensions/common/BUILD.gn
@@ -180,6 +180,8 @@
       "manifest_handlers/options_page_info.h",
       "manifest_handlers/permissions_parser.cc",
       "manifest_handlers/permissions_parser.h",
+      "manifest_handlers/plugins_handler.cc",
+      "manifest_handlers/plugins_handler.h",
       "manifest_handlers/requirements_info.cc",
       "manifest_handlers/requirements_info.h",
       "manifest_handlers/sandboxed_page_info.cc",
diff --git a/extensions/common/common_manifest_handlers.cc b/extensions/common/common_manifest_handlers.cc
index a40be154..b4e4cc8 100644
--- a/extensions/common/common_manifest_handlers.cc
+++ b/extensions/common/common_manifest_handlers.cc
@@ -23,6 +23,8 @@
 #include "extensions/common/manifest_handlers/nacl_modules_handler.h"
 #include "extensions/common/manifest_handlers/oauth2_manifest_handler.h"
 #include "extensions/common/manifest_handlers/offline_enabled_info.h"
+#include "extensions/common/manifest_handlers/plugins_handler.h"
+#include "extensions/common/manifest_handlers/requirements_info.h"
 #include "extensions/common/manifest_handlers/sandboxed_page_info.h"
 #include "extensions/common/manifest_handlers/shared_module_info.h"
 #include "extensions/common/manifest_handlers/web_accessible_resources_info.h"
@@ -58,6 +60,8 @@
 #endif
   (new OAuth2ManifestHandler)->Register();
   (new OfflineEnabledHandler)->Register();
+  (new PluginsHandler)->Register();
+  (new RequirementsHandler)->Register();  // Depends on plugins.
   (new SandboxedPageHandler)->Register();
   (new SharedModuleHandler)->Register();
   (new SocketsManifestHandler)->Register();
diff --git a/chrome/common/extensions/api/plugins/plugins_handler.cc b/extensions/common/manifest_handlers/plugins_handler.cc
similarity index 84%
rename from chrome/common/extensions/api/plugins/plugins_handler.cc
rename to extensions/common/manifest_handlers/plugins_handler.cc
index dedf995..d89fdbe 100644
--- a/chrome/common/extensions/api/plugins/plugins_handler.cc
+++ b/extensions/common/manifest_handlers/plugins_handler.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/common/extensions/api/plugins/plugins_handler.h"
+#include "extensions/common/manifest_handlers/plugins_handler.h"
 
 #include <stddef.h>
 
@@ -13,13 +13,13 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "build/build_config.h"
-#include "chrome/grit/generated_resources.h"
 #include "extensions/common/error_utils.h"
 #include "extensions/common/manifest.h"
 #include "extensions/common/manifest_constants.h"
 #include "extensions/common/manifest_handlers/permissions_parser.h"
 #include "extensions/common/permissions/api_permission.h"
 #include "extensions/common/permissions/api_permission_set.h"
+#include "extensions/strings/grit/extensions_strings.h"
 #include "ui/base/l10n/l10n_util.h"
 
 namespace extensions {
@@ -36,11 +36,9 @@
 }  // namespace
 
 PluginInfo::PluginInfo(const base::FilePath& plugin_path, bool plugin_is_public)
-    : path(plugin_path), is_public(plugin_is_public) {
-}
+    : path(plugin_path), is_public(plugin_is_public) {}
 
-PluginInfo::~PluginInfo() {
-}
+PluginInfo::~PluginInfo() {}
 
 // static
 const PluginInfo::PluginVector* PluginInfo::GetPlugins(
@@ -57,11 +55,9 @@
   return data && !data->plugins.empty() ? true : false;
 }
 
-PluginsHandler::PluginsHandler() {
-}
+PluginsHandler::PluginsHandler() {}
 
-PluginsHandler::~PluginsHandler() {
-}
+PluginsHandler::~PluginsHandler() {}
 
 const std::vector<std::string> PluginsHandler::Keys() const {
   return SingleKey(keys::kPlugins);
@@ -100,10 +96,10 @@
       }
     }
 
-    // We don't allow extensions to load NPAPI plugins on Chrome OS, but still
-    // parse the entries to display consistent error messages. If the extension
-    // actually requires the plugins then LoadRequirements will prevent it
-    // loading.
+// We don't allow extensions to load NPAPI plugins on Chrome OS, but still
+// parse the entries to display consistent error messages. If the extension
+// actually requires the plugins then LoadRequirements will prevent it
+// loading.
 #if defined(OS_CHROMEOS)
     continue;
 #endif  // defined(OS_CHROMEOS).
@@ -132,10 +128,10 @@
              plugins->begin();
          plugin != plugins->end(); ++plugin) {
       if (!base::PathExists(plugin->path)) {
-        *error = l10n_util::GetStringFUTF8(
-            IDS_EXTENSION_LOAD_PLUGIN_PATH_FAILED,
-            plugin->path.LossyDisplayName());
-      return false;
+        *error =
+            l10n_util::GetStringFUTF8(IDS_EXTENSION_LOAD_PLUGIN_PATH_FAILED,
+                                      plugin->path.LossyDisplayName());
+        return false;
       }
     }
   }
diff --git a/chrome/common/extensions/api/plugins/plugins_handler.h b/extensions/common/manifest_handlers/plugins_handler.h
similarity index 83%
rename from chrome/common/extensions/api/plugins/plugins_handler.h
rename to extensions/common/manifest_handlers/plugins_handler.h
index 6568b38..c2a580096 100644
--- a/chrome/common/extensions/api/plugins/plugins_handler.h
+++ b/extensions/common/manifest_handlers/plugins_handler.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_COMMON_EXTENSIONS_API_PLUGINS_PLUGINS_HANDLER_H_
-#define CHROME_COMMON_EXTENSIONS_API_PLUGINS_PLUGINS_HANDLER_H_
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_PLUGINS_HANDLER_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_PLUGINS_HANDLER_H_
 
 #include <memory>
 #include <string>
@@ -22,7 +22,7 @@
   ~PluginInfo();
 
   base::FilePath path;  // Path to the plugin.
-  bool is_public;  // False if only this extension can load this plugin.
+  bool is_public;       // False if only this extension can load this plugin.
 
   // Return the plugins for a given |extensions|, or NULL if none exist.
   static const PluginVector* GetPlugins(const Extension* extension);
@@ -50,4 +50,4 @@
 
 }  // namespace extensions
 
-#endif  // CHROME_COMMON_EXTENSIONS_API_PLUGINS_PLUGINS_HANDLER_H_
+#endif  // EXTENSIONS_COMMON_MANIFEST_HANDLERS_PLUGINS_HANDLER_H_
diff --git a/extensions/strings/extensions_strings.grd b/extensions/strings/extensions_strings.grd
index bc58dba..ad0f8918 100644
--- a/extensions/strings/extensions_strings.grd
+++ b/extensions/strings/extensions_strings.grd
@@ -163,6 +163,9 @@
       <message name="IDS_EXTENSION_LOAD_OPTIONS_PAGE_FAILED" desc="">
         Could not load options page '<ph name="OPTIONS_PAGE">$1<ex>page.html</ex></ph>'.
       </message>
+      <message name="IDS_EXTENSION_LOAD_PLUGIN_PATH_FAILED" desc="">
+        Could not load '<ph name="PLUGIN_PATH">$1<ex>/path/to/file</ex></ph>' for plugin.
+      </message>
       <message name="IDS_EXTENSION_LOCALES_NO_DEFAULT_LOCALE_SPECIFIED" desc="">
         Localization used, but default_locale wasn't specified in the manifest.
       </message>
@@ -308,6 +311,11 @@
       <message name="IDS_EXTENSION_INSTALL_PROCESS_CRASHED" desc="Error message in case package fails to install because a utility process crashed.">
         Could not install package because a utility process crashed. Try restarting Chrome and trying again.
       </message>
+      <if expr="is_posix and not is_macosx">
+        <message name="IDS_EXTENSION_NPAPI_NOT_SUPPORTED" desc="Error message when an extension has a requirement for plugins that the system does not support.">
+          NPAPI plugins are not supported.
+        </message>
+      </if>
       <message name="IDS_EXTENSION_PACKAGE_ERROR_CODE" desc="Error message in cases where we fail to install the extension because the crx file is invalid. For example, because the crx header or signature is invalid.">
         Package is invalid: '<ph name="ERROR_CODE">$1<ex>error</ex></ph>'.
       </message>
@@ -327,6 +335,14 @@
           Can not unpack extension.  To safely unpack an extension, there must be a path to your profile directory that does not contain a symlink.  No such path exists for your profile.
         </message>
       </if>
+      <message name="IDS_EXTENSION_WEBGL_NOT_SUPPORTED" desc="Error message when an extension has a requirement for WebGL that the system does not support.">
+        WebGL is not supported.
+      </message>
+      <if expr="not use_aura">
+        <message name="IDS_EXTENSION_WINDOW_SHAPE_NOT_SUPPORTED" desc="Error message when an extension has a requirement for shaped windows that the system does not support.">
+          Shaped windows are not supported.
+        </message>
+      </if>
 
       <!-- Utility process names. Please keep alphabetized. -->
       <message name="IDS_UTILITY_PROCESS_EXTENSION_UNPACKER_NAME" desc="The name of the utility process used for unpacking extensions.">