• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ProfileForWebContents(content::WebContents * contents)48 Profile* ProfileForWebContents(content::WebContents* contents) {
49   if (!contents)
50     return NULL;
51 
52   return Profile::FromBrowserContext(contents->GetBrowserContext());
53 }
54 
NativeWindowForWebContents(content::WebContents * contents)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.
CheckCommonLaunchCriteria(Profile * profile,const Extension * extension,webstore_install::Result * reason,std::string * error)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
IsFeatureEnabled()91 bool EphemeralAppLauncher::IsFeatureEnabled() {
92   return CommandLine::ForCurrentProcess()->HasSwitch(
93       switches::kEnableEphemeralApps);
94 }
95 
96 // static
CreateForLauncher(const std::string & webstore_item_id,Profile * profile,gfx::NativeWindow parent_window,const LaunchCallback & callback)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
CreateForWebContents(const std::string & webstore_item_id,content::WebContents * web_contents,const LaunchCallback & callback)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 
Start()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 
EphemeralAppLauncher(const std::string & webstore_item_id,Profile * profile,gfx::NativeWindow parent_window,const LaunchCallback & callback)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 
EphemeralAppLauncher(const std::string & webstore_item_id,content::WebContents * web_contents,const LaunchCallback & callback)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 
~EphemeralAppLauncher()179 EphemeralAppLauncher::~EphemeralAppLauncher() {}
180 
181 scoped_ptr<extensions::ExtensionInstallChecker>
CreateInstallChecker()182 EphemeralAppLauncher::CreateInstallChecker() {
183   return make_scoped_ptr(new ExtensionInstallChecker(profile()));
184 }
185 
CreateInstallUI()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 
CreateApproval() const194 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 
CanLaunchInstalledApp(const extensions::Extension * extension,webstore_install::Result * reason,std::string * error)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 
EnableInstalledApp(const Extension * extension)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 
MaybeLaunchApp()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 
LaunchApp(const Extension * extension) const275 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 
LaunchHostedApp(const Extension * extension) const286 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 
InvokeCallback(webstore_install::Result result,const std::string & error)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 
AbortLaunch(webstore_install::Result result,const std::string & error)309 void EphemeralAppLauncher::AbortLaunch(webstore_install::Result result,
310                                        const std::string& error) {
311   InvokeCallback(result, error);
312   WebstoreStandaloneInstaller::CompleteInstall(result, error);
313 }
314 
CheckEphemeralInstallPermitted()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 
OnInstallChecked(int check_failures)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 
InitInstallData(extensions::ActiveInstallData * install_data) const351 void EphemeralAppLauncher::InitInstallData(
352     extensions::ActiveInstallData* install_data) const {
353   install_data->is_ephemeral = true;
354 }
355 
CheckRequestorAlive() const356 bool EphemeralAppLauncher::CheckRequestorAlive() const {
357   return dummy_web_contents_.get() != NULL || web_contents() != NULL;
358 }
359 
GetRequestorURL() const360 const GURL& EphemeralAppLauncher::GetRequestorURL() const {
361   return GURL::EmptyGURL();
362 }
363 
ShouldShowPostInstallUI() const364 bool EphemeralAppLauncher::ShouldShowPostInstallUI() const {
365   return false;
366 }
367 
ShouldShowAppInstalledBubble() const368 bool EphemeralAppLauncher::ShouldShowAppInstalledBubble() const {
369   return false;
370 }
371 
GetWebContents() const372 WebContents* EphemeralAppLauncher::GetWebContents() const {
373   return web_contents() ? web_contents() : dummy_web_contents_.get();
374 }
375 
376 scoped_refptr<ExtensionInstallPrompt::Prompt>
CreateInstallPrompt() const377 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 
CheckInlineInstallPermitted(const base::DictionaryValue & webstore_data,std::string * error) const392 bool EphemeralAppLauncher::CheckInlineInstallPermitted(
393     const base::DictionaryValue& webstore_data,
394     std::string* error) const {
395   *error = "";
396   return true;
397 }
398 
CheckRequestorPermitted(const base::DictionaryValue & webstore_data,std::string * error) const399 bool EphemeralAppLauncher::CheckRequestorPermitted(
400     const base::DictionaryValue& webstore_data,
401     std::string* error) const {
402   *error = "";
403   return true;
404 }
405 
OnManifestParsed()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 
CompleteInstall(webstore_install::Result result,const std::string & error)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 
WebContentsDestroyed()449 void EphemeralAppLauncher::WebContentsDestroyed() {
450   launch_callback_.Reset();
451   AbortInstall();
452 }
453 
ExtensionEnableFlowFinished()454 void EphemeralAppLauncher::ExtensionEnableFlowFinished() {
455   MaybeLaunchApp();
456 
457   // CompleteInstall will call Release.
458   WebstoreStandaloneInstaller::CompleteInstall(webstore_install::SUCCESS,
459                                                std::string());
460 }
461 
ExtensionEnableFlowAborted(bool user_initiated)462 void EphemeralAppLauncher::ExtensionEnableFlowAborted(bool user_initiated) {
463   // CompleteInstall will call Release.
464   CompleteInstall(webstore_install::USER_CANCELLED, kUserCancelledError);
465 }
466