// 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/cookies_tree_model.h"

#include <algorithm>
#include <functional>
#include <vector>

#include "base/callback.h"
#include "base/memory/linked_ptr.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/extensions/extension_service.h"
#include "content/browser/in_process_webkit/webkit_context.h"
#include "grit/app_resources.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "net/base/cookie_monster.h"
#include "net/base/registry_controlled_domain.h"
#include "net/url_request/url_request_context.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"

static const char kFileOriginNodeName[] = "file://";

///////////////////////////////////////////////////////////////////////////////
// CookieTreeNode, public:

void CookieTreeNode::DeleteStoredObjects() {
  std::for_each(children().begin(),
                children().end(),
                std::mem_fun(&CookieTreeNode::DeleteStoredObjects));
}

CookiesTreeModel* CookieTreeNode::GetModel() const {
  if (parent())
    return parent()->GetModel();
  else
    return NULL;
}

///////////////////////////////////////////////////////////////////////////////
// CookieTreeCookieNode, public:

CookieTreeCookieNode::CookieTreeCookieNode(
    net::CookieMonster::CanonicalCookie* cookie)
    : CookieTreeNode(UTF8ToUTF16(cookie->Name())),
      cookie_(cookie) {
}

CookieTreeCookieNode::~CookieTreeCookieNode() {}

void CookieTreeCookieNode::DeleteStoredObjects() {
  // notify CookieMonster that we should delete this cookie
  // We have stored a copy of all the cookies in the model, and our model is
  // never re-calculated. Thus, we just need to delete the nodes from our
  // model, and tell CookieMonster to delete the cookies. We can keep the
  // vector storing the cookies in-tact and not delete from there (that would
  // invalidate our pointers), and the fact that it contains semi out-of-date
  // data is not problematic as we don't re-build the model based on that.
  GetModel()->cookie_monster_->DeleteCanonicalCookie(*cookie_);
}

CookieTreeNode::DetailedInfo CookieTreeCookieNode::GetDetailedInfo() const {
  return DetailedInfo(parent()->parent()->GetTitle(),
                      DetailedInfo::TYPE_COOKIE,
                      cookie_, NULL, NULL, NULL, NULL, NULL);
}

namespace {
// comparison functor, for use in CookieTreeRootNode
class OriginNodeComparator {
 public:
  bool operator() (const CookieTreeNode* lhs,
                   const CookieTreeNode* rhs) {
    // We want to order by registry controlled domain, so we would get
    // google.com, ad.google.com, www.google.com,
    // microsoft.com, ad.microsoft.com. CanonicalizeHost transforms the origins
    // into a form like google.com.www so that string comparisons work.
    return (CanonicalizeHost(lhs->GetTitle()) <
            CanonicalizeHost(rhs->GetTitle()));
  }

 private:
  static std::string CanonicalizeHost(const string16& host16) {
    // The canonicalized representation makes the registry controlled domain
    // come first, and then adds subdomains in reverse order, e.g.
    // 1.mail.google.com would become google.com.mail.1, and then a standard
    // string comparison works to order hosts by registry controlled domain
    // first. Leading dots are ignored, ".google.com" is the same as
    // "google.com".

    std::string host = UTF16ToUTF8(host16);
    std::string retval = net::RegistryControlledDomainService::
        GetDomainAndRegistry(host);
    if (!retval.length())  // Is an IP address or other special origin.
      return host;

    std::string::size_type position = host.rfind(retval);

    // The host may be the registry controlled domain, in which case fail fast.
    if (position == 0 || position == std::string::npos)
      return host;

    // If host is www.google.com, retval will contain google.com at this point.
    // Start operating to the left of the registry controlled domain, e.g. in
    // the www.google.com example, start at index 3.
    --position;

    // If position == 0, that means it's a dot; this will be ignored to treat
    // ".google.com" the same as "google.com".
    while (position > 0) {
      retval += std::string(".");
      // Copy up to the next dot. host[position] is a dot so start after it.
      std::string::size_type next_dot = host.rfind(".", position - 1);
      if (next_dot == std::string::npos) {
        retval += host.substr(0, position);
        break;
      }
      retval += host.substr(next_dot + 1, position - (next_dot + 1));
      position = next_dot;
    }
    return retval;
  }
};

}  // namespace

///////////////////////////////////////////////////////////////////////////////
// CookieTreeAppCacheNode, public:

