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_system.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/extension_icon_set.h"
42 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
43 #include "chrome/common/favicon/favicon_types.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 "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/management_policy.h"
52 #include "extensions/common/constants.h"
53 #include "extensions/common/extension.h"
54 #include "grit/browser_resources.h"
55 #include "grit/generated_resources.h"
56 #include "ui/base/l10n/l10n_util.h"
57 #include "ui/base/webui/web_ui_util.h"
58 #include "ui/gfx/favicon_size.h"
59 #include "url/gurl.h"
60
61 using content::WebContents;
62 using extensions::AppSorting;
63 using extensions::CrxInstaller;
64 using extensions::Extension;
65 using extensions::ExtensionPrefs;
66 using extensions::UnloadedExtensionInfo;
67
68 namespace {
69
ShouldDisplayInNewTabPage(const Extension * app,PrefService * prefs)70 bool ShouldDisplayInNewTabPage(const Extension* app, PrefService* prefs) {
71 bool blocked_by_policy =
72 (app->id() == extension_misc::kWebStoreAppId ||
73 app->id() == extension_misc::kEnterpriseWebStoreAppId) &&
74 prefs->GetBoolean(prefs::kHideWebStoreIcon);
75 return app->ShouldDisplayInNewTabPage() && !blocked_by_policy;
76 }
77
RecordAppLauncherPromoHistogram(apps::AppLauncherPromoHistogramValues value)78 void RecordAppLauncherPromoHistogram(
79 apps::AppLauncherPromoHistogramValues value) {
80 DCHECK_LT(value, apps::APP_LAUNCHER_PROMO_MAX);
81 UMA_HISTOGRAM_ENUMERATION(
82 "Apps.AppLauncherPromo", value, apps::APP_LAUNCHER_PROMO_MAX);
83 }
84
85 // This is used to avoid a DCHECK due to an unhandled WebUI callback. The
86 // JavaScript used to switch between pages sends "pageSelected" which is used
87 // in the context of the NTP for recording metrics we don't need here.
NoOpCallback(const ListValue * args)88 void NoOpCallback(const ListValue* args) {}
89
90 } // namespace
91
AppInstallInfo()92 AppLauncherHandler::AppInstallInfo::AppInstallInfo() {}
93
~AppInstallInfo()94 AppLauncherHandler::AppInstallInfo::~AppInstallInfo() {}
95
AppLauncherHandler(ExtensionService * extension_service)96 AppLauncherHandler::AppLauncherHandler(ExtensionService* extension_service)
97 : extension_service_(extension_service),
98 ignore_changes_(false),
99 attempted_bookmark_app_install_(false),
100 has_loaded_apps_(false) {
101 if (IsAppLauncherEnabled())
102 RecordAppLauncherPromoHistogram(apps::APP_LAUNCHER_PROMO_ALREADY_INSTALLED);
103 else if (ShouldShowAppLauncherPromo())
104 RecordAppLauncherPromoHistogram(apps::APP_LAUNCHER_PROMO_SHOWN);
105 }
106
~AppLauncherHandler()107 AppLauncherHandler::~AppLauncherHandler() {}
108
CreateAppInfo(const Extension * extension,ExtensionService * service,DictionaryValue * value)109 void AppLauncherHandler::CreateAppInfo(
110 const Extension* extension,
111 ExtensionService* service,
112 DictionaryValue* value) {
113 value->Clear();
114
115 // The Extension class 'helpfully' wraps bidi control characters that
116 // impede our ability to determine directionality.
117 base::string16 short_name = UTF8ToUTF16(extension->short_name());
118 base::i18n::UnadjustStringForLocaleDirection(&short_name);
119 NewTabUI::SetUrlTitleAndDirection(
120 value,
121 short_name,
122 extensions::AppLaunchInfo::GetFullLaunchURL(extension));
123
124 base::string16 name = UTF8ToUTF16(extension->name());
125 base::i18n::UnadjustStringForLocaleDirection(&name);
126 NewTabUI::SetFullNameAndDirection(name, value);
127
128 bool enabled = service->IsExtensionEnabled(extension->id()) &&
129 !service->GetTerminatedExtension(extension->id());
130 extensions::GetExtensionBasicInfo(extension, enabled, value);
131
132 value->SetBoolean("mayDisable", extensions::ExtensionSystem::Get(
133 service->profile())->management_policy()->UserMayModifySettings(
134 extension, NULL));
135
136 bool icon_big_exists = true;
137 // Instead of setting grayscale here, we do it in apps_page.js.
138 GURL icon_big = extensions::ExtensionIconSource::GetIconURL(
139 extension,
140 extension_misc::EXTENSION_ICON_LARGE,
141 ExtensionIconSet::MATCH_BIGGER,
142 false,
143 &icon_big_exists);
144 value->SetString("icon_big", icon_big.spec());
145 value->SetBoolean("icon_big_exists", icon_big_exists);
146 bool icon_small_exists = true;
147 GURL icon_small = extensions::ExtensionIconSource::GetIconURL(
148 extension,
149 extension_misc::EXTENSION_ICON_BITTY,
150 ExtensionIconSet::MATCH_BIGGER,
151 false,
152 &icon_small_exists);
153 value->SetString("icon_small", icon_small.spec());
154 value->SetBoolean("icon_small_exists", icon_small_exists);
155 value->SetInteger("launch_container",
156 extensions::AppLaunchInfo::GetLaunchContainer(extension));
157 ExtensionPrefs* prefs = service->extension_prefs();
158 value->SetInteger("launch_type", extensions::GetLaunchType(prefs, extension));
159 value->SetBoolean("is_component",
160 extension->location() == extensions::Manifest::COMPONENT);
161 value->SetBoolean("is_webstore",
162 extension->id() == extension_misc::kWebStoreAppId);
163
164 AppSorting* sorting = prefs->app_sorting();
165 syncer::StringOrdinal page_ordinal = sorting->GetPageOrdinal(extension->id());
166 if (!page_ordinal.IsValid()) {
167 // Make sure every app has a page ordinal (some predate the page ordinal).
168 // The webstore app should be on the first page.
169 page_ordinal = extension->id() == extension_misc::kWebStoreAppId ?
170 sorting->CreateFirstAppPageOrdinal() :
171 sorting->GetNaturalAppPageOrdinal();
172 sorting->SetPageOrdinal(extension->id(), page_ordinal);
173 }
174 value->SetInteger("page_index",
175 sorting->PageStringOrdinalAsInteger(page_ordinal));
176
177 syncer::StringOrdinal app_launch_ordinal =
178 sorting->GetAppLaunchOrdinal(extension->id());
179 if (!app_launch_ordinal.IsValid()) {
180 // Make sure every app has a launch ordinal (some predate the launch
181 // ordinal). The webstore's app launch ordinal is always set to the first
182 // position.
183 app_launch_ordinal = extension->id() == extension_misc::kWebStoreAppId ?
184 sorting->CreateFirstAppLaunchOrdinal(page_ordinal) :
185 sorting->CreateNextAppLaunchOrdinal(page_ordinal);
186 sorting->SetAppLaunchOrdinal(extension->id(), app_launch_ordinal);
187 }
188 value->SetString("app_launch_ordinal", app_launch_ordinal.ToInternalValue());
189 }
190
RegisterMessages()191 void AppLauncherHandler::RegisterMessages() {
192 registrar_.Add(this, chrome::NOTIFICATION_APP_INSTALLED_TO_NTP,
193 content::Source<WebContents>(web_ui()->GetWebContents()));
194
195 // Some tests don't have a local state.
196 #if defined(ENABLE_APP_LIST)
197 if (g_browser_process->local_state()) {
198 local_state_pref_change_registrar_.Init(g_browser_process->local_state());
199 local_state_pref_change_registrar_.Add(
200 prefs::kShowAppLauncherPromo,
201 base::Bind(&AppLauncherHandler::OnLocalStatePreferenceChanged,
202 base::Unretained(this)));
203 }
204 #endif
205 web_ui()->RegisterMessageCallback("getApps",
206 base::Bind(&AppLauncherHandler::HandleGetApps,
207 base::Unretained(this)));
208 web_ui()->RegisterMessageCallback("launchApp",
209 base::Bind(&AppLauncherHandler::HandleLaunchApp,
210 base::Unretained(this)));
211 web_ui()->RegisterMessageCallback("setLaunchType",
212 base::Bind(&AppLauncherHandler::HandleSetLaunchType,
213 base::Unretained(this)));
214 web_ui()->RegisterMessageCallback("uninstallApp",
215 base::Bind(&AppLauncherHandler::HandleUninstallApp,
216 base::Unretained(this)));
217 web_ui()->RegisterMessageCallback("createAppShortcut",
218 base::Bind(&AppLauncherHandler::HandleCreateAppShortcut,
219 base::Unretained(this)));
220 web_ui()->RegisterMessageCallback("reorderApps",
221 base::Bind(&AppLauncherHandler::HandleReorderApps,
222 base::Unretained(this)));
223 web_ui()->RegisterMessageCallback("setPageIndex",
224 base::Bind(&AppLauncherHandler::HandleSetPageIndex,
225 base::Unretained(this)));
226 web_ui()->RegisterMessageCallback("saveAppPageName",
227 base::Bind(&AppLauncherHandler::HandleSaveAppPageName,
228 base::Unretained(this)));
229 web_ui()->RegisterMessageCallback("generateAppForLink",
230 base::Bind(&AppLauncherHandler::HandleGenerateAppForLink,
231 base::Unretained(this)));
232 web_ui()->RegisterMessageCallback("stopShowingAppLauncherPromo",
233 base::Bind(&AppLauncherHandler::StopShowingAppLauncherPromo,
234 base::Unretained(this)));
235 web_ui()->RegisterMessageCallback("onLearnMore",
236 base::Bind(&AppLauncherHandler::OnLearnMore,
237 base::Unretained(this)));
238 web_ui()->RegisterMessageCallback("pageSelected", base::Bind(&NoOpCallback));
239 }
240
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)241 void AppLauncherHandler::Observe(int type,
242 const content::NotificationSource& source,
243 const content::NotificationDetails& details) {
244 if (type == chrome::NOTIFICATION_APP_INSTALLED_TO_NTP) {
245 highlight_app_id_ = *content::Details<const std::string>(details).ptr();
246 if (has_loaded_apps_)
247 SetAppToBeHighlighted();
248 return;
249 }
250
251 if (ignore_changes_ || !has_loaded_apps_)
252 return;
253
254 switch (type) {
255 case chrome::NOTIFICATION_EXTENSION_LOADED: {
256 const Extension* extension =
257 content::Details<const Extension>(details).ptr();
258 if (!extension->is_app())
259 return;
260
261 PrefService* prefs = Profile::FromWebUI(web_ui())->GetPrefs();
262 if (!ShouldDisplayInNewTabPage(extension, prefs))
263 return;
264
265 scoped_ptr<DictionaryValue> app_info(GetAppInfo(extension));
266 if (app_info.get()) {
267 visible_apps_.insert(extension->id());
268
269 ExtensionPrefs* prefs = extension_service_->extension_prefs();
270 scoped_ptr<base::FundamentalValue> highlight(Value::CreateBooleanValue(
271 prefs->IsFromBookmark(extension->id()) &&
272 attempted_bookmark_app_install_));
273 attempted_bookmark_app_install_ = false;
274 web_ui()->CallJavascriptFunction(
275 "ntp.appAdded", *app_info, *highlight);
276 }
277
278 break;
279 }
280 case chrome::NOTIFICATION_EXTENSION_UNLOADED:
281 case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: {
282 const Extension* extension = NULL;
283 bool uninstalled = false;
284 if (type == chrome::NOTIFICATION_EXTENSION_UNINSTALLED) {
285 extension = content::Details<const Extension>(details).ptr();
286 uninstalled = true;
287 } else { // NOTIFICATION_EXTENSION_UNLOADED
288 if (content::Details<UnloadedExtensionInfo>(details)->reason ==
289 UnloadedExtensionInfo::REASON_UNINSTALL) {
290 // Uninstalls are tracked by NOTIFICATION_EXTENSION_UNINSTALLED.
291 return;
292 }
293 extension = content::Details<extensions::UnloadedExtensionInfo>(
294 details)->extension;
295 uninstalled = false;
296 }
297 if (!extension->is_app())
298 return;
299
300 PrefService* prefs = Profile::FromWebUI(web_ui())->GetPrefs();
301 if (!ShouldDisplayInNewTabPage(extension, prefs))
302 return;
303
304 scoped_ptr<DictionaryValue> app_info(GetAppInfo(extension));
305 if (app_info.get()) {
306 if (uninstalled)
307 visible_apps_.erase(extension->id());
308
309 scoped_ptr<base::FundamentalValue> uninstall_value(
310 Value::CreateBooleanValue(uninstalled));
311 scoped_ptr<base::FundamentalValue> from_page(
312 Value::CreateBooleanValue(!extension_id_prompting_.empty()));
313 web_ui()->CallJavascriptFunction(
314 "ntp.appRemoved", *app_info, *uninstall_value, *from_page);
315 }
316 break;
317 }
318 case chrome::NOTIFICATION_EXTENSION_LAUNCHER_REORDERED: {
319 const std::string* id =
320 content::Details<const std::string>(details).ptr();
321 if (id) {
322 const Extension* extension =
323 extension_service_->GetInstalledExtension(*id);
324 if (!extension) {
325 // Extension could still be downloading or installing.
326 return;
327 }
328
329 DictionaryValue app_info;
330 CreateAppInfo(extension,
331 extension_service_,
332 &app_info);
333 web_ui()->CallJavascriptFunction("ntp.appMoved", app_info);
334 } else {
335 HandleGetApps(NULL);
336 }
337 break;
338 }
339 case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: {
340 CrxInstaller* crx_installer = content::Source<CrxInstaller>(source).ptr();
341 if (!Profile::FromWebUI(web_ui())->IsSameProfile(
342 crx_installer->profile())) {
343 return;
344 }
345 // Fall through.
346 }
347 case chrome::NOTIFICATION_EXTENSION_LOAD_ERROR: {
348 attempted_bookmark_app_install_ = false;
349 break;
350 }
351 default:
352 NOTREACHED();
353 }
354 }
355
FillAppDictionary(DictionaryValue * dictionary)356 void AppLauncherHandler::FillAppDictionary(DictionaryValue* dictionary) {
357 // CreateAppInfo and ClearOrdinals can change the extension prefs.
358 base::AutoReset<bool> auto_reset(&ignore_changes_, true);
359
360 ListValue* list = new ListValue();
361 PrefService* prefs = Profile::FromWebUI(web_ui())->GetPrefs();
362
363 for (std::set<std::string>::iterator it = visible_apps_.begin();
364 it != visible_apps_.end(); ++it) {
365 const Extension* extension = extension_service_->GetInstalledExtension(*it);
366 if (extension && ShouldDisplayInNewTabPage(extension, prefs)) {
367 DictionaryValue* app_info = GetAppInfo(extension);
368 list->Append(app_info);
369 }
370 }
371
372 dictionary->Set("apps", list);
373
374 // TODO(estade): remove these settings when the old NTP is removed. The new
375 // NTP does it in js.
376 #if defined(OS_MACOSX)
377 // App windows are not yet implemented on mac.
378 dictionary->SetBoolean("disableAppWindowLaunch", true);
379 dictionary->SetBoolean("disableCreateAppShortcut", true);
380 #endif
381
382 #if defined(OS_CHROMEOS)
383 // Making shortcut does not make sense on ChromeOS because it does not have
384 // a desktop.
385 dictionary->SetBoolean("disableCreateAppShortcut", true);
386 #endif
387
388 const ListValue* app_page_names = prefs->GetList(prefs::kNtpAppPageNames);
389 if (!app_page_names || !app_page_names->GetSize()) {
390 ListPrefUpdate update(prefs, prefs::kNtpAppPageNames);
391 ListValue* list = update.Get();
392 list->Set(0, new base::StringValue(
393 l10n_util::GetStringUTF16(IDS_APP_DEFAULT_PAGE_NAME)));
394 dictionary->Set("appPageNames",
395 static_cast<ListValue*>(list->DeepCopy()));
396 } else {
397 dictionary->Set("appPageNames",
398 static_cast<ListValue*>(app_page_names->DeepCopy()));
399 }
400 }
401
GetAppInfo(const Extension * extension)402 DictionaryValue* AppLauncherHandler::GetAppInfo(const Extension* extension) {
403 DictionaryValue* app_info = new DictionaryValue();
404 // CreateAppInfo can change the extension prefs.
405 base::AutoReset<bool> auto_reset(&ignore_changes_, true);
406 CreateAppInfo(extension,
407 extension_service_,
408 app_info);
409 return app_info;
410 }
411
HandleGetApps(const ListValue * args)412 void AppLauncherHandler::HandleGetApps(const ListValue* args) {
413 DictionaryValue dictionary;
414
415 // Tell the client whether to show the promo for this view. We don't do this
416 // in the case of PREF_CHANGED because:
417 //
418 // a) At that point in time, depending on the pref that changed, it can look
419 // like the set of apps installed has changed, and we will mark the promo
420 // expired.
421 // b) Conceptually, it doesn't really make sense to count a
422 // prefchange-triggered refresh as a promo 'view'.
423 Profile* profile = Profile::FromWebUI(web_ui());
424
425 // The first time we load the apps we must add all current app to the list
426 // of apps visible on the NTP.
427 if (!has_loaded_apps_) {
428 const ExtensionSet* extensions = extension_service_->extensions();
429 for (ExtensionSet::const_iterator it = extensions->begin();
430 it != extensions->end(); ++it) {
431 visible_apps_.insert((*it)->id());
432 }
433
434 extensions = extension_service_->disabled_extensions();
435 for (ExtensionSet::const_iterator it = extensions->begin();
436 it != extensions->end(); ++it) {
437 visible_apps_.insert((*it)->id());
438 }
439
440 extensions = extension_service_->terminated_extensions();
441 for (ExtensionSet::const_iterator it = extensions->begin();
442 it != extensions->end(); ++it) {
443 visible_apps_.insert((*it)->id());
444 }
445 }
446
447 SetAppToBeHighlighted();
448 FillAppDictionary(&dictionary);
449 web_ui()->CallJavascriptFunction("ntp.getAppsCallback", dictionary);
450
451 // First time we get here we set up the observer so that we can tell update
452 // the apps as they change.
453 if (!has_loaded_apps_) {
454 base::Closure callback = base::Bind(
455 &AppLauncherHandler::OnExtensionPreferenceChanged,
456 base::Unretained(this));
457 extension_pref_change_registrar_.Init(
458 extension_service_->extension_prefs()->pref_service());
459 extension_pref_change_registrar_.Add(prefs::kExtensionsPref, callback);
460 extension_pref_change_registrar_.Add(prefs::kNtpAppPageNames, callback);
461
462 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
463 content::Source<Profile>(profile));
464 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
465 content::Source<Profile>(profile));
466 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
467 content::Source<Profile>(profile));
468 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LAUNCHER_REORDERED,
469 content::Source<AppSorting>(
470 extension_service_->extension_prefs()->app_sorting()));
471 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR,
472 content::Source<CrxInstaller>(NULL));
473 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOAD_ERROR,
474 content::Source<Profile>(profile));
475 }
476
477 has_loaded_apps_ = true;
478 }
479
HandleLaunchApp(const ListValue * args)480 void AppLauncherHandler::HandleLaunchApp(const ListValue* args) {
481 std::string extension_id;
482 CHECK(args->GetString(0, &extension_id));
483 double source = -1.0;
484 CHECK(args->GetDouble(1, &source));
485 std::string url;
486 if (args->GetSize() > 2)
487 CHECK(args->GetString(2, &url));
488
489 extension_misc::AppLaunchBucket launch_bucket =
490 static_cast<extension_misc::AppLaunchBucket>(
491 static_cast<int>(source));
492 CHECK(launch_bucket >= 0 &&
493 launch_bucket < extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
494
495 const Extension* extension =
496 extension_service_->GetExtensionById(extension_id, false);
497
498 // Prompt the user to re-enable the application if disabled.
499 if (!extension) {
500 PromptToEnableApp(extension_id);
501 return;
502 }
503
504 Profile* profile = extension_service_->profile();
505
506 WindowOpenDisposition disposition = args->GetSize() > 3 ?
507 webui::GetDispositionFromClick(args, 3) : CURRENT_TAB;
508 if (extension_id != extension_misc::kWebStoreAppId) {
509 CHECK_NE(launch_bucket, extension_misc::APP_LAUNCH_BUCKET_INVALID);
510 CoreAppLauncherHandler::RecordAppLaunchType(launch_bucket,
511 extension->GetType());
512 } else {
513 CoreAppLauncherHandler::RecordWebStoreLaunch();
514 }
515
516 if (disposition == NEW_FOREGROUND_TAB || disposition == NEW_BACKGROUND_TAB ||
517 disposition == NEW_WINDOW) {
518 // TODO(jamescook): Proper support for background tabs.
519 AppLaunchParams params(profile, extension,
520 disposition == NEW_WINDOW ?
521 extensions::LAUNCH_CONTAINER_WINDOW :
522 extensions::LAUNCH_CONTAINER_TAB,
523 disposition);
524 params.override_url = GURL(url);
525 OpenApplication(params);
526 } else {
527 // To give a more "launchy" experience when using the NTP launcher, we close
528 // it automatically.
529 Browser* browser = chrome::FindBrowserWithWebContents(
530 web_ui()->GetWebContents());
531 WebContents* old_contents = NULL;
532 if (browser)
533 old_contents = browser->tab_strip_model()->GetActiveWebContents();
534
535 AppLaunchParams params(profile, extension,
536 old_contents ? CURRENT_TAB : NEW_FOREGROUND_TAB);
537 params.override_url = GURL(url);
538 WebContents* new_contents = OpenApplication(params);
539
540 // This will also destroy the handler, so do not perform any actions after.
541 if (new_contents != old_contents && browser &&
542 browser->tab_strip_model()->count() > 1) {
543 chrome::CloseWebContents(browser, old_contents, true);
544 }
545 }
546 }
547
HandleSetLaunchType(const ListValue * args)548 void AppLauncherHandler::HandleSetLaunchType(const ListValue* args) {
549 std::string extension_id;
550 double launch_type;
551 CHECK(args->GetString(0, &extension_id));
552 CHECK(args->GetDouble(1, &launch_type));
553
554 const Extension* extension =
555 extension_service_->GetExtensionById(extension_id, true);
556 if (!extension)
557 return;
558
559 // Don't update the page; it already knows about the launch type change.
560 base::AutoReset<bool> auto_reset(&ignore_changes_, true);
561
562 extensions::SetLaunchType(
563 extension_service_->extension_prefs(),
564 extension_id,
565 static_cast<extensions::LaunchType>(
566 static_cast<int>(launch_type)));
567 }
568
HandleUninstallApp(const ListValue * args)569 void AppLauncherHandler::HandleUninstallApp(const 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 ListValue * args)598 void AppLauncherHandler::HandleCreateAppShortcut(const 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::Closure());
612 }
613
HandleReorderApps(const ListValue * args)614 void AppLauncherHandler::HandleReorderApps(const ListValue* args) {
615 CHECK(args->GetSize() == 2);
616
617 std::string dragged_app_id;
618 const 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 extension_service_->extension_prefs()->SetAppDraggedByUser(dragged_app_id);
638 extension_service_->OnExtensionMoved(dragged_app_id,
639 predecessor_to_moved_ext,
640 successor_to_moved_ext);
641 }
642
HandleSetPageIndex(const ListValue * args)643 void AppLauncherHandler::HandleSetPageIndex(const ListValue* args) {
644 AppSorting* app_sorting =
645 extension_service_->extension_prefs()->app_sorting();
646
647 std::string extension_id;
648 double page_index;
649 CHECK(args->GetString(0, &extension_id));
650 CHECK(args->GetDouble(1, &page_index));
651 const syncer::StringOrdinal& page_ordinal =
652 app_sorting->PageIntegerAsStringOrdinal(static_cast<size_t>(page_index));
653
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);
657 }
658
HandleSaveAppPageName(const ListValue * args)659 void AppLauncherHandler::HandleSaveAppPageName(const ListValue* args) {
660 base::string16 name;
661 CHECK(args->GetString(0, &name));
662
663 double page_index;
664 CHECK(args->GetDouble(1, &page_index));
665
666 base::AutoReset<bool> auto_reset(&ignore_changes_, true);
667 PrefService* prefs = Profile::FromWebUI(web_ui())->GetPrefs();
668 ListPrefUpdate update(prefs, prefs::kNtpAppPageNames);
669 ListValue* list = update.Get();
670 list->Set(static_cast<size_t>(page_index), new base::StringValue(name));
671 }
672
HandleGenerateAppForLink(const ListValue * args)673 void AppLauncherHandler::HandleGenerateAppForLink(const ListValue* args) {
674 std::string url;
675 CHECK(args->GetString(0, &url));
676 GURL launch_url(url);
677
678 base::string16 title;
679 CHECK(args->GetString(1, &title));
680
681 double page_index;
682 CHECK(args->GetDouble(2, &page_index));
683 AppSorting* app_sorting =
684 extension_service_->extension_prefs()->app_sorting();
685 const syncer::StringOrdinal& page_ordinal =
686 app_sorting->PageIntegerAsStringOrdinal(static_cast<size_t>(page_index));
687
688 Profile* profile = Profile::FromWebUI(web_ui());
689 FaviconService* favicon_service =
690 FaviconServiceFactory::GetForProfile(profile, Profile::EXPLICIT_ACCESS);
691 if (!favicon_service) {
692 LOG(ERROR) << "No favicon service";
693 return;
694 }
695
696 scoped_ptr<AppInstallInfo> install_info(new AppInstallInfo());
697 install_info->is_bookmark_app = true;
698 install_info->title = title;
699 install_info->app_url = launch_url;
700 install_info->page_ordinal = page_ordinal;
701
702 favicon_service->GetFaviconImageForURL(
703 FaviconService::FaviconForURLParams(launch_url,
704 chrome::FAVICON,
705 gfx::kFaviconSize),
706 base::Bind(&AppLauncherHandler::OnFaviconForApp,
707 base::Unretained(this),
708 base::Passed(&install_info)),
709 &cancelable_task_tracker_);
710 }
711
StopShowingAppLauncherPromo(const base::ListValue * args)712 void AppLauncherHandler::StopShowingAppLauncherPromo(
713 const base::ListValue* args) {
714 #if defined(ENABLE_APP_LIST)
715 g_browser_process->local_state()->SetBoolean(
716 prefs::kShowAppLauncherPromo, false);
717 RecordAppLauncherPromoHistogram(apps::APP_LAUNCHER_PROMO_DISMISSED);
718 #endif
719 }
720
OnLearnMore(const base::ListValue * args)721 void AppLauncherHandler::OnLearnMore(const base::ListValue* args) {
722 RecordAppLauncherPromoHistogram(apps::APP_LAUNCHER_PROMO_LEARN_MORE);
723 }
724
OnFaviconForApp(scoped_ptr<AppInstallInfo> install_info,const chrome::FaviconImageResult & image_result)725 void AppLauncherHandler::OnFaviconForApp(
726 scoped_ptr<AppInstallInfo> install_info,
727 const chrome::FaviconImageResult& image_result) {
728 scoped_ptr<WebApplicationInfo> web_app(new WebApplicationInfo());
729 web_app->is_bookmark_app = install_info->is_bookmark_app;
730 web_app->title = install_info->title;
731 web_app->app_url = install_info->app_url;
732 web_app->urls.push_back(install_info->app_url);
733
734 if (!image_result.image.IsEmpty()) {
735 WebApplicationInfo::IconInfo icon;
736 icon.data = image_result.image.AsBitmap();
737 icon.width = icon.data.width();
738 icon.height = icon.data.height();
739 web_app->icons.push_back(icon);
740 }
741
742 scoped_refptr<CrxInstaller> installer(
743 CrxInstaller::CreateSilent(extension_service_));
744 installer->set_error_on_unsupported_requirements(true);
745 installer->set_page_ordinal(install_info->page_ordinal);
746 installer->InstallWebApp(*web_app);
747 attempted_bookmark_app_install_ = true;
748 }
749
SetAppToBeHighlighted()750 void AppLauncherHandler::SetAppToBeHighlighted() {
751 if (highlight_app_id_.empty())
752 return;
753
754 StringValue app_id(highlight_app_id_);
755 web_ui()->CallJavascriptFunction("ntp.setAppToBeHighlighted", app_id);
756 highlight_app_id_.clear();
757 }
758
OnExtensionPreferenceChanged()759 void AppLauncherHandler::OnExtensionPreferenceChanged() {
760 DictionaryValue dictionary;
761 FillAppDictionary(&dictionary);
762 web_ui()->CallJavascriptFunction("ntp.appsPrefChangeCallback", dictionary);
763 }
764
OnLocalStatePreferenceChanged()765 void AppLauncherHandler::OnLocalStatePreferenceChanged() {
766 #if defined(ENABLE_APP_LIST)
767 web_ui()->CallJavascriptFunction(
768 "ntp.appLauncherPromoPrefChangeCallback",
769 base::FundamentalValue(g_browser_process->local_state()->GetBoolean(
770 prefs::kShowAppLauncherPromo)));
771 #endif
772 }
773
CleanupAfterUninstall()774 void AppLauncherHandler::CleanupAfterUninstall() {
775 extension_id_prompting_.clear();
776 }
777
PromptToEnableApp(const std::string & extension_id)778 void AppLauncherHandler::PromptToEnableApp(const std::string& extension_id) {
779 if (!extension_id_prompting_.empty())
780 return; // Only one prompt at a time.
781
782 extension_id_prompting_ = extension_id;
783 extension_enable_flow_.reset(new ExtensionEnableFlow(
784 Profile::FromWebUI(web_ui()), extension_id, this));
785 extension_enable_flow_->StartForWebContents(web_ui()->GetWebContents());
786 }
787
ExtensionUninstallAccepted()788 void AppLauncherHandler::ExtensionUninstallAccepted() {
789 // Do the uninstall work here.
790 DCHECK(!extension_id_prompting_.empty());
791
792 // The extension can be uninstalled in another window while the UI was
793 // showing. Do nothing in that case.
794 const Extension* extension =
795 extension_service_->GetInstalledExtension(extension_id_prompting_);
796 if (!extension)
797 return;
798
799 extension_service_->UninstallExtension(extension_id_prompting_,
800 false /* external_uninstall */, NULL);
801 CleanupAfterUninstall();
802 }
803
ExtensionUninstallCanceled()804 void AppLauncherHandler::ExtensionUninstallCanceled() {
805 CleanupAfterUninstall();
806 }
807
ExtensionEnableFlowFinished()808 void AppLauncherHandler::ExtensionEnableFlowFinished() {
809 DCHECK_EQ(extension_id_prompting_, extension_enable_flow_->extension_id());
810
811 // We bounce this off the NTP so the browser can update the apps icon.
812 // If we don't launch the app asynchronously, then the app's disabled
813 // icon disappears but isn't replaced by the enabled icon, making a poor
814 // visual experience.
815 StringValue app_id(extension_id_prompting_);
816 web_ui()->CallJavascriptFunction("ntp.launchAppAfterEnable", app_id);
817
818 extension_enable_flow_.reset();
819 extension_id_prompting_ = "";
820 }
821
ExtensionEnableFlowAborted(bool user_initiated)822 void AppLauncherHandler::ExtensionEnableFlowAborted(bool user_initiated) {
823 DCHECK_EQ(extension_id_prompting_, extension_enable_flow_->extension_id());
824
825 // We record the histograms here because ExtensionUninstallCanceled is also
826 // called when the extension uninstall dialog is canceled.
827 const Extension* extension =
828 extension_service_->GetExtensionById(extension_id_prompting_, true);
829 std::string histogram_name = user_initiated ?
830 "Extensions.Permissions_ReEnableCancel" :
831 "Extensions.Permissions_ReEnableAbort";
832 ExtensionService::RecordPermissionMessagesHistogram(
833 extension, histogram_name.c_str());
834
835 extension_enable_flow_.reset();
836 CleanupAfterUninstall();
837 }
838
GetExtensionUninstallDialog()839 ExtensionUninstallDialog* AppLauncherHandler::GetExtensionUninstallDialog() {
840 if (!extension_uninstall_dialog_.get()) {
841 Browser* browser = chrome::FindBrowserWithWebContents(
842 web_ui()->GetWebContents());
843 extension_uninstall_dialog_.reset(
844 ExtensionUninstallDialog::Create(extension_service_->profile(),
845 browser, this));
846 }
847 return extension_uninstall_dialog_.get();
848 }
849