• 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_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