summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/cocoa/cookies_window_controller.h10
-rw-r--r--chrome/browser/cocoa/cookies_window_controller.mm63
-rw-r--r--chrome/browser/cocoa/cookies_window_controller_unittest.mm4
-rw-r--r--chrome/browser/cookies_tree_model.cc44
-rw-r--r--chrome/browser/cookies_tree_model.h34
-rw-r--r--chrome/browser/views/options/cookies_view.cc1
-rw-r--r--chrome/browser/views/options/cookies_view.h4
7 files changed, 136 insertions, 24 deletions
diff --git a/chrome/browser/cocoa/cookies_window_controller.h b/chrome/browser/cocoa/cookies_window_controller.h
index e0d31f3..8f85285 100644
--- a/chrome/browser/cocoa/cookies_window_controller.h
+++ b/chrome/browser/cocoa/cookies_window_controller.h
@@ -21,7 +21,7 @@ class CookiesWindowControllerTest;
// Thin bridge to the window controller that performs model update actions
// directly on the treeController_.
-class CookiesTreeModelObserverBridge : public TreeModelObserver {
+class CookiesTreeModelObserverBridge : public CookiesTreeModel::Observer {
public:
explicit CookiesTreeModelObserverBridge(CookiesWindowController* controller);
@@ -45,6 +45,9 @@ class CookiesTreeModelObserverBridge : public TreeModelObserver {
// Notification that the contents of a node has changed.
virtual void TreeNodeChanged(TreeModel* model, TreeModelNode* node);
+ virtual void TreeModelBeginBatch(CookiesTreeModel* model);
+ virtual void TreeModelEndBatch(CookiesTreeModel* model);
+
// Invalidates the Cocoa model. This is used to tear down the Cocoa model
// when we're about to entirely rebuild it.
void InvalidateCocoaModel();
@@ -66,6 +69,11 @@ class CookiesTreeModelObserverBridge : public TreeModelObserver {
bool HasCocoaModel();
CookiesWindowController* window_controller_; // weak, owns us.
+
+ // If this is true, then the Model has informed us that it is batching
+ // updates. Rather than updating the Cocoa side of the model, we ignore those
+ // small changes and rebuild once at the end.
+ bool batch_update_;
};
// Controller for the cookies manager. This class stores an internal copy of
diff --git a/chrome/browser/cocoa/cookies_window_controller.mm b/chrome/browser/cocoa/cookies_window_controller.mm
index b9fbc59..e2f53c5 100644
--- a/chrome/browser/cocoa/cookies_window_controller.mm
+++ b/chrome/browser/cocoa/cookies_window_controller.mm
@@ -4,6 +4,7 @@
#import "chrome/browser/cocoa/cookies_window_controller.h"
+#include <queue>
#include <vector>
#include "app/l10n_util_mac.h"
@@ -25,7 +26,8 @@ static NSString* const kCocoaTreeModel = @"cocoaTreeModel";
CookiesTreeModelObserverBridge::CookiesTreeModelObserverBridge(
CookiesWindowController* controller)
- : window_controller_(controller) {
+ : window_controller_(controller),
+ batch_update_(false) {
}
// Notification that nodes were added to the specified parent.
@@ -34,7 +36,7 @@ void CookiesTreeModelObserverBridge::TreeNodesAdded(TreeModel* model,
int start,
int count) {
// We're in for a major rebuild. Ignore this request.
- if (!HasCocoaModel())
+ if (batch_update_ || !HasCocoaModel())
return;
CocoaCookieTreeNode* cocoa_parent = FindCocoaNode(parent, nil);
@@ -56,7 +58,7 @@ void CookiesTreeModelObserverBridge::TreeNodesRemoved(TreeModel* model,
int start,
int count) {
// We're in for a major rebuild. Ignore this request.
- if (!HasCocoaModel())
+ if (batch_update_ || !HasCocoaModel())
return;
CocoaCookieTreeNode* cocoa_parent = FindCocoaNode(parent, nil);
@@ -73,7 +75,7 @@ void CookiesTreeModelObserverBridge::TreeNodesRemoved(TreeModel* model,
void CookiesTreeModelObserverBridge::TreeNodeChildrenReordered(TreeModel* model,
TreeModelNode* parent) {
// We're in for a major rebuild. Ignore this request.
- if (!HasCocoaModel())
+ if (batch_update_ || !HasCocoaModel())
return;
CocoaCookieTreeNode* cocoa_parent = FindCocoaNode(parent, nil);
@@ -101,7 +103,7 @@ void CookiesTreeModelObserverBridge::TreeNodeChildrenReordered(TreeModel* model,
void CookiesTreeModelObserverBridge::TreeNodeChanged(TreeModel* model,
TreeModelNode* node) {
// If we don't have a Cocoa model, only let the root node change.
- if (!HasCocoaModel() && model->GetRoot() != node)
+ if (batch_update_ || (!HasCocoaModel() && model->GetRoot() != node))
return;
if (HasCocoaModel()) {
@@ -116,6 +118,19 @@ void CookiesTreeModelObserverBridge::TreeNodeChanged(TreeModel* model,
}
}
+void CookiesTreeModelObserverBridge::TreeModelBeginBatch(
+ CookiesTreeModel* model) {
+ batch_update_ = true;
+}
+
+void CookiesTreeModelObserverBridge::TreeModelEndBatch(
+ CookiesTreeModel* model) {
+ DCHECK(batch_update_);
+ CocoaCookieTreeNode* root = CocoaNodeFromTreeNode(model->GetRoot());
+ [window_controller_ setCocoaTreeModel:root];
+ batch_update_ = false;
+}
+
void CookiesTreeModelObserverBridge::InvalidateCocoaModel() {
[[[window_controller_ cocoaTreeModel] mutableChildren] removeAllObjects];
}
@@ -126,27 +141,41 @@ CocoaCookieTreeNode* CookiesTreeModelObserverBridge::CocoaNodeFromTreeNode(
return [[[CocoaCookieTreeNode alloc] initWithNode:cookie_node] autorelease];
}
-// Does a pre-order traversal on the tree to find |node|.
+// Does breadth-first search on the tree to find |node|. This method is most
+// commonly used to find origin/folder nodes, which are at the first level off
+// the root (hence breadth-first search).
CocoaCookieTreeNode* CookiesTreeModelObserverBridge::FindCocoaNode(
- TreeModelNode* node, CocoaCookieTreeNode* start) {
+ TreeModelNode* target, CocoaCookieTreeNode* start) {
if (!start) {
start = [window_controller_ cocoaTreeModel];
}
- if ([start treeNode] == node) {
+ if ([start treeNode] == target) {
return start;
}
- NSArray* children = [start children];
- for (CocoaCookieTreeNode* child in children) {
- if ([child treeNode] == node) {
- return child;
+ // Enqueue the root node of the search (sub-)tree.
+ std::queue<CocoaCookieTreeNode*> horizon;
+ horizon.push(start);
+
+ // Loop until we've looked at every node or we found the target.
+ while (!horizon.empty()) {
+ // Dequeue the item at the front.
+ CocoaCookieTreeNode* node = horizon.front();
+ horizon.pop();
+
+ // If this is the droid we're looking for, report it.
+ if ([node treeNode] == target)
+ return node;
+
+ // "Move along, move along." by adding all child nodes to the queue.
+ if (![node isLeaf]) {
+ NSArray* children = [node children];
+ for (CocoaCookieTreeNode* child in children) {
+ horizon.push(child);
+ }
}
-
- // Search the children. Return the result if we find one.
- CocoaCookieTreeNode* recurse = FindCocoaNode(node, child);
- if (recurse)
- return recurse;
}
+
return nil; // We couldn't find the node.
}
diff --git a/chrome/browser/cocoa/cookies_window_controller_unittest.mm b/chrome/browser/cocoa/cookies_window_controller_unittest.mm
index 69757cf..b58c584 100644
--- a/chrome/browser/cocoa/cookies_window_controller_unittest.mm
+++ b/chrome/browser/cocoa/cookies_window_controller_unittest.mm
@@ -345,7 +345,7 @@ TEST_F(CookiesWindowControllerTest, TreeNodeChanged) {
EXPECT_TRUE([@"Silly Change" isEqualToString:[cocoa_node title]]);
}
-TEST_F(CookiesWindowControllerTest, TestDeleteCookie) {
+TEST_F(CookiesWindowControllerTest, DeleteCookie) {
const GURL url = GURL("http://foo.com");
TestingProfile* profile = browser_helper_.profile();
net::CookieMonster* cm = profile->GetCookieMonster();
@@ -379,7 +379,7 @@ TEST_F(CookiesWindowControllerTest, TestDeleteCookie) {
[controller closeSheet:nil];
}
-TEST_F(CookiesWindowControllerTest, TestDidExpandItem) {
+TEST_F(CookiesWindowControllerTest, DidExpandItem) {
const GURL url = GURL("http://foo.com");
TestingProfile* profile = browser_helper_.profile();
net::CookieMonster* cm = profile->GetCookieMonster();
diff --git a/chrome/browser/cookies_tree_model.cc b/chrome/browser/cookies_tree_model.cc
index 8e3f611..d884b11 100644
--- a/chrome/browser/cookies_tree_model.cc
+++ b/chrome/browser/cookies_tree_model.cc
@@ -301,7 +301,8 @@ CookiesTreeModel::CookiesTreeModel(
profile_(profile),
appcache_helper_(appcache_helper),
database_helper_(database_helper),
- local_storage_helper_(local_storage_helper) {
+ local_storage_helper_(local_storage_helper),
+ batch_update_(0) {
LoadCookies();
DCHECK(database_helper_);
database_helper_->StartFetching(NewCallback(
@@ -395,12 +396,14 @@ void CookiesTreeModel::LoadCookiesWithFilter(const std::wstring& filter) {
}
void CookiesTreeModel::DeleteAllStoredObjects() {
+ NotifyObserverBeginBatch();
CookieTreeNode* root = GetRoot();
root->DeleteStoredObjects();
int num_children = root->GetChildCount();
for (int i = num_children - 1; i >= 0; --i)
delete Remove(root, i);
NotifyObserverTreeNodeChanged(root);
+ NotifyObserverEndBatch();
}
void CookiesTreeModel::DeleteCookieNode(CookieTreeNode* cookie_node) {
@@ -414,6 +417,7 @@ void CookiesTreeModel::DeleteCookieNode(CookieTreeNode* cookie_node) {
void CookiesTreeModel::UpdateSearchResults(const std::wstring& filter) {
CookieTreeNode* root = GetRoot();
int num_children = root->GetChildCount();
+ NotifyObserverBeginBatch();
for (int i = num_children - 1; i >= 0; --i)
delete Remove(root, i);
LoadCookiesWithFilter(filter);
@@ -421,6 +425,19 @@ void CookiesTreeModel::UpdateSearchResults(const std::wstring& filter) {
PopulateLocalStorageInfoWithFilter(filter);
PopulateAppCacheInfoWithFilter(filter);
NotifyObserverTreeNodeChanged(root);
+ NotifyObserverEndBatch();
+}
+
+void CookiesTreeModel::AddObserver(Observer* observer) {
+ cookies_observer_list_.AddObserver(observer);
+ // Call super so that TreeNodeModel can notify, too.
+ TreeNodeModel<CookieTreeNode>::AddObserver(observer);
+}
+
+void CookiesTreeModel::RemoveObserver(Observer* observer) {
+ cookies_observer_list_.RemoveObserver(observer);
+ // Call super so that TreeNodeModel doesn't have dead pointers.
+ TreeNodeModel<CookieTreeNode>::RemoveObserver(observer);
}
void CookiesTreeModel::OnAppCacheModelInfoLoaded() {
@@ -432,6 +449,7 @@ void CookiesTreeModel::PopulateAppCacheInfoWithFilter(
if (!appcache_helper_ || appcache_helper_->info_list().empty())
return;
CookieTreeRootNode* root = static_cast<CookieTreeRootNode*>(GetRoot());
+ NotifyObserverBeginBatch();
for (AppCacheInfoList::const_iterator info =
appcache_helper_->info_list().begin();
info != appcache_helper_->info_list().end(); ++info) {
@@ -447,6 +465,7 @@ void CookiesTreeModel::PopulateAppCacheInfoWithFilter(
}
}
NotifyObserverTreeNodeChanged(root);
+ NotifyObserverEndBatch();
}
void CookiesTreeModel::OnDatabaseModelInfoLoaded(
@@ -460,6 +479,7 @@ void CookiesTreeModel::PopulateDatabaseInfoWithFilter(
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) {
@@ -483,6 +503,7 @@ void CookiesTreeModel::PopulateDatabaseInfoWithFilter(
}
}
NotifyObserverTreeNodeChanged(root);
+ NotifyObserverEndBatch();
}
void CookiesTreeModel::OnStorageModelInfoLoaded(
@@ -496,6 +517,7 @@ void CookiesTreeModel::PopulateLocalStorageInfoWithFilter(
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();
@@ -520,6 +542,26 @@ void CookiesTreeModel::PopulateLocalStorageInfoWithFilter(
}
}
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));
+ }
}
std::wstring CookiesTreeModel::FormExtensionNodeName(
diff --git a/chrome/browser/cookies_tree_model.h b/chrome/browser/cookies_tree_model.h
index 3fabbaf..056fe5f 100644
--- a/chrome/browser/cookies_tree_model.h
+++ b/chrome/browser/cookies_tree_model.h
@@ -9,6 +9,7 @@
#include <vector>
#include "app/tree_node_model.h"
+#include "base/observer_list.h"
#include "base/scoped_ptr.h"
#include "chrome/browser/browsing_data_appcache_helper.h"
#include "chrome/browser/browsing_data_database_helper.h"
@@ -344,6 +345,17 @@ class CookieTreeLocalStoragesNode : public CookieTreeNode {
class CookiesTreeModel : public TreeNodeModel<CookieTreeNode> {
public:
+ // Because non-cookie nodes are fetched in a background thread, they are not
+ // present at the time the Model is created. The Model then notifies its
+ // observers for every item added from databases, local storage, and
+ // appcache. We extend the Observer interface to add notifications before and
+ // after these batch inserts.
+ class Observer : public TreeModelObserver {
+ public:
+ virtual void TreeModelBeginBatch(CookiesTreeModel* model) {}
+ virtual void TreeModelEndBatch(CookiesTreeModel* model) {}
+ };
+
CookiesTreeModel(
Profile* profile,
BrowsingDataDatabaseHelper* database_helper,
@@ -368,6 +380,16 @@ class CookiesTreeModel : public TreeNodeModel<CookieTreeNode> {
// Filter the origins to only display matched results.
void UpdateSearchResults(const std::wstring& filter);
+ // Overload the Add/Remove observer methods so we can notify about
+ // CookiesTreeModel-specific things. Note that this is NOT overriding the
+ // method by the same name in TreeNodeModel because the argument type is
+ // different. Therefore, if this AddObserver(TreeModelObserver*) is called,
+ // the observer will NOT be notified about batching. This is also why we
+ // maintain a separate list of observers that are specifically Observer*
+ // objects.
+ virtual void AddObserver(Observer* observer);
+ virtual void RemoveObserver(Observer* observer);
+
private:
enum CookieIconIndex {
ORIGIN = 0,
@@ -396,6 +418,9 @@ class CookiesTreeModel : public TreeNodeModel<CookieTreeNode> {
std::wstring FormExtensionNodeName(const std::string& extension_id);
+ void NotifyObserverBeginBatch();
+ void NotifyObserverEndBatch();
+
// The profile from which this model sources cookies.
Profile* profile_;
CookieList all_cookies_;
@@ -407,6 +432,15 @@ class CookiesTreeModel : public TreeNodeModel<CookieTreeNode> {
scoped_refptr<BrowsingDataLocalStorageHelper> local_storage_helper_;
LocalStorageInfoList local_storage_info_list_;
+ // The CookiesTreeModel maintains a separate list of observers that are
+ // specifically of the type CookiesTreeModel::Observer.
+ ObserverList<Observer> cookies_observer_list_;
+
+ // If this is non-zero, then this model is batching updates (there's a lot of
+ // notifications coming down the pipe). This is an integer is used to balance
+ // calls to Begin/EndBatch() if they're called in a nested manner.
+ int batch_update_;
+
friend class CookieTreeAppCacheNode;
friend class CookieTreeCookieNode;
friend class CookieTreeDatabaseNode;
diff --git a/chrome/browser/views/options/cookies_view.cc b/chrome/browser/views/options/cookies_view.cc
index 584cf85..2271536 100644
--- a/chrome/browser/views/options/cookies_view.cc
+++ b/chrome/browser/views/options/cookies_view.cc
@@ -12,7 +12,6 @@
#include "base/i18n/time_formatting.h"
#include "base/message_loop.h"
#include "base/string_util.h"
-#include "chrome/browser/cookies_tree_model.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/views/appcache_info_view.h"
#include "chrome/browser/views/cookie_info_view.h"
diff --git a/chrome/browser/views/options/cookies_view.h b/chrome/browser/views/options/cookies_view.h
index ee2c314..64ca05d 100644
--- a/chrome/browser/views/options/cookies_view.h
+++ b/chrome/browser/views/options/cookies_view.h
@@ -9,6 +9,7 @@
#include "app/tree_model.h"
#include "base/task.h"
+#include "chrome/browser/cookies_tree_model.h"
#include "net/base/cookie_monster.h"
#include "views/controls/button/button.h"
#include "views/controls/tree/tree_view.h"
@@ -27,7 +28,6 @@ class NativeButton;
class AppCacheInfoView;
class CookieInfoView;
-class CookiesTreeModel;
class CookiesTreeView;
class DatabaseInfoView;
class LocalStorageInfoView;
@@ -35,7 +35,7 @@ class Profile;
class Timer;
-class CookiesView : public TreeModelObserver,
+class CookiesView : public CookiesTreeModel::Observer,
public views::View,
public views::DialogDelegate,
public views::ButtonListener,