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