Popular sites on the NTP: check that experiment group StartsWith (rather than IS...
[chromium-blink-merge.git] / chrome / browser / background / background_application_list_model.cc
blob6883ced8cc0b820abf0217bc8fbcb0ec12532c03
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/stl_util.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "chrome/app/chrome_command_ids.h"
14 #include "chrome/browser/background/background_contents_service.h"
15 #include "chrome/browser/background/background_contents_service_factory.h"
16 #include "chrome/browser/background/background_mode_manager.h"
17 #include "chrome/browser/browser_process.h"
18 #include "chrome/browser/chrome_notification_types.h"
19 #include "chrome/browser/extensions/extension_service.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/common/extensions/extension_constants.h"
22 #include "components/crx_file/id_util.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 bool operator()(const scoped_refptr<const Extension>& x,
55 const scoped_refptr<const Extension>& y) {
56 return x->name() < y->name();
60 // Background application representation, private to the
61 // BackgroundApplicationListModel class.
62 class BackgroundApplicationListModel::Application
63 : public base::SupportsWeakPtr<Application> {
64 public:
65 Application(BackgroundApplicationListModel* model,
66 const Extension* an_extension);
68 virtual ~Application();
70 // Invoked when a request icon is available.
71 void OnImageLoaded(const gfx::Image& image);
73 // Uses the FILE thread to request this extension's icon, sized
74 // appropriately.
75 void RequestIcon(extension_misc::ExtensionIcons size);
77 const Extension* extension_;
78 scoped_ptr<gfx::ImageSkia> icon_;
79 BackgroundApplicationListModel* model_;
82 namespace {
83 void GetServiceApplications(ExtensionService* service,
84 ExtensionList* applications_result) {
85 ExtensionRegistry* registry = ExtensionRegistry::Get(service->profile());
86 const ExtensionSet& enabled_extensions = registry->enabled_extensions();
88 for (ExtensionSet::const_iterator cursor = enabled_extensions.begin();
89 cursor != enabled_extensions.end();
90 ++cursor) {
91 const Extension* extension = cursor->get();
92 if (BackgroundApplicationListModel::IsBackgroundApp(*extension,
93 service->profile())) {
94 applications_result->push_back(extension);
98 // Walk the list of terminated extensions also (just because an extension
99 // crashed doesn't mean we should ignore it).
100 const ExtensionSet& terminated_extensions = registry->terminated_extensions();
101 for (ExtensionSet::const_iterator cursor = terminated_extensions.begin();
102 cursor != terminated_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 std::sort(applications_result->begin(), applications_result->end(),
112 ExtensionNameComparator());
115 } // namespace
117 void
118 BackgroundApplicationListModel::Observer::OnApplicationDataChanged(
119 const Extension* extension, Profile* profile) {
122 void
123 BackgroundApplicationListModel::Observer::OnApplicationListChanged(
124 Profile* profile) {
127 BackgroundApplicationListModel::Observer::~Observer() {
130 BackgroundApplicationListModel::Application::~Application() {
133 BackgroundApplicationListModel::Application::Application(
134 BackgroundApplicationListModel* model,
135 const Extension* extension)
136 : extension_(extension), model_(model) {}
138 void BackgroundApplicationListModel::Application::OnImageLoaded(
139 const gfx::Image& image) {
140 if (image.IsEmpty())
141 return;
142 icon_.reset(image.CopyImageSkia());
143 model_->SendApplicationDataChangedNotifications(extension_);
146 void BackgroundApplicationListModel::Application::RequestIcon(
147 extension_misc::ExtensionIcons size) {
148 extensions::ExtensionResource resource =
149 extensions::IconsInfo::GetIconResource(
150 extension_, size, ExtensionIconSet::MATCH_BIGGER);
151 extensions::ImageLoader::Get(model_->profile_)->LoadImageAsync(
152 extension_, resource, gfx::Size(size, size),
153 base::Bind(&Application::OnImageLoaded, AsWeakPtr()));
156 BackgroundApplicationListModel::~BackgroundApplicationListModel() {
157 STLDeleteContainerPairSecondPointers(applications_.begin(),
158 applications_.end());
161 BackgroundApplicationListModel::BackgroundApplicationListModel(Profile* profile)
162 : profile_(profile),
163 ready_(false) {
164 DCHECK(profile_);
165 registrar_.Add(this,
166 extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
167 content::Source<Profile>(profile));
168 registrar_.Add(this,
169 extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
170 content::Source<Profile>(profile));
171 registrar_.Add(this,
172 extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
173 content::Source<Profile>(profile));
174 registrar_.Add(this,
175 extensions::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED,
176 content::Source<Profile>(profile));
177 registrar_.Add(this,
178 chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED,
179 content::Source<Profile>(profile));
180 ExtensionService* service = extensions::ExtensionSystem::Get(profile)->
181 extension_service();
182 if (service && service->is_ready()) {
183 Update();
184 ready_ = true;
188 void BackgroundApplicationListModel::AddObserver(Observer* observer) {
189 observers_.AddObserver(observer);
192 void BackgroundApplicationListModel::AssociateApplicationData(
193 const Extension* extension) {
194 DCHECK(IsBackgroundApp(*extension, profile_));
195 Application* application = FindApplication(extension);
196 if (!application) {
197 // App position is used as a dynamic command and so must be less than any
198 // predefined command id.
199 if (applications_.size() >= IDC_MinimumLabelValue) {
200 LOG(ERROR) << "Background application limit of " << IDC_MinimumLabelValue
201 << " exceeded. Ignoring.";
202 return;
204 application = new Application(this, extension);
205 applications_[extension->id()] = application;
206 Update();
207 application->RequestIcon(extension_misc::EXTENSION_ICON_BITTY);
211 void BackgroundApplicationListModel::DissociateApplicationData(
212 const Extension* extension) {
213 ApplicationMap::iterator found = applications_.find(extension->id());
214 if (found != applications_.end()) {
215 delete found->second;
216 applications_.erase(found);
220 const Extension* BackgroundApplicationListModel::GetExtension(
221 int position) const {
222 DCHECK(position >= 0 && static_cast<size_t>(position) < extensions_.size());
223 return extensions_[position].get();
226 const BackgroundApplicationListModel::Application*
227 BackgroundApplicationListModel::FindApplication(
228 const Extension* extension) const {
229 const std::string& id = extension->id();
230 ApplicationMap::const_iterator found = applications_.find(id);
231 return (found == applications_.end()) ? NULL : found->second;
234 BackgroundApplicationListModel::Application*
235 BackgroundApplicationListModel::FindApplication(
236 const Extension* extension) {
237 const std::string& id = extension->id();
238 ApplicationMap::iterator found = applications_.find(id);
239 return (found == applications_.end()) ? NULL : found->second;
242 const gfx::ImageSkia* BackgroundApplicationListModel::GetIcon(
243 const Extension* extension) {
244 const Application* application = FindApplication(extension);
245 if (application)
246 return application->icon_.get();
247 AssociateApplicationData(extension);
248 return NULL;
251 int BackgroundApplicationListModel::GetPosition(
252 const Extension* extension) const {
253 int position = 0;
254 const std::string& id = extension->id();
255 for (ExtensionList::const_iterator cursor = extensions_.begin();
256 cursor != extensions_.end();
257 ++cursor, ++position) {
258 if (id == cursor->get()->id())
259 return position;
261 NOTREACHED();
262 return -1;
265 // static
266 bool BackgroundApplicationListModel::IsBackgroundApp(
267 const Extension& extension, Profile* profile) {
268 // An extension is a "background app" if it has the "background API"
269 // permission, and meets one of the following criteria:
270 // 1) It is an extension (not a hosted app).
271 // 2) It is a hosted app, and has a background contents registered or in the
272 // manifest.
274 // Ephemeral apps are denied any background activity after their event page
275 // has been destroyed, thus they cannot be background apps.
276 if (extensions::util::IsEphemeralApp(extension.id(), profile))
277 return false;
279 // Not a background app if we don't have the background permission.
280 if (!extension.permissions_data()->HasAPIPermission(
281 APIPermission::kBackground)) {
282 return false;
285 // Extensions and packaged apps with background permission are always treated
286 // as background apps.
287 if (!extension.is_hosted_app())
288 return true;
290 // Hosted apps with manifest-provided background pages are background apps.
291 if (extensions::BackgroundInfo::HasBackgroundPage(&extension))
292 return true;
294 BackgroundContentsService* service =
295 BackgroundContentsServiceFactory::GetForProfile(profile);
296 base::string16 app_id = base::ASCIIToUTF16(extension.id());
297 // If we have an active or registered background contents for this app, then
298 // it's a background app. This covers the cases where the app has created its
299 // background contents, but it hasn't navigated yet, or the background
300 // contents crashed and hasn't yet been restarted - in both cases we still
301 // want to treat the app as a background app.
302 if (service->GetAppBackgroundContents(app_id) ||
303 service->HasRegisteredBackgroundContents(app_id)) {
304 return true;
307 // Doesn't meet our criteria, so it's not a background app.
308 return false;
311 void BackgroundApplicationListModel::Observe(
312 int type,
313 const content::NotificationSource& source,
314 const content::NotificationDetails& details) {
315 if (type == extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED) {
316 Update();
317 ready_ = true;
318 return;
320 ExtensionService* service = extensions::ExtensionSystem::Get(profile_)->
321 extension_service();
322 if (!service || !service->is_ready())
323 return;
325 switch (type) {
326 case extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED:
327 OnExtensionLoaded(content::Details<Extension>(details).ptr());
328 break;
329 case extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED:
330 OnExtensionUnloaded(
331 content::Details<UnloadedExtensionInfo>(details)->extension);
332 break;
333 case extensions::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED:
334 OnExtensionPermissionsUpdated(
335 content::Details<UpdatedExtensionPermissionsInfo>(details)->extension,
336 content::Details<UpdatedExtensionPermissionsInfo>(details)->reason,
337 content::Details<UpdatedExtensionPermissionsInfo>(details)->
338 permissions);
339 break;
340 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED:
341 Update();
342 break;
343 default:
344 NOTREACHED() << "Received unexpected notification";
348 void BackgroundApplicationListModel::SendApplicationDataChangedNotifications(
349 const Extension* extension) {
350 FOR_EACH_OBSERVER(Observer, observers_, OnApplicationDataChanged(extension,
351 profile_));
354 void BackgroundApplicationListModel::OnExtensionLoaded(
355 const Extension* extension) {
356 // We only care about extensions that are background applications
357 if (!IsBackgroundApp(*extension, profile_))
358 return;
359 AssociateApplicationData(extension);
362 void BackgroundApplicationListModel::OnExtensionUnloaded(
363 const Extension* extension) {
364 if (!IsBackgroundApp(*extension, profile_))
365 return;
366 Update();
367 DissociateApplicationData(extension);
370 void BackgroundApplicationListModel::OnExtensionPermissionsUpdated(
371 const Extension* extension,
372 UpdatedExtensionPermissionsInfo::Reason reason,
373 const PermissionSet* permissions) {
374 if (permissions->HasAPIPermission(APIPermission::kBackground)) {
375 switch (reason) {
376 case UpdatedExtensionPermissionsInfo::ADDED:
377 DCHECK(IsBackgroundApp(*extension, profile_));
378 OnExtensionLoaded(extension);
379 break;
380 case UpdatedExtensionPermissionsInfo::REMOVED:
381 DCHECK(!IsBackgroundApp(*extension, profile_));
382 Update();
383 DissociateApplicationData(extension);
384 break;
385 default:
386 NOTREACHED();
391 void BackgroundApplicationListModel::RemoveObserver(Observer* observer) {
392 observers_.RemoveObserver(observer);
395 // Update queries the extensions service of the profile with which the model was
396 // initialized to determine the current set of background applications. If that
397 // differs from the old list, it generates OnApplicationListChanged events for
398 // each observer.
399 void BackgroundApplicationListModel::Update() {
400 ExtensionService* service = extensions::ExtensionSystem::Get(profile_)->
401 extension_service();
403 // Discover current background applications, compare with previous list, which
404 // is consistently sorted, and notify observers if they differ.
405 ExtensionList extensions;
406 GetServiceApplications(service, &extensions);
407 ExtensionList::const_iterator old_cursor = extensions_.begin();
408 ExtensionList::const_iterator new_cursor = extensions.begin();
409 while (old_cursor != extensions_.end() &&
410 new_cursor != extensions.end() &&
411 (*old_cursor)->name() == (*new_cursor)->name() &&
412 (*old_cursor)->id() == (*new_cursor)->id()) {
413 ++old_cursor;
414 ++new_cursor;
416 if (old_cursor != extensions_.end() || new_cursor != extensions.end()) {
417 extensions_ = extensions;
418 FOR_EACH_OBSERVER(Observer, observers_, OnApplicationListChanged(profile_));