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