Home | History | Annotate | Download | only in ntp
      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 
     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 
     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.
     88 void NoOpCallback(const ListValue* args) {}
     89 
     90 }  // namespace
     91 
     92 AppLauncherHandler::AppInstallInfo::AppInstallInfo() {}
     93 
     94 AppLauncherHandler::AppInstallInfo::~AppInstallInfo() {}
     95 
     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 
    107 AppLauncherHandler::~AppLauncherHandler() {}
    108 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    721 void AppLauncherHandler::OnLearnMore(const base::ListValue* args) {
    722   RecordAppLauncherPromoHistogram(apps::APP_LAUNCHER_PROMO_LEARN_MORE);
    723 }
    724 
    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 
    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 
    759 void AppLauncherHandler::OnExtensionPreferenceChanged() {
    760   DictionaryValue dictionary;
    761   FillAppDictionary(&dictionary);
    762   web_ui()->CallJavascriptFunction("ntp.appsPrefChangeCallback", dictionary);
    763 }
    764 
    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 
    774 void AppLauncherHandler::CleanupAfterUninstall() {
    775   extension_id_prompting_.clear();
    776 }
    777 
    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 
    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 
    804 void AppLauncherHandler::ExtensionUninstallCanceled() {
    805   CleanupAfterUninstall();
    806 }
    807 
    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 
    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 
    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