CookieTreeAppCacheNode::CookieTreeAppCacheNode(
    const appcache::AppCacheInfo* appcache_info)
    : CookieTreeNode(UTF8ToUTF16(appcache_info->manifest_url.spec())),
      appcache_info_(appcache_info) {
}

void CookieTreeAppCacheNode::DeleteStoredObjects() {
  DCHECK(GetModel()->appcache_helper_);
  GetModel()->appcache_helper_->DeleteAppCacheGroup(
      appcache_info_->manifest_url);
}

CookieTreeNode::DetailedInfo CookieTreeAppCacheNode::GetDetailedInfo() const {
  return DetailedInfo(parent()->parent()->GetTitle(),
                      DetailedInfo::TYPE_APPCACHE,
                      NULL, NULL, NULL, NULL, appcache_info_, NULL);
}

///////////////////////////////////////////////////////////////////////////////
// CookieTreeDatabaseNode, public:

CookieTreeDatabaseNode::CookieTreeDatabaseNode(
    BrowsingDataDatabaseHelper::DatabaseInfo* database_info)
    : CookieTreeNode(database_info->database_name.empty() ?
          l10n_util::GetStringUTF16(IDS_COOKIES_WEB_DATABASE_UNNAMED_NAME) :
          UTF8ToUTF16(database_info->database_name)),
      database_info_(database_info) {
}

CookieTreeDatabaseNode::~CookieTreeDatabaseNode() {}

void CookieTreeDatabaseNode::DeleteStoredObjects() {
  GetModel()->database_helper_->DeleteDatabase(
      database_info_->origin_identifier, database_info_->database_name);
}

CookieTreeNode::DetailedInfo CookieTreeDatabaseNode::GetDetailedInfo() const {
  return DetailedInfo(parent()->parent()->GetTitle(),
                      DetailedInfo::TYPE_DATABASE,
                      NULL, database_info_, NULL, NULL, NULL, NULL);
}

///////////////////////////////////////////////////////////////////////////////
// CookieTreeLocalStorageNode, public:

CookieTreeLocalStorageNode::CookieTreeLocalStorageNode(
    BrowsingDataLocalStorageHelper::LocalStorageInfo* local_storage_info)
    : CookieTreeNode(UTF8ToUTF16(
          local_storage_info->origin.empty() ?
              local_storage_info->database_identifier :
              local_storage_info->origin)),
      local_storage_info_(local_storage_info) {
}

CookieTreeLocalStorageNode::~CookieTreeLocalStorageNode() {}

void CookieTreeLocalStorageNode::DeleteStoredObjects() {
  GetModel()->local_storage_helper_->DeleteLocalStorageFile(
      local_storage_info_->file_path);
}

CookieTreeNode::DetailedInfo
CookieTreeLocalStorageNode::GetDetailedInfo() const {
  return DetailedInfo(parent()->parent()->GetTitle(),
                      DetailedInfo::TYPE_LOCAL_STORAGE,
                      NULL, NULL, local_storage_info_, NULL, NULL, NULL);
}

///////////////////////////////////////////////////////////////////////////////
// CookieTreeSessionStorageNode, public:

CookieTreeSessionStorageNode::CookieTreeSessionStorageNode(
    BrowsingDataLocalStorageHelper::LocalStorageInfo* session_storage_info)
    : CookieTreeNode(UTF8ToUTF16(
          session_storage_info->origin.empty() ?
              session_storage_info->database_identifier :
              session_storage_info->origin)),
      session_storage_info_(session_storage_info) {
}

CookieTreeSessionStorageNode::~CookieTreeSessionStorageNode() {}

CookieTreeNode::DetailedInfo
CookieTreeSessionStorageNode::GetDetailedInfo() const {
  return DetailedInfo(parent()->parent()->GetTitle(),
                      DetailedInfo::TYPE_SESSION_STORAGE,
                      NULL, NULL, NULL, session_storage_info_, NULL, NULL);
}

///////////////////////////////////////////////////////////////////////////////
// CookieTreeIndexedDBNode, public:

CookieTreeIndexedDBNode::CookieTreeIndexedDBNode(
    BrowsingDataIndexedDBHelper::IndexedDBInfo* indexed_db_info)
    : CookieTreeNode(UTF8ToUTF16(
          indexed_db_info->origin.empty() ?
              indexed_db_info->database_identifier :
              indexed_db_info->origin)),
      indexed_db_info_(indexed_db_info) {
}

