1 // Copyright (c) 2012 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/ui/webui/ntp/app_launcher_handler.h"
6
7 #include <vector>
8
9 #include "apps/metrics_names.h"
10 #include "base/auto_reset.h"
11 #include "base/bind.h"
12 #include "base/bind_helpers.h"
13 #include "base/i18n/rtl.h"
14 #include "base/metrics/field_trial.h"
15 #include "base/metrics/histogram.h"
16 #include "base/prefs/pref_service.h"
17 #include "base/prefs/scoped_user_pref_update.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/values.h"
20 #include "chrome/browser/browser_process.h"
21 #include "chrome/browser/chrome_notification_types.h"
22 #include "chrome/browser/extensions/crx_installer.h"
23 #include "chrome/browser/extensions/extension_service.h"
24 #include "chrome/browser/extensions/extension_ui_util.h"
25 #include "chrome/browser/extensions/launch_util.h"
26 #include "chrome/browser/favicon/favicon_service_factory.h"
27 #include "chrome/browser/profiles/profile.h"
28 #include "chrome/browser/ui/app_list/app_list_util.h"
29 #include "chrome/browser/ui/browser_dialogs.h"
30 #include "chrome/browser/ui/browser_finder.h"
31 #include "chrome/browser/ui/browser_tabstrip.h"
32 #include "chrome/browser/ui/browser_window.h"
33 #include "chrome/browser/ui/extensions/application_launch.h"
34 #include "chrome/browser/ui/extensions/extension_enable_flow.h"
35 #include "chrome/browser/ui/tabs/tab_strip_model.h"
36 #include "chrome/browser/ui/webui/extensions/extension_basic_info.h"
37 #include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
38 #include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
39 #include "chrome/browser/ui/webui/ntp/new_tab_ui.h"
40 #include "chrome/common/extensions/extension_constants.h"
41 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
42 #include "chrome/common/pref_names.h"
43 #include "chrome/common/url_constants.h"
44 #include "chrome/common/web_application_info.h"
45 #include "components/favicon_base/favicon_types.h"
46 #include "content/public/browser/notification_service.h"
47 #include "content/public/browser/web_ui.h"
48 #include "content/public/common/favicon_url.h"
49 #include "extensions/browser/app_sorting.h"
50 #include "extensions/browser/extension_prefs.h"
51 #include "extensions/browser/extension_registry.h"
52 #include "extensions/browser/extension_system.h"
53 #include "extensions/browser/management_policy.h"
54 #include "extensions/browser/pref_names.h"
55 #include "extensions/common/constants.h"
56 #include "extensions/common/extension.h"
57 #include "extensions/common/extension_icon_set.h"
58 #include "extensions/common/extension_set.h"
59 #include "grit/browser_resources.h"
60 #include "grit/generated_resources.h"
61 #include "ui/base/l10n/l10n_util.h"
62 #include "ui/base/webui/web_ui_util.h"
63 #include "ui/gfx/favicon_size.h"
64 #include "url/gurl.h"
65
66 using content::WebContents;
67 using extensions::AppSorting;
68 using extensions::CrxInstaller;
69 using extensions::Extension;
70 using extensions::ExtensionPrefs;
71 using extensions::ExtensionRegistry;
72 using extensions::ExtensionSet;
73 using extensions::UnloadedExtensionInfo;
74
75 namespace {
76
RecordAppLauncherPromoHistogram(apps::AppLauncherPromoHistogramValues value)77 void RecordAppLauncherPromoHistogram(
78 apps::AppLauncherPromoHistogramValues value) {
79 DCHECK_LT(value, apps::APP_LAUNCHER_PROMO_MAX);
80 UMA_HISTOGRAM_ENUMERATION(
81 "Apps.AppLauncherPromo", value, apps::APP_LAUNCHER_PROMO_MAX);
82 }
83
84 // This is used to avoid a DCHECK due to an unhandled WebUI callback. The
85 // JavaScript used to switch between pages sends "pageSelected" which is used
86 // in the context of the NTP for recording metrics we don't need here.
NoOpCallback(const base::ListValue * args)87 void NoOpCallback(const base::ListValue* args) {}
88
89 } // namespace
90
AppInstallInfo()91 AppLauncherHandler::AppInstallInfo::AppInstallInfo() {}
92
~AppInstallInfo()93 AppLauncherHandler::AppInstallInfo::~AppInstallInfo() {}
94
AppLauncherHandler(ExtensionService * extension_service)95 AppLauncherHandler::AppLauncherHandler(ExtensionService* extension_service)
96 : extension_service_(extension_service),
97 ignore_changes_(false),
98 attempted_bookmark_app_install_(false),
99 has_loaded_apps_(false) {
100 if (IsAppLauncherEnabled())
101 RecordAppLauncherPromoHistogram(apps::APP_LAUNCHER_PROMO_ALREADY_INSTALLED);
102 else if (ShouldShowAppLauncherPromo())
103 RecordAppLauncherPromoHistogram(apps::APP_LAUNCHER_PROMO_SHOWN);
104 }
105
~AppLauncherHandler()106 AppLauncherHandler::~AppLauncherHandler() {}
107
CreateAppInfo(const Extension * extension,ExtensionService * service,base::DictionaryValue * value)108 void AppLauncherHandler::CreateAppInfo(
109 const Extension* extension,
110 ExtensionService* service,
111 base::DictionaryValue* value) {
112 value->Clear();
113
114 // The Extension class 'helpfully' wraps bidi control characters that
115 // impede our ability to determine directionality.
116 base::string16 short_name = base::UTF8ToUTF16(extension->short_name());
117 base::i18n::UnadjustStringForLocaleDirection(&short_name);
118 NewTabUI::SetUrlTitleAndDirection(
119 value,
120 short_name,
121 extensions::AppLaunchInfo::GetFullLaunchURL(extension));
122
123 base::string16 name = base::UTF8ToUTF16(extension->name());
124 base::i18n::UnadjustStringForLocaleDirection(&name);
125 NewTabUI::SetFullNameAndDirection(name, value);
126
127 bool enabled =
128 service->IsExtensionEnabled(extension->id()) &&
129 !extensions::ExtensionRegistry::Get(service->GetBrowserContext())
130 ->GetExtensionById(extension->id(),
131 extensions::ExtensionRegistry::TERMINATED);
132 extensions::GetExtensionBasicInfo(extension, enabled, value);
133
134 value->SetBoolean("mayDisable", extensions::ExtensionSystem::Get(
135 service->profile())->management_policy()->UserMayModifySettings(
136 extension, NULL));
137
138 bool icon_big_exists = true;
139 // Instead of setting grayscale here, we do it in apps_page.js.
140 GURL icon_big = extensions::ExtensionIconSource::GetIconURL(
141 extension,
142 extension_misc::EXTENSION_ICON_LARGE,
143 ExtensionIconSet::MATCH_BIGGER,
144 false,
145 &icon_big_exists);
146 value->SetString("icon_big", icon_big.spec());
147 value->SetBoolean("icon_big_exists", icon_big_exists);
148 bool icon_small_exists = true;
149 GURL icon_small = extensions::ExtensionIconSource::GetIconURL(
150 extension,
151 extension_misc::EXTENSION_ICON_BITTY,
152 ExtensionIconSet::MATCH_BIGGER,
153 false,
154 &icon_small_exists);
155 value->SetString("icon_small", icon_small.spec());
156 value->SetBoolean("icon_small_exists", icon_small_exists);
157 value->SetInteger("launch_container",
158 extensions::AppLaunchInfo::GetLaunchContainer(extension));
159 ExtensionPrefs* prefs = ExtensionPrefs::Get(service->profile());
160 value->SetInteger("launch_type", extensions::GetLaunchType(prefs, extension));
161 value->SetBoolean("is_component",
162 extension->location() == extensions::Manifest::COMPONENT);
163 value->SetBoolean("is_webstore",
164 extension->id() == extension_misc::kWebStoreAppId);
165
166 AppSorting* sorting = prefs->app_sorting();
167 syncer::StringOrdinal page_ordinal = sorting->GetPageOrdinal(extension->id());
168 if (!page_ordinal.IsValid()) {
169 // Make sure every app has a page ordinal (some predate the page ordinal).
170 // The webstore app should be on the first page.
171 page_ordinal = extension->id() == extension_misc::kWebStoreAppId ?
172 sorting->CreateFirstAppPageOrdinal() :
173 sorting->GetNaturalAppPageOrdinal();
174 sorting->SetPageOrdinal(extension->id(), page_ordinal);
175 }
176 value->SetInteger("page_index",
177 sorting->PageStringOrdinalAsInteger(page_ordinal));
178
179 syncer::StringOrdinal app_launch_ordinal =
180 sorting->GetAppLaunchOrdinal(extension->id());
181 if (!app_launch_ordinal.IsValid()) {
182 // Make sure every app has a launch ordinal (some predate the launch
183 // ordinal). The webstore's app launch ordinal is always set to the first
184 // position.
185 app_launch_ordinal = extension->id() == extension_misc::kWebStoreAppId ?
186 sorting->CreateFirstAppLaunchOrdinal(page_ordinal) :
187 sorting->CreateNextAppLaunchOrdinal(page_ordinal);
188 sorting->SetAppLaunchOrdinal(extension->id(), app_launch_ordinal);
189 }
190 value->SetString("app_launch_ordinal", app_launch_ordinal.ToInternalValue());
191 }
192
RegisterMessages()193 void AppLauncherHandler::RegisterMessages() {
194 registrar_.Add(this, chrome::NOTIFICATION_APP_INSTALLED_TO_NTP,
195 content::Source<WebContents>(web_ui()->GetWebContents()));
196
197 // Some tests don't have a local state.
198 #if defined(ENABLE_APP_LIST)
199 if (g_browser_process->local_state()) {
200 local_state_pref_change_registrar_.Init(g_browser_process->local_state());
201 local_state_pref_change_registrar_.Add(
202 prefs::kShowAppLauncherPromo,
203 base::Bind(&AppLauncherHandler::OnLocalStatePreferenceChanged,
204 base::Unretained(this)));
205 }
206 #endif
207 web_ui()->RegisterMessageCallback("getApps",
208 base::Bind(&AppLauncherHandler::HandleGetApps,
209 base::Unretained(this)));
210 web_ui()->RegisterMessageCallback("launchApp",
211 base::Bind(&AppLauncherHandler::HandleLaunchApp,
212 base::Unretained(this)));
213 web_ui()->RegisterMessageCallback("setLaunchType",
214 base::Bind(&AppLauncherHandler::HandleSetLaunchType,
215 base::Unretained(this)));
216 web_ui()->RegisterMessageCallback("uninstallApp",
217 base::Bind(&AppLauncherHandler::HandleUninstallApp,
218 base::Unretained(this)));
219 web_ui()->RegisterMessageCallback("createAppShortcut",
220 base::Bind(&AppLauncherHandler::HandleCreateAppShortcut,
221 base::Unretained(this)));
222 web_ui()->RegisterMessageCallback("reorderApps",
223 base::Bind(&AppLauncherHandler::HandleReorderApps,
224 base::Unretained(this)));
225 web_ui()->RegisterMessageCallback("setPageIndex",
226 base::Bind(&AppLauncherHandler::HandleSetPageIndex,
227 base::Unretained(this)));
228 web_ui()->RegisterMessageCallback("saveAppPageName",
229 base::Bind(&AppLauncherHandler::HandleSaveAppPageName,
230 base::Unretained(this)));
231 web_ui()->RegisterMessageCallback("generateAppForLink",
232 base::Bind(&AppLauncherHandler::HandleGenerateAppForLink,
233 base::Unretained(this)));
234 web_ui()->RegisterMessageCallback("stopShowingAppLauncherPromo",
235 base::Bind(&AppLauncherHandler::StopShowingAppLauncherPromo,
236 base::Unretained(this)));
237 web_ui()->RegisterMessageCallback("onLearnMore",
238 base::Bind(&AppLauncherHandler::OnLearnMore,
239 base::Unretained(this)));
240 web_ui()->RegisterMessageCallback("pageSelected", base::Bind(&NoOpCallback));
241 }
242
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)243 void AppLauncherHandler::Observe(int type,
244 const content::NotificationSource& source,
245 const content::NotificationDetails& details) {
246 if (type == chrome::NOTIFICATION_APP_INSTALLED_TO_NTP) {
247 highlight_app_id_ = *content::Details<const std::string>(details).ptr();
248 if (has_loaded_apps_)
249 SetAppToBeHighlighted();
250 return;
251 }
252
253 if (ignore_changes_ || !has_loaded_apps_)
254 return;
255
256 switch (type) {
257 case chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED: {
258 const Extension* extension =
259 content::Details<const Extension>(details).ptr();
260 if (!extension->is_app())
261 return;
262
263 if (!extensions::ui_util::ShouldDisplayInNewTabPage(
264 extension, Profile::FromWebUI(web_ui()))) {
265 return;
266 }
267
268 scoped_ptr<base::DictionaryValue> app_info(GetAppInfo(extension));
269 if (app_info.get()) {
270 visible_apps_.insert(extension->id());
271
272 ExtensionPrefs* prefs =
273 ExtensionPrefs::Get(extension_service_->profile());
274 scoped_ptr<base::FundamentalValue> highlight(
275 base::Value::CreateBooleanValue(
276 prefs->IsFromBookmark(extension->id()) &&
277 attempted_bookmark_app_install_));
278 attempted_bookmark_app_install_ = false;
279 web_ui()->CallJavascriptFunction(
280 "ntp.appAdded", *app_info, *highlight);
281 }
282
283 break;
284 }
285 case chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED:
286 case chrome::NOTIFICATION_EXTENSION_UNINSTALLED_DEPRECATED: {
287 const Extension* extension = NULL;
288 bool uninstalled = false;
289 if (type == chrome::NOTIFICATION_EXTENSION_UNINSTALLED_DEPRECATED) {
290 extension = content::Details<const Extension>(details).ptr();
291 uninstalled = true;
292 } else { // NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED
293 if (content::Details<UnloadedExtensionInfo>(details)->reason ==
294 UnloadedExtensionInfo::REASON_UNINSTALL) {
295 // Uninstalls are tracked by
296 // NOTIFICATION_EXTENSION_UNINSTALLED_DEPRECATED.
297 return;
298 }
299 extension = content::Details<extensions::UnloadedExtensionInfo>(
300 details)->extension;
301 uninstalled = false;
302 }
303 if (!extension->is_app())
304 return;
305
306 if (!extensions::ui_util::ShouldDisplayInNewTabPage(
307 extension, Profile::FromWebUI(web_ui()))) {
308 return;
309 }
310
311 scoped_ptr<base::DictionaryValue> app_info(GetAppInfo(extension));
312 if (app_info.get()) {
313 if (uninstalled)
314 visible_apps_.erase(extension->id());
315
316 scoped_ptr<base::FundamentalValue> uninstall_value(
317 base::Value::CreateBooleanValue(uninstalled));
318 scoped_ptr<base::FundamentalValue> from_page(
319 base::Value::CreateBooleanValue(!extension_id_prompting_.empty()));
320 web_ui()->CallJavascriptFunction(
321 "ntp.appRemoved", *app_info, *uninstall_value, *from_page);
322 }
323 break;
324 }
325 case chrome::NOTIFICATION_EXTENSION_LAUNCHER_REORDERED: {
326 const std::string* id =
327 content::Details<const std::string>(details).ptr();
328 if (id) {
329 const Extension* extension =
330 extension_service_->GetInstalledExtension(*id);
331 if (!extension) {
332 // Extension could still be downloading or installing.
333 return;
334 }
335
336 base::DictionaryValue app_info;
337 CreateAppInfo(extension,
338 extension_service_,
339 &app_info);
340 web_ui()->CallJavascriptFunction("ntp.appMoved", app_info);
341 } else {
342 HandleGetApps(NULL);
343 }
344 break;
345 }
346 case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: {
347 CrxInstaller* crx_installer = content::Source<CrxInstaller>(source).ptr();
348 if (!Profile::FromWebUI(web_ui())->IsSameProfile(
349 crx_installer->profile())) {
350 return;
351 }
352 // Fall through.
353 }
354 case chrome::NOTIFICATION_EXTENSION_LOAD_ERROR: {
355 attempted_bookmark_app_install_ = false;
356 break;
357 }
358 default:
359 NOTREACHED();
360 }
361 }
362
FillAppDictionary(base::DictionaryValue * dictionary)363 void AppLauncherHandler::FillAppDictionary(base::DictionaryValue* dictionary) {
364 // CreateAppInfo and ClearOrdinals can change the extension prefs.
365 base::AutoReset<bool> auto_reset(&ignore_changes_, true);
366
367 base::ListValue* list = new base::ListValue();
368 Profile* profile = Profile::FromWebUI(web_ui());
369 PrefService* prefs = profile->GetPrefs();
370
371 for (std::set<std::string>::iterator it = visible_apps_.begin();
372 it != visible_apps_.end(); ++it) {
373 const Extension* extension = extension_service_->GetInstalledExtension(*it);
374 if (extension && extensions::ui_util::ShouldDisplayInNewTabPage(
375 extension, profile)) {
376 base::DictionaryValue* app_info = GetAppInfo(extension);
377 list->Append(app_info);
378 }
379 }
380
381 dictionary->Set("apps", list);
382
383 const base::ListValue* app_page_names =
384 prefs->GetList(prefs::kNtpAppPageNames);
385 if (!app_page_names || !app_page_names->GetSize()) {
386 ListPrefUpdate update(prefs, prefs::kNtpAppPageNames);
387 base::ListValue* list = update.Get();
388 list->Set(0, new base::StringValue(
389 l10n_util::GetStringUTF16(IDS_APP_DEFAULT_PAGE_NAME)));
390 dictionary->Set("appPageNames",
391 static_cast<base::ListValue*>(list->DeepCopy()));
392 } else {
393 dictionary->Set("appPageNames",
394 static_cast<base::ListValue*>(app_page_names->DeepCopy()));
395 }
396 }
397
GetAppInfo(const Extension * extension)398 base::DictionaryValue* AppLauncherHandler::GetAppInfo(
399 const Extension* extension) {
400 base::DictionaryValue* app_info = new base::DictionaryValue();
401 // CreateAppInfo can change the extension prefs.
402 base::AutoReset<bool> auto_reset(&ignore_changes_, true);
403 CreateAppInfo(extension,
404 extension_service_,
405 app_info);
406 return app_info;
407 }
408
HandleGetApps(const base::ListValue * args)409 void AppLauncherHandler::HandleGetApps(const base::ListValue* args) {
410 base::DictionaryValue dictionary;
411
412 // Tell the client whether to show the promo for this view. We don't do this
413 // in the case of PREF_CHANGED because:
414 //
415 // a) At that point in time, depending on the pref that changed, it can look
416 // like the set of apps installed has changed, and we will mark the promo
417 // expired.
418 // b) Conceptually, it doesn't really make sense to count a
419 // prefchange-triggered refresh as a promo 'view'.
420 Profile* profile = Profile::FromWebUI(web_ui());
421
422 // The first time we load the apps we must add all current app to the list
423 // of apps visible on the NTP.
424 if (!has_loaded_apps_) {
425 ExtensionRegistry* registry = ExtensionRegistry::Get(profile);
426 const ExtensionSet& enabled_set = registry->enabled_extensions();
427 for (extensions::ExtensionSet::const_iterator it = enabled_set.begin();
428 it != enabled_set.end(); ++it) {
429 visible_apps_.insert((*it)->id());
430 }
431
432 const ExtensionSet& disabled_set = registry->disabled_extensions();
433 for (ExtensionSet::const_iterator it = disabled_set.begin();
434 it != disabled_set.end(); ++it) {
435 visible_apps_.insert((*it)->id());
436 }
437
438 const ExtensionSet& terminated_set = registry->terminated_extensions();
439 for (ExtensionSet::const_iterator it = terminated_set.begin();
440 it != terminated_set.end(); ++it) {
441 visible_apps_.insert((*it)->id());
442 }
443 }
444
445 SetAppToBeHighlighted();
446 FillAppDictionary(&dictionary);
447 web_ui()->CallJavascriptFunction("ntp.getAppsCallback", dictionary);
448
449 // First time we get here we set up the observer so that we can tell update
450 // the apps as they change.
451 if (!has_loaded_apps_) {
452 base::Closure callback = base::Bind(
453 &AppLauncherHandler::OnExtensionPreferenceChanged,
454 base::Unretained(this));
455 extension_pref_change_registrar_.Init(
456 ExtensionPrefs::Get(profile)->pref_service());
457 extension_pref_change_registrar_.Add(
458 extensions::pref_names::kExtensions, callback);
459 extension_pref_change_registrar_.Add(prefs::kNtpAppPageNames, callback);
460
461 registrar_.Add(this,
462 chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
463 content::Source<Profile>(profile));
464 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
465 content::Source<Profile>(profile));
466 registrar_.Add(this,
467 chrome::NOTIFICATION_EXTENSION_UNINSTALLED_DEPRECATED,
468 content::Source<Profile>(profile));
469 registrar_.Add(this,
470 chrome::NOTIFICATION_EXTENSION_LAUNCHER_REORDERED,
471 content::Source<AppSorting>(
472 ExtensionPrefs::Get(profile)->app_sorting()));
473 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR,
474 content::Source<CrxInstaller>(NULL));
475 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOAD_ERROR,
476 content::Source<Profile>(profile));
477 }
478
479 has_loaded_apps_ = true;
480 }
481
HandleLaunchApp(const base::ListValue * args)482 void AppLauncherHandler::HandleLaunchApp(const base::ListValue* args) {
483 std::string extension_id;
484 CHECK(args->GetString(0, &extension_id));
485 double source = -1.0;
486 CHECK(args->GetDouble(1, &source));
487 std::string url;
488 if (args->GetSize() > 2)
489 CHECK(args->GetString(2, &url));
490
491 extension_misc::AppLaunchBucket launch_bucket =
492 static_cast<extension_misc::AppLaunchBucket>(
493 static_cast<int>(source));
494 CHECK(launch_bucket >= 0 &&
495 launch_bucket < extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
496
497 const Extension* extension =
498 extension_service_->GetExtensionById(extension_id, false);
499
500 // Prompt the user to re-enable the application if disabled.
501 if (!extension) {
502 PromptToEnableApp(extension_id);
503 return;
504 }
505
506 Profile* profile = extension_service_->profile();
507
508 WindowOpenDisposition disposition = args->GetSize() > 3 ?
509 webui::GetDispositionFromClick(args, 3) : CURRENT_TAB;
510 if (extension_id != extension_misc::kWebStoreAppId) {
511 CHECK_NE(launch_bucket, extension_misc::APP_LAUNCH_BUCKET_INVALID);
512 CoreAppLauncherHandler::RecordAppLaunchType(launch_bucket,
513 extension->GetType());
514 } else {
515 CoreAppLauncherHandler::RecordWebStoreLaunch();
516 }
517
518 if (disposition == NEW_FOREGROUND_TAB || disposition == NEW_BACKGROUND_TAB ||
519 disposition == NEW_WINDOW) {
520 // TODO(jamescook): Proper support for background tabs.
521 AppLaunchParams params(profile, extension,
522 disposition == NEW_WINDOW ?
523 extensions::LAUNCH_CONTAINER_WINDOW :
524 extensions::LAUNCH_CONTAINER_TAB,
525 disposition);
526 params.override_url = GURL(url);
527 OpenApplication(params);
528 } else {
529 // To give a more "launchy" experience when using the NTP launcher, we close
530 // it automatically.
531 Browser* browser = chrome::FindBrowserWithWebContents(
532 web_ui()->GetWebContents());
533 WebContents* old_contents = NULL;
534 if (browser)
535 old_contents = browser->tab_strip_model()->GetActiveWebContents();
536
537 AppLaunchParams params(profile, extension,
538 old_contents ? CURRENT_TAB : NEW_FOREGROUND_TAB);
539 params.override_url = GURL(url);
540 WebContents* new_contents = OpenApplication(params);
541
542 // This will also destroy the handler, so do not perform any actions after.
543 if (new_contents != old_contents && browser &&
544 browser->tab_strip_model()->count() > 1) {
545 chrome::CloseWebContents(browser, old_contents, true);
546 }
547 }
548 }
549
HandleSetLaunchType(const base::ListValue * args)550 void AppLauncherHandler::HandleSetLaunchType(const base::ListValue* args) {
551 std::string extension_id;
552 double launch_type;
553 CHECK(args->GetString(0, &extension_id));
554 CHECK(args->GetDouble(1, &launch_type));
555
556 const Extension* extension =
557 extension_service_->GetExtensionById(extension_id, true);
558 if (!extension)
559 return;
560
561 // Don't update the page; it already knows about the launch type change.
562 base::AutoReset<bool> auto_reset(&ignore_changes_, true);
563
564 extensions::SetLaunchType(
565 extension_service_,
566 extension_id,
567 static_cast<extensions::LaunchType>(static_cast<int>(launch_type)));
568 }
569
HandleUninstallApp(const base::ListValue * args)570 void AppLauncherHandler::HandleUninstallApp(const base::ListValue* args) {
571 std::string extension_id;
572 CHECK(args->GetString(0, &extension_id));
573
574 const Extension* extension = extension_service_->GetInstalledExtension(
575 extension_id);
576 if (!extension)
577 return;
578
579 if (!extensions::ExtensionSystem::Get(extension_service_->profile())->
580 management_policy()->UserMayModifySettings(extension, NULL)) {
581 LOG(ERROR) << "Attempt to uninstall an extension that is non-usermanagable "
582 << "was made. Extension id : " << extension->id();
583 return;
584 }
585 if (!extension_id_prompting_.empty())
586 return; // Only one prompt at a time.
587
588 extension_id_prompting_ = extension_id;
589
590 bool dont_confirm = false;
591 if (args->GetBoolean(1, &dont_confirm) && dont_confirm) {
592 base::AutoReset<bool> auto_reset(&ignore_changes_, true);
593 ExtensionUninstallAccepted();
594 } else {
595 GetExtensionUninstallDialog()->ConfirmUninstall(extension);
596 }
597 }
598
HandleCreateAppShortcut(const base::ListValue * args)599 void AppLauncherHandler::HandleCreateAppShortcut(const base::ListValue* args) {
600 std::string extension_id;
601 CHECK(args->GetString(0, &extension_id));
602
603 const Extension* extension =
604 extension_service_->GetExtensionById(extension_id, true);
605 if (!extension)
606 return;
607
608 Browser* browser = chrome::FindBrowserWithWebContents(
609 web_ui()->GetWebContents());
610 chrome::ShowCreateChromeAppShortcutsDialog(
611 browser->window()->GetNativeWindow(), browser->profile(), extension,
612 base::Callback<void(bool)>());
613 }
614
HandleReorderApps(const base::ListValue * args)615 void AppLauncherHandler::HandleReorderApps(const base::ListValue* args) {
616 CHECK(args->GetSize() == 2);
617
618 std::string dragged_app_id;
619 const base::ListValue* app_order;
620 CHECK(args->GetString(0, &dragged_app_id));
621 CHECK(args->GetList(1, &app_order));
622
623 std::string predecessor_to_moved_ext;
624 std::string successor_to_moved_ext;
625 for (size_t i = 0; i < app_order->GetSize(); ++i) {
626 std::string value;
627 if (app_order->GetString(i, &value) && value == dragged_app_id) {
628 if (i > 0)
629 CHECK(app_order->GetString(i - 1, &predecessor_to_moved_ext));
630 if (i + 1 < app_order->GetSize())
631 CHECK(app_order->GetString(i + 1, &successor_to_moved_ext));
632 break;
633 }
634 }
635
636 // Don't update the page; it already knows the apps have been reordered.
637 base::AutoReset<bool> auto_reset(&ignore_changes_, true);
638 ExtensionPrefs* extension_prefs =
639 ExtensionPrefs::Get(extension_service_->GetBrowserContext());
640 extension_prefs->SetAppDraggedByUser(dragged_app_id);
641 extension_prefs->app_sorting()->OnExtensionMoved(
642 dragged_app_id, predecessor_to_moved_ext, successor_to_moved_ext);
643 }
644
HandleSetPageIndex(const base::ListValue * args)645 void AppLauncherHandler::HandleSetPageIndex(const base::ListValue* args) {
646 AppSorting* app_sorting =
647 ExtensionPrefs::Get(extension_service_->profile())->app_sorting();
648
649 std::string extension_id;
650 double page_index;
651 CHECK(args->GetString(0, &extension_id));
652 CHECK(args->GetDouble(1, &page_index));
653 const syncer::StringOrdinal& page_ordinal =
654 app_sorting->PageIntegerAsStringOrdinal(static_cast<size_t>(page_index));
655
656 // Don't update the page; it already knows the apps have been reordered.
657 base::AutoReset<bool> auto_reset(&ignore_changes_, true);
658 app_sorting->SetPageOrdinal(extension_id, page_ordinal);
659 }
660
HandleSaveAppPageName(const base::ListValue * args)661 void AppLauncherHandler::HandleSaveAppPageName(const base::ListValue* args) {
662 base::string16 name;
663 CHECK(args->GetString(0, &name));
664
665 double page_index;
666 CHECK(args->GetDouble(1, &page_index));
667
668 base::AutoReset<bool> auto_reset(&ignore_changes_, true);
669 PrefService* prefs = Profile::FromWebUI(web_ui())->GetPrefs();
670 ListPrefUpdate update(prefs, prefs::kNtpAppPageNames);
671 base::ListValue* list = update.Get();
672 list->Set(static_cast<size_t>(page_index), new base::StringValue(name));
673 }
674
HandleGenerateAppForLink(const base::ListValue * args)675 void AppLauncherHandler::HandleGenerateAppForLink(const base::ListValue* args) {
676 std::string url;
677 CHECK(args->GetString(0, &url));
678 GURL launch_url(url);
679
680 base::string16 title;
681 CHECK(args->GetString(1, &title));
682
683 double page_index;
684 CHECK(args->GetDouble(2, &page_index));
685 AppSorting* app_sorting =
686 ExtensionPrefs::Get(extension_service_->profile())->app_sorting();
687 const syncer::StringOrdinal& page_ordinal =
688 app_sorting->PageIntegerAsStringOrdinal(static_cast<size_t>(page_index));
689
690 Profile* profile = Profile::FromWebUI(web_ui());
691 FaviconService* favicon_service =
692 FaviconServiceFactory::GetForProfile(profile, Profile::EXPLICIT_ACCESS);
693 if (!favicon_service) {
694 LOG(ERROR) << "No favicon service";
695 return;
696 }
697
698 scoped_ptr<AppInstallInfo> install_info(new AppInstallInfo());
699 install_info->title = title;
700 install_info->app_url = launch_url;
701 install_info->page_ordinal = page_ordinal;
702
703 favicon_service->GetFaviconImageForPageURL(
704 FaviconService::FaviconForPageURLParams(
705 launch_url, favicon_base::FAVICON, gfx::kFaviconSize),
706 base::Bind(&AppLauncherHandler::OnFaviconForApp,
707 base::Unretained(this),
708 base::Passed(&install_info)),
709 &cancelable_task_tracker_);
710 }
711
StopShowingAppLauncherPromo(const base::ListValue * args)712 void AppLauncherHandler::StopShowingAppLauncherPromo(
713 const base::ListValue* args) {
714 #if defined(ENABLE_APP_LIST)
715 g_browser_process->local_state()->SetBoolean(
716 prefs::kShowAppLauncherPromo, false);
717 RecordAppLauncherPromoHistogram(apps::APP_LAUNCHER_PROMO_DISMISSED);
718 #endif
719 }
720
OnLearnMore(const base::ListValue * args)721 void AppLauncherHandler::OnLearnMore(const base::ListValue* args) {
722 RecordAppLauncherPromoHistogram(apps::APP_LAUNCHER_PROMO_LEARN_MORE);
723 }
724
OnFaviconForApp(scoped_ptr<AppInstallInfo> install_info,const favicon_base::FaviconImageResult & image_result)725 void AppLauncherHandler::OnFaviconForApp(
726 scoped_ptr<AppInstallInfo> install_info,
727 const favicon_base::FaviconImageResult& image_result) {
728 scoped_ptr<WebApplicationInfo> web_app(new WebApplicationInfo());
729 web_app->title = install_info->title;
730 web_app->app_url = install_info->app_url;
731
732 if (!image_result.image.IsEmpty()) {
733 WebApplicationInfo::IconInfo icon;
734 icon.data = image_result.image.AsBitmap();
735 icon.width = icon.data.width();
736 icon.height = icon.data.height();
737 web_app->icons.push_back(icon);
738 }
739
740 scoped_refptr<CrxInstaller> installer(
741 CrxInstaller::CreateSilent(extension_service_));
742 installer->set_error_on_unsupported_requirements(true);
743 installer->set_page_ordinal(install_info->page_ordinal);
744 installer->InstallWebApp(*web_app);
745 attempted_bookmark_app_install_ = true;
746 }
747
SetAppToBeHighlighted()748 void AppLauncherHandler::SetAppToBeHighlighted() {
749 if (highlight_app_id_.empty())
750 return;
751
752 base::StringValue app_id(highlight_app_id_);
753 web_ui()->CallJavascriptFunction("ntp.setAppToBeHighlighted", app_id);
754 highlight_app_id_.clear();
755 }
756
OnExtensionPreferenceChanged()757 void AppLauncherHandler::OnExtensionPreferenceChanged() {
758 base::DictionaryValue dictionary;
759 FillAppDictionary(&dictionary);
760 web_ui()->CallJavascriptFunction("ntp.appsPrefChangeCallback", dictionary);
761 }
762
OnLocalStatePreferenceChanged()763 void AppLauncherHandler::OnLocalStatePreferenceChanged() {
764 #if defined(ENABLE_APP_LIST)
765 web_ui()->CallJavascriptFunction(
766 "ntp.appLauncherPromoPrefChangeCallback",
767 base::FundamentalValue(g_browser_process->local_state()->GetBoolean(
768 prefs::kShowAppLauncherPromo)));
769 #endif
770 }
771
CleanupAfterUninstall()772 void AppLauncherHandler::CleanupAfterUninstall() {
773 extension_id_prompting_.clear();
774 }
775
PromptToEnableApp(const std::string & extension_id)776 void AppLauncherHandler::PromptToEnableApp(const std::string& extension_id) {
777 if (!extension_id_prompting_.empty())
778 return; // Only one prompt at a time.
779
780 extension_id_prompting_ = extension_id;
781 extension_enable_flow_.reset(new ExtensionEnableFlow(
782 Profile::FromWebUI(web_ui()), extension_id, this));
783 extension_enable_flow_->StartForWebContents(web_ui()->GetWebContents());
784 }
785
ExtensionUninstallAccepted()786 void AppLauncherHandler::ExtensionUninstallAccepted() {
787 // Do the uninstall work here.
788 DCHECK(!extension_id_prompting_.empty());
789
790 // The extension can be uninstalled in another window while the UI was
791 // showing. Do nothing in that case.
792 const Extension* extension =
793 extension_service_->GetInstalledExtension(extension_id_prompting_);
794 if (!extension)
795 return;
796
797 extension_service_->UninstallExtension(extension_id_prompting_,
798 false /* external_uninstall */, NULL);
799 CleanupAfterUninstall();
800 }
801
ExtensionUninstallCanceled()802 void AppLauncherHandler::ExtensionUninstallCanceled() {
803 CleanupAfterUninstall();
804 }
805
ExtensionEnableFlowFinished()806 void AppLauncherHandler::ExtensionEnableFlowFinished() {
807 DCHECK_EQ(extension_id_prompting_, extension_enable_flow_->extension_id());
808
809 // We bounce this off the NTP so the browser can update the apps icon.
810 // If we don't launch the app asynchronously, then the app's disabled
811 // icon disappears but isn't replaced by the enabled icon, making a poor
812 // visual experience.
813 base::StringValue app_id(extension_id_prompting_);
814 web_ui()->CallJavascriptFunction("ntp.launchAppAfterEnable", app_id);
815
816 extension_enable_flow_.reset();
817 extension_id_prompting_ = "";
818 }
819
ExtensionEnableFlowAborted(bool user_initiated)820 void AppLauncherHandler::ExtensionEnableFlowAborted(bool user_initiated) {
821 DCHECK_EQ(extension_id_prompting_, extension_enable_flow_->extension_id());
822
823 // We record the histograms here because ExtensionUninstallCanceled is also
824 // called when the extension uninstall dialog is canceled.
825 const Extension* extension =
826 extension_service_->GetExtensionById(extension_id_prompting_, true);
827 std::string histogram_name = user_initiated
828 ? "Extensions.Permissions_ReEnableCancel2"
829 : "Extensions.Permissions_ReEnableAbort2";
830 ExtensionService::RecordPermissionMessagesHistogram(
831 extension, histogram_name.c_str());
832
833 extension_enable_flow_.reset();
834 CleanupAfterUninstall();
835 }
836
837 extensions::ExtensionUninstallDialog*
GetExtensionUninstallDialog()838 AppLauncherHandler::GetExtensionUninstallDialog() {
839 if (!extension_uninstall_dialog_.get()) {
840 Browser* browser = chrome::FindBrowserWithWebContents(
841 web_ui()->GetWebContents());
842 extension_uninstall_dialog_.reset(
843 extensions::ExtensionUninstallDialog::Create(
844 extension_service_->profile(), browser, this));
845 }
846 return extension_uninstall_dialog_.get();
847 }
848