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"
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 "chrome/grit/generated_resources.h"
46 #include "components/favicon_base/favicon_types.h"
47 #include "content/public/browser/notification_service.h"
48 #include "content/public/browser/web_ui.h"
49 #include "content/public/common/favicon_url.h"
50 #include "extensions/browser/app_sorting.h"
51 #include "extensions/browser/extension_prefs.h"
52 #include "extensions/browser/extension_registry.h"
53 #include "extensions/browser/extension_system.h"
54 #include "extensions/browser/management_policy.h"
55 #include "extensions/browser/pref_names.h"
56 #include "extensions/browser/uninstall_reason.h"
57 #include "extensions/common/constants.h"
58 #include "extensions/common/extension.h"
59 #include "extensions/common/extension_icon_set.h"
60 #include "extensions/common/extension_set.h"
61 #include "ui/base/l10n/l10n_util.h"
62 #include "ui/base/webui/web_ui_util.h"
65 using content::WebContents;
66 using extensions::AppSorting;
67 using extensions::CrxInstaller;
68 using extensions::Extension;
69 using extensions::ExtensionPrefs;
70 using extensions::ExtensionRegistry;
71 using extensions::ExtensionSet;
72 using extensions::UnloadedExtensionInfo;
76 void RecordAppLauncherPromoHistogram(
77 apps::AppLauncherPromoHistogramValues value) {
78 DCHECK_LT(value, apps::APP_LAUNCHER_PROMO_MAX);
79 UMA_HISTOGRAM_ENUMERATION(
80 "Apps.AppLauncherPromo", value, apps::APP_LAUNCHER_PROMO_MAX);
83 // This is used to avoid a DCHECK due to an unhandled WebUI callback. The
84 // JavaScript used to switch between pages sends "pageSelected" which is used
85 // in the context of the NTP for recording metrics we don't need here.
86 void NoOpCallback(const base::ListValue* args) {}
90 AppLauncherHandler::AppInstallInfo::AppInstallInfo() {}
92 AppLauncherHandler::AppInstallInfo::~AppInstallInfo() {}
94 AppLauncherHandler::AppLauncherHandler(ExtensionService* extension_service)
95 : extension_service_(extension_service),
96 ignore_changes_(false),
97 attempted_bookmark_app_install_(false),
98 has_loaded_apps_(false) {
99 if (IsAppLauncherEnabled())
100 RecordAppLauncherPromoHistogram(apps::APP_LAUNCHER_PROMO_ALREADY_INSTALLED);
101 else if (ShouldShowAppLauncherPromo())
102 RecordAppLauncherPromoHistogram(apps::APP_LAUNCHER_PROMO_SHOWN);
105 AppLauncherHandler::~AppLauncherHandler() {}
107 void AppLauncherHandler::CreateAppInfo(
108 const Extension* extension,
109 ExtensionService* service,
110 base::DictionaryValue* value) {
111 // The items which are to be written into |value| are also described in
112 // chrome/browser/resources/ntp4/page_list_view.js in @typedef for AppInfo.
113 // Please update it whenever you add or remove any keys here.
116 // The Extension class 'helpfully' wraps bidi control characters that
117 // impede our ability to determine directionality.
118 base::string16 short_name = base::UTF8ToUTF16(extension->short_name());
119 base::i18n::UnadjustStringForLocaleDirection(&short_name);
120 NewTabUI::SetUrlTitleAndDirection(
123 extensions::AppLaunchInfo::GetFullLaunchURL(extension));
125 base::string16 name = base::UTF8ToUTF16(extension->name());
126 base::i18n::UnadjustStringForLocaleDirection(&name);
127 NewTabUI::SetFullNameAndDirection(name, value);
130 service->IsExtensionEnabled(extension->id()) &&
131 !extensions::ExtensionRegistry::Get(service->GetBrowserContext())
132 ->GetExtensionById(extension->id(),
133 extensions::ExtensionRegistry::TERMINATED);
134 extensions::GetExtensionBasicInfo(extension, enabled, value);
136 value->SetBoolean("mayDisable", extensions::ExtensionSystem::Get(
137 service->profile())->management_policy()->UserMayModifySettings(
140 bool icon_big_exists = true;
141 // Instead of setting grayscale here, we do it in apps_page.js.
142 GURL icon_big = extensions::ExtensionIconSource::GetIconURL(
144 extension_misc::EXTENSION_ICON_LARGE,
145 ExtensionIconSet::MATCH_BIGGER,
148 value->SetString("icon_big", icon_big.spec());
149 value->SetBoolean("icon_big_exists", icon_big_exists);
150 bool icon_small_exists = true;
151 GURL icon_small = extensions::ExtensionIconSource::GetIconURL(
153 extension_misc::EXTENSION_ICON_BITTY,
154 ExtensionIconSet::MATCH_BIGGER,
157 value->SetString("icon_small", icon_small.spec());
158 value->SetBoolean("icon_small_exists", icon_small_exists);
159 value->SetInteger("launch_container",
160 extensions::AppLaunchInfo::GetLaunchContainer(extension));
161 ExtensionPrefs* prefs = ExtensionPrefs::Get(service->profile());
162 value->SetInteger("launch_type", extensions::GetLaunchType(prefs, extension));
163 value->SetBoolean("is_component",
164 extension->location() == extensions::Manifest::COMPONENT);
165 value->SetBoolean("is_webstore",
166 extension->id() == extensions::kWebStoreAppId);
168 AppSorting* sorting = prefs->app_sorting();
169 syncer::StringOrdinal page_ordinal = sorting->GetPageOrdinal(extension->id());
170 if (!page_ordinal.IsValid()) {
171 // Make sure every app has a page ordinal (some predate the page ordinal).
172 // The webstore app should be on the first page.
173 page_ordinal = extension->id() == extensions::kWebStoreAppId ?
174 sorting->CreateFirstAppPageOrdinal() :
175 sorting->GetNaturalAppPageOrdinal();
176 sorting->SetPageOrdinal(extension->id(), page_ordinal);
178 value->SetInteger("page_index",
179 sorting->PageStringOrdinalAsInteger(page_ordinal));
181 syncer::StringOrdinal app_launch_ordinal =
182 sorting->GetAppLaunchOrdinal(extension->id());
183 if (!app_launch_ordinal.IsValid()) {
184 // Make sure every app has a launch ordinal (some predate the launch
185 // ordinal). The webstore's app launch ordinal is always set to the first
187 app_launch_ordinal = extension->id() == extensions::kWebStoreAppId ?
188 sorting->CreateFirstAppLaunchOrdinal(page_ordinal) :
189 sorting->CreateNextAppLaunchOrdinal(page_ordinal);
190 sorting->SetAppLaunchOrdinal(extension->id(), app_launch_ordinal);
192 value->SetString("app_launch_ordinal", app_launch_ordinal.ToInternalValue());
195 void AppLauncherHandler::RegisterMessages() {
196 registrar_.Add(this, chrome::NOTIFICATION_APP_INSTALLED_TO_NTP,
197 content::Source<WebContents>(web_ui()->GetWebContents()));
199 // Some tests don't have a local state.
200 #if defined(ENABLE_APP_LIST)
201 if (g_browser_process->local_state()) {
202 local_state_pref_change_registrar_.Init(g_browser_process->local_state());
203 local_state_pref_change_registrar_.Add(
204 prefs::kShowAppLauncherPromo,
205 base::Bind(&AppLauncherHandler::OnLocalStatePreferenceChanged,
206 base::Unretained(this)));
209 web_ui()->RegisterMessageCallback("getApps",
210 base::Bind(&AppLauncherHandler::HandleGetApps,
211 base::Unretained(this)));
212 web_ui()->RegisterMessageCallback("launchApp",
213 base::Bind(&AppLauncherHandler::HandleLaunchApp,
214 base::Unretained(this)));
215 web_ui()->RegisterMessageCallback("setLaunchType",
216 base::Bind(&AppLauncherHandler::HandleSetLaunchType,
217 base::Unretained(this)));
218 web_ui()->RegisterMessageCallback("uninstallApp",
219 base::Bind(&AppLauncherHandler::HandleUninstallApp,
220 base::Unretained(this)));
221 web_ui()->RegisterMessageCallback("createAppShortcut",
222 base::Bind(&AppLauncherHandler::HandleCreateAppShortcut,
223 base::Unretained(this)));
224 web_ui()->RegisterMessageCallback("reorderApps",
225 base::Bind(&AppLauncherHandler::HandleReorderApps,
226 base::Unretained(this)));
227 web_ui()->RegisterMessageCallback("setPageIndex",
228 base::Bind(&AppLauncherHandler::HandleSetPageIndex,
229 base::Unretained(this)));
230 web_ui()->RegisterMessageCallback("saveAppPageName",
231 base::Bind(&AppLauncherHandler::HandleSaveAppPageName,
232 base::Unretained(this)));
233 web_ui()->RegisterMessageCallback("generateAppForLink",
234 base::Bind(&AppLauncherHandler::HandleGenerateAppForLink,
235 base::Unretained(this)));
236 web_ui()->RegisterMessageCallback("stopShowingAppLauncherPromo",
237 base::Bind(&AppLauncherHandler::StopShowingAppLauncherPromo,
238 base::Unretained(this)));
239 web_ui()->RegisterMessageCallback("onLearnMore",
240 base::Bind(&AppLauncherHandler::OnLearnMore,
241 base::Unretained(this)));
242 web_ui()->RegisterMessageCallback("pageSelected", base::Bind(&NoOpCallback));
245 void AppLauncherHandler::Observe(int type,
246 const content::NotificationSource& source,
247 const content::NotificationDetails& details) {
248 if (type == chrome::NOTIFICATION_APP_INSTALLED_TO_NTP) {
249 highlight_app_id_ = *content::Details<const std::string>(details).ptr();
250 if (has_loaded_apps_)
251 SetAppToBeHighlighted();
255 if (ignore_changes_ || !has_loaded_apps_)
259 case extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED: {
260 const Extension* extension =
261 content::Details<const Extension>(details).ptr();
262 if (!extension->is_app())
265 if (!extensions::ui_util::ShouldDisplayInNewTabPage(
266 extension, Profile::FromWebUI(web_ui()))) {
270 scoped_ptr<base::DictionaryValue> app_info(GetAppInfo(extension));
271 if (app_info.get()) {
272 visible_apps_.insert(extension->id());
274 ExtensionPrefs* prefs =
275 ExtensionPrefs::Get(extension_service_->profile());
276 base::FundamentalValue highlight(
277 prefs->IsFromBookmark(extension->id()) &&
278 attempted_bookmark_app_install_);
279 attempted_bookmark_app_install_ = false;
280 web_ui()->CallJavascriptFunction("ntp.appAdded", *app_info, highlight);
285 case extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED:
286 case extensions::NOTIFICATION_EXTENSION_UNINSTALLED_DEPRECATED: {
287 const Extension* extension = NULL;
288 bool uninstalled = false;
289 if (type == extensions::NOTIFICATION_EXTENSION_UNINSTALLED_DEPRECATED) {
290 extension = content::Details<const Extension>(details).ptr();
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.
299 extension = content::Details<extensions::UnloadedExtensionInfo>(
303 if (!extension->is_app())
306 if (!extensions::ui_util::ShouldDisplayInNewTabPage(
307 extension, Profile::FromWebUI(web_ui()))) {
311 scoped_ptr<base::DictionaryValue> app_info(GetAppInfo(extension));
312 if (app_info.get()) {
314 visible_apps_.erase(extension->id());
316 web_ui()->CallJavascriptFunction(
319 base::FundamentalValue(uninstalled),
320 base::FundamentalValue(!extension_id_prompting_.empty()));
324 case chrome::NOTIFICATION_APP_LAUNCHER_REORDERED: {
325 const std::string* id =
326 content::Details<const std::string>(details).ptr();
328 const Extension* extension =
329 extension_service_->GetInstalledExtension(*id);
331 // Extension could still be downloading or installing.
335 base::DictionaryValue app_info;
336 CreateAppInfo(extension,
339 web_ui()->CallJavascriptFunction("ntp.appMoved", app_info);
345 case extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR: {
346 CrxInstaller* crx_installer = content::Source<CrxInstaller>(source).ptr();
347 if (!Profile::FromWebUI(web_ui())->IsSameProfile(
348 crx_installer->profile())) {
353 case extensions::NOTIFICATION_EXTENSION_LOAD_ERROR: {
354 attempted_bookmark_app_install_ = false;
362 void AppLauncherHandler::FillAppDictionary(base::DictionaryValue* dictionary) {
363 // CreateAppInfo and ClearOrdinals can change the extension prefs.
364 base::AutoReset<bool> auto_reset(&ignore_changes_, true);
366 base::ListValue* list = new base::ListValue();
367 Profile* profile = Profile::FromWebUI(web_ui());
368 PrefService* prefs = profile->GetPrefs();
370 for (std::set<std::string>::iterator it = visible_apps_.begin();
371 it != visible_apps_.end(); ++it) {
372 const Extension* extension = extension_service_->GetInstalledExtension(*it);
373 if (extension && extensions::ui_util::ShouldDisplayInNewTabPage(
374 extension, profile)) {
375 base::DictionaryValue* app_info = GetAppInfo(extension);
376 list->Append(app_info);
380 dictionary->Set("apps", list);
382 const base::ListValue* app_page_names =
383 prefs->GetList(prefs::kNtpAppPageNames);
384 if (!app_page_names || !app_page_names->GetSize()) {
385 ListPrefUpdate update(prefs, prefs::kNtpAppPageNames);
386 base::ListValue* list = update.Get();
387 list->Set(0, new base::StringValue(
388 l10n_util::GetStringUTF16(IDS_APP_DEFAULT_PAGE_NAME)));
389 dictionary->Set("appPageNames",
390 static_cast<base::ListValue*>(list->DeepCopy()));
392 dictionary->Set("appPageNames",
393 static_cast<base::ListValue*>(app_page_names->DeepCopy()));
397 base::DictionaryValue* AppLauncherHandler::GetAppInfo(
398 const Extension* extension) {
399 base::DictionaryValue* app_info = new base::DictionaryValue();
400 // CreateAppInfo can change the extension prefs.
401 base::AutoReset<bool> auto_reset(&ignore_changes_, true);
402 CreateAppInfo(extension,
408 void AppLauncherHandler::HandleGetApps(const base::ListValue* args) {
409 base::DictionaryValue dictionary;
411 // Tell the client whether to show the promo for this view. We don't do this
412 // in the case of PREF_CHANGED because:
414 // a) At that point in time, depending on the pref that changed, it can look
415 // like the set of apps installed has changed, and we will mark the promo
417 // b) Conceptually, it doesn't really make sense to count a
418 // prefchange-triggered refresh as a promo 'view'.
419 Profile* profile = Profile::FromWebUI(web_ui());
421 // The first time we load the apps we must add all current app to the list
422 // of apps visible on the NTP.
423 if (!has_loaded_apps_) {
424 ExtensionRegistry* registry = ExtensionRegistry::Get(profile);
425 const ExtensionSet& enabled_set = registry->enabled_extensions();
426 for (extensions::ExtensionSet::const_iterator it = enabled_set.begin();
427 it != enabled_set.end(); ++it) {
428 visible_apps_.insert((*it)->id());
431 const ExtensionSet& disabled_set = registry->disabled_extensions();
432 for (ExtensionSet::const_iterator it = disabled_set.begin();
433 it != disabled_set.end(); ++it) {
434 visible_apps_.insert((*it)->id());
437 const ExtensionSet& terminated_set = registry->terminated_extensions();
438 for (ExtensionSet::const_iterator it = terminated_set.begin();
439 it != terminated_set.end(); ++it) {
440 visible_apps_.insert((*it)->id());
444 SetAppToBeHighlighted();
445 FillAppDictionary(&dictionary);
446 web_ui()->CallJavascriptFunction("ntp.getAppsCallback", dictionary);
448 // First time we get here we set up the observer so that we can tell update
449 // the apps as they change.
450 if (!has_loaded_apps_) {
451 base::Closure callback = base::Bind(
452 &AppLauncherHandler::OnExtensionPreferenceChanged,
453 base::Unretained(this));
454 extension_pref_change_registrar_.Init(
455 ExtensionPrefs::Get(profile)->pref_service());
456 extension_pref_change_registrar_.Add(
457 extensions::pref_names::kExtensions, callback);
458 extension_pref_change_registrar_.Add(prefs::kNtpAppPageNames, callback);
461 extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
462 content::Source<Profile>(profile));
464 extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
465 content::Source<Profile>(profile));
467 extensions::NOTIFICATION_EXTENSION_UNINSTALLED_DEPRECATED,
468 content::Source<Profile>(profile));
470 chrome::NOTIFICATION_APP_LAUNCHER_REORDERED,
471 content::Source<AppSorting>(
472 ExtensionPrefs::Get(profile)->app_sorting()));
474 extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR,
475 content::Source<CrxInstaller>(NULL));
477 extensions::NOTIFICATION_EXTENSION_LOAD_ERROR,
478 content::Source<Profile>(profile));
481 has_loaded_apps_ = true;
484 void AppLauncherHandler::HandleLaunchApp(const base::ListValue* args) {
485 std::string extension_id;
486 CHECK(args->GetString(0, &extension_id));
487 double source = -1.0;
488 CHECK(args->GetDouble(1, &source));
490 if (args->GetSize() > 2)
491 CHECK(args->GetString(2, &url));
493 extension_misc::AppLaunchBucket launch_bucket =
494 static_cast<extension_misc::AppLaunchBucket>(
495 static_cast<int>(source));
496 CHECK(launch_bucket >= 0 &&
497 launch_bucket < extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
499 const Extension* extension =
500 extension_service_->GetExtensionById(extension_id, false);
502 // Prompt the user to re-enable the application if disabled.
504 PromptToEnableApp(extension_id);
508 Profile* profile = extension_service_->profile();
510 WindowOpenDisposition disposition = args->GetSize() > 3 ?
511 webui::GetDispositionFromClick(args, 3) : CURRENT_TAB;
512 if (extension_id != extensions::kWebStoreAppId) {
513 CHECK_NE(launch_bucket, extension_misc::APP_LAUNCH_BUCKET_INVALID);
514 CoreAppLauncherHandler::RecordAppLaunchType(launch_bucket,
515 extension->GetType());
517 CoreAppLauncherHandler::RecordWebStoreLaunch();
520 if (disposition == NEW_FOREGROUND_TAB || disposition == NEW_BACKGROUND_TAB ||
521 disposition == NEW_WINDOW) {
522 // TODO(jamescook): Proper support for background tabs.
523 AppLaunchParams params(profile, extension,
524 disposition == NEW_WINDOW ?
525 extensions::LAUNCH_CONTAINER_WINDOW :
526 extensions::LAUNCH_CONTAINER_TAB,
528 params.override_url = GURL(url);
529 params.source = extensions::SOURCE_NEW_TAB_PAGE;
530 OpenApplication(params);
532 // To give a more "launchy" experience when using the NTP launcher, we close
534 Browser* browser = chrome::FindBrowserWithWebContents(
535 web_ui()->GetWebContents());
536 WebContents* old_contents = NULL;
538 old_contents = browser->tab_strip_model()->GetActiveWebContents();
540 AppLaunchParams params(profile, extension,
541 old_contents ? CURRENT_TAB : NEW_FOREGROUND_TAB);
542 params.override_url = GURL(url);
543 params.source = extensions::SOURCE_NEW_TAB_PAGE;
544 WebContents* new_contents = OpenApplication(params);
546 // This will also destroy the handler, so do not perform any actions after.
547 if (new_contents != old_contents && browser &&
548 browser->tab_strip_model()->count() > 1) {
549 chrome::CloseWebContents(browser, old_contents, true);
554 void AppLauncherHandler::HandleSetLaunchType(const base::ListValue* args) {
555 std::string extension_id;
557 CHECK(args->GetString(0, &extension_id));
558 CHECK(args->GetDouble(1, &launch_type));
560 const Extension* extension =
561 extension_service_->GetExtensionById(extension_id, true);
565 // Don't update the page; it already knows about the launch type change.
566 base::AutoReset<bool> auto_reset(&ignore_changes_, true);
568 extensions::SetLaunchType(
571 static_cast<extensions::LaunchType>(static_cast<int>(launch_type)));
574 void AppLauncherHandler::HandleUninstallApp(const base::ListValue* args) {
575 std::string extension_id;
576 CHECK(args->GetString(0, &extension_id));
578 const Extension* extension = extension_service_->GetInstalledExtension(
583 if (!extensions::ExtensionSystem::Get(extension_service_->profile())->
584 management_policy()->UserMayModifySettings(extension, NULL)) {
585 LOG(ERROR) << "Attempt to uninstall an extension that is non-usermanagable "
586 << "was made. Extension id : " << extension->id();
589 if (!extension_id_prompting_.empty())
590 return; // Only one prompt at a time.
592 extension_id_prompting_ = extension_id;
594 bool dont_confirm = false;
595 if (args->GetBoolean(1, &dont_confirm) && dont_confirm) {
596 base::AutoReset<bool> auto_reset(&ignore_changes_, true);
597 ExtensionUninstallAccepted();
599 GetExtensionUninstallDialog()->ConfirmUninstall(extension);
603 void AppLauncherHandler::HandleCreateAppShortcut(const base::ListValue* args) {
604 std::string extension_id;
605 CHECK(args->GetString(0, &extension_id));
607 const Extension* extension =
608 extension_service_->GetExtensionById(extension_id, true);
612 Browser* browser = chrome::FindBrowserWithWebContents(
613 web_ui()->GetWebContents());
614 chrome::ShowCreateChromeAppShortcutsDialog(
615 browser->window()->GetNativeWindow(), browser->profile(), extension,
616 base::Callback<void(bool)>());
619 void AppLauncherHandler::HandleReorderApps(const base::ListValue* args) {
620 CHECK(args->GetSize() == 2);
622 std::string dragged_app_id;
623 const base::ListValue* app_order;
624 CHECK(args->GetString(0, &dragged_app_id));
625 CHECK(args->GetList(1, &app_order));
627 std::string predecessor_to_moved_ext;
628 std::string successor_to_moved_ext;
629 for (size_t i = 0; i < app_order->GetSize(); ++i) {
631 if (app_order->GetString(i, &value) && value == dragged_app_id) {
633 CHECK(app_order->GetString(i - 1, &predecessor_to_moved_ext));
634 if (i + 1 < app_order->GetSize())
635 CHECK(app_order->GetString(i + 1, &successor_to_moved_ext));
640 // Don't update the page; it already knows the apps have been reordered.
641 base::AutoReset<bool> auto_reset(&ignore_changes_, true);
642 ExtensionPrefs* extension_prefs =
643 ExtensionPrefs::Get(extension_service_->GetBrowserContext());
644 extension_prefs->SetAppDraggedByUser(dragged_app_id);
645 extension_prefs->app_sorting()->OnExtensionMoved(
646 dragged_app_id, predecessor_to_moved_ext, successor_to_moved_ext);
649 void AppLauncherHandler::HandleSetPageIndex(const base::ListValue* args) {
650 AppSorting* app_sorting =
651 ExtensionPrefs::Get(extension_service_->profile())->app_sorting();
653 std::string extension_id;
655 CHECK(args->GetString(0, &extension_id));
656 CHECK(args->GetDouble(1, &page_index));
657 const syncer::StringOrdinal& page_ordinal =
658 app_sorting->PageIntegerAsStringOrdinal(static_cast<size_t>(page_index));
660 // Don't update the page; it already knows the apps have been reordered.
661 base::AutoReset<bool> auto_reset(&ignore_changes_, true);
662 app_sorting->SetPageOrdinal(extension_id, page_ordinal);
665 void AppLauncherHandler::HandleSaveAppPageName(const base::ListValue* args) {
667 CHECK(args->GetString(0, &name));
670 CHECK(args->GetDouble(1, &page_index));
672 base::AutoReset<bool> auto_reset(&ignore_changes_, true);
673 PrefService* prefs = Profile::FromWebUI(web_ui())->GetPrefs();
674 ListPrefUpdate update(prefs, prefs::kNtpAppPageNames);
675 base::ListValue* list = update.Get();
676 list->Set(static_cast<size_t>(page_index), new base::StringValue(name));
679 void AppLauncherHandler::HandleGenerateAppForLink(const base::ListValue* args) {
681 CHECK(args->GetString(0, &url));
682 GURL launch_url(url);
684 base::string16 title;
685 CHECK(args->GetString(1, &title));
688 CHECK(args->GetDouble(2, &page_index));
689 AppSorting* app_sorting =
690 ExtensionPrefs::Get(extension_service_->profile())->app_sorting();
691 const syncer::StringOrdinal& page_ordinal =
692 app_sorting->PageIntegerAsStringOrdinal(static_cast<size_t>(page_index));
694 Profile* profile = Profile::FromWebUI(web_ui());
695 FaviconService* favicon_service =
696 FaviconServiceFactory::GetForProfile(profile, Profile::EXPLICIT_ACCESS);
697 if (!favicon_service) {
698 LOG(ERROR) << "No favicon service";
702 scoped_ptr<AppInstallInfo> install_info(new AppInstallInfo());
703 install_info->title = title;
704 install_info->app_url = launch_url;
705 install_info->page_ordinal = page_ordinal;
707 favicon_service->GetFaviconImageForPageURL(
709 base::Bind(&AppLauncherHandler::OnFaviconForApp,
710 base::Unretained(this),
711 base::Passed(&install_info)),
712 &cancelable_task_tracker_);
715 void AppLauncherHandler::StopShowingAppLauncherPromo(
716 const base::ListValue* args) {
717 #if defined(ENABLE_APP_LIST)
718 g_browser_process->local_state()->SetBoolean(
719 prefs::kShowAppLauncherPromo, false);
720 RecordAppLauncherPromoHistogram(apps::APP_LAUNCHER_PROMO_DISMISSED);
724 void AppLauncherHandler::OnLearnMore(const base::ListValue* args) {
725 RecordAppLauncherPromoHistogram(apps::APP_LAUNCHER_PROMO_LEARN_MORE);
728 void AppLauncherHandler::OnFaviconForApp(
729 scoped_ptr<AppInstallInfo> install_info,
730 const favicon_base::FaviconImageResult& image_result) {
731 scoped_ptr<WebApplicationInfo> web_app(new WebApplicationInfo());
732 web_app->title = install_info->title;
733 web_app->app_url = install_info->app_url;
735 if (!image_result.image.IsEmpty()) {
736 WebApplicationInfo::IconInfo icon;
737 icon.data = image_result.image.AsBitmap();
738 icon.width = icon.data.width();
739 icon.height = icon.data.height();
740 web_app->icons.push_back(icon);
743 scoped_refptr<CrxInstaller> installer(
744 CrxInstaller::CreateSilent(extension_service_));
745 installer->set_error_on_unsupported_requirements(true);
746 installer->set_page_ordinal(install_info->page_ordinal);
747 installer->InstallWebApp(*web_app);
748 attempted_bookmark_app_install_ = true;
751 void AppLauncherHandler::SetAppToBeHighlighted() {
752 if (highlight_app_id_.empty())
755 base::StringValue app_id(highlight_app_id_);
756 web_ui()->CallJavascriptFunction("ntp.setAppToBeHighlighted", app_id);
757 highlight_app_id_.clear();
760 void AppLauncherHandler::OnExtensionPreferenceChanged() {
761 base::DictionaryValue dictionary;
762 FillAppDictionary(&dictionary);
763 web_ui()->CallJavascriptFunction("ntp.appsPrefChangeCallback", dictionary);
766 void AppLauncherHandler::OnLocalStatePreferenceChanged() {
767 #if defined(ENABLE_APP_LIST)
768 web_ui()->CallJavascriptFunction(
769 "ntp.appLauncherPromoPrefChangeCallback",
770 base::FundamentalValue(g_browser_process->local_state()->GetBoolean(
771 prefs::kShowAppLauncherPromo)));
775 void AppLauncherHandler::CleanupAfterUninstall() {
776 extension_id_prompting_.clear();
779 void AppLauncherHandler::PromptToEnableApp(const std::string& extension_id) {
780 if (!extension_id_prompting_.empty())
781 return; // Only one prompt at a time.
783 extension_id_prompting_ = extension_id;
784 extension_enable_flow_.reset(new ExtensionEnableFlow(
785 Profile::FromWebUI(web_ui()), extension_id, this));
786 extension_enable_flow_->StartForWebContents(web_ui()->GetWebContents());
789 void AppLauncherHandler::ExtensionUninstallAccepted() {
790 // Do the uninstall work here.
791 DCHECK(!extension_id_prompting_.empty());
793 // The extension can be uninstalled in another window while the UI was
794 // showing. Do nothing in that case.
795 const Extension* extension =
796 extension_service_->GetInstalledExtension(extension_id_prompting_);
800 extension_service_->UninstallExtension(
801 extension_id_prompting_,
802 extensions::UNINSTALL_REASON_USER_INITIATED,
803 base::Bind(&base::DoNothing),
805 CleanupAfterUninstall();
808 void AppLauncherHandler::ExtensionUninstallCanceled() {
809 CleanupAfterUninstall();
812 void AppLauncherHandler::ExtensionEnableFlowFinished() {
813 DCHECK_EQ(extension_id_prompting_, extension_enable_flow_->extension_id());
815 // We bounce this off the NTP so the browser can update the apps icon.
816 // If we don't launch the app asynchronously, then the app's disabled
817 // icon disappears but isn't replaced by the enabled icon, making a poor
818 // visual experience.
819 base::StringValue app_id(extension_id_prompting_);
820 web_ui()->CallJavascriptFunction("ntp.launchAppAfterEnable", app_id);
822 extension_enable_flow_.reset();
823 extension_id_prompting_ = "";
826 void AppLauncherHandler::ExtensionEnableFlowAborted(bool user_initiated) {
827 DCHECK_EQ(extension_id_prompting_, extension_enable_flow_->extension_id());
829 // We record the histograms here because ExtensionUninstallCanceled is also
830 // called when the extension uninstall dialog is canceled.
831 const Extension* extension =
832 extension_service_->GetExtensionById(extension_id_prompting_, true);
833 std::string histogram_name = user_initiated
834 ? "Extensions.Permissions_ReEnableCancel2"
835 : "Extensions.Permissions_ReEnableAbort2";
836 ExtensionService::RecordPermissionMessagesHistogram(
837 extension, histogram_name.c_str());
839 extension_enable_flow_.reset();
840 CleanupAfterUninstall();
843 extensions::ExtensionUninstallDialog*
844 AppLauncherHandler::GetExtensionUninstallDialog() {
845 if (!extension_uninstall_dialog_.get()) {
846 Browser* browser = chrome::FindBrowserWithWebContents(
847 web_ui()->GetWebContents());
848 extension_uninstall_dialog_.reset(
849 extensions::ExtensionUninstallDialog::Create(
850 extension_service_->profile(),
851 browser->window()->GetNativeWindow(),
854 return extension_uninstall_dialog_.get();