CookieTreeIndexedDBNode::~CookieTreeIndexedDBNode() {}

void CookieTreeIndexedDBNode::DeleteStoredObjects() {
  GetModel()->indexed_db_helper_->DeleteIndexedDBFile(
      indexed_db_info_->file_path);
}

CookieTreeNode::DetailedInfo CookieTreeIndexedDBNode::GetDetailedInfo() const {
  return DetailedInfo(parent()->parent()->GetTitle(),
                      DetailedInfo::TYPE_INDEXED_DB,
                      NULL, NULL, NULL, NULL, NULL, indexed_db_info_);
}

///////////////////////////////////////////////////////////////////////////////
// CookieTreeRootNode, public:

CookieTreeRootNode::CookieTreeRootNode(CookiesTreeModel* model)
    : model_(model) {
}

CookieTreeRootNode::~CookieTreeRootNode() {}

CookieTreeOriginNode* CookieTreeRootNode::GetOrCreateOriginNode(
    const GURL& url) {
  CookieTreeOriginNode origin_node(url);

  // First see if there is an existing match.
  std::vector<CookieTreeNode*>::iterator origin_node_iterator =
      lower_bound(children().begin(),
                  children().end(),
                  &origin_node,
                  OriginNodeComparator());

  if (origin_node_iterator != children().end() &&
      WideToUTF16Hack(CookieTreeOriginNode::TitleForUrl(url)) ==
      (*origin_node_iterator)->GetTitle())
    return static_cast<CookieTreeOriginNode*>(*origin_node_iterator);
  // Node doesn't exist, create a new one and insert it into the (ordered)
  // children.
  CookieTreeOriginNode* retval = new CookieTreeOriginNode(url);
  DCHECK(model_);
  model_->Add(this, retval, (origin_node_iterator - children().begin()));
  return retval;
}

CookiesTreeModel* CookieTreeRootNode::GetModel() const {
  return model_;
}

CookieTreeNode::DetailedInfo CookieTreeRootNode::GetDetailedInfo() const {
  return DetailedInfo(string16(),
                      DetailedInfo::TYPE_ROOT,
                      NULL, NULL, NULL, NULL, NULL, NULL);
}

///////////////////////////////////////////////////////////////////////////////
// CookieTreeOriginNode, public:

// static
std::wstring CookieTreeOriginNode::TitleForUrl(
    const GURL& url) {
  return UTF8ToWide(url.SchemeIsFile() ? kFileOriginNodeName : url.host());
}

CookieTreeOriginNode::CookieTreeOriginNode(const GURL& url)
    : CookieTreeNode(WideToUTF16Hack(TitleForUrl(url))),
      cookies_child_(NULL),
      databases_child_(NULL),
      local_storages_child_(NULL),
      session_storages_child_(NULL),
      appcaches_child_(NULL),
      indexed_dbs_child_(NULL),
      url_(url) {}

CookieTreeOriginNode::~CookieTreeOriginNode() {}

CookieTreeNode::DetailedInfo CookieTreeOriginNode::GetDetailedInfo() const {
  return DetailedInfo(GetTitle(),
                      DetailedInfo::TYPE_ORIGIN,
                      NULL, NULL, NULL, NULL, NULL, NULL);
}

CookieTreeCookiesNode* CookieTreeOriginNode::GetOrCreateCookiesNode() {
  if (cookies_child_)
    return cookies_child_;
  cookies_child_ = new CookieTreeCookiesNode;
  AddChildSortedByTitle(cookies_child_);
  return cookies_child_;
}

CookieTreeDatabasesNode* CookieTreeOriginNode::GetOrCreateDatabasesNode() {
  if (databases_child_)
    return databases_child_;
  databases_child_ = new CookieTreeDatabasesNode;
  AddChildSortedByTitle(databases_child_);
  return databases_child_;
}

CookieTreeLocalStoragesNode*
    CookieTreeOriginNode::GetOrCreateLocalStoragesNode() {
  if (local_storages_child_)
    return local_storages_child_;
  local_storages_child_ = new CookieTreeLocalStoragesNode;
  AddChildSortedByTitle(local_storages_child_);
  return local_storages_child_;
}

CookieTreeSessionStoragesNode*
    CookieTreeOriginNode::GetOrCreateSessionStoragesNode() {
  if (session_storages_child_)
    return session_storages_child_;
  session_storages_child_ = new CookieTreeSessionStoragesNode;
  AddChildSortedByTitle(session_storages_child_);
  return session_storages_child_;
}

