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
43using extensions::APIPermission;
44using extensions::Extension;
45using extensions::ExtensionList;
46using extensions::ExtensionRegistry;
47using extensions::ExtensionSet;
48using extensions::PermissionSet;
49using extensions::UnloadedExtensionInfo;
50using extensions::UpdatedExtensionPermissionsInfo;
51
52class 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
62ExtensionNameComparator::ExtensionNameComparator(icu::Collator* collator)
63  : collator_(collator) {
64}
65
66bool 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.
75class 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
95namespace {
96void 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
134void
135BackgroundApplicationListModel::Observer::OnApplicationDataChanged(
136    const Extension* extension, Profile* profile) {
137}
138
139void
140BackgroundApplicationListModel::Observer::OnApplicationListChanged(
141    Profile* profile) {
142}
143
144BackgroundApplicationListModel::Observer::~Observer() {
145}
146
147BackgroundApplicationListModel::Application::~Application() {
148}
149
150BackgroundApplicationListModel::Application::Application(
151    BackgroundApplicationListModel* model,
152    const Extension* extension)
153    : extension_(extension), model_(model) {}
154
155void 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
163void 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
173BackgroundApplicationListModel::~BackgroundApplicationListModel() {
174  STLDeleteContainerPairSecondPointers(applications_.begin(),
175                                       applications_.end());
176}
177
178BackgroundApplicationListModel::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
205void BackgroundApplicationListModel::AddObserver(Observer* observer) {
206  observers_.AddObserver(observer);
207}
208
209void 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
228void 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
237const 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
243const BackgroundApplicationListModel::Application*
244BackgroundApplicationListModel::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
251BackgroundApplicationListModel::Application*
252BackgroundApplicationListModel::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
259const 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
268int 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
283bool 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
307bool 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
353void 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
390void BackgroundApplicationListModel::SendApplicationDataChangedNotifications(
391    const Extension* extension) {
392  FOR_EACH_OBSERVER(Observer, observers_, OnApplicationDataChanged(extension,
393                                                                   profile_));
394}
395
396void 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
404void BackgroundApplicationListModel::OnExtensionUnloaded(
405    const Extension* extension) {
406  if (!IsBackgroundApp(*extension, profile_))
407    return;
408  Update();
409  DissociateApplicationData(extension);
410}
411
412void 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
433void 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.
441void 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