Index: chrome/browser/ui/webui/ntp/app_launcher_handler.cc |
diff --git a/chrome/browser/ui/webui/ntp/app_launcher_handler.cc b/chrome/browser/ui/webui/ntp/app_launcher_handler.cc |
index 424bc0e628d71f6c1dc3ee86029bd19f48b0204d..5d2ba3e4da92150ffa1acbea4ffbeff720db3c9c 100644 |
--- a/chrome/browser/ui/webui/ntp/app_launcher_handler.cc |
+++ b/chrome/browser/ui/webui/ntp/app_launcher_handler.cc |
@@ -71,7 +71,8 @@ AppLauncherHandler::AppLauncherHandler(ExtensionService* extension_service) |
: extension_service_(extension_service), |
ignore_changes_(false), |
attempted_bookmark_app_install_(false), |
- has_loaded_apps_(false) { |
+ has_loaded_apps_(false), |
+ uninstall_from_page_(false) { |
} |
AppLauncherHandler::~AppLauncherHandler() {} |
@@ -170,15 +171,8 @@ void AppLauncherHandler::CreateAppInfo(const Extension* extension, |
} |
value->SetInteger("app_launch_index", app_launch_index); |
- int page_index = prefs->GetPageIndex(extension->id()); |
- if (page_index < 0) { |
- // Make sure every app has a page index (some predate the page index). |
- // The webstore app should be on the first page. |
- page_index = extension->id() == extension_misc::kWebStoreAppId ? |
- 0 : prefs->GetNaturalAppPageIndex(); |
- prefs->SetPageIndex(extension->id(), page_index); |
- } |
- value->SetInteger("page_index", page_index); |
+ EnsureAppHasPageIndex(service, extension->id()); |
+ value->SetInteger("page_index", prefs->GetPageIndex(extension->id())); |
} |
WebUIMessageHandler* AppLauncherHandler::Attach(WebUI* web_ui) { |
@@ -188,6 +182,9 @@ WebUIMessageHandler* AppLauncherHandler::Attach(WebUI* web_ui) { |
} |
void AppLauncherHandler::RegisterMessages() { |
+ web_ui_->RegisterMessageCallback("deleteAppsPage", |
+ base::Bind(&AppLauncherHandler::HandleDeleteAppsPage, |
+ base::Unretained(this))); |
web_ui_->RegisterMessageCallback("getApps", |
base::Bind(&AppLauncherHandler::HandleGetApps, |
base::Unretained(this))); |
@@ -215,8 +212,8 @@ void AppLauncherHandler::RegisterMessages() { |
web_ui_->RegisterMessageCallback("promoSeen", |
base::Bind(&AppLauncherHandler::HandlePromoSeen, |
base::Unretained(this))); |
- web_ui_->RegisterMessageCallback("saveAppPageName", |
- base::Bind(&AppLauncherHandler::HandleSaveAppPageName, |
+ web_ui_->RegisterMessageCallback("saveAppsPageName", |
+ base::Bind(&AppLauncherHandler::HandleSaveAppsPageName, |
base::Unretained(this))); |
web_ui_->RegisterMessageCallback("generateAppForLink", |
base::Bind(&AppLauncherHandler::HandleGenerateAppForLink, |
@@ -293,18 +290,40 @@ void AppLauncherHandler::Observe(int type, |
content::Details<UnloadedExtensionInfo>(details)->reason == |
extension_misc::UNLOAD_REASON_UNINSTALL)); |
if (app_info.get()) { |
+ scoped_ptr<base::FundamentalValue> from_page( |
+ Value::CreateBooleanValue(uninstall_from_page_)); |
web_ui_->CallJavascriptFunction( |
- "ntp4.appRemoved", *app_info, *uninstall_value); |
+ "ntp4.appRemoved", *app_info, *uninstall_value, *from_page); |
} |
break; |
} |
- case chrome::NOTIFICATION_EXTENSION_LAUNCHER_REORDERED: |
- // The promo may not load until a couple seconds after the first NTP view, |
- // so we listen for the load notification and notify the NTP when ready. |
- case chrome::NOTIFICATION_WEB_STORE_PROMO_LOADED: |
- // TODO(estade): try to get rid of this inefficient operation. |
+ case chrome::NOTIFICATION_EXTENSION_LAUNCHER_REORDERED: { |
+ DictionaryValue pages; |
+ const ExtensionSet* extensions = extension_service_->extensions(); |
+ ExtensionPrefs* prefs = extension_service_->extension_prefs(); |
+ for (ExtensionSet::const_iterator it = extensions->begin(); |
+ it != extensions->end(); ++it) { |
+ if (!IsAppExcludedFromList(*it)) { |
+ std::string page_index = |
+ base::IntToString(prefs->GetPageIndex((*it)->id())); |
+ if (!pages.HasKey(page_index)) |
+ pages.Set(page_index, new DictionaryValue()); |
+ DictionaryValue* page; |
+ CHECK(pages.GetDictionary(page_index, &page)); |
+ page->SetString( |
+ base::IntToString(prefs->GetAppLaunchIndex((*it)->id())), |
+ (*it)->id()); |
+ } |
+ } |
+ web_ui_->CallJavascriptFunction("ntp4.appsReordered", pages); |
+ break; |
+ } |
+ case chrome::NOTIFICATION_WEB_STORE_PROMO_LOADED: { |
+ // The promo may not load until a couple seconds after the first NTP view, |
+ // so we listen for the load notification and notify the NTP when ready. |
HandleGetApps(NULL); |
break; |
+ } |
case chrome::NOTIFICATION_PREF_CHANGED: { |
DictionaryValue dictionary; |
FillAppDictionary(&dictionary); |
@@ -326,6 +345,18 @@ void AppLauncherHandler::Observe(int type, |
} |
} |
+// static |
+void AppLauncherHandler::EnsureAppHasPageIndex(ExtensionService* service, |
+ const std::string& id) { |
+ if (service->extension_prefs()->GetPageIndex(id) < 0) { |
+ // Make sure every app has a page index (some predate the page index). |
+ // The webstore app should be on the first page. |
+ int page_index = id == extension_misc::kWebStoreAppId ? |
+ 0 : service->extension_prefs()->GetNaturalAppPageIndex(); |
+ service->extension_prefs()->SetPageIndex(id, page_index); |
+ } |
+} |
+ |
void AppLauncherHandler::FillAppDictionary(DictionaryValue* dictionary) { |
// CreateAppInfo and ClearPageIndex can change the extension prefs. |
AutoReset<bool> auto_reset(&ignore_changes_, true); |
@@ -401,10 +432,10 @@ void AppLauncherHandler::FillAppDictionary(DictionaryValue* dictionary) { |
ListValue* list = update.Get(); |
list->Set(0, Value::CreateStringValue( |
l10n_util::GetStringUTF16(IDS_APP_DEFAULT_PAGE_NAME))); |
- dictionary->Set("appPageNames", |
+ dictionary->Set("appsPageNames", |
static_cast<ListValue*>(list->DeepCopy())); |
} else { |
- dictionary->Set("appPageNames", |
+ dictionary->Set("appsPageNames", |
static_cast<ListValue*>(app_page_names->DeepCopy())); |
} |
} |
@@ -431,6 +462,84 @@ void AppLauncherHandler::FillPromoDictionary(DictionaryValue* dictionary) { |
dictionary->SetString("promoExpire", data.expire); |
} |
+void AppLauncherHandler::HandleDeleteAppsPage(const ListValue* args) { |
+ double page_index_double; |
+ CHECK(args->GetDouble(0, &page_index_double) && page_index_double >= 0); |
+ size_t page_index = static_cast<size_t>(page_index_double); |
+ |
+ AutoReset<bool> auto_reset(&ignore_changes_, true); |
+ |
+ DeleteAppsPageRange(page_index, 1); |
+} |
+ |
+void AppLauncherHandler::CleanupAfterUninstall() { |
+ uninstall_from_page_ = false; |
+ extension_id_prompting_ = ""; |
+} |
+ |
+void AppLauncherHandler::CondenseAppsPages() { |
+ std::vector<bool> has_apps; |
+ const ExtensionSet* extensions = extension_service_->extensions(); |
+ for (ExtensionSet::const_iterator it = extensions->begin(); |
+ it != extensions->end(); ++it) { |
+ if (!IsAppExcludedFromList(*it)) { |
+ EnsureAppHasPageIndex(extension_service_, (*it)->id()); |
+ size_t page_index = |
+ extension_service_->extension_prefs()->GetPageIndex((*it)->id()); |
+ if (has_apps.size() < page_index + 1) |
+ has_apps.resize(page_index + 1); |
+ has_apps[page_index] = true; |
+ } |
+ } |
+ |
+ // Delete empty ranges of apps pages. |
+ int first_empty = -1; |
+ for (size_t i = 0; i < has_apps.size(); ++i) { |
+ if (!has_apps[i]) { |
+ if (first_empty == -1) |
+ first_empty = i; |
+ } else if (first_empty != -1) { |
+ DeleteAppsPageRange(first_empty, i - first_empty); |
+ has_apps.erase(has_apps.begin() + first_empty, has_apps.begin() + i); |
+ i -= (i - first_empty); |
+ first_empty = -1; |
+ } |
+ } |
+ // Make sure there are no pending empty apps pages at the end of our list. |
+ CHECK_EQ(first_empty, -1); |
+} |
+ |
+void AppLauncherHandler::DeleteAppsPageRange(size_t index, size_t how_many) { |
+ // Delete the apps page name from our prefs. |
+ PrefService* prefs = Profile::FromWebUI(web_ui_)->GetPrefs(); |
+ ListPrefUpdate update(prefs, prefs::kNTPAppPageNames); |
+ ListValue* list = update.Get(); |
+ |
+ // Index checking is done within list->Remove() and will return false if an |
+ // index to remove is negative or larger than the size of the list. This is |
+ // intentionally left un-CHECK()-ed in the case that there's a skew between |
+ // the list pref size and the last page index of all the apps. |
+ for (size_t i = 0; i < how_many; ++i) |
+ list->Remove(index, NULL); |
+ |
+ // Move apps that were on a page past the one we're deleting to their newly |
+ // adjusted page index. |
+ ExtensionPrefs* ext_prefs = extension_service_->extension_prefs(); |
+ const ExtensionSet* extensions = extension_service_->extensions(); |
+ for (ExtensionSet::const_iterator it = extensions->begin(); |
+ it != extensions->end(); ++it) { |
+ // Ignore extensions and the cloud print app (but handle CWS app). |
+ if (!IsAppExcludedFromList(*it)) { |
+ size_t apps_page_index = |
+ static_cast<size_t>(ext_prefs->GetPageIndex((*it)->id())); |
+ // Don't delete pages with apps still on them. |
+ CHECK(apps_page_index < index || apps_page_index >= (index + how_many)); |
+ if (apps_page_index >= (index + how_many)) |
+ ext_prefs->SetPageIndex((*it)->id(), apps_page_index - how_many); |
+ } |
+ } |
+} |
+ |
void AppLauncherHandler::HandleGetApps(const ListValue* args) { |
DictionaryValue dictionary; |
@@ -461,6 +570,7 @@ void AppLauncherHandler::HandleGetApps(const ListValue* args) { |
ignore_changes_ = false; |
} |
+ CondenseAppsPages(); |
SetAppToBeHighlighted(); |
FillAppDictionary(&dictionary); |
web_ui_->CallJavascriptFunction("getAppsCallback", dictionary); |
@@ -604,6 +714,9 @@ void AppLauncherHandler::HandleUninstallApp(const ListValue* args) { |
AutoReset<bool> auto_reset(&ignore_changes_, true); |
ExtensionUninstallAccepted(); |
} else { |
+ // We don't use an AutoReset<bool> here as the uninstall dialog runs in a |
+ // different thread so it's not sync. |
+ uninstall_from_page_ = true; |
GetExtensionUninstallDialog()->ConfirmUninstall(extension); |
} |
} |
@@ -674,18 +787,25 @@ void AppLauncherHandler::HandlePromoSeen(const ListValue* args) { |
extension_misc::PROMO_BUCKET_BOUNDARY); |
} |
-void AppLauncherHandler::HandleSaveAppPageName(const ListValue* args) { |
+void AppLauncherHandler::HandleSaveAppsPageName(const ListValue* args) { |
string16 name; |
CHECK(args->GetString(0, &name)); |
- double page_index; |
- CHECK(args->GetDouble(1, &page_index)); |
+ double page_index_double; |
+ CHECK(args->GetDouble(1, &page_index_double)); |
AutoReset<bool> auto_reset(&ignore_changes_, true); |
PrefService* prefs = Profile::FromWebUI(web_ui_)->GetPrefs(); |
ListPrefUpdate update(prefs, prefs::kNTPAppPageNames); |
ListValue* list = update.Get(); |
- list->Set(static_cast<size_t>(page_index), Value::CreateStringValue(name)); |
+ size_t page_index = static_cast<size_t>(page_index_double); |
+ // Though it's valid to set items of a list at a non-existent positive index |
+ // (that's greater than the current length), this is not desired in this case |
+ // as it creates null values in the middle of the list. Instead, only allow a |
+ // save to act as an append when the page being saved is exactly equal to the |
+ // length of the list. |
+ CHECK(page_index <= list->GetSize()); |
+ list->Set(page_index, Value::CreateStringValue(name)); |
} |
void AppLauncherHandler::HandleGenerateAppForLink(const ListValue* args) { |
@@ -699,6 +819,9 @@ void AppLauncherHandler::HandleGenerateAppForLink(const ListValue* args) { |
double page_index; |
CHECK(args->GetDouble(2, &page_index)); |
+ // TODO(dbeam): Handle third argument (app_launch_index) and pass through |
+ // the installation process. |
+ |
Profile* profile = Profile::FromWebUI(web_ui_); |
FaviconService* favicon_service = |
profile->GetFaviconService(Profile::EXPLICIT_ACCESS); |
@@ -891,12 +1014,11 @@ void AppLauncherHandler::ExtensionUninstallAccepted() { |
extension_service_->UninstallExtension(extension_id_prompting_, |
false /* external_uninstall */, NULL); |
- |
- extension_id_prompting_ = ""; |
+ CleanupAfterUninstall(); |
} |
void AppLauncherHandler::ExtensionUninstallCanceled() { |
- extension_id_prompting_ = ""; |
+ CleanupAfterUninstall(); |
} |
void AppLauncherHandler::InstallUIProceed() { |