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/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"
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
;
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
) {}
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.
121 // Communicate the kiosk flag so the apps page can disable showing the
122 // context menu in kiosk mode.
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(
134 extensions::AppLaunchInfo::GetFullLaunchURL(extension
));
136 base::string16 name
= base::UTF8ToUTF16(extension
->name());
137 base::i18n::UnadjustStringForLocaleDirection(&name
);
138 NewTabUI::SetFullNameAndDirection(name
, value
);
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(
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(
155 extension_misc::EXTENSION_ICON_LARGE
,
156 ExtensionIconSet::MATCH_BIGGER
,
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(
164 extension_misc::EXTENSION_ICON_BITTY
,
165 ExtensionIconSet::MATCH_BIGGER
,
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
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)));
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();
269 if (ignore_changes_
|| !has_loaded_apps_
)
273 case chrome::NOTIFICATION_APP_LAUNCHER_REORDERED
: {
274 const std::string
* id
=
275 content::Details
<const std::string
>(details
).ptr();
277 const Extension
* extension
=
278 extension_service_
->GetInstalledExtension(*id
);
280 // Extension could still be downloading or installing.
284 base::DictionaryValue app_info
;
285 CreateAppInfo(extension
,
288 web_ui()->CallJavascriptFunction("ntp.appMoved", app_info
);
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())) {
302 case extensions::NOTIFICATION_EXTENSION_LOAD_ERROR
: {
303 attempted_bookmark_app_install_
= false;
311 void AppLauncherHandler::OnExtensionLoaded(
312 content::BrowserContext
* browser_context
,
313 const Extension
* extension
) {
314 if (!ShouldShow(extension
))
317 scoped_ptr
<base::DictionaryValue
> app_info(GetAppInfo(extension
));
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()));
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
,
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
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);
443 chrome::NOTIFICATION_APP_LAUNCHER_REORDERED
,
444 content::Source
<AppSorting
>(
445 ExtensionPrefs::Get(profile
)->app_sorting()));
447 extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR
,
448 content::Source
<CrxInstaller
>(NULL
));
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
));
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.
477 PromptToEnableApp(extension_id
);
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());
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
);
503 // To give a more "launchy" experience when using the NTP launcher, we close
505 Browser
* browser
= chrome::FindBrowserWithWebContents(
506 web_ui()->GetWebContents());
507 WebContents
* old_contents
= NULL
;
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
;
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);
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(
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();
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();
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);
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);
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
) {
625 if (app_order
->GetString(i
, &value
) && value
== dragged_app_id
) {
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
));
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
;
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
) {
661 CHECK(args
->GetString(0, &name
));
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
) {
675 CHECK(args
->GetString(0, &url
));
676 GURL
launch_url(url
);
678 base::string16 title
;
679 CHECK(args
->GetString(1, &title
));
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";
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(
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
);
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())
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
)));
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"
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(),
831 return extension_uninstall_dialog_
.get();
834 void AppLauncherHandler::AppRemoved(const Extension
* extension
,
836 if (!ShouldShow(extension
))
839 scoped_ptr
<base::DictionaryValue
> app_info(GetAppInfo(extension
));
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())
852 Profile
* profile
= Profile::FromWebUI(web_ui());
853 return extensions::ui_util::ShouldDisplayInNewTabPage(extension
, profile
);