• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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