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/stl_util.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "chrome/app/chrome_command_ids.h"
14 #include "chrome/browser/background/background_contents_service.h"
15 #include "chrome/browser/background/background_contents_service_factory.h"
16 #include "chrome/browser/background/background_mode_manager.h"
17 #include "chrome/browser/browser_process.h"
18 #include "chrome/browser/chrome_notification_types.h"
19 #include "chrome/browser/extensions/extension_service.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/common/extensions/extension_constants.h"
22 #include "components/crx_file/id_util.h"
23 #include "components/variations/variations_associated_data.h"
24 #include "content/public/browser/notification_details.h"
25 #include "content/public/browser/notification_source.h"
26 #include "extensions/browser/extension_prefs.h"
27 #include "extensions/browser/extension_registry.h"
28 #include "extensions/browser/extension_system.h"
29 #include "extensions/browser/extension_util.h"
30 #include "extensions/browser/image_loader.h"
31 #include "extensions/browser/notification_types.h"
32 #include "extensions/common/extension.h"
33 #include "extensions/common/extension_icon_set.h"
34 #include "extensions/common/extension_resource.h"
35 #include "extensions/common/extension_set.h"
36 #include "extensions/common/manifest_handlers/background_info.h"
37 #include "extensions/common/manifest_handlers/icons_handler.h"
38 #include "extensions/common/permissions/permission_set.h"
39 #include "extensions/common/permissions/permissions_data.h"
40 #include "ui/base/l10n/l10n_util_collator.h"
41 #include "ui/gfx/image/image.h"
42 #include "ui/gfx/image/image_skia.h"
44 using extensions::APIPermission
;
45 using extensions::Extension
;
46 using extensions::ExtensionList
;
47 using extensions::ExtensionRegistry
;
48 using extensions::ExtensionSet
;
49 using extensions::PermissionSet
;
50 using extensions::UnloadedExtensionInfo
;
51 using extensions::UpdatedExtensionPermissionsInfo
;
53 class ExtensionNameComparator
{
55 explicit ExtensionNameComparator(icu::Collator
* collator
);
56 bool operator()(const scoped_refptr
<const Extension
>& x
,
57 const scoped_refptr
<const Extension
>& y
);
60 icu::Collator
* collator_
;
63 ExtensionNameComparator::ExtensionNameComparator(icu::Collator
* collator
)
64 : collator_(collator
) {
67 bool ExtensionNameComparator::operator()(
68 const scoped_refptr
<const Extension
>& x
,
69 const scoped_refptr
<const Extension
>& y
) {
70 return l10n_util::StringComparator
<base::string16
>(collator_
)(
71 base::UTF8ToUTF16(x
->name()), base::UTF8ToUTF16(y
->name()));
74 class ExtensionIdComparator
{
76 bool operator()(const scoped_refptr
<const Extension
>& x
,
77 const scoped_refptr
<const Extension
>& y
) {
78 return x
->id() < y
->id();
82 // Background application representation, private to the
83 // BackgroundApplicationListModel class.
84 class BackgroundApplicationListModel::Application
85 : public base::SupportsWeakPtr
<Application
> {
87 Application(BackgroundApplicationListModel
* model
,
88 const Extension
* an_extension
);
90 virtual ~Application();
92 // Invoked when a request icon is available.
93 void OnImageLoaded(const gfx::Image
& image
);
95 // Uses the FILE thread to request this extension's icon, sized
97 void RequestIcon(extension_misc::ExtensionIcons size
);
99 const Extension
* extension_
;
100 scoped_ptr
<gfx::ImageSkia
> icon_
;
101 BackgroundApplicationListModel
* model_
;
105 void GetServiceApplications(ExtensionService
* service
,
106 ExtensionList
* applications_result
) {
107 ExtensionRegistry
* registry
= ExtensionRegistry::Get(service
->profile());
108 const ExtensionSet
& enabled_extensions
= registry
->enabled_extensions();
110 for (ExtensionSet::const_iterator cursor
= enabled_extensions
.begin();
111 cursor
!= enabled_extensions
.end();
113 const Extension
* extension
= cursor
->get();
114 if (BackgroundApplicationListModel::IsBackgroundApp(*extension
,
115 service
->profile())) {
116 applications_result
->push_back(extension
);
120 // Walk the list of terminated extensions also (just because an extension
121 // crashed doesn't mean we should ignore it).
122 const ExtensionSet
& terminated_extensions
= registry
->terminated_extensions();
123 for (ExtensionSet::const_iterator cursor
= terminated_extensions
.begin();
124 cursor
!= terminated_extensions
.end();
126 const Extension
* extension
= cursor
->get();
127 if (BackgroundApplicationListModel::IsBackgroundApp(*extension
,
128 service
->profile())) {
129 applications_result
->push_back(extension
);
133 if (!variations::GetVariationParamValue("LightSpeed", "AvoidMMap").empty()) {
134 std::sort(applications_result
->begin(), applications_result
->end(),
135 ExtensionIdComparator());
137 std::string locale
= g_browser_process
->GetApplicationLocale();
138 icu::Locale
loc(locale
.c_str());
139 UErrorCode error
= U_ZERO_ERROR
;
140 scoped_ptr
<icu::Collator
> collator(
141 icu::Collator::createInstance(loc
, error
));
142 std::sort(applications_result
->begin(), applications_result
->end(),
143 ExtensionNameComparator(collator
.get()));
150 BackgroundApplicationListModel::Observer::OnApplicationDataChanged(
151 const Extension
* extension
, Profile
* profile
) {
155 BackgroundApplicationListModel::Observer::OnApplicationListChanged(
159 BackgroundApplicationListModel::Observer::~Observer() {
162 BackgroundApplicationListModel::Application::~Application() {
165 BackgroundApplicationListModel::Application::Application(
166 BackgroundApplicationListModel
* model
,
167 const Extension
* extension
)
168 : extension_(extension
), model_(model
) {}
170 void BackgroundApplicationListModel::Application::OnImageLoaded(
171 const gfx::Image
& image
) {
174 icon_
.reset(image
.CopyImageSkia());
175 model_
->SendApplicationDataChangedNotifications(extension_
);
178 void BackgroundApplicationListModel::Application::RequestIcon(
179 extension_misc::ExtensionIcons size
) {
180 extensions::ExtensionResource resource
=
181 extensions::IconsInfo::GetIconResource(
182 extension_
, size
, ExtensionIconSet::MATCH_BIGGER
);
183 extensions::ImageLoader::Get(model_
->profile_
)->LoadImageAsync(
184 extension_
, resource
, gfx::Size(size
, size
),
185 base::Bind(&Application::OnImageLoaded
, AsWeakPtr()));
188 BackgroundApplicationListModel::~BackgroundApplicationListModel() {
189 STLDeleteContainerPairSecondPointers(applications_
.begin(),
190 applications_
.end());
193 BackgroundApplicationListModel::BackgroundApplicationListModel(Profile
* profile
)
198 extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED
,
199 content::Source
<Profile
>(profile
));
201 extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED
,
202 content::Source
<Profile
>(profile
));
204 extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED
,
205 content::Source
<Profile
>(profile
));
207 extensions::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED
,
208 content::Source
<Profile
>(profile
));
210 chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED
,
211 content::Source
<Profile
>(profile
));
212 ExtensionService
* service
= extensions::ExtensionSystem::Get(profile
)->
214 if (service
&& service
->is_ready()) {
220 void BackgroundApplicationListModel::AddObserver(Observer
* observer
) {
221 observers_
.AddObserver(observer
);
224 void BackgroundApplicationListModel::AssociateApplicationData(
225 const Extension
* extension
) {
226 DCHECK(IsBackgroundApp(*extension
, profile_
));
227 Application
* application
= FindApplication(extension
);
229 // App position is used as a dynamic command and so must be less than any
230 // predefined command id.
231 if (applications_
.size() >= IDC_MinimumLabelValue
) {
232 LOG(ERROR
) << "Background application limit of " << IDC_MinimumLabelValue
233 << " exceeded. Ignoring.";
236 application
= new Application(this, extension
);
237 applications_
[extension
->id()] = application
;
239 application
->RequestIcon(extension_misc::EXTENSION_ICON_BITTY
);
243 void BackgroundApplicationListModel::DissociateApplicationData(
244 const Extension
* extension
) {
245 ApplicationMap::iterator found
= applications_
.find(extension
->id());
246 if (found
!= applications_
.end()) {
247 delete found
->second
;
248 applications_
.erase(found
);
252 const Extension
* BackgroundApplicationListModel::GetExtension(
253 int position
) const {
254 DCHECK(position
>= 0 && static_cast<size_t>(position
) < extensions_
.size());
255 return extensions_
[position
].get();
258 const BackgroundApplicationListModel::Application
*
259 BackgroundApplicationListModel::FindApplication(
260 const Extension
* extension
) const {
261 const std::string
& id
= extension
->id();
262 ApplicationMap::const_iterator found
= applications_
.find(id
);
263 return (found
== applications_
.end()) ? NULL
: found
->second
;
266 BackgroundApplicationListModel::Application
*
267 BackgroundApplicationListModel::FindApplication(
268 const Extension
* extension
) {
269 const std::string
& id
= extension
->id();
270 ApplicationMap::iterator found
= applications_
.find(id
);
271 return (found
== applications_
.end()) ? NULL
: found
->second
;
274 const gfx::ImageSkia
* BackgroundApplicationListModel::GetIcon(
275 const Extension
* extension
) {
276 const Application
* application
= FindApplication(extension
);
278 return application
->icon_
.get();
279 AssociateApplicationData(extension
);
283 int BackgroundApplicationListModel::GetPosition(
284 const Extension
* extension
) const {
286 const std::string
& id
= extension
->id();
287 for (ExtensionList::const_iterator cursor
= extensions_
.begin();
288 cursor
!= extensions_
.end();
289 ++cursor
, ++position
) {
290 if (id
== cursor
->get()->id())
298 bool BackgroundApplicationListModel::IsBackgroundApp(
299 const Extension
& extension
, Profile
* profile
) {
300 // An extension is a "background app" if it has the "background API"
301 // permission, and meets one of the following criteria:
302 // 1) It is an extension (not a hosted app).
303 // 2) It is a hosted app, and has a background contents registered or in the
306 // Ephemeral apps are denied any background activity after their event page
307 // has been destroyed, thus they cannot be background apps.
308 if (extensions::util::IsEphemeralApp(extension
.id(), profile
))
311 // Not a background app if we don't have the background permission.
312 if (!extension
.permissions_data()->HasAPIPermission(
313 APIPermission::kBackground
)) {
317 // Extensions and packaged apps with background permission are always treated
318 // as background apps.
319 if (!extension
.is_hosted_app())
322 // Hosted apps with manifest-provided background pages are background apps.
323 if (extensions::BackgroundInfo::HasBackgroundPage(&extension
))
326 BackgroundContentsService
* service
=
327 BackgroundContentsServiceFactory::GetForProfile(profile
);
328 base::string16 app_id
= base::ASCIIToUTF16(extension
.id());
329 // If we have an active or registered background contents for this app, then
330 // it's a background app. This covers the cases where the app has created its
331 // background contents, but it hasn't navigated yet, or the background
332 // contents crashed and hasn't yet been restarted - in both cases we still
333 // want to treat the app as a background app.
334 if (service
->GetAppBackgroundContents(app_id
) ||
335 service
->HasRegisteredBackgroundContents(app_id
)) {
339 // Doesn't meet our criteria, so it's not a background app.
343 void BackgroundApplicationListModel::Observe(
345 const content::NotificationSource
& source
,
346 const content::NotificationDetails
& details
) {
347 if (type
== extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED
) {
352 ExtensionService
* service
= extensions::ExtensionSystem::Get(profile_
)->
354 if (!service
|| !service
->is_ready())
358 case extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED
:
359 OnExtensionLoaded(content::Details
<Extension
>(details
).ptr());
361 case extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED
:
363 content::Details
<UnloadedExtensionInfo
>(details
)->extension
);
365 case extensions::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED
:
366 OnExtensionPermissionsUpdated(
367 content::Details
<UpdatedExtensionPermissionsInfo
>(details
)->extension
,
368 content::Details
<UpdatedExtensionPermissionsInfo
>(details
)->reason
,
369 content::Details
<UpdatedExtensionPermissionsInfo
>(details
)->
372 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED
:
376 NOTREACHED() << "Received unexpected notification";
380 void BackgroundApplicationListModel::SendApplicationDataChangedNotifications(
381 const Extension
* extension
) {
382 FOR_EACH_OBSERVER(Observer
, observers_
, OnApplicationDataChanged(extension
,
386 void BackgroundApplicationListModel::OnExtensionLoaded(
387 const Extension
* extension
) {
388 // We only care about extensions that are background applications
389 if (!IsBackgroundApp(*extension
, profile_
))
391 AssociateApplicationData(extension
);
394 void BackgroundApplicationListModel::OnExtensionUnloaded(
395 const Extension
* extension
) {
396 if (!IsBackgroundApp(*extension
, profile_
))
399 DissociateApplicationData(extension
);
402 void BackgroundApplicationListModel::OnExtensionPermissionsUpdated(
403 const Extension
* extension
,
404 UpdatedExtensionPermissionsInfo::Reason reason
,
405 const PermissionSet
* permissions
) {
406 if (permissions
->HasAPIPermission(APIPermission::kBackground
)) {
408 case UpdatedExtensionPermissionsInfo::ADDED
:
409 DCHECK(IsBackgroundApp(*extension
, profile_
));
410 OnExtensionLoaded(extension
);
412 case UpdatedExtensionPermissionsInfo::REMOVED
:
413 DCHECK(!IsBackgroundApp(*extension
, profile_
));
415 DissociateApplicationData(extension
);
423 void BackgroundApplicationListModel::RemoveObserver(Observer
* observer
) {
424 observers_
.RemoveObserver(observer
);
427 // Update queries the extensions service of the profile with which the model was
428 // initialized to determine the current set of background applications. If that
429 // differs from the old list, it generates OnApplicationListChanged events for
431 void BackgroundApplicationListModel::Update() {
432 ExtensionService
* service
= extensions::ExtensionSystem::Get(profile_
)->
435 // Discover current background applications, compare with previous list, which
436 // is consistently sorted, and notify observers if they differ.
437 ExtensionList extensions
;
438 GetServiceApplications(service
, &extensions
);
439 ExtensionList::const_iterator old_cursor
= extensions_
.begin();
440 ExtensionList::const_iterator new_cursor
= extensions
.begin();
441 while (old_cursor
!= extensions_
.end() &&
442 new_cursor
!= extensions
.end() &&
443 (*old_cursor
)->name() == (*new_cursor
)->name() &&
444 (*old_cursor
)->id() == (*new_cursor
)->id()) {
448 if (old_cursor
!= extensions_
.end() || new_cursor
!= extensions
.end()) {
449 extensions_
= extensions
;
450 FOR_EACH_OBSERVER(Observer
, observers_
, OnApplicationListChanged(profile_
));