Commit 9c7b3096 authored by tmdiep@chromium.org's avatar tmdiep@chromium.org

Update the EphemeralAppLauncher for use by the webstorePrivate API

- The WebstoreStandaloneInstaller now supports error codes to allow
translation to API error codes.
- The EphemeralAppLauncher now also returns error codes and performs
more checks before installing an ephemeral app or enabling an
installed app.
- Introduces the ExtensionInstallChecker, which checks blacklist
state, requirement errors and management policies for a given
extension.

BUG=380980
TEST=browser_tests (EphemeralAppLauncherTest.*)
unit_tests (ExtensionInstallCheckerTest.*)

Review URL: https://codereview.chromium.org/339103002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@278890 0039d316-1c4b-4281-b951-d872f2087c98
parent 6964f564
......@@ -4,25 +4,42 @@
#include "chrome/browser/apps/ephemeral_app_launcher.h"
#include "base/command_line.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/extensions/extension_install_checker.h"
#include "chrome/browser/extensions/extension_install_prompt.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/extensions/application_launch.h"
#include "chrome/browser/ui/extensions/extension_enable_flow.h"
#include "chrome/common/chrome_switches.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/management_policy.h"
#include "extensions/common/permissions/permissions_data.h"
using content::WebContents;
using extensions::Extension;
using extensions::ExtensionInstallChecker;
using extensions::ExtensionPrefs;
using extensions::ExtensionRegistry;
using extensions::ExtensionSystem;
using extensions::ManagementPolicy;
using extensions::WebstoreInstaller;
namespace webstore_install = extensions::webstore_install;
namespace {
const char kInvalidManifestError[] = "Invalid manifest";
const char kExtensionTypeError[] = "Ephemeral extensions are not permitted";
const char kLaunchAbortedError[] = "Launch aborted";
const char kExtensionTypeError[] = "Cannot launch an extension";
const char kUserCancelledError[] = "Launch cancelled by the user";
const char kBlacklistedError[] = "App is blacklisted for malware";
const char kRequirementsError[] = "App has missing requirements";
const char kFeatureDisabledError[] = "Launching ephemeral apps is not enabled";
const char kMissingAppError[] = "App is not installed";
const char kAppDisabledError[] = "App is disabled";
Profile* ProfileForWebContents(content::WebContents* contents) {
if (!contents)
......@@ -38,15 +55,46 @@ gfx::NativeWindow NativeWindowForWebContents(content::WebContents* contents) {
return contents->GetTopLevelNativeWindow();
}
// Check whether an extension can be launched. The extension does not need to
// be currently installed.
bool CheckCommonLaunchCriteria(Profile* profile,
const extensions::Extension* extension,
webstore_install::Result* reason,
std::string* error) {
// Only apps can be launched.
if (!extension->is_app()) {
*reason = webstore_install::LAUNCH_UNSUPPORTED_EXTENSION_TYPE;
*error = kExtensionTypeError;
return false;
}
// Do not launch apps blocked by management policies.
ManagementPolicy* management_policy =
ExtensionSystem::Get(profile)->management_policy();
base::string16 policy_error;
if (!management_policy->UserMayLoad(extension, &policy_error)) {
*reason = webstore_install::BLOCKED_BY_POLICY;
*error = base::UTF16ToUTF8(policy_error);
return false;
}
return true;
}
} // namespace
// static
scoped_refptr<EphemeralAppLauncher>
EphemeralAppLauncher::CreateForLauncher(
bool EphemeralAppLauncher::IsFeatureEnabled() {
return CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableEphemeralApps);
}
// static
scoped_refptr<EphemeralAppLauncher> EphemeralAppLauncher::CreateForLauncher(
const std::string& webstore_item_id,
Profile* profile,
gfx::NativeWindow parent_window,
const Callback& callback) {
const LaunchCallback& callback) {
scoped_refptr<EphemeralAppLauncher> installer =
new EphemeralAppLauncher(webstore_item_id,
profile,
......@@ -57,54 +105,57 @@ EphemeralAppLauncher::CreateForLauncher(
}
// static
scoped_refptr<EphemeralAppLauncher>
EphemeralAppLauncher::CreateForLink(
scoped_refptr<EphemeralAppLauncher> EphemeralAppLauncher::CreateForWebContents(
const std::string& webstore_item_id,
content::WebContents* web_contents) {
content::WebContents* web_contents,
const LaunchCallback& callback) {
scoped_refptr<EphemeralAppLauncher> installer =
new EphemeralAppLauncher(webstore_item_id,
web_contents,
Callback());
new EphemeralAppLauncher(webstore_item_id, web_contents, callback);
installer->set_install_source(WebstoreInstaller::INSTALL_SOURCE_OTHER);
return installer;
}
void EphemeralAppLauncher::Start() {
if (!IsFeatureEnabled()) {
InvokeCallback(webstore_install::LAUNCH_FEATURE_DISABLED,
kFeatureDisabledError);
return;
}
// Check whether the app already exists in extension system before downloading
// from the webstore.
const Extension* extension =
ExtensionRegistry::Get(profile())
->GetExtensionById(id(), ExtensionRegistry::EVERYTHING);
if (extension) {
webstore_install::Result result = webstore_install::UNKNOWN_ERROR;
std::string error;
if (!CanLaunchInstalledApp(extension, &result, &error)) {
InvokeCallback(result, error);
return;
}
if (extensions::util::IsAppLaunchableWithoutEnabling(extension->id(),
profile())) {
LaunchApp(extension);
InvokeCallback(std::string());
InvokeCallback(webstore_install::SUCCESS, std::string());
return;
}
// The ephemeral app may have been updated and disabled as it requests
// more permissions. In this case we should always prompt before
// launching.
extension_enable_flow_.reset(
new ExtensionEnableFlow(profile(), extension->id(), this));
if (web_contents())
extension_enable_flow_->StartForWebContents(web_contents());
else
extension_enable_flow_->StartForNativeWindow(parent_window_);
// Keep this object alive until the enable flow is complete.
AddRef(); // Balanced in WebstoreStandaloneInstaller::CompleteInstall.
EnableInstalledApp(extension);
return;
}
// Fetch the app from the webstore.
// Install the app ephemerally and launch when complete.
BeginInstall();
}
EphemeralAppLauncher::EphemeralAppLauncher(const std::string& webstore_item_id,
Profile* profile,
gfx::NativeWindow parent_window,
const Callback& callback)
: WebstoreStandaloneInstaller(webstore_item_id, profile, callback),
const LaunchCallback& callback)
: WebstoreStandaloneInstaller(webstore_item_id, profile, Callback()),
launch_callback_(callback),
parent_window_(parent_window),
dummy_web_contents_(
WebContents::Create(WebContents::CreateParams(profile))) {
......@@ -112,30 +163,149 @@ EphemeralAppLauncher::EphemeralAppLauncher(const std::string& webstore_item_id,
EphemeralAppLauncher::EphemeralAppLauncher(const std::string& webstore_item_id,
content::WebContents* web_contents,
const Callback& callback)
const LaunchCallback& callback)
: WebstoreStandaloneInstaller(webstore_item_id,
ProfileForWebContents(web_contents),
callback),
Callback()),
content::WebContentsObserver(web_contents),
launch_callback_(callback),
parent_window_(NativeWindowForWebContents(web_contents)) {
}
EphemeralAppLauncher::~EphemeralAppLauncher() {}
void EphemeralAppLauncher::LaunchApp(const Extension* extension) const {
DCHECK(extension);
if (!extension->is_app()) {
LOG(ERROR) << "Unable to launch extension " << extension->id()
<< ". It is not an app.";
return;
bool EphemeralAppLauncher::CanLaunchInstalledApp(
const extensions::Extension* extension,
webstore_install::Result* reason,
std::string* error) {
if (!CheckCommonLaunchCriteria(profile(), extension, reason, error))
return false;
// Do not launch blacklisted apps.
if (ExtensionPrefs::Get(profile())->IsExtensionBlacklisted(extension->id())) {
*reason = webstore_install::BLACKLISTED;
*error = kBlacklistedError;
return false;
}
// If the app has missing requirements, it cannot be launched.
if (!extensions::util::IsAppLaunchable(extension->id(), profile())) {
*reason = webstore_install::REQUIREMENT_VIOLATIONS;
*error = kRequirementsError;
return false;
}
return true;
}
void EphemeralAppLauncher::EnableInstalledApp(const Extension* extension) {
extension_enable_flow_.reset(
new ExtensionEnableFlow(profile(), extension->id(), this));
if (web_contents())
extension_enable_flow_->StartForWebContents(web_contents());
else
extension_enable_flow_->StartForNativeWindow(parent_window_);
// Keep this object alive until the enable flow is complete. Either
// ExtensionEnableFlowFinished() or ExtensionEnableFlowAborted() will be
// called.
AddRef();
}
void EphemeralAppLauncher::MaybeLaunchApp() {
webstore_install::Result result = webstore_install::UNKNOWN_ERROR;
std::string error;
ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
const Extension* extension =
registry->GetExtensionById(id(), ExtensionRegistry::EVERYTHING);
if (extension) {
// Although the installation was successful, the app may not be
// launchable.
if (registry->enabled_extensions().Contains(extension->id())) {
result = webstore_install::SUCCESS;
LaunchApp(extension);
} else {
error = kAppDisabledError;
// Determine why the app cannot be launched.
CanLaunchInstalledApp(extension, &result, &error);
}
} else {
// The extension must be present in the registry if installed.
NOTREACHED();
error = kMissingAppError;
}
InvokeCallback(result, error);
}
void EphemeralAppLauncher::LaunchApp(const Extension* extension) const {
DCHECK(extension && extension->is_app() &&
ExtensionRegistry::Get(profile())
->GetExtensionById(extension->id(), ExtensionRegistry::ENABLED));
AppLaunchParams params(profile(), extension, NEW_FOREGROUND_TAB);
params.desktop_type =
chrome::GetHostDesktopTypeForNativeWindow(parent_window_);
OpenApplication(params);
}
void EphemeralAppLauncher::InvokeCallback(webstore_install::Result result,
const std::string& error) {
if (!launch_callback_.is_null()) {
launch_callback_.Run(result, error);
launch_callback_.Reset();
}
}
void EphemeralAppLauncher::AbortLaunch(webstore_install::Result result,
const std::string& error) {
InvokeCallback(result, error);
WebstoreStandaloneInstaller::CompleteInstall(webstore_install::ABORTED,
std::string());
}
scoped_ptr<extensions::ExtensionInstallChecker>
EphemeralAppLauncher::CreateInstallChecker() {
return make_scoped_ptr(new ExtensionInstallChecker(profile()));
}
void EphemeralAppLauncher::CheckEphemeralInstallPermitted() {
scoped_refptr<const Extension> extension = GetLocalizedExtensionForDisplay();
DCHECK(extension.get()); // Checked in OnManifestParsed().
install_checker_ = CreateInstallChecker();
DCHECK(install_checker_.get());
install_checker_->set_extension(extension);
install_checker_->Start(ExtensionInstallChecker::CHECK_BLACKLIST |
ExtensionInstallChecker::CHECK_REQUIREMENTS,
true,
base::Bind(&EphemeralAppLauncher::OnInstallChecked,
base::Unretained(this)));
}
void EphemeralAppLauncher::OnInstallChecked(int check_failures) {
if (!CheckRequestorAlive()) {
AbortLaunch(webstore_install::UNKNOWN_ERROR, std::string());
return;
}
if (install_checker_->blacklist_state() == extensions::BLACKLISTED_MALWARE) {
AbortLaunch(webstore_install::BLACKLISTED, kBlacklistedError);
return;
}
if (!install_checker_->requirement_errors().empty()) {
AbortLaunch(webstore_install::REQUIREMENT_VIOLATIONS,
install_checker_->requirement_errors().front());
return;
}
// Proceed with the normal install flow.
ProceedWithInstallPrompt();
}
bool EphemeralAppLauncher::CheckRequestorAlive() const {
return dummy_web_contents_.get() != NULL || web_contents() != NULL;
}
......@@ -158,12 +328,13 @@ WebContents* EphemeralAppLauncher::GetWebContents() const {
scoped_refptr<ExtensionInstallPrompt::Prompt>
EphemeralAppLauncher::CreateInstallPrompt() const {
DCHECK(extension_.get() != NULL);
const Extension* extension = localized_extension_for_display();
DCHECK(extension); // Checked in OnManifestParsed().
// Skip the prompt by returning null if the app does not need to display
// permission warnings.
extensions::PermissionMessages permissions =
extension_->permissions_data()->GetPermissionMessages();
extension->permissions_data()->GetPermissionMessages();
if (permissions.empty())
return NULL;
......@@ -185,31 +356,24 @@ bool EphemeralAppLauncher::CheckRequestorPermitted(
return true;
}
bool EphemeralAppLauncher::CheckInstallValid(
const base::DictionaryValue& manifest,
std::string* error) {
extension_ = Extension::Create(
base::FilePath(),
extensions::Manifest::INTERNAL,
manifest,
Extension::REQUIRE_KEY | Extension::FROM_WEBSTORE,
id(),
error);
if (!extension_.get()) {
*error = kInvalidManifestError;
return false;
void EphemeralAppLauncher::OnManifestParsed() {
const Extension* extension = GetLocalizedExtensionForDisplay();
if (!extension) {
AbortLaunch(webstore_install::INVALID_MANIFEST, kInvalidManifestError);
return;
}
if (!extension_->is_app()) {
*error = kExtensionTypeError;
return false;
webstore_install::Result result = webstore_install::UNKNOWN_ERROR;
std::string error;
if (!CheckCommonLaunchCriteria(profile(), extension, &result, &error)) {
AbortLaunch(result, error);
return;
}
return true;
CheckEphemeralInstallPermitted();
}
scoped_ptr<ExtensionInstallPrompt>
EphemeralAppLauncher::CreateInstallUI() {
scoped_ptr<ExtensionInstallPrompt> EphemeralAppLauncher::CreateInstallUI() {
if (web_contents())
return make_scoped_ptr(new ExtensionInstallPrompt(web_contents()));
......@@ -225,26 +389,27 @@ EphemeralAppLauncher::CreateApproval() const {
return approval.Pass();
}
void EphemeralAppLauncher::CompleteInstall(const std::string& error) {
if (error.empty()) {
const Extension* extension =
ExtensionRegistry::Get(profile())
->GetExtensionById(id(), ExtensionRegistry::ENABLED);
if (extension)
LaunchApp(extension);
}
void EphemeralAppLauncher::CompleteInstall(webstore_install::Result result,
const std::string& error) {
if (result == webstore_install::SUCCESS)
MaybeLaunchApp();
else if (!launch_callback_.is_null())
InvokeCallback(result, error);
WebstoreStandaloneInstaller::CompleteInstall(error);
WebstoreStandaloneInstaller::CompleteInstall(result, error);
}
void EphemeralAppLauncher::WebContentsDestroyed() {
launch_callback_.Reset();
AbortInstall();
}
void EphemeralAppLauncher::ExtensionEnableFlowFinished() {
CompleteInstall(std::string());
MaybeLaunchApp();
Release(); // Matches the AddRef in EnableInstalledApp().
}
void EphemeralAppLauncher::ExtensionEnableFlowAborted(bool user_initiated) {
CompleteInstall(kLaunchAbortedError);
InvokeCallback(webstore_install::USER_CANCELLED, kUserCancelledError);
Release(); // Matches the AddRef in EnableInstalledApp().
}
......@@ -6,8 +6,10 @@
#define CHROME_BROWSER_APPS_EPHEMERAL_APP_LAUNCHER_H_
#include <string>
#include <vector>
#include "base/basictypes.h"
#include "base/callback.h"
#include "base/scoped_observer.h"
#include "chrome/browser/extensions/webstore_standalone_installer.h"
#include "chrome/browser/ui/extensions/extension_enable_flow_delegate.h"
......@@ -22,6 +24,7 @@ class WebContents;
namespace extensions {
class Extension;
class ExtensionInstallChecker;
class ExtensionRegistry;
}
......@@ -32,38 +35,74 @@ class EphemeralAppLauncher : public extensions::WebstoreStandaloneInstaller,
public content::WebContentsObserver,
public ExtensionEnableFlowDelegate {
public:
typedef WebstoreStandaloneInstaller::Callback Callback;
typedef base::Callback<void(extensions::webstore_install::Result result,
const std::string& error)> LaunchCallback;
// Returns true if launching ephemeral apps is enabled.
static bool IsFeatureEnabled();
// Create for the app launcher.
static scoped_refptr<EphemeralAppLauncher> CreateForLauncher(
const std::string& webstore_item_id,
Profile* profile,
gfx::NativeWindow parent_window,
const Callback& callback);
const LaunchCallback& callback);
// Create for a link within a browser tab.
static scoped_refptr<EphemeralAppLauncher> CreateForLink(
// Create for a web contents.
static scoped_refptr<EphemeralAppLauncher> CreateForWebContents(
const std::string& webstore_item_id,
content::WebContents* web_contents);
content::WebContents* web_contents,
const LaunchCallback& callback);
// Initiate app launch.
void Start();
private:
friend class base::RefCountedThreadSafe<EphemeralAppLauncher>;
protected:
EphemeralAppLauncher(const std::string& webstore_item_id,
Profile* profile,
gfx::NativeWindow parent_window,
const Callback& callback);
const LaunchCallback& callback);
EphemeralAppLauncher(const std::string& webstore_item_id,
content::WebContents* web_contents,
const Callback& callback);
const LaunchCallback& callback);
virtual ~EphemeralAppLauncher();
// Returns true if an app that is already installed in extension system can
// be launched.
bool CanLaunchInstalledApp(const extensions::Extension* extension,
extensions::webstore_install::Result* reason,
std::string* error);
// Initiates the enable flow for an app before it can be launched.
void EnableInstalledApp(const extensions::Extension* extension);
// After the ephemeral installation or enable flow are complete, attempts to
// launch the app and notify the client of the outcome.
void MaybeLaunchApp();
// Launches an app. At this point, it is assumed that the app is enabled and
// can be launched.
void LaunchApp(const extensions::Extension* extension) const;
// Notifies the client of the launch outcome.
void InvokeCallback(extensions::webstore_install::Result result,
const std::string& error);
// Aborts the ephemeral install and notifies the client of the outcome.
void AbortLaunch(extensions::webstore_install::Result result,
const std::string& error);
// Creates an install checker. Allows tests to mock the install checker.
virtual scoped_ptr<extensions::ExtensionInstallChecker>
CreateInstallChecker();
// Determines whether the app can be installed ephemerally.
void CheckEphemeralInstallPermitted();
// Install checker callback.
void OnInstallChecked(int check_failures);
// WebstoreStandaloneInstaller implementation.
virtual bool CheckRequestorAlive() const OVERRIDE;
virtual const GURL& GetRequestorURL() const OVERRIDE;
......@@ -78,13 +117,12 @@ class EphemeralAppLauncher : public extensions::WebstoreStandaloneInstaller,
virtual bool CheckRequestorPermitted(
const base::DictionaryValue& webstore_data,
std::string* error) const OVERRIDE;
virtual bool CheckInstallValid(
const base::DictionaryValue& manifest,
std::string* error) OVERRIDE;
virtual void OnManifestParsed() OVERRIDE;
virtual scoped_ptr<ExtensionInstallPrompt> CreateInstallUI() OVERRIDE;
virtual scoped_ptr<extensions::WebstoreInstaller::Approval>
CreateApproval() const OVERRIDE;
virtual void CompleteInstall(const std::string& error) OVERRIDE;
virtual void CompleteInstall(extensions::webstore_install::Result result,
const std::string& error) OVERRIDE;
// content::WebContentsObserver implementation.
virtual void WebContentsDestroyed() OVERRIDE;
......@@ -93,14 +131,19 @@ class EphemeralAppLauncher : public extensions::WebstoreStandaloneInstaller,
virtual void ExtensionEnableFlowFinished() OVERRIDE;
virtual void ExtensionEnableFlowAborted(bool user_initiated) OVERRIDE;
private:
friend class base::RefCountedThreadSafe<EphemeralAppLauncher>;
friend class EphemeralAppLauncherTest;
LaunchCallback launch_callback_;
gfx::NativeWindow parent_window_;
scoped_ptr<content::WebContents> dummy_web_contents_;
// Created in CheckInstallValid().
scoped_refptr<extensions::Extension> extension_;
scoped_ptr<ExtensionEnableFlow> extension_enable_flow_;
scoped_ptr<extensions::ExtensionInstallChecker> install_checker_;
DISALLOW_COPY_AND_ASSIGN(EphemeralAppLauncher);
};
......
// Copyright 2014 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 "base/message_loop/message_loop_proxy.h"
#include "chrome/browser/apps/ephemeral_app_launcher.h"
#include "chrome/browser/extensions/extension_install_checker.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_test_message_listener.h"
#include "chrome/browser/extensions/test_blacklist.h"
#include "chrome/browser/extensions/webstore_installer_test.h"
#include "chrome/common/chrome_switches.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/management_policy.h"
using extensions::Extension;
using extensions::ExtensionPrefs;
using extensions::ExtensionRegistry;
using extensions::ExtensionSystem;
namespace webstore_install = extensions::webstore_install;
namespace {
const char kWebstoreDomain[] = "cws.com";
const char kAppDomain[] = "app.com";
const char kNonAppDomain[] = "nonapp.com";
const char kTestDataPath[] = "extensions/platform_apps/ephemeral_launcher";
const char kExtensionId[] = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeid";
const char kExtensionTestPath[] = "extension";
const char kNonExistentId[] = "baaaaaaaaaaaaaaaaaaaaaaaaaaaadid";
const char kDefaultAppId[] = "kbiancnbopdghkfedjhfdoegjadfjeal";
const char kDefaultAppCrxFilename[] = "app.crx";
const char kDefaultAppTestPath[] = "app";
const char kAppWithPermissionsId[] = "mbfcnecjknjpipkfkoangpfnhhlpamki";
const char kAppWithPermissionsFilename[] = "app_with_permissions.crx";
class ExtensionInstallCheckerMock : public extensions::ExtensionInstallChecker {
public:
ExtensionInstallCheckerMock(Profile* profile,
const std::string& requirements_error)
: extensions::ExtensionInstallChecker(profile),
requirements_error_(requirements_error) {}
virtual ~ExtensionInstallCheckerMock() {}
private:
virtual void CheckRequirements() OVERRIDE {
// Simulate an asynchronous operation.
base::MessageLoopProxy::current()->PostTask(
FROM_HERE,
base::Bind(&ExtensionInstallCheckerMock::RequirementsErrorCheckDone,
base::Unretained(this),
current_sequence_number()));
}
void RequirementsErrorCheckDone(int sequence_number) {
std::vector<std::string> errors;
errors.push_back(requirements_error_);
OnRequirementsCheckDone(sequence_number, errors);
}
std::string requirements_error_;
};
class EphemeralAppLauncherForTest : public EphemeralAppLauncher {
public:
EphemeralAppLauncherForTest(const std::string& id, Profile* profile)
: EphemeralAppLauncher(id, profile, NULL, LaunchCallback()),
install_initiated_(false),
install_prompt_created_(false) {}
bool install_initiated() const { return install_initiated_; }
bool install_prompt_created() const { return install_prompt_created_; }
void set_requirements_error(const std::string& error) {
requirements_check_error_ = error;
}
private:
// Override necessary functions for testing.
virtual scoped_ptr<extensions::ExtensionInstallChecker> CreateInstallChecker()
OVERRIDE {
if (requirements_check_error_.empty()) {
return EphemeralAppLauncher::CreateInstallChecker();
} else {
return scoped_ptr<extensions::ExtensionInstallChecker>(
new ExtensionInstallCheckerMock(profile(),
requirements_check_error_));
}
}
virtual scoped_ptr<ExtensionInstallPrompt> CreateInstallUI() OVERRIDE {
install_prompt_created_ = true;
return EphemeralAppLauncher::CreateInstallUI();
}
virtual scoped_ptr<extensions::WebstoreInstaller::Approval> CreateApproval()
const OVERRIDE {
install_initiated_ = true;
return EphemeralAppLauncher::CreateApproval();
}
private:
virtual ~EphemeralAppLauncherForTest() {}
friend class base::RefCountedThreadSafe<EphemeralAppLauncherForTest>;
mutable bool install_initiated_;
std::string requirements_check_error_;
bool install_prompt_created_;
};
class LaunchObserver {
public:
LaunchObserver()
: done_(false),
waiting_(false),
result_(webstore_install::UNKNOWN_ERROR) {}
webstore_install::Result result() const { return result_; }
const std::string& error() const { return error_; }
void OnLaunchCallback(webstore_install::Result result,
const std::string& error) {
result_ = result;
error_ = error;
done_ = true;
if (waiting_) {
waiting_ = false;
base::MessageLoopForUI::current()->Quit();
}
}
void Wait() {
if (done_)
return;
waiting_ = true;
content::RunMessageLoop();
}
private:
bool done_;
bool waiting_;
webstore_install::Result result_;
std::string error_;
};
class ManagementPolicyMock : public extensions::ManagementPolicy::Provider {
public:
ManagementPolicyMock() {}
virtual std::string GetDebugPolicyProviderName() const OVERRIDE {
return "ManagementPolicyMock";
}
virtual bool UserMayLoad(const Extension* extension,
base::string16* error) const OVERRIDE {
return false;
}
};
} // namespace
class EphemeralAppLauncherTest : public WebstoreInstallerTest {
public:
EphemeralAppLauncherTest()
: WebstoreInstallerTest(kWebstoreDomain,
kTestDataPath,
kDefaultAppCrxFilename,
kAppDomain,
kNonAppDomain) {}
virtual void SetUpCommandLine(base::CommandLine* command_line) OVERRIDE {
WebstoreInstallerTest::SetUpCommandLine(command_line);
// Enable ephemeral apps flag.
command_line->AppendSwitch(switches::kEnableEphemeralApps);
}
base::FilePath GetTestPath(const char* test_name) {
return test_data_dir_.AppendASCII("platform_apps/ephemeral_launcher")
.AppendASCII(test_name);
}
const Extension* GetInstalledExtension(const std::string& id) {
return ExtensionRegistry::Get(profile())
->GetExtensionById(id, ExtensionRegistry::EVERYTHING);
}
void SetCrxFilename(const std::string& filename) {
GURL crx_url = GenerateTestServerUrl(kWebstoreDomain, filename);
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switches::kAppsGalleryUpdateURL, crx_url.spec());
}
void StartLauncherAndCheckResult(EphemeralAppLauncherForTest* launcher,
webstore_install::Result expected_result,
bool expect_install_initiated) {
ExtensionTestMessageListener launched_listener("launched", false);
LaunchObserver launch_observer;
launcher->launch_callback_ = base::Bind(&LaunchObserver::OnLaunchCallback,
base::Unretained(&launch_observer));
launcher->Start();
launch_observer.Wait();
// Verify the launch result.
EXPECT_EQ(expected_result, launch_observer.result());
EXPECT_EQ(expect_install_initiated, launcher->install_initiated());
// Verify that the app was actually launched if the launcher succeeded.
if (launch_observer.result() == webstore_install::SUCCESS)
EXPECT_TRUE(launched_listener.WaitUntilSatisfied());
else
EXPECT_FALSE(launched_listener.was_satisfied());
// Check the reference count to ensure the launcher instance will not be
// leaked.
EXPECT_TRUE(launcher->HasOneRef());
}
void RunLaunchTest(const std::string& id,
webstore_install::Result expected_result,
bool expect_install_initiated) {
scoped_refptr<EphemeralAppLauncherForTest> launcher(
new EphemeralAppLauncherForTest(id, profile()));
StartLauncherAndCheckResult(
launcher.get(), expected_result, expect_install_initiated);
}
void ValidateAppInstalledEphemerally(const std::string& id) {
EXPECT_TRUE(GetInstalledExtension(id));
EXPECT_TRUE(extensions::util::IsEphemeralApp(id, profile()));
}
const Extension* InstallAndDisableApp(
const char* test_path,
Extension::DisableReason disable_reason) {
const Extension* app = InstallExtension(GetTestPath(test_path), 1);
EXPECT_TRUE(app);
if (!app)
return NULL;
if (disable_reason == Extension::DISABLE_GREYLIST) {
ExtensionPrefs::Get(profile())->SetExtensionBlacklistState(
app->id(), extensions::BLACKLISTED_MALWARE);
}
ExtensionService* service =
ExtensionSystem::Get(profile())->extension_service();
service->DisableExtension(app->id(), disable_reason);
if (disable_reason == Extension::DISABLE_PERMISSIONS_INCREASE) {
// When an extension is disabled due to a permissions increase, this
// flag needs to be set too, for some reason.
ExtensionPrefs::Get(profile())
->SetDidExtensionEscalatePermissions(app, true);
}
EXPECT_FALSE(
ExtensionRegistry::Get(profile())->enabled_extensions().Contains(
app->id()));
return app;
}
};
class EphemeralAppLauncherTestDisabled : public EphemeralAppLauncherTest {
public:
virtual void SetUpCommandLine(base::CommandLine* command_line) OVERRIDE {
// Skip EphemeralAppLauncherTest as it enables the feature.
WebstoreInstallerTest::SetUpCommandLine(command_line);
}
};
// Verifies that an ephemeral app will not be installed and launched if the
// feature is disabled.
IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTestDisabled, FeatureDisabled) {
RunLaunchTest(
kDefaultAppCrxFilename, webstore_install::LAUNCH_FEATURE_DISABLED, false);
EXPECT_FALSE(GetInstalledExtension(kDefaultAppId));
}
// Verifies that an app with no permission warnings will be installed
// ephemerally and launched without prompting the user.
IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest,
LaunchAppWithNoPermissionWarnings) {
scoped_refptr<EphemeralAppLauncherForTest> launcher(
new EphemeralAppLauncherForTest(kDefaultAppId, profile()));
StartLauncherAndCheckResult(launcher.get(), webstore_install::SUCCESS, true);
ValidateAppInstalledEphemerally(kDefaultAppId);
// Apps with no permission warnings should not result in a prompt.
EXPECT_FALSE(launcher->install_prompt_created());
// After an app has been installed ephemerally, it can be launched again
// without installing from the web store.
RunLaunchTest(kDefaultAppId, webstore_install::SUCCESS, false);
}
// Verifies that an app with permission warnings will be installed
// ephemerally and launched if accepted by the user.
IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest,
LaunchAppWithPermissionsWarnings) {
SetCrxFilename(kAppWithPermissionsFilename);
AutoAcceptInstall();
scoped_refptr<EphemeralAppLauncherForTest> launcher(
new EphemeralAppLauncherForTest(kAppWithPermissionsId, profile()));
StartLauncherAndCheckResult(launcher.get(), webstore_install::SUCCESS, true);
ValidateAppInstalledEphemerally(kAppWithPermissionsId);
EXPECT_TRUE(launcher->install_prompt_created());
}
// Verifies that an app with permission warnings will not be installed
// ephemerally if cancelled by the user.
IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest,
CancelInstallAppWithPermissionWarnings) {
SetCrxFilename(kAppWithPermissionsFilename);
AutoCancelInstall();
scoped_refptr<EphemeralAppLauncherForTest> launcher(
new EphemeralAppLauncherForTest(kAppWithPermissionsId, profile()));
StartLauncherAndCheckResult(
launcher.get(), webstore_install::USER_CANCELLED, false);
EXPECT_FALSE(GetInstalledExtension(kAppWithPermissionsId));
EXPECT_TRUE(launcher->install_prompt_created());
}
// Verifies that an extension will not be installed ephemerally.
IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, InstallExtension) {
RunLaunchTest(
kExtensionId, webstore_install::LAUNCH_UNSUPPORTED_EXTENSION_TYPE, false);
EXPECT_FALSE(GetInstalledExtension(kExtensionId));
}
// Verifies that an already installed extension will not be launched.
IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, LaunchExtension) {
const Extension* extension =
InstallExtension(GetTestPath(kExtensionTestPath), 1);
ASSERT_TRUE(extension);
RunLaunchTest(extension->id(),
webstore_install::LAUNCH_UNSUPPORTED_EXTENSION_TYPE,
false);
}
// Verifies that the EphemeralAppLauncher handles non-existent extension ids.
IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, NonExistentExtensionId) {
RunLaunchTest(
kNonExistentId, webstore_install::WEBSTORE_REQUEST_ERROR, false);
EXPECT_FALSE(GetInstalledExtension(kNonExistentId));
}
// Verifies that an app blocked by management policy is not installed
// ephemerally.
IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, BlockedByPolicy) {
// Register a provider that blocks the installation of all apps.
ManagementPolicyMock policy;
ExtensionSystem::Get(profile())->management_policy()->RegisterProvider(
&policy);
RunLaunchTest(kDefaultAppId, webstore_install::BLOCKED_BY_POLICY, false);
EXPECT_FALSE(GetInstalledExtension(kDefaultAppId));
}
// Verifies that an app blacklisted for malware is not installed ephemerally.
IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, BlacklistedForMalware) {
// Mock a BLACKLISTED_MALWARE return status.
extensions::TestBlacklist blacklist_tester(
ExtensionSystem::Get(profile())->blacklist());
blacklist_tester.SetBlacklistState(
kDefaultAppId, extensions::BLACKLISTED_MALWARE, false);
RunLaunchTest(kDefaultAppId, webstore_install::BLACKLISTED, false);
EXPECT_FALSE(GetInstalledExtension(kDefaultAppId));
}
// Verifies that an app with unknown blacklist status is installed ephemerally
// and launched.
IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, BlacklistStateUnknown) {
// Mock a BLACKLISTED_MALWARE return status.
extensions::TestBlacklist blacklist_tester(
ExtensionSystem::Get(profile())->blacklist());
blacklist_tester.SetBlacklistState(
kDefaultAppId, extensions::BLACKLISTED_UNKNOWN, false);
RunLaunchTest(kDefaultAppId, webstore_install::SUCCESS, true);
ValidateAppInstalledEphemerally(kDefaultAppId);
}
// Verifies that an app with unsupported requirements is not installed
// ephemerally.
IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, UnsupportedRequirements) {
scoped_refptr<EphemeralAppLauncherForTest> launcher(
new EphemeralAppLauncherForTest(kDefaultAppId, profile()));
launcher->set_requirements_error("App has unsupported requirements");
StartLauncherAndCheckResult(
launcher.get(), webstore_install::REQUIREMENT_VIOLATIONS, false);
EXPECT_FALSE(GetInstalledExtension(kDefaultAppId));
}
// Verifies that an app disabled due to permissions increase can be enabled
// and launched.
IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, EnableAndLaunchApp) {
const Extension* app = InstallAndDisableApp(
kDefaultAppTestPath, Extension::DISABLE_PERMISSIONS_INCREASE);
ASSERT_TRUE(app);
AutoAcceptInstall();
RunLaunchTest(app->id(), webstore_install::SUCCESS, false);
}
// Verifies that if the user cancels the enable flow, the app will not be
// enabled and launched.
IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, EnableCancelled) {
const Extension* app = InstallAndDisableApp(
kDefaultAppTestPath, Extension::DISABLE_PERMISSIONS_INCREASE);
ASSERT_TRUE(app);
AutoCancelInstall();
RunLaunchTest(app->id(), webstore_install::USER_CANCELLED, false);
}
// Verifies that an installed app that had been blocked by policy cannot be
// launched.
IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, LaunchAppBlockedByPolicy) {
const Extension* app = InstallExtension(GetTestPath(kDefaultAppTestPath), 1);
ASSERT_TRUE(app);
// Simulate blocking of the app after it has been installed.
ManagementPolicyMock policy;
ExtensionSystem::Get(profile())->management_policy()->RegisterProvider(
&policy);
ExtensionSystem::Get(profile())->extension_service()->CheckManagementPolicy();
RunLaunchTest(app->id(), webstore_install::BLOCKED_BY_POLICY, false);
}
// Verifies that an installed blacklisted app cannot be launched.
IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, LaunchBlacklistedApp) {
const Extension* app =
InstallAndDisableApp(kDefaultAppTestPath, Extension::DISABLE_GREYLIST);
ASSERT_TRUE(app);
RunLaunchTest(app->id(), webstore_install::BLACKLISTED, false);
}
// Verifies that an installed app with unsupported requirements cannot be
// launched.
IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest,
LaunchAppWithUnsupportedRequirements) {
const Extension* app = InstallAndDisableApp(
kDefaultAppTestPath, Extension::DISABLE_UNSUPPORTED_REQUIREMENT);
ASSERT_TRUE(app);
RunLaunchTest(app->id(), webstore_install::REQUIREMENT_VIOLATIONS, false);
}
......@@ -52,7 +52,8 @@ bool LaunchEphemeralApp(
// The EphemeralAppLauncher will handle launching of an existing app or
// installing and launching a new ephemeral app.
scoped_refptr<EphemeralAppLauncher> installer =
EphemeralAppLauncher::CreateForLink(app_id, source);
EphemeralAppLauncher::CreateForWebContents(
app_id, source, EphemeralAppLauncher::LaunchCallback());
installer->Start();
return true;
}
......
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/extension_install_checker.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/extensions/blacklist.h"
#include "chrome/browser/extensions/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/management_policy.h"
namespace extensions {
ExtensionInstallChecker::ExtensionInstallChecker(Profile* profile)
: profile_(profile),
blacklist_state_(NOT_BLACKLISTED),
policy_allows_load_(true),
current_sequence_number_(0),
running_checks_(0),
fail_fast_(false),
weak_ptr_factory_(this) {
}
ExtensionInstallChecker::~ExtensionInstallChecker() {
}
void ExtensionInstallChecker::Start(int enabled_checks,
bool fail_fast,
const Callback& callback) {
// Profile is null in tests.
if (profile_) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!extension_.get()) {
NOTREACHED();
return;
}
}
if (is_running() || !enabled_checks || callback.is_null()) {
NOTREACHED();
return;
}
running_checks_ = enabled_checks;
fail_fast_ = fail_fast;
callback_ = callback;
ResetResults();
// Execute the management policy check first as it is synchronous.
if (enabled_checks & CHECK_MANAGEMENT_POLICY) {
CheckManagementPolicy();
if (!is_running())
return;
}
if (enabled_checks & CHECK_REQUIREMENTS) {
CheckRequirements();
if (!is_running())
return;
}
if (enabled_checks & CHECK_BLACKLIST)
CheckBlacklistState();
}
void ExtensionInstallChecker::CheckManagementPolicy() {
DCHECK(extension_.get());
base::string16 error;
bool allow = ExtensionSystem::Get(profile_)->management_policy()->UserMayLoad(
extension_.get(), &error);
OnManagementPolicyCheckDone(allow, base::UTF16ToUTF8(error));
}
void ExtensionInstallChecker::OnManagementPolicyCheckDone(
bool allows_load,
const std::string& error) {
policy_allows_load_ = allows_load;
policy_error_ = error;
DCHECK(policy_allows_load_ || !policy_error_.empty());
running_checks_ &= ~CHECK_MANAGEMENT_POLICY;
MaybeInvokeCallback();
}
void ExtensionInstallChecker::CheckRequirements() {
DCHECK(extension_.get());
if (!requirements_checker_.get())
requirements_checker_.reset(new RequirementsChecker());
requirements_checker_->Check(
extension_,
base::Bind(&ExtensionInstallChecker::OnRequirementsCheckDone,
weak_ptr_factory_.GetWeakPtr(),
current_sequence_number_));
}
void ExtensionInstallChecker::OnRequirementsCheckDone(
int sequence_number,
std::vector<std::string> errors) {
// Some pending results may arrive after fail fast.
if (sequence_number != current_sequence_number_)
return;
requirement_errors_ = errors;
running_checks_ &= ~CHECK_REQUIREMENTS;
MaybeInvokeCallback();
}
void ExtensionInstallChecker::CheckBlacklistState() {
DCHECK(extension_.get());
extensions::Blacklist* blacklist =
ExtensionSystem::Get(profile_)->blacklist();
blacklist->IsBlacklisted(
extension_->id(),
base::Bind(&ExtensionInstallChecker::OnBlacklistStateCheckDone,
weak_ptr_factory_.GetWeakPtr(),
current_sequence_number_));
}
void ExtensionInstallChecker::OnBlacklistStateCheckDone(int sequence_number,
BlacklistState state) {
// Some pending results may arrive after fail fast.
if (sequence_number != current_sequence_number_)
return;
blacklist_state_ = state;
running_checks_ &= ~CHECK_BLACKLIST;
MaybeInvokeCallback();
}
void ExtensionInstallChecker::ResetResults() {
requirement_errors_.clear();
blacklist_state_ = NOT_BLACKLISTED;
policy_allows_load_ = true;
policy_error_.clear();
}
void ExtensionInstallChecker::MaybeInvokeCallback() {
if (callback_.is_null())
return;
// Set bits for failed checks.
int failed_mask = 0;
if (blacklist_state_ == BLACKLISTED_MALWARE)
failed_mask |= CHECK_BLACKLIST;
if (!requirement_errors_.empty())
failed_mask |= CHECK_REQUIREMENTS;
if (!policy_allows_load_)
failed_mask |= CHECK_MANAGEMENT_POLICY;
// Invoke callback if all checks are complete or there was at least one
// failure and |fail_fast_| is true.
if (!is_running() || (failed_mask && fail_fast_)) {
// If we are failing fast, discard any pending results.
weak_ptr_factory_.InvalidateWeakPtrs();
running_checks_ = 0;
++current_sequence_number_;
callback_.Run(failed_mask);
callback_.Reset();
}
}
} // namespace extensions
// Copyright 2014 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_EXTENSION_INSTALL_CHECKER_H_
#define CHROME_BROWSER_EXTENSIONS_EXTENSION_INSTALL_CHECKER_H_
#include <string>
#include <vector>
#include "base/callback.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/string16.h"
#include "extensions/browser/blacklist_state.h"
#include "extensions/common/extension.h"
class Profile;
namespace extensions {
class RequirementsChecker;
// Performs common checks for an extension. Extensions that violate these checks
// would be disabled or not even installed.
class ExtensionInstallChecker {
public:
// Called when checks are complete. The returned value is a bitmask of
// failed checks.
typedef base::Callback<void(int)> Callback;
enum CheckType {
// Check the blacklist state of the extension.
CHECK_BLACKLIST = 1 << 0,
// Check whether the extension has requirement errors.
CHECK_REQUIREMENTS = 1 << 1,
// Check whether the extension can be installed and loaded, according to
// management policies.
CHECK_MANAGEMENT_POLICY = 1 << 2,
// Perform all checks.
CHECK_ALL = (1 << 3) - 1
};
explicit ExtensionInstallChecker(Profile* profile);
virtual ~ExtensionInstallChecker();
// Start a set of checks. |enabled_checks| is a bitmask of CheckTypes to run.
// If |fail_fast| is true, the callback will be invoked once any check fails.
// Otherwise it will be invoked when all checks have completed. |callback|
// will only be called once.
// This function must be called on the UI thread. The callback also occurs on
// the UI thread. Checks may run asynchronously in parallel.
// If checks are currently running, the caller must wait for the callback to
// be invoked before starting another set of checks.
void Start(int enabled_checks, bool fail_fast, const Callback& callback);
Profile* profile() const { return profile_; }
scoped_refptr<const Extension> extension() { return extension_; }
void set_extension(const Extension* extension) { extension_ = extension; }
// 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 blacklist state of the extension. A blacklist state of
// BLACKLISTED_MALWARE is considered to be a check failure.
BlacklistState blacklist_state() const { return blacklist_state_; }
// Returns whether management policy permits installation of the extension.
bool policy_allows_load() const { return policy_allows_load_; }
const std::string& policy_error() const { return policy_error_; }
protected:
virtual void CheckManagementPolicy();
void OnManagementPolicyCheckDone(bool allows_load, const std::string& error);
virtual void CheckRequirements();
void OnRequirementsCheckDone(int sequence_number,
std::vector<std::string> errors);
virtual void CheckBlacklistState();
void OnBlacklistStateCheckDone(int sequence_number, BlacklistState state);
virtual void ResetResults();
int current_sequence_number() const { return current_sequence_number_; }
private:
void MaybeInvokeCallback();
scoped_ptr<RequirementsChecker> requirements_checker_;
// The Profile where the extension is being installed in.
Profile* profile_;
// The extension to run checks for.
scoped_refptr<const Extension> extension_;
// Requirement violations.
std::vector<std::string> requirement_errors_;
// Result of the blacklist state check.
BlacklistState blacklist_state_;
// Whether the extension can be installed, according to management policies.
bool policy_allows_load_;
std::string policy_error_;
// The sequence number of the currently running checks.
int current_sequence_number_;
// Bitmask of currently running checks.
int running_checks_;
// If true, the callback is invoked when the first check fails.
bool fail_fast_;
// The callback to invoke when checks are complete.
Callback callback_;
base::WeakPtrFactory<ExtensionInstallChecker> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(ExtensionInstallChecker);
};
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_INSTALL_CHECKER_H_
// Copyright 2014 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 "base/bind.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "chrome/browser/extensions/extension_install_checker.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace extensions {
namespace {
const BlacklistState kBlacklistStateError = BLACKLISTED_MALWARE;
const char kDummyRequirementsError[] = "Requirements error";
const char kDummyPolicyError[] = "Cannot install extension";
const char kDummyPolicyError2[] = "Another policy error";
const char kDummyRequirementsError2[] = "Another requirements error";
const BlacklistState kBlacklistState2 = BLACKLISTED_SECURITY_VULNERABILITY;
} // namespace
// Stubs most of the checks since we are interested in validating the logic in
// the install checker. This class implements a synchronous version of all
// checks.
class ExtensionInstallCheckerForTest : public ExtensionInstallChecker {
public:
ExtensionInstallCheckerForTest()
: ExtensionInstallChecker(NULL),
requirements_check_called_(false),
blacklist_check_called_(false),
policy_check_called_(false),
blacklist_state_(NOT_BLACKLISTED) {}
virtual ~ExtensionInstallCheckerForTest() {}
void set_requirements_error(const std::string& error) {
requirements_error_ = error;
}
void set_policy_check_error(const std::string& error) {
policy_check_error_ = error;
}
void set_blacklist_state(BlacklistState state) { blacklist_state_ = state; }
bool requirements_check_called() const { return requirements_check_called_; }
bool blacklist_check_called() const { return blacklist_check_called_; }
bool policy_check_called() const { return policy_check_called_; }
void MockCheckRequirements(int sequence_number) {
std::vector<std::string> errors;
if (!requirements_error_.empty())
errors.push_back(requirements_error_);
OnRequirementsCheckDone(sequence_number, errors);
}
void MockCheckBlacklistState(int sequence_number) {
OnBlacklistStateCheckDone(sequence_number, blacklist_state_);
}
protected:
virtual void CheckRequirements() OVERRIDE {
requirements_check_called_ = true;
MockCheckRequirements(current_sequence_number());
}
virtual void CheckManagementPolicy() OVERRIDE {
policy_check_called_ = true;
OnManagementPolicyCheckDone(policy_check_error_.empty(),
policy_check_error_);
}
virtual void CheckBlacklistState() OVERRIDE {
blacklist_check_called_ = true;
MockCheckBlacklistState(current_sequence_number());
}
virtual void ResetResults() OVERRIDE {
ExtensionInstallChecker::ResetResults();
requirements_check_called_ = false;
blacklist_check_called_ = false;
policy_check_called_ = false;
}
bool requirements_check_called_;
bool blacklist_check_called_;
bool policy_check_called_;
// Dummy errors for testing.
std::string requirements_error_;
std::string policy_check_error_;
BlacklistState blacklist_state_;
};
// This class implements asynchronous mocks of the requirements and blacklist
// checks.
class ExtensionInstallCheckerAsync : public ExtensionInstallCheckerForTest {
protected:
virtual void CheckRequirements() OVERRIDE {
requirements_check_called_ = true;
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&ExtensionInstallCheckerForTest::MockCheckRequirements,
base::Unretained(this),
current_sequence_number()));
}
virtual void CheckBlacklistState() OVERRIDE {
blacklist_check_called_ = true;
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&ExtensionInstallCheckerForTest::MockCheckBlacklistState,
base::Unretained(this),
current_sequence_number()));
}
};
class CheckObserver {
public:
CheckObserver() : result_(0), call_count_(0) {}
int result() const { return result_; }
int call_count() const { return call_count_; }
void OnChecksComplete(int checks_failed) {
result_ = checks_failed;
++call_count_;
}
void Wait() {
if (call_count_)
return;
base::RunLoop().RunUntilIdle();
}
private:
int result_;
int call_count_;
};
class ExtensionInstallCheckerTest : public testing::Test {
public:
ExtensionInstallCheckerTest() {}
virtual ~ExtensionInstallCheckerTest() {}
void RunSecondInvocation(ExtensionInstallCheckerForTest* checker,
int checks_failed) {
EXPECT_GT(checks_failed, 0);
EXPECT_FALSE(checker->is_running());
ValidateExpectedCalls(ExtensionInstallChecker::CHECK_ALL, *checker);
// Set up different return values.
checker->set_blacklist_state(kBlacklistState2);
checker->set_policy_check_error(kDummyPolicyError2);
checker->set_requirements_error(kDummyRequirementsError2);
// Run the install checker again and ensure the second set of return values
// is received.
checker->Start(
ExtensionInstallChecker::CHECK_ALL,
false /* fail fast */,
base::Bind(&ExtensionInstallCheckerTest::ValidateSecondInvocation,
base::Unretained(this),
checker));
}
void ValidateSecondInvocation(ExtensionInstallCheckerForTest* checker,
int checks_failed) {
EXPECT_FALSE(checker->is_running());
EXPECT_EQ(ExtensionInstallChecker::CHECK_REQUIREMENTS |
ExtensionInstallChecker::CHECK_MANAGEMENT_POLICY,
checks_failed);
ValidateExpectedCalls(ExtensionInstallChecker::CHECK_ALL, *checker);
EXPECT_EQ(kBlacklistState2, checker->blacklist_state());
ExpectPolicyError(kDummyPolicyError2, *checker);
ExpectRequirementsError(kDummyRequirementsError2, *checker);
}
protected:
void SetAllErrors(ExtensionInstallCheckerForTest* checker) {
checker->set_blacklist_state(kBlacklistStateError);
checker->set_policy_check_error(kDummyPolicyError);
checker->set_requirements_error(kDummyRequirementsError);
}
void ValidateExpectedCalls(int call_mask,
const ExtensionInstallCheckerForTest& checker) {
bool expect_blacklist_checked =
(call_mask & ExtensionInstallChecker::CHECK_BLACKLIST) != 0;
bool expect_requirements_checked =
(call_mask & ExtensionInstallChecker::CHECK_REQUIREMENTS) != 0;
bool expect_policy_checked =
(call_mask & ExtensionInstallChecker::CHECK_MANAGEMENT_POLICY) != 0;
EXPECT_EQ(expect_blacklist_checked, checker.blacklist_check_called());
EXPECT_EQ(expect_policy_checked, checker.policy_check_called());
EXPECT_EQ(expect_requirements_checked, checker.requirements_check_called());
}
void ExpectRequirementsPass(const ExtensionInstallCheckerForTest& checker) {
EXPECT_TRUE(checker.requirement_errors().empty());
}
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());
}
void ExpectRequirementsError(const ExtensionInstallCheckerForTest& checker) {
ExpectRequirementsError(kDummyRequirementsError, checker);
}
void ExpectBlacklistPass(const ExtensionInstallCheckerForTest& checker) {
EXPECT_EQ(NOT_BLACKLISTED, checker.blacklist_state());
}
void ExpectBlacklistError(const ExtensionInstallCheckerForTest& checker) {
EXPECT_EQ(kBlacklistStateError, checker.blacklist_state());
}
void ExpectPolicyPass(const ExtensionInstallCheckerForTest& checker) {
EXPECT_TRUE(checker.policy_allows_load());
EXPECT_TRUE(checker.policy_error().empty());
}
void ExpectPolicyError(const char* expected_error,
const ExtensionInstallCheckerForTest& checker) {
EXPECT_FALSE(checker.policy_allows_load());
EXPECT_FALSE(checker.policy_error().empty());
EXPECT_EQ(std::string(expected_error), checker.policy_error());
}
void ExpectPolicyError(const ExtensionInstallCheckerForTest& checker) {
ExpectPolicyError(kDummyPolicyError, checker);
}
void RunChecker(ExtensionInstallCheckerForTest* checker,
bool fail_fast,
int checks_to_run,
int expected_checks_run,
int expected_result) {
CheckObserver observer;
checker->Start(checks_to_run,
fail_fast,
base::Bind(&CheckObserver::OnChecksComplete,
base::Unretained(&observer)));
observer.Wait();
EXPECT_FALSE(checker->is_running());
EXPECT_EQ(expected_result, observer.result());
EXPECT_EQ(1, observer.call_count());
ValidateExpectedCalls(expected_checks_run, *checker);
}
void DoRunAllChecksPass(ExtensionInstallCheckerForTest* checker) {
RunChecker(checker,
false /* fail fast */,
ExtensionInstallChecker::CHECK_ALL,
ExtensionInstallChecker::CHECK_ALL,
0);
ExpectRequirementsPass(*checker);
ExpectPolicyPass(*checker);
ExpectBlacklistPass(*checker);
}
void DoRunAllChecksFail(ExtensionInstallCheckerForTest* checker) {
SetAllErrors(checker);
RunChecker(checker,
false /* fail fast */,
ExtensionInstallChecker::CHECK_ALL,
ExtensionInstallChecker::CHECK_ALL,
ExtensionInstallChecker::CHECK_ALL);
ExpectRequirementsError(*checker);
ExpectPolicyError(*checker);
ExpectBlacklistError(*checker);
}
void DoRunSubsetOfChecks(ExtensionInstallCheckerForTest* checker) {
// Test check set 1.
int tests_to_run = ExtensionInstallChecker::CHECK_MANAGEMENT_POLICY |
ExtensionInstallChecker::CHECK_REQUIREMENTS;
SetAllErrors(checker);
RunChecker(checker, false, tests_to_run, tests_to_run, tests_to_run);
ExpectRequirementsError(*checker);
ExpectPolicyError(*checker);
ExpectBlacklistPass(*checker);
// Test check set 2.
tests_to_run = ExtensionInstallChecker::CHECK_BLACKLIST |
ExtensionInstallChecker::CHECK_REQUIREMENTS;
SetAllErrors(checker);
RunChecker(checker, false, tests_to_run, tests_to_run, tests_to_run);
ExpectRequirementsError(*checker);
ExpectPolicyPass(*checker);
ExpectBlacklistError(*checker);
// Test a single check.
tests_to_run = ExtensionInstallChecker::CHECK_BLACKLIST;
SetAllErrors(checker);
RunChecker(checker, false, tests_to_run, tests_to_run, tests_to_run);
ExpectRequirementsPass(*checker);
ExpectPolicyPass(*checker);
ExpectBlacklistError(*checker);
}
private:
// A message loop is required for the asynchronous tests.
base::MessageLoop message_loop;
};
class ExtensionInstallCheckerMultipleInvocationTest
: public ExtensionInstallCheckerTest {
public:
ExtensionInstallCheckerMultipleInvocationTest() : callback_count_(0) {}
virtual ~ExtensionInstallCheckerMultipleInvocationTest() {}
void RunSecondInvocation(ExtensionInstallCheckerForTest* checker,
int checks_failed) {
ASSERT_EQ(0, callback_count_);
++callback_count_;
EXPECT_FALSE(checker->is_running());
EXPECT_GT(checks_failed, 0);
ValidateExpectedCalls(ExtensionInstallChecker::CHECK_ALL, *checker);
// Set up different return values.
checker->set_blacklist_state(kBlacklistState2);
checker->set_policy_check_error(kDummyPolicyError2);
checker->set_requirements_error(kDummyRequirementsError2);
// Run the install checker again and ensure the second set of return values
// is received.
checker->Start(ExtensionInstallChecker::CHECK_ALL,
false /* fail fast */,
base::Bind(&ExtensionInstallCheckerMultipleInvocationTest::
ValidateSecondInvocation,
base::Unretained(this),
checker));
}
void ValidateSecondInvocation(ExtensionInstallCheckerForTest* checker,
int checks_failed) {
ASSERT_EQ(1, callback_count_);
EXPECT_FALSE(checker->is_running());
EXPECT_EQ(ExtensionInstallChecker::CHECK_REQUIREMENTS |
ExtensionInstallChecker::CHECK_MANAGEMENT_POLICY,
checks_failed);
ValidateExpectedCalls(ExtensionInstallChecker::CHECK_ALL, *checker);
EXPECT_EQ(kBlacklistState2, checker->blacklist_state());
ExpectPolicyError(kDummyPolicyError2, *checker);
ExpectRequirementsError(kDummyRequirementsError2, *checker);
}
private:
int callback_count_;
};
// Test the case where all tests pass.
TEST_F(ExtensionInstallCheckerTest, AllSucceeded) {
ExtensionInstallCheckerForTest sync_checker;
DoRunAllChecksPass(&sync_checker);
ExtensionInstallCheckerAsync async_checker;
DoRunAllChecksPass(&async_checker);
}
// Test the case where all tests fail.
TEST_F(ExtensionInstallCheckerTest, AllFailed) {
ExtensionInstallCheckerForTest sync_checker;
DoRunAllChecksFail(&sync_checker);
ExtensionInstallCheckerAsync async_checker;
DoRunAllChecksFail(&async_checker);
}
// Test running only a subset of tests.
TEST_F(ExtensionInstallCheckerTest, RunSubsetOfChecks) {
ExtensionInstallCheckerForTest sync_checker;
ExtensionInstallCheckerAsync async_checker;
DoRunSubsetOfChecks(&sync_checker);
DoRunSubsetOfChecks(&async_checker);
}
// Test fail fast with synchronous callbacks.
TEST_F(ExtensionInstallCheckerTest, FailFastSync) {
// This test assumes some internal knowledge of the implementation - that
// the policy check runs first.
ExtensionInstallCheckerForTest checker;
SetAllErrors(&checker);
RunChecker(&checker,
true /* fail fast */,
ExtensionInstallChecker::CHECK_ALL,
ExtensionInstallChecker::CHECK_MANAGEMENT_POLICY,
ExtensionInstallChecker::CHECK_MANAGEMENT_POLICY);
ExpectRequirementsPass(checker);
ExpectPolicyError(checker);
ExpectBlacklistPass(checker);
// This test assumes some internal knowledge of the implementation - that
// the requirements check runs before the blacklist check.
SetAllErrors(&checker);
RunChecker(&checker,
true /* fail fast */,
ExtensionInstallChecker::CHECK_REQUIREMENTS |
ExtensionInstallChecker::CHECK_BLACKLIST,
ExtensionInstallChecker::CHECK_REQUIREMENTS,
ExtensionInstallChecker::CHECK_REQUIREMENTS);
ExpectRequirementsError(checker);
ExpectPolicyPass(checker);
ExpectBlacklistPass(checker);
}
// Test fail fast with asynchronous callbacks.
TEST_F(ExtensionInstallCheckerTest, FailFastAsync) {
// This test assumes some internal knowledge of the implementation - that
// the requirements check runs before the blacklist check. Both checks should
// be called, but the requirements check callback arrives first and the
// blacklist result will be discarded.
ExtensionInstallCheckerAsync checker;
SetAllErrors(&checker);
// The policy check is synchronous and needs to pass for the other tests to
// run.
checker.set_policy_check_error(std::string());
RunChecker(&checker,
true /* fail fast */,
ExtensionInstallChecker::CHECK_ALL,
ExtensionInstallChecker::CHECK_ALL,
ExtensionInstallChecker::CHECK_REQUIREMENTS);
ExpectRequirementsError(checker);
ExpectPolicyPass(checker);
ExpectBlacklistPass(checker);
}
// Test multiple invocations of the install checker. Wait for all checks to
// complete.
TEST_F(ExtensionInstallCheckerMultipleInvocationTest, CompleteAll) {
ExtensionInstallCheckerAsync checker;
SetAllErrors(&checker);
// Start the second check as soon as the callback of the first run is invoked.
checker.Start(
ExtensionInstallChecker::CHECK_ALL,
false /* fail fast */,
base::Bind(
&ExtensionInstallCheckerMultipleInvocationTest::RunSecondInvocation,
base::Unretained(this),
&checker));
base::RunLoop().RunUntilIdle();
}
// Test multiple invocations of the install checker and fail fast.
TEST_F(ExtensionInstallCheckerMultipleInvocationTest, FailFast) {
ExtensionInstallCheckerAsync checker;
SetAllErrors(&checker);
// The policy check is synchronous and needs to pass for the other tests to
// run.
checker.set_policy_check_error(std::string());
// Start the second check as soon as the callback of the first run is invoked.
checker.Start(
ExtensionInstallChecker::CHECK_ALL,
true /* fail fast */,
base::Bind(
&ExtensionInstallCheckerMultipleInvocationTest::RunSecondInvocation,
base::Unretained(this),
&checker));
base::RunLoop().RunUntilIdle();
}
} // namespace extensions
......@@ -24,6 +24,11 @@ namespace extensions {
// Holds common methods and data of extension installers.
//
// NOTE: This class is deprecated and has been replaced by
// ExtensionInstallChecker.
// TODO(tmdiep): CrxInstaller and UnpackedInstaller should be refactored to use
// this class (crbug.com/386404).
class ExtensionInstaller {
public:
typedef base::Callback<void(std::vector<std::string>)> RequirementsCallback;
......
// Copyright 2014 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_WEBSTORE_INSTALL_RESULT_H_
#define CHROME_BROWSER_EXTENSIONS_WEBSTORE_INSTALL_RESULT_H_
namespace extensions {
namespace webstore_install {
// Result codes returned by WebstoreStandaloneInstaller and its subclasses.
enum Result {
// Successful operation.
SUCCESS,
// Unknown error.
UNKNOWN_ERROR,
// The operation was aborted as the requestor is no longer alive.
ABORTED,
// The installation is not permitted.
NOT_PERMITTED,
// Invalid Chrome Web Store item ID.
INVALID_ID,
// Failed to retrieve extension metadata from the Web Store.
WEBSTORE_REQUEST_ERROR,
// The extension metadata retrieved from the Web Store was invalid.
INVALID_WEBSTORE_RESPONSE,
// An error occurred while parsing the extension manifest retrieved from the
// Web Store.
INVALID_MANIFEST,
// Failed to retrieve the extension's icon from the Web Store, or the icon
// was invalid.
ICON_ERROR,
// The user cancelled the operation.
USER_CANCELLED,
// The extension is blacklisted.
BLACKLISTED,
// Unsatisfied dependencies, such as shared modules.
MISSING_DEPENDENCIES,
// Unsatisfied requirements, such as webgl.
REQUIREMENT_VIOLATIONS,
// The extension is blocked by management policies.
BLOCKED_BY_POLICY,
// The launch feature is not available.
LAUNCH_FEATURE_DISABLED,
// The launch feature is not supported for the extension type.
LAUNCH_UNSUPPORTED_EXTENSION_TYPE
};
} // namespace webstore_install
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_WEBSTORE_INSTALL_RESULT_H_
......@@ -52,6 +52,7 @@ WebstoreInstallerTest::WebstoreInstallerTest(
WebstoreInstallerTest::~WebstoreInstallerTest() {}
void WebstoreInstallerTest::SetUpCommandLine(CommandLine* command_line) {
ExtensionBrowserTest::SetUpCommandLine(command_line);
// We start the test server now instead of in
// SetUpInProcessBrowserTestFixture so that we can get its port number.
ASSERT_TRUE(test_server()->Start());
......@@ -79,7 +80,7 @@ void WebstoreInstallerTest::SetUpInProcessBrowserTestFixture() {
}
void WebstoreInstallerTest::SetUpOnMainThread() {
InProcessBrowserTest::SetUpOnMainThread();
ExtensionBrowserTest::SetUpOnMainThread();
ASSERT_TRUE(download_directory_.CreateUniqueTempDir());
DownloadPrefs* download_prefs = DownloadPrefs::FromBrowserContext(
browser()->profile());
......@@ -132,3 +133,15 @@ void WebstoreInstallerTest::RunTestAsync(
browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame()->
ExecuteJavaScript(base::UTF8ToUTF16(script));
}
void WebstoreInstallerTest::AutoAcceptInstall() {
// TODO(tmdiep): Refactor and remove the use of the command line flag.
// See crbug.com/357774.
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switches::kAppsGalleryInstallAutoConfirmForTests, "accept");
}
void WebstoreInstallerTest::AutoCancelInstall() {
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switches::kAppsGalleryInstallAutoConfirmForTests, "cancel");
}
......@@ -8,14 +8,14 @@
#include <string>
#include "base/files/scoped_temp_dir.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "url/gurl.h"
namespace base {
class CommandLine;
} // namespace base
class WebstoreInstallerTest : public InProcessBrowserTest {
class WebstoreInstallerTest : public ExtensionBrowserTest {
public:
WebstoreInstallerTest(const std::string& webstore_domain,
const std::string& test_data_path,
......@@ -45,6 +45,14 @@ class WebstoreInstallerTest : public InProcessBrowserTest {
// Runs a test without waiting for any results from the renderer.
void RunTestAsync(const std::string& test_function_name);
// Configures command line switches to simulate a user accepting the install
// prompt.
void AutoAcceptInstall();
// Configures command line switches to simulate a user cancelling the install
// prompt.
void AutoCancelInstall();
std::string webstore_domain_;
std::string test_data_path_;
std::string crx_filename_;
......
......@@ -5,6 +5,7 @@
#include "chrome/browser/extensions/webstore_standalone_installer.h"
#include "base/values.h"
#include "base/version.h"
#include "chrome/browser/extensions/crx_installer.h"
#include "chrome/browser/extensions/extension_install_prompt.h"
#include "chrome/browser/extensions/extension_install_ui.h"
......@@ -44,12 +45,6 @@ WebstoreStandaloneInstaller::WebstoreStandaloneInstaller(
rating_count_(0) {
}
WebstoreStandaloneInstaller::~WebstoreStandaloneInstaller() {}
//
// Private interface implementation.
//
void WebstoreStandaloneInstaller::BeginInstall() {
// Add a ref to keep this alive for WebstoreDataFetcher.
// All code paths from here eventually lead to either CompleteInstall or
......@@ -57,7 +52,7 @@ void WebstoreStandaloneInstaller::BeginInstall() {
AddRef();
if (!Extension::IdIsValid(id_)) {
CompleteInstall(kInvalidWebstoreItemId);
CompleteInstall(webstore_install::INVALID_ID, kInvalidWebstoreItemId);
return;
}
......@@ -72,10 +67,62 @@ void WebstoreStandaloneInstaller::BeginInstall() {
webstore_data_fetcher_->Start();
}
bool WebstoreStandaloneInstaller::CheckInstallValid(
const base::DictionaryValue& manifest,
std::string* error) {
return true;
//
// Private interface implementation.
//
WebstoreStandaloneInstaller::~WebstoreStandaloneInstaller() {
}
void WebstoreStandaloneInstaller::AbortInstall() {
callback_.Reset();
// Abort any in-progress fetches.
if (webstore_data_fetcher_) {
webstore_data_fetcher_.reset();
Release(); // Matches the AddRef in BeginInstall.
}
}
void WebstoreStandaloneInstaller::CompleteInstall(
webstore_install::Result result,
const std::string& error) {
if (!callback_.is_null())
callback_.Run(result == webstore_install::SUCCESS, error);
Release(); // Matches the AddRef in BeginInstall.
}
void WebstoreStandaloneInstaller::ProceedWithInstallPrompt() {
install_prompt_ = CreateInstallPrompt();
if (install_prompt_) {
ShowInstallUI();
// Control flow finishes up in InstallUIProceed or InstallUIAbort.
} else {
InstallUIProceed();
}
}
scoped_refptr<const Extension>
WebstoreStandaloneInstaller::GetLocalizedExtensionForDisplay() {
if (!localized_extension_for_display_.get()) {
DCHECK(manifest_.get());
if (!manifest_.get())
return NULL;
std::string error;
localized_extension_for_display_ =
ExtensionInstallPrompt::GetLocalizedExtensionForDisplay(
manifest_.get(),
Extension::REQUIRE_KEY | Extension::FROM_WEBSTORE,
id_,
localized_name_,
localized_description_,
&error);
}
return localized_extension_for_display_.get();
}
void WebstoreStandaloneInstaller::OnManifestParsed() {
ProceedWithInstallPrompt();
}
scoped_ptr<ExtensionInstallPrompt>
......@@ -99,7 +146,8 @@ WebstoreStandaloneInstaller::CreateApproval() const {
void WebstoreStandaloneInstaller::OnWebstoreRequestFailure() {
OnWebStoreDataFetcherDone();
CompleteInstall(kWebstoreRequestError);
CompleteInstall(webstore_install::WEBSTORE_REQUEST_ERROR,
kWebstoreRequestError);
}
void WebstoreStandaloneInstaller::OnWebstoreResponseParseSuccess(
......@@ -107,19 +155,19 @@ void WebstoreStandaloneInstaller::OnWebstoreResponseParseSuccess(
OnWebStoreDataFetcherDone();
if (!CheckRequestorAlive()) {
CompleteInstall(std::string());
CompleteInstall(webstore_install::ABORTED, std::string());
return;
}
std::string error;
if (!CheckInlineInstallPermitted(*webstore_data, &error)) {
CompleteInstall(error);
CompleteInstall(webstore_install::NOT_PERMITTED, error);
return;
}
if (!CheckRequestorPermitted(*webstore_data, &error)) {
CompleteInstall(error);
CompleteInstall(webstore_install::NOT_PERMITTED, error);
return;
}
......@@ -129,7 +177,8 @@ void WebstoreStandaloneInstaller::OnWebstoreResponseParseSuccess(
!webstore_data->GetString(kUsersKey, &localized_user_count_) ||
!webstore_data->GetDouble(kAverageRatingKey, &average_rating_) ||
!webstore_data->GetInteger(kRatingCountKey, &rating_count_)) {
CompleteInstall(kInvalidWebstoreResponseError);
CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE,
kInvalidWebstoreResponseError);
return;
}
......@@ -139,7 +188,8 @@ void WebstoreStandaloneInstaller::OnWebstoreResponseParseSuccess(
if (average_rating_ < ExtensionInstallPrompt::kMinExtensionRating ||
average_rating_ > ExtensionInstallPrompt::kMaxExtensionRating) {
CompleteInstall(kInvalidWebstoreResponseError);
CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE,
kInvalidWebstoreResponseError);
return;
}
......@@ -149,7 +199,8 @@ void WebstoreStandaloneInstaller::OnWebstoreResponseParseSuccess(
(webstore_data->HasKey(kLocalizedDescriptionKey) &&
!webstore_data->GetString(
kLocalizedDescriptionKey, &localized_description_))) {
CompleteInstall(kInvalidWebstoreResponseError);
CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE,
kInvalidWebstoreResponseError);
return;
}
......@@ -158,13 +209,15 @@ void WebstoreStandaloneInstaller::OnWebstoreResponseParseSuccess(
if (webstore_data->HasKey(kIconUrlKey)) {
std::string icon_url_string;
if (!webstore_data->GetString(kIconUrlKey, &icon_url_string)) {
CompleteInstall(kInvalidWebstoreResponseError);
CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE,
kInvalidWebstoreResponseError);
return;
}
icon_url = GURL(extension_urls::GetWebstoreLaunchURL()).Resolve(
icon_url_string);
if (!icon_url.is_valid()) {
CompleteInstall(kInvalidWebstoreResponseError);
CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE,
kInvalidWebstoreResponseError);
return;
}
}
......@@ -187,7 +240,7 @@ void WebstoreStandaloneInstaller::OnWebstoreResponseParseSuccess(
void WebstoreStandaloneInstaller::OnWebstoreResponseParseFailure(
const std::string& error) {
OnWebStoreDataFetcherDone();
CompleteInstall(error);
CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE, error);
}
void WebstoreStandaloneInstaller::OnWebstoreParseSuccess(
......@@ -197,39 +250,38 @@ void WebstoreStandaloneInstaller::OnWebstoreParseSuccess(
CHECK_EQ(id_, id);
if (!CheckRequestorAlive()) {
CompleteInstall(std::string());
CompleteInstall(webstore_install::ABORTED, std::string());
return;
}
manifest_.reset(manifest);
icon_ = icon;
std::string error;
if (!CheckInstallValid(*manifest, &error)) {
DCHECK(!error.empty());
CompleteInstall(error);
return;
}
install_prompt_ = CreateInstallPrompt();
if (install_prompt_) {
ShowInstallUI();
// Control flow finishes up in InstallUIProceed or InstallUIAbort.
} else {
InstallUIProceed();
}
OnManifestParsed();
}
void WebstoreStandaloneInstaller::OnWebstoreParseFailure(
const std::string& id,
InstallHelperResultCode result_code,
const std::string& error_message) {
CompleteInstall(error_message);
webstore_install::Result install_result = webstore_install::UNKNOWN_ERROR;
switch (result_code) {
case WebstoreInstallHelper::Delegate::MANIFEST_ERROR:
install_result = webstore_install::INVALID_MANIFEST;
break;
case WebstoreInstallHelper::Delegate::ICON_ERROR:
install_result = webstore_install::ICON_ERROR;
break;
default:
break;
}
CompleteInstall(install_result, error_message);
}
void WebstoreStandaloneInstaller::InstallUIProceed() {
if (!CheckRequestorAlive()) {
CompleteInstall(std::string());
CompleteInstall(webstore_install::ABORTED, std::string());
return;
}
......@@ -237,28 +289,47 @@ void WebstoreStandaloneInstaller::InstallUIProceed() {
ExtensionService* extension_service =
ExtensionSystem::Get(profile_)->extension_service();
const Extension* extension =
const Extension* installed_extension =
extension_service->GetExtensionById(id_, true /* include disabled */);
if (extension) {
std::string install_result; // Empty string for install success.
if (installed_extension) {
std::string install_message;
webstore_install::Result install_result = webstore_install::SUCCESS;
bool done = true;
if (ExtensionPrefs::Get(profile_)->IsExtensionBlacklisted(id_)) {
// Don't install a blacklisted extension.
install_result = kExtensionIsBlacklisted;
} else if (util::IsEphemeralApp(extension->id(), profile_) &&
install_result = webstore_install::BLACKLISTED;
install_message = kExtensionIsBlacklisted;
} else if (util::IsEphemeralApp(installed_extension->id(), profile_) &&
!approval->is_ephemeral) {
// If the target extension has already been installed ephemerally, it can
// be promoted to a regular installed extension and downloading from the
// Web Store is not necessary.
extension_service->PromoteEphemeralApp(extension, false);
// If the target extension has already been installed ephemerally and is
// up to date, it can be promoted to a regular installed extension and
// downloading from the Web Store is not necessary.
const Extension* extension_to_install = GetLocalizedExtensionForDisplay();
if (!extension_to_install) {
CompleteInstall(webstore_install::INVALID_MANIFEST,
kInvalidManifestError);
return;
}
if (installed_extension->version()->CompareTo(
*extension_to_install->version()) < 0) {
// If the existing extension is out of date, proceed with the install
// to update the extension.
done = false;
} else {
extension_service->PromoteEphemeralApp(installed_extension, false);
}
} else if (!extension_service->IsExtensionEnabled(id_)) {
// If the extension is installed but disabled, and not blacklisted,
// enable it.
extension_service->EnableExtension(id_);
} // else extension is installed and enabled; no work to be done.
CompleteInstall(install_result);
return;
if (done) {
CompleteInstall(install_result, install_message);
return;
}
}
scoped_refptr<WebstoreInstaller> installer = new WebstoreInstaller(
......@@ -272,60 +343,47 @@ void WebstoreStandaloneInstaller::InstallUIProceed() {
}
void WebstoreStandaloneInstaller::InstallUIAbort(bool user_initiated) {
CompleteInstall(kUserCancelledError);
CompleteInstall(webstore_install::USER_CANCELLED, kUserCancelledError);
}
void WebstoreStandaloneInstaller::OnExtensionInstallSuccess(
const std::string& id) {
CHECK_EQ(id_, id);
CompleteInstall(std::string());
CompleteInstall(webstore_install::SUCCESS, std::string());
}
void WebstoreStandaloneInstaller::OnExtensionInstallFailure(
const std::string& id,
const std::string& error,
WebstoreInstaller::FailureReason cancelled) {
WebstoreInstaller::FailureReason reason) {
CHECK_EQ(id_, id);
CompleteInstall(error);
}
void WebstoreStandaloneInstaller::AbortInstall() {
callback_.Reset();
// Abort any in-progress fetches.
if (webstore_data_fetcher_) {
webstore_data_fetcher_.reset();
Release(); // Matches the AddRef in BeginInstall.
webstore_install::Result install_result = webstore_install::UNKNOWN_ERROR;
switch (reason) {
case WebstoreInstaller::FAILURE_REASON_CANCELLED:
install_result = webstore_install::USER_CANCELLED;
break;
case WebstoreInstaller::FAILURE_REASON_DEPENDENCY_NOT_FOUND:
case WebstoreInstaller::FAILURE_REASON_DEPENDENCY_NOT_SHARED_MODULE:
install_result = webstore_install::MISSING_DEPENDENCIES;
break;
default:
break;
}
}
void WebstoreStandaloneInstaller::InvokeCallback(const std::string& error) {
if (!callback_.is_null())
callback_.Run(error.empty(), error);
}
void WebstoreStandaloneInstaller::CompleteInstall(const std::string& error) {
InvokeCallback(error);
Release(); // Matches the AddRef in BeginInstall.
CompleteInstall(install_result, error);
}
void WebstoreStandaloneInstaller::ShowInstallUI() {
std::string error;
localized_extension_for_display_ =
ExtensionInstallPrompt::GetLocalizedExtensionForDisplay(
manifest_.get(),
Extension::REQUIRE_KEY | Extension::FROM_WEBSTORE,
id_,
localized_name_,
localized_description_,
&error);
if (!localized_extension_for_display_.get()) {
CompleteInstall(kInvalidManifestError);
const Extension* localized_extension = GetLocalizedExtensionForDisplay();
if (!localized_extension) {
CompleteInstall(webstore_install::INVALID_MANIFEST, kInvalidManifestError);
return;
}
install_ui_ = CreateInstallUI();
install_ui_->ConfirmStandaloneInstall(
this, localized_extension_for_display_.get(), &icon_, install_prompt_);
this, localized_extension, &icon_, install_prompt_);
}
void WebstoreStandaloneInstaller::OnWebStoreDataFetcherDone() {
......
......@@ -13,6 +13,7 @@
#include "chrome/browser/extensions/extension_install_prompt.h"
#include "chrome/browser/extensions/webstore_data_fetcher_delegate.h"
#include "chrome/browser/extensions/webstore_install_helper.h"
#include "chrome/browser/extensions/webstore_install_result.h"
#include "chrome/browser/extensions/webstore_installer.h"
#include "net/url_request/url_fetcher_delegate.h"
#include "third_party/skia/include/core/SkBitmap.h"
......@@ -60,9 +61,20 @@ class WebstoreStandaloneInstaller
protected:
virtual ~WebstoreStandaloneInstaller();
// Called when the install should be aborted. The callback is cleared.
void AbortInstall();
void InvokeCallback(const std::string& error);
virtual void CompleteInstall(const std::string& error);
// Called when the install is complete.
virtual void CompleteInstall(webstore_install::Result result,
const std::string& error);
// Called when the installer should proceed to prompt the user.
void ProceedWithInstallPrompt();
// Lazily creates a dummy extension for display from the parsed manifest. This
// is safe to call from OnManifestParsed() onwards. The manifest may be
// invalid, thus the caller must check that the return value is not NULL.
scoped_refptr<const Extension> GetLocalizedExtensionForDisplay();
// Template Method's hooks to be implemented by subclasses.
......@@ -108,11 +120,12 @@ class WebstoreStandaloneInstaller
const base::DictionaryValue& webstore_data,
std::string* error) const = 0;
// Perform all necessary checks after the manifest has been parsed to make
// sure that the install should still proceed.
virtual bool CheckInstallValid(
const base::DictionaryValue& manifest,
std::string* error);
// Will be called after the extension's manifest has been successfully parsed.
// Subclasses can perform asynchronous checks at this point and call
// ProceedWithInstallPrompt() to proceed with the install or otherwise call
// CompleteInstall() with an error code. The default implementation calls
// ProceedWithInstallPrompt().
virtual void OnManifestParsed();
// Returns an install UI to be shown. By default, this returns an install UI
// that is a transient child of the host window for GetWebContents().
......@@ -137,6 +150,9 @@ class WebstoreStandaloneInstaller
Profile* profile() const { return profile_; }
const std::string& id() const { return id_; }
const base::DictionaryValue* manifest() const { return manifest_.get(); }
const Extension* localized_extension_for_display() const {
return localized_extension_for_display_.get();
}
private:
friend class base::RefCountedThreadSafe<WebstoreStandaloneInstaller>;
......
......@@ -7,7 +7,6 @@
#include <vector>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/memory/ref_counted.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/apps/ephemeral_app_launcher.h"
......@@ -20,7 +19,6 @@
#include "chrome/browser/ui/app_list/search/webstore/webstore_installer.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/extensions/application_launch.h"
#include "chrome/common/chrome_switches.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extension_util.h"
......@@ -134,8 +132,7 @@ void WebstoreResult::UpdateActions() {
extensions::util::IsExtensionInstalledPermanently(app_id_, profile_);
if (!is_otr && !is_installed && !is_installing()) {
if (CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableEphemeralApps)) {
if (EphemeralAppLauncher::IsFeatureEnabled()) {
actions.push_back(Action(
l10n_util::GetStringUTF16(IDS_WEBSTORE_RESULT_INSTALL),
l10n_util::GetStringUTF16(
......@@ -186,7 +183,7 @@ void WebstoreResult::StartInstall(bool launch_ephemeral_app) {
app_id_,
profile_,
controller_->GetAppListWindow(),
base::Bind(&WebstoreResult::InstallCallback,
base::Bind(&WebstoreResult::LaunchCallback,
weak_factory_.GetWeakPtr()));
installer->Start();
return;
......@@ -209,10 +206,18 @@ void WebstoreResult::InstallCallback(bool success, const std::string& error) {
return;
}
// Success handling is continued in OnExtensionInstalled.
// Success handling is continued in OnExtensionWillBeInstalled.
SetPercentDownloaded(100);
}
void WebstoreResult::LaunchCallback(extensions::webstore_install::Result result,
const std::string& error) {
if (result != extensions::webstore_install::SUCCESS)
LOG(ERROR) << "Failed to launch app, error=" << error;
SetIsInstalling(false);
}
void WebstoreResult::StartObserving() {
DCHECK(!install_tracker_ && !extension_registry_);
......@@ -253,8 +258,12 @@ void WebstoreResult::OnExtensionWillBeInstalled(
return;
SetIsInstalling(false);
UpdateActions();
NotifyItemInstalled();
if (extensions::util::IsExtensionInstalledPermanently(extension->id(),
profile_)) {
UpdateActions();
NotifyItemInstalled();
}
}
void WebstoreResult::OnShutdown() {
......
......@@ -10,6 +10,7 @@
#include "base/basictypes.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/extensions/install_observer.h"
#include "chrome/browser/extensions/webstore_install_result.h"
#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
#include "extensions/browser/extension_registry_observer.h"
#include "url/gurl.h"
......@@ -48,6 +49,8 @@ class WebstoreResult : public ChromeSearchResult,
void StartInstall(bool launch_ephemeral_app);
void InstallCallback(bool success, const std::string& error);
void LaunchCallback(extensions::webstore_install::Result result,
const std::string& error);
// Start observing both InstallObserver and ExtensionRegistryObserver.
void StartObserving();
......
......@@ -746,6 +746,8 @@
'browser/extensions/extension_icon_manager.h',
'browser/extensions/extension_infobar_delegate.cc',
'browser/extensions/extension_infobar_delegate.h',
'browser/extensions/extension_install_checker.cc',
'browser/extensions/extension_install_checker.h',
'browser/extensions/extension_install_prompt.cc',
'browser/extensions/extension_install_prompt.h',
'browser/extensions/extension_install_prompt_experiment.cc',
......@@ -941,6 +943,7 @@
'browser/extensions/webstore_inline_installer_factory.h',
'browser/extensions/webstore_install_helper.cc',
'browser/extensions/webstore_install_helper.h',
'browser/extensions/webstore_install_result.h',
'browser/extensions/webstore_install_with_prompt.cc',
'browser/extensions/webstore_install_with_prompt.h',
'browser/extensions/webstore_installer.cc',
......
......@@ -884,6 +884,7 @@
'browser/apps/drive/drive_app_provider_browsertest.cc',
'browser/apps/ephemeral_app_browsertest.cc',
'browser/apps/ephemeral_app_browsertest.h',
'browser/apps/ephemeral_app_launcher_browsertest.cc',
'browser/apps/ephemeral_app_service_browsertest.cc',
'browser/apps/event_page_browsertest.cc',
'browser/apps/speech_recognition_browsertest.cc',
......
......@@ -966,6 +966,7 @@
'browser/extensions/extension_garbage_collector_chromeos_unittest.cc',
'browser/extensions/extension_gcm_app_handler_unittest.cc',
'browser/extensions/extension_icon_manager_unittest.cc',
'browser/extensions/extension_install_checker_unittest.cc',
'browser/extensions/extension_message_bubble_controller_unittest.cc',
'browser/extensions/extension_prefs_unittest.cc',
'browser/extensions/extension_prefs_unittest.h',
......
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDBUe9m01dbRMdT
irq/xyQojlugvSchBty8H/qWVs471yggJjkuN1A70Fmnk/oQCHkPhF5O6hV3ud0g
u89dDG0NPDSWl2qX0No4jqyeQ2VFOV31ZHPVrtGO21xNjIebAQZ/svNrce9Q8bVy
A3wUQXGvV+sOt7BhFU8AfYIqeQ5VldFeLM1zaLC8CsO8Eq12wsB/+h15EEKQtcDN
c+y/BnKd1tq89Vl+CcUe5d5mZS11E9SsxdNxzY3JTh/0OSNF71edKYFchnNbe0/3
Z85CfwktoQcIY7LovESm5o8Xf+3T1XBbc6Ifa4bfNjULGZuLXaNmksIz6nOw2GYt
jKJHNoDVAgMBAAECggEBAIpD6N75PxyG9603ptOtQZ5EGyK+xrKnbs6abKaEFWLV
QtyjmfgFFbyCCu2BN7Op67P+b1kvsQT9iTt1OB2L2p9BtTcTuug0Ny+GU82p1E+L
dTkijHqDW1w6Zi0M2pwxQo2o1tK9HXYeOjD+bwspt366eeL/3AXDsMpf+e4YwPjS
PT9ZFUr58pJH3e9p9oWa1i7LQssEqtgvHQjsLch05kj2V/pyJnydgwffcRQNguYE
uVfxSUnrmE2w438pJbOXWOMiGNnWeI01gTs7hhjOETX6uGmQyoPCo/wMm9fd3vCc
mjtJojsExxXHL1Mgbz4455SHPc33JCtI1My+RG4mSQ0CgYEA36XkTgYDVLnFBwfh
2MytmI0PlU5uZP78t0E6rnshbaseQPos5e+/tfn1/Vb64Qmk528+6fbTzM4e3vLo
d4KsvGf9SPs19ypm8OmAAu/3M6KjKOgG5YqoDsd6ED5qUjsTVB9LLjr9Xb5dPhte
0ErFG8dqS7IkIRwr/lLBRkM7ec8CgYEA3UjxICZk7i+7apOmutr6Y9vcRiVFoDhj
YHwZkr3CoRbwU0fPR1nm5qDXTAGDJ4S3iq7HsiSFByvzKizu4eIAyUz6028yKQ64
di98B79S+Lh7PykWx27nujVMWWw4du46kkr1qEVHvOu0exPMlfGgUEb8zSSDZrnk
31LDRRi22BsCgYEAkdMtJeKt0VS/r3HoROBQYKO5gXT+bkQEq8iQZqxKg6OLunpl
RBgrPJCbYHCSbKFJMM5Mi2kxeNz9LAneoe5GiyKS+D80VNEFBhh6K3AkEzr8f/Oq
a4kZQ2m1oVnRIYT7eWJeNuqnwbQaQCDAlbfrBc3k9S5WeHBSDyYOd63m3ekCgYEA
mAUHWf3mctep2nLrJQ1Q3Q9rCWmLpqF2bA91hiEFN6PJoH0BwlPZ0/EFV3rmflyn
tweRuylpll60I/JQfi/EOIbA/kYy4I/zW4YNoEjpGGOsj9yLgKEJYxxAgmJdfB73
cA89KsfFm90V1UPp1cz0RJIwFEKUXxswR9fUvIBZBKcCgYAngAbZkkaq1VWo0qYy
6tk7DyYob5kLvMY/FCDPx5+FZKz5NaJkIWkEvtytphidk2r4vqaCa9tp0VaTIe2Q
2BShVaBnkKyF0Qn0f1XXiTfVIa/zduQc580vqI1FTCoXaPO/3wYa345AjrQoRZls
nviXEgVqFDhTgBG0yzpSnHhhsg==
-----END PRIVATE KEY-----
// Copyright 2014 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.
chrome.app.runtime.onLaunched.addListener(function() {
chrome.app.window.create('index.html', {}, function(win) {
chrome.test.sendMessage('launched');
win.close();
});
});
{
"name": "Simple Test App",
"manifest_version": 2,
"version": "1",
"app": {
"background": {
"scripts": ["main.js"]
}
}
}
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC5ZOMEtT5hrz5k
Mh9cbalNWt8P81unURX+Q5sPx1PxOlCylF6EiciE+pQjcXSqFm2OsKQuNojqyEM6
93LMQnT1UKRxrV7tSNALqGiQ6Xh4JKKe0sLqW4rFscw9Hjrzop8KeOpwUygiFgdD
6t0NAVErSr85PNatureoC+nYEZxA7ESHI0X7SVnWoew7AFULfdWPYwQzmBbldFxx
UNeGklYlxviahey9HHiHC/kWomwQ0NvdN9jGAhI5W9nuFs5XY+fSiO/HPYSX37vP
0fkTyRPTkrRcncH9/YTh2vaEHDbHKsGZKuY8/nz2B32SX5zP+jjSQPJ8dfqjEFX6
He6d4C7tAgMBAAECggEAMm4J/sbacDH+M70aoMbHrd4RHUKiZNH+vIphTJp47/5f
4yPOzhi4rcuHrlh3VqkBJjZQgq2c8tiEyS50ULGnJO/Ju45gawVKalFmGD0Z4vx8
K93C3YMHLZ2eo+VsPce3oV6gzusf2J3LXiMt/BKmpLMRbx1ofBt1R8Xt1zN7IIFt
mR9gPF7EIzYRlD5M6Lj76j2e0dv/QZVdDOsNEcww1bys9If00BgLCPZmlAUz7rE5
uQytekLwrb5EM3jb3MTANHivNwWWMY1BR2hQTgRQ+QDKmK020GIRzth3B0Xa3fkJ
WpG26JbznF079IAFpb/+6b365mkTPs8TX3dyAa7iwQKBgQDpZYCh15bznP2jodfi
LpT40xuyxvhiKLvH1WlRxgcm3QewoZvKHfZXQ6HUCOcfirunlNdQviiByIr8za7A
qIXAsqg9W5BLCeLCMSAlRV4CnAF4457PShoFTaSN8Hu7MbpHSvM+NaDXm/lv5gNF
XSrH0wBP/PmtInn4mb6BzZFTcQKBgQDLWUgYPuF6sFcsD1ve2gKoj71jGEnuPdqP
5RF/ZIitS9sk+TP8KgsSycNFX1u/HrcuiOClVz3bZC39oKgqyEMiaUKYYBc+Tk71
ymJkAdGoLoJnaINXqTTRUZO8zaErhyNQxFut6X+ejgqLgGPV5Mc/HQXVr8eG0NA7
4W5Lo8OdPQKBgHu3PRcMw0xA1EXPfTK8hGTc6lq6k2DcvKTxn9Ejyhi0ouXi1ESv
2ytogzh0u6aBkHUFNZjsI2agGeRlVpNzAOsjCID1ryfHhrZu+d4nxexWK4WcCktQ
uThEbsx6Q+v3MQmnVgaMwMRpPjAtWWCEQTAIzaqYqvQ2STcYr934TGdRAoGAS997
Q/iqXwHO9NSiPctwdH51drUIWMlhdJFO7w6O5MJJ+Ui2ed8iFJeNsaO78tq+FLHj
yC1+Gg8ODfVU6emhCwTlYT9xTMEhOxqRjakUFkGMTJM1do7e8z8R9b9v5HLe3XL2
ljRqdrme+6AjIBrlsQneNHYK56WgHMenRLfM9C0CgYEA4SW4+QaO7AYJIJhWCZ8I
blFj6q95FUfsMEUBlEqJQJxKUSdn58AJ96RcwLD1KmXhyTNctD5BidkApFmi8IXa
qJcbbCOinEJn2NFT6/C8ZbF8w6cvJaG8c3iLZaUwDmmX+oKC2GbqSYhqN2K96HUp
tpcPgbZnwkbs/8pYq2jxTLk=
-----END PRIVATE KEY-----
// Copyright 2014 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.
chrome.app.runtime.onLaunched.addListener(function() {
chrome.app.window.create('index.html', {}, function(win) {
chrome.test.sendMessage('launched');
win.close();
});
});
{
"name": "Test App With Permissions",
"manifest_version": 2,
"version": "1",
"app": {
"background": {
"scripts": ["main.js"]
}
},
"permissions": ["audioCapture"]
}
{
"name": "Test Extension",
"version": "0.1",
"manifest_version": 2
}
{
"users": "371,674",
"average_rating": 4.36,
"rating_count": 788,
"manifest": "{\"name\":\"Test Extension\",\"version\":\"0.1\"}"
}
{
"users": "371,674",
"average_rating": 4.36,
"rating_count": 788,
"manifest": "{\"name\":\"Simple Test App\",\"manifest_version\":2,\"version\":\"1\",\"app\": {\"background\":{\"scripts\":[\"main.js\"]}}}"
}
{
"users": "371,674",
"average_rating": 4.36,
"rating_count": 788,
"manifest": "{\"name\":\"Test App With Permissions\",\"manifest_version\":2,\"version\":\"1\",\"app\":{\"background\":{\"scripts\":[\"main.js\"]}},\"permissions\":[\"audioCapture\"]}"
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment