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