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