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