CookieTreeAppCachesNode* CookieTreeOriginNode::GetOrCreateAppCachesNode() {
  if (appcaches_child_)
    return appcaches_child_;
  appcaches_child_ = new CookieTreeAppCachesNode;
  AddChildSortedByTitle(appcaches_child_);
  return appcaches_child_;
}

CookieTreeIndexedDBsNode* CookieTreeOriginNode::GetOrCreateIndexedDBsNode() {
  if (indexed_dbs_child_)
    return indexed_dbs_child_;
  indexed_dbs_child_ = new CookieTreeIndexedDBsNode;
  AddChildSortedByTitle(indexed_dbs_child_);
  return indexed_dbs_child_;
}

void CookieTreeOriginNode::CreateContentException(
    HostContentSettingsMap* content_settings, ContentSetting setting) const {
  if (CanCreateContentException()) {
    content_settings->AddExceptionForURL(url_,
                                         CONTENT_SETTINGS_TYPE_COOKIES,
                                         "",
                                         setting);
  }
}

bool CookieTreeOriginNode::CanCreateContentException() const {
  return !url_.SchemeIsFile();
}

///////////////////////////////////////////////////////////////////////////////
// CookieTreeCookiesNode, public:

CookieTreeCookiesNode::CookieTreeCookiesNode()
    : CookieTreeNode(l10n_util::GetStringUTF16(IDS_COOKIES_COOKIES)) {
}

CookieTreeCookiesNode::~CookieTreeCookiesNode() {
}

CookieTreeNode::DetailedInfo CookieTreeCookiesNode::GetDetailedInfo() const {
  return DetailedInfo(parent()->GetTitle(),
                      DetailedInfo::TYPE_COOKIES,
                      NULL, NULL, NULL, NULL, NULL, NULL);
}

///////////////////////////////////////////////////////////////////////////////
// CookieTreeAppCachesNode, public:

CookieTreeAppCachesNode::CookieTreeAppCachesNode()
    : CookieTreeNode(l10n_util::GetStringUTF16(
                         IDS_COOKIES_APPLICATION_CACHES)) {
}

CookieTreeAppCachesNode::~CookieTreeAppCachesNode() {}

CookieTreeNode::DetailedInfo CookieTreeAppCachesNode::GetDetailedInfo() const {
  return DetailedInfo(parent()->GetTitle(),
                      DetailedInfo::TYPE_APPCACHES,
                      NULL, NULL, NULL, NULL, NULL, NULL);
}

///////////////////////////////////////////////////////////////////////////////
// CookieTreeDatabasesNode, public:

CookieTreeDatabasesNode::CookieTreeDatabasesNode()
    : CookieTreeNode(l10n_util::GetStringUTF16(IDS_COOKIES_WEB_DATABASES)) {
}

CookieTreeDatabasesNode::~CookieTreeDatabasesNode() {}

CookieTreeNode::DetailedInfo CookieTreeDatabasesNode::GetDetailedInfo() const {
  return DetailedInfo(parent()->GetTitle(),
                      DetailedInfo::TYPE_DATABASES,
                      NULL, NULL, NULL, NULL, NULL, NULL);
}

///////////////////////////////////////////////////////////////////////////////
// CookieTreeLocalStoragesNode, public:

CookieTreeLocalStoragesNode::CookieTreeLocalStoragesNode()
    : CookieTreeNode(l10n_util::GetStringUTF16(IDS_COOKIES_LOCAL_STORAGE)) {
}

CookieTreeLocalStoragesNode::~CookieTreeLocalStoragesNode() {}

CookieTreeNode::DetailedInfo
CookieTreeLocalStoragesNode::GetDetailedInfo() const {
  return DetailedInfo(parent()->GetTitle(),
                      DetailedInfo::TYPE_LOCAL_STORAGES,
                      NULL, NULL, NULL, NULL, NULL, NULL);
}

///////////////////////////////////////////////////////////////////////////////
// CookieTreeSessionStoragesNode, public:

CookieTreeSessionStoragesNode::CookieTreeSessionStoragesNode()
    : CookieTreeNode(l10n_util::GetStringUTF16(IDS_COOKIES_SESSION_STORAGE)) {
}

CookieTreeSessionStoragesNode::~CookieTreeSessionStoragesNode() {}

