| Index: chrome/browser/apps/ephemeral_app_launcher.cc
|
| diff --git a/chrome/browser/apps/ephemeral_app_launcher.cc b/chrome/browser/apps/ephemeral_app_launcher.cc
|
| index d46942ea85cc8d7ca2bfc8e3a8dc5a7ae81a6462..040d6299e0f975d39c5c089a530955de23e56af5 100644
|
| --- a/chrome/browser/apps/ephemeral_app_launcher.cc
|
| +++ b/chrome/browser/apps/ephemeral_app_launcher.cc
|
| @@ -4,25 +4,41 @@
|
|
|
| #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 {
|
|
|
| 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 kDependenciesError[] = "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 +54,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,
|
| + EphemeralAppLauncher::LaunchResult* reason,
|
| + std::string* error) {
|
| + // Only apps can be launched.
|
| + if (!extension->is_app()) {
|
| + *reason = EphemeralAppLauncher::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 = EphemeralAppLauncher::LAUNCH_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 +104,56 @@ 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(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) {
|
| + LaunchResult result = LAUNCH_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(LAUNCH_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 +161,148 @@ 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,
|
| + LaunchResult* 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 = LAUNCH_BLACKLISTED;
|
| + *error = kBlacklistedError;
|
| + return false;
|
| + }
|
| +
|
| + // If the app has missing requirements, it cannot be launched.
|
| + if (!extensions::util::IsAppLaunchable(extension->id(), profile())) {
|
| + *reason = LAUNCH_MISSING_DEPENDENCIES;
|
| + *error = kDependenciesError;
|
| + 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() {
|
| + LaunchResult result = LAUNCH_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 = LAUNCH_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(LaunchResult result,
|
| + const std::string& error) {
|
| + if (!launch_callback_.is_null()) {
|
| + launch_callback_.Run(result, error);
|
| + launch_callback_.Reset();
|
| + }
|
| +}
|
| +
|
| +void EphemeralAppLauncher::AbortLaunch(LaunchResult result,
|
| + const std::string& error) {
|
| + InvokeCallback(result, error);
|
| + WebstoreStandaloneInstaller::CompleteInstall(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(LAUNCH_UNKNOWN_ERROR, std::string());
|
| + return;
|
| + }
|
| +
|
| + if (install_checker_->blacklist_state() == extensions::BLACKLISTED_MALWARE) {
|
| + AbortLaunch(LAUNCH_BLACKLISTED, kBlacklistedError);
|
| + return;
|
| + }
|
| +
|
| + if (!install_checker_->requirement_errors().empty()) {
|
| + AbortLaunch(LAUNCH_MISSING_DEPENDENCIES,
|
| + 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 +325,13 @@ WebContents* EphemeralAppLauncher::GetWebContents() const {
|
|
|
| scoped_ptr<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 scoped_ptr<ExtensionInstallPrompt::Prompt>();
|
|
|
| @@ -185,31 +353,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(LAUNCH_INVALID_MANIFEST, kInvalidManifestError);
|
| + return;
|
| }
|
|
|
| - if (!extension_->is_app()) {
|
| - *error = kExtensionTypeError;
|
| - return false;
|
| + LaunchResult result = LAUNCH_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 +386,56 @@ 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(InstallResult result,
|
| + const std::string& error) {
|
| + if (result == INSTALL_SUCCESS) {
|
| + MaybeLaunchApp();
|
| + } else if (!launch_callback_.is_null()) {
|
| + // Convert the install error codes to launch error codes.
|
| + LaunchResult launch_result = LAUNCH_UNKNOWN_ERROR;
|
| + switch (result) {
|
| + case INSTALL_USER_CANCELLED:
|
| + launch_result = LAUNCH_USER_CANCELLED;
|
| + break;
|
| + case INSTALL_INVALID_ID:
|
| + launch_result = LAUNCH_INVALID_ID;
|
| + break;
|
| + case INSTALL_INVALID_MANIFEST:
|
| + launch_result = LAUNCH_INVALID_MANIFEST;
|
| + break;
|
| + case INSTALL_NOT_PERMITTED:
|
| + case INSTALL_WEBSTORE_REQUEST_ERROR:
|
| + case INSTALL_INVALID_WEBSTORE_RESPONSE:
|
| + case INSTALL_ICON_ERROR:
|
| + launch_result = LAUNCH_INSTALL_ERROR;
|
| + break;
|
| + case INSTALL_BLACKLISTED:
|
| + launch_result = LAUNCH_BLACKLISTED;
|
| + break;
|
| + case INSTALL_MISSING_DEPENDENCIES:
|
| + launch_result = LAUNCH_MISSING_DEPENDENCIES;
|
| + break;
|
| + default:
|
| + break;
|
| + }
|
| +
|
| + InvokeCallback(launch_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(LAUNCH_USER_CANCELLED, kUserCancelledError);
|
| + Release(); // Matches the AddRef in EnableInstalledApp().
|
| }
|
|
|