// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/background_page_tracker.h" #include #include #include #include "base/command_line.h" #include "base/utf_string_conversions.h" #include "base/values.h" #include "chrome/browser/background_application_list_model.h" #include "chrome/browser/background_contents_service.h" #include "chrome/browser/background_contents_service_factory.h" #include "chrome/browser/background_mode_manager.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/prefs/scoped_user_pref_update.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/pref_names.h" #include "content/common/notification_service.h" #include "content/common/notification_type.h" /////////////////////////////////////////////////////////////////////////////// // BackgroundPageTracker keeps a single DictionaryValue (stored at // prefs::kKnownBackgroundPages). We keep only two pieces of information for // each background page: the parent application/extension ID, and a boolean // flag that is true if the user has acknowledged this page. // // kKnownBackgroundPages: // DictionaryValue { // : false, // : true, // ... etc ... // } // static void BackgroundPageTracker::RegisterPrefs(PrefService* prefs) { prefs->RegisterDictionaryPref(prefs::kKnownBackgroundPages); } // static BackgroundPageTracker* BackgroundPageTracker::GetInstance() { return Singleton::get(); } int BackgroundPageTracker::GetBackgroundPageCount() { if (!IsEnabled()) return 0; PrefService* prefs = GetPrefService(); const DictionaryValue* contents = prefs->GetDictionary(prefs::kKnownBackgroundPages); return contents ? contents->size() : 0; } int BackgroundPageTracker::GetUnacknowledgedBackgroundPageCount() { if (!IsEnabled()) return 0; PrefService* prefs = GetPrefService(); const DictionaryValue* contents = prefs->GetDictionary(prefs::kKnownBackgroundPages); if (!contents) return 0; int count = 0; for (DictionaryValue::key_iterator it = contents->begin_keys(); it != contents->end_keys(); ++it) { Value* value; bool found = contents->GetWithoutPathExpansion(*it, &value); DCHECK(found); bool acknowledged = true; bool valid = value->GetAsBoolean(&acknowledged); DCHECK(valid); if (!acknowledged) count++; } return count; } void BackgroundPageTracker::AcknowledgeBackgroundPages() { if (!IsEnabled()) return; PrefService* prefs = GetPrefService(); DictionaryPrefUpdate update(prefs, prefs::kKnownBackgroundPages); DictionaryValue* contents = update.Get(); bool prefs_modified = false; for (DictionaryValue::key_iterator it = contents->begin_keys(); it != contents->end_keys(); ++it) { contents->SetWithoutPathExpansion(*it, Value::CreateBooleanValue(true)); prefs_modified = true; } if (prefs_modified) { prefs->ScheduleSavePersistentPrefs(); SendChangeNotification(); } } BackgroundPageTracker::BackgroundPageTracker() { // If background mode is disabled, just exit - don't load information from // prefs or listen for any notifications so we will act as if there are no // background pages, effectively disabling any associated badging. if (!IsEnabled()) return; // Check to make sure all of the extensions are loaded - once they are loaded // we can update the list. Profile* profile = g_browser_process->profile_manager()->GetDefaultProfile(); if (profile->GetExtensionService() && profile->GetExtensionService()->is_ready()) { UpdateExtensionList(); // We do not send any change notifications here, because the object was // just created (it doesn't seem appropriate to send a change notification // at initialization time). Also, since this is a singleton object, sending // a notification in the constructor can lead to deadlock if one of the // observers tries to get the singleton. } else { // Extensions aren't loaded yet - register to be notified when they are // ready. registrar_.Add(this, NotificationType::EXTENSIONS_READY, NotificationService::AllSources()); } } BackgroundPageTracker::~BackgroundPageTracker() { } PrefService* BackgroundPageTracker::GetPrefService() { PrefService* service = g_browser_process->local_state(); DCHECK(service); return service; } bool BackgroundPageTracker::IsEnabled() { // Disable the background page tracker for unittests. if (!g_browser_process->local_state()) return false; // BackgroundPageTracker is enabled if background mode is enabled. CommandLine* command_line = CommandLine::ForCurrentProcess(); return BackgroundModeManager::IsBackgroundModeEnabled(command_line); } void BackgroundPageTracker::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { switch (type.value) { case NotificationType::EXTENSIONS_READY: if (UpdateExtensionList()) SendChangeNotification(); break; case NotificationType::BACKGROUND_CONTENTS_OPENED: { std::string id = UTF16ToUTF8( Details(details)->application_id); OnBackgroundPageLoaded(id); break; } case NotificationType::EXTENSION_LOADED: { const Extension* extension = Details(details).ptr(); if (!extension->is_hosted_app() && extension->background_url().is_valid()) OnBackgroundPageLoaded(extension->id()); break; } case NotificationType::EXTENSION_UNLOADED: { std::string id = Details(details)->extension->id(); OnExtensionUnloaded(id); break; } default: NOTREACHED(); } } bool BackgroundPageTracker::UpdateExtensionList() { // Extensions are loaded - update our list. Profile* profile = g_browser_process->profile_manager()->GetDefaultProfile(); ExtensionService* extensions_service = profile->GetExtensionService(); DCHECK(extensions_service); // We will make two passes to update the list: // 1) Walk our list, and make sure that there's a corresponding extension for // each item in the list. If not, delete it (extension was uninstalled). // 2) Walk the set of currently loaded extensions and background contents, and // make sure there's an entry in our list for each one. If not, create one. PrefService* prefs = GetPrefService(); std::set keys_to_delete; bool pref_modified = false; // If we've never set any prefs, then this is the first launch ever, so we // want to automatically mark all existing extensions as acknowledged. bool first_launch = prefs->GetDictionary(prefs::kKnownBackgroundPages) == NULL; DictionaryPrefUpdate update(prefs, prefs::kKnownBackgroundPages); DictionaryValue* contents = update.Get(); for (DictionaryValue::key_iterator it = contents->begin_keys(); it != contents->end_keys(); ++it) { // Check to make sure that the parent extension is still enabled. const Extension* extension = extensions_service->GetExtensionById( *it, false); // If the extension is not loaded, add the id to our list of keys to delete // later (can't delete now since we're still iterating). if (!extension) { keys_to_delete.insert(*it); pref_modified = true; } } for (std::set::const_iterator iter = keys_to_delete.begin(); iter != keys_to_delete.end(); ++iter) { contents->RemoveWithoutPathExpansion(*iter, NULL); } // Look for new extensions/background contents. const ExtensionList* list = extensions_service->extensions(); for (ExtensionList::const_iterator iter = list->begin(); iter != list->begin(); ++iter) { // Any extension with a background page should be in our list. if ((*iter)->background_url().is_valid()) { // If we have not seen this extension ID before, add it to our list. if (!contents->HasKey((*iter)->id())) { contents->SetWithoutPathExpansion( (*iter)->id(), Value::CreateBooleanValue(first_launch)); pref_modified = true; } } } // Add all apps with background contents also. BackgroundContentsService* background_contents_service = BackgroundContentsServiceFactory::GetForProfile(profile); std::vector background_contents = background_contents_service->GetBackgroundContents(); for (std::vector::const_iterator iter = background_contents.begin(); iter != background_contents.end(); ++iter) { std::string application_id = UTF16ToUTF8( background_contents_service->GetParentApplicationId(*iter)); if (!contents->HasKey(application_id)) { contents->SetWithoutPathExpansion( application_id, Value::CreateBooleanValue(first_launch)); pref_modified = true; } } // Register for when new pages are loaded/unloaded so we can update our list. registrar_.Add(this, NotificationType::EXTENSION_LOADED, NotificationService::AllSources()); registrar_.Add(this, NotificationType::EXTENSION_UNLOADED, NotificationService::AllSources()); registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_OPENED, NotificationService::AllSources()); // If we modified the list, save it to prefs and let our caller know. if (pref_modified) prefs->ScheduleSavePersistentPrefs(); return pref_modified; } void BackgroundPageTracker::OnBackgroundPageLoaded(const std::string& id) { DCHECK(IsEnabled()); PrefService* prefs = GetPrefService(); DictionaryPrefUpdate update(prefs, prefs::kKnownBackgroundPages); DictionaryValue* contents = update.Get(); // No need to update our list if this extension was already known. if (contents->HasKey(id)) return; // Update our list with this new as-yet-unacknowledged page. contents->SetWithoutPathExpansion(id, Value::CreateBooleanValue(false)); prefs->ScheduleSavePersistentPrefs(); SendChangeNotification(); } void BackgroundPageTracker::OnExtensionUnloaded(const std::string& id) { DCHECK(IsEnabled()); PrefService* prefs = GetPrefService(); DictionaryPrefUpdate update(prefs, prefs::kKnownBackgroundPages); DictionaryValue* contents = update.Get(); if (!contents->HasKey(id)) return; contents->RemoveWithoutPathExpansion(id, NULL); prefs->ScheduleSavePersistentPrefs(); SendChangeNotification(); } void BackgroundPageTracker::SendChangeNotification() { NotificationService::current()->Notify( NotificationType::BACKGROUND_PAGE_TRACKER_CHANGED, Source(this), NotificationService::NoDetails()); }