CookieTreeNode::DetailedInfo
CookieTreeSessionStoragesNode::GetDetailedInfo() const {
  return DetailedInfo(parent()->GetTitle(),
                      DetailedInfo::TYPE_SESSION_STORAGES,
                      NULL, NULL, NULL, NULL, NULL, NULL);
}

///////////////////////////////////////////////////////////////////////////////
// CookieTreeIndexedDBsNode, public:

CookieTreeIndexedDBsNode::CookieTreeIndexedDBsNode()
    : CookieTreeNode(l10n_util::GetStringUTF16(IDS_COOKIES_INDEXED_DBS)) {
}

CookieTreeIndexedDBsNode::~CookieTreeIndexedDBsNode() {}

CookieTreeNode::DetailedInfo
CookieTreeIndexedDBsNode::GetDetailedInfo() const {
  return DetailedInfo(parent()->GetTitle(),
                      DetailedInfo::TYPE_INDEXED_DBS,
                      NULL, NULL, NULL, NULL, NULL, NULL);
}

///////////////////////////////////////////////////////////////////////////////
// CookieTreeNode, protected

bool CookieTreeNode::NodeTitleComparator::operator() (
    const CookieTreeNode* lhs, const CookieTreeNode* rhs) {
  const CookieTreeNode* left =
      static_cast<const CookieTreeNode*>(lhs);
  const CookieTreeNode* right =
      static_cast<const CookieTreeNode*>(rhs);
  return (left->GetTitle() < right->GetTitle());
}

void CookieTreeNode::AddChildSortedByTitle(CookieTreeNode* new_child) {
  std::vector<CookieTreeNode*>::iterator iter =
      lower_bound(children().begin(),
                  children().end(),
                  new_child,
                  NodeTitleComparator());
  GetModel()->Add(this, new_child, iter - children().begin());
}

///////////////////////////////////////////////////////////////////////////////
// CookiesTreeModel, public:

CookiesTreeModel::CookiesTreeModel(
    net::CookieMonster* cookie_monster,
    BrowsingDataDatabaseHelper* database_helper,
    BrowsingDataLocalStorageHelper* local_storage_helper,
    BrowsingDataLocalStorageHelper* session_storage_helper,
    BrowsingDataAppCacheHelper* appcache_helper,
    BrowsingDataIndexedDBHelper* indexed_db_helper,
    bool use_cookie_source)
    : ALLOW_THIS_IN_INITIALIZER_LIST(ui::TreeNodeModel<CookieTreeNode>(
          new CookieTreeRootNode(this))),
      cookie_monster_(cookie_monster),
      appcache_helper_(appcache_helper),
      database_helper_(database_helper),
      local_storage_helper_(local_storage_helper),
      session_storage_helper_(session_storage_helper),
      indexed_db_helper_(indexed_db_helper),
      batch_update_(0),
      use_cookie_source_(use_cookie_source) {
  LoadCookies();
  DCHECK(database_helper_);
  database_helper_->StartFetching(NewCallback(
      this, &CookiesTreeModel::OnDatabaseModelInfoLoaded));
  DCHECK(local_storage_helper_);
  local_storage_helper_->StartFetching(NewCallback(
      this, &CookiesTreeModel::OnLocalStorageModelInfoLoaded));
  if (session_storage_helper_) {
    session_storage_helper_->StartFetching(NewCallback(
        this, &CookiesTreeModel::OnSessionStorageModelInfoLoaded));
  }

  // TODO(michaeln): when all of the ui impls have been updated,
  // make this a required parameter.
  if (appcache_helper_) {
    appcache_helper_->StartFetching(NewCallback(
        this, &CookiesTreeModel::OnAppCacheModelInfoLoaded));
  }

  if (indexed_db_helper_) {
    indexed_db_helper_->StartFetching(NewCallback(
        this, &CookiesTreeModel::OnIndexedDBModelInfoLoaded));
  }
}

CookiesTreeModel::~CookiesTreeModel() {
  database_helper_->CancelNotification();
  local_storage_helper_->CancelNotification();
  if (session_storage_helper_)
    session_storage_helper_->CancelNotification();
  if (appcache_helper_)
    appcache_helper_->CancelNotification();
  if (indexed_db_helper_)
    indexed_db_helper_->CancelNotification();
}

///////////////////////////////////////////////////////////////////////////////
// CookiesTreeModel, TreeModel methods (public):

