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.
5 #include "chrome/browser/extensions/extension_sorting.h"
7 #include "chrome/browser/extensions/extension_scoped_prefs.h"
8 #include "chrome/browser/extensions/extension_service.h"
9 #include "chrome/common/chrome_notification_types.h"
10 #include "content/public/browser/notification_service.h"
14 // The number of apps per page. This isn't a hard limit, but new apps installed
15 // from the webstore will overflow onto a new page if this limit is reached.
16 const size_t kNaturalAppPageSize
= 18;
18 // A preference determining the order of which the apps appear on the NTP.
19 const char kPrefAppLaunchIndexDeprecated
[] = "app_launcher_index";
20 const char kPrefAppLaunchOrdinal
[] = "app_launcher_ordinal";
22 // A preference determining the page on which an app appears in the NTP.
23 const char kPrefPageIndexDeprecated
[] = "page_index";
24 const char kPrefPageOrdinal
[] = "page_ordinal";
28 ExtensionSorting::ExtensionSorting(ExtensionScopedPrefs
* extension_scoped_prefs
,
29 PrefService
* pref_service
)
30 : extension_scoped_prefs_(extension_scoped_prefs
),
31 pref_service_(pref_service
) {
34 ExtensionSorting::~ExtensionSorting() {
37 void ExtensionSorting::Initialize(
38 const ExtensionPrefs::ExtensionIdSet
& extension_ids
) {
39 InitializePageOrdinalMap(extension_ids
);
41 MigrateAppIndex(extension_ids
);
44 void ExtensionSorting::MigrateAppIndex(
45 const ExtensionPrefs::ExtensionIdSet
& extension_ids
) {
46 if (extension_ids
.empty())
49 // Convert all the page index values to page ordinals. If there are any
50 // app launch values that need to be migrated, inserted them into a sorted
51 // set to be dealt with later.
52 typedef std::map
<StringOrdinal
, std::map
<int, const std::string
*>,
53 StringOrdinalLessThan
> AppPositionToIdMapping
;
54 AppPositionToIdMapping app_launches_to_convert
;
55 for (ExtensionPrefs::ExtensionIdSet::const_iterator ext_id
=
56 extension_ids
.begin(); ext_id
!= extension_ids
.end(); ++ext_id
) {
57 int old_page_index
= 0;
58 StringOrdinal page
= GetPageOrdinal(*ext_id
);
59 if (extension_scoped_prefs_
->ReadExtensionPrefInteger(
61 kPrefPageIndexDeprecated
,
63 // Some extensions have invalid page index, so we don't
64 // attempt to convert them.
65 if (old_page_index
< 0) {
66 DLOG(WARNING
) << "Extension " << *ext_id
67 << " has an invalid page index " << old_page_index
68 << ". Aborting attempt to convert its index.";
72 // Since we require all earlier StringOrdinals to already exist in order
73 // to properly convert from integers and we are iterating though them in
74 // no given order, we create earlier StringOrdinal values as required.
75 // This should be filled in by the time we are done with this loop.
76 if (ntp_ordinal_map_
.empty())
77 ntp_ordinal_map_
[StringOrdinal::CreateInitialOrdinal()];
78 while (ntp_ordinal_map_
.size()
79 <= static_cast<size_t>(old_page_index
)) {
80 StringOrdinal earlier_page
=
81 ntp_ordinal_map_
.rbegin()->first
.CreateAfter();
82 ntp_ordinal_map_
[earlier_page
];
85 page
= PageIntegerAsStringOrdinal(old_page_index
);
86 SetPageOrdinal(*ext_id
, page
);
87 extension_scoped_prefs_
->UpdateExtensionPref(
88 *ext_id
, kPrefPageIndexDeprecated
, NULL
);
91 int old_app_launch_index
= 0;
92 if (extension_scoped_prefs_
->ReadExtensionPrefInteger(
94 kPrefAppLaunchIndexDeprecated
,
95 &old_app_launch_index
)) {
96 // We can't update the app launch index value yet, because we use
97 // GetNextAppLaunchOrdinal to get the new ordinal value and it requires
98 // all the ordinals with lower values to have already been migrated.
99 // A valid page ordinal is also required because otherwise there is
100 // no page to add the app to.
102 app_launches_to_convert
[page
][old_app_launch_index
] = &*ext_id
;
104 extension_scoped_prefs_
->UpdateExtensionPref(
105 *ext_id
, kPrefAppLaunchIndexDeprecated
, NULL
);
109 // Remove any empty pages that may have been added. This shouldn't occur,
110 // but double check here to prevent future problems with conversions between
111 // integers and StringOrdinals.
112 for (PageOrdinalMap::iterator it
= ntp_ordinal_map_
.begin();
113 it
!= ntp_ordinal_map_
.end();) {
114 if (it
->second
.empty()) {
115 PageOrdinalMap::iterator prev_it
= it
;
117 ntp_ordinal_map_
.erase(prev_it
);
123 if (app_launches_to_convert
.empty())
126 // Create the new app launch ordinals and remove the old preferences. Since
127 // the set is sorted, each time we migrate an apps index, we know that all of
128 // the remaining apps will appear further down the NTP than it or on a
130 for (AppPositionToIdMapping::const_iterator page_it
=
131 app_launches_to_convert
.begin();
132 page_it
!= app_launches_to_convert
.end(); ++page_it
) {
133 StringOrdinal page
= page_it
->first
;
134 for (std::map
<int, const std::string
*>::const_iterator launch_it
=
135 page_it
->second
.begin(); launch_it
!= page_it
->second
.end();
137 SetAppLaunchOrdinal(*(launch_it
->second
),
138 CreateNextAppLaunchOrdinal(page
));
143 void ExtensionSorting::FixNTPOrdinalCollisions() {
144 for (PageOrdinalMap::iterator page_it
= ntp_ordinal_map_
.begin();
145 page_it
!= ntp_ordinal_map_
.end(); ++page_it
) {
146 AppLaunchOrdinalMap
& page
= page_it
->second
;
148 for (AppLaunchOrdinalMap::iterator app_launch_it
= page
.begin();
149 app_launch_it
!= page
.end(); ++app_launch_it
) {
150 int collisions
= page
.count(app_launch_it
->first
);
154 StringOrdinal repeated_ordinal
= app_launch_it
->first
;
156 // Sort the conflicting keys by their extension id, this is how
157 // the order is decided.
158 std::vector
<std::string
> conflicting_ids
;
159 for (int i
= 0; i
< collisions
; ++i
, ++app_launch_it
)
160 conflicting_ids
.push_back(app_launch_it
->second
);
161 std::sort(conflicting_ids
.begin(), conflicting_ids
.end());
163 StringOrdinal upper_bound_ordinal
= app_launch_it
== page
.end() ?
165 app_launch_it
->first
;
166 StringOrdinal lower_bound_ordinal
= repeated_ordinal
;
168 // Start at position 1 because the first extension can keep the conflicted
170 for (int i
= 1; i
< collisions
; ++i
) {
171 StringOrdinal unique_app_launch
;
172 if (upper_bound_ordinal
.IsValid()) {
174 lower_bound_ordinal
.CreateBetween(upper_bound_ordinal
);
176 unique_app_launch
= lower_bound_ordinal
.CreateAfter();
179 SetAppLaunchOrdinal(conflicting_ids
[i
], unique_app_launch
);
180 lower_bound_ordinal
= unique_app_launch
;
183 // This exit condition exists because the iterator may have moved to
184 // the end of the map after getting the conflicting ids.
185 if (app_launch_it
== page
.end())
191 void ExtensionSorting::OnExtensionMoved(
192 const std::string
& moved_extension_id
,
193 const std::string
& predecessor_extension_id
,
194 const std::string
& successor_extension_id
) {
195 // If there are no neighbors then no changes are required, so we can return.
196 if (predecessor_extension_id
.empty() && successor_extension_id
.empty())
199 if (predecessor_extension_id
.empty()) {
202 GetAppLaunchOrdinal(successor_extension_id
).CreateBefore());
203 } else if (successor_extension_id
.empty()) {
206 GetAppLaunchOrdinal(predecessor_extension_id
).CreateAfter());
208 const StringOrdinal
& predecessor_ordinal
=
209 GetAppLaunchOrdinal(predecessor_extension_id
);
210 const StringOrdinal
& successor_ordinal
=
211 GetAppLaunchOrdinal(successor_extension_id
);
212 SetAppLaunchOrdinal(moved_extension_id
,
213 predecessor_ordinal
.CreateBetween(successor_ordinal
));
216 content::NotificationService::current()->Notify(
217 chrome::NOTIFICATION_EXTENSION_LAUNCHER_REORDERED
,
218 content::Source
<ExtensionSorting
>(this),
219 content::NotificationService::NoDetails());
223 StringOrdinal
ExtensionSorting::GetAppLaunchOrdinal(
224 const std::string
& extension_id
) const {
225 std::string raw_value
;
226 // If the preference read fails then raw_value will still be unset and we
227 // will return an invalid StringOrdinal to signal that no app launch ordinal
229 extension_scoped_prefs_
->ReadExtensionPrefString(
230 extension_id
, kPrefAppLaunchOrdinal
, &raw_value
);
231 return StringOrdinal(raw_value
);
234 void ExtensionSorting::SetAppLaunchOrdinal(
235 const std::string
& extension_id
,
236 const StringOrdinal
& new_app_launch_ordinal
) {
237 StringOrdinal page_ordinal
= GetPageOrdinal(extension_id
);
238 RemoveOrdinalMapping(
239 extension_id
, page_ordinal
, GetAppLaunchOrdinal(extension_id
));
240 AddOrdinalMapping(extension_id
, page_ordinal
, new_app_launch_ordinal
);
242 extension_scoped_prefs_
->UpdateExtensionPref(
244 kPrefAppLaunchOrdinal
,
245 Value::CreateStringValue(new_app_launch_ordinal
.ToString()));
248 StringOrdinal
ExtensionSorting::CreateFirstAppLaunchOrdinal(
249 const StringOrdinal
& page_ordinal
) const {
250 const StringOrdinal
& min_ordinal
=
251 GetMinOrMaxAppLaunchOrdinalsOnPage(page_ordinal
,
252 ExtensionSorting::MIN_ORDINAL
);
254 if (min_ordinal
.IsValid())
255 return min_ordinal
.CreateBefore();
257 return StringOrdinal::CreateInitialOrdinal();
260 StringOrdinal
ExtensionSorting::CreateNextAppLaunchOrdinal(
261 const StringOrdinal
& page_ordinal
) const {
262 const StringOrdinal
& max_ordinal
=
263 GetMinOrMaxAppLaunchOrdinalsOnPage(page_ordinal
,
264 ExtensionSorting::MAX_ORDINAL
);
266 if (max_ordinal
.IsValid())
267 return max_ordinal
.CreateAfter();
269 return StringOrdinal::CreateInitialOrdinal();
272 StringOrdinal
ExtensionSorting::CreateFirstAppPageOrdinal() const {
273 const DictionaryValue
* extensions
= pref_service_
->GetDictionary(
274 ExtensionPrefs::kExtensionsPref
);
277 if (ntp_ordinal_map_
.empty())
278 return StringOrdinal::CreateInitialOrdinal();
280 return ntp_ordinal_map_
.begin()->first
;
283 StringOrdinal
ExtensionSorting::GetNaturalAppPageOrdinal() const {
284 const DictionaryValue
* extensions
= pref_service_
->GetDictionary(
285 ExtensionPrefs::kExtensionsPref
);
288 if (ntp_ordinal_map_
.empty())
289 return StringOrdinal::CreateInitialOrdinal();
291 for (PageOrdinalMap::const_iterator it
= ntp_ordinal_map_
.begin();
292 it
!= ntp_ordinal_map_
.end(); ++it
) {
293 if (it
->second
.size() < kNaturalAppPageSize
)
297 // Add a new page as all existing pages are full.
298 StringOrdinal last_element
= ntp_ordinal_map_
.rbegin()->first
;
299 return last_element
.CreateAfter();
302 StringOrdinal
ExtensionSorting::GetPageOrdinal(const std::string
& extension_id
)
304 std::string raw_data
;
305 // If the preference read fails then raw_data will still be unset and we will
306 // return an invalid StringOrdinal to signal that no page ordinal was found.
307 extension_scoped_prefs_
->ReadExtensionPrefString(
308 extension_id
, kPrefPageOrdinal
, &raw_data
);
309 return StringOrdinal(raw_data
);
312 void ExtensionSorting::SetPageOrdinal(const std::string
& extension_id
,
313 const StringOrdinal
& new_page_ordinal
) {
314 StringOrdinal app_launch_ordinal
= GetAppLaunchOrdinal(extension_id
);
315 RemoveOrdinalMapping(
316 extension_id
, GetPageOrdinal(extension_id
), app_launch_ordinal
);
317 AddOrdinalMapping(extension_id
, new_page_ordinal
, app_launch_ordinal
);
319 extension_scoped_prefs_
->UpdateExtensionPref(
322 Value::CreateStringValue(new_page_ordinal
.ToString()));
325 void ExtensionSorting::ClearPageOrdinal(const std::string
& extension_id
) {
326 RemoveOrdinalMapping(extension_id
,
327 GetPageOrdinal(extension_id
),
328 GetAppLaunchOrdinal(extension_id
));
330 extension_scoped_prefs_
->UpdateExtensionPref(
331 extension_id
, kPrefPageOrdinal
, NULL
);
334 int ExtensionSorting::PageStringOrdinalAsInteger(
335 const StringOrdinal
& page_ordinal
) const {
336 if (!page_ordinal
.IsValid())
339 PageOrdinalMap::const_iterator it
= ntp_ordinal_map_
.find(page_ordinal
);
340 if (it
!= ntp_ordinal_map_
.end()) {
341 return std::distance(ntp_ordinal_map_
.begin(), it
);
347 StringOrdinal
ExtensionSorting::PageIntegerAsStringOrdinal(size_t page_index
)
349 // We shouldn't have a page_index that is more than 1 position away from the
351 CHECK_LE(page_index
, ntp_ordinal_map_
.size());
353 const DictionaryValue
* extensions
= pref_service_
->GetDictionary(
354 ExtensionPrefs::kExtensionsPref
);
356 return StringOrdinal();
358 if (page_index
< ntp_ordinal_map_
.size()) {
359 PageOrdinalMap::const_iterator it
= ntp_ordinal_map_
.begin();
360 std::advance(it
, page_index
);
365 if (ntp_ordinal_map_
.empty())
366 return StringOrdinal::CreateInitialOrdinal();
368 return ntp_ordinal_map_
.rbegin()->first
.CreateAfter();
372 StringOrdinal
ExtensionSorting::GetMinOrMaxAppLaunchOrdinalsOnPage(
373 const StringOrdinal
& target_page_ordinal
,
374 AppLaunchOrdinalReturn return_type
) const {
375 const DictionaryValue
* extensions
= pref_service_
->GetDictionary(
376 ExtensionPrefs::kExtensionsPref
);
378 CHECK(target_page_ordinal
.IsValid());
380 StringOrdinal return_value
;
381 for (DictionaryValue::key_iterator ext_it
= extensions
->begin_keys();
382 ext_it
!= extensions
->end_keys(); ++ext_it
) {
383 const StringOrdinal
& page_ordinal
= GetPageOrdinal(*ext_it
);
384 if (page_ordinal
.IsValid() && page_ordinal
.Equal(target_page_ordinal
)) {
385 const StringOrdinal
& app_launch_ordinal
= GetAppLaunchOrdinal(*ext_it
);
386 if (app_launch_ordinal
.IsValid()) {
387 if (!return_value
.IsValid())
388 return_value
= app_launch_ordinal
;
389 else if (return_type
== ExtensionSorting::MIN_ORDINAL
&&
390 app_launch_ordinal
.LessThan(return_value
))
391 return_value
= app_launch_ordinal
;
392 else if (return_type
== ExtensionSorting::MAX_ORDINAL
&&
393 app_launch_ordinal
.GreaterThan(return_value
))
394 return_value
= app_launch_ordinal
;
402 void ExtensionSorting::InitializePageOrdinalMap(
403 const ExtensionPrefs::ExtensionIdSet
& extension_ids
) {
404 for (ExtensionPrefs::ExtensionIdSet::const_iterator ext_it
=
405 extension_ids
.begin(); ext_it
!= extension_ids
.end(); ++ext_it
) {
406 AddOrdinalMapping(*ext_it
,
407 GetPageOrdinal(*ext_it
),
408 GetAppLaunchOrdinal(*ext_it
));
410 // Ensure that the web store app still isn't found in this list, since
411 // it is added after this loop.
412 DCHECK(*ext_it
!= extension_misc::kWebStoreAppId
);
415 // Include the Web Store App since it is displayed on the NTP.
416 StringOrdinal web_store_app_page
=
417 GetPageOrdinal(extension_misc::kWebStoreAppId
);
418 if (web_store_app_page
.IsValid()) {
419 AddOrdinalMapping(extension_misc::kWebStoreAppId
,
421 GetAppLaunchOrdinal(extension_misc::kWebStoreAppId
));
425 void ExtensionSorting::AddOrdinalMapping(
426 const std::string
& extension_id
,
427 const StringOrdinal
& page_ordinal
,
428 const StringOrdinal
& app_launch_ordinal
) {
429 if (!page_ordinal
.IsValid() || !app_launch_ordinal
.IsValid())
432 ntp_ordinal_map_
[page_ordinal
].insert(
433 std::make_pair(app_launch_ordinal
, extension_id
));
436 void ExtensionSorting::RemoveOrdinalMapping(
437 const std::string
& extension_id
,
438 const StringOrdinal
& page_ordinal
,
439 const StringOrdinal
& app_launch_ordinal
) {
440 if (!page_ordinal
.IsValid() || !app_launch_ordinal
.IsValid())
443 // Check that the page exists using find to prevent creating a new page
444 // if |page_ordinal| isn't a used page.
445 PageOrdinalMap::iterator page_map
= ntp_ordinal_map_
.find(page_ordinal
);
446 if (page_map
== ntp_ordinal_map_
.end())
449 for (AppLaunchOrdinalMap::iterator it
=
450 page_map
->second
.find(app_launch_ordinal
);
451 it
!= page_map
->second
.end(); ++it
) {
452 if (it
->second
== extension_id
) {
453 page_map
->second
.erase(it
);