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.
5 #include "chrome/browser/apps/ephemeral_app_launcher.h"
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/app_launch_params.h"
15 #include "chrome/browser/ui/extensions/application_launch.h"
16 #include "chrome/browser/ui/extensions/extension_enable_flow.h"
17 #include "chrome/browser/ui/native_window_tracker.h"
18 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
19 #include "chrome/common/chrome_switches.h"
20 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
21 #include "content/public/browser/web_contents.h"
22 #include "extensions/browser/extension_prefs.h"
23 #include "extensions/browser/extension_registry.h"
24 #include "extensions/browser/extension_system.h"
25 #include "extensions/browser/management_policy.h"
26 #include "extensions/common/constants.h"
27 #include "extensions/common/permissions/permissions_data.h"
28 #include "ui/app_list/app_list_switches.h"
30 using content::WebContents
;
31 using extensions::Extension
;
32 using extensions::ExtensionInstallChecker
;
33 using extensions::ExtensionPrefs
;
34 using extensions::ExtensionRegistry
;
35 using extensions::ExtensionSystem
;
36 using extensions::ManagementPolicy
;
37 using extensions::WebstoreInstaller
;
38 namespace webstore_install
= extensions::webstore_install
;
42 const char kInvalidManifestError
[] = "Invalid manifest";
43 const char kExtensionTypeError
[] = "Not an app";
44 const char kAppTypeError
[] = "Ephemeral legacy packaged apps not supported";
45 const char kUserCancelledError
[] = "Launch cancelled by the user";
46 const char kBlacklistedError
[] = "App is blacklisted for malware";
47 const char kRequirementsError
[] = "App has missing requirements";
48 const char kFeatureDisabledError
[] = "Launching ephemeral apps is not enabled";
49 const char kMissingAppError
[] = "App is not installed";
50 const char kAppDisabledError
[] = "App is disabled";
52 Profile
* ProfileForWebContents(content::WebContents
* contents
) {
56 return Profile::FromBrowserContext(contents
->GetBrowserContext());
59 gfx::NativeWindow
NativeWindowForWebContents(content::WebContents
* contents
) {
63 return contents
->GetTopLevelNativeWindow();
66 // Check whether an extension can be launched. The extension does not need to
67 // be currently installed.
68 bool CheckCommonLaunchCriteria(Profile
* profile
,
69 const Extension
* extension
,
70 webstore_install::Result
* reason
,
72 // Only apps can be launched.
73 if (!extension
->is_app()) {
74 *reason
= webstore_install::LAUNCH_UNSUPPORTED_EXTENSION_TYPE
;
75 *error
= kExtensionTypeError
;
79 // Do not launch apps blocked by management policies.
80 ManagementPolicy
* management_policy
=
81 ExtensionSystem::Get(profile
)->management_policy();
82 base::string16 policy_error
;
83 if (!management_policy
->UserMayLoad(extension
, &policy_error
)) {
84 *reason
= webstore_install::BLOCKED_BY_POLICY
;
85 *error
= base::UTF16ToUTF8(policy_error
);
95 bool EphemeralAppLauncher::IsFeatureEnabled() {
96 return base::CommandLine::ForCurrentProcess()->HasSwitch(
97 switches::kEnableEphemeralAppsInWebstore
);
101 scoped_refptr
<EphemeralAppLauncher
> EphemeralAppLauncher::CreateForLauncher(
102 const std::string
& webstore_item_id
,
104 gfx::NativeWindow parent_window
,
105 const LaunchCallback
& callback
) {
106 scoped_refptr
<EphemeralAppLauncher
> installer
=
107 new EphemeralAppLauncher(webstore_item_id
,
111 installer
->set_install_source(WebstoreInstaller::INSTALL_SOURCE_APP_LAUNCHER
);
116 scoped_refptr
<EphemeralAppLauncher
> EphemeralAppLauncher::CreateForWebContents(
117 const std::string
& webstore_item_id
,
118 content::WebContents
* web_contents
,
119 const LaunchCallback
& callback
) {
120 scoped_refptr
<EphemeralAppLauncher
> installer
=
121 new EphemeralAppLauncher(webstore_item_id
, web_contents
, callback
);
122 installer
->set_install_source(WebstoreInstaller::INSTALL_SOURCE_OTHER
);
126 void EphemeralAppLauncher::Start() {
127 if (!IsFeatureEnabled()) {
128 InvokeCallback(webstore_install::LAUNCH_FEATURE_DISABLED
,
129 kFeatureDisabledError
);
133 // Check whether the app already exists in extension system before downloading
134 // from the webstore.
135 const Extension
* extension
=
136 ExtensionRegistry::Get(profile())
137 ->GetExtensionById(id(), ExtensionRegistry::EVERYTHING
);
139 webstore_install::Result result
= webstore_install::OTHER_ERROR
;
141 if (!CanLaunchInstalledApp(extension
, &result
, &error
)) {
142 InvokeCallback(result
, error
);
146 if (extensions::util::IsAppLaunchableWithoutEnabling(extension
->id(),
148 LaunchApp(extension
);
149 InvokeCallback(webstore_install::SUCCESS
, std::string());
153 EnableInstalledApp(extension
);
157 // Install the app ephemerally and launch when complete.
161 EphemeralAppLauncher::EphemeralAppLauncher(const std::string
& webstore_item_id
,
163 gfx::NativeWindow parent_window
,
164 const LaunchCallback
& callback
)
165 : WebstoreStandaloneInstaller(webstore_item_id
, profile
, Callback()),
166 launch_callback_(callback
),
167 parent_window_(parent_window
),
169 WebContents::Create(WebContents::CreateParams(profile
))) {
171 parent_window_tracker_
= NativeWindowTracker::Create(parent_window
);
174 EphemeralAppLauncher::EphemeralAppLauncher(const std::string
& webstore_item_id
,
175 content::WebContents
* web_contents
,
176 const LaunchCallback
& callback
)
177 : WebstoreStandaloneInstaller(webstore_item_id
,
178 ProfileForWebContents(web_contents
),
180 content::WebContentsObserver(web_contents
),
181 launch_callback_(callback
),
182 parent_window_(NativeWindowForWebContents(web_contents
)) {
185 EphemeralAppLauncher::~EphemeralAppLauncher() {}
187 scoped_ptr
<extensions::ExtensionInstallChecker
>
188 EphemeralAppLauncher::CreateInstallChecker() {
189 return make_scoped_ptr(new ExtensionInstallChecker(profile()));
192 scoped_ptr
<ExtensionInstallPrompt
> EphemeralAppLauncher::CreateInstallUI() {
194 return make_scoped_ptr(new ExtensionInstallPrompt(web_contents()));
196 return make_scoped_ptr(new ExtensionInstallPrompt(profile(), parent_window_
));
199 scoped_ptr
<WebstoreInstaller::Approval
> EphemeralAppLauncher::CreateApproval()
201 scoped_ptr
<WebstoreInstaller::Approval
> approval
=
202 WebstoreStandaloneInstaller::CreateApproval();
203 approval
->is_ephemeral
= true;
204 return approval
.Pass();
207 bool EphemeralAppLauncher::CanLaunchInstalledApp(
208 const extensions::Extension
* extension
,
209 webstore_install::Result
* reason
,
210 std::string
* error
) {
211 if (!CheckCommonLaunchCriteria(profile(), extension
, reason
, error
))
214 // Do not launch blacklisted apps.
215 if (ExtensionPrefs::Get(profile())->IsExtensionBlacklisted(extension
->id())) {
216 *reason
= webstore_install::BLACKLISTED
;
217 *error
= kBlacklistedError
;
221 // If the app has missing requirements, it cannot be launched.
222 if (!extensions::util::IsAppLaunchable(extension
->id(), profile())) {
223 *reason
= webstore_install::REQUIREMENT_VIOLATIONS
;
224 *error
= kRequirementsError
;
231 void EphemeralAppLauncher::EnableInstalledApp(const Extension
* extension
) {
232 // Check whether an install is already in progress.
233 webstore_install::Result result
= webstore_install::OTHER_ERROR
;
235 if (!EnsureUniqueInstall(&result
, &error
)) {
236 InvokeCallback(result
, error
);
240 // Keep this object alive until the enable flow is complete. Either
241 // ExtensionEnableFlowFinished() or ExtensionEnableFlowAborted() will be
245 extension_enable_flow_
.reset(
246 new ExtensionEnableFlow(profile(), extension
->id(), this));
248 extension_enable_flow_
->StartForWebContents(web_contents());
250 extension_enable_flow_
->StartForNativeWindow(parent_window_
);
253 void EphemeralAppLauncher::MaybeLaunchApp() {
254 webstore_install::Result result
= webstore_install::OTHER_ERROR
;
257 ExtensionRegistry
* registry
= ExtensionRegistry::Get(profile());
258 const Extension
* extension
=
259 registry
->GetExtensionById(id(), ExtensionRegistry::EVERYTHING
);
261 // Although the installation was successful, the app may not be
263 if (registry
->enabled_extensions().Contains(extension
->id())) {
264 result
= webstore_install::SUCCESS
;
265 LaunchApp(extension
);
267 error
= kAppDisabledError
;
268 // Determine why the app cannot be launched.
269 CanLaunchInstalledApp(extension
, &result
, &error
);
272 // The extension must be present in the registry if installed.
274 error
= kMissingAppError
;
277 InvokeCallback(result
, error
);
280 void EphemeralAppLauncher::LaunchApp(const Extension
* extension
) const {
281 DCHECK(extension
&& extension
->is_app() &&
282 ExtensionRegistry::Get(profile())
283 ->GetExtensionById(extension
->id(), ExtensionRegistry::ENABLED
));
285 AppLaunchParams
params(profile(), extension
, NEW_FOREGROUND_TAB
,
286 extensions::SOURCE_EPHEMERAL_APP
);
287 params
.desktop_type
=
288 chrome::GetHostDesktopTypeForNativeWindow(parent_window_
);
289 OpenApplication(params
);
292 bool EphemeralAppLauncher::LaunchHostedApp(const Extension
* extension
) const {
293 GURL launch_url
= extensions::AppLaunchInfo::GetLaunchWebURL(extension
);
294 if (!launch_url
.is_valid())
297 chrome::ScopedTabbedBrowserDisplayer
displayer(
298 profile(), chrome::GetHostDesktopTypeForNativeWindow(parent_window_
));
299 chrome::NavigateParams
params(
300 displayer
.browser(), launch_url
, ui::PAGE_TRANSITION_AUTO_TOPLEVEL
);
301 params
.disposition
= NEW_FOREGROUND_TAB
;
302 chrome::Navigate(¶ms
);
306 void EphemeralAppLauncher::InvokeCallback(webstore_install::Result result
,
307 const std::string
& error
) {
308 if (!launch_callback_
.is_null()) {
309 LaunchCallback callback
= launch_callback_
;
310 launch_callback_
.Reset();
311 callback
.Run(result
, error
);
315 void EphemeralAppLauncher::AbortLaunch(webstore_install::Result result
,
316 const std::string
& error
) {
317 InvokeCallback(result
, error
);
318 WebstoreStandaloneInstaller::CompleteInstall(result
, error
);
321 void EphemeralAppLauncher::CheckEphemeralInstallPermitted() {
322 scoped_refptr
<const Extension
> extension
= GetLocalizedExtensionForDisplay();
323 DCHECK(extension
.get()); // Checked in OnManifestParsed().
325 install_checker_
= CreateInstallChecker();
326 DCHECK(install_checker_
.get());
328 install_checker_
->set_extension(extension
);
329 install_checker_
->Start(ExtensionInstallChecker::CHECK_BLACKLIST
|
330 ExtensionInstallChecker::CHECK_REQUIREMENTS
,
332 base::Bind(&EphemeralAppLauncher::OnInstallChecked
,
333 base::Unretained(this)));
336 void EphemeralAppLauncher::OnInstallChecked(int check_failures
) {
337 if (!CheckRequestorAlive()) {
338 AbortLaunch(webstore_install::OTHER_ERROR
, std::string());
342 if (install_checker_
->blacklist_state() == extensions::BLACKLISTED_MALWARE
) {
343 AbortLaunch(webstore_install::BLACKLISTED
, kBlacklistedError
);
347 if (!install_checker_
->requirement_errors().empty()) {
348 AbortLaunch(webstore_install::REQUIREMENT_VIOLATIONS
,
349 install_checker_
->requirement_errors().front());
353 // Proceed with the normal install flow.
354 ProceedWithInstallPrompt();
357 void EphemeralAppLauncher::InitInstallData(
358 extensions::ActiveInstallData
* install_data
) const {
359 install_data
->is_ephemeral
= true;
362 bool EphemeralAppLauncher::CheckRequestorAlive() const {
363 if (!parent_window_
) {
364 // Assume the requestor is always alive if |parent_window_| is null.
368 return (web_contents() != nullptr ||
369 (parent_window_tracker_
&&
370 !parent_window_tracker_
->WasNativeWindowClosed()));
373 const GURL
& EphemeralAppLauncher::GetRequestorURL() const {
374 return GURL::EmptyGURL();
377 bool EphemeralAppLauncher::ShouldShowPostInstallUI() const {
381 bool EphemeralAppLauncher::ShouldShowAppInstalledBubble() const {
385 WebContents
* EphemeralAppLauncher::GetWebContents() const {
386 return web_contents() ? web_contents() : dummy_web_contents_
.get();
389 scoped_refptr
<ExtensionInstallPrompt::Prompt
>
390 EphemeralAppLauncher::CreateInstallPrompt() const {
391 const Extension
* extension
= localized_extension_for_display();
392 DCHECK(extension
); // Checked in OnManifestParsed().
394 // Skip the prompt by returning null if the app does not need to display
395 // permission warnings.
396 if (extension
->permissions_data()->GetPermissionMessages().empty())
399 return make_scoped_refptr(new ExtensionInstallPrompt::Prompt(
400 ExtensionInstallPrompt::LAUNCH_PROMPT
));
403 bool EphemeralAppLauncher::CheckInlineInstallPermitted(
404 const base::DictionaryValue
& webstore_data
,
405 std::string
* error
) const {
410 bool EphemeralAppLauncher::CheckRequestorPermitted(
411 const base::DictionaryValue
& webstore_data
,
412 std::string
* error
) const {
417 void EphemeralAppLauncher::OnManifestParsed() {
418 scoped_refptr
<const Extension
> extension
= GetLocalizedExtensionForDisplay();
419 if (!extension
.get()) {
420 AbortLaunch(webstore_install::INVALID_MANIFEST
, kInvalidManifestError
);
424 webstore_install::Result result
= webstore_install::OTHER_ERROR
;
426 if (!CheckCommonLaunchCriteria(profile(), extension
.get(), &result
, &error
)) {
427 AbortLaunch(result
, error
);
431 if (extension
->is_legacy_packaged_app()) {
432 AbortLaunch(webstore_install::LAUNCH_UNSUPPORTED_EXTENSION_TYPE
,
437 if (extension
->is_hosted_app()) {
438 // Hosted apps do not need to be installed ephemerally. Just navigate to
440 if (LaunchHostedApp(extension
.get()))
441 AbortLaunch(webstore_install::SUCCESS
, std::string());
443 AbortLaunch(webstore_install::INVALID_MANIFEST
, kInvalidManifestError
);
447 CheckEphemeralInstallPermitted();
450 void EphemeralAppLauncher::CompleteInstall(webstore_install::Result result
,
451 const std::string
& error
) {
452 if (result
== webstore_install::SUCCESS
)
454 else if (!launch_callback_
.is_null())
455 InvokeCallback(result
, error
);
457 WebstoreStandaloneInstaller::CompleteInstall(result
, error
);
460 void EphemeralAppLauncher::WebContentsDestroyed() {
461 launch_callback_
.Reset();
465 void EphemeralAppLauncher::ExtensionEnableFlowFinished() {
468 // CompleteInstall will call Release.
469 WebstoreStandaloneInstaller::CompleteInstall(webstore_install::SUCCESS
,
473 void EphemeralAppLauncher::ExtensionEnableFlowAborted(bool user_initiated
) {
474 // CompleteInstall will call Release.
475 CompleteInstall(webstore_install::USER_CANCELLED
, kUserCancelledError
);