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