// Copyright 2013 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/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/browser_navigator.h" #include "chrome/browser/ui/extensions/app_launch_params.h" #include "chrome/browser/ui/extensions/application_launch.h" #include "chrome/browser/ui/extensions/extension_enable_flow.h" #include "chrome/browser/ui/native_window_tracker.h" #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/manifest_handlers/app_launch_info.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/constants.h" #include "extensions/common/permissions/permissions_data.h" #include "ui/app_list/app_list_switches.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[] = "Not an app"; const char kAppTypeError[] = "Ephemeral legacy packaged apps not supported"; 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) return NULL; return Profile::FromBrowserContext(contents->GetBrowserContext()); } gfx::NativeWindow NativeWindowForWebContents(content::WebContents* contents) { if (!contents) return NULL; return contents->GetTopLevelNativeWindow(); } // Check whether an extension can be launched. The extension does not need to // be currently installed. bool CheckCommonLaunchCriteria(Profile* profile, const 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 bool EphemeralAppLauncher::IsFeatureEnabled() { return base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kEnableEphemeralAppsInWebstore); } // static scoped_refptr EphemeralAppLauncher::CreateForLauncher( const std::string& webstore_item_id, Profile* profile, gfx::NativeWindow parent_window, const LaunchCallback& callback) { scoped_refptr installer = new EphemeralAppLauncher(webstore_item_id, profile, parent_window, callback); installer->set_install_source(WebstoreInstaller::INSTALL_SOURCE_APP_LAUNCHER); return installer; } // static scoped_refptr EphemeralAppLauncher::CreateForWebContents( const std::string& webstore_item_id, content::WebContents* web_contents, const LaunchCallback& callback) { scoped_refptr installer = 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::OTHER_ERROR; std::string error; if (!CanLaunchInstalledApp(extension, &result, &error)) { InvokeCallback(result, error); return; } if (extensions::util::IsAppLaunchableWithoutEnabling(extension->id(), profile())) { LaunchApp(extension); InvokeCallback(webstore_install::SUCCESS, std::string()); return; } EnableInstalledApp(extension); return; } // Install the app ephemerally and launch when complete. BeginInstall(); } EphemeralAppLauncher::EphemeralAppLauncher(const std::string& webstore_item_id, Profile* profile, gfx::NativeWindow parent_window, const LaunchCallback& callback) : WebstoreStandaloneInstaller(webstore_item_id, profile, Callback()), launch_callback_(callback), parent_window_(parent_window), dummy_web_contents_( WebContents::Create(WebContents::CreateParams(profile))) { if (parent_window_) parent_window_tracker_ = NativeWindowTracker::Create(parent_window); } EphemeralAppLauncher::EphemeralAppLauncher(const std::string& webstore_item_id, content::WebContents* web_contents, const LaunchCallback& callback) : WebstoreStandaloneInstaller(webstore_item_id, ProfileForWebContents(web_contents), Callback()), content::WebContentsObserver(web_contents), launch_callback_(callback), parent_window_(NativeWindowForWebContents(web_contents)) { } EphemeralAppLauncher::~EphemeralAppLauncher() {} scoped_ptr EphemeralAppLauncher::CreateInstallChecker() { return make_scoped_ptr(new ExtensionInstallChecker(profile())); } scoped_ptr EphemeralAppLauncher::CreateInstallUI() { if (web_contents()) return make_scoped_ptr(new ExtensionInstallPrompt(web_contents())); return make_scoped_ptr(new ExtensionInstallPrompt(profile(), parent_window_)); } scoped_ptr EphemeralAppLauncher::CreateApproval() const { scoped_ptr approval = WebstoreStandaloneInstaller::CreateApproval(); approval->is_ephemeral = true; return approval.Pass(); } 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) { // Check whether an install is already in progress. webstore_install::Result result = webstore_install::OTHER_ERROR; std::string error; if (!EnsureUniqueInstall(&result, &error)) { InvokeCallback(result, error); return; } // Keep this object alive until the enable flow is complete. Either // ExtensionEnableFlowFinished() or ExtensionEnableFlowAborted() will be // called. AddRef(); 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_); } void EphemeralAppLauncher::MaybeLaunchApp() { webstore_install::Result result = webstore_install::OTHER_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, extensions::SOURCE_EPHEMERAL_APP); params.desktop_type = chrome::GetHostDesktopTypeForNativeWindow(parent_window_); OpenApplication(params); } bool EphemeralAppLauncher::LaunchHostedApp(const Extension* extension) const { GURL launch_url = extensions::AppLaunchInfo::GetLaunchWebURL(extension); if (!launch_url.is_valid()) return false; chrome::ScopedTabbedBrowserDisplayer displayer( profile(), chrome::GetHostDesktopTypeForNativeWindow(parent_window_)); chrome::NavigateParams params( displayer.browser(), launch_url, ui::PAGE_TRANSITION_AUTO_TOPLEVEL); params.disposition = NEW_FOREGROUND_TAB; chrome::Navigate(¶ms); return true; } void EphemeralAppLauncher::InvokeCallback(webstore_install::Result result, const std::string& error) { if (!launch_callback_.is_null()) { LaunchCallback callback = launch_callback_; launch_callback_.Reset(); callback.Run(result, error); } } void EphemeralAppLauncher::AbortLaunch(webstore_install::Result result, const std::string& error) { InvokeCallback(result, error); WebstoreStandaloneInstaller::CompleteInstall(result, error); } void EphemeralAppLauncher::CheckEphemeralInstallPermitted() { scoped_refptr 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::OTHER_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(); } void EphemeralAppLauncher::InitInstallData( extensions::ActiveInstallData* install_data) const { install_data->is_ephemeral = true; } bool EphemeralAppLauncher::CheckRequestorAlive() const { if (!parent_window_) { // Assume the requestor is always alive if |parent_window_| is null. return true; } return (web_contents() != nullptr || (parent_window_tracker_ && !parent_window_tracker_->WasNativeWindowClosed())); } const GURL& EphemeralAppLauncher::GetRequestorURL() const { return GURL::EmptyGURL(); } bool EphemeralAppLauncher::ShouldShowPostInstallUI() const { return false; } bool EphemeralAppLauncher::ShouldShowAppInstalledBubble() const { return false; } WebContents* EphemeralAppLauncher::GetWebContents() const { return web_contents() ? web_contents() : dummy_web_contents_.get(); } scoped_refptr EphemeralAppLauncher::CreateInstallPrompt() const { 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. if (extension->permissions_data()->GetPermissionMessageStrings().empty()) return NULL; return make_scoped_refptr(new ExtensionInstallPrompt::Prompt( ExtensionInstallPrompt::LAUNCH_PROMPT)); } bool EphemeralAppLauncher::CheckInlineInstallPermitted( const base::DictionaryValue& webstore_data, std::string* error) const { *error = ""; return true; } bool EphemeralAppLauncher::CheckRequestorPermitted( const base::DictionaryValue& webstore_data, std::string* error) const { *error = ""; return true; } void EphemeralAppLauncher::OnManifestParsed() { scoped_refptr extension = GetLocalizedExtensionForDisplay(); if (!extension.get()) { AbortLaunch(webstore_install::INVALID_MANIFEST, kInvalidManifestError); return; } webstore_install::Result result = webstore_install::OTHER_ERROR; std::string error; if (!CheckCommonLaunchCriteria(profile(), extension.get(), &result, &error)) { AbortLaunch(result, error); return; } if (extension->is_legacy_packaged_app()) { AbortLaunch(webstore_install::LAUNCH_UNSUPPORTED_EXTENSION_TYPE, kAppTypeError); return; } if (extension->is_hosted_app()) { // Hosted apps do not need to be installed ephemerally. Just navigate to // their launch url. if (LaunchHostedApp(extension.get())) AbortLaunch(webstore_install::SUCCESS, std::string()); else AbortLaunch(webstore_install::INVALID_MANIFEST, kInvalidManifestError); return; } CheckEphemeralInstallPermitted(); } 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(result, error); } void EphemeralAppLauncher::WebContentsDestroyed() { launch_callback_.Reset(); AbortInstall(); } void EphemeralAppLauncher::ExtensionEnableFlowFinished() { MaybeLaunchApp(); // CompleteInstall will call Release. WebstoreStandaloneInstaller::CompleteInstall(webstore_install::SUCCESS, std::string()); } void EphemeralAppLauncher::ExtensionEnableFlowAborted(bool user_initiated) { // CompleteInstall will call Release. CompleteInstall(webstore_install::USER_CANCELLED, kUserCancelledError); }