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/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"
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
;
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";
48 Profile
* ProfileForWebContents(content::WebContents
* contents
) {
52 return Profile::FromBrowserContext(contents
->GetBrowserContext());
55 gfx::NativeWindow
NativeWindowForWebContents(content::WebContents
* contents
) {
59 return contents
->GetTopLevelNativeWindow();
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
,
68 // Only apps can be launched.
69 if (!extension
->is_app()) {
70 *reason
= webstore_install::LAUNCH_UNSUPPORTED_EXTENSION_TYPE
;
71 *error
= kExtensionTypeError
;
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
);
91 bool EphemeralAppLauncher::IsFeatureEnabled() {
92 return CommandLine::ForCurrentProcess()->HasSwitch(
93 switches::kEnableEphemeralApps
);
97 scoped_refptr
<EphemeralAppLauncher
> EphemeralAppLauncher::CreateForLauncher(
98 const std::string
& webstore_item_id
,
100 gfx::NativeWindow parent_window
,
101 const LaunchCallback
& callback
) {
102 scoped_refptr
<EphemeralAppLauncher
> installer
=
103 new EphemeralAppLauncher(webstore_item_id
,
107 installer
->set_install_source(WebstoreInstaller::INSTALL_SOURCE_APP_LAUNCHER
);
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
);
122 void EphemeralAppLauncher::Start() {
123 if (!IsFeatureEnabled()) {
124 InvokeCallback(webstore_install::LAUNCH_FEATURE_DISABLED
,
125 kFeatureDisabledError
);
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
);
135 webstore_install::Result result
= webstore_install::OTHER_ERROR
;
137 if (!CanLaunchInstalledApp(extension
, &result
, &error
)) {
138 InvokeCallback(result
, error
);
142 if (extensions::util::IsAppLaunchableWithoutEnabling(extension
->id(),
144 LaunchApp(extension
);
145 InvokeCallback(webstore_install::SUCCESS
, std::string());
149 EnableInstalledApp(extension
);
153 // Install the app ephemerally and launch when complete.
157 EphemeralAppLauncher::EphemeralAppLauncher(const std::string
& webstore_item_id
,
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
),
165 WebContents::Create(WebContents::CreateParams(profile
))) {
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
),
174 content::WebContentsObserver(web_contents
),
175 launch_callback_(callback
),
176 parent_window_(NativeWindowForWebContents(web_contents
)) {
179 EphemeralAppLauncher::~EphemeralAppLauncher() {}
181 scoped_ptr
<extensions::ExtensionInstallChecker
>
182 EphemeralAppLauncher::CreateInstallChecker() {
183 return make_scoped_ptr(new ExtensionInstallChecker(profile()));
186 scoped_ptr
<ExtensionInstallPrompt
> EphemeralAppLauncher::CreateInstallUI() {
188 return make_scoped_ptr(new ExtensionInstallPrompt(web_contents()));
190 return make_scoped_ptr(
191 new ExtensionInstallPrompt(profile(), parent_window_
, NULL
));
194 scoped_ptr
<WebstoreInstaller::Approval
> EphemeralAppLauncher::CreateApproval()
196 scoped_ptr
<WebstoreInstaller::Approval
> approval
=
197 WebstoreStandaloneInstaller::CreateApproval();
198 approval
->is_ephemeral
= true;
199 return approval
.Pass();
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
))
209 // Do not launch blacklisted apps.
210 if (ExtensionPrefs::Get(profile())->IsExtensionBlacklisted(extension
->id())) {
211 *reason
= webstore_install::BLACKLISTED
;
212 *error
= kBlacklistedError
;
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
;
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
;
230 if (!EnsureUniqueInstall(&result
, &error
)) {
231 InvokeCallback(result
, error
);
235 // Keep this object alive until the enable flow is complete. Either
236 // ExtensionEnableFlowFinished() or ExtensionEnableFlowAborted() will be
240 extension_enable_flow_
.reset(
241 new ExtensionEnableFlow(profile(), extension
->id(), this));
243 extension_enable_flow_
->StartForWebContents(web_contents());
245 extension_enable_flow_
->StartForNativeWindow(parent_window_
);
248 void EphemeralAppLauncher::MaybeLaunchApp() {
249 webstore_install::Result result
= webstore_install::OTHER_ERROR
;
252 ExtensionRegistry
* registry
= ExtensionRegistry::Get(profile());
253 const Extension
* extension
=
254 registry
->GetExtensionById(id(), ExtensionRegistry::EVERYTHING
);
256 // Although the installation was successful, the app may not be
258 if (registry
->enabled_extensions().Contains(extension
->id())) {
259 result
= webstore_install::SUCCESS
;
260 LaunchApp(extension
);
262 error
= kAppDisabledError
;
263 // Determine why the app cannot be launched.
264 CanLaunchInstalledApp(extension
, &result
, &error
);
267 // The extension must be present in the registry if installed.
269 error
= kMissingAppError
;
272 InvokeCallback(result
, error
);
275 void EphemeralAppLauncher::LaunchApp(const Extension
* extension
) const {
276 DCHECK(extension
&& extension
->is_app() &&
277 ExtensionRegistry::Get(profile())
278 ->GetExtensionById(extension
->id(), ExtensionRegistry::ENABLED
));
280 AppLaunchParams
params(profile(), extension
, NEW_FOREGROUND_TAB
);
281 params
.desktop_type
=
282 chrome::GetHostDesktopTypeForNativeWindow(parent_window_
);
283 OpenApplication(params
);
286 bool EphemeralAppLauncher::LaunchHostedApp(const Extension
* extension
) const {
287 GURL launch_url
= extensions::AppLaunchInfo::GetLaunchWebURL(extension
);
288 if (!launch_url
.is_valid())
291 chrome::ScopedTabbedBrowserDisplayer
displayer(
292 profile(), chrome::GetHostDesktopTypeForNativeWindow(parent_window_
));
293 chrome::NavigateParams
params(
294 displayer
.browser(), launch_url
, content::PAGE_TRANSITION_AUTO_TOPLEVEL
);
295 params
.disposition
= NEW_FOREGROUND_TAB
;
296 chrome::Navigate(¶ms
);
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
);
309 void EphemeralAppLauncher::AbortLaunch(webstore_install::Result result
,
310 const std::string
& error
) {
311 InvokeCallback(result
, error
);
312 WebstoreStandaloneInstaller::CompleteInstall(result
, error
);
315 void EphemeralAppLauncher::CheckEphemeralInstallPermitted() {
316 scoped_refptr
<const Extension
> extension
= GetLocalizedExtensionForDisplay();
317 DCHECK(extension
.get()); // Checked in OnManifestParsed().
319 install_checker_
= CreateInstallChecker();
320 DCHECK(install_checker_
.get());
322 install_checker_
->set_extension(extension
);
323 install_checker_
->Start(ExtensionInstallChecker::CHECK_BLACKLIST
|
324 ExtensionInstallChecker::CHECK_REQUIREMENTS
,
326 base::Bind(&EphemeralAppLauncher::OnInstallChecked
,
327 base::Unretained(this)));
330 void EphemeralAppLauncher::OnInstallChecked(int check_failures
) {
331 if (!CheckRequestorAlive()) {
332 AbortLaunch(webstore_install::OTHER_ERROR
, std::string());
336 if (install_checker_
->blacklist_state() == extensions::BLACKLISTED_MALWARE
) {
337 AbortLaunch(webstore_install::BLACKLISTED
, kBlacklistedError
);
341 if (!install_checker_
->requirement_errors().empty()) {
342 AbortLaunch(webstore_install::REQUIREMENT_VIOLATIONS
,
343 install_checker_
->requirement_errors().front());
347 // Proceed with the normal install flow.
348 ProceedWithInstallPrompt();
351 void EphemeralAppLauncher::InitInstallData(
352 extensions::ActiveInstallData
* install_data
) const {
353 install_data
->is_ephemeral
= true;
356 bool EphemeralAppLauncher::CheckRequestorAlive() const {
357 return dummy_web_contents_
.get() != NULL
|| web_contents() != NULL
;
360 const GURL
& EphemeralAppLauncher::GetRequestorURL() const {
361 return GURL::EmptyGURL();
364 bool EphemeralAppLauncher::ShouldShowPostInstallUI() const {
368 bool EphemeralAppLauncher::ShouldShowAppInstalledBubble() const {
372 WebContents
* EphemeralAppLauncher::GetWebContents() const {
373 return web_contents() ? web_contents() : dummy_web_contents_
.get();
376 scoped_refptr
<ExtensionInstallPrompt::Prompt
>
377 EphemeralAppLauncher::CreateInstallPrompt() const {
378 const Extension
* extension
= localized_extension_for_display();
379 DCHECK(extension
); // Checked in OnManifestParsed().
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())
388 return make_scoped_refptr(new ExtensionInstallPrompt::Prompt(
389 ExtensionInstallPrompt::LAUNCH_PROMPT
));
392 bool EphemeralAppLauncher::CheckInlineInstallPermitted(
393 const base::DictionaryValue
& webstore_data
,
394 std::string
* error
) const {
399 bool EphemeralAppLauncher::CheckRequestorPermitted(
400 const base::DictionaryValue
& webstore_data
,
401 std::string
* error
) const {
406 void EphemeralAppLauncher::OnManifestParsed() {
407 const Extension
* extension
= GetLocalizedExtensionForDisplay();
409 AbortLaunch(webstore_install::INVALID_MANIFEST
, kInvalidManifestError
);
413 webstore_install::Result result
= webstore_install::OTHER_ERROR
;
415 if (!CheckCommonLaunchCriteria(profile(), extension
, &result
, &error
)) {
416 AbortLaunch(result
, error
);
420 if (extension
->is_legacy_packaged_app()) {
421 AbortLaunch(webstore_install::LAUNCH_UNSUPPORTED_EXTENSION_TYPE
,
426 if (extension
->is_hosted_app()) {
427 // Hosted apps do not need to be installed ephemerally. Just navigate to
429 if (LaunchHostedApp(extension
))
430 AbortLaunch(webstore_install::SUCCESS
, std::string());
432 AbortLaunch(webstore_install::INVALID_MANIFEST
, kInvalidManifestError
);
436 CheckEphemeralInstallPermitted();
439 void EphemeralAppLauncher::CompleteInstall(webstore_install::Result result
,
440 const std::string
& error
) {
441 if (result
== webstore_install::SUCCESS
)
443 else if (!launch_callback_
.is_null())
444 InvokeCallback(result
, error
);
446 WebstoreStandaloneInstaller::CompleteInstall(result
, error
);
449 void EphemeralAppLauncher::WebContentsDestroyed() {
450 launch_callback_
.Reset();
454 void EphemeralAppLauncher::ExtensionEnableFlowFinished() {
457 // CompleteInstall will call Release.
458 WebstoreStandaloneInstaller::CompleteInstall(webstore_install::SUCCESS
,
462 void EphemeralAppLauncher::ExtensionEnableFlowAborted(bool user_initiated
) {
463 // CompleteInstall will call Release.
464 CompleteInstall(webstore_install::USER_CANCELLED
, kUserCancelledError
);