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 26using content::WebContents; 27using extensions::Extension; 28using extensions::ExtensionInstallChecker; 29using extensions::ExtensionPrefs; 30using extensions::ExtensionRegistry; 31using extensions::ExtensionSystem; 32using extensions::ManagementPolicy; 33using extensions::WebstoreInstaller; 34namespace webstore_install = extensions::webstore_install; 35 36namespace { 37 38const char kInvalidManifestError[] = "Invalid manifest"; 39const char kExtensionTypeError[] = "Not an app"; 40const char kAppTypeError[] = "Ephemeral legacy packaged apps not supported"; 41const char kUserCancelledError[] = "Launch cancelled by the user"; 42const char kBlacklistedError[] = "App is blacklisted for malware"; 43const char kRequirementsError[] = "App has missing requirements"; 44const char kFeatureDisabledError[] = "Launching ephemeral apps is not enabled"; 45const char kMissingAppError[] = "App is not installed"; 46const char kAppDisabledError[] = "App is disabled"; 47 48Profile* ProfileForWebContents(content::WebContents* contents) { 49 if (!contents) 50 return NULL; 51 52 return Profile::FromBrowserContext(contents->GetBrowserContext()); 53} 54 55gfx::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. 64bool 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 91bool EphemeralAppLauncher::IsFeatureEnabled() { 92 return CommandLine::ForCurrentProcess()->HasSwitch( 93 switches::kEnableEphemeralApps); 94} 95 96// static 97scoped_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 112scoped_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 122void 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 157EphemeralAppLauncher::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 168EphemeralAppLauncher::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 179EphemeralAppLauncher::~EphemeralAppLauncher() {} 180 181scoped_ptr<extensions::ExtensionInstallChecker> 182EphemeralAppLauncher::CreateInstallChecker() { 183 return make_scoped_ptr(new ExtensionInstallChecker(profile())); 184} 185 186scoped_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 194scoped_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 202bool 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 226void 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 248void 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 275void 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 286bool 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(¶ms); 297 return true; 298} 299 300void 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 309void EphemeralAppLauncher::AbortLaunch(webstore_install::Result result, 310 const std::string& error) { 311 InvokeCallback(result, error); 312 WebstoreStandaloneInstaller::CompleteInstall(result, error); 313} 314 315void 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 330void 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 351void EphemeralAppLauncher::InitInstallData( 352 extensions::ActiveInstallData* install_data) const { 353 install_data->is_ephemeral = true; 354} 355 356bool EphemeralAppLauncher::CheckRequestorAlive() const { 357 return dummy_web_contents_.get() != NULL || web_contents() != NULL; 358} 359 360const GURL& EphemeralAppLauncher::GetRequestorURL() const { 361 return GURL::EmptyGURL(); 362} 363 364bool EphemeralAppLauncher::ShouldShowPostInstallUI() const { 365 return false; 366} 367 368bool EphemeralAppLauncher::ShouldShowAppInstalledBubble() const { 369 return false; 370} 371 372WebContents* EphemeralAppLauncher::GetWebContents() const { 373 return web_contents() ? web_contents() : dummy_web_contents_.get(); 374} 375 376scoped_refptr<ExtensionInstallPrompt::Prompt> 377EphemeralAppLauncher::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 392bool EphemeralAppLauncher::CheckInlineInstallPermitted( 393 const base::DictionaryValue& webstore_data, 394 std::string* error) const { 395 *error = ""; 396 return true; 397} 398 399bool EphemeralAppLauncher::CheckRequestorPermitted( 400 const base::DictionaryValue& webstore_data, 401 std::string* error) const { 402 *error = ""; 403 return true; 404} 405 406void 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 439void 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 449void EphemeralAppLauncher::WebContentsDestroyed() { 450 launch_callback_.Reset(); 451 AbortInstall(); 452} 453 454void EphemeralAppLauncher::ExtensionEnableFlowFinished() { 455 MaybeLaunchApp(); 456 457 // CompleteInstall will call Release. 458 WebstoreStandaloneInstaller::CompleteInstall(webstore_install::SUCCESS, 459 std::string()); 460} 461 462void EphemeralAppLauncher::ExtensionEnableFlowAborted(bool user_initiated) { 463 // CompleteInstall will call Release. 464 CompleteInstall(webstore_install::USER_CANCELLED, kUserCancelledError); 465} 466