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