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
43void BackgroundPageTracker::RegisterPrefs(PrefService* prefs) {
44  prefs->RegisterDictionaryPref(prefs::kKnownBackgroundPages);
45}
46
47// static
48BackgroundPageTracker* BackgroundPageTracker::GetInstance() {
49  return Singleton<BackgroundPageTracker>::get();
50}
51
52int 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
62int 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
85void 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
103BackgroundPageTracker::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
129BackgroundPageTracker::~BackgroundPageTracker() {
130}
131
132PrefService* BackgroundPageTracker::GetPrefService() {
133  PrefService* service = g_browser_process->local_state();
134  DCHECK(service);
135  return service;
136}
137
138bool 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
148void 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
179bool 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
267void 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
282void 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
296void BackgroundPageTracker::SendChangeNotification() {
297  NotificationService::current()->Notify(
298      NotificationType::BACKGROUND_PAGE_TRACKER_CHANGED,
299      Source<BackgroundPageTracker>(this),
300      NotificationService::NoDetails());
301}
302