summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
authorsky@google.com <sky@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-08-27 03:27:46 +0000
committersky@google.com <sky@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-08-27 03:27:46 +0000
commit90ef1313cb672e7da91312c4f7d3cdee41c7a767 (patch)
treeb42df35c8c71ca921bf7900beb537a1773debacf /chrome/browser
parent7459794d5e6251014e322a4042d68c4f3f19a5f3 (diff)
downloadchromium_src-90ef1313cb672e7da91312c4f7d3cdee41c7a767.zip
chromium_src-90ef1313cb672e7da91312c4f7d3cdee41c7a767.tar.gz
chromium_src-90ef1313cb672e7da91312c4f7d3cdee41c7a767.tar.bz2
Makes deleting history no longer delete starred urls. Thiseffectively reenables the code in ExpireHistoryBackend. I also madethe code consistent so that when we delete visits as the result ofhistory deletion we don't change the typed/visit count of theunderlying url.BUG=1214201 1256202TEST=none
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@1423 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
-rw-r--r--chrome/browser/autocomplete/history_contents_provider_unittest.cc2
-rw-r--r--chrome/browser/autocomplete/history_url_provider_unittest.cc2
-rw-r--r--chrome/browser/bookmark_bar_context_menu_controller_test.cc2
-rw-r--r--chrome/browser/bookmark_bar_model.cc123
-rw-r--r--chrome/browser/bookmark_bar_model.h35
-rw-r--r--chrome/browser/bookmark_bar_model_unittest.cc72
-rw-r--r--chrome/browser/bookmark_codec.cc3
-rw-r--r--chrome/browser/bookmark_storage.cc337
-rw-r--r--chrome/browser/bookmarks/bookmark_service.h61
-rw-r--r--chrome/browser/browser.vcproj4
-rw-r--r--chrome/browser/history/expire_history_backend.cc77
-rw-r--r--chrome/browser/history/expire_history_backend.h34
-rw-r--r--chrome/browser/history/expire_history_backend_unittest.cc142
-rw-r--r--chrome/browser/history/history.cc35
-rw-r--r--chrome/browser/history/history.h52
-rw-r--r--chrome/browser/history/history_backend.cc278
-rw-r--r--chrome/browser/history/history_backend.h49
-rw-r--r--chrome/browser/history/history_backend_unittest.cc131
-rw-r--r--chrome/browser/history/history_database.cc4
-rw-r--r--chrome/browser/history/history_database.h10
-rw-r--r--chrome/browser/history/history_marshaling.h3
-rw-r--r--chrome/browser/history/history_querying_unittest.cc2
-rw-r--r--chrome/browser/history/history_unittest.cc22
-rw-r--r--chrome/browser/history/url_database.cc5
-rw-r--r--chrome/browser/profile.cc16
-rw-r--r--chrome/browser/views/bookmark_bar_view_test.cc2
-rw-r--r--chrome/browser/views/bookmark_editor_view_unittest.cc2
-rw-r--r--chrome/browser/visitedlink_unittest.cc2
28 files changed, 1029 insertions, 478 deletions
diff --git a/chrome/browser/autocomplete/history_contents_provider_unittest.cc b/chrome/browser/autocomplete/history_contents_provider_unittest.cc
index ba7985a..2e1a646 100644
--- a/chrome/browser/autocomplete/history_contents_provider_unittest.cc
+++ b/chrome/browser/autocomplete/history_contents_provider_unittest.cc
@@ -54,7 +54,7 @@ class HistoryContentsProviderTest : public testing::Test,
file_util::CreateDirectoryW(history_dir_);
history_service_ = new HistoryService;
- history_service_->Init(history_dir_);
+ history_service_->Init(history_dir_, NULL);
// Populate history.
for (int i = 0; i < arraysize(test_entries); i++) {
diff --git a/chrome/browser/autocomplete/history_url_provider_unittest.cc b/chrome/browser/autocomplete/history_url_provider_unittest.cc
index 05a696d..6dd80e9 100644
--- a/chrome/browser/autocomplete/history_url_provider_unittest.cc
+++ b/chrome/browser/autocomplete/history_url_provider_unittest.cc
@@ -114,7 +114,7 @@ void HistoryURLProviderTest::OnProviderUpdate(bool updated_matches) {
void HistoryURLProviderTest::SetUp() {
profile_.reset(new TestingProfile());
- profile_->CreateBookmarkBarModel();
+ profile_->CreateBookmarkBarModel(true);
profile_->CreateHistoryService(true);
history_service_ = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
diff --git a/chrome/browser/bookmark_bar_context_menu_controller_test.cc b/chrome/browser/bookmark_bar_context_menu_controller_test.cc
index 4c2cfa4..d2aac6f 100644
--- a/chrome/browser/bookmark_bar_context_menu_controller_test.cc
+++ b/chrome/browser/bookmark_bar_context_menu_controller_test.cc
@@ -39,7 +39,7 @@ class BookmarkBarContextMenuControllerTest : public testing::Test {
profile_.reset(new TestingProfile());
profile_->set_has_history_service(true);
- profile_->CreateBookmarkBarModel();
+ profile_->CreateBookmarkBarModel(true);
model_ = profile_->GetBookmarkBarModel();
diff --git a/chrome/browser/bookmark_bar_model.cc b/chrome/browser/bookmark_bar_model.cc
index 3aa6e26..b86131d 100644
--- a/chrome/browser/bookmark_bar_model.cc
+++ b/chrome/browser/bookmark_bar_model.cc
@@ -73,7 +73,9 @@ BookmarkBarModel::BookmarkBarModel(Profile* profile)
#pragma warning(suppress: 4355) // Okay to pass "this" here.
root_(this, GURL()),
bookmark_bar_node_(NULL),
- other_node_(NULL) {
+ other_node_(NULL),
+ waiting_for_history_load_(false),
+ loaded_signal_(CreateEvent(NULL, TRUE, FALSE, NULL)) {
// Create the bookmark bar and other bookmarks folders. These always exist.
CreateBookmarkBarNode();
CreateOtherBookmarksNode();
@@ -87,26 +89,21 @@ BookmarkBarModel::BookmarkBarModel(Profile* profile)
if (!profile_) {
// Profile is null during testing.
- loaded_ = true;
- return;
+ DoneLoading();
}
-
- // Listen for changes to starred icons so that we can update the favicon of
- // the node appropriately.
- NotificationService::current()->AddObserver(
- this, NOTIFY_FAVICON_CHANGED, Source<Profile>(profile_));
-
- // Load the bookmarks. BookmarkStorage notifies us when done.
- store_ = new BookmarkStorage(profile_, this);
- store_->LoadBookmarks(false);
}
BookmarkBarModel::~BookmarkBarModel() {
- if (profile_) {
+ if (profile_ && store_.get()) {
NotificationService::current()->RemoveObserver(
this, NOTIFY_FAVICON_CHANGED, Source<Profile>(profile_));
}
+ if (waiting_for_history_load_) {
+ NotificationService::current()->RemoveObserver(
+ this, NOTIFY_HISTORY_LOADED, Source<Profile>(profile_));
+ }
+
if (store_) {
// The store maintains a reference back to us. We need to tell it we're gone
// so that it doesn't try and invoke a method back on us again.
@@ -114,6 +111,24 @@ BookmarkBarModel::~BookmarkBarModel() {
}
}
+void BookmarkBarModel::Load() {
+ if (store_.get()) {
+ // If the store is non-null, it means Load was already invoked. Load should
+ // only be invoked once.
+ NOTREACHED();
+ return;
+ }
+
+ // Listen for changes to favicons so that we can update the favicon of the
+ // node appropriately.
+ NotificationService::current()->AddObserver(
+ this, NOTIFY_FAVICON_CHANGED, Source<Profile>(profile_));
+
+ // Load the bookmarks. BookmarkStorage notifies us when done.
+ store_ = new BookmarkStorage(profile_, this);
+ store_->LoadBookmarks(false);
+}
+
BookmarkBarNode* BookmarkBarModel::GetParentForNewNodes() {
std::vector<BookmarkBarNode*> nodes;
@@ -142,6 +157,7 @@ std::vector<BookmarkBarNode*> BookmarkBarModel::GetMostRecentlyModifiedGroups(
void BookmarkBarModel::GetMostRecentlyAddedEntries(
size_t count,
std::vector<BookmarkBarNode*>* nodes) {
+ AutoLock url_lock(url_lock_);
for (NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.begin();
i != nodes_ordered_by_url_set_.end(); ++i) {
std::vector<BookmarkBarNode*>::iterator insert_position =
@@ -163,6 +179,7 @@ void BookmarkBarModel::GetBookmarksMatchingText(
if (query_nodes.empty())
return;
+ AutoLock url_lock(url_lock_);
for (NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.begin();
i != nodes_ordered_by_url_set_.end(); ++i) {
if (parser.DoesQueryMatch((*i)->GetTitle(), query_nodes.get()))
@@ -236,11 +253,20 @@ void BookmarkBarModel::SetTitle(BookmarkBarNode* node,
}
BookmarkBarNode* BookmarkBarModel::GetNodeByURL(const GURL& url) {
+ AutoLock url_lock(url_lock_);
BookmarkBarNode tmp_node(this, url);
NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.find(&tmp_node);
return (i != nodes_ordered_by_url_set_.end()) ? *i : NULL;
}
+void BookmarkBarModel::GetBookmarks(std::vector<GURL>* urls) {
+ AutoLock url_lock(url_lock_);
+ for (NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.begin();
+ i != nodes_ordered_by_url_set_.end(); ++i) {
+ urls->push_back((*i)->GetURL());
+ }
+}
+
BookmarkBarNode* BookmarkBarModel::GetNodeByID(int id) {
// TODO(sky): TreeNode needs a method that visits all nodes using a predicate.
return GetNodeByID(&root_, id);
@@ -296,6 +322,7 @@ BookmarkBarNode* BookmarkBarModel::AddURLWithCreationTime(
new_node->date_added_ = creation_time;
new_node->type_ = history::StarredEntry::URL;
+ AutoLock url_lock(url_lock_);
nodes_ordered_by_url_set_.insert(new_node);
return AddNode(parent, index, new_node);
@@ -333,6 +360,8 @@ void BookmarkBarModel::RemoveNode(BookmarkBarNode* node,
}
if (node->GetType() == history::StarredEntry::URL) {
+ // NOTE: this is called in such a way that url_lock_ is already held. As
+ // such, this doesn't explicitly grab the lock.
NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.find(node);
DCHECK(i != nodes_ordered_by_url_set_.end());
nodes_ordered_by_url_set_.erase(i);
@@ -367,14 +396,23 @@ void BookmarkBarModel::OnBookmarkStorageLoadedBookmarks(
return;
}
- // The file doesn't exist. If the bookmarks were in the db the history db
- // will copy them to a file on init. Schedule an empty request to history so
- // that we know history will have saved the bookmarks (if it had them). When
- // the callback is processed
- // when done we'll try and load the bookmarks again.
- profile_->GetHistoryService(Profile::EXPLICIT_ACCESS)->
- ScheduleEmptyCallback(&load_consumer_,
- NewCallback(this, &BookmarkBarModel::OnHistoryDone));
+ // The file doesn't exist. This means one of two things:
+ // 1. A clean profile.
+ // 2. The user is migrating from an older version where bookmarks were saved
+ // in history.
+ // We assume step 2. If history had the bookmarks, history will write the
+ // bookmarks to a file for us. We need to wait until history has finished
+ // loading before reading from that file.
+ HistoryService* history =
+ profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
+ if (!history->backend_loaded()) {
+ // The backend isn't finished loading. Wait for it.
+ waiting_for_history_load_ = true;
+ NotificationService::current()->AddObserver(
+ this, NOTIFY_HISTORY_LOADED, Source<Profile>(profile_));
+ } else {
+ OnHistoryDone();
+ }
}
void BookmarkBarModel::OnHistoryDone() {
@@ -389,11 +427,18 @@ void BookmarkBarModel::OnHistoryDone() {
}
void BookmarkBarModel::DoneLoading() {
- // Update nodes_ordered_by_url_set_ from the nodes.
- PopulateNodesByURL(&root_);
+ {
+ AutoLock url_lock(url_lock_);
+ // Update nodes_ordered_by_url_set_ from the nodes.
+ PopulateNodesByURL(&root_);
+ }
loaded_ = true;
+ if (loaded_signal_.Get())
+ SetEvent(loaded_signal_.Get());
+
+
// Notify our direct observers.
FOR_EACH_OBSERVER(BookmarkBarModelObserver, observers_, Loaded(this));
@@ -412,7 +457,10 @@ void BookmarkBarModel::RemoveAndDeleteNode(BookmarkBarNode* delete_me) {
int index = parent->IndexOfChild(node.get());
parent->Remove(index);
history::URLsStarredDetails details(false);
- RemoveNode(node.get(), &details.changed_urls);
+ {
+ AutoLock url_lock(url_lock_);
+ RemoveNode(node.get(), &details.changed_urls);
+ }
if (store_.get())
store_->ScheduleSave();
@@ -420,6 +468,13 @@ void BookmarkBarModel::RemoveAndDeleteNode(BookmarkBarNode* delete_me) {
FOR_EACH_OBSERVER(BookmarkBarModelObserver, observers_,
BookmarkNodeRemoved(this, parent, index));
+ if (profile_) {
+ HistoryService* history =
+ profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
+ if (history)
+ history->URLsNoLongerBookmarked(details.changed_urls);
+ }
+
NotificationService::current()->Notify(NOTIFY_URLS_STARRED,
Source<Profile>(profile_),
Details<history::URLsStarredDetails>(&details));
@@ -446,6 +501,11 @@ BookmarkBarNode* BookmarkBarModel::AddNode(BookmarkBarNode* parent,
return node;
}
+void BookmarkBarModel::BlockTillLoaded() {
+ if (loaded_signal_.Get())
+ WaitForSingleObject(loaded_signal_.Get(), INFINITE);
+}
+
BookmarkBarNode* BookmarkBarModel::GetNodeByID(BookmarkBarNode* node,
int id) {
if (node->id() == id)
@@ -595,6 +655,18 @@ void BookmarkBarModel::Observe(NotificationType type,
break;
}
+ case NOTIFY_HISTORY_LOADED: {
+ if (waiting_for_history_load_) {
+ waiting_for_history_load_ = false;
+ NotificationService::current()->RemoveObserver(
+ this, NOTIFY_HISTORY_LOADED, Source<Profile>(profile_));
+ OnHistoryDone();
+ } else {
+ NOTREACHED();
+ }
+ break;
+ }
+
default:
NOTREACHED();
break;
@@ -602,9 +674,10 @@ void BookmarkBarModel::Observe(NotificationType type,
}
void BookmarkBarModel::PopulateNodesByURL(BookmarkBarNode* node) {
+ // NOTE: this is called with url_lock_ already held. As such, this doesn't
+ // explicitly grab the lock.
if (node->is_url())
nodes_ordered_by_url_set_.insert(node);
for (int i = 0; i < node->GetChildCount(); ++i)
PopulateNodesByURL(node->GetChild(i));
}
-
diff --git a/chrome/browser/bookmark_bar_model.h b/chrome/browser/bookmark_bar_model.h
index c4c3bd6..22c5291 100644
--- a/chrome/browser/bookmark_bar_model.h
+++ b/chrome/browser/bookmark_bar_model.h
@@ -5,7 +5,10 @@
#ifndef CHROME_BROWSER_BOOKMARK_BAR_MODEL_H_
#define CHROME_BROWSER_BOOKMARK_BAR_MODEL_H_
+#include "base/lock.h"
#include "base/observer_list.h"
+#include "base/scoped_handle.h"
+#include "chrome/browser/bookmarks/bookmark_service.h"
#include "chrome/browser/bookmark_storage.h"
#include "chrome/browser/cancelable_request.h"
#include "chrome/browser/history/history.h"
@@ -37,7 +40,6 @@ class BookmarkBarNode : public ChromeViews::TreeNode<BookmarkBarNode> {
public:
BookmarkBarNode(BookmarkBarModel* model, const GURL& url);
-
virtual ~BookmarkBarNode() {}
// Returns the favicon for the this node. If the favicon has not yet been
@@ -167,7 +169,7 @@ class BookmarkBarModelObserver {
// Profile.
// TODO(sky): rename to BookmarkModel.
-class BookmarkBarModel : public NotificationObserver {
+class BookmarkBarModel : public NotificationObserver, public BookmarkService {
friend class BookmarkBarNode;
friend class BookmarkBarModelTest;
friend class BookmarkStorage;
@@ -175,6 +177,10 @@ class BookmarkBarModel : public NotificationObserver {
public:
explicit BookmarkBarModel(Profile* profile);
virtual ~BookmarkBarModel();
+
+ // Loads the bookmarks. This is called by Profile upon creation of the
+ // BookmarkBarModel. You need not invoke this directly.
+ void Load();
// Returns the root node. The bookmark bar node and other node are children of
// the root node.
@@ -224,14 +230,22 @@ class BookmarkBarModel : public NotificationObserver {
bool IsLoaded() { return loaded_; }
// Returns the node with the specified URL, or NULL if there is no node with
- // the specified URL.
+ // the specified URL. This method is thread safe.
BookmarkBarNode* GetNodeByURL(const GURL& url);
- // Returns true if there is a bookmark for the specified URL.
- bool IsBookmarked(const GURL& url) {
+ // Returns all the bookmarked urls. This method is thread safe.
+ virtual void GetBookmarks(std::vector<GURL>* urls);
+
+ // Returns true if there is a bookmark for the specified URL. This method is
+ // thread safe. See BookmarkService for more details on this.
+ virtual bool IsBookmarked(const GURL& url) {
return GetNodeByURL(url) != NULL;
}
+ // Blocks until loaded; this is NOT invoked on the main thread. See
+ // BookmarkService for more details on this.
+ virtual void BlockTillLoaded();
+
// Returns the node with the specified id, or NULL if there is no node with
// the specified id.
BookmarkBarNode* GetNodeByID(int id);
@@ -381,8 +395,11 @@ class BookmarkBarModel : public NotificationObserver {
// Set of nodes ordered by URL. This is not a map to avoid copying the
// urls.
+ // WARNING: nodes_ordered_by_url_set_ is accessed on multiple threads. As
+ // such, be sure and wrap all usage of it around url_lock_.
typedef std::set<BookmarkBarNode*,NodeURLComparator> NodesOrderedByURLSet;
NodesOrderedByURLSet nodes_ordered_by_url_set_;
+ Lock url_lock_;
// Used for loading favicons and the empty history request.
CancelableRequestConsumerT<BookmarkBarNode*, NULL> load_consumer_;
@@ -390,6 +407,14 @@ class BookmarkBarModel : public NotificationObserver {
// Reads/writes bookmarks to disk.
scoped_refptr<BookmarkStorage> store_;
+ // Have we installed a listener on the NotificationService for
+ // NOTIFY_HISTORY_LOADED? A listener is installed if the bookmarks file
+ // doesn't exist and the history service hasn't finished loading.
+ bool waiting_for_history_load_;
+
+ // Handle to event signaled when loading is done.
+ ScopedHandle loaded_signal_;
+
DISALLOW_EVIL_CONSTRUCTORS(BookmarkBarModel);
};
diff --git a/chrome/browser/bookmark_bar_model_unittest.cc b/chrome/browser/bookmark_bar_model_unittest.cc
index 809084e..3f5493d 100644
--- a/chrome/browser/bookmark_bar_model_unittest.cc
+++ b/chrome/browser/bookmark_bar_model_unittest.cc
@@ -438,8 +438,6 @@ class BookmarkBarModelTestWithProfile : public testing::Test,
public BookmarkBarModelObserver {
public:
virtual void SetUp() {
- profile_.reset(new TestingProfile());
- profile_->CreateHistoryService(true);
}
virtual void TearDown() {
@@ -473,30 +471,24 @@ class BookmarkBarModelTestWithProfile : public testing::Test,
}
}
- // Creates the bookmark bar model. If the bookmark bar model has already been
- // created a new one is created and the old one destroyed.
- void CreateBookmarkBarModel() {
- bb_model_.reset(NULL);
-
- BookmarkBarModel* new_model = new BookmarkBarModel(profile_.get());
- if (!new_model->IsLoaded())
- BlockTillLoaded(new_model);
+ void BlockTillBookmarkModelLoaded() {
+ bb_model_ = profile_->GetBookmarkBarModel();
+ if (!bb_model_->IsLoaded())
+ BlockTillLoaded(bb_model_);
else
- new_model->AddObserver(this);
- bb_model_.reset(new_model);
+ bb_model_->AddObserver(this);
}
- // Destroys the bookmark bar model, then the profile, then creates a new
- // profile.
+ // Destroys the current profile, creates a new one and creates the history
+ // service.
void RecreateProfile() {
- bb_model_.reset(NULL);
// Need to shutdown the old one before creating a new one.
profile_.reset(NULL);
profile_.reset(new TestingProfile());
profile_->CreateHistoryService(true);
}
- scoped_ptr<BookmarkBarModel> bb_model_;
+ BookmarkBarModel* bb_model_;
private:
// Blocks until the BookmarkBarModel has finished loading.
@@ -548,20 +540,25 @@ TEST_F(BookmarkBarModelTestWithProfile, CreateAndRestore) {
{ L"a b c [ d e [ f ] ]", L"g h i [ j k [ l ] ]"},
};
for (int i = 0; i < arraysize(data); ++i) {
- RecreateProfile();
-
- CreateBookmarkBarModel();
+ // Recreate the profile. We need to reset with NULL first so that the last
+ // HistoryService releases the locks on the files it creates and we can
+ // delete them.
+ profile_.reset(NULL);
+ profile_.reset(new TestingProfile());
+ profile_->CreateBookmarkBarModel(true);
+ profile_->CreateHistoryService(true);
+ BlockTillBookmarkModelLoaded();
TestNode bbn;
PopulateNodeFromString(data[i].bbn_contents, &bbn);
- PopulateBookmarkBarNode(&bbn, bb_model_.get(),
- bb_model_->GetBookmarkBarNode());
+ PopulateBookmarkBarNode(&bbn, bb_model_, bb_model_->GetBookmarkBarNode());
TestNode other;
PopulateNodeFromString(data[i].other_contents, &other);
- PopulateBookmarkBarNode(&other, bb_model_.get(), bb_model_->other_node());
+ PopulateBookmarkBarNode(&other, bb_model_, bb_model_->other_node());
- CreateBookmarkBarModel();
+ profile_->CreateBookmarkBarModel(false);
+ BlockTillBookmarkModelLoaded();
VerifyModelMatchesNode(&bbn, bb_model_->GetBookmarkBarNode());
VerifyModelMatchesNode(&other, bb_model_->other_node());
@@ -656,8 +653,8 @@ TEST_F(BookmarkBarModelTestWithProfile2, MigrateFromDBToFileTest) {
// Create the history service making sure it doesn't blow away the file we
// just copied.
profile_->CreateHistoryService(false);
-
- CreateBookmarkBarModel();
+ profile_->CreateBookmarkBarModel(true);
+ BlockTillBookmarkModelLoaded();
// Make sure we loaded OK.
VerifyExpectedState();
@@ -665,7 +662,8 @@ TEST_F(BookmarkBarModelTestWithProfile2, MigrateFromDBToFileTest) {
return;
// Create again. This time we shouldn't load from history at all.
- CreateBookmarkBarModel();
+ profile_->CreateBookmarkBarModel(false);
+ BlockTillBookmarkModelLoaded();
// Make sure we loaded OK.
VerifyExpectedState();
@@ -675,7 +673,27 @@ TEST_F(BookmarkBarModelTestWithProfile2, MigrateFromDBToFileTest) {
// Recreate the history service (with a clean db). Do this just to make sure
// we're loading correctly from the bookmarks file.
profile_->CreateHistoryService(true);
- CreateBookmarkBarModel();
+ profile_->CreateBookmarkBarModel(false);
+ BlockTillBookmarkModelLoaded();
VerifyExpectedState();
}
+// Simple test that removes a bookmark. This test exercises the code paths in
+// History that block till bookmark bar model is loaded.
+TEST_F(BookmarkBarModelTestWithProfile2, RemoveNotification) {
+ profile_->CreateHistoryService(false);
+ profile_->CreateBookmarkBarModel(true);
+ BlockTillBookmarkModelLoaded();
+
+ // Add a URL.
+ GURL url("http://www.google.com");
+ bb_model_->SetURLStarred(url, std::wstring(), true);
+
+ profile_->GetHistoryService(Profile::EXPLICIT_ACCESS)->AddPage(
+ url, NULL, 1, GURL(), PageTransition::TYPED,
+ HistoryService::RedirectList());
+
+ // This won't actually delete the URL, rather it'll empty out the visits.
+ // This triggers blocking on the BookmarkBarModel.
+ profile_->GetHistoryService(Profile::EXPLICIT_ACCESS)->DeleteURL(url);
+}
diff --git a/chrome/browser/bookmark_codec.cc b/chrome/browser/bookmark_codec.cc
index de8e47c..14d8851 100644
--- a/chrome/browser/bookmark_codec.cc
+++ b/chrome/browser/bookmark_codec.cc
@@ -139,6 +139,8 @@ bool BookmarkCodec::DecodeNode(BookmarkBarModel* model,
if (!value.GetString(kNameKey, &title))
return false;
+ // TODO(sky): this should be more flexible. Don't hoark if we can't parse it
+ // all.
std::wstring date_added_string;
if (!value.GetString(kDateAddedKey, &date_added_string))
return false;
@@ -154,6 +156,7 @@ bool BookmarkCodec::DecodeNode(BookmarkBarModel* model,
std::wstring url_string;
if (!value.GetString(kURLKey, &url_string))
return false;
+ // TODO(sky): this should ignore the node if not a valid URL.
if (!node)
node = new BookmarkBarNode(model, GURL(url_string));
if (parent)
diff --git a/chrome/browser/bookmark_storage.cc b/chrome/browser/bookmark_storage.cc
index d760b5a..e944de7 100644
--- a/chrome/browser/bookmark_storage.cc
+++ b/chrome/browser/bookmark_storage.cc
@@ -1,171 +1,166 @@
-// Copyright (c) 2006-2008 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.
-
-// NOTE: This class is currently unsed. The plan is to move bookmarks
-// out of the history db using this class and BookmarksCodec instead
-// (bug 1256202).
-
-#include "chrome/browser/bookmark_storage.h"
-
-#include "base/file_util.h"
-#include "base/json_writer.h"
-#include "base/message_loop.h"
-#include "chrome/browser/bookmark_bar_model.h"
-#include "chrome/browser/bookmark_codec.h"
-#include "chrome/browser/profile.h"
-#include "chrome/common/chrome_constants.h"
-#include "chrome/common/json_value_serializer.h"
-
-namespace {
-
-// Extension used for backup files (copy of main file created during startup).
-const wchar_t* const kBackupExtension = L"bak";
-
-// Extension for the temporary file. We write to the temp file than move to
-// kBookmarksFileName.
-const wchar_t* const kTmpExtension = L"tmp";
-
-// How often we save.
-const int kSaveDelayMS = 2500;
-
-} // namespace
-
-// BookmarkStorage -------------------------------------------------------------
-
-BookmarkStorage::BookmarkStorage(Profile* profile, BookmarkBarModel* model)
- : model_(model),
-#pragma warning(suppress: 4355) // Okay to pass "this" here.
- save_factory_(this),
- backend_thread_(g_browser_process->file_thread()) {
- std::wstring path = profile->GetPath();
- file_util::AppendToPath(&path, chrome::kBookmarksFileName);
- std::wstring tmp_history_path = profile->GetPath();
- file_util::AppendToPath(&tmp_history_path, chrome::kHistoryBookmarksFileName);
- backend_ = new BookmarkStorageBackend(path, tmp_history_path);
-}
-
-void BookmarkStorage::LoadBookmarks(bool load_from_history) {
- if (!backend_thread()) {
- backend_->Read(scoped_refptr<BookmarkStorage>(this), NULL,
- load_from_history);
- } else {
- backend_thread()->message_loop()->PostTask(
- FROM_HERE,
- NewRunnableMethod(backend_.get(), &BookmarkStorageBackend::Read,
- scoped_refptr<BookmarkStorage>(this),
- MessageLoop::current(), load_from_history));
- }
-}
-
-void BookmarkStorage::ScheduleSave() {
- if (save_factory_.empty()) {
- MessageLoop::current()->PostDelayedTask(
- FROM_HERE, save_factory_.NewRunnableMethod(&BookmarkStorage::SaveNow),
- kSaveDelayMS);
- }
-}
-
-void BookmarkStorage::BookmarkModelDeleted() {
- if (!save_factory_.empty()) {
- // There's a pending save. We need to save now as otherwise by the time
- // SaveNow is invoked the model is gone.
- save_factory_.RevokeAll();
- SaveNow();
- }
- model_ = NULL;
-}
-
-void BookmarkStorage::LoadedBookmarks(Value* root_value,
- bool bookmark_file_exists,
- bool loaded_from_history) {
- scoped_ptr<Value> value_ref(root_value);
-
- if (model_) {
- if (root_value) {
- BookmarkCodec codec;
- codec.Decode(model_, *root_value);
- }
- model_->OnBookmarkStorageLoadedBookmarks(bookmark_file_exists,
- loaded_from_history);
- }
-}
-
-void BookmarkStorage::SaveNow() {
- if (!model_ || !model_->IsLoaded()) {
- // We should only get here if we have a valid model and it's finished
- // loading.
- NOTREACHED();
- return;
- }
-
- BookmarkCodec codec;
- Value* value = codec.Encode(model_);
- // The backend deletes value in write.
- if (!backend_thread()) {
- backend_->Write(value);
- } else {
- backend_thread()->message_loop()->PostTask(
- FROM_HERE,
- NewRunnableMethod(backend_.get(), &BookmarkStorageBackend::Write,
- value));
- }
-}
-
-// BookmarkStorageBackend ------------------------------------------------------
-
-BookmarkStorageBackend::BookmarkStorageBackend(
- const std::wstring& path,
- const std::wstring& tmp_history_path)
- : path_(path),
- tmp_history_path_(tmp_history_path) {
- // Make a backup of the current file.
- std::wstring backup_path = path;
- file_util::ReplaceExtension(&backup_path, kBackupExtension);
- file_util::CopyFile(path, backup_path);
-}
-
-void BookmarkStorageBackend::Write(Value* value) {
- DCHECK(value);
-
- // We own Value.
- scoped_ptr<Value> value_ref(value);
-
- std::string content;
- JSONWriter::Write(value, true, &content);
-
- // Write to a temp file, then rename.
- std::wstring tmp_file = path_;
- file_util::ReplaceExtension(&tmp_file, kTmpExtension);
-
- int bytes_written = file_util::WriteFile(tmp_file, content.c_str(),
- static_cast<int>(content.length()));
- if (bytes_written != -1) {
- MoveFileEx(tmp_file.c_str(), path_.c_str(),
- MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING);
- // Nuke the history file so that we don't attempt to load from it again.
- file_util::Delete(tmp_history_path_, false);
- }
-}
-
-void BookmarkStorageBackend::Read(scoped_refptr<BookmarkStorage> service,
- MessageLoop* message_loop,
- bool load_from_history) {
- const std::wstring& path = load_from_history ? tmp_history_path_ : path_;
- bool bookmark_file_exists = file_util::PathExists(path);
- Value* root = NULL;
- if (bookmark_file_exists) {
- JSONFileValueSerializer serializer(path);
- serializer.Deserialize(&root);
- }
-
- // BookmarkStorage takes ownership of root.
- if (message_loop) {
- message_loop->PostTask(FROM_HERE, NewRunnableMethod(
- service.get(), &BookmarkStorage::LoadedBookmarks, root,
- bookmark_file_exists, load_from_history));
- } else {
- service->LoadedBookmarks(root, bookmark_file_exists, load_from_history);
- }
-}
-
+// Copyright (c) 2006-2008 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/bookmark_storage.h"
+
+#include "base/file_util.h"
+#include "base/json_writer.h"
+#include "base/message_loop.h"
+#include "chrome/browser/bookmark_bar_model.h"
+#include "chrome/browser/bookmark_codec.h"
+#include "chrome/browser/profile.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/json_value_serializer.h"
+
+namespace {
+
+// Extension used for backup files (copy of main file created during startup).
+const wchar_t* const kBackupExtension = L"bak";
+
+// Extension for the temporary file. We write to the temp file than move to
+// kBookmarksFileName.
+const wchar_t* const kTmpExtension = L"tmp";
+
+// How often we save.
+const int kSaveDelayMS = 2500;
+
+} // namespace
+
+// BookmarkStorage -------------------------------------------------------------
+
+BookmarkStorage::BookmarkStorage(Profile* profile, BookmarkBarModel* model)
+ : model_(model),
+#pragma warning(suppress: 4355) // Okay to pass "this" here.
+ save_factory_(this),
+ backend_thread_(g_browser_process->file_thread()) {
+ std::wstring path = profile->GetPath();
+ file_util::AppendToPath(&path, chrome::kBookmarksFileName);
+ std::wstring tmp_history_path = profile->GetPath();
+ file_util::AppendToPath(&tmp_history_path, chrome::kHistoryBookmarksFileName);
+ backend_ = new BookmarkStorageBackend(path, tmp_history_path);
+}
+
+void BookmarkStorage::LoadBookmarks(bool load_from_history) {
+ if (!backend_thread()) {
+ backend_->Read(scoped_refptr<BookmarkStorage>(this), NULL,
+ load_from_history);
+ } else {
+ backend_thread()->message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(backend_.get(), &BookmarkStorageBackend::Read,
+ scoped_refptr<BookmarkStorage>(this),
+ MessageLoop::current(), load_from_history));
+ }
+}
+
+void BookmarkStorage::ScheduleSave() {
+ if (save_factory_.empty()) {
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, save_factory_.NewRunnableMethod(&BookmarkStorage::SaveNow),
+ kSaveDelayMS);
+ }
+}
+
+void BookmarkStorage::BookmarkModelDeleted() {
+ if (!save_factory_.empty()) {
+ // There's a pending save. We need to save now as otherwise by the time
+ // SaveNow is invoked the model is gone.
+ save_factory_.RevokeAll();
+ SaveNow();
+ }
+ model_ = NULL;
+}
+
+void BookmarkStorage::LoadedBookmarks(Value* root_value,
+ bool bookmark_file_exists,
+ bool loaded_from_history) {
+ scoped_ptr<Value> value_ref(root_value);
+
+ if (model_) {
+ if (root_value) {
+ BookmarkCodec codec;
+ codec.Decode(model_, *root_value);
+ }
+ model_->OnBookmarkStorageLoadedBookmarks(bookmark_file_exists,
+ loaded_from_history);
+ }
+}
+
+void BookmarkStorage::SaveNow() {
+ if (!model_ || !model_->IsLoaded()) {
+ // We should only get here if we have a valid model and it's finished
+ // loading.
+ NOTREACHED();
+ return;
+ }
+
+ BookmarkCodec codec;
+ Value* value = codec.Encode(model_);
+ // The backend deletes value in write.
+ if (!backend_thread()) {
+ backend_->Write(value);
+ } else {
+ backend_thread()->message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(backend_.get(), &BookmarkStorageBackend::Write,
+ value));
+ }
+}
+
+// BookmarkStorageBackend ------------------------------------------------------
+
+BookmarkStorageBackend::BookmarkStorageBackend(
+ const std::wstring& path,
+ const std::wstring& tmp_history_path)
+ : path_(path),
+ tmp_history_path_(tmp_history_path) {
+ // Make a backup of the current file.
+ std::wstring backup_path = path;
+ file_util::ReplaceExtension(&backup_path, kBackupExtension);
+ file_util::CopyFile(path, backup_path);
+}
+
+void BookmarkStorageBackend::Write(Value* value) {
+ DCHECK(value);
+
+ // We own Value.
+ scoped_ptr<Value> value_ref(value);
+
+ std::string content;
+ JSONWriter::Write(value, true, &content);
+
+ // Write to a temp file, then rename.
+ std::wstring tmp_file = path_;
+ file_util::ReplaceExtension(&tmp_file, kTmpExtension);
+
+ int bytes_written = file_util::WriteFile(tmp_file, content.c_str(),
+ static_cast<int>(content.length()));
+ if (bytes_written != -1) {
+ MoveFileEx(tmp_file.c_str(), path_.c_str(),
+ MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING);
+ // Nuke the history file so that we don't attempt to load from it again.
+ file_util::Delete(tmp_history_path_, false);
+ }
+}
+
+void BookmarkStorageBackend::Read(scoped_refptr<BookmarkStorage> service,
+ MessageLoop* message_loop,
+ bool load_from_history) {
+ const std::wstring& path = load_from_history ? tmp_history_path_ : path_;
+ bool bookmark_file_exists = file_util::PathExists(path);
+ Value* root = NULL;
+ if (bookmark_file_exists) {
+ JSONFileValueSerializer serializer(path);
+ serializer.Deserialize(&root);
+ }
+
+ // BookmarkStorage takes ownership of root.
+ if (message_loop) {
+ message_loop->PostTask(FROM_HERE, NewRunnableMethod(
+ service.get(), &BookmarkStorage::LoadedBookmarks, root,
+ bookmark_file_exists, load_from_history));
+ } else {
+ service->LoadedBookmarks(root, bookmark_file_exists, load_from_history);
+ }
+}
diff --git a/chrome/browser/bookmarks/bookmark_service.h b/chrome/browser/bookmarks/bookmark_service.h
new file mode 100644
index 0000000..a758382
--- /dev/null
+++ b/chrome/browser/bookmarks/bookmark_service.h
@@ -0,0 +1,61 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CHROME_BROWSER_BOOKMARKS_BOOKMARK_SERVICE_H_
+#define CHROME_BROWSER_BOOKMARKS_BOOKMARK_SERVICE_H_
+
+#include <vector>
+
+#include "base/ref_counted.h"
+
+class GURL;
+
+// BookmarkService provides a thread safe view of bookmarks. It is used by
+// HistoryBackend when it needs to determine the set of bookmarked URLs
+// or if a URL is bookmarked.
+//
+// BookmarkService is owned by Profile and deleted when the Profile is deleted.
+class BookmarkService {
+ public:
+ // Returns true if the specified URL is bookmarked.
+ //
+ // If not on the main thread you *must* invoke BlockTillLoaded first.
+ virtual bool IsBookmarked(const GURL& url) = 0;
+
+ // Returns, by reference in urls, the set of bookmarked urls.
+ //
+ // If not on the main thread you *must* invoke BlockTillLoaded first.
+ virtual void GetBookmarks(std::vector<GURL>* urls) = 0;
+
+ // Blocks until loaded. This is intended for usage on a thread other than
+ // the main thread.
+ virtual void BlockTillLoaded() = 0;
+};
+
+#endif // CHROME_BROWSER_BOOKMARKS_BOOKMARK_SERVICE_H_
diff --git a/chrome/browser/browser.vcproj b/chrome/browser/browser.vcproj
index cb2ee81..5a76452 100644
--- a/chrome/browser/browser.vcproj
+++ b/chrome/browser/browser.vcproj
@@ -834,6 +834,10 @@
>
</File>
<File
+ RelativePath=".\bookmarks\bookmark_service.h"
+ >
+ </File>
+ <File
RelativePath=".\bookmark_storage.cc"
>
</File>
diff --git a/chrome/browser/history/expire_history_backend.cc b/chrome/browser/history/expire_history_backend.cc
index 63983a0..fcd8f67 100644
--- a/chrome/browser/history/expire_history_backend.cc
+++ b/chrome/browser/history/expire_history_backend.cc
@@ -8,6 +8,7 @@
#include <limits>
#include "base/file_util.h"
+#include "chrome/browser/bookmarks/bookmark_service.h"
#include "chrome/browser/history/archived_database.h"
#include "chrome/browser/history/history_database.h"
#include "chrome/browser/history/history_notifications.h"
@@ -63,14 +64,16 @@ const int kExpirationEmptyDelayMin = 5;
} // namespace
ExpireHistoryBackend::ExpireHistoryBackend(
- BroadcastNotificationDelegate* delegate)
+ BroadcastNotificationDelegate* delegate,
+ BookmarkService* bookmark_service)
: delegate_(delegate),
main_db_(NULL),
archived_db_(NULL),
thumb_db_(NULL),
text_db_(NULL),
#pragma warning(suppress: 4355) // Okay to pass "this" here.
- factory_(this) {
+ factory_(this),
+ bookmark_service_(bookmark_service) {
}
ExpireHistoryBackend::~ExpireHistoryBackend() {
@@ -94,10 +97,6 @@ void ExpireHistoryBackend::DeleteURL(const GURL& url) {
if (!main_db_->GetRowForURL(url, &url_row))
return; // Nothing to delete.
- // The URL may be in the text database manager's temporary cache.
- if (text_db_)
- text_db_->DeleteURLFromUncommitted(url);
-
// Collect all the visits and delete them. Note that we don't give up if
// there are no visits, since the URL could still have an entry that we should
// delete.
@@ -111,11 +110,18 @@ void ExpireHistoryBackend::DeleteURL(const GURL& url) {
// We skip ExpireURLsForVisits (since we are deleting from the URL, and not
// starting with visits in a given time range). We therefore need to call the
// deletion and favicon update functions manually.
- DeleteOneURL(url_row, &dependencies);
- DeleteFaviconsIfPossible(dependencies.affected_favicons);
+
+ BookmarkService* bookmark_service = GetBookmarkService();
+ bool is_bookmarked =
+ (bookmark_service && bookmark_service->IsBookmarked(url));
+
+ DeleteOneURL(url_row, is_bookmarked, &dependencies);
+ if (!is_bookmarked)
+ DeleteFaviconsIfPossible(dependencies.affected_favicons);
if (text_db_)
text_db_->OptimizeChangedDatabases(dependencies.text_db_changes);
+
BroadcastDeleteNotifications(&dependencies);
}
@@ -243,20 +249,28 @@ void ExpireHistoryBackend::DeleteVisitRelatedInfo(
void ExpireHistoryBackend::DeleteOneURL(
const URLRow& url_row,
+ bool is_bookmarked,
DeleteDependencies* dependencies) {
- dependencies->deleted_urls.push_back(url_row);
-
- // Delete stuff that references this URL.
- if (thumb_db_)
- thumb_db_->DeleteThumbnail(url_row.id());
main_db_->DeleteSegmentForURL(url_row.id());
- // Collect shared information.
- if (url_row.favicon_id())
- dependencies->affected_favicons.insert(url_row.favicon_id());
+ // The URL may be in the text database manager's temporary cache.
+ if (text_db_)
+ text_db_->DeleteURLFromUncommitted(url_row.url());
- // Last, delete the URL entry.
- main_db_->DeleteURLRow(url_row.id());
+ if (!is_bookmarked) {
+ dependencies->deleted_urls.push_back(url_row);
+
+ // Delete stuff that references this URL.
+ if (thumb_db_)
+ thumb_db_->DeleteThumbnail(url_row.id());
+
+ // Collect shared information.
+ if (url_row.favicon_id())
+ dependencies->affected_favicons.insert(url_row.favicon_id());
+
+ // Last, delete the URL entry.
+ main_db_->DeleteURLRow(url_row.id());
+ }
}
URLID ExpireHistoryBackend::ArchiveOneURL(const URLRow& url_row) {
@@ -304,6 +318,7 @@ void ExpireHistoryBackend::ExpireURLsForVisits(
}
// Check each unique URL with deleted visits.
+ BookmarkService* bookmark_service = GetBookmarkService();
for (std::map<URLID, ChangedURL>::const_iterator i = changed_urls.begin();
i != changed_urls.end(); ++i) {
// The unique URL rows should already be filled into the dependencies.
@@ -320,20 +335,22 @@ void ExpireHistoryBackend::ExpireURLsForVisits(
else
url_row.set_last_visit(Time());
- // Don't delete URLs with visits still in the DB.
- if (!url_row.last_visit().is_null()) {
- // We're not deleting the URL, update its counts when we're deleting those
- // visits.
+ // Don't delete URLs with visits still in the DB, or bookmarked.
+ bool is_bookmarked =
+ (bookmark_service && bookmark_service->IsBookmarked(url_row.url()));
+ if (!is_bookmarked && url_row.last_visit().is_null()) {
+ // Not bookmarked and no more visits. Nuke the url.
+ DeleteOneURL(url_row, is_bookmarked, dependencies);
+ } else {
// NOTE: The calls to std::max() below are a backstop, but they should
// never actually be needed unless the database is corrupt (I think).
url_row.set_visit_count(
std::max(0, url_row.visit_count() - i->second.visit_count));
url_row.set_typed_count(
std::max(0, url_row.typed_count() - i->second.typed_count));
+
+ // Update the db with the new details.
main_db_->UpdateURLRow(url_row.id(), url_row);
- } else {
- // This URL is toast.
- DeleteOneURL(url_row, dependencies);
}
}
}
@@ -467,5 +484,15 @@ void ExpireHistoryBackend::ParanoidExpireHistory() {
// FIXME(brettw): Bug 1067331: write this to clean up any errors.
}
+BookmarkService* ExpireHistoryBackend::GetBookmarkService() {
+ // We use the bookmark service to determine if a URL is bookmarked. The
+ // bookmark service is loaded on a separate thread and may not be done by the
+ // time we get here. We therefor block until the bookmarks have finished
+ // loading.
+ if (bookmark_service_)
+ bookmark_service_->BlockTillLoaded();
+ return bookmark_service_;
+}
+
} // namespace history
diff --git a/chrome/browser/history/expire_history_backend.h b/chrome/browser/history/expire_history_backend.h
index 78c7c75..d795f88 100644
--- a/chrome/browser/history/expire_history_backend.h
+++ b/chrome/browser/history/expire_history_backend.h
@@ -15,6 +15,7 @@
#include "chrome/browser/history/text_database_manager.h"
#include "testing/gtest/include/gtest/gtest_prod.h"
+class BookmarkService;
class GURL;
enum NotificationType;
@@ -43,7 +44,11 @@ class BroadcastNotificationDelegate {
class ExpireHistoryBackend {
public:
// The delegate pointer must be non-NULL. We will NOT take ownership of it.
- explicit ExpireHistoryBackend(BroadcastNotificationDelegate* delegate);
+ // BookmarkService may be NULL. The BookmarkService is used when expiring
+ // URLs so that we don't remove any URLs or favicons that are bookmarked
+ // (visits are removed though).
+ ExpireHistoryBackend(BroadcastNotificationDelegate* delegate,
+ BookmarkService* bookmark_service);
~ExpireHistoryBackend();
// Completes initialization by setting the databases that this class will use.
@@ -80,6 +85,7 @@ class ExpireHistoryBackend {
FRIEND_TEST(ExpireHistoryTest, DeleteTextIndexForURL);
FRIEND_TEST(ExpireHistoryTest, DeleteFaviconsIfPossible);
FRIEND_TEST(ExpireHistoryTest, ArchiveSomeOldHistory);
+ friend class TestingProfile;
struct DeleteDependencies {
// The time range affected. These can be is_null() to be unbounded in one
@@ -144,7 +150,13 @@ class ExpireHistoryBackend {
// check for shared information at the end).
//
// Assumes the main_db_ is non-NULL.
- void DeleteOneURL(const URLRow& url_row, DeleteDependencies* dependencies);
+ //
+ // NOTE: If the url is bookmarked only the segments and text db are updated,
+ // everything else is unchanged. This is done so that bookmarks retain their
+ // favicons and thumbnails.
+ void DeleteOneURL(const URLRow& url_row,
+ bool is_bookmarked,
+ DeleteDependencies* dependencies);
// Adds or merges the given URL row with the archived database, returning the
// ID of the URL in the archived database, or 0 on failure. The main (source)
@@ -191,12 +203,7 @@ class ExpireHistoryBackend {
// care about favicons so much, so don't want to stop everything if it fails).
void DeleteFaviconsIfPossible(const std::set<FavIconID>& favicon_id);
- // Broadcasts that the given URLs and star entries were deleted. Either list
- // can be empty, in which case no notification will be sent for that type.
- //
- // The passed-in arguments will be cleared becuase they will be swapped into
- // the destination message to avoid copying). However, ownership of the
- // dependencies object will not transfer.
+ // Broadcast the URL deleted notification.
void BroadcastDeleteNotifications(DeleteDependencies* dependencies);
// Schedules a call to DoArchiveIteration at the given time in the
@@ -217,6 +224,10 @@ class ExpireHistoryBackend {
// and deletes items. For example, URLs with no visits.
void ParanoidExpireHistory();
+ // Returns the BookmarkService, blocking until it is loaded. This may return
+ // NULL.
+ BookmarkService* GetBookmarkService();
+
// Non-owning pointer to the notification delegate (guaranteed non-NULL).
BroadcastNotificationDelegate* delegate_;
@@ -234,10 +245,15 @@ class ExpireHistoryBackend {
// the archived database.
TimeDelta expiration_threshold_;
+ // The BookmarkService; may be null. This is owned by the Profile.
+ //
+ // Use GetBookmarkService to access this, which makes sure the service is
+ // loaded.
+ BookmarkService* bookmark_service_;
+
DISALLOW_EVIL_CONSTRUCTORS(ExpireHistoryBackend);
};
} // namespace history
#endif // CHROME_BROWSER_HISTORY_EXPIRE_HISTORY_BACKEND_H__
-
diff --git a/chrome/browser/history/expire_history_backend_unittest.cc b/chrome/browser/history/expire_history_backend_unittest.cc
index fba70eb..05c382a 100644
--- a/chrome/browser/history/expire_history_backend_unittest.cc
+++ b/chrome/browser/history/expire_history_backend_unittest.cc
@@ -7,6 +7,7 @@
#include "base/file_util.h"
#include "base/path_service.h"
#include "base/scoped_ptr.h"
+#include "chrome/browser/bookmark_bar_model.h"
#include "chrome/browser/history/archived_database.h"
#include "chrome/browser/history/expire_history_backend.h"
#include "chrome/browser/history/history_database.h"
@@ -29,7 +30,9 @@ class ExpireHistoryTest : public testing::Test,
public BroadcastNotificationDelegate {
public:
ExpireHistoryTest()
- : ALLOW_THIS_IN_INITIALIZER_LIST(expirer_(this)), now_(Time::Now()) {
+ : bookmark_model_(NULL),
+ ALLOW_THIS_IN_INITIALIZER_LIST(expirer_(this, &bookmark_model_)),
+ now_(Time::Now()) {
}
protected:
@@ -55,8 +58,15 @@ class ExpireHistoryTest : public testing::Test,
notifications_.clear();
}
+ void StarURL(const GURL& url) {
+ bookmark_model_.AddURL(
+ bookmark_model_.GetBookmarkBarNode(), 0, std::wstring(), url);
+ }
+
static bool IsStringInFile(std::wstring& filename, const char* str);
+ BookmarkBarModel bookmark_model_;
+
MessageLoop message_loop_;
ExpireHistoryBackend expirer_;
@@ -438,6 +448,47 @@ TEST_F(ExpireHistoryTest, DeleteURLWithoutFavicon) {
EXPECT_TRUE(HasFavIcon(last_row.favicon_id()));
}
+// DeleteURL should not delete starred urls.
+TEST_F(ExpireHistoryTest, DontDeleteStarredURL) {
+ URLID url_ids[3];
+ Time visit_times[4];
+ AddExampleData(url_ids, visit_times);
+
+ URLRow url_row;
+ ASSERT_TRUE(main_db_->GetURLRow(url_ids[2], &url_row));
+
+ // Star the last URL.
+ StarURL(url_row.url());
+
+ // Attempt to delete the url.
+ expirer_.DeleteURL(url_row.url());
+
+ // Because the url is starred, it shouldn't be deleted.
+ GURL url = url_row.url();
+ ASSERT_TRUE(main_db_->GetRowForURL(url, &url_row));
+
+ // And the favicon should exist.
+ EXPECT_TRUE(HasFavIcon(url_row.favicon_id()));
+
+ // But there should be no fts.
+ ASSERT_EQ(0, CountTextMatchesForURL(url_row.url()));
+
+ // And no visits.
+ VisitVector visits;
+ main_db_->GetVisitsForURL(url_row.id(), &visits);
+ ASSERT_EQ(0, visits.size());
+
+ // Should still have the thumbnail.
+ ASSERT_TRUE(HasThumbnail(url_row.id()));
+
+ // Unstar the URL and delete again.
+ bookmark_model_.SetURLStarred(url, std::wstring(), false);
+ expirer_.DeleteURL(url);
+
+ // Now it should be completely deleted.
+ EnsureURLInfoGone(url_row);
+}
+
// Expires all URLs more recent than a given time, with no starred items.
// Our time threshold is such that one URL should be updated (we delete one of
// the two visits) and one is deleted.
@@ -492,6 +543,49 @@ TEST_F(ExpireHistoryTest, FlushRecentURLsUnstarred) {
EXPECT_FALSE(HasFavIcon(url_row2.favicon_id()));
}
+// Expire a starred URL, it shouldn't get deleted
+TEST_F(ExpireHistoryTest, FlushRecentURLsStarred) {
+ URLID url_ids[3];
+ Time visit_times[4];
+ AddExampleData(url_ids, visit_times);
+
+ URLRow url_row1, url_row2;
+ ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &url_row1));
+ ASSERT_TRUE(main_db_->GetURLRow(url_ids[2], &url_row2));
+
+ // Star the last two URLs.
+ StarURL(url_row1.url());
+ StarURL(url_row2.url());
+
+ // This should delete the last two visits.
+ expirer_.ExpireHistoryBetween(visit_times[2], Time());
+
+ // The URL rows should still exist.
+ URLRow new_url_row1, new_url_row2;
+ ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &new_url_row1));
+ ASSERT_TRUE(main_db_->GetURLRow(url_ids[2], &new_url_row2));
+
+ // The visit times should be updated.
+ EXPECT_TRUE(new_url_row1.last_visit() == visit_times[1]);
+ EXPECT_TRUE(new_url_row2.last_visit().is_null()); // No last visit time.
+
+ // Visit/typed count should not be updated for bookmarks.
+ EXPECT_EQ(0, new_url_row1.typed_count());
+ EXPECT_EQ(1, new_url_row1.visit_count());
+ EXPECT_EQ(0, new_url_row2.typed_count());
+ EXPECT_EQ(0, new_url_row2.visit_count());
+
+ // Thumbnails and favicons should still exist. Note that we keep thumbnails
+ // that may have been updated since the time threshold. Since the URL still
+ // exists in history, this should not be a privacy problem, we only update
+ // the visit counts in this case for consistency anyway.
+ EXPECT_TRUE(HasFavIcon(new_url_row1.favicon_id()));
+ EXPECT_TRUE(HasThumbnail(new_url_row1.id()));
+ EXPECT_TRUE(HasFavIcon(new_url_row2.favicon_id()));
+ EXPECT_TRUE(HasThumbnail(new_url_row2.id()));
+
+}
+
TEST_F(ExpireHistoryTest, ArchiveHistoryBeforeUnstarred) {
URLID url_ids[3];
Time visit_times[4];
@@ -530,6 +624,51 @@ TEST_F(ExpireHistoryTest, ArchiveHistoryBeforeUnstarred) {
EXPECT_EQ(1, archived_visits.size());
}
+TEST_F(ExpireHistoryTest, ArchiveHistoryBeforeStarred) {
+ URLID url_ids[3];
+ Time visit_times[4];
+ AddExampleData(url_ids, visit_times);
+
+ URLRow url_row0, url_row1;
+ ASSERT_TRUE(main_db_->GetURLRow(url_ids[0], &url_row0));
+ ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &url_row1));
+
+ // Star the URLs.
+ StarURL(url_row0.url());
+ StarURL(url_row1.url());
+
+ // Now archive the first three visits (first two URLs). The first two visits
+ // should be, the third deleted, but the URL records should not.
+ expirer_.ArchiveHistoryBefore(visit_times[2]);
+
+ // The first URL should have its visit deleted, but it should still be present
+ // in the main DB and not in the archived one since it is starred.
+ URLRow temp_row;
+ ASSERT_TRUE(main_db_->GetURLRow(url_ids[0], &temp_row));
+ // Note that the ID is different in the archived DB, so look up by URL.
+ EXPECT_FALSE(archived_db_->GetRowForURL(temp_row.url(), NULL));
+ VisitVector visits;
+ main_db_->GetVisitsForURL(temp_row.id(), &visits);
+ EXPECT_EQ(0, visits.size());
+
+ // The second URL should have its first visit deleted and its second visit
+ // archived. It should be present in both the main DB (because it's starred)
+ // and the archived DB (for the archived visit).
+ ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &temp_row));
+ main_db_->GetVisitsForURL(temp_row.id(), &visits);
+ EXPECT_EQ(0, visits.size());
+
+ // Note that the ID is different in the archived DB, so look up by URL.
+ ASSERT_TRUE(archived_db_->GetRowForURL(temp_row.url(), &temp_row));
+ archived_db_->GetVisitsForURL(temp_row.id(), &visits);
+ ASSERT_EQ(1, visits.size());
+ EXPECT_TRUE(visit_times[2] == visits[0].visit_time);
+
+ // The third URL should be unchanged.
+ EXPECT_TRUE(main_db_->GetURLRow(url_ids[2], &temp_row));
+ EXPECT_FALSE(archived_db_->GetRowForURL(temp_row.url(), NULL));
+}
+
// Tests the return values from ArchiveSomeOldHistory. The rest of the
// functionality of this function is tested by the ArchiveHistoryBefore*
// tests which use this function internally.
@@ -557,4 +696,3 @@ TEST_F(ExpireHistoryTest, ArchiveSomeOldHistory) {
// Maybe also refer to invalid favicons.
} // namespace history
-
diff --git a/chrome/browser/history/history.cc b/chrome/browser/history/history.cc
index abbde8b..94f980e 100644
--- a/chrome/browser/history/history.cc
+++ b/chrome/browser/history/history.cc
@@ -78,6 +78,11 @@ class HistoryService::BackendDelegate : public HistoryBackend::Delegate {
&HistoryService::BroadcastNotifications, type, details));
}
+ virtual void DBLoaded() {
+ message_loop_->PostTask(FROM_HERE, NewRunnableMethod(history_service_.get(),
+ &HistoryService::OnDBLoaded));
+ }
+
private:
scoped_refptr<HistoryService> history_service_;
MessageLoop* message_loop_;
@@ -88,7 +93,8 @@ const history::StarID HistoryService::kBookmarkBarID = 1;
HistoryService::HistoryService()
: thread_(new ChromeThread(ChromeThread::HISTORY)),
- profile_(NULL) {
+ profile_(NULL),
+ backend_loaded_(false) {
if (NotificationService::current()) { // Is NULL when running generate_profile.
NotificationService::current()->AddObserver(
this, NOTIFY_HISTORY_URLS_DELETED, Source<Profile>(profile_));
@@ -97,7 +103,8 @@ HistoryService::HistoryService()
HistoryService::HistoryService(Profile* profile)
: thread_(new ChromeThread(ChromeThread::HISTORY)),
- profile_(profile) {
+ profile_(profile),
+ backend_loaded_(false) {
NotificationService::current()->AddObserver(
this, NOTIFY_HISTORY_URLS_DELETED, Source<Profile>(profile_));
}
@@ -113,13 +120,15 @@ HistoryService::~HistoryService() {
}
}
-bool HistoryService::Init(const std::wstring& history_dir) {
+bool HistoryService::Init(const std::wstring& history_dir,
+ BookmarkService* bookmark_service) {
if (!thread_->Start())
return false;
// Create the history backend.
scoped_refptr<HistoryBackend> backend(
- new HistoryBackend(history_dir, new BackendDelegate(this)));
+ new HistoryBackend(history_dir, new BackendDelegate(this),
+ bookmark_service));
history_backend_.swap(backend);
ScheduleAndForget(PRIORITY_UI, &HistoryBackend::Init);
@@ -206,6 +215,11 @@ HistoryService::Handle HistoryService::GetMostRecentKeywordSearchTerms(
keyword_id, prefix, max_count);
}
+void HistoryService::URLsNoLongerBookmarked(const std::set<GURL>& urls) {
+ ScheduleAndForget(PRIORITY_NORMAL, &HistoryBackend::URLsNoLongerBookmarked,
+ urls);
+}
+
HistoryService::Handle HistoryService::ScheduleDBTask(
HistoryDBTask* task,
CancelableRequestConsumerBase* consumer) {
@@ -216,13 +230,6 @@ HistoryService::Handle HistoryService::ScheduleDBTask(
request);
}
-HistoryService::Handle HistoryService::ScheduleEmptyCallback(
- CancelableRequestConsumerBase* consumer,
- EmptyHistoryCallback* callback) {
- return Schedule(PRIORITY_UI, &HistoryBackend::ProcessEmptyRequest,
- consumer, new history::EmptyHistoryRequest(callback));
-}
-
HistoryService::Handle HistoryService::QuerySegmentUsageSince(
CancelableRequestConsumerBase* consumer,
const Time from_time,
@@ -629,3 +636,9 @@ void HistoryService::BroadcastNotifications(
NotificationService::current()->Notify(type, source, det);
}
+void HistoryService::OnDBLoaded() {
+ backend_loaded_ = true;
+ NotificationService::current()->Notify(NOTIFY_HISTORY_LOADED,
+ Source<Profile>(profile_),
+ Details<HistoryService>(this));
+}
diff --git a/chrome/browser/history/history.h b/chrome/browser/history/history.h
index ec42335..74ef67b 100644
--- a/chrome/browser/history/history.h
+++ b/chrome/browser/history/history.h
@@ -25,7 +25,7 @@
#include "chrome/common/page_transition_types.h"
#include "chrome/common/ref_counted_util.h"
-class BookmarkBarModel;
+class BookmarkService;
struct DownloadCreateInfo;
class GURL;
class HistoryURLProvider;
@@ -98,8 +98,12 @@ class HistoryService : public CancelableRequestProvider,
// Initializes the history service, returning true on success. On false, do
// not call any other functions. The given directory will be used for storing
- // the history files.
- bool Init(const std::wstring& history_dir);
+ // the history files. The BookmarkService is used when deleting URLs to
+ // test if a URL is bookmarked; it may be NULL during testing.
+ bool Init(const std::wstring& history_dir, BookmarkService* bookmark_service);
+
+ // Did the backend finish loading the databases?
+ bool backend_loaded() const { return backend_loaded_; }
// Called on shutdown, this will tell the history backend to complete and
// will release pointers to it. No other functions should be called once
@@ -487,6 +491,11 @@ class HistoryService : public CancelableRequestProvider,
CancelableRequestConsumerBase* consumer,
GetMostRecentKeywordSearchTermsCallback* callback);
+ // Bookmarks -----------------------------------------------------------------
+
+ // Notification that a URL is no longer bookmarked.
+ void URLsNoLongerBookmarked(const std::set<GURL>& urls);
+
// Generic Stuff -------------------------------------------------------------
typedef Callback0::Type HistoryDBTaskCallback;
@@ -496,13 +505,6 @@ class HistoryService : public CancelableRequestProvider,
Handle ScheduleDBTask(HistoryDBTask* task,
CancelableRequestConsumerBase* consumer);
- typedef Callback0::Type EmptyHistoryCallback;
-
- // Schedules a task that does nothing on the backend. This can be used to get
- // notification when then history backend is done processing requests.
- Handle ScheduleEmptyCallback(CancelableRequestConsumerBase* consumer,
- EmptyHistoryCallback* callback);
-
// Testing -------------------------------------------------------------------
// Designed for unit tests, this passes the given task on to the history
@@ -536,6 +538,16 @@ class HistoryService : public CancelableRequestProvider,
private:
class BackendDelegate;
friend class BackendDelegate;
+ friend class history::HistoryBackend;
+ friend class history::HistoryQueryTest;
+ friend class HistoryOperation;
+ friend class HistoryURLProvider;
+ friend class HistoryURLProviderTest;
+ template<typename Info, typename Callback> friend class DownloadRequest;
+ friend class PageUsageRequest;
+ friend class RedirectRequest;
+ friend class FavIconRequest;
+ friend class TestingProfile;
// These are not currently used, hopefully we can do something in the future
// to ensure that the most important things happen first.
@@ -545,17 +557,6 @@ class HistoryService : public CancelableRequestProvider,
PRIORITY_LOW, // Low priority things like indexing or expiration.
};
- friend class BookmarkBarModel;
- friend class HistoryURLProvider;
- friend class history::HistoryBackend;
- template<typename Info, typename Callback> friend class DownloadRequest;
- friend class PageUsageRequest;
- friend class RedirectRequest;
- friend class FavIconRequest;
- friend class history::HistoryQueryTest;
- friend class HistoryOperation;
- friend class HistoryURLProviderTest;
-
// Implementation of NotificationObserver.
virtual void Observe(NotificationType type,
const NotificationSource& source,
@@ -577,6 +578,10 @@ class HistoryService : public CancelableRequestProvider,
void BroadcastNotifications(NotificationType type,
history::HistoryDetails* details_deleted);
+ // Notification from the backend that it has finished loading. Sends
+ // notification (NOTIFY_HISTORY_LOADED) and sets backend_loaded_ to true.
+ void OnDBLoaded();
+
// Returns true if this looks like the type of URL we want to add to the
// history. We filter out some URLs such as JavaScript.
bool CanAddURL(const GURL& url) const;
@@ -747,8 +752,11 @@ class HistoryService : public CancelableRequestProvider,
// The profile, may be null when testing.
Profile* profile_;
+ // Has the backend finished loading? The backend is loaded once Init has
+ // completed.
+ bool backend_loaded_;
+
DISALLOW_EVIL_CONSTRUCTORS(HistoryService);
};
#endif // CHROME_BROWSER_HISTORY_HISTORY_H__
-
diff --git a/chrome/browser/history/history_backend.cc b/chrome/browser/history/history_backend.cc
index 7682f6074..21f77fb 100644
--- a/chrome/browser/history/history_backend.cc
+++ b/chrome/browser/history/history_backend.cc
@@ -13,6 +13,7 @@
#include "base/string_util.h"
#include "base/time.h"
#include "chrome/browser/autocomplete/history_url_provider.h"
+#include "chrome/browser/bookmarks/bookmark_service.h"
#include "chrome/browser/history/download_types.h"
#include "chrome/browser/history/in_memory_history_backend.h"
#include "chrome/browser/history/page_usage_data.h"
@@ -25,8 +26,7 @@
/* The HistoryBackend consists of a number of components:
HistoryDatabase (stores past 3 months of history)
- StarredURLDatabase (stores starred pages)
- URLDatabase (stores a list of URLs)
+ URLDatabase (stores a list of URLs)
DownloadDatabase (stores a list of downloads)
VisitDatabase (stores a list of visits for the URLs)
VisitSegmentDatabase (stores groups of URLs for the most visited view).
@@ -36,8 +36,7 @@
DownloadDatabase (stores a list of downloads)
VisitDatabase (stores a list of visits for the URLs)
- (this does not store starred things or visit segments, all starred info
- is stored in HistoryDatabase, and visit segments expire after 3 mos.)
+ (this does not store visit segments as they expire after 3 mos.)
TextDatabaseManager (manages multiple text database for different times)
TextDatabase (represents a single month of full-text index).
@@ -187,15 +186,17 @@ class HistoryBackend::URLQuerier {
// HistoryBackend --------------------------------------------------------------
HistoryBackend::HistoryBackend(const std::wstring& history_dir,
- Delegate* delegate)
+ Delegate* delegate,
+ BookmarkService* bookmark_service)
: delegate_(delegate),
history_dir_(history_dir),
#pragma warning(suppress: 4355) // OK to pass "this" here.
- expirer_(this),
+ expirer_(this, bookmark_service),
backend_destroy_message_loop_(NULL),
recent_redirects_(kMaxRedirectCount),
backend_destroy_task_(NULL),
- segment_queried_(false) {
+ segment_queried_(false),
+ bookmark_service_(bookmark_service) {
}
HistoryBackend::~HistoryBackend() {
@@ -229,103 +230,8 @@ HistoryBackend::~HistoryBackend() {
}
void HistoryBackend::Init() {
- DCHECK(!db_.get()) << "Initializing HistoryBackend twice";
- // In the rare case where the db fails to initialize a dialog may get shown
- // the blocks the caller, yet allows other messages through. For this reason
- // we only set db_ to the created database if creation is successful. That
- // way other methods won't do anything as db_ is still NULL.
-
- TimeTicks beginning_time = TimeTicks::Now();
-
- // Compute the file names. Note that the index file can be removed when the
- // text db manager is finished being hooked up.
- std::wstring history_name = history_dir_;
- file_util::AppendToPath(&history_name, chrome::kHistoryFilename);
- std::wstring thumbnail_name = GetThumbnailFileName();
- std::wstring archived_name = GetArchivedFileName();
- std::wstring tmp_bookmarks_file = history_dir_;
- file_util::AppendToPath(&tmp_bookmarks_file,
- chrome::kHistoryBookmarksFileName);
-
- // History database.
- db_.reset(new HistoryDatabase());
- switch (db_->Init(history_name, tmp_bookmarks_file)) {
- case INIT_OK:
- break;
- case INIT_FAILURE:
- // A NULL db_ will cause all calls on this object to notice this error
- // and to not continue.
- LOG(WARNING) << "Unable to initialize history DB.";
- db_.reset();
- return;
- case INIT_TOO_NEW:
- delegate_->NotifyTooNew();
- db_.reset();
- return;
- default:
- NOTREACHED();
- }
-
- // Fill the in-memory database and send it back to the history service on the
- // main thread.
- InMemoryHistoryBackend* mem_backend = new InMemoryHistoryBackend;
- if (mem_backend->Init(history_name))
- delegate_->SetInMemoryBackend(mem_backend); // Takes ownership of pointer.
- else
- delete mem_backend; // Error case, run without the in-memory DB.
- db_->BeginExclusiveMode(); // Must be after the mem backend read the data.
-
- // Full-text database. This has to be first so we can pass it to the
- // HistoryDatabase for migration.
- text_database_.reset(new TextDatabaseManager(history_dir_, db_.get()));
- if (!text_database_->Init()) {
- LOG(WARNING) << "Text database initialization failed, running without it.";
- text_database_.reset();
- }
-
- // Thumbnail database.
- thumbnail_db_.reset(new ThumbnailDatabase());
- if (thumbnail_db_->Init(thumbnail_name) != INIT_OK) {
- // Unlike the main database, we don't error out when the database is too
- // new because this error is much less severe. Generally, this shouldn't
- // happen since the thumbnail and main datbase versions should be in sync.
- // We'll just continue without thumbnails & favicons in this case or any
- // other error.
- LOG(WARNING) << "Could not initialize the thumbnail database.";
- thumbnail_db_.reset();
- }
-
- // Archived database.
- archived_db_.reset(new ArchivedDatabase());
- if (!archived_db_->Init(archived_name)) {
- LOG(WARNING) << "Could not initialize the archived database.";
- archived_db_.reset();
- }
-
- // Tell the expiration module about all the nice databases we made. This must
- // happen before db_->Init() is called since the callback ForceArchiveHistory
- // may need to expire stuff.
- //
- // *sigh*, this can all be cleaned up when that migration code is removed.
- // The main DB initialization should intuitively be first (not that it
- // actually matters) and the expirer should be set last.
- expirer_.SetDatabases(db_.get(), archived_db_.get(),
- thumbnail_db_.get(), text_database_.get());
-
- // Open the long-running transaction.
- db_->BeginTransaction();
- if (thumbnail_db_.get())
- thumbnail_db_->BeginTransaction();
- if (archived_db_.get())
- archived_db_->BeginTransaction();
- if (text_database_.get())
- text_database_->BeginTransaction();
-
- // Start expiring old stuff.
- expirer_.StartArchivingOldStuff(TimeDelta::FromDays(kArchiveDaysThreshold));
-
- HISTOGRAM_TIMES(L"History.InitTime",
- TimeTicks::Now() - beginning_time);
+ InitImpl();
+ delegate_->DBLoaded();
}
void HistoryBackend::SetOnBackendDestroyTask(MessageLoop* message_loop,
@@ -574,6 +480,106 @@ void HistoryBackend::AddPage(scoped_refptr<HistoryAddPageArgs> request) {
ScheduleCommit();
}
+void HistoryBackend::InitImpl() {
+ DCHECK(!db_.get()) << "Initializing HistoryBackend twice";
+ // In the rare case where the db fails to initialize a dialog may get shown
+ // the blocks the caller, yet allows other messages through. For this reason
+ // we only set db_ to the created database if creation is successful. That
+ // way other methods won't do anything as db_ is still NULL.
+
+ TimeTicks beginning_time = TimeTicks::Now();
+
+ // Compute the file names. Note that the index file can be removed when the
+ // text db manager is finished being hooked up.
+ std::wstring history_name = history_dir_;
+ file_util::AppendToPath(&history_name, chrome::kHistoryFilename);
+ std::wstring thumbnail_name = GetThumbnailFileName();
+ std::wstring archived_name = GetArchivedFileName();
+ std::wstring tmp_bookmarks_file = history_dir_;
+ file_util::AppendToPath(&tmp_bookmarks_file,
+ chrome::kHistoryBookmarksFileName);
+
+ // History database.
+ db_.reset(new HistoryDatabase());
+ switch (db_->Init(history_name, tmp_bookmarks_file)) {
+ case INIT_OK:
+ break;
+ case INIT_FAILURE:
+ // A NULL db_ will cause all calls on this object to notice this error
+ // and to not continue.
+ LOG(WARNING) << "Unable to initialize history DB.";
+ db_.reset();
+ return;
+ case INIT_TOO_NEW:
+ delegate_->NotifyTooNew();
+ db_.reset();
+ return;
+ default:
+ NOTREACHED();
+ }
+
+ // Fill the in-memory database and send it back to the history service on the
+ // main thread.
+ InMemoryHistoryBackend* mem_backend = new InMemoryHistoryBackend;
+ if (mem_backend->Init(history_name))
+ delegate_->SetInMemoryBackend(mem_backend); // Takes ownership of pointer.
+ else
+ delete mem_backend; // Error case, run without the in-memory DB.
+ db_->BeginExclusiveMode(); // Must be after the mem backend read the data.
+
+ // Full-text database. This has to be first so we can pass it to the
+ // HistoryDatabase for migration.
+ text_database_.reset(new TextDatabaseManager(history_dir_, db_.get()));
+ if (!text_database_->Init()) {
+ LOG(WARNING) << "Text database initialization failed, running without it.";
+ text_database_.reset();
+ }
+
+ // Thumbnail database.
+ thumbnail_db_.reset(new ThumbnailDatabase());
+ if (thumbnail_db_->Init(thumbnail_name) != INIT_OK) {
+ // Unlike the main database, we don't error out when the database is too
+ // new because this error is much less severe. Generally, this shouldn't
+ // happen since the thumbnail and main datbase versions should be in sync.
+ // We'll just continue without thumbnails & favicons in this case or any
+ // other error.
+ LOG(WARNING) << "Could not initialize the thumbnail database.";
+ thumbnail_db_.reset();
+ }
+
+ // Archived database.
+ archived_db_.reset(new ArchivedDatabase());
+ if (!archived_db_->Init(archived_name)) {
+ LOG(WARNING) << "Could not initialize the archived database.";
+ archived_db_.reset();
+ }
+
+ // Tell the expiration module about all the nice databases we made. This must
+ // happen before db_->Init() is called since the callback ForceArchiveHistory
+ // may need to expire stuff.
+ //
+ // *sigh*, this can all be cleaned up when that migration code is removed.
+ // The main DB initialization should intuitively be first (not that it
+ // actually matters) and the expirer should be set last.
+ expirer_.SetDatabases(db_.get(), archived_db_.get(),
+ thumbnail_db_.get(), text_database_.get());
+
+ // Open the long-running transaction.
+ db_->BeginTransaction();
+ if (thumbnail_db_.get())
+ thumbnail_db_->BeginTransaction();
+ if (archived_db_.get())
+ archived_db_->BeginTransaction();
+ if (text_database_.get())
+ text_database_->BeginTransaction();
+
+ // Start expiring old stuff.
+ expirer_.StartArchivingOldStuff(TimeDelta::FromDays(kArchiveDaysThreshold));
+
+ HISTOGRAM_TIMES(L"History.InitTime",
+ TimeTicks::Now() - beginning_time);
+}
+
std::pair<URLID, VisitID> HistoryBackend::AddPageVisit(
const GURL& url,
Time time,
@@ -1052,8 +1058,7 @@ void HistoryBackend::QueryHistoryFTS(const std::wstring& text_query,
URLQuerier querier(db_.get(), archived_db_.get(), true);
- // Now get the row and visit information for each one, optionally
- // filtering on starred items.
+ // Now get the row and visit information for each one.
URLResult url_result; // Declare outside loop to prevent re-construction.
for (size_t i = 0; i < fts_matches.size(); i++) {
if (options.max_count != 0 &&
@@ -1291,7 +1296,7 @@ void HistoryBackend::SetImportedFavicons(
Time now = Time::Now();
- // Track all starred URLs that had their favicons set or updated.
+ // Track all URLs that had their favicons set or updated.
std::set<GURL> favicons_changed;
for (size_t i = 0; i < favicon_usage.size(); i++) {
@@ -1320,7 +1325,7 @@ void HistoryBackend::SetImportedFavicons(
}
if (!favicons_changed.empty()) {
- // Send the notification about the changed favicons for starred URLs.
+ // Send the notification about the changed favicon URLs.
FavIconChangeDetails* changed_details = new FavIconChangeDetails;
changed_details->urls.swap(favicons_changed);
BroadcastNotifications(NOTIFY_FAVICON_CHANGED, changed_details);
@@ -1602,6 +1607,23 @@ void HistoryBackend::ExpireHistoryBetween(
request->ForwardResult(ExpireHistoryRequest::TupleType());
}
+void HistoryBackend::URLsNoLongerBookmarked(const std::set<GURL>& urls) {
+ if (!db_.get())
+ return;
+
+ for (std::set<GURL>::const_iterator i = urls.begin(); i != urls.end(); ++i) {
+ URLRow url_row;
+ if (!db_->GetRowForURL(*i, &url_row))
+ continue; // The URL isn't in the db; nothing to do.
+
+ VisitVector visits;
+ db_->GetVisitsForURL(url_row.id(), &visits);
+
+ if (visits.empty())
+ expirer_.DeleteURL(*i); // There are no more visits; nuke the URL.
+ }
+}
+
void HistoryBackend::ProcessDBTask(
scoped_refptr<HistoryDBTaskRequest> request) {
DCHECK(request.get());
@@ -1619,11 +1641,6 @@ void HistoryBackend::ProcessDBTask(
}
}
-void HistoryBackend::ProcessEmptyRequest(
- scoped_refptr<EmptyHistoryRequest> request) {
- request->ForwardResult(EmptyHistoryRequest::TupleType());
-}
-
void HistoryBackend::BroadcastNotifications(
NotificationType type,
HistoryDetails* details_deleted) {
@@ -1645,29 +1662,23 @@ void HistoryBackend::DeleteAllHistory() {
// Since we are likely to have very few bookmarks and their dependencies
// compared to all history, this is also much faster than just deleting from
// the original tables directly.
- //
- // TODO(brettw): bug 989802: When we store bookmarks in a separate file, this
- // function can be simplified to having all the database objects close their
- // connections and we just delete the files.
- // Get starred entries and their corresponding URL rows.
- std::vector<StarredEntry> starred_entries;
+ // Get the bookmarked URLs.
+ std::vector<GURL> starred_urls;
+ BookmarkService* bookmark_service = GetBookmarkService();
+ if (bookmark_service)
+ bookmark_service_->GetBookmarks(&starred_urls);
std::vector<URLRow> kept_urls;
- for (size_t i = 0; i < starred_entries.size(); i++) {
- if (starred_entries[i].type != StarredEntry::URL)
- continue;
-
+ for (size_t i = 0; i < starred_urls.size(); i++) {
URLRow row;
- if (!db_->GetURLRow(starred_entries[i].url_id, &row))
+ if (!db_->GetRowForURL(starred_urls[i], &row))
continue;
// Clear the last visit time so when we write these rows they are "clean."
- // We keep the typed and visit counts. Since the kept URLs are bookmarks,
- // we can assume that the user isn't trying to hide that they like them,
- // and we can use these counts for giving better autocomplete suggestions.
row.set_last_visit(Time());
-
+ row.set_visit_count(0);
+ row.set_typed_count(0);
kept_urls.push_back(row);
}
@@ -1681,7 +1692,7 @@ void HistoryBackend::DeleteAllHistory() {
// ClearAllMainHistory will change the IDs of the URLs in kept_urls. Therfore,
// we clear the list afterwards to make sure nobody uses this invalid data.
- if (!ClearAllMainHistory(&starred_entries, kept_urls))
+ if (!ClearAllMainHistory(kept_urls))
LOG(ERROR) << "Main history could not be cleared";
kept_urls.clear();
@@ -1775,7 +1786,6 @@ bool HistoryBackend::ClearAllThumbnailHistory(
}
bool HistoryBackend::ClearAllMainHistory(
- std::vector<StarredEntry>* starred_entries,
const std::vector<URLRow>& kept_urls) {
// Create the duplicate URL table. We will copy the kept URLs into this.
if (!db_->CreateTemporaryURLTable())
@@ -1797,7 +1807,7 @@ bool HistoryBackend::ClearAllMainHistory(
return false;
// Delete the old tables and recreate them empty.
- db_->RecreateAllButStarAndURLTables();
+ db_->RecreateAllTablesButURL();
// Vacuum to reclaim the space from the dropped tables. This must be done
// when there is no transaction open, and we assume that our long-running
@@ -1808,5 +1818,11 @@ bool HistoryBackend::ClearAllMainHistory(
return true;
}
+BookmarkService* HistoryBackend::GetBookmarkService() {
+ if (bookmark_service_)
+ bookmark_service_->BlockTillLoaded();
+ return bookmark_service_;
+}
+
} // namespace history
diff --git a/chrome/browser/history/history_backend.h b/chrome/browser/history/history_backend.h
index 1143cef..dd37c3e 100644
--- a/chrome/browser/history/history_backend.h
+++ b/chrome/browser/history/history_backend.h
@@ -25,6 +25,7 @@
#include "chrome/common/scoped_vector.h"
#include "testing/gtest/include/gtest/gtest_prod.h"
+class BookmarkService;
struct ThumbnailScore;
namespace history {
@@ -75,6 +76,9 @@ class HistoryBackend : public base::RefCountedThreadSafe<HistoryBackend>,
// Ownership of the HistoryDetails is transferred to this function.
virtual void BroadcastNotifications(NotificationType type,
HistoryDetails* details) = 0;
+
+ // Invoked when the backend has finished loading the db.
+ virtual void DBLoaded() = 0;
};
// Init must be called to complete object creation. This object can be
@@ -85,8 +89,13 @@ class HistoryBackend : public base::RefCountedThreadSafe<HistoryBackend>,
// See the definition of BroadcastNotificationsCallback above. This function
// takes ownership of the callback pointer.
//
+ // |bookmark_service| is used to determine bookmarked URLs when deleting and
+ // may be NULL.
+ //
// This constructor is fast and does no I/O, so can be called at any time.
- HistoryBackend(const std::wstring& history_dir, Delegate* delegate);
+ HistoryBackend(const std::wstring& history_dir,
+ Delegate* delegate,
+ BookmarkService* bookmark_service);
~HistoryBackend();
@@ -218,8 +227,6 @@ class HistoryBackend : public base::RefCountedThreadSafe<HistoryBackend>,
void ProcessDBTask(scoped_refptr<HistoryDBTaskRequest> request);
- void ProcessEmptyRequest(scoped_refptr<EmptyHistoryRequest> request);
-
// Deleting ------------------------------------------------------------------
void DeleteURL(const GURL& url);
@@ -229,6 +236,12 @@ class HistoryBackend : public base::RefCountedThreadSafe<HistoryBackend>,
Time begin_time,
Time end_time);
+ // Bookmarks -----------------------------------------------------------------
+
+ // Notification that a URL is no longer bookmarked. If there are no visits
+ // for the specified url, it is deleted.
+ void URLsNoLongerBookmarked(const std::set<GURL>& urls);
+
// Testing -------------------------------------------------------------------
// Sets the task to run and the message loop to run it on when this object
@@ -244,6 +257,8 @@ class HistoryBackend : public base::RefCountedThreadSafe<HistoryBackend>,
friend class CommitLaterTask; // The commit task needs to call Commit().
friend class HistoryTest; // So the unit tests can poke our innards.
FRIEND_TEST(HistoryBackendTest, DeleteAll);
+ FRIEND_TEST(HistoryBackendTest, URLsNoLongerBookmarked);
+ friend class TestingProfile;
// For invoking methods that circumvent requests.
friend class HistoryTest;
@@ -255,6 +270,9 @@ class HistoryBackend : public base::RefCountedThreadSafe<HistoryBackend>,
class URLQuerier;
friend class URLQuerier;
+ // Does the work of Init.
+ void InitImpl();
+
// Adds a single visit to the database, updating the URL information such
// as visit and typed count. The visit ID of the added visit and the URL ID
// of the associated URL (whether added or not) is returned. Both values will
@@ -374,15 +392,16 @@ class HistoryBackend : public base::RefCountedThreadSafe<HistoryBackend>,
// vector to reference the new IDs.
bool ClearAllThumbnailHistory(std::vector<URLRow>* kept_urls);
- // Deletes all information in the history database, except the star table
- // (all entries should be in the given vector) and the given URLs in the URL
- // table (these should correspond to the bookmarked URLs).
+ // Deletes all information in the history database, except for the supplied
+ // set of URLs in the URL table (these should correspond to the bookmarked
+ // URLs).
//
- // The IDs of the URLs may change, and the starred table will be updated
- // accordingly. This function will also update the |starred_entries| input
- // vector.
- bool ClearAllMainHistory(std::vector<StarredEntry>* starred_entries,
- const std::vector<URLRow>& kept_urls);
+ // The IDs of the URLs may change.
+ bool ClearAllMainHistory(const std::vector<URLRow>& kept_urls);
+
+ // Returns the BookmarkService, blocking until it is loaded. This may return
+ // NULL during testing.
+ BookmarkService* GetBookmarkService();
// Data ----------------------------------------------------------------------
@@ -457,10 +476,16 @@ class HistoryBackend : public base::RefCountedThreadSafe<HistoryBackend>,
// done.
std::list<HistoryDBTaskRequest*> db_task_requests_;
+ // Used to determine if a URL is bookmarked. This is owned by the Profile and
+ // may be NULL (during testing).
+ //
+ // Use GetBookmarkService to access this, which makes sure the service is
+ // loaded.
+ BookmarkService* bookmark_service_;
+
DISALLOW_EVIL_CONSTRUCTORS(HistoryBackend);
};
} // namespace history
#endif // CHROME_BROWSER_HISTORY_HISTORY_BACKEND_H__
-
diff --git a/chrome/browser/history/history_backend_unittest.cc b/chrome/browser/history/history_backend_unittest.cc
index baf5f0e..4067440 100644
--- a/chrome/browser/history/history_backend_unittest.cc
+++ b/chrome/browser/history/history_backend_unittest.cc
@@ -5,6 +5,7 @@
#include "base/file_util.h"
#include "base/path_service.h"
#include "base/scoped_ptr.h"
+#include "chrome/browser/bookmark_bar_model.h"
#include "chrome/browser/history/history_backend.h"
#include "chrome/browser/history/in_memory_history_backend.h"
#include "chrome/browser/history/in_memory_database.h"
@@ -34,6 +35,7 @@ class HistoryBackendTestDelegate : public HistoryBackend::Delegate {
virtual void SetInMemoryBackend(InMemoryHistoryBackend* backend);
virtual void BroadcastNotifications(NotificationType type,
HistoryDetails* details);
+ virtual void DBLoaded();
private:
// Not owned by us.
@@ -44,6 +46,7 @@ class HistoryBackendTestDelegate : public HistoryBackend::Delegate {
class HistoryBackendTest : public testing::Test {
public:
+ HistoryBackendTest() : bookmark_model_(NULL), loaded_(false) {}
virtual ~HistoryBackendTest() {
}
@@ -66,6 +69,11 @@ class HistoryBackendTest : public testing::Test {
backend_->AddPage(request);
}
+ BookmarkBarModel bookmark_model_;
+
+ protected:
+ bool loaded_;
+
private:
friend HistoryBackendTestDelegate;
@@ -74,7 +82,8 @@ class HistoryBackendTest : public testing::Test {
if (!file_util::CreateNewTempDirectory(L"BackendTest", &test_dir_))
return;
backend_ = new HistoryBackend(test_dir_,
- new HistoryBackendTestDelegate(this));
+ new HistoryBackendTestDelegate(this),
+ &bookmark_model_);
backend_->Init();
}
virtual void TearDown() {
@@ -113,6 +122,14 @@ void HistoryBackendTestDelegate::BroadcastNotifications(
test_->BroadcastNotifications(type, details);
}
+void HistoryBackendTestDelegate::DBLoaded() {
+ test_->loaded_ = true;
+}
+
+TEST_F(HistoryBackendTest, Loaded) {
+ ASSERT_TRUE(backend_.get());
+ ASSERT_TRUE(loaded_);
+}
TEST_F(HistoryBackendTest, DeleteAll) {
ASSERT_TRUE(backend_.get());
@@ -181,6 +198,10 @@ TEST_F(HistoryBackendTest, DeleteAll) {
JPEGCodec::Decode(kWeewarThumbnail, sizeof(kWeewarThumbnail)));
backend_->thumbnail_db_->SetPageThumbnail(row2_id, *weewar_bitmap, score);
+ // Star row1.
+ bookmark_model_.AddURL(
+ bookmark_model_.GetBookmarkBarNode(), 0, std::wstring(), row1.url());
+
// Set full text index for each one.
backend_->text_database_->AddPageData(row1.url(), row1_id, visit1_id,
row1.last_visit(),
@@ -192,8 +213,11 @@ TEST_F(HistoryBackendTest, DeleteAll) {
// Now finally clear all history.
backend_->DeleteAllHistory();
- // The first URL should be deleted.
- EXPECT_FALSE(backend_->db_->GetRowForURL(row1.url(), &outrow1));
+ // The first URL should be preserved but the time should be cleared.
+ EXPECT_TRUE(backend_->db_->GetRowForURL(row1.url(), &outrow1));
+ EXPECT_EQ(0, outrow1.visit_count());
+ EXPECT_EQ(0, outrow1.typed_count());
+ EXPECT_TRUE(Time() == outrow1.last_visit());
// The second row should be deleted.
URLRow outrow2;
@@ -210,15 +234,22 @@ TEST_F(HistoryBackendTest, DeleteAll) {
&out_data));
EXPECT_FALSE(backend_->thumbnail_db_->GetPageThumbnail(row2_id, &out_data));
- // Make sure the favicons were deleted.
- // TODO(sky): would be nice if this didn't happen.
+ // We should have a favicon for the first URL only. We look them up by favicon
+ // URL since the IDs may hav changed.
FavIconID out_favicon1 = backend_->thumbnail_db_->
GetFavIconIDForFavIconURL(favicon_url1);
- EXPECT_FALSE(out_favicon1);
+ EXPECT_TRUE(out_favicon1);
FavIconID out_favicon2 = backend_->thumbnail_db_->
GetFavIconIDForFavIconURL(favicon_url2);
EXPECT_FALSE(out_favicon2) << "Favicon not deleted";
+ // The remaining URL should still reference the same favicon, even if its
+ // ID has changed.
+ EXPECT_EQ(out_favicon1, outrow1.favicon_id());
+
+ // The first URL should still be bookmarked.
+ EXPECT_TRUE(bookmark_model_.IsBookmarked(row1.url()));
+
// The full text database should have no data.
std::vector<TextDatabase::Match> text_matches;
Time first_time_searched;
@@ -228,6 +259,94 @@ TEST_F(HistoryBackendTest, DeleteAll) {
EXPECT_EQ(0, text_matches.size());
}
+TEST_F(HistoryBackendTest, URLsNoLongerBookmarked) {
+ GURL favicon_url1("http://www.google.com/favicon.ico");
+ GURL favicon_url2("http://news.google.com/favicon.ico");
+ FavIconID favicon2 = backend_->thumbnail_db_->AddFavIcon(favicon_url2);
+ FavIconID favicon1 = backend_->thumbnail_db_->AddFavIcon(favicon_url1);
+
+ std::vector<unsigned char> data;
+ data.push_back('1');
+ EXPECT_TRUE(backend_->thumbnail_db_->SetFavIcon(
+ favicon1, data, Time::Now()));
+
+ data[0] = '2';
+ EXPECT_TRUE(backend_->thumbnail_db_->SetFavIcon(
+ favicon2, data, Time::Now()));
+
+ // First visit two URLs.
+ URLRow row1(GURL("http://www.google.com/"));
+ row1.set_visit_count(2);
+ row1.set_typed_count(1);
+ row1.set_last_visit(Time::Now());
+ row1.set_favicon_id(favicon1);
+
+ URLRow row2(GURL("http://news.google.com/"));
+ row2.set_visit_count(1);
+ row2.set_last_visit(Time::Now());
+ row2.set_favicon_id(favicon2);
+
+ std::vector<URLRow> rows;
+ rows.push_back(row2); // Reversed order for the same reason as favicons.
+ rows.push_back(row1);
+ backend_->AddPagesWithDetails(rows);
+
+ URLID row1_id = backend_->db_->GetRowForURL(row1.url(), NULL);
+ URLID row2_id = backend_->db_->GetRowForURL(row2.url(), NULL);
+
+ // Star the two URLs.
+ bookmark_model_.SetURLStarred(row1.url(), std::wstring(), true);
+ bookmark_model_.SetURLStarred(row2.url(), std::wstring(), true);
+
+ // Delete url 2. Because url 2 is starred this won't delete the URL, only
+ // the visits.
+ backend_->expirer_.DeleteURL(row2.url());
+
+ // Make sure url 2 is still valid, but has no visits.
+ URLRow tmp_url_row;
+ EXPECT_EQ(row2_id, backend_->db_->GetRowForURL(row2.url(), NULL));
+ VisitVector visits;
+ backend_->db_->GetVisitsForURL(row2_id, &visits);
+ EXPECT_EQ(0, visits.size());
+ // The favicon should still be valid.
+ EXPECT_EQ(favicon2,
+ backend_->thumbnail_db_->GetFavIconIDForFavIconURL(favicon_url2));
+
+ // Unstar row2.
+ bookmark_model_.SetURLStarred(row2.url(), std::wstring(), false);
+ // Tell the backend it was unstarred. We have to explicitly do this as
+ // BookmarkBarModel isn't wired up to the backend during testing.
+ std::set<GURL> unstarred_urls;
+ unstarred_urls.insert(row2.url());
+ backend_->URLsNoLongerBookmarked(unstarred_urls);
+
+ // The URL should no longer exist.
+ EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), &tmp_url_row));
+ // And the favicon should be deleted.
+ EXPECT_EQ(0,
+ backend_->thumbnail_db_->GetFavIconIDForFavIconURL(favicon_url2));
+
+ // Unstar row 1.
+ bookmark_model_.SetURLStarred(row1.url(), std::wstring(), false);
+ // Tell the backend it was unstarred. We have to explicitly do this as
+ // BookmarkBarModel isn't wired up to the backend during testing.
+ unstarred_urls.clear();
+ unstarred_urls.insert(row1.url());
+ backend_->URLsNoLongerBookmarked(unstarred_urls);
+
+ // The URL should still exist (because there were visits).
+ EXPECT_EQ(row1_id, backend_->db_->GetRowForURL(row1.url(), NULL));
+
+ // There should still be visits.
+ visits.clear();
+ backend_->db_->GetVisitsForURL(row1_id, &visits);
+ EXPECT_EQ(1, visits.size());
+
+ // The favicon should still be valid.
+ EXPECT_EQ(favicon1,
+ backend_->thumbnail_db_->GetFavIconIDForFavIconURL(favicon_url1));
+}
+
TEST_F(HistoryBackendTest, GetPageThumbnailAfterRedirects) {
ASSERT_TRUE(backend_.get());
diff --git a/chrome/browser/history/history_database.cc b/chrome/browser/history/history_database.cc
index c33dee8..cf49883 100644
--- a/chrome/browser/history/history_database.cc
+++ b/chrome/browser/history/history_database.cc
@@ -127,7 +127,7 @@ void HistoryDatabase::CommitTransaction() {
}
}
-bool HistoryDatabase::RecreateAllButStarAndURLTables() {
+bool HistoryDatabase::RecreateAllTablesButURL() {
if (!DropVisitTable())
return false;
if (!InitVisitTable())
@@ -143,7 +143,7 @@ bool HistoryDatabase::RecreateAllButStarAndURLTables() {
if (!InitSegmentTables())
return false;
- // We also add the supplimentary URL indices at this point. This index is
+ // We also add the supplementary URL indices at this point. This index is
// over parts of the URL table that weren't automatically created when the
// temporary URL table was
CreateSupplimentaryURLIndices();
diff --git a/chrome/browser/history/history_database.h b/chrome/browser/history/history_database.h
index edde249..c69150b 100644
--- a/chrome/browser/history/history_database.h
+++ b/chrome/browser/history/history_database.h
@@ -88,8 +88,8 @@ class HistoryDatabase : public DownloadDatabase,
return transaction_nesting_;
}
- // Drops all tables except the URL, starred and download tables, and recreates
- // them from scratch. This is done to rapidly clean up stuff when deleting all
+ // Drops all tables except the URL, and download tables, and recreates them
+ // from scratch. This is done to rapidly clean up stuff when deleting all
// history. It is faster and less likely to have problems that deleting all
// rows in the tables.
//
@@ -102,10 +102,10 @@ class HistoryDatabase : public DownloadDatabase,
// This should be treated the same as an init failure, and the database
// should not be used any more.
//
- // This will also recreate the supplimentary URL indices, since these
+ // This will also recreate the supplementary URL indices, since these
// indices won't be created automatically when using the temporary URL
- // talbe (what the caller does right before calling this).
- bool RecreateAllButStarAndURLTables();
+ // table (what the caller does right before calling this).
+ bool RecreateAllTablesButURL();
// Vacuums the database. This will cause sqlite to defragment and collect
// unused space in the file. It can be VERY SLOW.
diff --git a/chrome/browser/history/history_marshaling.h b/chrome/browser/history/history_marshaling.h
index 146e6d4..fa02095 100644
--- a/chrome/browser/history/history_marshaling.h
+++ b/chrome/browser/history/history_marshaling.h
@@ -117,9 +117,6 @@ typedef CancelableRequest1<HistoryService::HistoryDBTaskCallback,
scoped_refptr<HistoryDBTask> >
HistoryDBTaskRequest;
-typedef CancelableRequest<HistoryService::EmptyHistoryCallback>
- EmptyHistoryRequest;
-
} // namespace history
#endif // CHROME_BROWSER_HISTORY_HISTORY_MARSHALING_H__
diff --git a/chrome/browser/history/history_querying_unittest.cc b/chrome/browser/history/history_querying_unittest.cc
index c62fc40..625bc5b 100644
--- a/chrome/browser/history/history_querying_unittest.cc
+++ b/chrome/browser/history/history_querying_unittest.cc
@@ -86,7 +86,7 @@ class HistoryQueryTest : public testing::Test {
file_util::CreateDirectory(history_dir_);
history_ = new HistoryService;
- if (!history_->Init(history_dir_)) {
+ if (!history_->Init(history_dir_, NULL)) {
history_ = NULL; // Tests should notice this NULL ptr & fail.
return;
}
diff --git a/chrome/browser/history/history_unittest.cc b/chrome/browser/history/history_unittest.cc
index 29f674e..c2ec919 100644
--- a/chrome/browser/history/history_unittest.cc
+++ b/chrome/browser/history/history_unittest.cc
@@ -90,6 +90,7 @@ class BackendDelegate : public HistoryBackend::Delegate {
virtual void SetInMemoryBackend(InMemoryHistoryBackend* backend);
virtual void BroadcastNotifications(NotificationType type,
HistoryDetails* details);
+ virtual void DBLoaded() {}
private:
HistoryTest* history_test_;
@@ -122,7 +123,8 @@ class HistoryTest : public testing::Test {
// Creates the HistoryBackend and HistoryDatabase on the current thread,
// assigning the values to backend_ and db_.
void CreateBackendAndDatabase() {
- backend_ = new HistoryBackend(history_dir_, new BackendDelegate(this));
+ backend_ =
+ new HistoryBackend(history_dir_, new BackendDelegate(this), NULL);
backend_->Init();
db_ = backend_->db_.get();
DCHECK(in_mem_backend_.get()) << "Mem backend should have been set by "
@@ -373,7 +375,7 @@ TEST_F(HistoryTest, ClearBrowsingData_Downloads) {
TEST_F(HistoryTest, AddPage) {
scoped_refptr<HistoryService> history(new HistoryService);
history_service_ = history;
- ASSERT_TRUE(history->Init(history_dir_));
+ ASSERT_TRUE(history->Init(history_dir_, NULL));
// Add the page once from a child frame.
const GURL test_url("http://www.google.com/");
@@ -397,7 +399,7 @@ TEST_F(HistoryTest, AddPage) {
TEST_F(HistoryTest, AddPageSameTimes) {
scoped_refptr<HistoryService> history(new HistoryService);
history_service_ = history;
- ASSERT_TRUE(history->Init(history_dir_));
+ ASSERT_TRUE(history->Init(history_dir_, NULL));
Time now = Time::Now();
const GURL test_urls[] = {
@@ -437,7 +439,7 @@ TEST_F(HistoryTest, AddPageSameTimes) {
TEST_F(HistoryTest, AddRedirect) {
scoped_refptr<HistoryService> history(new HistoryService);
history_service_ = history;
- ASSERT_TRUE(history->Init(history_dir_));
+ ASSERT_TRUE(history->Init(history_dir_, NULL));
const wchar_t* first_sequence[] = {
L"http://first.page/",
@@ -508,7 +510,7 @@ TEST_F(HistoryTest, AddRedirect) {
TEST_F(HistoryTest, Typed) {
scoped_refptr<HistoryService> history(new HistoryService);
history_service_ = history;
- ASSERT_TRUE(history->Init(history_dir_));
+ ASSERT_TRUE(history->Init(history_dir_, NULL));
// Add the page once as typed.
const GURL test_url("http://www.google.com/");
@@ -551,7 +553,7 @@ TEST_F(HistoryTest, Typed) {
TEST_F(HistoryTest, SetTitle) {
scoped_refptr<HistoryService> history(new HistoryService);
history_service_ = history;
- ASSERT_TRUE(history->Init(history_dir_));
+ ASSERT_TRUE(history->Init(history_dir_, NULL));
// Add a URL.
const GURL existing_url(L"http://www.google.com/");
@@ -582,7 +584,7 @@ TEST_F(HistoryTest, Segments) {
scoped_refptr<HistoryService> history(new HistoryService);
history_service_ = history;
- ASSERT_TRUE(history->Init(history_dir_));
+ ASSERT_TRUE(history->Init(history_dir_, NULL));
static const void* scope = static_cast<void*>(this);
@@ -648,7 +650,7 @@ TEST_F(HistoryTest, Segments) {
TEST_F(HistoryTest, Thumbnails) {
scoped_refptr<HistoryService> history(new HistoryService);
history_service_ = history;
- ASSERT_TRUE(history->Init(history_dir_));
+ ASSERT_TRUE(history->Init(history_dir_, NULL));
scoped_ptr<SkBitmap> thumbnail(
JPEGCodec::Decode(kGoogleThumbnail, sizeof(kGoogleThumbnail)));
@@ -798,7 +800,7 @@ class HistoryDBTaskImpl : public HistoryDBTask {
TEST_F(HistoryTest, HistoryDBTask) {
CancelableRequestConsumerT<int, 0> request_consumer;
HistoryService* history = new HistoryService();
- ASSERT_TRUE(history->Init(history_dir_));
+ ASSERT_TRUE(history->Init(history_dir_, NULL));
scoped_refptr<HistoryDBTaskImpl> task(new HistoryDBTaskImpl());
history_service_ = history;
history->ScheduleDBTask(task.get(), &request_consumer);
@@ -816,7 +818,7 @@ TEST_F(HistoryTest, HistoryDBTask) {
TEST_F(HistoryTest, HistoryDBTaskCanceled) {
CancelableRequestConsumerT<int, 0> request_consumer;
HistoryService* history = new HistoryService();
- ASSERT_TRUE(history->Init(history_dir_));
+ ASSERT_TRUE(history->Init(history_dir_, NULL));
scoped_refptr<HistoryDBTaskImpl> task(new HistoryDBTaskImpl());
history_service_ = history;
history->ScheduleDBTask(task.get(), &request_consumer);
diff --git a/chrome/browser/history/url_database.cc b/chrome/browser/history/url_database.cc
index 27a4994..aeb9ed2 100644
--- a/chrome/browser/history/url_database.cc
+++ b/chrome/browser/history/url_database.cc
@@ -224,8 +224,9 @@ bool URLDatabase::IsFavIconUsed(FavIconID favicon_id) {
void URLDatabase::AutocompleteForPrefix(const std::wstring& prefix,
size_t max_results,
std::vector<history::URLRow>* results) {
- // TODO(sky): this query should order by starred as the second order by
- // clause.
+ // NOTE: this query originally sorted by starred as the second parameter. But
+ // as bookmarks is no longer part of the db we no longer include the order
+ // by clause.
results->clear();
SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(),
"SELECT" HISTORY_URL_ROW_FIELDS "FROM urls "
diff --git a/chrome/browser/profile.cc b/chrome/browser/profile.cc
index e974e2a..9a64b40 100644
--- a/chrome/browser/profile.cc
+++ b/chrome/browser/profile.cc
@@ -617,6 +617,14 @@ ProfileImpl::~ProfileImpl() {
request_context_ = NULL;
}
+ // HistoryService may call into the BookmarkBarModel, as such we need to
+ // delete HistoryService before the BookmarkBarModel. The destructor for
+ // HistoryService will join with HistoryService's backend thread so that
+ // by the time the destructor has finished we're sure it will no longer call
+ // into the BookmarkBarModel.
+ history_service_ = NULL;
+ bookmark_bar_model_.reset();
+
MarkAsCleanShutdown();
}
@@ -721,11 +729,11 @@ URLRequestContext* ProfileImpl::GetRequestContext() {
HistoryService* ProfileImpl::GetHistoryService(ServiceAccessType sat) {
if (!history_service_created_) {
+ history_service_created_ = true;
scoped_refptr<HistoryService> history(new HistoryService(this));
- if (!history->Init(GetPath()))
+ if (!history->Init(GetPath(), GetBookmarkBarModel()))
return NULL;
history_service_.swap(history);
- history_service_created_ = true;
// Send out the notification that the history service was created.
NotificationService::current()->
@@ -844,8 +852,10 @@ bool ProfileImpl::HasBookmarkBarModel() {
}
BookmarkBarModel* ProfileImpl::GetBookmarkBarModel() {
- if (!bookmark_bar_model_.get())
+ if (!bookmark_bar_model_.get()) {
bookmark_bar_model_.reset(new BookmarkBarModel(this));
+ bookmark_bar_model_->Load();
+ }
return bookmark_bar_model_.get();
}
diff --git a/chrome/browser/views/bookmark_bar_view_test.cc b/chrome/browser/views/bookmark_bar_view_test.cc
index fb24dbf..b6571c6 100644
--- a/chrome/browser/views/bookmark_bar_view_test.cc
+++ b/chrome/browser/views/bookmark_bar_view_test.cc
@@ -73,7 +73,7 @@ class BookmarkBarViewEventTestBase : public ViewEventTestBase {
profile_.reset(new TestingProfile());
profile_->set_has_history_service(true);
- profile_->CreateBookmarkBarModel();
+ profile_->CreateBookmarkBarModel(true);
profile_->GetPrefs()->SetBoolean(prefs::kShowBookmarkBar, true);
model_ = profile_->GetBookmarkBarModel();
diff --git a/chrome/browser/views/bookmark_editor_view_unittest.cc b/chrome/browser/views/bookmark_editor_view_unittest.cc
index 93a99bf..7838113 100644
--- a/chrome/browser/views/bookmark_editor_view_unittest.cc
+++ b/chrome/browser/views/bookmark_editor_view_unittest.cc
@@ -21,7 +21,7 @@ class BookmarkEditorViewTest : public testing::Test {
virtual void SetUp() {
profile_.reset(new TestingProfile());
profile_->set_has_history_service(true);
- profile_->CreateBookmarkBarModel();
+ profile_->CreateBookmarkBarModel(true);
model_ = profile_->GetBookmarkBarModel();
diff --git a/chrome/browser/visitedlink_unittest.cc b/chrome/browser/visitedlink_unittest.cc
index afad81c..7f082b7 100644
--- a/chrome/browser/visitedlink_unittest.cc
+++ b/chrome/browser/visitedlink_unittest.cc
@@ -49,7 +49,7 @@ class VisitedLinkTest : public testing::Test {
// Initialize the history system. This should be called before InitVisited().
bool InitHistory() {
history_service_ = new HistoryService;
- return history_service_->Init(history_dir_);
+ return history_service_->Init(history_dir_, NULL);
}
// Initializes the visited link objects. Pass in the size that you want a