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