| // 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 <set> |
| #include <string> |
| #include <vector> |
| |
| #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 { |
| // <appid_1>: false, |
| // <appid_2>: true, |
| // ... etc ... |
| // } |
| |
| // static |
| void BackgroundPageTracker::RegisterPrefs(PrefService* prefs) { |
| prefs->RegisterDictionaryPref(prefs::kKnownBackgroundPages); |
| } |
| |
| // static |
| BackgroundPageTracker* BackgroundPageTracker::GetInstance() { |
| return Singleton<BackgroundPageTracker>::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<BackgroundContentsOpenedDetails>(details)->application_id); |
| OnBackgroundPageLoaded(id); |
| break; |
| } |
| case NotificationType::EXTENSION_LOADED: { |
| const Extension* extension = Details<const Extension>(details).ptr(); |
| if (!extension->is_hosted_app() && |
| extension->background_url().is_valid()) |
| OnBackgroundPageLoaded(extension->id()); |
| break; |
| } |
| case NotificationType::EXTENSION_UNLOADED: { |
| std::string id = Details<UnloadedExtensionInfo>(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<std::string> 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<std::string>::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<BackgroundContents*> background_contents = |
| background_contents_service->GetBackgroundContents(); |
| for (std::vector<BackgroundContents*>::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<BackgroundPageTracker>(this), |
| NotificationService::NoDetails()); |
| } |