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