1 // Copyright 2013 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/extensions/chrome_app_sorting.h" 6 7 #include <algorithm> 8 #include <vector> 9 10 #include "chrome/browser/chrome_notification_types.h" 11 #include "chrome/browser/extensions/extension_sync_service.h" 12 #include "chrome/common/extensions/extension_constants.h" 13 #include "content/public/browser/notification_service.h" 14 #include "extensions/browser/extension_scoped_prefs.h" 15 #include "extensions/common/extension.h" 16 17 #if defined(OS_CHROMEOS) 18 #include "chrome/browser/chromeos/extensions/default_app_order.h" 19 #endif 20 21 namespace extensions { 22 23 namespace { 24 25 // The number of apps per page. This isn't a hard limit, but new apps installed 26 // from the webstore will overflow onto a new page if this limit is reached. 27 const size_t kNaturalAppPageSize = 18; 28 29 // A preference determining the order of which the apps appear on the NTP. 30 const char kPrefAppLaunchIndexDeprecated[] = "app_launcher_index"; 31 const char kPrefAppLaunchOrdinal[] = "app_launcher_ordinal"; 32 33 // A preference determining the page on which an app appears in the NTP. 34 const char kPrefPageIndexDeprecated[] = "page_index"; 35 const char kPrefPageOrdinal[] = "page_ordinal"; 36 37 } // namespace 38 39 //////////////////////////////////////////////////////////////////////////////// 40 // ChromeAppSorting::AppOrdinals 41 42 ChromeAppSorting::AppOrdinals::AppOrdinals() {} 43 44 ChromeAppSorting::AppOrdinals::~AppOrdinals() {} 45 46 //////////////////////////////////////////////////////////////////////////////// 47 // ChromeAppSorting 48 49 ChromeAppSorting::ChromeAppSorting() 50 : extension_scoped_prefs_(NULL), 51 extension_sync_service_(NULL), 52 default_ordinals_created_(false) { 53 } 54 55 ChromeAppSorting::~ChromeAppSorting() { 56 } 57 58 void ChromeAppSorting::SetExtensionScopedPrefs(ExtensionScopedPrefs* prefs) { 59 extension_scoped_prefs_ = prefs; 60 } 61 62 void ChromeAppSorting::SetExtensionSyncService( 63 ExtensionSyncService* extension_sync_service) { 64 extension_sync_service_ = extension_sync_service; 65 } 66 67 void ChromeAppSorting::Initialize( 68 const extensions::ExtensionIdList& extension_ids) { 69 InitializePageOrdinalMap(extension_ids); 70 71 MigrateAppIndex(extension_ids); 72 } 73 74 void ChromeAppSorting::CreateOrdinalsIfNecessary(size_t minimum_size) { 75 // Create StringOrdinal values as required to ensure |ntp_ordinal_map_| has at 76 // least |minimum_size| entries. 77 if (ntp_ordinal_map_.empty() && minimum_size > 0) 78 ntp_ordinal_map_[syncer::StringOrdinal::CreateInitialOrdinal()]; 79 80 while (ntp_ordinal_map_.size() < minimum_size) { 81 syncer::StringOrdinal filler = 82 ntp_ordinal_map_.rbegin()->first.CreateAfter(); 83 AppLaunchOrdinalMap empty_ordinal_map; 84 ntp_ordinal_map_.insert(std::make_pair(filler, empty_ordinal_map)); 85 } 86 } 87 88 void ChromeAppSorting::MigrateAppIndex( 89 const extensions::ExtensionIdList& extension_ids) { 90 if (extension_ids.empty()) 91 return; 92 93 // Convert all the page index values to page ordinals. If there are any 94 // app launch values that need to be migrated, inserted them into a sorted 95 // set to be dealt with later. 96 typedef std::map<syncer::StringOrdinal, std::map<int, const std::string*>, 97 syncer::StringOrdinal::LessThanFn> AppPositionToIdMapping; 98 AppPositionToIdMapping app_launches_to_convert; 99 for (extensions::ExtensionIdList::const_iterator ext_id = 100 extension_ids.begin(); ext_id != extension_ids.end(); ++ext_id) { 101 int old_page_index = 0; 102 syncer::StringOrdinal page = GetPageOrdinal(*ext_id); 103 if (extension_scoped_prefs_->ReadPrefAsInteger( 104 *ext_id, 105 kPrefPageIndexDeprecated, 106 &old_page_index)) { 107 // Some extensions have invalid page index, so we don't 108 // attempt to convert them. 109 if (old_page_index < 0) { 110 DLOG(WARNING) << "Extension " << *ext_id 111 << " has an invalid page index " << old_page_index 112 << ". Aborting attempt to convert its index."; 113 break; 114 } 115 116 CreateOrdinalsIfNecessary(static_cast<size_t>(old_page_index) + 1); 117 118 page = PageIntegerAsStringOrdinal(old_page_index); 119 SetPageOrdinal(*ext_id, page); 120 extension_scoped_prefs_->UpdateExtensionPref( 121 *ext_id, kPrefPageIndexDeprecated, NULL); 122 } 123 124 int old_app_launch_index = 0; 125 if (extension_scoped_prefs_->ReadPrefAsInteger( 126 *ext_id, 127 kPrefAppLaunchIndexDeprecated, 128 &old_app_launch_index)) { 129 // We can't update the app launch index value yet, because we use 130 // GetNextAppLaunchOrdinal to get the new ordinal value and it requires 131 // all the ordinals with lower values to have already been migrated. 132 // A valid page ordinal is also required because otherwise there is 133 // no page to add the app to. 134 if (page.IsValid()) 135 app_launches_to_convert[page][old_app_launch_index] = &*ext_id; 136 137 extension_scoped_prefs_->UpdateExtensionPref( 138 *ext_id, kPrefAppLaunchIndexDeprecated, NULL); 139 } 140 } 141 142 // Remove any empty pages that may have been added. This shouldn't occur, 143 // but double check here to prevent future problems with conversions between 144 // integers and StringOrdinals. 145 for (PageOrdinalMap::iterator it = ntp_ordinal_map_.begin(); 146 it != ntp_ordinal_map_.end();) { 147 if (it->second.empty()) { 148 PageOrdinalMap::iterator prev_it = it; 149 ++it; 150 ntp_ordinal_map_.erase(prev_it); 151 } else { 152 ++it; 153 } 154 } 155 156 if (app_launches_to_convert.empty()) 157 return; 158 159 // Create the new app launch ordinals and remove the old preferences. Since 160 // the set is sorted, each time we migrate an apps index, we know that all of 161 // the remaining apps will appear further down the NTP than it or on a 162 // different page. 163 for (AppPositionToIdMapping::const_iterator page_it = 164 app_launches_to_convert.begin(); 165 page_it != app_launches_to_convert.end(); ++page_it) { 166 syncer::StringOrdinal page = page_it->first; 167 for (std::map<int, const std::string*>::const_iterator launch_it = 168 page_it->second.begin(); launch_it != page_it->second.end(); 169 ++launch_it) { 170 SetAppLaunchOrdinal(*(launch_it->second), 171 CreateNextAppLaunchOrdinal(page)); 172 } 173 } 174 } 175 176 void ChromeAppSorting::FixNTPOrdinalCollisions() { 177 for (PageOrdinalMap::iterator page_it = ntp_ordinal_map_.begin(); 178 page_it != ntp_ordinal_map_.end(); ++page_it) { 179 AppLaunchOrdinalMap& page = page_it->second; 180 181 AppLaunchOrdinalMap::iterator app_launch_it = page.begin(); 182 while (app_launch_it != page.end()) { 183 int app_count = page.count(app_launch_it->first); 184 if (app_count == 1) { 185 ++app_launch_it; 186 continue; 187 } 188 189 syncer::StringOrdinal repeated_ordinal = app_launch_it->first; 190 191 // Sort the conflicting keys by their extension id, this is how 192 // the order is decided. 193 std::vector<std::string> conflicting_ids; 194 for (int i = 0; i < app_count; ++i, ++app_launch_it) 195 conflicting_ids.push_back(app_launch_it->second); 196 std::sort(conflicting_ids.begin(), conflicting_ids.end()); 197 198 syncer::StringOrdinal upper_bound_ordinal = app_launch_it == page.end() ? 199 syncer::StringOrdinal() : 200 app_launch_it->first; 201 syncer::StringOrdinal lower_bound_ordinal = repeated_ordinal; 202 203 // Start at position 1 because the first extension can keep the conflicted 204 // value. 205 for (int i = 1; i < app_count; ++i) { 206 syncer::StringOrdinal unique_app_launch; 207 if (upper_bound_ordinal.IsValid()) { 208 unique_app_launch = 209 lower_bound_ordinal.CreateBetween(upper_bound_ordinal); 210 } else { 211 unique_app_launch = lower_bound_ordinal.CreateAfter(); 212 } 213 214 SetAppLaunchOrdinal(conflicting_ids[i], unique_app_launch); 215 lower_bound_ordinal = unique_app_launch; 216 } 217 } 218 } 219 220 content::NotificationService::current()->Notify( 221 chrome::NOTIFICATION_EXTENSION_LAUNCHER_REORDERED, 222 content::Source<ChromeAppSorting>(this), 223 content::NotificationService::NoDetails()); 224 } 225 226 void ChromeAppSorting::EnsureValidOrdinals( 227 const std::string& extension_id, 228 const syncer::StringOrdinal& suggested_page) { 229 syncer::StringOrdinal page_ordinal = GetPageOrdinal(extension_id); 230 if (!page_ordinal.IsValid()) { 231 if (suggested_page.IsValid()) { 232 page_ordinal = suggested_page; 233 } else if (!GetDefaultOrdinals(extension_id, &page_ordinal, NULL) || 234 !page_ordinal.IsValid()) { 235 page_ordinal = GetNaturalAppPageOrdinal(); 236 } 237 238 SetPageOrdinal(extension_id, page_ordinal); 239 } 240 241 syncer::StringOrdinal app_launch_ordinal = GetAppLaunchOrdinal(extension_id); 242 if (!app_launch_ordinal.IsValid()) { 243 // If using default app launcher ordinal, make sure there is no collision. 244 if (GetDefaultOrdinals(extension_id, NULL, &app_launch_ordinal) && 245 app_launch_ordinal.IsValid()) 246 app_launch_ordinal = ResolveCollision(page_ordinal, app_launch_ordinal); 247 else 248 app_launch_ordinal = CreateNextAppLaunchOrdinal(page_ordinal); 249 250 SetAppLaunchOrdinal(extension_id, app_launch_ordinal); 251 } 252 } 253 254 void ChromeAppSorting::OnExtensionMoved( 255 const std::string& moved_extension_id, 256 const std::string& predecessor_extension_id, 257 const std::string& successor_extension_id) { 258 // We only need to change the StringOrdinal if there are neighbours. 259 if (!predecessor_extension_id.empty() || !successor_extension_id.empty()) { 260 if (predecessor_extension_id.empty()) { 261 // Only a successor. 262 SetAppLaunchOrdinal( 263 moved_extension_id, 264 GetAppLaunchOrdinal(successor_extension_id).CreateBefore()); 265 } else if (successor_extension_id.empty()) { 266 // Only a predecessor. 267 SetAppLaunchOrdinal( 268 moved_extension_id, 269 GetAppLaunchOrdinal(predecessor_extension_id).CreateAfter()); 270 } else { 271 // Both a successor and predecessor 272 const syncer::StringOrdinal& predecessor_ordinal = 273 GetAppLaunchOrdinal(predecessor_extension_id); 274 const syncer::StringOrdinal& successor_ordinal = 275 GetAppLaunchOrdinal(successor_extension_id); 276 SetAppLaunchOrdinal(moved_extension_id, 277 predecessor_ordinal.CreateBetween(successor_ordinal)); 278 } 279 } 280 281 SyncIfNeeded(moved_extension_id); 282 283 content::NotificationService::current()->Notify( 284 chrome::NOTIFICATION_EXTENSION_LAUNCHER_REORDERED, 285 content::Source<ChromeAppSorting>(this), 286 content::Details<const std::string>(&moved_extension_id)); 287 } 288 289 290 syncer::StringOrdinal ChromeAppSorting::GetAppLaunchOrdinal( 291 const std::string& extension_id) const { 292 std::string raw_value; 293 // If the preference read fails then raw_value will still be unset and we 294 // will return an invalid StringOrdinal to signal that no app launch ordinal 295 // was found. 296 extension_scoped_prefs_->ReadPrefAsString( 297 extension_id, kPrefAppLaunchOrdinal, &raw_value); 298 return syncer::StringOrdinal(raw_value); 299 } 300 301 void ChromeAppSorting::SetAppLaunchOrdinal( 302 const std::string& extension_id, 303 const syncer::StringOrdinal& new_app_launch_ordinal) { 304 // No work is required if the old and new values are the same. 305 if (new_app_launch_ordinal.EqualsOrBothInvalid( 306 GetAppLaunchOrdinal(extension_id))) { 307 return; 308 } 309 310 syncer::StringOrdinal page_ordinal = GetPageOrdinal(extension_id); 311 RemoveOrdinalMapping( 312 extension_id, page_ordinal, GetAppLaunchOrdinal(extension_id)); 313 AddOrdinalMapping(extension_id, page_ordinal, new_app_launch_ordinal); 314 315 base::Value* new_value = new_app_launch_ordinal.IsValid() ? 316 new base::StringValue(new_app_launch_ordinal.ToInternalValue()) : 317 NULL; 318 319 extension_scoped_prefs_->UpdateExtensionPref( 320 extension_id, 321 kPrefAppLaunchOrdinal, 322 new_value); 323 SyncIfNeeded(extension_id); 324 } 325 326 syncer::StringOrdinal ChromeAppSorting::CreateFirstAppLaunchOrdinal( 327 const syncer::StringOrdinal& page_ordinal) const { 328 const syncer::StringOrdinal& min_ordinal = 329 GetMinOrMaxAppLaunchOrdinalsOnPage(page_ordinal, 330 ChromeAppSorting::MIN_ORDINAL); 331 332 if (min_ordinal.IsValid()) 333 return min_ordinal.CreateBefore(); 334 else 335 return syncer::StringOrdinal::CreateInitialOrdinal(); 336 } 337 338 syncer::StringOrdinal ChromeAppSorting::CreateNextAppLaunchOrdinal( 339 const syncer::StringOrdinal& page_ordinal) const { 340 const syncer::StringOrdinal& max_ordinal = 341 GetMinOrMaxAppLaunchOrdinalsOnPage(page_ordinal, 342 ChromeAppSorting::MAX_ORDINAL); 343 344 if (max_ordinal.IsValid()) 345 return max_ordinal.CreateAfter(); 346 else 347 return syncer::StringOrdinal::CreateInitialOrdinal(); 348 } 349 350 syncer::StringOrdinal ChromeAppSorting::CreateFirstAppPageOrdinal() const { 351 if (ntp_ordinal_map_.empty()) 352 return syncer::StringOrdinal::CreateInitialOrdinal(); 353 354 return ntp_ordinal_map_.begin()->first; 355 } 356 357 syncer::StringOrdinal ChromeAppSorting::GetNaturalAppPageOrdinal() const { 358 if (ntp_ordinal_map_.empty()) 359 return syncer::StringOrdinal::CreateInitialOrdinal(); 360 361 for (PageOrdinalMap::const_iterator it = ntp_ordinal_map_.begin(); 362 it != ntp_ordinal_map_.end(); ++it) { 363 if (CountItemsVisibleOnNtp(it->second) < kNaturalAppPageSize) 364 return it->first; 365 } 366 367 // Add a new page as all existing pages are full. 368 syncer::StringOrdinal last_element = ntp_ordinal_map_.rbegin()->first; 369 return last_element.CreateAfter(); 370 } 371 372 syncer::StringOrdinal ChromeAppSorting::GetPageOrdinal( 373 const std::string& extension_id) const { 374 std::string raw_data; 375 // If the preference read fails then raw_data will still be unset and we will 376 // return an invalid StringOrdinal to signal that no page ordinal was found. 377 extension_scoped_prefs_->ReadPrefAsString( 378 extension_id, kPrefPageOrdinal, &raw_data); 379 return syncer::StringOrdinal(raw_data); 380 } 381 382 void ChromeAppSorting::SetPageOrdinal( 383 const std::string& extension_id, 384 const syncer::StringOrdinal& new_page_ordinal) { 385 // No work is required if the old and new values are the same. 386 if (new_page_ordinal.EqualsOrBothInvalid(GetPageOrdinal(extension_id))) 387 return; 388 389 syncer::StringOrdinal app_launch_ordinal = GetAppLaunchOrdinal(extension_id); 390 RemoveOrdinalMapping( 391 extension_id, GetPageOrdinal(extension_id), app_launch_ordinal); 392 AddOrdinalMapping(extension_id, new_page_ordinal, app_launch_ordinal); 393 394 base::Value* new_value = new_page_ordinal.IsValid() ? 395 new base::StringValue(new_page_ordinal.ToInternalValue()) : 396 NULL; 397 398 extension_scoped_prefs_->UpdateExtensionPref( 399 extension_id, 400 kPrefPageOrdinal, 401 new_value); 402 SyncIfNeeded(extension_id); 403 } 404 405 void ChromeAppSorting::ClearOrdinals(const std::string& extension_id) { 406 RemoveOrdinalMapping(extension_id, 407 GetPageOrdinal(extension_id), 408 GetAppLaunchOrdinal(extension_id)); 409 410 extension_scoped_prefs_->UpdateExtensionPref( 411 extension_id, kPrefPageOrdinal, NULL); 412 extension_scoped_prefs_->UpdateExtensionPref( 413 extension_id, kPrefAppLaunchOrdinal, NULL); 414 } 415 416 int ChromeAppSorting::PageStringOrdinalAsInteger( 417 const syncer::StringOrdinal& page_ordinal) const { 418 if (!page_ordinal.IsValid()) 419 return -1; 420 421 PageOrdinalMap::const_iterator it = ntp_ordinal_map_.find(page_ordinal); 422 return it != ntp_ordinal_map_.end() ? 423 std::distance(ntp_ordinal_map_.begin(), it) : -1; 424 } 425 426 syncer::StringOrdinal ChromeAppSorting::PageIntegerAsStringOrdinal( 427 size_t page_index) { 428 if (page_index < ntp_ordinal_map_.size()) { 429 PageOrdinalMap::const_iterator it = ntp_ordinal_map_.begin(); 430 std::advance(it, page_index); 431 return it->first; 432 } 433 434 CreateOrdinalsIfNecessary(page_index + 1); 435 return ntp_ordinal_map_.rbegin()->first; 436 } 437 438 void ChromeAppSorting::MarkExtensionAsHidden(const std::string& extension_id) { 439 ntp_hidden_extensions_.insert(extension_id); 440 } 441 442 syncer::StringOrdinal ChromeAppSorting::GetMinOrMaxAppLaunchOrdinalsOnPage( 443 const syncer::StringOrdinal& target_page_ordinal, 444 AppLaunchOrdinalReturn return_type) const { 445 CHECK(target_page_ordinal.IsValid()); 446 447 syncer::StringOrdinal return_value; 448 449 PageOrdinalMap::const_iterator page = 450 ntp_ordinal_map_.find(target_page_ordinal); 451 if (page != ntp_ordinal_map_.end()) { 452 const AppLaunchOrdinalMap& app_list = page->second; 453 454 if (app_list.empty()) 455 return syncer::StringOrdinal(); 456 457 if (return_type == ChromeAppSorting::MAX_ORDINAL) 458 return_value = app_list.rbegin()->first; 459 else if (return_type == ChromeAppSorting::MIN_ORDINAL) 460 return_value = app_list.begin()->first; 461 } 462 463 return return_value; 464 } 465 466 void ChromeAppSorting::InitializePageOrdinalMap( 467 const extensions::ExtensionIdList& extension_ids) { 468 for (extensions::ExtensionIdList::const_iterator ext_it = 469 extension_ids.begin(); ext_it != extension_ids.end(); ++ext_it) { 470 AddOrdinalMapping(*ext_it, 471 GetPageOrdinal(*ext_it), 472 GetAppLaunchOrdinal(*ext_it)); 473 474 // Ensure that the web store app still isn't found in this list, since 475 // it is added after this loop. 476 DCHECK(*ext_it != extension_misc::kWebStoreAppId); 477 DCHECK(*ext_it != extension_misc::kChromeAppId); 478 } 479 480 // Include the Web Store App since it is displayed on the NTP. 481 syncer::StringOrdinal web_store_app_page = 482 GetPageOrdinal(extension_misc::kWebStoreAppId); 483 if (web_store_app_page.IsValid()) { 484 AddOrdinalMapping(extension_misc::kWebStoreAppId, 485 web_store_app_page, 486 GetAppLaunchOrdinal(extension_misc::kWebStoreAppId)); 487 } 488 // Include the Chrome App since it is displayed in the app launcher. 489 syncer::StringOrdinal chrome_app_page = 490 GetPageOrdinal(extension_misc::kChromeAppId); 491 if (chrome_app_page.IsValid()) { 492 AddOrdinalMapping(extension_misc::kChromeAppId, 493 chrome_app_page, 494 GetAppLaunchOrdinal(extension_misc::kChromeAppId)); 495 } 496 } 497 498 void ChromeAppSorting::AddOrdinalMapping( 499 const std::string& extension_id, 500 const syncer::StringOrdinal& page_ordinal, 501 const syncer::StringOrdinal& app_launch_ordinal) { 502 if (!page_ordinal.IsValid() || !app_launch_ordinal.IsValid()) 503 return; 504 505 ntp_ordinal_map_[page_ordinal].insert( 506 std::make_pair(app_launch_ordinal, extension_id)); 507 } 508 509 void ChromeAppSorting::RemoveOrdinalMapping( 510 const std::string& extension_id, 511 const syncer::StringOrdinal& page_ordinal, 512 const syncer::StringOrdinal& app_launch_ordinal) { 513 if (!page_ordinal.IsValid() || !app_launch_ordinal.IsValid()) 514 return; 515 516 // Check that the page exists using find to prevent creating a new page 517 // if |page_ordinal| isn't a used page. 518 PageOrdinalMap::iterator page_map = ntp_ordinal_map_.find(page_ordinal); 519 if (page_map == ntp_ordinal_map_.end()) 520 return; 521 522 for (AppLaunchOrdinalMap::iterator it = 523 page_map->second.find(app_launch_ordinal); 524 it != page_map->second.end(); ++it) { 525 if (it->second == extension_id) { 526 page_map->second.erase(it); 527 break; 528 } 529 } 530 } 531 532 void ChromeAppSorting::SyncIfNeeded(const std::string& extension_id) { 533 if (extension_sync_service_) 534 extension_sync_service_->SyncOrderingChange(extension_id); 535 } 536 537 void ChromeAppSorting::CreateDefaultOrdinals() { 538 if (default_ordinals_created_) 539 return; 540 default_ordinals_created_ = true; 541 542 // The following defines the default order of apps. 543 #if defined(OS_CHROMEOS) 544 std::vector<std::string> app_ids; 545 chromeos::default_app_order::Get(&app_ids); 546 #else 547 const char* kDefaultAppOrder[] = { 548 extension_misc::kChromeAppId, 549 extension_misc::kWebStoreAppId, 550 }; 551 const std::vector<const char*> app_ids( 552 kDefaultAppOrder, kDefaultAppOrder + arraysize(kDefaultAppOrder)); 553 #endif 554 555 syncer::StringOrdinal page_ordinal = CreateFirstAppPageOrdinal(); 556 syncer::StringOrdinal app_launch_ordinal = 557 CreateFirstAppLaunchOrdinal(page_ordinal); 558 for (size_t i = 0; i < app_ids.size(); ++i) { 559 const std::string extension_id = app_ids[i]; 560 default_ordinals_[extension_id].page_ordinal = page_ordinal; 561 default_ordinals_[extension_id].app_launch_ordinal = app_launch_ordinal; 562 app_launch_ordinal = app_launch_ordinal.CreateAfter(); 563 } 564 } 565 566 bool ChromeAppSorting::GetDefaultOrdinals( 567 const std::string& extension_id, 568 syncer::StringOrdinal* page_ordinal, 569 syncer::StringOrdinal* app_launch_ordinal) { 570 CreateDefaultOrdinals(); 571 AppOrdinalsMap::const_iterator it = default_ordinals_.find(extension_id); 572 if (it == default_ordinals_.end()) 573 return false; 574 575 if (page_ordinal) 576 *page_ordinal = it->second.page_ordinal; 577 if (app_launch_ordinal) 578 *app_launch_ordinal = it->second.app_launch_ordinal; 579 return true; 580 } 581 582 syncer::StringOrdinal ChromeAppSorting::ResolveCollision( 583 const syncer::StringOrdinal& page_ordinal, 584 const syncer::StringOrdinal& app_launch_ordinal) const { 585 DCHECK(page_ordinal.IsValid() && app_launch_ordinal.IsValid()); 586 587 PageOrdinalMap::const_iterator page_it = ntp_ordinal_map_.find(page_ordinal); 588 if (page_it == ntp_ordinal_map_.end()) 589 return app_launch_ordinal; 590 591 const AppLaunchOrdinalMap& page = page_it->second; 592 AppLaunchOrdinalMap::const_iterator app_it = page.find(app_launch_ordinal); 593 if (app_it == page.end()) 594 return app_launch_ordinal; 595 596 // Finds the next app launcher ordinal. This is done by the following loop 597 // because this function could be called before FixNTPOrdinalCollisions and 598 // thus |page| might contains multiple entries with the same app launch 599 // ordinal. See http://crbug.com/155603 600 while (app_it != page.end() && app_launch_ordinal.Equals(app_it->first)) 601 ++app_it; 602 603 // If there is no next after the collision, returns the next ordinal. 604 if (app_it == page.end()) 605 return app_launch_ordinal.CreateAfter(); 606 607 // Otherwise, returns the ordinal between the collision and the next ordinal. 608 return app_launch_ordinal.CreateBetween(app_it->first); 609 } 610 611 size_t ChromeAppSorting::CountItemsVisibleOnNtp( 612 const AppLaunchOrdinalMap& m) const { 613 size_t result = 0; 614 for (AppLaunchOrdinalMap::const_iterator it = m.begin(); it != m.end(); 615 ++it) { 616 const std::string& id = it->second; 617 if (ntp_hidden_extensions_.count(id) == 0) 618 result++; 619 } 620 return result; 621 } 622 623 } // namespace extensions 624