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