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(&params);
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