// TreeModel methods:
// Returns the set of icons for the nodes in the tree. You only need override
// this if you don't want to use the default folder icons.
void CookiesTreeModel::GetIcons(std::vector<SkBitmap>* icons) {
  icons->push_back(*ResourceBundle::GetSharedInstance().GetBitmapNamed(
      IDR_OMNIBOX_HTTP));
  icons->push_back(*ResourceBundle::GetSharedInstance().GetBitmapNamed(
      IDR_COOKIE_ICON));
  icons->push_back(*ResourceBundle::GetSharedInstance().GetBitmapNamed(
      IDR_COOKIE_STORAGE_ICON));
}

// Returns the index of the icon to use for |node|. Return -1 to use the
// default icon. The index is relative to the list of icons returned from
// GetIcons.
int CookiesTreeModel::GetIconIndex(ui::TreeModelNode* node) {
  CookieTreeNode* ct_node = static_cast<CookieTreeNode*>(node);
  switch (ct_node->GetDetailedInfo().node_type) {
    case CookieTreeNode::DetailedInfo::TYPE_ORIGIN:
      return ORIGIN;
    case CookieTreeNode::DetailedInfo::TYPE_COOKIE:
      return COOKIE;
    case CookieTreeNode::DetailedInfo::TYPE_DATABASE:
      return DATABASE;
    case CookieTreeNode::DetailedInfo::TYPE_LOCAL_STORAGE:
      return DATABASE;  // close enough
    case CookieTreeNode::DetailedInfo::TYPE_SESSION_STORAGE:
      return DATABASE;  // ditto
    case CookieTreeNode::DetailedInfo::TYPE_APPCACHE:
      return DATABASE;  // ditto
    case CookieTreeNode::DetailedInfo::TYPE_INDEXED_DB:
      return DATABASE;  // ditto
    default:
      break;
  }
  return -1;
}

void CookiesTreeModel::LoadCookies() {
  LoadCookiesWithFilter(std::wstring());
}

void CookiesTreeModel::LoadCookiesWithFilter(const std::wstring& filter) {
  // mmargh mmargh mmargh! delicious!

  all_cookies_ = cookie_monster_->GetAllCookies();
  CookieTreeRootNode* root = static_cast<CookieTreeRootNode*>(GetRoot());
  for (CookieList::iterator it = all_cookies_.begin();
       it != all_cookies_.end(); ++it) {
    std::string source_string = it->Source();
    if (source_string.empty() || !use_cookie_source_) {
      std::string domain = it->Domain();
      if (domain.length() > 1 && domain[0] == '.')
        domain = domain.substr(1);

      // We treat secure cookies just the same as normal ones.
      source_string = std::string(chrome::kHttpScheme) +
          chrome::kStandardSchemeSeparator + domain + "/";
    }

    GURL source(source_string);
    if (!filter.size() ||
        (CookieTreeOriginNode::TitleForUrl(source).find(filter) !=
         std::string::npos)) {
      CookieTreeOriginNode* origin_node =
          root->GetOrCreateOriginNode(source);
      CookieTreeCookiesNode* cookies_node =
          origin_node->GetOrCreateCookiesNode();
      CookieTreeCookieNode* new_cookie = new CookieTreeCookieNode(&*it);
      cookies_node->AddCookieNode(new_cookie);
    }
  }
}

void CookiesTreeModel::DeleteAllStoredObjects() {
  NotifyObserverBeginBatch();
  CookieTreeNode* root = GetRoot();
  root->DeleteStoredObjects();
  int num_children = root->child_count();
  for (int i = num_children - 1; i >= 0; --i)
    delete Remove(root, root->GetChild(i));
  NotifyObserverTreeNodeChanged(root);
  NotifyObserverEndBatch();
}

void CookiesTreeModel::DeleteCookieNode(CookieTreeNode* cookie_node) {
  if (cookie_node == GetRoot())
    return;
  cookie_node->DeleteStoredObjects();
  CookieTreeNode* parent_node = cookie_node->parent();
  delete Remove(parent_node, cookie_node);
  if (parent_node->child_count() == 0)
    DeleteCookieNode(parent_node);
}

