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(¶ms);
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