OLD | NEW |
| (Empty) |
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/browser_navigator_params.h" | |
15 #include "chrome/browser/ui/extensions/app_launch_params.h" | |
16 #include "chrome/browser/ui/extensions/application_launch.h" | |
17 #include "chrome/browser/ui/extensions/extension_enable_flow.h" | |
18 #include "chrome/browser/ui/native_window_tracker.h" | |
19 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h" | |
20 #include "chrome/common/chrome_switches.h" | |
21 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h" | |
22 #include "content/public/browser/web_contents.h" | |
23 #include "extensions/browser/extension_prefs.h" | |
24 #include "extensions/browser/extension_registry.h" | |
25 #include "extensions/browser/extension_system.h" | |
26 #include "extensions/browser/management_policy.h" | |
27 #include "extensions/common/constants.h" | |
28 #include "extensions/common/permissions/permissions_data.h" | |
29 #include "ui/app_list/app_list_switches.h" | |
30 | |
31 using content::WebContents; | |
32 using extensions::Extension; | |
33 using extensions::ExtensionInstallChecker; | |
34 using extensions::ExtensionPrefs; | |
35 using extensions::ExtensionRegistry; | |
36 using extensions::ExtensionSystem; | |
37 using extensions::ManagementPolicy; | |
38 using extensions::WebstoreInstaller; | |
39 namespace webstore_install = extensions::webstore_install; | |
40 | |
41 namespace { | |
42 | |
43 const char kInvalidManifestError[] = "Invalid manifest"; | |
44 const char kExtensionTypeError[] = "Not an app"; | |
45 const char kAppTypeError[] = "Ephemeral legacy packaged apps not supported"; | |
46 const char kUserCancelledError[] = "Launch cancelled by the user"; | |
47 const char kBlacklistedError[] = "App is blacklisted for malware"; | |
48 const char kRequirementsError[] = "App has missing requirements"; | |
49 const char kFeatureDisabledError[] = "Launching ephemeral apps is not enabled"; | |
50 const char kMissingAppError[] = "App is not installed"; | |
51 const char kAppDisabledError[] = "App is disabled"; | |
52 | |
53 Profile* ProfileForWebContents(content::WebContents* contents) { | |
54 if (!contents) | |
55 return NULL; | |
56 | |
57 return Profile::FromBrowserContext(contents->GetBrowserContext()); | |
58 } | |
59 | |
60 gfx::NativeWindow NativeWindowForWebContents(content::WebContents* contents) { | |
61 if (!contents) | |
62 return NULL; | |
63 | |
64 return contents->GetTopLevelNativeWindow(); | |
65 } | |
66 | |
67 // Check whether an extension can be launched. The extension does not need to | |
68 // be currently installed. | |
69 bool CheckCommonLaunchCriteria(Profile* profile, | |
70 const Extension* extension, | |
71 webstore_install::Result* reason, | |
72 std::string* error) { | |
73 // Only apps can be launched. | |
74 if (!extension->is_app()) { | |
75 *reason = webstore_install::LAUNCH_UNSUPPORTED_EXTENSION_TYPE; | |
76 *error = kExtensionTypeError; | |
77 return false; | |
78 } | |
79 | |
80 // Do not launch apps blocked by management policies. | |
81 ManagementPolicy* management_policy = | |
82 ExtensionSystem::Get(profile)->management_policy(); | |
83 base::string16 policy_error; | |
84 if (!management_policy->UserMayLoad(extension, &policy_error)) { | |
85 *reason = webstore_install::BLOCKED_BY_POLICY; | |
86 *error = base::UTF16ToUTF8(policy_error); | |
87 return false; | |
88 } | |
89 | |
90 return true; | |
91 } | |
92 | |
93 } // namespace | |
94 | |
95 // static | |
96 bool EphemeralAppLauncher::IsFeatureEnabled() { | |
97 return base::CommandLine::ForCurrentProcess()->HasSwitch( | |
98 switches::kEnableEphemeralAppsInWebstore); | |
99 } | |
100 | |
101 // static | |
102 scoped_refptr<EphemeralAppLauncher> EphemeralAppLauncher::CreateForLauncher( | |
103 const std::string& webstore_item_id, | |
104 Profile* profile, | |
105 gfx::NativeWindow parent_window, | |
106 const LaunchCallback& callback) { | |
107 scoped_refptr<EphemeralAppLauncher> installer = | |
108 new EphemeralAppLauncher(webstore_item_id, | |
109 profile, | |
110 parent_window, | |
111 callback); | |
112 installer->set_install_source(WebstoreInstaller::INSTALL_SOURCE_APP_LAUNCHER); | |
113 return installer; | |
114 } | |
115 | |
116 // static | |
117 scoped_refptr<EphemeralAppLauncher> EphemeralAppLauncher::CreateForWebContents( | |
118 const std::string& webstore_item_id, | |
119 content::WebContents* web_contents, | |
120 const LaunchCallback& callback) { | |
121 scoped_refptr<EphemeralAppLauncher> installer = | |
122 new EphemeralAppLauncher(webstore_item_id, web_contents, callback); | |
123 installer->set_install_source(WebstoreInstaller::INSTALL_SOURCE_OTHER); | |
124 return installer; | |
125 } | |
126 | |
127 void EphemeralAppLauncher::Start() { | |
128 if (!IsFeatureEnabled()) { | |
129 InvokeCallback(webstore_install::LAUNCH_FEATURE_DISABLED, | |
130 kFeatureDisabledError); | |
131 return; | |
132 } | |
133 | |
134 // Check whether the app already exists in extension system before downloading | |
135 // from the webstore. | |
136 const Extension* extension = | |
137 ExtensionRegistry::Get(profile()) | |
138 ->GetExtensionById(id(), ExtensionRegistry::EVERYTHING); | |
139 if (extension) { | |
140 webstore_install::Result result = webstore_install::OTHER_ERROR; | |
141 std::string error; | |
142 if (!CanLaunchInstalledApp(extension, &result, &error)) { | |
143 InvokeCallback(result, error); | |
144 return; | |
145 } | |
146 | |
147 if (extensions::util::IsAppLaunchableWithoutEnabling(extension->id(), | |
148 profile())) { | |
149 LaunchApp(extension); | |
150 InvokeCallback(webstore_install::SUCCESS, std::string()); | |
151 return; | |
152 } | |
153 | |
154 EnableInstalledApp(extension); | |
155 return; | |
156 } | |
157 | |
158 // Install the app ephemerally and launch when complete. | |
159 BeginInstall(); | |
160 } | |
161 | |
162 EphemeralAppLauncher::EphemeralAppLauncher(const std::string& webstore_item_id, | |
163 Profile* profile, | |
164 gfx::NativeWindow parent_window, | |
165 const LaunchCallback& callback) | |
166 : WebstoreStandaloneInstaller(webstore_item_id, profile, Callback()), | |
167 launch_callback_(callback), | |
168 parent_window_(parent_window), | |
169 dummy_web_contents_( | |
170 WebContents::Create(WebContents::CreateParams(profile))) { | |
171 if (parent_window_) | |
172 parent_window_tracker_ = NativeWindowTracker::Create(parent_window); | |
173 } | |
174 | |
175 EphemeralAppLauncher::EphemeralAppLauncher(const std::string& webstore_item_id, | |
176 content::WebContents* web_contents, | |
177 const LaunchCallback& callback) | |
178 : WebstoreStandaloneInstaller(webstore_item_id, | |
179 ProfileForWebContents(web_contents), | |
180 Callback()), | |
181 content::WebContentsObserver(web_contents), | |
182 launch_callback_(callback), | |
183 parent_window_(NativeWindowForWebContents(web_contents)) { | |
184 } | |
185 | |
186 EphemeralAppLauncher::~EphemeralAppLauncher() {} | |
187 | |
188 scoped_ptr<extensions::ExtensionInstallChecker> | |
189 EphemeralAppLauncher::CreateInstallChecker() { | |
190 return make_scoped_ptr(new ExtensionInstallChecker(profile())); | |
191 } | |
192 | |
193 scoped_ptr<ExtensionInstallPrompt> EphemeralAppLauncher::CreateInstallUI() { | |
194 if (web_contents()) | |
195 return make_scoped_ptr(new ExtensionInstallPrompt(web_contents())); | |
196 | |
197 return make_scoped_ptr(new ExtensionInstallPrompt(profile(), parent_window_)); | |
198 } | |
199 | |
200 scoped_ptr<WebstoreInstaller::Approval> EphemeralAppLauncher::CreateApproval() | |
201 const { | |
202 scoped_ptr<WebstoreInstaller::Approval> approval = | |
203 WebstoreStandaloneInstaller::CreateApproval(); | |
204 approval->is_ephemeral = true; | |
205 return approval.Pass(); | |
206 } | |
207 | |
208 bool EphemeralAppLauncher::CanLaunchInstalledApp( | |
209 const extensions::Extension* extension, | |
210 webstore_install::Result* reason, | |
211 std::string* error) { | |
212 if (!CheckCommonLaunchCriteria(profile(), extension, reason, error)) | |
213 return false; | |
214 | |
215 // Do not launch blacklisted apps. | |
216 if (ExtensionPrefs::Get(profile())->IsExtensionBlacklisted(extension->id())) { | |
217 *reason = webstore_install::BLACKLISTED; | |
218 *error = kBlacklistedError; | |
219 return false; | |
220 } | |
221 | |
222 // If the app has missing requirements, it cannot be launched. | |
223 if (!extensions::util::IsAppLaunchable(extension->id(), profile())) { | |
224 *reason = webstore_install::REQUIREMENT_VIOLATIONS; | |
225 *error = kRequirementsError; | |
226 return false; | |
227 } | |
228 | |
229 return true; | |
230 } | |
231 | |
232 void EphemeralAppLauncher::EnableInstalledApp(const Extension* extension) { | |
233 // Check whether an install is already in progress. | |
234 webstore_install::Result result = webstore_install::OTHER_ERROR; | |
235 std::string error; | |
236 if (!EnsureUniqueInstall(&result, &error)) { | |
237 InvokeCallback(result, error); | |
238 return; | |
239 } | |
240 | |
241 // Keep this object alive until the enable flow is complete. Either | |
242 // ExtensionEnableFlowFinished() or ExtensionEnableFlowAborted() will be | |
243 // called. | |
244 AddRef(); | |
245 | |
246 extension_enable_flow_.reset( | |
247 new ExtensionEnableFlow(profile(), extension->id(), this)); | |
248 if (web_contents()) | |
249 extension_enable_flow_->StartForWebContents(web_contents()); | |
250 else | |
251 extension_enable_flow_->StartForNativeWindow(parent_window_); | |
252 } | |
253 | |
254 void EphemeralAppLauncher::MaybeLaunchApp() { | |
255 webstore_install::Result result = webstore_install::OTHER_ERROR; | |
256 std::string error; | |
257 | |
258 ExtensionRegistry* registry = ExtensionRegistry::Get(profile()); | |
259 const Extension* extension = | |
260 registry->GetExtensionById(id(), ExtensionRegistry::EVERYTHING); | |
261 if (extension) { | |
262 // Although the installation was successful, the app may not be | |
263 // launchable. | |
264 if (registry->enabled_extensions().Contains(extension->id())) { | |
265 result = webstore_install::SUCCESS; | |
266 LaunchApp(extension); | |
267 } else { | |
268 error = kAppDisabledError; | |
269 // Determine why the app cannot be launched. | |
270 CanLaunchInstalledApp(extension, &result, &error); | |
271 } | |
272 } else { | |
273 // The extension must be present in the registry if installed. | |
274 NOTREACHED(); | |
275 error = kMissingAppError; | |
276 } | |
277 | |
278 InvokeCallback(result, error); | |
279 } | |
280 | |
281 void EphemeralAppLauncher::LaunchApp(const Extension* extension) const { | |
282 DCHECK(extension && extension->is_app() && | |
283 ExtensionRegistry::Get(profile()) | |
284 ->GetExtensionById(extension->id(), ExtensionRegistry::ENABLED)); | |
285 | |
286 AppLaunchParams params(profile(), extension, NEW_FOREGROUND_TAB, | |
287 extensions::SOURCE_EPHEMERAL_APP); | |
288 params.desktop_type = | |
289 chrome::GetHostDesktopTypeForNativeWindow(parent_window_); | |
290 OpenApplication(params); | |
291 } | |
292 | |
293 bool EphemeralAppLauncher::LaunchHostedApp(const Extension* extension) const { | |
294 GURL launch_url = extensions::AppLaunchInfo::GetLaunchWebURL(extension); | |
295 if (!launch_url.is_valid()) | |
296 return false; | |
297 | |
298 chrome::ScopedTabbedBrowserDisplayer displayer( | |
299 profile(), chrome::GetHostDesktopTypeForNativeWindow(parent_window_)); | |
300 chrome::NavigateParams params( | |
301 displayer.browser(), launch_url, ui::PAGE_TRANSITION_AUTO_TOPLEVEL); | |
302 params.disposition = NEW_FOREGROUND_TAB; | |
303 chrome::Navigate(¶ms); | |
304 return true; | |
305 } | |
306 | |
307 void EphemeralAppLauncher::InvokeCallback(webstore_install::Result result, | |
308 const std::string& error) { | |
309 if (!launch_callback_.is_null()) { | |
310 LaunchCallback callback = launch_callback_; | |
311 launch_callback_.Reset(); | |
312 callback.Run(result, error); | |
313 } | |
314 } | |
315 | |
316 void EphemeralAppLauncher::AbortLaunch(webstore_install::Result result, | |
317 const std::string& error) { | |
318 InvokeCallback(result, error); | |
319 WebstoreStandaloneInstaller::CompleteInstall(result, error); | |
320 } | |
321 | |
322 void EphemeralAppLauncher::CheckEphemeralInstallPermitted() { | |
323 scoped_refptr<const Extension> extension = GetLocalizedExtensionForDisplay(); | |
324 DCHECK(extension.get()); // Checked in OnManifestParsed(). | |
325 | |
326 install_checker_ = CreateInstallChecker(); | |
327 DCHECK(install_checker_.get()); | |
328 | |
329 install_checker_->set_extension(extension); | |
330 install_checker_->Start(ExtensionInstallChecker::CHECK_BLACKLIST | | |
331 ExtensionInstallChecker::CHECK_REQUIREMENTS, | |
332 true, | |
333 base::Bind(&EphemeralAppLauncher::OnInstallChecked, | |
334 base::Unretained(this))); | |
335 } | |
336 | |
337 void EphemeralAppLauncher::OnInstallChecked(int check_failures) { | |
338 if (!CheckRequestorAlive()) { | |
339 AbortLaunch(webstore_install::OTHER_ERROR, std::string()); | |
340 return; | |
341 } | |
342 | |
343 if (install_checker_->blacklist_state() == extensions::BLACKLISTED_MALWARE) { | |
344 AbortLaunch(webstore_install::BLACKLISTED, kBlacklistedError); | |
345 return; | |
346 } | |
347 | |
348 if (!install_checker_->requirement_errors().empty()) { | |
349 AbortLaunch(webstore_install::REQUIREMENT_VIOLATIONS, | |
350 install_checker_->requirement_errors().front()); | |
351 return; | |
352 } | |
353 | |
354 // Proceed with the normal install flow. | |
355 ProceedWithInstallPrompt(); | |
356 } | |
357 | |
358 void EphemeralAppLauncher::InitInstallData( | |
359 extensions::ActiveInstallData* install_data) const { | |
360 install_data->is_ephemeral = true; | |
361 } | |
362 | |
363 bool EphemeralAppLauncher::CheckRequestorAlive() const { | |
364 if (!parent_window_) { | |
365 // Assume the requestor is always alive if |parent_window_| is null. | |
366 return true; | |
367 } | |
368 | |
369 return (web_contents() != nullptr || | |
370 (parent_window_tracker_ && | |
371 !parent_window_tracker_->WasNativeWindowClosed())); | |
372 } | |
373 | |
374 const GURL& EphemeralAppLauncher::GetRequestorURL() const { | |
375 return GURL::EmptyGURL(); | |
376 } | |
377 | |
378 bool EphemeralAppLauncher::ShouldShowPostInstallUI() const { | |
379 return false; | |
380 } | |
381 | |
382 bool EphemeralAppLauncher::ShouldShowAppInstalledBubble() const { | |
383 return false; | |
384 } | |
385 | |
386 WebContents* EphemeralAppLauncher::GetWebContents() const { | |
387 return web_contents() ? web_contents() : dummy_web_contents_.get(); | |
388 } | |
389 | |
390 scoped_refptr<ExtensionInstallPrompt::Prompt> | |
391 EphemeralAppLauncher::CreateInstallPrompt() const { | |
392 const Extension* extension = localized_extension_for_display(); | |
393 DCHECK(extension); // Checked in OnManifestParsed(). | |
394 | |
395 // Skip the prompt by returning null if the app does not need to display | |
396 // permission warnings. | |
397 if (extension->permissions_data()->GetPermissionMessages().empty()) | |
398 return NULL; | |
399 | |
400 return make_scoped_refptr(new ExtensionInstallPrompt::Prompt( | |
401 ExtensionInstallPrompt::LAUNCH_PROMPT)); | |
402 } | |
403 | |
404 bool EphemeralAppLauncher::CheckInlineInstallPermitted( | |
405 const base::DictionaryValue& webstore_data, | |
406 std::string* error) const { | |
407 *error = ""; | |
408 return true; | |
409 } | |
410 | |
411 bool EphemeralAppLauncher::CheckRequestorPermitted( | |
412 const base::DictionaryValue& webstore_data, | |
413 std::string* error) const { | |
414 *error = ""; | |
415 return true; | |
416 } | |
417 | |
418 void EphemeralAppLauncher::OnManifestParsed() { | |
419 scoped_refptr<const Extension> extension = GetLocalizedExtensionForDisplay(); | |
420 if (!extension.get()) { | |
421 AbortLaunch(webstore_install::INVALID_MANIFEST, kInvalidManifestError); | |
422 return; | |
423 } | |
424 | |
425 webstore_install::Result result = webstore_install::OTHER_ERROR; | |
426 std::string error; | |
427 if (!CheckCommonLaunchCriteria(profile(), extension.get(), &result, &error)) { | |
428 AbortLaunch(result, error); | |
429 return; | |
430 } | |
431 | |
432 if (extension->is_legacy_packaged_app()) { | |
433 AbortLaunch(webstore_install::LAUNCH_UNSUPPORTED_EXTENSION_TYPE, | |
434 kAppTypeError); | |
435 return; | |
436 } | |
437 | |
438 if (extension->is_hosted_app()) { | |
439 // Hosted apps do not need to be installed ephemerally. Just navigate to | |
440 // their launch url. | |
441 if (LaunchHostedApp(extension.get())) | |
442 AbortLaunch(webstore_install::SUCCESS, std::string()); | |
443 else | |
444 AbortLaunch(webstore_install::INVALID_MANIFEST, kInvalidManifestError); | |
445 return; | |
446 } | |
447 | |
448 CheckEphemeralInstallPermitted(); | |
449 } | |
450 | |
451 void EphemeralAppLauncher::CompleteInstall(webstore_install::Result result, | |
452 const std::string& error) { | |
453 if (result == webstore_install::SUCCESS) | |
454 MaybeLaunchApp(); | |
455 else if (!launch_callback_.is_null()) | |
456 InvokeCallback(result, error); | |
457 | |
458 WebstoreStandaloneInstaller::CompleteInstall(result, error); | |
459 } | |
460 | |
461 void EphemeralAppLauncher::WebContentsDestroyed() { | |
462 launch_callback_.Reset(); | |
463 AbortInstall(); | |
464 } | |
465 | |
466 void EphemeralAppLauncher::ExtensionEnableFlowFinished() { | |
467 MaybeLaunchApp(); | |
468 | |
469 // CompleteInstall will call Release. | |
470 WebstoreStandaloneInstaller::CompleteInstall(webstore_install::SUCCESS, | |
471 std::string()); | |
472 } | |
473 | |
474 void EphemeralAppLauncher::ExtensionEnableFlowAborted(bool user_initiated) { | |
475 // CompleteInstall will call Release. | |
476 CompleteInstall(webstore_install::USER_CANCELLED, kUserCancelledError); | |
477 } | |
OLD | NEW |