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