void CookiesTreeModel::UpdateSearchResults(const std::wstring& filter) {
  CookieTreeNode* root = GetRoot();
  int num_children = root->child_count();
  NotifyObserverBeginBatch();
  for (int i = num_children - 1; i >= 0; --i)
    delete Remove(root, root->GetChild(i));
  LoadCookiesWithFilter(filter);
  PopulateDatabaseInfoWithFilter(filter);
  PopulateLocalStorageInfoWithFilter(filter);
  PopulateSessionStorageInfoWithFilter(filter);
  PopulateAppCacheInfoWithFilter(filter);
  PopulateIndexedDBInfoWithFilter(filter);
  NotifyObserverTreeNodeChanged(root);
  NotifyObserverEndBatch();
}

void CookiesTreeModel::AddCookiesTreeObserver(Observer* observer) {
  cookies_observer_list_.AddObserver(observer);
  // Call super so that TreeNodeModel can notify, too.
  ui::TreeNodeModel<CookieTreeNode>::AddObserver(observer);
}

void CookiesTreeModel::RemoveCookiesTreeObserver(Observer* observer) {
  cookies_observer_list_.RemoveObserver(observer);
  // Call super so that TreeNodeModel doesn't have dead pointers.
  ui::TreeNodeModel<CookieTreeNode>::RemoveObserver(observer);
}

void CookiesTreeModel::OnAppCacheModelInfoLoaded() {
  appcache_info_ = appcache_helper_->info_collection();
  PopulateAppCacheInfoWithFilter(std::wstring());
}

void CookiesTreeModel::PopulateAppCacheInfoWithFilter(
    const std::wstring& filter) {
  using appcache::AppCacheInfo;
  using appcache::AppCacheInfoVector;
  typedef std::map<GURL, AppCacheInfoVector> InfoByOrigin;

  if (!appcache_info_ || appcache_info_->infos_by_origin.empty())
    return;

  CookieTreeRootNode* root = static_cast<CookieTreeRootNode*>(GetRoot());
  NotifyObserverBeginBatch();
  for (InfoByOrigin::const_iterator origin =
           appcache_info_->infos_by_origin.begin();
       origin != appcache_info_->infos_by_origin.end(); ++origin) {
    std::wstring origin_node_name = UTF8ToWide(origin->first.host());
    if (filter.empty() ||
        (origin_node_name.find(filter) != std::wstring::npos)) {
      CookieTreeOriginNode* origin_node =
          root->GetOrCreateOriginNode(origin->first);
      CookieTreeAppCachesNode* appcaches_node =
          origin_node->GetOrCreateAppCachesNode();

      for (AppCacheInfoVector::const_iterator info = origin->second.begin();
           info != origin->second.end(); ++info) {
        appcaches_node->AddAppCacheNode(
            new CookieTreeAppCacheNode(&(*info)));
      }
    }
  }
  NotifyObserverTreeNodeChanged(root);
  NotifyObserverEndBatch();
}

void CookiesTreeModel::OnDatabaseModelInfoLoaded(
    const DatabaseInfoList& database_info) {
  database_info_list_ = database_info;
  PopulateDatabaseInfoWithFilter(std::wstring());
}

void CookiesTreeModel::PopulateDatabaseInfoWithFilter(
    const std::wstring& filter) {
  if (database_info_list_.empty())
    return;
  CookieTreeRootNode* root = static_cast<CookieTreeRootNode*>(GetRoot());
  NotifyObserverBeginBatch();
  for (DatabaseInfoList::iterator database_info = database_info_list_.begin();
       database_info != database_info_list_.end();
       ++database_info) {
    GURL origin(database_info->origin);

    if (!filter.size() ||
        (CookieTreeOriginNode::TitleForUrl(origin).find(filter) !=
         std::wstring::npos)) {
      CookieTreeOriginNode* origin_node =
          root->GetOrCreateOriginNode(origin);
      CookieTreeDatabasesNode* databases_node =
          origin_node->GetOrCreateDatabasesNode();
      databases_node->AddDatabaseNode(
          new CookieTreeDatabaseNode(&(*database_info)));
    }
  }
  NotifyObserverTreeNodeChanged(root);
  NotifyObserverEndBatch();
}

void CookiesTreeModel::OnLocalStorageModelInfoLoaded(
    const LocalStorageInfoList& local_storage_info) {
  local_storage_info_list_ = local_storage_info;
  PopulateLocalStorageInfoWithFilter(std::wstring());
}

