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.
4
5 #include "chrome/browser/background/background_application_list_model.h"
6
7 #include <algorithm>
8 #include <set>
9
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_service.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/common/extensions/extension_constants.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/image_loader.h"
29 #include "extensions/common/extension.h"
30 #include "extensions/common/extension_icon_set.h"
31 #include "extensions/common/extension_resource.h"
32 #include "extensions/common/extension_set.h"
33 #include "extensions/common/manifest_handlers/background_info.h"
34 #include "extensions/common/manifest_handlers/icons_handler.h"
35 #include "extensions/common/permissions/permission_set.h"
36 #include "extensions/common/permissions/permissions_data.h"
37 #include "ui/base/l10n/l10n_util_collator.h"
38 #include "ui/gfx/image/image.h"
39 #include "ui/gfx/image/image_skia.h"
40
41 using extensions::APIPermission;
42 using extensions::Extension;
43 using extensions::ExtensionList;
44 using extensions::ExtensionRegistry;
45 using extensions::ExtensionSet;
46 using extensions::PermissionSet;
47 using extensions::UnloadedExtensionInfo;
48 using extensions::UpdatedExtensionPermissionsInfo;
49
50 class ExtensionNameComparator {
51 public:
52 explicit ExtensionNameComparator(icu::Collator* collator);
53 bool operator()(const scoped_refptr<const Extension>& x,
54 const scoped_refptr<const Extension>& y);
55
56 private:
57 icu::Collator* collator_;
58 };
59
ExtensionNameComparator(icu::Collator * collator)60 ExtensionNameComparator::ExtensionNameComparator(icu::Collator* collator)
61 : collator_(collator) {
62 }
63
operator ()(const scoped_refptr<const Extension> & x,const scoped_refptr<const Extension> & y)64 bool ExtensionNameComparator::operator()(
65 const scoped_refptr<const Extension>& x,
66 const scoped_refptr<const Extension>& y) {
67 return l10n_util::StringComparator<base::string16>(collator_)(
68 base::UTF8ToUTF16(x->name()), base::UTF8ToUTF16(y->name()));
69 }
70
71 // Background application representation, private to the
72 // BackgroundApplicationListModel class.
73 class BackgroundApplicationListModel::Application
74 : public base::SupportsWeakPtr<Application> {
75 public:
76 Application(BackgroundApplicationListModel* model,
77 const Extension* an_extension);
78
79 virtual ~Application();
80
81 // Invoked when a request icon is available.
82 void OnImageLoaded(const gfx::Image& image);
83
84 // Uses the FILE thread to request this extension's icon, sized
85 // appropriately.
86 void RequestIcon(extension_misc::ExtensionIcons size);
87
88 const Extension* extension_;
89 scoped_ptr<gfx::ImageSkia> icon_;
90 BackgroundApplicationListModel* model_;
91 };
92
93 namespace {
GetServiceApplications(ExtensionService * service,ExtensionList * applications_result)94 void GetServiceApplications(ExtensionService* service,
95 ExtensionList* applications_result) {
96 ExtensionRegistry* registry = ExtensionRegistry::Get(service->profile());
97 const ExtensionSet& enabled_extensions = registry->enabled_extensions();
98
99 for (ExtensionSet::const_iterator cursor = enabled_extensions.begin();
100 cursor != enabled_extensions.end();
101 ++cursor) {
102 const Extension* extension = cursor->get();
103 if (BackgroundApplicationListModel::IsBackgroundApp(*extension,
104 service->profile())) {
105 applications_result->push_back(extension);
106 }
107 }
108
109 // Walk the list of terminated extensions also (just because an extension
110 // crashed doesn't mean we should ignore it).
111 const ExtensionSet& terminated_extensions = registry->terminated_extensions();
112 for (ExtensionSet::const_iterator cursor = terminated_extensions.begin();
113 cursor != terminated_extensions.end();
114 ++cursor) {
115 const Extension* extension = cursor->get();
116 if (BackgroundApplicationListModel::IsBackgroundApp(*extension,
117 service->profile())) {
118 applications_result->push_back(extension);
119 }
120 }
121
122 std::string locale = g_browser_process->GetApplicationLocale();
123 icu::Locale loc(locale.c_str());
124 UErrorCode error = U_ZERO_ERROR;
125 scoped_ptr<icu::Collator> collator(icu::Collator::createInstance(loc, error));
126 std::sort(applications_result->begin(), applications_result->end(),
127 ExtensionNameComparator(collator.get()));
128 }
129
130 } // namespace
131
132 void
OnApplicationDataChanged(const Extension * extension,Profile * profile)133 BackgroundApplicationListModel::Observer::OnApplicationDataChanged(
134 const Extension* extension, Profile* profile) {
135 }
136
137 void
OnApplicationListChanged(Profile * profile)138 BackgroundApplicationListModel::Observer::OnApplicationListChanged(
139 Profile* profile) {
140 }
141
~Observer()142 BackgroundApplicationListModel::Observer::~Observer() {
143 }
144
~Application()145 BackgroundApplicationListModel::Application::~Application() {
146 }
147
Application(BackgroundApplicationListModel * model,const Extension * extension)148 BackgroundApplicationListModel::Application::Application(
149 BackgroundApplicationListModel* model,
150 const Extension* extension)
151 : extension_(extension), model_(model) {}
152
OnImageLoaded(const gfx::Image & image)153 void BackgroundApplicationListModel::Application::OnImageLoaded(
154 const gfx::Image& image) {
155 if (image.IsEmpty())
156 return;
157 icon_.reset(image.CopyImageSkia());
158 model_->SendApplicationDataChangedNotifications(extension_);
159 }
160
RequestIcon(extension_misc::ExtensionIcons size)161 void BackgroundApplicationListModel::Application::RequestIcon(
162 extension_misc::ExtensionIcons size) {
163 extensions::ExtensionResource resource =
164 extensions::IconsInfo::GetIconResource(
165 extension_, size, ExtensionIconSet::MATCH_BIGGER);
166 extensions::ImageLoader::Get(model_->profile_)->LoadImageAsync(
167 extension_, resource, gfx::Size(size, size),
168 base::Bind(&Application::OnImageLoaded, AsWeakPtr()));
169 }
170
~BackgroundApplicationListModel()171 BackgroundApplicationListModel::~BackgroundApplicationListModel() {
172 STLDeleteContainerPairSecondPointers(applications_.begin(),
173 applications_.end());
174 }
175
BackgroundApplicationListModel(Profile * profile)176 BackgroundApplicationListModel::BackgroundApplicationListModel(Profile* profile)
177 : profile_(profile) {
178 DCHECK(profile_);
179 registrar_.Add(this,
180 chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
181 content::Source<Profile>(profile));
182 registrar_.Add(this,
183 chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
184 content::Source<Profile>(profile));
185 registrar_.Add(this,
186 chrome::NOTIFICATION_EXTENSIONS_READY,
187 content::Source<Profile>(profile));
188 registrar_.Add(this,
189 chrome::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED,
190 content::Source<Profile>(profile));
191 registrar_.Add(this,
192 chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED,
193 content::Source<Profile>(profile));
194 ExtensionService* service = extensions::ExtensionSystem::Get(profile)->
195 extension_service();
196 if (service && service->is_ready())
197 Update();
198 }
199
AddObserver(Observer * observer)200 void BackgroundApplicationListModel::AddObserver(Observer* observer) {
201 observers_.AddObserver(observer);
202 }
203
AssociateApplicationData(const Extension * extension)204 void BackgroundApplicationListModel::AssociateApplicationData(
205 const Extension* extension) {
206 DCHECK(IsBackgroundApp(*extension, profile_));
207 Application* application = FindApplication(extension);
208 if (!application) {
209 // App position is used as a dynamic command and so must be less than any
210 // predefined command id.
211 if (applications_.size() >= IDC_MinimumLabelValue) {
212 LOG(ERROR) << "Background application limit of " << IDC_MinimumLabelValue
213 << " exceeded. Ignoring.";
214 return;
215 }
216 application = new Application(this, extension);
217 applications_[extension->id()] = application;
218 Update();
219 application->RequestIcon(extension_misc::EXTENSION_ICON_BITTY);
220 }
221 }
222
DissociateApplicationData(const Extension * extension)223 void BackgroundApplicationListModel::DissociateApplicationData(
224 const Extension* extension) {
225 ApplicationMap::iterator found = applications_.find(extension->id());
226 if (found != applications_.end()) {
227 delete found->second;
228 applications_.erase(found);
229 }
230 }
231
GetExtension(int position) const232 const Extension* BackgroundApplicationListModel::GetExtension(
233 int position) const {
234 DCHECK(position >= 0 && static_cast<size_t>(position) < extensions_.size());
235 return extensions_[position].get();
236 }
237
238 const BackgroundApplicationListModel::Application*
FindApplication(const Extension * extension) const239 BackgroundApplicationListModel::FindApplication(
240 const Extension* extension) const {
241 const std::string& id = extension->id();
242 ApplicationMap::const_iterator found = applications_.find(id);
243 return (found == applications_.end()) ? NULL : found->second;
244 }
245
246 BackgroundApplicationListModel::Application*
FindApplication(const Extension * extension)247 BackgroundApplicationListModel::FindApplication(
248 const Extension* extension) {
249 const std::string& id = extension->id();
250 ApplicationMap::iterator found = applications_.find(id);
251 return (found == applications_.end()) ? NULL : found->second;
252 }
253
GetIcon(const Extension * extension)254 const gfx::ImageSkia* BackgroundApplicationListModel::GetIcon(
255 const Extension* extension) {
256 const Application* application = FindApplication(extension);
257 if (application)
258 return application->icon_.get();
259 AssociateApplicationData(extension);
260 return NULL;
261 }
262
GetPosition(const Extension * extension) const263 int BackgroundApplicationListModel::GetPosition(
264 const Extension* extension) const {
265 int position = 0;
266 const std::string& id = extension->id();
267 for (ExtensionList::const_iterator cursor = extensions_.begin();
268 cursor != extensions_.end();
269 ++cursor, ++position) {
270 if (id == cursor->get()->id())
271 return position;
272 }
273 NOTREACHED();
274 return -1;
275 }
276
277 // static
RequiresBackgroundModeForPushMessaging(const Extension & extension)278 bool BackgroundApplicationListModel::RequiresBackgroundModeForPushMessaging(
279 const Extension& extension) {
280 // No PushMessaging permission - does not require the background mode.
281 if (!extension.permissions_data()->HasAPIPermission(
282 APIPermission::kPushMessaging)) {
283 return false;
284 }
285
286 // If in the whitelist, then does not require background mode even if
287 // uses push messaging.
288 // TODO(dimich): remove this whitelist once we have a better way to keep
289 // listening for GCM. http://crbug.com/311268
290 std::string id_hash = base::SHA1HashString(extension.id());
291 std::string hexencoded_id_hash = base::HexEncode(id_hash.c_str(),
292 id_hash.length());
293 // The id starting from "9A04..." is a one from unit test.
294 if (hexencoded_id_hash == "C41AD9DCD670210295614257EF8C9945AD68D86E" ||
295 hexencoded_id_hash == "9A0417016F345C934A1A88F55CA17C05014EEEBA")
296 return false;
297
298 return true;
299 }
300
301 // static
IsBackgroundApp(const Extension & extension,Profile * profile)302 bool BackgroundApplicationListModel::IsBackgroundApp(
303 const Extension& extension, Profile* profile) {
304 // An extension is a "background app" if it has the "background API"
305 // permission, and meets one of the following criteria:
306 // 1) It is an extension (not a hosted app).
307 // 2) It is a hosted app, and has a background contents registered or in the
308 // manifest.
309
310 // Not a background app if we don't have the background permission or
311 // the push messaging permission
312 if (!extension.permissions_data()->HasAPIPermission(
313 APIPermission::kBackground) &&
314 !RequiresBackgroundModeForPushMessaging(extension))
315 return false;
316
317 // Extensions and packaged apps with background permission are always treated
318 // as background apps.
319 if (!extension.is_hosted_app())
320 return true;
321
322 // Hosted apps with manifest-provided background pages are background apps.
323 if (extensions::BackgroundInfo::HasBackgroundPage(&extension))
324 return true;
325
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)) {
336 return true;
337 }
338
339 // Doesn't meet our criteria, so it's not a background app.
340 return false;
341 }
342
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)343 void BackgroundApplicationListModel::Observe(
344 int type,
345 const content::NotificationSource& source,
346 const content::NotificationDetails& details) {
347 if (type == chrome::NOTIFICATION_EXTENSIONS_READY) {
348 Update();
349 return;
350 }
351 ExtensionService* service = extensions::ExtensionSystem::Get(profile_)->
352 extension_service();
353 if (!service || !service->is_ready())
354 return;
355
356 switch (type) {
357 case chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED:
358 OnExtensionLoaded(content::Details<Extension>(details).ptr());
359 break;
360 case chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED:
361 OnExtensionUnloaded(
362 content::Details<UnloadedExtensionInfo>(details)->extension);
363 break;
364 case chrome::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED:
365 OnExtensionPermissionsUpdated(
366 content::Details<UpdatedExtensionPermissionsInfo>(details)->extension,
367 content::Details<UpdatedExtensionPermissionsInfo>(details)->reason,
368 content::Details<UpdatedExtensionPermissionsInfo>(details)->
369 permissions);
370 break;
371 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED:
372 Update();
373 break;
374 default:
375 NOTREACHED() << "Received unexpected notification";
376 }
377 }
378
SendApplicationDataChangedNotifications(const Extension * extension)379 void BackgroundApplicationListModel::SendApplicationDataChangedNotifications(
380 const Extension* extension) {
381 FOR_EACH_OBSERVER(Observer, observers_, OnApplicationDataChanged(extension,
382 profile_));
383 }
384
OnExtensionLoaded(const Extension * extension)385 void BackgroundApplicationListModel::OnExtensionLoaded(
386 const Extension* extension) {
387 // We only care about extensions that are background applications
388 if (!IsBackgroundApp(*extension, profile_))
389 return;
390 AssociateApplicationData(extension);
391 }
392
OnExtensionUnloaded(const Extension * extension)393 void BackgroundApplicationListModel::OnExtensionUnloaded(
394 const Extension* extension) {
395 if (!IsBackgroundApp(*extension, profile_))
396 return;
397 Update();
398 DissociateApplicationData(extension);
399 }
400
OnExtensionPermissionsUpdated(const Extension * extension,UpdatedExtensionPermissionsInfo::Reason reason,const PermissionSet * permissions)401 void BackgroundApplicationListModel::OnExtensionPermissionsUpdated(
402 const Extension* extension,
403 UpdatedExtensionPermissionsInfo::Reason reason,
404 const PermissionSet* permissions) {
405 if (permissions->HasAPIPermission(APIPermission::kBackground)) {
406 switch (reason) {
407 case UpdatedExtensionPermissionsInfo::ADDED:
408 DCHECK(IsBackgroundApp(*extension, profile_));
409 OnExtensionLoaded(extension);
410 break;
411 case UpdatedExtensionPermissionsInfo::REMOVED:
412 DCHECK(!IsBackgroundApp(*extension, profile_));
413 Update();
414 DissociateApplicationData(extension);
415 break;
416 default:
417 NOTREACHED();
418 }
419 }
420 }
421
RemoveObserver(Observer * observer)422 void BackgroundApplicationListModel::RemoveObserver(Observer* observer) {
423 observers_.RemoveObserver(observer);
424 }
425
426 // Update queries the extensions service of the profile with which the model was
427 // initialized to determine the current set of background applications. If that
428 // differs from the old list, it generates OnApplicationListChanged events for
429 // each observer.
Update()430 void BackgroundApplicationListModel::Update() {
431 ExtensionService* service = extensions::ExtensionSystem::Get(profile_)->
432 extension_service();
433
434 // Discover current background applications, compare with previous list, which
435 // is consistently sorted, and notify observers if they differ.
436 ExtensionList extensions;
437 GetServiceApplications(service, &extensions);
438 ExtensionList::const_iterator old_cursor = extensions_.begin();
439 ExtensionList::const_iterator new_cursor = extensions.begin();
440 while (old_cursor != extensions_.end() &&
441 new_cursor != extensions.end() &&
442 (*old_cursor)->name() == (*new_cursor)->name() &&
443 (*old_cursor)->id() == (*new_cursor)->id()) {
444 ++old_cursor;
445 ++new_cursor;
446 }
447 if (old_cursor != extensions_.end() || new_cursor != extensions.end()) {
448 extensions_ = extensions;
449 FOR_EACH_OBSERVER(Observer, observers_, OnApplicationListChanged(profile_));
450 }
451 }
452