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