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