Home | History | Annotate | Download | only in apps
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/apps/ephemeral_app_launcher.h"
      6 
      7 #include "base/command_line.h"
      8 #include "base/strings/utf_string_conversions.h"
      9 #include "chrome/browser/extensions/extension_install_checker.h"
     10 #include "chrome/browser/extensions/extension_install_prompt.h"
     11 #include "chrome/browser/extensions/extension_util.h"
     12 #include "chrome/browser/profiles/profile.h"
     13 #include "chrome/browser/ui/browser_navigator.h"
     14 #include "chrome/browser/ui/extensions/application_launch.h"
     15 #include "chrome/browser/ui/extensions/extension_enable_flow.h"
     16 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
     17 #include "chrome/common/chrome_switches.h"
     18 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
     19 #include "content/public/browser/web_contents.h"
     20 #include "extensions/browser/extension_prefs.h"
     21 #include "extensions/browser/extension_registry.h"
     22 #include "extensions/browser/extension_system.h"
     23 #include "extensions/browser/management_policy.h"
     24 #include "extensions/common/permissions/permissions_data.h"
     25 
     26 using content::WebContents;
     27 using extensions::Extension;
     28 using extensions::ExtensionInstallChecker;
     29 using extensions::ExtensionPrefs;
     30 using extensions::ExtensionRegistry;
     31 using extensions::ExtensionSystem;
     32 using extensions::ManagementPolicy;
     33 using extensions::WebstoreInstaller;
     34 namespace webstore_install = extensions::webstore_install;
     35 
     36 namespace {
     37 
     38 const char kInvalidManifestError[] = "Invalid manifest";
     39 const char kExtensionTypeError[] = "Not an app";
     40 const char kAppTypeError[] = "Ephemeral legacy packaged apps not supported";
     41 const char kUserCancelledError[] = "Launch cancelled by the user";
     42 const char kBlacklistedError[] = "App is blacklisted for malware";
     43 const char kRequirementsError[] = "App has missing requirements";
     44 const char kFeatureDisabledError[] = "Launching ephemeral apps is not enabled";
     45 const char kMissingAppError[] = "App is not installed";
     46 const char kAppDisabledError[] = "App is disabled";
     47 
     48 Profile* ProfileForWebContents(content::WebContents* contents) {
     49   if (!contents)
     50     return NULL;
     51 
     52   return Profile::FromBrowserContext(contents->GetBrowserContext());
     53 }
     54 
     55 gfx::NativeWindow NativeWindowForWebContents(content::WebContents* contents) {
     56   if (!contents)
     57     return NULL;
     58 
     59   return contents->GetTopLevelNativeWindow();
     60 }
     61 
     62 // Check whether an extension can be launched. The extension does not need to
     63 // be currently installed.
     64 bool CheckCommonLaunchCriteria(Profile* profile,
     65                                const Extension* extension,
     66                                webstore_install::Result* reason,
     67                                std::string* error) {
     68   // Only apps can be launched.
     69   if (!extension->is_app()) {
     70     *reason = webstore_install::LAUNCH_UNSUPPORTED_EXTENSION_TYPE;
     71     *error = kExtensionTypeError;
     72     return false;
     73   }
     74 
     75   // Do not launch apps blocked by management policies.
     76   ManagementPolicy* management_policy =
     77       ExtensionSystem::Get(profile)->management_policy();
     78   base::string16 policy_error;
     79   if (!management_policy->UserMayLoad(extension, &policy_error)) {
     80     *reason = webstore_install::BLOCKED_BY_POLICY;
     81     *error = base::UTF16ToUTF8(policy_error);
     82     return false;
     83   }
     84 
     85   return true;
     86 }
     87 
     88 }  // namespace
     89 
     90 // static
     91 bool EphemeralAppLauncher::IsFeatureEnabled() {
     92   return CommandLine::ForCurrentProcess()->HasSwitch(
     93       switches::kEnableEphemeralApps);
     94 }
     95 
     96 // static
     97 scoped_refptr<EphemeralAppLauncher> EphemeralAppLauncher::CreateForLauncher(
     98     const std::string& webstore_item_id,
     99     Profile* profile,
    100     gfx::NativeWindow parent_window,
    101     const LaunchCallback& callback) {
    102   scoped_refptr<EphemeralAppLauncher> installer =
    103       new EphemeralAppLauncher(webstore_item_id,
    104                                profile,
    105                                parent_window,
    106                                callback);
    107   installer->set_install_source(WebstoreInstaller::INSTALL_SOURCE_APP_LAUNCHER);
    108   return installer;
    109 }
    110 
    111 // static
    112 scoped_refptr<EphemeralAppLauncher> EphemeralAppLauncher::CreateForWebContents(
    113     const std::string& webstore_item_id,
    114     content::WebContents* web_contents,
    115     const LaunchCallback& callback) {
    116   scoped_refptr<EphemeralAppLauncher> installer =
    117       new EphemeralAppLauncher(webstore_item_id, web_contents, callback);
    118   installer->set_install_source(WebstoreInstaller::INSTALL_SOURCE_OTHER);
    119   return installer;
    120 }
    121 
    122 void EphemeralAppLauncher::Start() {
    123   if (!IsFeatureEnabled()) {
    124     InvokeCallback(webstore_install::LAUNCH_FEATURE_DISABLED,
    125                    kFeatureDisabledError);
    126     return;
    127   }
    128 
    129   // Check whether the app already exists in extension system before downloading
    130   // from the webstore.
    131   const Extension* extension =
    132       ExtensionRegistry::Get(profile())
    133           ->GetExtensionById(id(), ExtensionRegistry::EVERYTHING);
    134   if (extension) {
    135     webstore_install::Result result = webstore_install::OTHER_ERROR;
    136     std::string error;
    137     if (!CanLaunchInstalledApp(extension, &result, &error)) {
    138       InvokeCallback(result, error);
    139       return;
    140     }
    141 
    142     if (extensions::util::IsAppLaunchableWithoutEnabling(extension->id(),
    143                                                          profile())) {
    144       LaunchApp(extension);
    145       InvokeCallback(webstore_install::SUCCESS, std::string());
    146       return;
    147     }
    148 
    149     EnableInstalledApp(extension);
    150     return;
    151   }
    152 
    153   // Install the app ephemerally and launch when complete.
    154   BeginInstall();
    155 }
    156 
    157 EphemeralAppLauncher::EphemeralAppLauncher(const std::string& webstore_item_id,
    158                                            Profile* profile,
    159                                            gfx::NativeWindow parent_window,
    160                                            const LaunchCallback& callback)
    161     : WebstoreStandaloneInstaller(webstore_item_id, profile, Callback()),
    162       launch_callback_(callback),
    163       parent_window_(parent_window),
    164       dummy_web_contents_(
    165           WebContents::Create(WebContents::CreateParams(profile))) {
    166 }
    167 
    168 EphemeralAppLauncher::EphemeralAppLauncher(const std::string& webstore_item_id,
    169                                            content::WebContents* web_contents,
    170                                            const LaunchCallback& callback)
    171     : WebstoreStandaloneInstaller(webstore_item_id,
    172                                   ProfileForWebContents(web_contents),
    173                                   Callback()),
    174       content::WebContentsObserver(web_contents),
    175       launch_callback_(callback),
    176       parent_window_(NativeWindowForWebContents(web_contents)) {
    177 }
    178 
    179 EphemeralAppLauncher::~EphemeralAppLauncher() {}
    180 
    181 scoped_ptr<extensions::ExtensionInstallChecker>
    182 EphemeralAppLauncher::CreateInstallChecker() {
    183   return make_scoped_ptr(new ExtensionInstallChecker(profile()));
    184 }
    185 
    186 scoped_ptr<ExtensionInstallPrompt> EphemeralAppLauncher::CreateInstallUI() {
    187   if (web_contents())
    188     return make_scoped_ptr(new ExtensionInstallPrompt(web_contents()));
    189 
    190   return make_scoped_ptr(
    191       new ExtensionInstallPrompt(profile(), parent_window_, NULL));
    192 }
    193 
    194 scoped_ptr<WebstoreInstaller::Approval> EphemeralAppLauncher::CreateApproval()
    195     const {
    196   scoped_ptr<WebstoreInstaller::Approval> approval =
    197       WebstoreStandaloneInstaller::CreateApproval();
    198   approval->is_ephemeral = true;
    199   return approval.Pass();
    200 }
    201 
    202 bool EphemeralAppLauncher::CanLaunchInstalledApp(
    203     const extensions::Extension* extension,
    204     webstore_install::Result* reason,
    205     std::string* error) {
    206   if (!CheckCommonLaunchCriteria(profile(), extension, reason, error))
    207     return false;
    208 
    209   // Do not launch blacklisted apps.
    210   if (ExtensionPrefs::Get(profile())->IsExtensionBlacklisted(extension->id())) {
    211     *reason = webstore_install::BLACKLISTED;
    212     *error = kBlacklistedError;
    213     return false;
    214   }
    215 
    216   // If the app has missing requirements, it cannot be launched.
    217   if (!extensions::util::IsAppLaunchable(extension->id(), profile())) {
    218     *reason = webstore_install::REQUIREMENT_VIOLATIONS;
    219     *error = kRequirementsError;
    220     return false;
    221   }
    222 
    223   return true;
    224 }
    225 
    226 void EphemeralAppLauncher::EnableInstalledApp(const Extension* extension) {
    227   // Check whether an install is already in progress.
    228   webstore_install::Result result = webstore_install::OTHER_ERROR;
    229   std::string error;
    230   if (!EnsureUniqueInstall(&result, &error)) {
    231     InvokeCallback(result, error);
    232     return;
    233   }
    234 
    235   // Keep this object alive until the enable flow is complete. Either
    236   // ExtensionEnableFlowFinished() or ExtensionEnableFlowAborted() will be
    237   // called.
    238   AddRef();
    239 
    240   extension_enable_flow_.reset(
    241       new ExtensionEnableFlow(profile(), extension->id(), this));
    242   if (web_contents())
    243     extension_enable_flow_->StartForWebContents(web_contents());
    244   else
    245     extension_enable_flow_->StartForNativeWindow(parent_window_);
    246 }
    247 
    248 void EphemeralAppLauncher::MaybeLaunchApp() {
    249   webstore_install::Result result = webstore_install::OTHER_ERROR;
    250   std::string error;
    251 
    252   ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
    253   const Extension* extension =
    254       registry->GetExtensionById(id(), ExtensionRegistry::EVERYTHING);
    255   if (extension) {
    256     // Although the installation was successful, the app may not be
    257     // launchable.
    258     if (registry->enabled_extensions().Contains(extension->id())) {
    259       result = webstore_install::SUCCESS;
    260       LaunchApp(extension);
    261     } else {
    262       error = kAppDisabledError;
    263       // Determine why the app cannot be launched.
    264       CanLaunchInstalledApp(extension, &result, &error);
    265     }
    266   } else {
    267     // The extension must be present in the registry if installed.
    268     NOTREACHED();
    269     error = kMissingAppError;
    270   }
    271 
    272   InvokeCallback(result, error);
    273 }
    274 
    275 void EphemeralAppLauncher::LaunchApp(const Extension* extension) const {
    276   DCHECK(extension && extension->is_app() &&
    277          ExtensionRegistry::Get(profile())
    278              ->GetExtensionById(extension->id(), ExtensionRegistry::ENABLED));
    279 
    280   AppLaunchParams params(profile(), extension, NEW_FOREGROUND_TAB);
    281   params.desktop_type =
    282       chrome::GetHostDesktopTypeForNativeWindow(parent_window_);
    283   OpenApplication(params);
    284 }
    285 
    286 bool EphemeralAppLauncher::LaunchHostedApp(const Extension* extension) const {
    287   GURL launch_url = extensions::AppLaunchInfo::GetLaunchWebURL(extension);
    288   if (!launch_url.is_valid())
    289     return false;
    290 
    291   chrome::ScopedTabbedBrowserDisplayer displayer(
    292       profile(), chrome::GetHostDesktopTypeForNativeWindow(parent_window_));
    293   chrome::NavigateParams params(
    294       displayer.browser(), launch_url, ui::PAGE_TRANSITION_AUTO_TOPLEVEL);
    295   params.disposition = NEW_FOREGROUND_TAB;
    296   chrome::Navigate(&params);
    297   return true;
    298 }
    299 
    300 void EphemeralAppLauncher::InvokeCallback(webstore_install::Result result,
    301                                           const std::string& error) {
    302   if (!launch_callback_.is_null()) {
    303     LaunchCallback callback = launch_callback_;
    304     launch_callback_.Reset();
    305     callback.Run(result, error);
    306   }
    307 }
    308 
    309 void EphemeralAppLauncher::AbortLaunch(webstore_install::Result result,
    310                                        const std::string& error) {
    311   InvokeCallback(result, error);
    312   WebstoreStandaloneInstaller::CompleteInstall(result, error);
    313 }
    314 
    315 void EphemeralAppLauncher::CheckEphemeralInstallPermitted() {
    316   scoped_refptr<const Extension> extension = GetLocalizedExtensionForDisplay();
    317   DCHECK(extension.get());  // Checked in OnManifestParsed().
    318 
    319   install_checker_ = CreateInstallChecker();
    320   DCHECK(install_checker_.get());
    321 
    322   install_checker_->set_extension(extension);
    323   install_checker_->Start(ExtensionInstallChecker::CHECK_BLACKLIST |
    324                               ExtensionInstallChecker::CHECK_REQUIREMENTS,
    325                           true,
    326                           base::Bind(&EphemeralAppLauncher::OnInstallChecked,
    327                                      base::Unretained(this)));
    328 }
    329 
    330 void EphemeralAppLauncher::OnInstallChecked(int check_failures) {
    331   if (!CheckRequestorAlive()) {
    332     AbortLaunch(webstore_install::OTHER_ERROR, std::string());
    333     return;
    334   }
    335 
    336   if (install_checker_->blacklist_state() == extensions::BLACKLISTED_MALWARE) {
    337     AbortLaunch(webstore_install::BLACKLISTED, kBlacklistedError);
    338     return;
    339   }
    340 
    341   if (!install_checker_->requirement_errors().empty()) {
    342     AbortLaunch(webstore_install::REQUIREMENT_VIOLATIONS,
    343                 install_checker_->requirement_errors().front());
    344     return;
    345   }
    346 
    347   // Proceed with the normal install flow.
    348   ProceedWithInstallPrompt();
    349 }
    350 
    351 void EphemeralAppLauncher::InitInstallData(
    352     extensions::ActiveInstallData* install_data) const {
    353   install_data->is_ephemeral = true;
    354 }
    355 
    356 bool EphemeralAppLauncher::CheckRequestorAlive() const {
    357   return dummy_web_contents_.get() != NULL || web_contents() != NULL;
    358 }
    359 
    360 const GURL& EphemeralAppLauncher::GetRequestorURL() const {
    361   return GURL::EmptyGURL();
    362 }
    363 
    364 bool EphemeralAppLauncher::ShouldShowPostInstallUI() const {
    365   return false;
    366 }
    367 
    368 bool EphemeralAppLauncher::ShouldShowAppInstalledBubble() const {
    369   return false;
    370 }
    371 
    372 WebContents* EphemeralAppLauncher::GetWebContents() const {
    373   return web_contents() ? web_contents() : dummy_web_contents_.get();
    374 }
    375 
    376 scoped_refptr<ExtensionInstallPrompt::Prompt>
    377 EphemeralAppLauncher::CreateInstallPrompt() const {
    378   const Extension* extension = localized_extension_for_display();
    379   DCHECK(extension);  // Checked in OnManifestParsed().
    380 
    381   // Skip the prompt by returning null if the app does not need to display
    382   // permission warnings.
    383   extensions::PermissionMessages permissions =
    384       extension->permissions_data()->GetPermissionMessages();
    385   if (permissions.empty())
    386     return NULL;
    387 
    388   return make_scoped_refptr(new ExtensionInstallPrompt::Prompt(
    389       ExtensionInstallPrompt::LAUNCH_PROMPT));
    390 }
    391 
    392 bool EphemeralAppLauncher::CheckInlineInstallPermitted(
    393     const base::DictionaryValue& webstore_data,
    394     std::string* error) const {
    395   *error = "";
    396   return true;
    397 }
    398 
    399 bool EphemeralAppLauncher::CheckRequestorPermitted(
    400     const base::DictionaryValue& webstore_data,
    401     std::string* error) const {
    402   *error = "";
    403   return true;
    404 }
    405 
    406 void EphemeralAppLauncher::OnManifestParsed() {
    407   scoped_refptr<const Extension> extension = GetLocalizedExtensionForDisplay();
    408   if (!extension.get()) {
    409     AbortLaunch(webstore_install::INVALID_MANIFEST, kInvalidManifestError);
    410     return;
    411   }
    412 
    413   webstore_install::Result result = webstore_install::OTHER_ERROR;
    414   std::string error;
    415   if (!CheckCommonLaunchCriteria(profile(), extension.get(), &result, &error)) {
    416     AbortLaunch(result, error);
    417     return;
    418   }
    419 
    420   if (extension->is_legacy_packaged_app()) {
    421     AbortLaunch(webstore_install::LAUNCH_UNSUPPORTED_EXTENSION_TYPE,
    422                 kAppTypeError);
    423     return;
    424   }
    425 
    426   if (extension->is_hosted_app()) {
    427     // Hosted apps do not need to be installed ephemerally. Just navigate to
    428     // their launch url.
    429     if (LaunchHostedApp(extension.get()))
    430       AbortLaunch(webstore_install::SUCCESS, std::string());
    431     else
    432       AbortLaunch(webstore_install::INVALID_MANIFEST, kInvalidManifestError);
    433     return;
    434   }
    435 
    436   CheckEphemeralInstallPermitted();
    437 }
    438 
    439 void EphemeralAppLauncher::CompleteInstall(webstore_install::Result result,
    440                                            const std::string& error) {
    441   if (result == webstore_install::SUCCESS)
    442     MaybeLaunchApp();
    443   else if (!launch_callback_.is_null())
    444     InvokeCallback(result, error);
    445 
    446   WebstoreStandaloneInstaller::CompleteInstall(result, error);
    447 }
    448 
    449 void EphemeralAppLauncher::WebContentsDestroyed() {
    450   launch_callback_.Reset();
    451   AbortInstall();
    452 }
    453 
    454 void EphemeralAppLauncher::ExtensionEnableFlowFinished() {
    455   MaybeLaunchApp();
    456 
    457   // CompleteInstall will call Release.
    458   WebstoreStandaloneInstaller::CompleteInstall(webstore_install::SUCCESS,
    459                                                std::string());
    460 }
    461 
    462 void EphemeralAppLauncher::ExtensionEnableFlowAborted(bool user_initiated) {
    463   // CompleteInstall will call Release.
    464   CompleteInstall(webstore_install::USER_CANCELLED, kUserCancelledError);
    465 }
    466