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