• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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