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
RegisterPrefs(PrefService * prefs)43 void BackgroundPageTracker::RegisterPrefs(PrefService* prefs) {
44 prefs->RegisterDictionaryPref(prefs::kKnownBackgroundPages);
45 }
46
47 // static
GetInstance()48 BackgroundPageTracker* BackgroundPageTracker::GetInstance() {
49 return Singleton<BackgroundPageTracker>::get();
50 }
51
GetBackgroundPageCount()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
GetUnacknowledgedBackgroundPageCount()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
AcknowledgeBackgroundPages()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
BackgroundPageTracker()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
~BackgroundPageTracker()129 BackgroundPageTracker::~BackgroundPageTracker() {
130 }
131
GetPrefService()132 PrefService* BackgroundPageTracker::GetPrefService() {
133 PrefService* service = g_browser_process->local_state();
134 DCHECK(service);
135 return service;
136 }
137
IsEnabled()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
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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
UpdateExtensionList()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
OnBackgroundPageLoaded(const std::string & id)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
OnExtensionUnloaded(const std::string & id)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
SendChangeNotification()296 void BackgroundPageTracker::SendChangeNotification() {
297 NotificationService::current()->Notify(
298 NotificationType::BACKGROUND_PAGE_TRACKER_CHANGED,
299 Source<BackgroundPageTracker>(this),
300 NotificationService::NoDetails());
301 }
302