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