void CookiesTreeModel::PopulateLocalStorageInfoWithFilter(
    const std::wstring& filter) {
  if (local_storage_info_list_.empty())
    return;
  CookieTreeRootNode* root = static_cast<CookieTreeRootNode*>(GetRoot());
  NotifyObserverBeginBatch();
  for (LocalStorageInfoList::iterator local_storage_info =
       local_storage_info_list_.begin();
       local_storage_info != local_storage_info_list_.end();
       ++local_storage_info) {
    GURL origin(local_storage_info->origin);

    if (!filter.size() ||
        (CookieTreeOriginNode::TitleForUrl(origin).find(filter) !=
         std::wstring::npos)) {
      CookieTreeOriginNode* origin_node =
          root->GetOrCreateOriginNode(origin);
      CookieTreeLocalStoragesNode* local_storages_node =
          origin_node->GetOrCreateLocalStoragesNode();
      local_storages_node->AddLocalStorageNode(
          new CookieTreeLocalStorageNode(&(*local_storage_info)));
    }
  }
  NotifyObserverTreeNodeChanged(root);
  NotifyObserverEndBatch();
}

void CookiesTreeModel::OnSessionStorageModelInfoLoaded(
    const LocalStorageInfoList& session_storage_info) {
  session_storage_info_list_ = session_storage_info;
  PopulateSessionStorageInfoWithFilter(std::wstring());
}

void CookiesTreeModel::PopulateSessionStorageInfoWithFilter(
    const std::wstring& filter) {
  if (session_storage_info_list_.empty())
    return;
  CookieTreeRootNode* root = static_cast<CookieTreeRootNode*>(GetRoot());
  NotifyObserverBeginBatch();
  for (LocalStorageInfoList::iterator session_storage_info =
       session_storage_info_list_.begin();
       session_storage_info != session_storage_info_list_.end();
       ++session_storage_info) {
    GURL origin(session_storage_info->origin);

    if (!filter.size() ||
        (CookieTreeOriginNode::TitleForUrl(origin).find(filter) !=
         std::wstring::npos)) {
      CookieTreeOriginNode* origin_node =
          root->GetOrCreateOriginNode(origin);
      CookieTreeSessionStoragesNode* session_storages_node =
          origin_node->GetOrCreateSessionStoragesNode();
      session_storages_node->AddSessionStorageNode(
          new CookieTreeSessionStorageNode(&(*session_storage_info)));
    }
  }
  NotifyObserverTreeNodeChanged(root);
  NotifyObserverEndBatch();
}

void CookiesTreeModel::OnIndexedDBModelInfoLoaded(
    const IndexedDBInfoList& indexed_db_info) {
  indexed_db_info_list_ = indexed_db_info;
  PopulateIndexedDBInfoWithFilter(std::wstring());
}

void CookiesTreeModel::PopulateIndexedDBInfoWithFilter(
    const std::wstring& filter) {
  if (indexed_db_info_list_.empty())
    return;
  CookieTreeRootNode* root = static_cast<CookieTreeRootNode*>(GetRoot());
  NotifyObserverBeginBatch();
  for (IndexedDBInfoList::iterator indexed_db_info =
       indexed_db_info_list_.begin();
       indexed_db_info != indexed_db_info_list_.end();
       ++indexed_db_info) {
    GURL origin(indexed_db_info->origin);

    if (!filter.size() ||
        (CookieTreeOriginNode::TitleForUrl(origin).find(filter) !=
         std::wstring::npos)) {
      CookieTreeOriginNode* origin_node =
          root->GetOrCreateOriginNode(origin);
      CookieTreeIndexedDBsNode* indexed_dbs_node =
          origin_node->GetOrCreateIndexedDBsNode();
      indexed_dbs_node->AddIndexedDBNode(
          new CookieTreeIndexedDBNode(&(*indexed_db_info)));
    }
  }
  NotifyObserverTreeNodeChanged(root);
  NotifyObserverEndBatch();
}

void CookiesTreeModel::NotifyObserverBeginBatch() {
  // Only notify the model once if we're batching in a nested manner.
  if (batch_update_++ == 0) {
    FOR_EACH_OBSERVER(Observer,
                      cookies_observer_list_,
                      TreeModelBeginBatch(this));
  }
}

void CookiesTreeModel::NotifyObserverEndBatch() {
  // Only notify the observers if this is the outermost call to EndBatch() if
  // called in a nested manner.
  if (--batch_update_ == 0) {
    FOR_EACH_OBSERVER(Observer,
                      cookies_observer_list_,
                      TreeModelEndBatch(this));
  }
}