Move Webstore URL concepts to //extensions and out
[chromium-blink-merge.git] / chrome / browser / background / background_application_list_model.cc
blob50c426b7a0129c93b50c99efdfc459428956b71b
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.
5 #include "chrome/browser/background/background_application_list_model.h"
7 #include <algorithm>
8 #include <set>
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"
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;
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);
58 private:
59 icu::Collator* collator_;
62 ExtensionNameComparator::ExtensionNameComparator(icu::Collator* collator)
63 : collator_(collator) {
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()));
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);
81 virtual ~Application();
83 // Invoked when a request icon is available.
84 void OnImageLoaded(const gfx::Image& image);
86 // Uses the FILE thread to request this extension's icon, sized
87 // appropriately.
88 void RequestIcon(extension_misc::ExtensionIcons size);
90 const Extension* extension_;
91 scoped_ptr<gfx::ImageSkia> icon_;
92 BackgroundApplicationListModel* model_;
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();
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);
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);
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()));
132 } // namespace
134 void
135 BackgroundApplicationListModel::Observer::OnApplicationDataChanged(
136 const Extension* extension, Profile* profile) {
139 void
140 BackgroundApplicationListModel::Observer::OnApplicationListChanged(
141 Profile* profile) {
144 BackgroundApplicationListModel::Observer::~Observer() {
147 BackgroundApplicationListModel::Application::~Application() {
150 BackgroundApplicationListModel::Application::Application(
151 BackgroundApplicationListModel* model,
152 const Extension* extension)
153 : extension_(extension), model_(model) {}
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_);
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()));
173 BackgroundApplicationListModel::~BackgroundApplicationListModel() {
174 STLDeleteContainerPairSecondPointers(applications_.begin(),
175 applications_.end());
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;
205 void BackgroundApplicationListModel::AddObserver(Observer* observer) {
206 observers_.AddObserver(observer);
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;
221 application = new Application(this, extension);
222 applications_[extension->id()] = application;
223 Update();
224 application->RequestIcon(extension_misc::EXTENSION_ICON_BITTY);
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);
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();
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;
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;
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;
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;
278 NOTREACHED();
279 return -1;
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;
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;
303 return true;
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.
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;
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;
327 // Extensions and packaged apps with background permission are always treated
328 // as background apps.
329 if (!extension.is_hosted_app())
330 return true;
332 // Hosted apps with manifest-provided background pages are background apps.
333 if (extensions::BackgroundInfo::HasBackgroundPage(&extension))
334 return true;
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;
349 // Doesn't meet our criteria, so it's not a background app.
350 return false;
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;
362 ExtensionService* service = extensions::ExtensionSystem::Get(profile_)->
363 extension_service();
364 if (!service || !service->is_ready())
365 return;
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";
390 void BackgroundApplicationListModel::SendApplicationDataChangedNotifications(
391 const Extension* extension) {
392 FOR_EACH_OBSERVER(Observer, observers_, OnApplicationDataChanged(extension,
393 profile_));
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);
404 void BackgroundApplicationListModel::OnExtensionUnloaded(
405 const Extension* extension) {
406 if (!IsBackgroundApp(*extension, profile_))
407 return;
408 Update();
409 DissociateApplicationData(extension);
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();
433 void BackgroundApplicationListModel::RemoveObserver(Observer* observer) {
434 observers_.RemoveObserver(observer);
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();
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;
458 if (old_cursor != extensions_.end() || new_cursor != extensions.end()) {
459 extensions_ = extensions;
460 FOR_EACH_OBSERVER(Observer, observers_, OnApplicationListChanged(profile_));