Home | History | Annotate | Download | only in extensions
      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