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/apps/app_info_dialog.h"
30 #include "chrome/browser/ui/browser_dialogs.h"
31 #include "chrome/browser/ui/browser_finder.h"
32 #include "chrome/browser/ui/browser_tabstrip.h"
33 #include "chrome/browser/ui/browser_window.h"
34 #include "chrome/browser/ui/extensions/app_launch_params.h"
35 #include "chrome/browser/ui/extensions/application_launch.h"
36 #include "chrome/browser/ui/extensions/extension_enable_flow.h"
37 #include "chrome/browser/ui/tabs/tab_strip_model.h"
38 #include "chrome/browser/ui/webui/extensions/extension_basic_info.h"
39 #include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
40 #include "chrome/browser/ui/webui/ntp/new_tab_ui.h"
41 #include "chrome/common/extensions/extension_constants.h"
42 #include "chrome/common/extensions/extension_metrics.h"
43 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
44 #include "chrome/common/pref_names.h"
45 #include "chrome/common/url_constants.h"
46 #include "chrome/common/web_application_info.h"
47 #include "chrome/grit/generated_resources.h"
48 #include "components/favicon_base/favicon_types.h"
49 #include "content/public/browser/notification_service.h"
50 #include "content/public/browser/web_ui.h"
51 #include "content/public/common/favicon_url.h"
52 #include "extensions/browser/app_sorting.h"
53 #include "extensions/browser/extension_prefs.h"
54 #include "extensions/browser/extension_registry.h"
55 #include "extensions/browser/extension_system.h"
56 #include "extensions/browser/management_policy.h"
57 #include "extensions/browser/pref_names.h"
58 #include "extensions/browser/uninstall_reason.h"
59 #include "extensions/common/constants.h"
60 #include "extensions/common/extension.h"
61 #include "extensions/common/extension_icon_set.h"
62 #include "extensions/common/extension_set.h"
63 #include "ui/base/l10n/l10n_util.h"
64 #include "ui/base/webui/web_ui_util.h"
67 using content::WebContents
;
68 using extensions::AppSorting
;
69 using extensions::CrxInstaller
;
70 using extensions::Extension
;
71 using extensions::ExtensionPrefs
;
72 using extensions::ExtensionRegistry
;
73 using extensions::ExtensionSet
;
77 void RecordAppLauncherPromoHistogram(
78 apps::AppLauncherPromoHistogramValues value
) {
79 DCHECK_LT(value
, apps::APP_LAUNCHER_PROMO_MAX
);
80 UMA_HISTOGRAM_ENUMERATION(
81 "Apps.AppLauncherPromo", value
, apps::APP_LAUNCHER_PROMO_MAX
);
84 // This is used to avoid a DCHECK due to an unhandled WebUI callback. The
85 // JavaScript used to switch between pages sends "pageSelected" which is used
86 // in the context of the NTP for recording metrics we don't need here.
87 void NoOpCallback(const base::ListValue
* args
) {}
91 AppLauncherHandler::AppInstallInfo::AppInstallInfo() {}
93 AppLauncherHandler::AppInstallInfo::~AppInstallInfo() {}
95 AppLauncherHandler::AppLauncherHandler(ExtensionService
* extension_service
)
96 : extension_service_(extension_service
),
97 ignore_changes_(false),
98 attempted_bookmark_app_install_(false),
99 has_loaded_apps_(false) {
100 if (IsAppLauncherEnabled())
101 RecordAppLauncherPromoHistogram(apps::APP_LAUNCHER_PROMO_ALREADY_INSTALLED
);
102 else if (ShouldShowAppLauncherPromo())
103 RecordAppLauncherPromoHistogram(apps::APP_LAUNCHER_PROMO_SHOWN
);
106 AppLauncherHandler::~AppLauncherHandler() {
107 ExtensionRegistry::Get(Profile::FromWebUI(web_ui()))->RemoveObserver(this);
110 void AppLauncherHandler::CreateAppInfo(
111 const Extension
* extension
,
112 ExtensionService
* service
,
113 base::DictionaryValue
* value
) {
114 // The items which are to be written into |value| are also described in
115 // chrome/browser/resources/ntp4/page_list_view.js in @typedef for AppInfo.
116 // Please update it whenever you add or remove any keys here.
119 // The Extension class 'helpfully' wraps bidi control characters that
120 // impede our ability to determine directionality.
121 base::string16 short_name
= base::UTF8ToUTF16(extension
->short_name());
122 base::i18n::UnadjustStringForLocaleDirection(&short_name
);
123 NewTabUI::SetUrlTitleAndDirection(
126 extensions::AppLaunchInfo::GetFullLaunchURL(extension
));
128 base::string16 name
= base::UTF8ToUTF16(extension
->name());
129 base::i18n::UnadjustStringForLocaleDirection(&name
);
130 NewTabUI::SetFullNameAndDirection(name
, value
);
133 service
->IsExtensionEnabled(extension
->id()) &&
134 !extensions::ExtensionRegistry::Get(service
->GetBrowserContext())
135 ->GetExtensionById(extension
->id(),
136 extensions::ExtensionRegistry::TERMINATED
);
137 extensions::GetExtensionBasicInfo(extension
, enabled
, value
);
139 value
->SetBoolean("mayDisable", extensions::ExtensionSystem::Get(
140 service
->profile())->management_policy()->UserMayModifySettings(
143 bool icon_big_exists
= true;
144 // Instead of setting grayscale here, we do it in apps_page.js.
145 GURL icon_big
= extensions::ExtensionIconSource::GetIconURL(
147 extension_misc::EXTENSION_ICON_LARGE
,
148 ExtensionIconSet::MATCH_BIGGER
,
151 value
->SetString("icon_big", icon_big
.spec());
152 value
->SetBoolean("icon_big_exists", icon_big_exists
);
153 bool icon_small_exists
= true;
154 GURL icon_small
= extensions::ExtensionIconSource::GetIconURL(
156 extension_misc::EXTENSION_ICON_BITTY
,
157 ExtensionIconSet::MATCH_BIGGER
,
160 value
->SetString("icon_small", icon_small
.spec());
161 value
->SetBoolean("icon_small_exists", icon_small_exists
);
162 value
->SetInteger("launch_container",
163 extensions::AppLaunchInfo::GetLaunchContainer(extension
));
164 ExtensionPrefs
* prefs
= ExtensionPrefs::Get(service
->profile());
165 value
->SetInteger("launch_type", extensions::GetLaunchType(prefs
, extension
));
166 value
->SetBoolean("is_component",
167 extension
->location() == extensions::Manifest::COMPONENT
);
168 value
->SetBoolean("is_webstore",
169 extension
->id() == extensions::kWebStoreAppId
);
171 AppSorting
* sorting
= prefs
->app_sorting();
172 syncer::StringOrdinal page_ordinal
= sorting
->GetPageOrdinal(extension
->id());
173 if (!page_ordinal
.IsValid()) {
174 // Make sure every app has a page ordinal (some predate the page ordinal).
175 // The webstore app should be on the first page.
176 page_ordinal
= extension
->id() == extensions::kWebStoreAppId
?
177 sorting
->CreateFirstAppPageOrdinal() :
178 sorting
->GetNaturalAppPageOrdinal();
179 sorting
->SetPageOrdinal(extension
->id(), page_ordinal
);
181 value
->SetInteger("page_index",
182 sorting
->PageStringOrdinalAsInteger(page_ordinal
));
184 syncer::StringOrdinal app_launch_ordinal
=
185 sorting
->GetAppLaunchOrdinal(extension
->id());
186 if (!app_launch_ordinal
.IsValid()) {
187 // Make sure every app has a launch ordinal (some predate the launch
188 // ordinal). The webstore's app launch ordinal is always set to the first
190 app_launch_ordinal
= extension
->id() == extensions::kWebStoreAppId
?
191 sorting
->CreateFirstAppLaunchOrdinal(page_ordinal
) :
192 sorting
->CreateNextAppLaunchOrdinal(page_ordinal
);
193 sorting
->SetAppLaunchOrdinal(extension
->id(), app_launch_ordinal
);
195 value
->SetString("app_launch_ordinal", app_launch_ordinal
.ToInternalValue());
198 void AppLauncherHandler::RegisterMessages() {
199 registrar_
.Add(this, chrome::NOTIFICATION_APP_INSTALLED_TO_NTP
,
200 content::Source
<WebContents
>(web_ui()->GetWebContents()));
202 // Some tests don't have a local state.
203 #if defined(ENABLE_APP_LIST)
204 if (g_browser_process
->local_state()) {
205 local_state_pref_change_registrar_
.Init(g_browser_process
->local_state());
206 local_state_pref_change_registrar_
.Add(
207 prefs::kShowAppLauncherPromo
,
208 base::Bind(&AppLauncherHandler::OnLocalStatePreferenceChanged
,
209 base::Unretained(this)));
212 web_ui()->RegisterMessageCallback("getApps",
213 base::Bind(&AppLauncherHandler::HandleGetApps
,
214 base::Unretained(this)));
215 web_ui()->RegisterMessageCallback("launchApp",
216 base::Bind(&AppLauncherHandler::HandleLaunchApp
,
217 base::Unretained(this)));
218 web_ui()->RegisterMessageCallback("setLaunchType",
219 base::Bind(&AppLauncherHandler::HandleSetLaunchType
,
220 base::Unretained(this)));
221 web_ui()->RegisterMessageCallback("uninstallApp",
222 base::Bind(&AppLauncherHandler::HandleUninstallApp
,
223 base::Unretained(this)));
224 web_ui()->RegisterMessageCallback("createAppShortcut",
225 base::Bind(&AppLauncherHandler::HandleCreateAppShortcut
,
226 base::Unretained(this)));
227 web_ui()->RegisterMessageCallback("showAppInfo",
228 base::Bind(&AppLauncherHandler::HandleShowAppInfo
,
229 base::Unretained(this)));
230 web_ui()->RegisterMessageCallback("reorderApps",
231 base::Bind(&AppLauncherHandler::HandleReorderApps
,
232 base::Unretained(this)));
233 web_ui()->RegisterMessageCallback("setPageIndex",
234 base::Bind(&AppLauncherHandler::HandleSetPageIndex
,
235 base::Unretained(this)));
236 web_ui()->RegisterMessageCallback("saveAppPageName",
237 base::Bind(&AppLauncherHandler::HandleSaveAppPageName
,
238 base::Unretained(this)));
239 web_ui()->RegisterMessageCallback("generateAppForLink",
240 base::Bind(&AppLauncherHandler::HandleGenerateAppForLink
,
241 base::Unretained(this)));
242 web_ui()->RegisterMessageCallback("stopShowingAppLauncherPromo",
243 base::Bind(&AppLauncherHandler::StopShowingAppLauncherPromo
,
244 base::Unretained(this)));
245 web_ui()->RegisterMessageCallback("onLearnMore",
246 base::Bind(&AppLauncherHandler::OnLearnMore
,
247 base::Unretained(this)));
248 web_ui()->RegisterMessageCallback("pageSelected", base::Bind(&NoOpCallback
));
251 void AppLauncherHandler::Observe(int type
,
252 const content::NotificationSource
& source
,
253 const content::NotificationDetails
& details
) {
254 if (type
== chrome::NOTIFICATION_APP_INSTALLED_TO_NTP
) {
255 highlight_app_id_
= *content::Details
<const std::string
>(details
).ptr();
256 if (has_loaded_apps_
)
257 SetAppToBeHighlighted();
261 if (ignore_changes_
|| !has_loaded_apps_
)
265 case chrome::NOTIFICATION_APP_LAUNCHER_REORDERED
: {
266 const std::string
* id
=
267 content::Details
<const std::string
>(details
).ptr();
269 const Extension
* extension
=
270 extension_service_
->GetInstalledExtension(*id
);
272 // Extension could still be downloading or installing.
276 base::DictionaryValue app_info
;
277 CreateAppInfo(extension
,
280 web_ui()->CallJavascriptFunction("ntp.appMoved", app_info
);
286 case extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR
: {
287 CrxInstaller
* crx_installer
= content::Source
<CrxInstaller
>(source
).ptr();
288 if (!Profile::FromWebUI(web_ui())->IsSameProfile(
289 crx_installer
->profile())) {
294 case extensions::NOTIFICATION_EXTENSION_LOAD_ERROR
: {
295 attempted_bookmark_app_install_
= false;
303 void AppLauncherHandler::OnExtensionLoaded(
304 content::BrowserContext
* browser_context
,
305 const Extension
* extension
) {
306 if (!ShouldShow(extension
))
309 scoped_ptr
<base::DictionaryValue
> app_info(GetAppInfo(extension
));
313 visible_apps_
.insert(extension
->id());
314 ExtensionPrefs
* prefs
= ExtensionPrefs::Get(extension_service_
->profile());
315 base::FundamentalValue
highlight(prefs
->IsFromBookmark(extension
->id()) &&
316 attempted_bookmark_app_install_
);
317 attempted_bookmark_app_install_
= false;
318 web_ui()->CallJavascriptFunction("ntp.appAdded", *app_info
, highlight
);
321 void AppLauncherHandler::OnExtensionUnloaded(
322 content::BrowserContext
* browser_context
,
323 const Extension
* extension
,
324 extensions::UnloadedExtensionInfo::Reason reason
) {
325 AppRemoved(extension
, false);
328 void AppLauncherHandler::OnExtensionUninstalled(
329 content::BrowserContext
* browser_context
,
330 const Extension
* extension
,
331 extensions::UninstallReason reason
) {
332 AppRemoved(extension
, true);
335 void AppLauncherHandler::FillAppDictionary(base::DictionaryValue
* dictionary
) {
336 // CreateAppInfo and ClearOrdinals can change the extension prefs.
337 base::AutoReset
<bool> auto_reset(&ignore_changes_
, true);
339 base::ListValue
* list
= new base::ListValue();
340 Profile
* profile
= Profile::FromWebUI(web_ui());
341 PrefService
* prefs
= profile
->GetPrefs();
343 for (std::set
<std::string
>::iterator it
= visible_apps_
.begin();
344 it
!= visible_apps_
.end(); ++it
) {
345 const Extension
* extension
= extension_service_
->GetInstalledExtension(*it
);
346 if (extension
&& extensions::ui_util::ShouldDisplayInNewTabPage(
347 extension
, profile
)) {
348 base::DictionaryValue
* app_info
= GetAppInfo(extension
);
349 list
->Append(app_info
);
353 dictionary
->Set("apps", list
);
355 const base::ListValue
* app_page_names
=
356 prefs
->GetList(prefs::kNtpAppPageNames
);
357 if (!app_page_names
|| !app_page_names
->GetSize()) {
358 ListPrefUpdate
update(prefs
, prefs::kNtpAppPageNames
);
359 base::ListValue
* list
= update
.Get();
360 list
->Set(0, new base::StringValue(
361 l10n_util::GetStringUTF16(IDS_APP_DEFAULT_PAGE_NAME
)));
362 dictionary
->Set("appPageNames",
363 static_cast<base::ListValue
*>(list
->DeepCopy()));
365 dictionary
->Set("appPageNames",
366 static_cast<base::ListValue
*>(app_page_names
->DeepCopy()));
370 base::DictionaryValue
* AppLauncherHandler::GetAppInfo(
371 const Extension
* extension
) {
372 base::DictionaryValue
* app_info
= new base::DictionaryValue();
373 // CreateAppInfo can change the extension prefs.
374 base::AutoReset
<bool> auto_reset(&ignore_changes_
, true);
375 CreateAppInfo(extension
,
381 void AppLauncherHandler::HandleGetApps(const base::ListValue
* args
) {
382 base::DictionaryValue dictionary
;
384 // Tell the client whether to show the promo for this view. We don't do this
385 // in the case of PREF_CHANGED because:
387 // a) At that point in time, depending on the pref that changed, it can look
388 // like the set of apps installed has changed, and we will mark the promo
390 // b) Conceptually, it doesn't really make sense to count a
391 // prefchange-triggered refresh as a promo 'view'.
392 Profile
* profile
= Profile::FromWebUI(web_ui());
394 // The first time we load the apps we must add all current app to the list
395 // of apps visible on the NTP.
396 if (!has_loaded_apps_
) {
397 ExtensionRegistry
* registry
= ExtensionRegistry::Get(profile
);
398 const ExtensionSet
& enabled_set
= registry
->enabled_extensions();
399 for (extensions::ExtensionSet::const_iterator it
= enabled_set
.begin();
400 it
!= enabled_set
.end(); ++it
) {
401 visible_apps_
.insert((*it
)->id());
404 const ExtensionSet
& disabled_set
= registry
->disabled_extensions();
405 for (ExtensionSet::const_iterator it
= disabled_set
.begin();
406 it
!= disabled_set
.end(); ++it
) {
407 visible_apps_
.insert((*it
)->id());
410 const ExtensionSet
& terminated_set
= registry
->terminated_extensions();
411 for (ExtensionSet::const_iterator it
= terminated_set
.begin();
412 it
!= terminated_set
.end(); ++it
) {
413 visible_apps_
.insert((*it
)->id());
417 SetAppToBeHighlighted();
418 FillAppDictionary(&dictionary
);
419 web_ui()->CallJavascriptFunction("ntp.getAppsCallback", dictionary
);
421 // First time we get here we set up the observer so that we can tell update
422 // the apps as they change.
423 if (!has_loaded_apps_
) {
424 base::Closure callback
= base::Bind(
425 &AppLauncherHandler::OnExtensionPreferenceChanged
,
426 base::Unretained(this));
427 extension_pref_change_registrar_
.Init(
428 ExtensionPrefs::Get(profile
)->pref_service());
429 extension_pref_change_registrar_
.Add(
430 extensions::pref_names::kExtensions
, callback
);
431 extension_pref_change_registrar_
.Add(prefs::kNtpAppPageNames
, callback
);
433 ExtensionRegistry::Get(profile
)->AddObserver(this);
435 chrome::NOTIFICATION_APP_LAUNCHER_REORDERED
,
436 content::Source
<AppSorting
>(
437 ExtensionPrefs::Get(profile
)->app_sorting()));
439 extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR
,
440 content::Source
<CrxInstaller
>(NULL
));
442 extensions::NOTIFICATION_EXTENSION_LOAD_ERROR
,
443 content::Source
<Profile
>(profile
));
446 has_loaded_apps_
= true;
449 void AppLauncherHandler::HandleLaunchApp(const base::ListValue
* args
) {
450 std::string extension_id
;
451 CHECK(args
->GetString(0, &extension_id
));
452 double source
= -1.0;
453 CHECK(args
->GetDouble(1, &source
));
455 if (args
->GetSize() > 2)
456 CHECK(args
->GetString(2, &url
));
458 extension_misc::AppLaunchBucket launch_bucket
=
459 static_cast<extension_misc::AppLaunchBucket
>(
460 static_cast<int>(source
));
461 CHECK(launch_bucket
>= 0 &&
462 launch_bucket
< extension_misc::APP_LAUNCH_BUCKET_BOUNDARY
);
464 const Extension
* extension
=
465 extension_service_
->GetExtensionById(extension_id
, false);
467 // Prompt the user to re-enable the application if disabled.
469 PromptToEnableApp(extension_id
);
473 Profile
* profile
= extension_service_
->profile();
475 WindowOpenDisposition disposition
= args
->GetSize() > 3 ?
476 webui::GetDispositionFromClick(args
, 3) : CURRENT_TAB
;
477 if (extension_id
!= extensions::kWebStoreAppId
) {
478 CHECK_NE(launch_bucket
, extension_misc::APP_LAUNCH_BUCKET_INVALID
);
479 extensions::RecordAppLaunchType(launch_bucket
, extension
->GetType());
481 extensions::RecordWebStoreLaunch();
484 if (disposition
== NEW_FOREGROUND_TAB
|| disposition
== NEW_BACKGROUND_TAB
||
485 disposition
== NEW_WINDOW
) {
486 // TODO(jamescook): Proper support for background tabs.
487 AppLaunchParams
params(profile
, extension
,
488 disposition
== NEW_WINDOW
489 ? extensions::LAUNCH_CONTAINER_WINDOW
490 : extensions::LAUNCH_CONTAINER_TAB
,
491 disposition
, extensions::SOURCE_NEW_TAB_PAGE
);
492 params
.override_url
= GURL(url
);
493 OpenApplication(params
);
495 // To give a more "launchy" experience when using the NTP launcher, we close
497 Browser
* browser
= chrome::FindBrowserWithWebContents(
498 web_ui()->GetWebContents());
499 WebContents
* old_contents
= NULL
;
501 old_contents
= browser
->tab_strip_model()->GetActiveWebContents();
503 AppLaunchParams
params(profile
, extension
,
504 old_contents
? CURRENT_TAB
: NEW_FOREGROUND_TAB
,
505 extensions::SOURCE_NEW_TAB_PAGE
);
506 params
.override_url
= GURL(url
);
507 WebContents
* new_contents
= OpenApplication(params
);
509 // This will also destroy the handler, so do not perform any actions after.
510 if (new_contents
!= old_contents
&& browser
&&
511 browser
->tab_strip_model()->count() > 1) {
512 chrome::CloseWebContents(browser
, old_contents
, true);
517 void AppLauncherHandler::HandleSetLaunchType(const base::ListValue
* args
) {
518 std::string extension_id
;
520 CHECK(args
->GetString(0, &extension_id
));
521 CHECK(args
->GetDouble(1, &launch_type
));
523 const Extension
* extension
=
524 extension_service_
->GetExtensionById(extension_id
, true);
528 // Don't update the page; it already knows about the launch type change.
529 base::AutoReset
<bool> auto_reset(&ignore_changes_
, true);
531 extensions::SetLaunchType(
532 Profile::FromWebUI(web_ui()), extension_id
,
533 static_cast<extensions::LaunchType
>(static_cast<int>(launch_type
)));
536 void AppLauncherHandler::HandleUninstallApp(const base::ListValue
* args
) {
537 std::string extension_id
;
538 CHECK(args
->GetString(0, &extension_id
));
540 const Extension
* extension
= extension_service_
->GetInstalledExtension(
545 if (!extensions::ExtensionSystem::Get(extension_service_
->profile())->
546 management_policy()->UserMayModifySettings(extension
, NULL
)) {
547 LOG(ERROR
) << "Attempt to uninstall an extension that is non-usermanagable "
548 << "was made. Extension id : " << extension
->id();
551 if (!extension_id_prompting_
.empty())
552 return; // Only one prompt at a time.
554 extension_id_prompting_
= extension_id
;
556 bool dont_confirm
= false;
557 if (args
->GetBoolean(1, &dont_confirm
) && dont_confirm
) {
558 base::AutoReset
<bool> auto_reset(&ignore_changes_
, true);
559 // Do the uninstall work here.
560 extension_service_
->UninstallExtension(
561 extension_id_prompting_
, extensions::UNINSTALL_REASON_USER_INITIATED
,
562 base::Bind(&base::DoNothing
), nullptr);
563 CleanupAfterUninstall();
565 GetExtensionUninstallDialog()->ConfirmUninstall(
566 extension
, extensions::UNINSTALL_REASON_USER_INITIATED
);
570 void AppLauncherHandler::HandleCreateAppShortcut(const base::ListValue
* args
) {
571 std::string extension_id
;
572 CHECK(args
->GetString(0, &extension_id
));
574 const Extension
* extension
=
575 extension_service_
->GetExtensionById(extension_id
, true);
579 Browser
* browser
= chrome::FindBrowserWithWebContents(
580 web_ui()->GetWebContents());
581 chrome::ShowCreateChromeAppShortcutsDialog(
582 browser
->window()->GetNativeWindow(), browser
->profile(), extension
,
583 base::Callback
<void(bool)>());
586 void AppLauncherHandler::HandleShowAppInfo(const base::ListValue
* args
) {
587 std::string extension_id
;
588 CHECK(args
->GetString(0, &extension_id
));
590 const Extension
* extension
=
591 extension_service_
->GetExtensionById(extension_id
, true);
595 UMA_HISTOGRAM_ENUMERATION("Apps.AppInfoDialog.Launches",
596 AppInfoLaunchSource::FROM_APPS_PAGE
,
597 AppInfoLaunchSource::NUM_LAUNCH_SOURCES
);
599 ShowAppInfoInNativeDialog(
600 web_ui()->GetWebContents()->GetTopLevelNativeWindow(),
601 GetAppInfoNativeDialogSize(), Profile::FromWebUI(web_ui()), extension
,
605 void AppLauncherHandler::HandleReorderApps(const base::ListValue
* args
) {
606 CHECK(args
->GetSize() == 2);
608 std::string dragged_app_id
;
609 const base::ListValue
* app_order
;
610 CHECK(args
->GetString(0, &dragged_app_id
));
611 CHECK(args
->GetList(1, &app_order
));
613 std::string predecessor_to_moved_ext
;
614 std::string successor_to_moved_ext
;
615 for (size_t i
= 0; i
< app_order
->GetSize(); ++i
) {
617 if (app_order
->GetString(i
, &value
) && value
== dragged_app_id
) {
619 CHECK(app_order
->GetString(i
- 1, &predecessor_to_moved_ext
));
620 if (i
+ 1 < app_order
->GetSize())
621 CHECK(app_order
->GetString(i
+ 1, &successor_to_moved_ext
));
626 // Don't update the page; it already knows the apps have been reordered.
627 base::AutoReset
<bool> auto_reset(&ignore_changes_
, true);
628 ExtensionPrefs
* extension_prefs
=
629 ExtensionPrefs::Get(extension_service_
->GetBrowserContext());
630 extension_prefs
->SetAppDraggedByUser(dragged_app_id
);
631 extension_prefs
->app_sorting()->OnExtensionMoved(
632 dragged_app_id
, predecessor_to_moved_ext
, successor_to_moved_ext
);
635 void AppLauncherHandler::HandleSetPageIndex(const base::ListValue
* args
) {
636 AppSorting
* app_sorting
=
637 ExtensionPrefs::Get(extension_service_
->profile())->app_sorting();
639 std::string extension_id
;
641 CHECK(args
->GetString(0, &extension_id
));
642 CHECK(args
->GetDouble(1, &page_index
));
643 const syncer::StringOrdinal
& page_ordinal
=
644 app_sorting
->PageIntegerAsStringOrdinal(static_cast<size_t>(page_index
));
646 // Don't update the page; it already knows the apps have been reordered.
647 base::AutoReset
<bool> auto_reset(&ignore_changes_
, true);
648 app_sorting
->SetPageOrdinal(extension_id
, page_ordinal
);
651 void AppLauncherHandler::HandleSaveAppPageName(const base::ListValue
* args
) {
653 CHECK(args
->GetString(0, &name
));
656 CHECK(args
->GetDouble(1, &page_index
));
658 base::AutoReset
<bool> auto_reset(&ignore_changes_
, true);
659 PrefService
* prefs
= Profile::FromWebUI(web_ui())->GetPrefs();
660 ListPrefUpdate
update(prefs
, prefs::kNtpAppPageNames
);
661 base::ListValue
* list
= update
.Get();
662 list
->Set(static_cast<size_t>(page_index
), new base::StringValue(name
));
665 void AppLauncherHandler::HandleGenerateAppForLink(const base::ListValue
* args
) {
667 CHECK(args
->GetString(0, &url
));
668 GURL
launch_url(url
);
670 base::string16 title
;
671 CHECK(args
->GetString(1, &title
));
674 CHECK(args
->GetDouble(2, &page_index
));
675 AppSorting
* app_sorting
=
676 ExtensionPrefs::Get(extension_service_
->profile())->app_sorting();
677 const syncer::StringOrdinal
& page_ordinal
=
678 app_sorting
->PageIntegerAsStringOrdinal(static_cast<size_t>(page_index
));
680 Profile
* profile
= Profile::FromWebUI(web_ui());
681 favicon::FaviconService
* favicon_service
=
682 FaviconServiceFactory::GetForProfile(profile
,
683 ServiceAccessType::EXPLICIT_ACCESS
);
684 if (!favicon_service
) {
685 LOG(ERROR
) << "No favicon service";
689 scoped_ptr
<AppInstallInfo
> install_info(new AppInstallInfo());
690 install_info
->title
= title
;
691 install_info
->app_url
= launch_url
;
692 install_info
->page_ordinal
= page_ordinal
;
694 favicon_service
->GetFaviconImageForPageURL(
696 base::Bind(&AppLauncherHandler::OnFaviconForApp
,
697 base::Unretained(this),
698 base::Passed(&install_info
)),
699 &cancelable_task_tracker_
);
702 void AppLauncherHandler::StopShowingAppLauncherPromo(
703 const base::ListValue
* args
) {
704 #if defined(ENABLE_APP_LIST)
705 g_browser_process
->local_state()->SetBoolean(
706 prefs::kShowAppLauncherPromo
, false);
707 RecordAppLauncherPromoHistogram(apps::APP_LAUNCHER_PROMO_DISMISSED
);
711 void AppLauncherHandler::OnLearnMore(const base::ListValue
* args
) {
712 RecordAppLauncherPromoHistogram(apps::APP_LAUNCHER_PROMO_LEARN_MORE
);
715 void AppLauncherHandler::OnFaviconForApp(
716 scoped_ptr
<AppInstallInfo
> install_info
,
717 const favicon_base::FaviconImageResult
& image_result
) {
718 scoped_ptr
<WebApplicationInfo
> web_app(new WebApplicationInfo());
719 web_app
->title
= install_info
->title
;
720 web_app
->app_url
= install_info
->app_url
;
722 if (!image_result
.image
.IsEmpty()) {
723 WebApplicationInfo::IconInfo icon
;
724 icon
.data
= image_result
.image
.AsBitmap();
725 icon
.width
= icon
.data
.width();
726 icon
.height
= icon
.data
.height();
727 web_app
->icons
.push_back(icon
);
730 scoped_refptr
<CrxInstaller
> installer(
731 CrxInstaller::CreateSilent(extension_service_
));
732 installer
->set_error_on_unsupported_requirements(true);
733 installer
->set_page_ordinal(install_info
->page_ordinal
);
734 installer
->InstallWebApp(*web_app
);
735 attempted_bookmark_app_install_
= true;
738 void AppLauncherHandler::SetAppToBeHighlighted() {
739 if (highlight_app_id_
.empty())
742 base::StringValue
app_id(highlight_app_id_
);
743 web_ui()->CallJavascriptFunction("ntp.setAppToBeHighlighted", app_id
);
744 highlight_app_id_
.clear();
747 void AppLauncherHandler::OnExtensionPreferenceChanged() {
748 base::DictionaryValue dictionary
;
749 FillAppDictionary(&dictionary
);
750 web_ui()->CallJavascriptFunction("ntp.appsPrefChangeCallback", dictionary
);
753 void AppLauncherHandler::OnLocalStatePreferenceChanged() {
754 #if defined(ENABLE_APP_LIST)
755 web_ui()->CallJavascriptFunction(
756 "ntp.appLauncherPromoPrefChangeCallback",
757 base::FundamentalValue(g_browser_process
->local_state()->GetBoolean(
758 prefs::kShowAppLauncherPromo
)));
762 void AppLauncherHandler::CleanupAfterUninstall() {
763 extension_id_prompting_
.clear();
766 void AppLauncherHandler::PromptToEnableApp(const std::string
& extension_id
) {
767 if (!extension_id_prompting_
.empty())
768 return; // Only one prompt at a time.
770 extension_id_prompting_
= extension_id
;
771 extension_enable_flow_
.reset(new ExtensionEnableFlow(
772 Profile::FromWebUI(web_ui()), extension_id
, this));
773 extension_enable_flow_
->StartForWebContents(web_ui()->GetWebContents());
776 void AppLauncherHandler::OnExtensionUninstallDialogClosed(
777 bool did_start_uninstall
,
778 const base::string16
& error
) {
779 CleanupAfterUninstall();
782 void AppLauncherHandler::ExtensionEnableFlowFinished() {
783 DCHECK_EQ(extension_id_prompting_
, extension_enable_flow_
->extension_id());
785 // We bounce this off the NTP so the browser can update the apps icon.
786 // If we don't launch the app asynchronously, then the app's disabled
787 // icon disappears but isn't replaced by the enabled icon, making a poor
788 // visual experience.
789 base::StringValue
app_id(extension_id_prompting_
);
790 web_ui()->CallJavascriptFunction("ntp.launchAppAfterEnable", app_id
);
792 extension_enable_flow_
.reset();
793 extension_id_prompting_
= "";
796 void AppLauncherHandler::ExtensionEnableFlowAborted(bool user_initiated
) {
797 DCHECK_EQ(extension_id_prompting_
, extension_enable_flow_
->extension_id());
799 // We record the histograms here because ExtensionUninstallCanceled is also
800 // called when the extension uninstall dialog is canceled.
801 const Extension
* extension
=
802 extension_service_
->GetExtensionById(extension_id_prompting_
, true);
803 std::string histogram_name
= user_initiated
? "ReEnableCancel"
805 ExtensionService::RecordPermissionMessagesHistogram(
806 extension
, histogram_name
.c_str());
808 extension_enable_flow_
.reset();
809 CleanupAfterUninstall();
812 extensions::ExtensionUninstallDialog
*
813 AppLauncherHandler::GetExtensionUninstallDialog() {
814 if (!extension_uninstall_dialog_
.get()) {
815 Browser
* browser
= chrome::FindBrowserWithWebContents(
816 web_ui()->GetWebContents());
817 extension_uninstall_dialog_
.reset(
818 extensions::ExtensionUninstallDialog::Create(
819 extension_service_
->profile(),
820 browser
->window()->GetNativeWindow(),
823 return extension_uninstall_dialog_
.get();
826 void AppLauncherHandler::AppRemoved(const Extension
* extension
,
828 if (!ShouldShow(extension
))
831 scoped_ptr
<base::DictionaryValue
> app_info(GetAppInfo(extension
));
835 web_ui()->CallJavascriptFunction(
836 "ntp.appRemoved", *app_info
, base::FundamentalValue(is_uninstall
),
837 base::FundamentalValue(!extension_id_prompting_
.empty()));
840 bool AppLauncherHandler::ShouldShow(const Extension
* extension
) const {
841 if (ignore_changes_
|| !has_loaded_apps_
|| !extension
->is_app())
844 Profile
* profile
= Profile::FromWebUI(web_ui());
845 return extensions::ui_util::ShouldDisplayInNewTabPage(extension
, profile
);