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