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
AppOrdinals()42 ChromeAppSorting::AppOrdinals::AppOrdinals() {}
43
~AppOrdinals()44 ChromeAppSorting::AppOrdinals::~AppOrdinals() {}
45
46 ////////////////////////////////////////////////////////////////////////////////
47 // ChromeAppSorting
48
ChromeAppSorting()49 ChromeAppSorting::ChromeAppSorting()
50 : extension_scoped_prefs_(NULL),
51 extension_sync_service_(NULL),
52 default_ordinals_created_(false) {
53 }
54
~ChromeAppSorting()55 ChromeAppSorting::~ChromeAppSorting() {
56 }
57
SetExtensionScopedPrefs(ExtensionScopedPrefs * prefs)58 void ChromeAppSorting::SetExtensionScopedPrefs(ExtensionScopedPrefs* prefs) {
59 extension_scoped_prefs_ = prefs;
60 }
61
SetExtensionSyncService(ExtensionSyncService * extension_sync_service)62 void ChromeAppSorting::SetExtensionSyncService(
63 ExtensionSyncService* extension_sync_service) {
64 extension_sync_service_ = extension_sync_service;
65 }
66
Initialize(const extensions::ExtensionIdList & extension_ids)67 void ChromeAppSorting::Initialize(
68 const extensions::ExtensionIdList& extension_ids) {
69 InitializePageOrdinalMap(extension_ids);
70
71 MigrateAppIndex(extension_ids);
72 }
73
CreateOrdinalsIfNecessary(size_t minimum_size)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
MigrateAppIndex(const extensions::ExtensionIdList & extension_ids)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
FixNTPOrdinalCollisions()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
EnsureValidOrdinals(const std::string & extension_id,const syncer::StringOrdinal & suggested_page)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
OnExtensionMoved(const std::string & moved_extension_id,const std::string & predecessor_extension_id,const std::string & successor_extension_id)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
GetAppLaunchOrdinal(const std::string & extension_id) const290 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
SetAppLaunchOrdinal(const std::string & extension_id,const syncer::StringOrdinal & new_app_launch_ordinal)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
CreateFirstAppLaunchOrdinal(const syncer::StringOrdinal & page_ordinal) const326 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
CreateNextAppLaunchOrdinal(const syncer::StringOrdinal & page_ordinal) const338 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
CreateFirstAppPageOrdinal() const350 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
GetNaturalAppPageOrdinal() const357 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
GetPageOrdinal(const std::string & extension_id) const372 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
SetPageOrdinal(const std::string & extension_id,const syncer::StringOrdinal & new_page_ordinal)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
ClearOrdinals(const std::string & extension_id)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
PageStringOrdinalAsInteger(const syncer::StringOrdinal & page_ordinal) const416 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
PageIntegerAsStringOrdinal(size_t page_index)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
MarkExtensionAsHidden(const std::string & extension_id)438 void ChromeAppSorting::MarkExtensionAsHidden(const std::string& extension_id) {
439 ntp_hidden_extensions_.insert(extension_id);
440 }
441
GetMinOrMaxAppLaunchOrdinalsOnPage(const syncer::StringOrdinal & target_page_ordinal,AppLaunchOrdinalReturn return_type) const442 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
InitializePageOrdinalMap(const extensions::ExtensionIdList & extension_ids)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
AddOrdinalMapping(const std::string & extension_id,const syncer::StringOrdinal & page_ordinal,const syncer::StringOrdinal & app_launch_ordinal)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
RemoveOrdinalMapping(const std::string & extension_id,const syncer::StringOrdinal & page_ordinal,const syncer::StringOrdinal & app_launch_ordinal)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
SyncIfNeeded(const std::string & extension_id)532 void ChromeAppSorting::SyncIfNeeded(const std::string& extension_id) {
533 if (extension_sync_service_)
534 extension_sync_service_->SyncOrderingChange(extension_id);
535 }
536
CreateDefaultOrdinals()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
GetDefaultOrdinals(const std::string & extension_id,syncer::StringOrdinal * page_ordinal,syncer::StringOrdinal * app_launch_ordinal)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
ResolveCollision(const syncer::StringOrdinal & page_ordinal,const syncer::StringOrdinal & app_launch_ordinal) const582 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
CountItemsVisibleOnNtp(const AppLaunchOrdinalMap & m) const611 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