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/background/background_application_list_model.h"
10 #include "base/sha1.h"
11 #include "base/stl_util.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/app/chrome_command_ids.h"
15 #include "chrome/browser/background/background_contents_service.h"
16 #include "chrome/browser/background/background_contents_service_factory.h"
17 #include "chrome/browser/background/background_mode_manager.h"
18 #include "chrome/browser/browser_process.h"
19 #include "chrome/browser/chrome_notification_types.h"
20 #include "chrome/browser/extensions/extension_prefs.h"
21 #include "chrome/browser/extensions/extension_service.h"
22 #include "chrome/browser/extensions/extension_system.h"
23 #include "chrome/browser/extensions/image_loader.h"
24 #include "chrome/browser/profiles/profile.h"
25 #include "chrome/common/extensions/extension_constants.h"
26 #include "chrome/common/extensions/extension_icon_set.h"
27 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
28 #include "content/public/browser/notification_details.h"
29 #include "content/public/browser/notification_source.h"
30 #include "extensions/browser/extension_registry.h"
31 #include "extensions/common/extension.h"
32 #include "extensions/common/extension_resource.h"
33 #include "extensions/common/extension_set.h"
34 #include "extensions/common/manifest_handlers/background_info.h"
35 #include "extensions/common/permissions/permission_set.h"
36 #include "ui/base/l10n/l10n_util_collator.h"
37 #include "ui/gfx/image/image.h"
38 #include "ui/gfx/image/image_skia.h"
40 using extensions::APIPermission
;
41 using extensions::Extension
;
42 using extensions::ExtensionList
;
43 using extensions::ExtensionRegistry
;
44 using extensions::ExtensionSet
;
45 using extensions::PermissionSet
;
46 using extensions::UnloadedExtensionInfo
;
47 using extensions::UpdatedExtensionPermissionsInfo
;
49 class ExtensionNameComparator
{
51 explicit ExtensionNameComparator(icu::Collator
* collator
);
52 bool operator()(const scoped_refptr
<const Extension
>& x
,
53 const scoped_refptr
<const Extension
>& y
);
56 icu::Collator
* collator_
;
59 ExtensionNameComparator::ExtensionNameComparator(icu::Collator
* collator
)
60 : collator_(collator
) {
63 bool ExtensionNameComparator::operator()(
64 const scoped_refptr
<const Extension
>& x
,
65 const scoped_refptr
<const Extension
>& y
) {
66 return l10n_util::StringComparator
<base::string16
>(collator_
)(
67 base::UTF8ToUTF16(x
->name()), base::UTF8ToUTF16(y
->name()));
70 // Background application representation, private to the
71 // BackgroundApplicationListModel class.
72 class BackgroundApplicationListModel::Application
73 : public base::SupportsWeakPtr
<Application
> {
75 Application(BackgroundApplicationListModel
* model
,
76 const Extension
* an_extension
);
78 virtual ~Application();
80 // Invoked when a request icon is available.
81 void OnImageLoaded(const gfx::Image
& image
);
83 // Uses the FILE thread to request this extension's icon, sized
85 void RequestIcon(extension_misc::ExtensionIcons size
);
87 const Extension
* extension_
;
88 scoped_ptr
<gfx::ImageSkia
> icon_
;
89 BackgroundApplicationListModel
* model_
;
93 void GetServiceApplications(ExtensionService
* service
,
94 ExtensionList
* applications_result
) {
95 ExtensionRegistry
* registry
= ExtensionRegistry::Get(service
->profile());
96 const ExtensionSet
& enabled_extensions
= registry
->enabled_extensions();
98 for (ExtensionSet::const_iterator cursor
= enabled_extensions
.begin();
99 cursor
!= enabled_extensions
.end();
101 const Extension
* extension
= cursor
->get();
102 if (BackgroundApplicationListModel::IsBackgroundApp(*extension
,
103 service
->profile())) {
104 applications_result
->push_back(extension
);
108 // Walk the list of terminated extensions also (just because an extension
109 // crashed doesn't mean we should ignore it).
110 const ExtensionSet
& terminated_extensions
= registry
->terminated_extensions();
111 for (ExtensionSet::const_iterator cursor
= terminated_extensions
.begin();
112 cursor
!= terminated_extensions
.end();
114 const Extension
* extension
= cursor
->get();
115 if (BackgroundApplicationListModel::IsBackgroundApp(*extension
,
116 service
->profile())) {
117 applications_result
->push_back(extension
);
121 std::string locale
= g_browser_process
->GetApplicationLocale();
122 icu::Locale
loc(locale
.c_str());
123 UErrorCode error
= U_ZERO_ERROR
;
124 scoped_ptr
<icu::Collator
> collator(icu::Collator::createInstance(loc
, error
));
125 std::sort(applications_result
->begin(), applications_result
->end(),
126 ExtensionNameComparator(collator
.get()));
132 BackgroundApplicationListModel::Observer::OnApplicationDataChanged(
133 const Extension
* extension
, Profile
* profile
) {
137 BackgroundApplicationListModel::Observer::OnApplicationListChanged(
141 BackgroundApplicationListModel::Observer::~Observer() {
144 BackgroundApplicationListModel::Application::~Application() {
147 BackgroundApplicationListModel::Application::Application(
148 BackgroundApplicationListModel
* model
,
149 const Extension
* extension
)
150 : extension_(extension
), model_(model
) {}
152 void BackgroundApplicationListModel::Application::OnImageLoaded(
153 const gfx::Image
& image
) {
156 icon_
.reset(image
.CopyImageSkia());
157 model_
->SendApplicationDataChangedNotifications(extension_
);
160 void BackgroundApplicationListModel::Application::RequestIcon(
161 extension_misc::ExtensionIcons size
) {
162 extensions::ExtensionResource resource
=
163 extensions::IconsInfo::GetIconResource(
164 extension_
, size
, ExtensionIconSet::MATCH_BIGGER
);
165 extensions::ImageLoader::Get(model_
->profile_
)->LoadImageAsync(
166 extension_
, resource
, gfx::Size(size
, size
),
167 base::Bind(&Application::OnImageLoaded
, AsWeakPtr()));
170 BackgroundApplicationListModel::~BackgroundApplicationListModel() {
171 STLDeleteContainerPairSecondPointers(applications_
.begin(),
172 applications_
.end());
175 BackgroundApplicationListModel::BackgroundApplicationListModel(Profile
* profile
)
176 : profile_(profile
) {
179 chrome::NOTIFICATION_EXTENSION_LOADED
,
180 content::Source
<Profile
>(profile
));
182 chrome::NOTIFICATION_EXTENSION_UNLOADED
,
183 content::Source
<Profile
>(profile
));
185 chrome::NOTIFICATION_EXTENSIONS_READY
,
186 content::Source
<Profile
>(profile
));
188 chrome::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED
,
189 content::Source
<Profile
>(profile
));
191 chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED
,
192 content::Source
<Profile
>(profile
));
193 ExtensionService
* service
= extensions::ExtensionSystem::Get(profile
)->
195 if (service
&& service
->is_ready())
199 void BackgroundApplicationListModel::AddObserver(Observer
* observer
) {
200 observers_
.AddObserver(observer
);
203 void BackgroundApplicationListModel::AssociateApplicationData(
204 const Extension
* extension
) {
205 DCHECK(IsBackgroundApp(*extension
, profile_
));
206 Application
* application
= FindApplication(extension
);
208 // App position is used as a dynamic command and so must be less than any
209 // predefined command id.
210 if (applications_
.size() >= IDC_MinimumLabelValue
) {
211 LOG(ERROR
) << "Background application limit of " << IDC_MinimumLabelValue
212 << " exceeded. Ignoring.";
215 application
= new Application(this, extension
);
216 applications_
[extension
->id()] = application
;
218 application
->RequestIcon(extension_misc::EXTENSION_ICON_BITTY
);
222 void BackgroundApplicationListModel::DissociateApplicationData(
223 const Extension
* extension
) {
224 ApplicationMap::iterator found
= applications_
.find(extension
->id());
225 if (found
!= applications_
.end()) {
226 delete found
->second
;
227 applications_
.erase(found
);
231 const Extension
* BackgroundApplicationListModel::GetExtension(
232 int position
) const {
233 DCHECK(position
>= 0 && static_cast<size_t>(position
) < extensions_
.size());
234 return extensions_
[position
].get();
237 const BackgroundApplicationListModel::Application
*
238 BackgroundApplicationListModel::FindApplication(
239 const Extension
* extension
) const {
240 const std::string
& id
= extension
->id();
241 ApplicationMap::const_iterator found
= applications_
.find(id
);
242 return (found
== applications_
.end()) ? NULL
: found
->second
;
245 BackgroundApplicationListModel::Application
*
246 BackgroundApplicationListModel::FindApplication(
247 const Extension
* extension
) {
248 const std::string
& id
= extension
->id();
249 ApplicationMap::iterator found
= applications_
.find(id
);
250 return (found
== applications_
.end()) ? NULL
: found
->second
;
253 const gfx::ImageSkia
* BackgroundApplicationListModel::GetIcon(
254 const Extension
* extension
) {
255 const Application
* application
= FindApplication(extension
);
257 return application
->icon_
.get();
258 AssociateApplicationData(extension
);
262 int BackgroundApplicationListModel::GetPosition(
263 const Extension
* extension
) const {
265 const std::string
& id
= extension
->id();
266 for (ExtensionList::const_iterator cursor
= extensions_
.begin();
267 cursor
!= extensions_
.end();
268 ++cursor
, ++position
) {
269 if (id
== cursor
->get()->id())
277 bool BackgroundApplicationListModel::RequiresBackgroundModeForPushMessaging(
278 const Extension
& extension
) {
279 // No PushMessaging permission - does not require the background mode.
280 if (!extension
.HasAPIPermission(APIPermission::kPushMessaging
))
283 // If in the whitelist, then does not require background mode even if
284 // uses push messaging.
285 // TODO(dimich): remove this whitelist once we have a better way to keep
286 // listening for GCM. http://crbug.com/311268
287 std::string id_hash
= base::SHA1HashString(extension
.id());
288 std::string hexencoded_id_hash
= base::HexEncode(id_hash
.c_str(),
290 // The id starting from "9A04..." is a one from unit test.
291 if (hexencoded_id_hash
== "C41AD9DCD670210295614257EF8C9945AD68D86E" ||
292 hexencoded_id_hash
== "9A0417016F345C934A1A88F55CA17C05014EEEBA")
299 bool BackgroundApplicationListModel::IsBackgroundApp(
300 const Extension
& extension
, Profile
* profile
) {
301 // An extension is a "background app" if it has the "background API"
302 // permission, and meets one of the following criteria:
303 // 1) It is an extension (not a hosted app).
304 // 2) It is a hosted app, and has a background contents registered or in the
307 // Not a background app if we don't have the background permission or
308 // the push messaging permission
309 if (!extension
.HasAPIPermission(APIPermission::kBackground
) &&
310 !RequiresBackgroundModeForPushMessaging(extension
))
313 // Extensions and packaged apps with background permission are always treated
314 // as background apps.
315 if (!extension
.is_hosted_app())
318 // Hosted apps with manifest-provided background pages are background apps.
319 if (extensions::BackgroundInfo::HasBackgroundPage(&extension
))
322 BackgroundContentsService
* service
=
323 BackgroundContentsServiceFactory::GetForProfile(profile
);
324 base::string16 app_id
= base::ASCIIToUTF16(extension
.id());
325 // If we have an active or registered background contents for this app, then
326 // it's a background app. This covers the cases where the app has created its
327 // background contents, but it hasn't navigated yet, or the background
328 // contents crashed and hasn't yet been restarted - in both cases we still
329 // want to treat the app as a background app.
330 if (service
->GetAppBackgroundContents(app_id
) ||
331 service
->HasRegisteredBackgroundContents(app_id
)) {
335 // Doesn't meet our criteria, so it's not a background app.
339 void BackgroundApplicationListModel::Observe(
341 const content::NotificationSource
& source
,
342 const content::NotificationDetails
& details
) {
343 if (type
== chrome::NOTIFICATION_EXTENSIONS_READY
) {
347 ExtensionService
* service
= extensions::ExtensionSystem::Get(profile_
)->
349 if (!service
|| !service
->is_ready())
353 case chrome::NOTIFICATION_EXTENSION_LOADED
:
354 OnExtensionLoaded(content::Details
<Extension
>(details
).ptr());
356 case chrome::NOTIFICATION_EXTENSION_UNLOADED
:
358 content::Details
<UnloadedExtensionInfo
>(details
)->extension
);
360 case chrome::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED
:
361 OnExtensionPermissionsUpdated(
362 content::Details
<UpdatedExtensionPermissionsInfo
>(details
)->extension
,
363 content::Details
<UpdatedExtensionPermissionsInfo
>(details
)->reason
,
364 content::Details
<UpdatedExtensionPermissionsInfo
>(details
)->
367 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED
:
371 NOTREACHED() << "Received unexpected notification";
375 void BackgroundApplicationListModel::SendApplicationDataChangedNotifications(
376 const Extension
* extension
) {
377 FOR_EACH_OBSERVER(Observer
, observers_
, OnApplicationDataChanged(extension
,
381 void BackgroundApplicationListModel::OnExtensionLoaded(
382 const Extension
* extension
) {
383 // We only care about extensions that are background applications
384 if (!IsBackgroundApp(*extension
, profile_
))
386 AssociateApplicationData(extension
);
389 void BackgroundApplicationListModel::OnExtensionUnloaded(
390 const Extension
* extension
) {
391 if (!IsBackgroundApp(*extension
, profile_
))
394 DissociateApplicationData(extension
);
397 void BackgroundApplicationListModel::OnExtensionPermissionsUpdated(
398 const Extension
* extension
,
399 UpdatedExtensionPermissionsInfo::Reason reason
,
400 const PermissionSet
* permissions
) {
401 if (permissions
->HasAPIPermission(APIPermission::kBackground
)) {
403 case UpdatedExtensionPermissionsInfo::ADDED
:
404 DCHECK(IsBackgroundApp(*extension
, profile_
));
405 OnExtensionLoaded(extension
);
407 case UpdatedExtensionPermissionsInfo::REMOVED
:
408 DCHECK(!IsBackgroundApp(*extension
, profile_
));
410 DissociateApplicationData(extension
);
418 void BackgroundApplicationListModel::RemoveObserver(Observer
* observer
) {
419 observers_
.RemoveObserver(observer
);
422 // Update queries the extensions service of the profile with which the model was
423 // initialized to determine the current set of background applications. If that
424 // differs from the old list, it generates OnApplicationListChanged events for
426 void BackgroundApplicationListModel::Update() {
427 ExtensionService
* service
= extensions::ExtensionSystem::Get(profile_
)->
430 // Discover current background applications, compare with previous list, which
431 // is consistently sorted, and notify observers if they differ.
432 ExtensionList extensions
;
433 GetServiceApplications(service
, &extensions
);
434 ExtensionList::const_iterator old_cursor
= extensions_
.begin();
435 ExtensionList::const_iterator new_cursor
= extensions
.begin();
436 while (old_cursor
!= extensions_
.end() &&
437 new_cursor
!= extensions
.end() &&
438 (*old_cursor
)->name() == (*new_cursor
)->name() &&
439 (*old_cursor
)->id() == (*new_cursor
)->id()) {
443 if (old_cursor
!= extensions_
.end() || new_cursor
!= extensions
.end()) {
444 extensions_
= extensions
;
445 FOR_EACH_OBSERVER(Observer
, observers_
, OnApplicationListChanged(profile_
));