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