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