Home | History | Annotate | Download | only in browser
      1 // Copyright (c) 2011 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_page_tracker.h"
      6 
      7 #include <set>
      8 #include <string>
      9 #include <vector>
     10 
     11 #include "base/command_line.h"
     12 #include "base/utf_string_conversions.h"
     13 #include "base/values.h"
     14 #include "chrome/browser/background_application_list_model.h"
     15 #include "chrome/browser/background_contents_service.h"
     16 #include "chrome/browser/background_contents_service_factory.h"
     17 #include "chrome/browser/background_mode_manager.h"
     18 #include "chrome/browser/browser_process.h"
     19 #include "chrome/browser/extensions/extension_service.h"
     20 #include "chrome/browser/prefs/pref_service.h"
     21 #include "chrome/browser/prefs/scoped_user_pref_update.h"
     22 #include "chrome/browser/profiles/profile.h"
     23 #include "chrome/browser/profiles/profile_manager.h"
     24 #include "chrome/common/extensions/extension.h"
     25 #include "chrome/common/pref_names.h"
     26 #include "content/common/notification_service.h"
     27 #include "content/common/notification_type.h"
     28 
     29 ///////////////////////////////////////////////////////////////////////////////
     30 // BackgroundPageTracker keeps a single DictionaryValue (stored at
     31 // prefs::kKnownBackgroundPages). We keep only two pieces of information for
     32 // each background page: the parent application/extension ID, and a boolean
     33 // flag that is true if the user has acknowledged this page.
     34 //
     35 // kKnownBackgroundPages:
     36 //    DictionaryValue {
     37 //       <appid_1>: false,
     38 //       <appid_2>: true,
     39 //         ... etc ...
     40 //    }
     41 
     42 // static
     43 void BackgroundPageTracker::RegisterPrefs(PrefService* prefs) {
     44   prefs->RegisterDictionaryPref(prefs::kKnownBackgroundPages);
     45 }
     46 
     47 // static
     48 BackgroundPageTracker* BackgroundPageTracker::GetInstance() {
     49   return Singleton<BackgroundPageTracker>::get();
     50 }
     51 
     52 int BackgroundPageTracker::GetBackgroundPageCount() {
     53   if (!IsEnabled())
     54     return 0;
     55 
     56   PrefService* prefs = GetPrefService();
     57   const DictionaryValue* contents =
     58       prefs->GetDictionary(prefs::kKnownBackgroundPages);
     59   return contents ? contents->size() : 0;
     60 }
     61 
     62 int BackgroundPageTracker::GetUnacknowledgedBackgroundPageCount() {
     63   if (!IsEnabled())
     64     return 0;
     65   PrefService* prefs = GetPrefService();
     66   const DictionaryValue* contents =
     67       prefs->GetDictionary(prefs::kKnownBackgroundPages);
     68   if (!contents)
     69     return 0;
     70   int count = 0;
     71   for (DictionaryValue::key_iterator it = contents->begin_keys();
     72        it != contents->end_keys(); ++it) {
     73     Value* value;
     74     bool found = contents->GetWithoutPathExpansion(*it, &value);
     75     DCHECK(found);
     76     bool acknowledged = true;
     77     bool valid = value->GetAsBoolean(&acknowledged);
     78     DCHECK(valid);
     79     if (!acknowledged)
     80       count++;
     81   }
     82   return count;
     83 }
     84 
     85 void BackgroundPageTracker::AcknowledgeBackgroundPages() {
     86   if (!IsEnabled())
     87     return;
     88   PrefService* prefs = GetPrefService();
     89   DictionaryPrefUpdate update(prefs, prefs::kKnownBackgroundPages);
     90   DictionaryValue* contents = update.Get();
     91   bool prefs_modified = false;
     92   for (DictionaryValue::key_iterator it = contents->begin_keys();
     93        it != contents->end_keys(); ++it) {
     94     contents->SetWithoutPathExpansion(*it, Value::CreateBooleanValue(true));
     95     prefs_modified = true;
     96   }
     97   if (prefs_modified) {
     98     prefs->ScheduleSavePersistentPrefs();
     99     SendChangeNotification();
    100   }
    101 }
    102 
    103 BackgroundPageTracker::BackgroundPageTracker() {
    104   // If background mode is disabled, just exit - don't load information from
    105   // prefs or listen for any notifications so we will act as if there are no
    106   // background pages, effectively disabling any associated badging.
    107   if (!IsEnabled())
    108     return;
    109 
    110   // Check to make sure all of the extensions are loaded - once they are loaded
    111   // we can update the list.
    112   Profile* profile = g_browser_process->profile_manager()->GetDefaultProfile();
    113   if (profile->GetExtensionService() &&
    114       profile->GetExtensionService()->is_ready()) {
    115     UpdateExtensionList();
    116     // We do not send any change notifications here, because the object was
    117     // just created (it doesn't seem appropriate to send a change notification
    118     // at initialization time). Also, since this is a singleton object, sending
    119     // a notification in the constructor can lead to deadlock if one of the
    120     // observers tries to get the singleton.
    121   } else {
    122     // Extensions aren't loaded yet - register to be notified when they are
    123     // ready.
    124     registrar_.Add(this, NotificationType::EXTENSIONS_READY,
    125                    NotificationService::AllSources());
    126   }
    127 }
    128 
    129 BackgroundPageTracker::~BackgroundPageTracker() {
    130 }
    131 
    132 PrefService* BackgroundPageTracker::GetPrefService() {
    133   PrefService* service = g_browser_process->local_state();
    134   DCHECK(service);
    135   return service;
    136 }
    137 
    138 bool BackgroundPageTracker::IsEnabled() {
    139   // Disable the background page tracker for unittests.
    140   if (!g_browser_process->local_state())
    141     return false;
    142 
    143   // BackgroundPageTracker is enabled if background mode is enabled.
    144   CommandLine* command_line = CommandLine::ForCurrentProcess();
    145   return BackgroundModeManager::IsBackgroundModeEnabled(command_line);
    146 }
    147 
    148 void BackgroundPageTracker::Observe(NotificationType type,
    149                                     const NotificationSource& source,
    150                                     const NotificationDetails& details) {
    151   switch (type.value) {
    152     case NotificationType::EXTENSIONS_READY:
    153       if (UpdateExtensionList())
    154         SendChangeNotification();
    155       break;
    156     case NotificationType::BACKGROUND_CONTENTS_OPENED: {
    157       std::string id = UTF16ToUTF8(
    158           Details<BackgroundContentsOpenedDetails>(details)->application_id);
    159       OnBackgroundPageLoaded(id);
    160       break;
    161     }
    162     case NotificationType::EXTENSION_LOADED: {
    163       const Extension* extension = Details<const Extension>(details).ptr();
    164       if (!extension->is_hosted_app() &&
    165           extension->background_url().is_valid())
    166         OnBackgroundPageLoaded(extension->id());
    167       break;
    168     }
    169     case NotificationType::EXTENSION_UNLOADED: {
    170       std::string id = Details<UnloadedExtensionInfo>(details)->extension->id();
    171       OnExtensionUnloaded(id);
    172       break;
    173     }
    174     default:
    175       NOTREACHED();
    176   }
    177 }
    178 
    179 bool BackgroundPageTracker::UpdateExtensionList() {
    180   // Extensions are loaded - update our list.
    181   Profile* profile = g_browser_process->profile_manager()->GetDefaultProfile();
    182   ExtensionService* extensions_service = profile->GetExtensionService();
    183   DCHECK(extensions_service);
    184 
    185   // We will make two passes to update the list:
    186   // 1) Walk our list, and make sure that there's a corresponding extension for
    187   //    each item in the list. If not, delete it (extension was uninstalled).
    188   // 2) Walk the set of currently loaded extensions and background contents, and
    189   //    make sure there's an entry in our list for each one. If not, create one.
    190 
    191   PrefService* prefs = GetPrefService();
    192   std::set<std::string> keys_to_delete;
    193   bool pref_modified = false;
    194   // If we've never set any prefs, then this is the first launch ever, so we
    195   // want to automatically mark all existing extensions as acknowledged.
    196   bool first_launch =
    197       prefs->GetDictionary(prefs::kKnownBackgroundPages) == NULL;
    198   DictionaryPrefUpdate update(prefs, prefs::kKnownBackgroundPages);
    199   DictionaryValue* contents = update.Get();
    200   for (DictionaryValue::key_iterator it = contents->begin_keys();
    201        it != contents->end_keys(); ++it) {
    202     // Check to make sure that the parent extension is still enabled.
    203     const Extension* extension = extensions_service->GetExtensionById(
    204         *it, false);
    205     // If the extension is not loaded, add the id to our list of keys to delete
    206     // later (can't delete now since we're still iterating).
    207     if (!extension) {
    208       keys_to_delete.insert(*it);
    209       pref_modified = true;
    210     }
    211   }
    212 
    213   for (std::set<std::string>::const_iterator iter = keys_to_delete.begin();
    214        iter != keys_to_delete.end();
    215        ++iter) {
    216     contents->RemoveWithoutPathExpansion(*iter, NULL);
    217   }
    218 
    219   // Look for new extensions/background contents.
    220   const ExtensionList* list = extensions_service->extensions();
    221   for (ExtensionList::const_iterator iter = list->begin();
    222        iter != list->begin();
    223        ++iter) {
    224     // Any extension with a background page should be in our list.
    225     if ((*iter)->background_url().is_valid()) {
    226       // If we have not seen this extension ID before, add it to our list.
    227       if (!contents->HasKey((*iter)->id())) {
    228         contents->SetWithoutPathExpansion(
    229             (*iter)->id(), Value::CreateBooleanValue(first_launch));
    230         pref_modified = true;
    231       }
    232     }
    233   }
    234 
    235   // Add all apps with background contents also.
    236   BackgroundContentsService* background_contents_service =
    237       BackgroundContentsServiceFactory::GetForProfile(profile);
    238   std::vector<BackgroundContents*> background_contents =
    239       background_contents_service->GetBackgroundContents();
    240   for (std::vector<BackgroundContents*>::const_iterator iter =
    241            background_contents.begin();
    242        iter != background_contents.end();
    243        ++iter) {
    244      std::string application_id = UTF16ToUTF8(
    245          background_contents_service->GetParentApplicationId(*iter));
    246      if (!contents->HasKey(application_id)) {
    247         contents->SetWithoutPathExpansion(
    248             application_id, Value::CreateBooleanValue(first_launch));
    249         pref_modified = true;
    250      }
    251   }
    252 
    253   // Register for when new pages are loaded/unloaded so we can update our list.
    254   registrar_.Add(this, NotificationType::EXTENSION_LOADED,
    255                  NotificationService::AllSources());
    256   registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
    257                  NotificationService::AllSources());
    258   registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_OPENED,
    259                  NotificationService::AllSources());
    260 
    261   // If we modified the list, save it to prefs and let our caller know.
    262   if (pref_modified)
    263     prefs->ScheduleSavePersistentPrefs();
    264   return pref_modified;
    265 }
    266 
    267 void BackgroundPageTracker::OnBackgroundPageLoaded(const std::string& id) {
    268   DCHECK(IsEnabled());
    269   PrefService* prefs = GetPrefService();
    270   DictionaryPrefUpdate update(prefs, prefs::kKnownBackgroundPages);
    271   DictionaryValue* contents = update.Get();
    272   // No need to update our list if this extension was already known.
    273   if (contents->HasKey(id))
    274     return;
    275 
    276   // Update our list with this new as-yet-unacknowledged page.
    277   contents->SetWithoutPathExpansion(id, Value::CreateBooleanValue(false));
    278   prefs->ScheduleSavePersistentPrefs();
    279   SendChangeNotification();
    280 }
    281 
    282 void BackgroundPageTracker::OnExtensionUnloaded(const std::string& id) {
    283   DCHECK(IsEnabled());
    284   PrefService* prefs = GetPrefService();
    285   DictionaryPrefUpdate update(prefs, prefs::kKnownBackgroundPages);
    286   DictionaryValue* contents = update.Get();
    287 
    288   if (!contents->HasKey(id))
    289     return;
    290 
    291   contents->RemoveWithoutPathExpansion(id, NULL);
    292   prefs->ScheduleSavePersistentPrefs();
    293   SendChangeNotification();
    294 }
    295 
    296 void BackgroundPageTracker::SendChangeNotification() {
    297   NotificationService::current()->Notify(
    298       NotificationType::BACKGROUND_PAGE_TRACKER_CHANGED,
    299       Source<BackgroundPageTracker>(this),
    300       NotificationService::NoDetails());
    301 }
    302