普通文本  |  302行  |  10.97 KB

// 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());
}