Home | History | Annotate | Download | only in webui
      1 // Copyright (c) 2011 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/app_launcher_handler.h"
      6 
      7 #include <string>
      8 #include <vector>
      9 
     10 #include "base/metrics/histogram.h"
     11 #include "base/string_number_conversions.h"
     12 #include "base/string_split.h"
     13 #include "base/string_util.h"
     14 #include "base/utf_string_conversions.h"
     15 #include "base/values.h"
     16 #include "chrome/browser/extensions/apps_promo.h"
     17 #include "chrome/browser/extensions/extension_prefs.h"
     18 #include "chrome/browser/extensions/extension_service.h"
     19 #include "chrome/browser/platform_util.h"
     20 #include "chrome/browser/profiles/profile.h"
     21 #include "chrome/browser/ui/browser.h"
     22 #include "chrome/browser/ui/browser_list.h"
     23 #include "chrome/browser/ui/browser_window.h"
     24 #include "chrome/browser/ui/webui/extension_icon_source.h"
     25 #include "chrome/browser/ui/webui/shown_sections_handler.h"
     26 #include "chrome/common/extensions/extension.h"
     27 #include "chrome/common/extensions/extension_constants.h"
     28 #include "chrome/common/extensions/extension_icon_set.h"
     29 #include "chrome/common/extensions/extension_resource.h"
     30 #include "chrome/common/url_constants.h"
     31 #include "content/browser/disposition_utils.h"
     32 #include "content/browser/tab_contents/tab_contents.h"
     33 #include "content/common/notification_service.h"
     34 #include "content/common/notification_type.h"
     35 #include "googleurl/src/gurl.h"
     36 #include "grit/browser_resources.h"
     37 #include "grit/generated_resources.h"
     38 #include "net/base/escape.h"
     39 #include "ui/base/animation/animation.h"
     40 #include "webkit/glue/window_open_disposition.h"
     41 
     42 namespace {
     43 
     44 // The URL prefixes used by the NTP to signal when the web store or an app
     45 // has launched so we can record the proper histogram.
     46 const char* kPingLaunchAppByID = "record-app-launch-by-id";
     47 const char* kPingLaunchWebStore = "record-webstore-launch";
     48 const char* kPingLaunchAppByURL = "record-app-launch-by-url";
     49 
     50 const UnescapeRule::Type kUnescapeRules =
     51     UnescapeRule::NORMAL | UnescapeRule::URL_SPECIAL_CHARS;
     52 
     53 extension_misc::AppLaunchBucket ParseLaunchSource(
     54     const std::string& launch_source) {
     55   int bucket_num = extension_misc::APP_LAUNCH_BUCKET_INVALID;
     56   base::StringToInt(launch_source, &bucket_num);
     57   extension_misc::AppLaunchBucket bucket =
     58       static_cast<extension_misc::AppLaunchBucket>(bucket_num);
     59   CHECK(bucket < extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
     60   return bucket;
     61 }
     62 
     63 }  // namespace
     64 
     65 AppLauncherHandler::AppLauncherHandler(ExtensionService* extension_service)
     66     : extensions_service_(extension_service),
     67       promo_active_(false),
     68       ignore_changes_(false) {
     69 }
     70 
     71 AppLauncherHandler::~AppLauncherHandler() {}
     72 
     73 // static
     74 void AppLauncherHandler::CreateAppInfo(const Extension* extension,
     75                                        ExtensionPrefs* prefs,
     76                                        DictionaryValue* value) {
     77   bool enabled =
     78       prefs->GetExtensionState(extension->id()) != Extension::DISABLED;
     79   GURL icon_big =
     80       ExtensionIconSource::GetIconURL(extension,
     81                                       Extension::EXTENSION_ICON_LARGE,
     82                                       ExtensionIconSet::MATCH_EXACTLY,
     83                                       !enabled);
     84   GURL icon_small =
     85       ExtensionIconSource::GetIconURL(extension,
     86                                       Extension::EXTENSION_ICON_BITTY,
     87                                       ExtensionIconSet::MATCH_BIGGER,
     88                                       !enabled);
     89 
     90   value->Clear();
     91   value->SetString("id", extension->id());
     92   value->SetString("name", extension->name());
     93   value->SetString("description", extension->description());
     94   value->SetString("launch_url", extension->GetFullLaunchURL().spec());
     95   value->SetString("options_url", extension->options_url().spec());
     96   value->SetBoolean("can_uninstall",
     97                     Extension::UserMayDisable(extension->location()));
     98   value->SetString("icon_big", icon_big.spec());
     99   value->SetString("icon_small", icon_small.spec());
    100   value->SetInteger("launch_container", extension->launch_container());
    101   value->SetInteger("launch_type",
    102       prefs->GetLaunchType(extension->id(),
    103                                      ExtensionPrefs::LAUNCH_DEFAULT));
    104 
    105   int app_launch_index = prefs->GetAppLaunchIndex(extension->id());
    106   if (app_launch_index == -1) {
    107     // Make sure every app has a launch index (some predate the launch index).
    108     app_launch_index = prefs->GetNextAppLaunchIndex();
    109     prefs->SetAppLaunchIndex(extension->id(), app_launch_index);
    110   }
    111   value->SetInteger("app_launch_index", app_launch_index);
    112 
    113   int page_index = prefs->GetPageIndex(extension->id());
    114   if (page_index >= 0) {
    115     // Only provide a value if one is stored
    116     value->SetInteger("page_index", page_index);
    117   }
    118 }
    119 
    120 // static
    121 bool AppLauncherHandler::HandlePing(Profile* profile, const std::string& path) {
    122   std::vector<std::string> params;
    123   base::SplitString(path, '+', &params);
    124 
    125   // Check if the user launched an app from the most visited or recently
    126   // closed sections.
    127   if (kPingLaunchAppByURL == params.at(0)) {
    128     CHECK(params.size() == 3);
    129     RecordAppLaunchByURL(
    130         profile, params.at(1), ParseLaunchSource(params.at(2)));
    131     return true;
    132   }
    133 
    134   bool is_web_store_ping = kPingLaunchWebStore == params.at(0);
    135   bool is_app_launch_ping = kPingLaunchAppByID == params.at(0);
    136 
    137   if (!is_web_store_ping && !is_app_launch_ping)
    138     return false;
    139 
    140   CHECK(params.size() >= 2);
    141 
    142   bool is_promo_active = params.at(1) == "true";
    143 
    144   // At this point, the user must have used the app launcher, so we hide the
    145   // promo if its still displayed.
    146   if (is_promo_active) {
    147     DCHECK(profile->GetExtensionService());
    148     profile->GetExtensionService()->apps_promo()->ExpireDefaultApps();
    149   }
    150 
    151   if (is_web_store_ping) {
    152     RecordWebStoreLaunch(is_promo_active);
    153   }  else {
    154     CHECK(params.size() == 3);
    155     RecordAppLaunchByID(is_promo_active, ParseLaunchSource(params.at(2)));
    156   }
    157 
    158   return true;
    159 }
    160 
    161 WebUIMessageHandler* AppLauncherHandler::Attach(WebUI* web_ui) {
    162   // TODO(arv): Add initialization code to the Apps store etc.
    163   return WebUIMessageHandler::Attach(web_ui);
    164 }
    165 
    166 void AppLauncherHandler::RegisterMessages() {
    167   web_ui_->RegisterMessageCallback("getApps",
    168       NewCallback(this, &AppLauncherHandler::HandleGetApps));
    169   web_ui_->RegisterMessageCallback("launchApp",
    170       NewCallback(this, &AppLauncherHandler::HandleLaunchApp));
    171   web_ui_->RegisterMessageCallback("setLaunchType",
    172       NewCallback(this, &AppLauncherHandler::HandleSetLaunchType));
    173   web_ui_->RegisterMessageCallback("uninstallApp",
    174       NewCallback(this, &AppLauncherHandler::HandleUninstallApp));
    175   web_ui_->RegisterMessageCallback("hideAppsPromo",
    176       NewCallback(this, &AppLauncherHandler::HandleHideAppsPromo));
    177   web_ui_->RegisterMessageCallback("createAppShortcut",
    178       NewCallback(this, &AppLauncherHandler::HandleCreateAppShortcut));
    179   web_ui_->RegisterMessageCallback("reorderApps",
    180       NewCallback(this, &AppLauncherHandler::HandleReorderApps));
    181   web_ui_->RegisterMessageCallback("setPageIndex",
    182       NewCallback(this, &AppLauncherHandler::HandleSetPageIndex));
    183   web_ui_->RegisterMessageCallback("promoSeen",
    184       NewCallback(this, &AppLauncherHandler::HandlePromoSeen));
    185 }
    186 
    187 void AppLauncherHandler::Observe(NotificationType type,
    188                                  const NotificationSource& source,
    189                                  const NotificationDetails& details) {
    190   if (ignore_changes_)
    191     return;
    192 
    193   switch (type.value) {
    194     case NotificationType::EXTENSION_LOADED:
    195     case NotificationType::EXTENSION_UNLOADED:
    196     case NotificationType::EXTENSION_LAUNCHER_REORDERED:
    197     // The promo may not load until a couple seconds after the first NTP view,
    198     // so we listen for the load notification and notify the NTP when ready.
    199     case NotificationType::WEB_STORE_PROMO_LOADED:
    200       if (web_ui_->tab_contents())
    201         HandleGetApps(NULL);
    202       break;
    203     case NotificationType::PREF_CHANGED: {
    204       if (!web_ui_->tab_contents())
    205         break;
    206 
    207       DictionaryValue dictionary;
    208       FillAppDictionary(&dictionary);
    209       web_ui_->CallJavascriptFunction("appsPrefChangeCallback", dictionary);
    210       break;
    211     }
    212     default:
    213       NOTREACHED();
    214   }
    215 }
    216 
    217 void AppLauncherHandler::FillAppDictionary(DictionaryValue* dictionary) {
    218   ListValue* list = new ListValue();
    219   const ExtensionList* extensions = extensions_service_->extensions();
    220   ExtensionList::const_iterator it;
    221   for (it = extensions->begin(); it != extensions->end(); ++it) {
    222     // Don't include the WebStore and other component apps.
    223     // The WebStore launcher gets special treatment in ntp/apps.js.
    224     if ((*it)->is_app() && (*it)->location() != Extension::COMPONENT) {
    225       DictionaryValue* app_info = new DictionaryValue();
    226       CreateAppInfo(*it, extensions_service_->extension_prefs(), app_info);
    227       list->Append(app_info);
    228     }
    229   }
    230 
    231   extensions = extensions_service_->disabled_extensions();
    232   for (it = extensions->begin(); it != extensions->end(); ++it) {
    233     if ((*it)->is_app() && (*it)->location() != Extension::COMPONENT) {
    234       DictionaryValue* app_info = new DictionaryValue();
    235       CreateAppInfo(*it, extensions_service_->extension_prefs(), app_info);
    236       list->Append(app_info);
    237     }
    238   }
    239 
    240   dictionary->Set("apps", list);
    241 
    242 #if defined(OS_MACOSX)
    243   // App windows are not yet implemented on mac.
    244   dictionary->SetBoolean("disableAppWindowLaunch", true);
    245   dictionary->SetBoolean("disableCreateAppShortcut", true);
    246 #endif
    247 
    248 #if defined(OS_CHROMEOS)
    249   // Making shortcut does not make sense on ChromeOS because it does not have
    250   // a desktop.
    251   dictionary->SetBoolean("disableCreateAppShortcut", true);
    252 #endif
    253 
    254   dictionary->SetBoolean(
    255       "showLauncher",
    256       extensions_service_->apps_promo()->ShouldShowAppLauncher(
    257           extensions_service_->GetAppIds()));
    258 }
    259 
    260 void AppLauncherHandler::FillPromoDictionary(DictionaryValue* dictionary) {
    261   dictionary->SetString("promoHeader", AppsPromo::GetPromoHeaderText());
    262   dictionary->SetString("promoButton", AppsPromo::GetPromoButtonText());
    263   dictionary->SetString("promoLink", AppsPromo::GetPromoLink().spec());
    264   dictionary->SetString("promoExpire", AppsPromo::GetPromoExpireText());
    265 }
    266 
    267 void AppLauncherHandler::HandleGetApps(const ListValue* args) {
    268   DictionaryValue dictionary;
    269 
    270   // Tell the client whether to show the promo for this view. We don't do this
    271   // in the case of PREF_CHANGED because:
    272   //
    273   // a) At that point in time, depending on the pref that changed, it can look
    274   //    like the set of apps installed has changed, and we will mark the promo
    275   //    expired.
    276   // b) Conceptually, it doesn't really make sense to count a
    277   //    prefchange-triggered refresh as a promo 'view'.
    278   AppsPromo* apps_promo = extensions_service_->apps_promo();
    279   PrefService* prefs = web_ui_->GetProfile()->GetPrefs();
    280   bool apps_promo_just_expired = false;
    281   if (apps_promo->ShouldShowPromo(extensions_service_->GetAppIds(),
    282                                   &apps_promo_just_expired)) {
    283     // Maximize the apps section on the first promo view.
    284     apps_promo->MaximizeAppsIfFirstView();
    285     dictionary.SetBoolean("showPromo", true);
    286     FillPromoDictionary(&dictionary);
    287     promo_active_ = true;
    288   } else {
    289     dictionary.SetBoolean("showPromo", false);
    290     promo_active_ = false;
    291   }
    292 
    293   // If the default apps have just expired (user viewed them too many times with
    294   // no interaction), then we uninstall them and focus the recent sites section.
    295   if (apps_promo_just_expired) {
    296     ignore_changes_ = true;
    297     UninstallDefaultApps();
    298     ignore_changes_ = false;
    299     ShownSectionsHandler::SetShownSection(prefs, THUMB);
    300   }
    301 
    302   FillAppDictionary(&dictionary);
    303   web_ui_->CallJavascriptFunction("getAppsCallback", dictionary);
    304 
    305   // First time we get here we set up the observer so that we can tell update
    306   // the apps as they change.
    307   if (registrar_.IsEmpty()) {
    308     registrar_.Add(this, NotificationType::EXTENSION_LOADED,
    309         NotificationService::AllSources());
    310     registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
    311         NotificationService::AllSources());
    312     registrar_.Add(this, NotificationType::EXTENSION_LAUNCHER_REORDERED,
    313         NotificationService::AllSources());
    314     registrar_.Add(this, NotificationType::WEB_STORE_PROMO_LOADED,
    315         NotificationService::AllSources());
    316   }
    317   if (pref_change_registrar_.IsEmpty()) {
    318     pref_change_registrar_.Init(
    319         extensions_service_->extension_prefs()->pref_service());
    320     pref_change_registrar_.Add(ExtensionPrefs::kExtensionsPref, this);
    321   }
    322 }
    323 
    324 void AppLauncherHandler::HandleLaunchApp(const ListValue* args) {
    325   std::string extension_id;
    326   double source = -1.0;
    327   bool alt_key = false;
    328   bool ctrl_key = false;
    329   bool meta_key = false;
    330   bool shift_key = false;
    331   double button = 0.0;
    332 
    333   CHECK(args->GetString(0, &extension_id));
    334   CHECK(args->GetDouble(1, &source));
    335   if (args->GetSize() > 2) {
    336       CHECK(args->GetBoolean(2, &alt_key));
    337       CHECK(args->GetBoolean(3, &ctrl_key));
    338       CHECK(args->GetBoolean(4, &meta_key));
    339       CHECK(args->GetBoolean(5, &shift_key));
    340       CHECK(args->GetDouble(6, &button));
    341   }
    342 
    343   extension_misc::AppLaunchBucket launch_bucket =
    344       static_cast<extension_misc::AppLaunchBucket>(
    345           static_cast<int>(source));
    346   CHECK(launch_bucket >= 0 &&
    347         launch_bucket < extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
    348 
    349   const Extension* extension =
    350       extensions_service_->GetExtensionById(extension_id, false);
    351 
    352   // Prompt the user to re-enable the application if disabled.
    353   if (!extension) {
    354     PromptToEnableApp(extension_id);
    355     return;
    356   }
    357 
    358   Profile* profile = extensions_service_->profile();
    359 
    360   // If the user pressed special keys when clicking, override the saved
    361   // preference for launch container.
    362   bool middle_button = (button == 1.0);
    363   WindowOpenDisposition disposition =
    364         disposition_utils::DispositionFromClick(middle_button, alt_key,
    365                                                 ctrl_key, meta_key, shift_key);
    366 
    367   if (extension_id != extension_misc::kWebStoreAppId) {
    368     RecordAppLaunchByID(promo_active_, launch_bucket);
    369     extensions_service_->apps_promo()->ExpireDefaultApps();
    370   }
    371 
    372   if (disposition == NEW_FOREGROUND_TAB || disposition == NEW_BACKGROUND_TAB) {
    373     // TODO(jamescook): Proper support for background tabs.
    374     Browser::OpenApplication(
    375         profile, extension, extension_misc::LAUNCH_TAB, NULL);
    376   } else if (disposition == NEW_WINDOW) {
    377     // Force a new window open.
    378     Browser::OpenApplication(
    379             profile, extension, extension_misc::LAUNCH_WINDOW, NULL);
    380   } else {
    381     // Look at preference to find the right launch container.  If no preference
    382     // is set, launch as a regular tab.
    383     extension_misc::LaunchContainer launch_container =
    384         extensions_service_->extension_prefs()->GetLaunchContainer(
    385             extension, ExtensionPrefs::LAUNCH_REGULAR);
    386 
    387     // To give a more "launchy" experience when using the NTP launcher, we close
    388     // it automatically.
    389     Browser* browser = BrowserList::GetLastActive();
    390     TabContents* old_contents = NULL;
    391     if (browser)
    392       old_contents = browser->GetSelectedTabContents();
    393 
    394     TabContents* new_contents = Browser::OpenApplication(
    395         profile, extension, launch_container, old_contents);
    396 
    397     // This will also destroy the handler, so do not perform any actions after.
    398     if (new_contents != old_contents && browser->tab_count() > 1)
    399       browser->CloseTabContents(old_contents);
    400   }
    401 
    402 }
    403 
    404 void AppLauncherHandler::HandleSetLaunchType(const ListValue* args) {
    405   std::string extension_id;
    406   double launch_type;
    407   CHECK(args->GetString(0, &extension_id));
    408   CHECK(args->GetDouble(1, &launch_type));
    409 
    410   const Extension* extension =
    411       extensions_service_->GetExtensionById(extension_id, true);
    412   CHECK(extension);
    413 
    414   extensions_service_->extension_prefs()->SetLaunchType(
    415       extension_id,
    416       static_cast<ExtensionPrefs::LaunchType>(
    417           static_cast<int>(launch_type)));
    418 }
    419 
    420 void AppLauncherHandler::HandleUninstallApp(const ListValue* args) {
    421   std::string extension_id = UTF16ToUTF8(ExtractStringValue(args));
    422   const Extension* extension = extensions_service_->GetExtensionById(
    423       extension_id, false);
    424   if (!extension)
    425     return;
    426 
    427   if (!Extension::UserMayDisable(extension->location())) {
    428     LOG(ERROR) << "Attempt to uninstall an extension that is non-usermanagable "
    429                << "was made. Extension id : " << extension->id();
    430     return;
    431   }
    432   if (!extension_id_prompting_.empty())
    433     return;  // Only one prompt at a time.
    434 
    435   extension_id_prompting_ = extension_id;
    436   GetExtensionUninstallDialog()->ConfirmUninstall(this, extension);
    437 }
    438 
    439 void AppLauncherHandler::HandleHideAppsPromo(const ListValue* args) {
    440   // If the user has intentionally hidden the promotion, we'll uninstall all the
    441   // default apps (we know the user hasn't installed any apps on their own at
    442   // this point, or the promotion wouldn't have been shown).
    443   ignore_changes_ = true;
    444   UninstallDefaultApps();
    445   extensions_service_->apps_promo()->HidePromo();
    446   ignore_changes_ = false;
    447   HandleGetApps(NULL);
    448 }
    449 
    450 void AppLauncherHandler::HandleCreateAppShortcut(const ListValue* args) {
    451   std::string extension_id;
    452   if (!args->GetString(0, &extension_id)) {
    453     NOTREACHED();
    454     return;
    455   }
    456 
    457   const Extension* extension =
    458       extensions_service_->GetExtensionById(extension_id, true);
    459   CHECK(extension);
    460 
    461   Browser* browser = BrowserList::GetLastActive();
    462   if (!browser)
    463     return;
    464   browser->window()->ShowCreateChromeAppShortcutsDialog(
    465       browser->profile(), extension);
    466 }
    467 
    468 void AppLauncherHandler::HandleReorderApps(const ListValue* args) {
    469   CHECK(args->GetSize() == 2);
    470 
    471   std::string dragged_app_id;
    472   ListValue* app_order;
    473   CHECK(args->GetString(0, &dragged_app_id));
    474   CHECK(args->GetList(1, &app_order));
    475 
    476   std::vector<std::string> extension_ids;
    477   for (size_t i = 0; i < app_order->GetSize(); ++i) {
    478     std::string value;
    479     if (app_order->GetString(i, &value))
    480       extension_ids.push_back(value);
    481   }
    482 
    483   extensions_service_->extension_prefs()->SetAppDraggedByUser(dragged_app_id);
    484   extensions_service_->extension_prefs()->SetAppLauncherOrder(extension_ids);
    485 }
    486 
    487 void AppLauncherHandler::HandleSetPageIndex(const ListValue* args) {
    488   std::string extension_id;
    489   double page_index;
    490   CHECK(args->GetString(0, &extension_id));
    491   CHECK(args->GetDouble(1, &page_index));
    492 
    493   extensions_service_->extension_prefs()->SetPageIndex(extension_id,
    494       static_cast<int>(page_index));
    495 }
    496 
    497 void AppLauncherHandler::HandlePromoSeen(const ListValue* args) {
    498   UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppsPromoHistogram,
    499                             extension_misc::PROMO_SEEN,
    500                             extension_misc::PROMO_BUCKET_BOUNDARY);
    501 }
    502 
    503 // static
    504 void AppLauncherHandler::RecordWebStoreLaunch(bool promo_active) {
    505   UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppLaunchHistogram,
    506                             extension_misc::APP_LAUNCH_NTP_WEBSTORE,
    507                             extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
    508 
    509   if (!promo_active) return;
    510 
    511   UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppsPromoHistogram,
    512                             extension_misc::PROMO_LAUNCH_WEB_STORE,
    513                             extension_misc::PROMO_BUCKET_BOUNDARY);
    514 }
    515 
    516 // static
    517 void AppLauncherHandler::RecordAppLaunchByID(
    518     bool promo_active, extension_misc::AppLaunchBucket bucket) {
    519   CHECK(bucket != extension_misc::APP_LAUNCH_BUCKET_INVALID);
    520 
    521   UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppLaunchHistogram, bucket,
    522                             extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
    523 
    524   if (!promo_active) return;
    525 
    526   UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppsPromoHistogram,
    527                             extension_misc::PROMO_LAUNCH_APP,
    528                             extension_misc::PROMO_BUCKET_BOUNDARY);
    529 }
    530 
    531 // static
    532 void AppLauncherHandler::RecordAppLaunchByURL(
    533     Profile* profile,
    534     std::string escaped_url,
    535     extension_misc::AppLaunchBucket bucket) {
    536   CHECK(bucket != extension_misc::APP_LAUNCH_BUCKET_INVALID);
    537 
    538   GURL url(UnescapeURLComponent(escaped_url, kUnescapeRules));
    539   DCHECK(profile->GetExtensionService());
    540   if (!profile->GetExtensionService()->IsInstalledApp(url))
    541     return;
    542 
    543   UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppLaunchHistogram, bucket,
    544                             extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
    545 }
    546 
    547 void AppLauncherHandler::PromptToEnableApp(const std::string& extension_id) {
    548   const Extension* extension =
    549       extensions_service_->GetExtensionById(extension_id, true);
    550   CHECK(extension);
    551 
    552   ExtensionPrefs* extension_prefs = extensions_service_->extension_prefs();
    553   if (!extension_prefs->DidExtensionEscalatePermissions(extension_id)) {
    554     // Enable the extension immediately if its privileges weren't escalated.
    555     extensions_service_->EnableExtension(extension_id);
    556 
    557     // Launch app asynchronously so the image will update.
    558     StringValue* app_id = Value::CreateStringValue(extension->id());
    559     web_ui_->CallJavascriptFunction("launchAppAfterEnable", *app_id);
    560     return;
    561   }
    562 
    563   if (!extension_id_prompting_.empty())
    564     return;  // Only one prompt at a time.
    565 
    566   extension_id_prompting_ = extension_id;
    567   GetExtensionInstallUI()->ConfirmReEnable(this, extension);
    568 }
    569 
    570 void AppLauncherHandler::ExtensionDialogAccepted() {
    571   // Do the uninstall work here.
    572   DCHECK(!extension_id_prompting_.empty());
    573 
    574   // The extension can be uninstalled in another window while the UI was
    575   // showing. Do nothing in that case.
    576   const Extension* extension =
    577       extensions_service_->GetExtensionById(extension_id_prompting_, true);
    578   if (!extension)
    579     return;
    580 
    581   extensions_service_->UninstallExtension(extension_id_prompting_,
    582                                           false /* external_uninstall */, NULL);
    583 
    584   extension_id_prompting_ = "";
    585 }
    586 
    587 void AppLauncherHandler::ExtensionDialogCanceled() {
    588   const Extension* extension =
    589       extensions_service_->GetExtensionById(extension_id_prompting_, true);
    590   ExtensionService::RecordPermissionMessagesHistogram(
    591       extension, "Extensions.Permissions_ReEnableCancel");
    592 
    593   extension_id_prompting_ = "";
    594 }
    595 
    596 void AppLauncherHandler::InstallUIProceed() {
    597   // Do the re-enable work here.
    598   DCHECK(!extension_id_prompting_.empty());
    599 
    600   // The extension can be uninstalled in another window while the UI was
    601   // showing. Do nothing in that case.
    602   const Extension* extension =
    603       extensions_service_->GetExtensionById(extension_id_prompting_, true);
    604   if (!extension)
    605     return;
    606 
    607   extensions_service_->GrantPermissionsAndEnableExtension(extension);
    608 
    609   // We bounce this off the NTP so the browser can update the apps icon.
    610   // If we don't launch the app asynchronously, then the app's disabled
    611   // icon disappears but isn't replaced by the enabled icon, making a poor
    612   // visual experience.
    613   StringValue* app_id = Value::CreateStringValue(extension->id());
    614   web_ui_->CallJavascriptFunction("launchAppAfterEnable", *app_id);
    615 
    616   extension_id_prompting_ = "";
    617 }
    618 
    619 void AppLauncherHandler::InstallUIAbort() {
    620   ExtensionDialogCanceled();
    621 }
    622 
    623 ExtensionUninstallDialog* AppLauncherHandler::GetExtensionUninstallDialog() {
    624   if (!extension_uninstall_dialog_.get()) {
    625     extension_uninstall_dialog_.reset(
    626         new ExtensionUninstallDialog(web_ui_->GetProfile()));
    627   }
    628   return extension_uninstall_dialog_.get();
    629 }
    630 
    631 ExtensionInstallUI* AppLauncherHandler::GetExtensionInstallUI() {
    632   if (!extension_install_ui_.get()) {
    633     extension_install_ui_.reset(
    634         new ExtensionInstallUI(web_ui_->GetProfile()));
    635   }
    636   return extension_install_ui_.get();
    637 }
    638 
    639 void AppLauncherHandler::UninstallDefaultApps() {
    640   AppsPromo* apps_promo = extensions_service_->apps_promo();
    641   const ExtensionIdSet& app_ids = apps_promo->old_default_apps();
    642   for (ExtensionIdSet::const_iterator iter = app_ids.begin();
    643        iter != app_ids.end(); ++iter) {
    644     if (extensions_service_->GetExtensionById(*iter, true))
    645       extensions_service_->UninstallExtension(*iter, false, NULL);
    646   }
    647 }
    648