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 "content/public/browser/notification_details.h"
24 #include "content/public/browser/notification_source.h"
25 #include "extensions/browser/extension_prefs.h"
26 #include "extensions/browser/extension_registry.h"
27 #include "extensions/browser/extension_system.h"
28 #include "extensions/browser/extension_util.h"
29 #include "extensions/browser/image_loader.h"
30 #include "extensions/browser/notification_types.h"
31 #include "extensions/common/extension.h"
32 #include "extensions/common/extension_icon_set.h"
33 #include "extensions/common/extension_resource.h"
34 #include "extensions/common/extension_set.h"
35 #include "extensions/common/manifest_handlers/background_info.h"
36 #include "extensions/common/manifest_handlers/icons_handler.h"
37 #include "extensions/common/permissions/permission_set.h"
38 #include "extensions/common/permissions/permissions_data.h"
39 #include "ui/base/l10n/l10n_util_collator.h"
40 #include "ui/gfx/image/image.h"
41 #include "ui/gfx/image/image_skia.h"
43 using extensions::APIPermission
;
44 using extensions::Extension
;
45 using extensions::ExtensionList
;
46 using extensions::ExtensionRegistry
;
47 using extensions::ExtensionSet
;
48 using extensions::PermissionSet
;
49 using extensions::UnloadedExtensionInfo
;
50 using extensions::UpdatedExtensionPermissionsInfo
;
52 class ExtensionNameComparator
{
54 bool operator()(const scoped_refptr
<const Extension
>& x
,
55 const scoped_refptr
<const Extension
>& y
) {
56 return x
->name() < y
->name();
60 // Background application representation, private to the
61 // BackgroundApplicationListModel class.
62 class BackgroundApplicationListModel::Application
63 : public base::SupportsWeakPtr
<Application
> {
65 Application(BackgroundApplicationListModel
* model
,
66 const Extension
* an_extension
);
68 virtual ~Application();
70 // Invoked when a request icon is available.
71 void OnImageLoaded(const gfx::Image
& image
);
73 // Uses the FILE thread to request this extension's icon, sized
75 void RequestIcon(extension_misc::ExtensionIcons size
);
77 const Extension
* extension_
;
78 scoped_ptr
<gfx::ImageSkia
> icon_
;
79 BackgroundApplicationListModel
* model_
;
83 void GetServiceApplications(ExtensionService
* service
,
84 ExtensionList
* applications_result
) {
85 ExtensionRegistry
* registry
= ExtensionRegistry::Get(service
->profile());
86 const ExtensionSet
& enabled_extensions
= registry
->enabled_extensions();
88 for (const auto& extension
: enabled_extensions
) {
89 if (BackgroundApplicationListModel::IsBackgroundApp(*extension
,
90 service
->profile())) {
91 applications_result
->push_back(extension
);
95 // Walk the list of terminated extensions also (just because an extension
96 // crashed doesn't mean we should ignore it).
97 const ExtensionSet
& terminated_extensions
= registry
->terminated_extensions();
98 for (const auto& extension
: terminated_extensions
) {
99 if (BackgroundApplicationListModel::IsBackgroundApp(*extension
,
100 service
->profile())) {
101 applications_result
->push_back(extension
);
105 std::sort(applications_result
->begin(), applications_result
->end(),
106 ExtensionNameComparator());
112 BackgroundApplicationListModel::Observer::OnApplicationDataChanged(
113 const Extension
* extension
, Profile
* profile
) {
117 BackgroundApplicationListModel::Observer::OnApplicationListChanged(
121 BackgroundApplicationListModel::Observer::~Observer() {
124 BackgroundApplicationListModel::Application::~Application() {
127 BackgroundApplicationListModel::Application::Application(
128 BackgroundApplicationListModel
* model
,
129 const Extension
* extension
)
130 : extension_(extension
), model_(model
) {}
132 void BackgroundApplicationListModel::Application::OnImageLoaded(
133 const gfx::Image
& image
) {
136 icon_
.reset(image
.CopyImageSkia());
137 model_
->SendApplicationDataChangedNotifications(extension_
);
140 void BackgroundApplicationListModel::Application::RequestIcon(
141 extension_misc::ExtensionIcons size
) {
142 extensions::ExtensionResource resource
=
143 extensions::IconsInfo::GetIconResource(
144 extension_
, size
, ExtensionIconSet::MATCH_BIGGER
);
145 extensions::ImageLoader::Get(model_
->profile_
)->LoadImageAsync(
146 extension_
, resource
, gfx::Size(size
, size
),
147 base::Bind(&Application::OnImageLoaded
, AsWeakPtr()));
150 BackgroundApplicationListModel::~BackgroundApplicationListModel() {
151 STLDeleteContainerPairSecondPointers(applications_
.begin(),
152 applications_
.end());
155 BackgroundApplicationListModel::BackgroundApplicationListModel(Profile
* profile
)
160 extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED
,
161 content::Source
<Profile
>(profile
));
163 extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED
,
164 content::Source
<Profile
>(profile
));
166 extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED
,
167 content::Source
<Profile
>(profile
));
169 extensions::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED
,
170 content::Source
<Profile
>(profile
));
172 chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED
,
173 content::Source
<Profile
>(profile
));
174 ExtensionService
* service
= extensions::ExtensionSystem::Get(profile
)->
176 if (service
&& service
->is_ready()) {
182 void BackgroundApplicationListModel::AddObserver(Observer
* observer
) {
183 observers_
.AddObserver(observer
);
186 void BackgroundApplicationListModel::AssociateApplicationData(
187 const Extension
* extension
) {
188 DCHECK(IsBackgroundApp(*extension
, profile_
));
189 Application
* application
= FindApplication(extension
);
191 // App position is used as a dynamic command and so must be less than any
192 // predefined command id.
193 if (applications_
.size() >= IDC_MinimumLabelValue
) {
194 LOG(ERROR
) << "Background application limit of " << IDC_MinimumLabelValue
195 << " exceeded. Ignoring.";
198 application
= new Application(this, extension
);
199 applications_
[extension
->id()] = application
;
201 application
->RequestIcon(extension_misc::EXTENSION_ICON_BITTY
);
205 void BackgroundApplicationListModel::DissociateApplicationData(
206 const Extension
* extension
) {
207 ApplicationMap::iterator found
= applications_
.find(extension
->id());
208 if (found
!= applications_
.end()) {
209 delete found
->second
;
210 applications_
.erase(found
);
214 const Extension
* BackgroundApplicationListModel::GetExtension(
215 int position
) const {
216 DCHECK(position
>= 0 && static_cast<size_t>(position
) < extensions_
.size());
217 return extensions_
[position
].get();
220 const BackgroundApplicationListModel::Application
*
221 BackgroundApplicationListModel::FindApplication(
222 const Extension
* extension
) const {
223 const std::string
& id
= extension
->id();
224 ApplicationMap::const_iterator found
= applications_
.find(id
);
225 return (found
== applications_
.end()) ? NULL
: found
->second
;
228 BackgroundApplicationListModel::Application
*
229 BackgroundApplicationListModel::FindApplication(
230 const Extension
* extension
) {
231 const std::string
& id
= extension
->id();
232 ApplicationMap::iterator found
= applications_
.find(id
);
233 return (found
== applications_
.end()) ? NULL
: found
->second
;
236 const gfx::ImageSkia
* BackgroundApplicationListModel::GetIcon(
237 const Extension
* extension
) {
238 const Application
* application
= FindApplication(extension
);
240 return application
->icon_
.get();
241 AssociateApplicationData(extension
);
245 int BackgroundApplicationListModel::GetPosition(
246 const Extension
* extension
) const {
248 const std::string
& id
= extension
->id();
249 for (const auto& it
: extensions_
) {
259 bool BackgroundApplicationListModel::IsBackgroundApp(
260 const Extension
& extension
, Profile
* profile
) {
261 // An extension is a "background app" if it has the "background API"
262 // permission, and meets one of the following criteria:
263 // 1) It is an extension (not a hosted app).
264 // 2) It is a hosted app, and has a background contents registered or in the
267 // Ephemeral apps are denied any background activity after their event page
268 // has been destroyed, thus they cannot be background apps.
269 if (extensions::util::IsEphemeralApp(extension
.id(), profile
))
272 // Not a background app if we don't have the background permission.
273 if (!extension
.permissions_data()->HasAPIPermission(
274 APIPermission::kBackground
)) {
278 // Extensions and packaged apps with background permission are always treated
279 // as background apps.
280 if (!extension
.is_hosted_app())
283 // Hosted apps with manifest-provided background pages are background apps.
284 if (extensions::BackgroundInfo::HasBackgroundPage(&extension
))
287 BackgroundContentsService
* service
=
288 BackgroundContentsServiceFactory::GetForProfile(profile
);
289 base::string16 app_id
= base::ASCIIToUTF16(extension
.id());
290 // If we have an active or registered background contents for this app, then
291 // it's a background app. This covers the cases where the app has created its
292 // background contents, but it hasn't navigated yet, or the background
293 // contents crashed and hasn't yet been restarted - in both cases we still
294 // want to treat the app as a background app.
295 if (service
->GetAppBackgroundContents(app_id
) ||
296 service
->HasRegisteredBackgroundContents(app_id
)) {
300 // Doesn't meet our criteria, so it's not a background app.
304 void BackgroundApplicationListModel::Observe(
306 const content::NotificationSource
& source
,
307 const content::NotificationDetails
& details
) {
308 if (type
== extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED
) {
313 ExtensionService
* service
= extensions::ExtensionSystem::Get(profile_
)->
315 if (!service
|| !service
->is_ready())
319 case extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED
:
320 OnExtensionLoaded(content::Details
<Extension
>(details
).ptr());
322 case extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED
:
324 content::Details
<UnloadedExtensionInfo
>(details
)->extension
);
326 case extensions::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED
:
327 OnExtensionPermissionsUpdated(
328 content::Details
<UpdatedExtensionPermissionsInfo
>(details
)->extension
,
329 content::Details
<UpdatedExtensionPermissionsInfo
>(details
)->reason
,
330 content::Details
<UpdatedExtensionPermissionsInfo
>(details
)->
333 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED
:
337 NOTREACHED() << "Received unexpected notification";
341 void BackgroundApplicationListModel::SendApplicationDataChangedNotifications(
342 const Extension
* extension
) {
343 FOR_EACH_OBSERVER(Observer
, observers_
, OnApplicationDataChanged(extension
,
347 void BackgroundApplicationListModel::OnExtensionLoaded(
348 const Extension
* extension
) {
349 // We only care about extensions that are background applications
350 if (!IsBackgroundApp(*extension
, profile_
))
352 AssociateApplicationData(extension
);
355 void BackgroundApplicationListModel::OnExtensionUnloaded(
356 const Extension
* extension
) {
357 if (!IsBackgroundApp(*extension
, profile_
))
360 DissociateApplicationData(extension
);
363 void BackgroundApplicationListModel::OnExtensionPermissionsUpdated(
364 const Extension
* extension
,
365 UpdatedExtensionPermissionsInfo::Reason reason
,
366 const PermissionSet
* permissions
) {
367 if (permissions
->HasAPIPermission(APIPermission::kBackground
)) {
369 case UpdatedExtensionPermissionsInfo::ADDED
:
370 DCHECK(IsBackgroundApp(*extension
, profile_
));
371 OnExtensionLoaded(extension
);
373 case UpdatedExtensionPermissionsInfo::REMOVED
:
374 DCHECK(!IsBackgroundApp(*extension
, profile_
));
376 DissociateApplicationData(extension
);
384 void BackgroundApplicationListModel::RemoveObserver(Observer
* observer
) {
385 observers_
.RemoveObserver(observer
);
388 // Update queries the extensions service of the profile with which the model was
389 // initialized to determine the current set of background applications. If that
390 // differs from the old list, it generates OnApplicationListChanged events for
392 void BackgroundApplicationListModel::Update() {
393 ExtensionService
* service
= extensions::ExtensionSystem::Get(profile_
)->
396 // Discover current background applications, compare with previous list, which
397 // is consistently sorted, and notify observers if they differ.
398 ExtensionList extensions
;
399 GetServiceApplications(service
, &extensions
);
400 ExtensionList::const_iterator old_cursor
= extensions_
.begin();
401 ExtensionList::const_iterator new_cursor
= extensions
.begin();
402 while (old_cursor
!= extensions_
.end() &&
403 new_cursor
!= extensions
.end() &&
404 (*old_cursor
)->name() == (*new_cursor
)->name() &&
405 (*old_cursor
)->id() == (*new_cursor
)->id()) {
409 if (old_cursor
!= extensions_
.end() || new_cursor
!= extensions
.end()) {
410 extensions_
= extensions
;
411 FOR_EACH_OBSERVER(Observer
, observers_
, OnApplicationListChanged(profile_
));