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 42namespace { 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. 46const char* kPingLaunchAppByID = "record-app-launch-by-id"; 47const char* kPingLaunchWebStore = "record-webstore-launch"; 48const char* kPingLaunchAppByURL = "record-app-launch-by-url"; 49 50const UnescapeRule::Type kUnescapeRules = 51 UnescapeRule::NORMAL | UnescapeRule::URL_SPECIAL_CHARS; 52 53extension_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 65AppLauncherHandler::AppLauncherHandler(ExtensionService* extension_service) 66 : extensions_service_(extension_service), 67 promo_active_(false), 68 ignore_changes_(false) { 69} 70 71AppLauncherHandler::~AppLauncherHandler() {} 72 73// static 74void 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 121bool AppLauncherHandler::HandlePing(Profile* profile, const std::string& path) { 122 std::vector<std::string> params; 123 base::SplitString(path, '+', ¶ms); 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 161WebUIMessageHandler* AppLauncherHandler::Attach(WebUI* web_ui) { 162 // TODO(arv): Add initialization code to the Apps store etc. 163 return WebUIMessageHandler::Attach(web_ui); 164} 165 166void 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 187void 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 217void 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 260void 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 267void 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 324void 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 404void 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 420void 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 439void 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 450void 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 468void 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 487void 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 497void 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 504void 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 517void 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 532void 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 547void 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 570void 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 587void 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 596void 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 619void AppLauncherHandler::InstallUIAbort() { 620 ExtensionDialogCanceled(); 621} 622 623ExtensionUninstallDialog* 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 631ExtensionInstallUI* 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 639void 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