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