summaryrefslogtreecommitdiff
path: root/chromium/components/bookmarks
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@theqtcompany.com>2016-05-09 14:22:11 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2016-05-09 15:11:45 +0000
commit2ddb2d3e14eef3de7dbd0cef553d669b9ac2361c (patch)
treee75f511546c5fd1a173e87c1f9fb11d7ac8d1af3 /chromium/components/bookmarks
parenta4f3d46271c57e8155ba912df46a05559d14726e (diff)
downloadqtwebengine-chromium-2ddb2d3e14eef3de7dbd0cef553d669b9ac2361c.tar.gz
BASELINE: Update Chromium to 51.0.2704.41
Also adds in all smaller components by reversing logic for exclusion. Change-Id: Ibf90b506e7da088ea2f65dcf23f2b0992c504422 Reviewed-by: Joerg Bornemann <joerg.bornemann@theqtcompany.com>
Diffstat (limited to 'chromium/components/bookmarks')
-rw-r--r--chromium/components/bookmarks/DEPS18
-rw-r--r--chromium/components/bookmarks/OWNERS1
-rw-r--r--chromium/components/bookmarks/browser/BUILD.gn99
-rw-r--r--chromium/components/bookmarks/browser/OWNERS2
-rw-r--r--chromium/components/bookmarks/browser/base_bookmark_model_observer.cc63
-rw-r--r--chromium/components/bookmarks/browser/base_bookmark_model_observer.h55
-rw-r--r--chromium/components/bookmarks/browser/bookmark_client.cc35
-rw-r--r--chromium/components/bookmarks/browser/bookmark_client.h100
-rw-r--r--chromium/components/bookmarks/browser/bookmark_codec.cc492
-rw-r--r--chromium/components/bookmarks/browser/bookmark_codec.h212
-rw-r--r--chromium/components/bookmarks/browser/bookmark_codec_unittest.cc478
-rw-r--r--chromium/components/bookmarks/browser/bookmark_expanded_state_tracker.cc116
-rw-r--r--chromium/components/bookmarks/browser/bookmark_expanded_state_tracker.h60
-rw-r--r--chromium/components/bookmarks/browser/bookmark_expanded_state_tracker_unittest.cc102
-rw-r--r--chromium/components/bookmarks/browser/bookmark_index.cc293
-rw-r--r--chromium/components/bookmarks/browser/bookmark_index.h94
-rw-r--r--chromium/components/bookmarks/browser/bookmark_index_unittest.cc562
-rw-r--r--chromium/components/bookmarks/browser/bookmark_match.cc50
-rw-r--r--chromium/components/bookmarks/browser/bookmark_match.h51
-rw-r--r--chromium/components/bookmarks/browser/bookmark_model.cc1118
-rw-r--r--chromium/components/bookmarks/browser/bookmark_model.h474
-rw-r--r--chromium/components/bookmarks/browser/bookmark_model_observer.h143
-rw-r--r--chromium/components/bookmarks/browser/bookmark_model_unittest.cc1403
-rw-r--r--chromium/components/bookmarks/browser/bookmark_node.cc136
-rw-r--r--chromium/components/bookmarks/browser/bookmark_node.h217
-rw-r--r--chromium/components/bookmarks/browser/bookmark_node_data.cc308
-rw-r--r--chromium/components/bookmarks/browser/bookmark_node_data.h193
-rw-r--r--chromium/components/bookmarks/browser/bookmark_node_data_ios.cc26
-rw-r--r--chromium/components/bookmarks/browser/bookmark_node_data_mac.cc30
-rw-r--r--chromium/components/bookmarks/browser/bookmark_node_data_unittest.cc406
-rw-r--r--chromium/components/bookmarks/browser/bookmark_node_data_views.cc68
-rw-r--r--chromium/components/bookmarks/browser/bookmark_pasteboard_helper_mac.h55
-rw-r--r--chromium/components/bookmarks/browser/bookmark_pasteboard_helper_mac.mm310
-rw-r--r--chromium/components/bookmarks/browser/bookmark_storage.cc233
-rw-r--r--chromium/components/bookmarks/browser/bookmark_storage.h213
-rw-r--r--chromium/components/bookmarks/browser/bookmark_undo_delegate.h35
-rw-r--r--chromium/components/bookmarks/browser/bookmark_undo_provider.h29
-rw-r--r--chromium/components/bookmarks/browser/bookmark_utils.cc572
-rw-r--r--chromium/components/bookmarks/browser/bookmark_utils.h167
-rw-r--r--chromium/components/bookmarks/browser/bookmark_utils_unittest.cc606
-rw-r--r--chromium/components/bookmarks/browser/scoped_group_bookmark_actions.cc22
-rw-r--r--chromium/components/bookmarks/browser/scoped_group_bookmark_actions.h28
-rw-r--r--chromium/components/bookmarks/browser/startup_task_runner_service.cc36
-rw-r--r--chromium/components/bookmarks/browser/startup_task_runner_service.h49
-rw-r--r--chromium/components/bookmarks/common/BUILD.gn19
-rw-r--r--chromium/components/bookmarks/common/android/BUILD.gn44
-rw-r--r--chromium/components/bookmarks/common/android/OWNERS2
-rw-r--r--chromium/components/bookmarks/common/android/bookmark_id.cc30
-rw-r--r--chromium/components/bookmarks/common/android/bookmark_id.h30
-rw-r--r--chromium/components/bookmarks/common/android/bookmark_type.h19
-rw-r--r--chromium/components/bookmarks/common/bookmark_constants.cc13
-rw-r--r--chromium/components/bookmarks/common/bookmark_constants.h16
-rw-r--r--chromium/components/bookmarks/common/bookmark_pref_names.cc43
-rw-r--r--chromium/components/bookmarks/common/bookmark_pref_names.h25
-rw-r--r--chromium/components/bookmarks/managed/BUILD.gn48
-rw-r--r--chromium/components/bookmarks/managed/managed_bookmark_service.cc186
-rw-r--r--chromium/components/bookmarks/managed/managed_bookmark_service.h102
-rw-r--r--chromium/components/bookmarks/managed/managed_bookmark_util.cc33
-rw-r--r--chromium/components/bookmarks/managed/managed_bookmark_util.h23
-rw-r--r--chromium/components/bookmarks/managed/managed_bookmarks_tracker.cc214
-rw-r--r--chromium/components/bookmarks/managed/managed_bookmarks_tracker.h98
-rw-r--r--chromium/components/bookmarks/managed/managed_bookmarks_tracker_unittest.cc360
62 files changed, 11065 insertions, 0 deletions
diff --git a/chromium/components/bookmarks/DEPS b/chromium/components/bookmarks/DEPS
new file mode 100644
index 00000000000..40b213ec092
--- /dev/null
+++ b/chromium/components/bookmarks/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+ "+components/favicon_base",
+ "+components/keyed_service",
+ "+components/pref_registry",
+ "+components/prefs",
+ "+components/query_parser",
+ "+components/url_formatter",
+ "+grit/components_strings.h",
+ "+jni",
+ "+net/base",
+ "+ui",
+]
+
+specific_include_rules = {
+ "bookmark_model_unittest.cc": [
+ "+third_party/skia",
+ ]
+}
diff --git a/chromium/components/bookmarks/OWNERS b/chromium/components/bookmarks/OWNERS
new file mode 100644
index 00000000000..90b3e809ffe
--- /dev/null
+++ b/chromium/components/bookmarks/OWNERS
@@ -0,0 +1 @@
+sky@chromium.org
diff --git a/chromium/components/bookmarks/browser/BUILD.gn b/chromium/components/bookmarks/browser/BUILD.gn
new file mode 100644
index 00000000000..66ffe44fb02
--- /dev/null
+++ b/chromium/components/bookmarks/browser/BUILD.gn
@@ -0,0 +1,99 @@
+# Copyright 2014 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.
+
+import("//build/config/ui.gni")
+
+source_set("browser") {
+ sources = [
+ "base_bookmark_model_observer.cc",
+ "base_bookmark_model_observer.h",
+ "bookmark_client.cc",
+ "bookmark_client.h",
+ "bookmark_codec.cc",
+ "bookmark_codec.h",
+ "bookmark_expanded_state_tracker.cc",
+ "bookmark_expanded_state_tracker.h",
+ "bookmark_index.cc",
+ "bookmark_index.h",
+ "bookmark_match.cc",
+ "bookmark_match.h",
+ "bookmark_model.cc",
+ "bookmark_model.h",
+ "bookmark_model_observer.h",
+ "bookmark_node.cc",
+ "bookmark_node.h",
+ "bookmark_node_data.cc",
+ "bookmark_node_data.h",
+ "bookmark_node_data_ios.cc",
+ "bookmark_node_data_mac.cc",
+ "bookmark_pasteboard_helper_mac.h",
+ "bookmark_pasteboard_helper_mac.mm",
+ "bookmark_storage.cc",
+ "bookmark_storage.h",
+ "bookmark_undo_delegate.h",
+ "bookmark_undo_provider.h",
+ "bookmark_utils.cc",
+ "bookmark_utils.h",
+ "scoped_group_bookmark_actions.cc",
+ "scoped_group_bookmark_actions.h",
+ "startup_task_runner_service.cc",
+ "startup_task_runner_service.h",
+ ]
+
+ public_deps = [
+ "//components/bookmarks/common",
+ ]
+
+ deps = [
+ "//base",
+ "//base:i18n",
+ "//components/favicon_base",
+ "//components/keyed_service/core",
+ "//components/pref_registry",
+ "//components/prefs",
+ "//components/query_parser",
+ "//components/strings",
+ "//components/url_formatter",
+ "//net",
+ "//third_party/icu",
+ "//ui/base",
+ "//ui/gfx",
+ "//url",
+ ]
+
+ if (toolkit_views) {
+ sources += [ "bookmark_node_data_views.cc" ]
+ deps += [ "//ui/views" ]
+ }
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [
+ "bookmark_codec_unittest.cc",
+ "bookmark_expanded_state_tracker_unittest.cc",
+ "bookmark_index_unittest.cc",
+ "bookmark_model_unittest.cc",
+ "bookmark_utils_unittest.cc",
+ ]
+
+ if (toolkit_views) {
+ sources += [ "bookmark_node_data_unittest.cc" ]
+ }
+
+ configs += [ "//build/config/compiler:no_size_t_to_int_warning" ]
+
+ deps = [
+ ":browser",
+ "//components/bookmarks/common",
+ "//components/bookmarks/test",
+ "//components/favicon_base",
+ "//components/pref_registry",
+ "//components/prefs",
+ "//components/prefs:test_support",
+ "//testing/gtest",
+ "//ui/base",
+ "//url",
+ ]
+}
diff --git a/chromium/components/bookmarks/browser/OWNERS b/chromium/components/bookmarks/browser/OWNERS
new file mode 100644
index 00000000000..3a970a95432
--- /dev/null
+++ b/chromium/components/bookmarks/browser/OWNERS
@@ -0,0 +1,2 @@
+per-file bookmark_pasteboard_helper_mac.*=avi@chromium.org
+per-file bookmark_node_data_mac.*=avi@chromium.org
diff --git a/chromium/components/bookmarks/browser/base_bookmark_model_observer.cc b/chromium/components/bookmarks/browser/base_bookmark_model_observer.cc
new file mode 100644
index 00000000000..7d40d0f9eb5
--- /dev/null
+++ b/chromium/components/bookmarks/browser/base_bookmark_model_observer.cc
@@ -0,0 +1,63 @@
+// Copyright 2014 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 "components/bookmarks/browser/base_bookmark_model_observer.h"
+
+namespace bookmarks {
+
+void BaseBookmarkModelObserver::BookmarkModelLoaded(BookmarkModel* model,
+ bool ids_reassigned) {}
+
+void BaseBookmarkModelObserver::BookmarkModelBeingDeleted(
+ BookmarkModel* model) {
+ BookmarkModelChanged();
+}
+
+void BaseBookmarkModelObserver::BookmarkNodeMoved(
+ BookmarkModel* model,
+ const BookmarkNode* old_parent,
+ int old_index,
+ const BookmarkNode* new_parent,
+ int new_index) {
+ BookmarkModelChanged();
+}
+
+void BaseBookmarkModelObserver::BookmarkNodeAdded(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int index) {
+ BookmarkModelChanged();
+}
+
+void BaseBookmarkModelObserver::BookmarkNodeRemoved(
+ BookmarkModel* model,
+ const BookmarkNode* parent,
+ int old_index,
+ const BookmarkNode* node,
+ const std::set<GURL>& removed_urls) {
+ BookmarkModelChanged();
+}
+
+void BaseBookmarkModelObserver::BookmarkAllUserNodesRemoved(
+ BookmarkModel* model,
+ const std::set<GURL>& removed_urls) {
+ BookmarkModelChanged();
+}
+
+void BaseBookmarkModelObserver::BookmarkNodeChanged(BookmarkModel* model,
+ const BookmarkNode* node) {
+ BookmarkModelChanged();
+}
+
+void BaseBookmarkModelObserver::BookmarkNodeFaviconChanged(
+ BookmarkModel* model,
+ const BookmarkNode* node) {
+}
+
+void BaseBookmarkModelObserver::BookmarkNodeChildrenReordered(
+ BookmarkModel* model,
+ const BookmarkNode* node) {
+ BookmarkModelChanged();
+}
+
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/browser/base_bookmark_model_observer.h b/chromium/components/bookmarks/browser/base_bookmark_model_observer.h
new file mode 100644
index 00000000000..14a184ef777
--- /dev/null
+++ b/chromium/components/bookmarks/browser/base_bookmark_model_observer.h
@@ -0,0 +1,55 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_BOOKMARKS_BROWSER_BASE_BOOKMARK_MODEL_OBSERVER_H_
+#define COMPONENTS_BOOKMARKS_BROWSER_BASE_BOOKMARK_MODEL_OBSERVER_H_
+
+#include "base/macros.h"
+#include "components/bookmarks/browser/bookmark_model_observer.h"
+
+namespace bookmarks {
+
+// Base class for a BookmarkModelObserver implementation. All mutations of the
+// model funnel into the method BookmarkModelChanged.
+class BaseBookmarkModelObserver : public BookmarkModelObserver {
+ public:
+ BaseBookmarkModelObserver() {}
+
+ virtual void BookmarkModelChanged() = 0;
+
+ // BookmarkModelObserver:
+ void BookmarkModelLoaded(BookmarkModel* model, bool ids_reassigned) override;
+ void BookmarkModelBeingDeleted(BookmarkModel* model) override;
+ void BookmarkNodeMoved(BookmarkModel* model,
+ const BookmarkNode* old_parent,
+ int old_index,
+ const BookmarkNode* new_parent,
+ int new_index) override;
+ void BookmarkNodeAdded(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int index) override;
+ void BookmarkNodeRemoved(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int old_index,
+ const BookmarkNode* node,
+ const std::set<GURL>& removed_urls) override;
+ void BookmarkAllUserNodesRemoved(BookmarkModel* model,
+ const std::set<GURL>& removed_urls) override;
+ void BookmarkNodeChanged(BookmarkModel* model,
+ const BookmarkNode* node) override;
+ void BookmarkNodeFaviconChanged(BookmarkModel* model,
+ const BookmarkNode* node) override;
+ void BookmarkNodeChildrenReordered(BookmarkModel* model,
+ const BookmarkNode* node) override;
+
+ protected:
+ ~BaseBookmarkModelObserver() override {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BaseBookmarkModelObserver);
+};
+
+} // namespace bookmarks
+
+#endif // COMPONENTS_BOOKMARKS_BROWSER_BASE_BOOKMARK_MODEL_OBSERVER_H_
diff --git a/chromium/components/bookmarks/browser/bookmark_client.cc b/chromium/components/bookmarks/browser/bookmark_client.cc
new file mode 100644
index 00000000000..93250153eaf
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_client.cc
@@ -0,0 +1,35 @@
+// Copyright 2014 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 "components/bookmarks/browser/bookmark_client.h"
+
+#include "base/logging.h"
+
+namespace bookmarks {
+
+void BookmarkClient::Init(BookmarkModel* model) {}
+
+bool BookmarkClient::PreferTouchIcon() {
+ return false;
+}
+
+base::CancelableTaskTracker::TaskId BookmarkClient::GetFaviconImageForPageURL(
+ const GURL& page_url,
+ favicon_base::IconType type,
+ const favicon_base::FaviconImageCallback& callback,
+ base::CancelableTaskTracker* tracker) {
+ return base::CancelableTaskTracker::kBadTaskId;
+}
+
+bool BookmarkClient::SupportsTypedCountForNodes() {
+ return false;
+}
+
+void BookmarkClient::GetTypedCountForNodes(
+ const NodeSet& nodes,
+ NodeTypedCountPairs* node_typed_count_pairs) {
+ NOTREACHED();
+}
+
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/browser/bookmark_client.h b/chromium/components/bookmarks/browser/bookmark_client.h
new file mode 100644
index 00000000000..5eb625fa88f
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_client.h
@@ -0,0 +1,100 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_CLIENT_H_
+#define COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_CLIENT_H_
+
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/task/cancelable_task_tracker.h"
+#include "components/bookmarks/browser/bookmark_storage.h"
+#include "components/favicon_base/favicon_callback.h"
+#include "components/favicon_base/favicon_types.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+class GURL;
+
+namespace base {
+struct UserMetricsAction;
+}
+
+namespace bookmarks {
+
+class BookmarkModel;
+class BookmarkNode;
+class BookmarkPermanentNode;
+
+// This class abstracts operations that depends on the embedder's environment,
+// e.g. Chrome.
+class BookmarkClient {
+ public:
+ // Types representing a set of BookmarkNode and a mapping from BookmarkNode
+ // to the number of time the corresponding URL has been typed by the user in
+ // the Omnibox.
+ typedef std::set<const BookmarkNode*> NodeSet;
+ typedef std::pair<const BookmarkNode*, int> NodeTypedCountPair;
+ typedef std::vector<NodeTypedCountPair> NodeTypedCountPairs;
+
+ virtual ~BookmarkClient() {}
+
+ // Called during initialization of BookmarkModel.
+ virtual void Init(BookmarkModel* model);
+
+ // Returns true if the embedder favors touch icons over favicons.
+ virtual bool PreferTouchIcon();
+
+ // Requests a favicon from the history cache for the web page at |page_url|.
+ // |callback| is run when the favicon has been fetched. If |type| is:
+ // - favicon_base::FAVICON, the returned gfx::Image is a multi-resolution
+ // image of gfx::kFaviconSize DIP width and height. The data from the
+ // history cache is resized if need be.
+ // - not favicon_base::FAVICON, the returned gfx::Image is a single-resolution
+ // image with the largest bitmap in the history cache for |page_url| and
+ // |type|.
+ virtual base::CancelableTaskTracker::TaskId GetFaviconImageForPageURL(
+ const GURL& page_url,
+ favicon_base::IconType type,
+ const favicon_base::FaviconImageCallback& callback,
+ base::CancelableTaskTracker* tracker);
+
+ // Returns true if the embedder supports typed count for URL.
+ virtual bool SupportsTypedCountForNodes();
+
+ // Retrieves the number of time each BookmarkNode URL has been typed in
+ // the Omnibox by the user.
+ virtual void GetTypedCountForNodes(
+ const NodeSet& nodes,
+ NodeTypedCountPairs* node_typed_count_pairs);
+
+ // Returns whether the embedder wants permanent node |node|
+ // to always be visible or to only show them when not empty.
+ virtual bool IsPermanentNodeVisible(const BookmarkPermanentNode* node) = 0;
+
+ // Wrapper around RecordAction defined in base/metrics/user_metrics.h
+ // that ensure that the action is posted from the correct thread.
+ virtual void RecordAction(const base::UserMetricsAction& action) = 0;
+
+ // Returns a task that will be used to load any additional root nodes. This
+ // task will be invoked in the Profile's IO task runner.
+ virtual LoadExtraCallback GetLoadExtraNodesCallback() = 0;
+
+ // Returns true if the |permanent_node| can have its title updated.
+ virtual bool CanSetPermanentNodeTitle(const BookmarkNode* permanent_node) = 0;
+
+ // Returns true if |node| should sync.
+ virtual bool CanSyncNode(const BookmarkNode* node) = 0;
+
+ // Returns true if this node can be edited by the user.
+ // TODO(joaodasilva): the model should check this more aggressively, and
+ // should give the client a means to temporarily disable those checks.
+ // http://crbug.com/49598
+ virtual bool CanBeEditedByUser(const BookmarkNode* node) = 0;
+};
+
+} // namespace bookmarks
+
+#endif // COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_CLIENT_H_
diff --git a/chromium/components/bookmarks/browser/bookmark_codec.cc b/chromium/components/bookmarks/browser/bookmark_codec.cc
new file mode 100644
index 00000000000..ddda44b7fd3
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_codec.cc
@@ -0,0 +1,492 @@
+// Copyright 2014 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 "components/bookmarks/browser/bookmark_codec.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+
+#include "base/json/json_string_value_serializer.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "components/bookmarks/browser/bookmark_model.h"
+#include "grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "url/gurl.h"
+
+using base::Time;
+
+namespace bookmarks {
+
+const char BookmarkCodec::kRootsKey[] = "roots";
+const char BookmarkCodec::kRootFolderNameKey[] = "bookmark_bar";
+const char BookmarkCodec::kOtherBookmarkFolderNameKey[] = "other";
+// The value is left as 'synced' for historical reasons.
+const char BookmarkCodec::kMobileBookmarkFolderNameKey[] = "synced";
+const char BookmarkCodec::kVersionKey[] = "version";
+const char BookmarkCodec::kChecksumKey[] = "checksum";
+const char BookmarkCodec::kIdKey[] = "id";
+const char BookmarkCodec::kTypeKey[] = "type";
+const char BookmarkCodec::kNameKey[] = "name";
+const char BookmarkCodec::kDateAddedKey[] = "date_added";
+const char BookmarkCodec::kURLKey[] = "url";
+const char BookmarkCodec::kDateModifiedKey[] = "date_modified";
+const char BookmarkCodec::kChildrenKey[] = "children";
+const char BookmarkCodec::kMetaInfo[] = "meta_info";
+const char BookmarkCodec::kSyncTransactionVersion[] =
+ "sync_transaction_version";
+const char BookmarkCodec::kTypeURL[] = "url";
+const char BookmarkCodec::kTypeFolder[] = "folder";
+
+// Current version of the file.
+static const int kCurrentVersion = 1;
+
+BookmarkCodec::BookmarkCodec()
+ : ids_reassigned_(false),
+ ids_valid_(true),
+ maximum_id_(0),
+ model_sync_transaction_version_(
+ BookmarkNode::kInvalidSyncTransactionVersion) {
+}
+
+BookmarkCodec::~BookmarkCodec() {}
+
+base::Value* BookmarkCodec::Encode(BookmarkModel* model) {
+ return Encode(model->bookmark_bar_node(),
+ model->other_node(),
+ model->mobile_node(),
+ model->root_node()->GetMetaInfoMap(),
+ model->root_node()->sync_transaction_version());
+}
+
+base::Value* BookmarkCodec::Encode(
+ const BookmarkNode* bookmark_bar_node,
+ const BookmarkNode* other_folder_node,
+ const BookmarkNode* mobile_folder_node,
+ const BookmarkNode::MetaInfoMap* model_meta_info_map,
+ int64_t sync_transaction_version) {
+ ids_reassigned_ = false;
+ InitializeChecksum();
+ base::DictionaryValue* roots = new base::DictionaryValue();
+ roots->Set(kRootFolderNameKey, EncodeNode(bookmark_bar_node));
+ roots->Set(kOtherBookmarkFolderNameKey, EncodeNode(other_folder_node));
+ roots->Set(kMobileBookmarkFolderNameKey, EncodeNode(mobile_folder_node));
+ if (model_meta_info_map)
+ roots->Set(kMetaInfo, EncodeMetaInfo(*model_meta_info_map));
+ if (sync_transaction_version !=
+ BookmarkNode::kInvalidSyncTransactionVersion) {
+ roots->SetString(kSyncTransactionVersion,
+ base::Int64ToString(sync_transaction_version));
+ }
+ base::DictionaryValue* main = new base::DictionaryValue();
+ main->SetInteger(kVersionKey, kCurrentVersion);
+ FinalizeChecksum();
+ // We are going to store the computed checksum. So set stored checksum to be
+ // the same as computed checksum.
+ stored_checksum_ = computed_checksum_;
+ main->Set(kChecksumKey, new base::StringValue(computed_checksum_));
+ main->Set(kRootsKey, roots);
+ return main;
+}
+
+bool BookmarkCodec::Decode(BookmarkNode* bb_node,
+ BookmarkNode* other_folder_node,
+ BookmarkNode* mobile_folder_node,
+ int64_t* max_id,
+ const base::Value& value) {
+ ids_.clear();
+ ids_reassigned_ = false;
+ ids_valid_ = true;
+ maximum_id_ = 0;
+ stored_checksum_.clear();
+ InitializeChecksum();
+ bool success = DecodeHelper(bb_node, other_folder_node, mobile_folder_node,
+ value);
+ FinalizeChecksum();
+ // If either the checksums differ or some IDs were missing/not unique,
+ // reassign IDs.
+ if (!ids_valid_ || computed_checksum() != stored_checksum())
+ ReassignIDs(bb_node, other_folder_node, mobile_folder_node);
+ *max_id = maximum_id_ + 1;
+ return success;
+}
+
+base::Value* BookmarkCodec::EncodeNode(const BookmarkNode* node) {
+ base::DictionaryValue* value = new base::DictionaryValue();
+ std::string id = base::Int64ToString(node->id());
+ value->SetString(kIdKey, id);
+ const base::string16& title = node->GetTitle();
+ value->SetString(kNameKey, title);
+ value->SetString(kDateAddedKey,
+ base::Int64ToString(node->date_added().ToInternalValue()));
+ if (node->is_url()) {
+ value->SetString(kTypeKey, kTypeURL);
+ std::string url = node->url().possibly_invalid_spec();
+ value->SetString(kURLKey, url);
+ UpdateChecksumWithUrlNode(id, title, url);
+ } else {
+ value->SetString(kTypeKey, kTypeFolder);
+ value->SetString(
+ kDateModifiedKey,
+ base::Int64ToString(node->date_folder_modified().ToInternalValue()));
+ UpdateChecksumWithFolderNode(id, title);
+
+ base::ListValue* child_values = new base::ListValue();
+ value->Set(kChildrenKey, child_values);
+ for (int i = 0; i < node->child_count(); ++i)
+ child_values->Append(EncodeNode(node->GetChild(i)));
+ }
+ const BookmarkNode::MetaInfoMap* meta_info_map = node->GetMetaInfoMap();
+ if (meta_info_map)
+ value->Set(kMetaInfo, EncodeMetaInfo(*meta_info_map));
+ if (node->sync_transaction_version() !=
+ BookmarkNode::kInvalidSyncTransactionVersion) {
+ value->SetString(kSyncTransactionVersion,
+ base::Int64ToString(node->sync_transaction_version()));
+ }
+ return value;
+}
+
+base::Value* BookmarkCodec::EncodeMetaInfo(
+ const BookmarkNode::MetaInfoMap& meta_info_map) {
+ base::DictionaryValue* meta_info = new base::DictionaryValue;
+ for (BookmarkNode::MetaInfoMap::const_iterator it = meta_info_map.begin();
+ it != meta_info_map.end(); ++it) {
+ meta_info->SetStringWithoutPathExpansion(it->first, it->second);
+ }
+ return meta_info;
+}
+
+bool BookmarkCodec::DecodeHelper(BookmarkNode* bb_node,
+ BookmarkNode* other_folder_node,
+ BookmarkNode* mobile_folder_node,
+ const base::Value& value) {
+ const base::DictionaryValue* d_value = nullptr;
+ if (!value.GetAsDictionary(&d_value))
+ return false; // Unexpected type.
+
+ int version;
+ if (!d_value->GetInteger(kVersionKey, &version) || version != kCurrentVersion)
+ return false; // Unknown version.
+
+ const base::Value* checksum_value;
+ if (d_value->Get(kChecksumKey, &checksum_value)) {
+ if (checksum_value->GetType() != base::Value::TYPE_STRING)
+ return false;
+ if (!checksum_value->GetAsString(&stored_checksum_))
+ return false;
+ }
+
+ const base::Value* roots;
+ if (!d_value->Get(kRootsKey, &roots))
+ return false; // No roots.
+
+ const base::DictionaryValue* roots_d_value = nullptr;
+ if (!roots->GetAsDictionary(&roots_d_value))
+ return false; // Invalid type for roots.
+ const base::Value* root_folder_value;
+ const base::Value* other_folder_value = nullptr;
+ const base::DictionaryValue* root_folder_d_value = nullptr;
+ const base::DictionaryValue* other_folder_d_value = nullptr;
+ if (!roots_d_value->Get(kRootFolderNameKey, &root_folder_value) ||
+ !root_folder_value->GetAsDictionary(&root_folder_d_value) ||
+ !roots_d_value->Get(kOtherBookmarkFolderNameKey, &other_folder_value) ||
+ !other_folder_value->GetAsDictionary(&other_folder_d_value)) {
+ return false; // Invalid type for root folder and/or other
+ // folder.
+ }
+ DecodeNode(*root_folder_d_value, nullptr, bb_node);
+ DecodeNode(*other_folder_d_value, nullptr, other_folder_node);
+
+ // Fail silently if we can't deserialize mobile bookmarks. We can't require
+ // them to exist in order to be backwards-compatible with older versions of
+ // chrome.
+ const base::Value* mobile_folder_value;
+ const base::DictionaryValue* mobile_folder_d_value = nullptr;
+ if (roots_d_value->Get(kMobileBookmarkFolderNameKey, &mobile_folder_value) &&
+ mobile_folder_value->GetAsDictionary(&mobile_folder_d_value)) {
+ DecodeNode(*mobile_folder_d_value, nullptr, mobile_folder_node);
+ } else {
+ // If we didn't find the mobile folder, we're almost guaranteed to have a
+ // duplicate id when we add the mobile folder. Consequently, if we don't
+ // intend to reassign ids in the future (ids_valid_ is still true), then at
+ // least reassign the mobile bookmarks to avoid it colliding with anything
+ // else.
+ if (ids_valid_)
+ ReassignIDsHelper(mobile_folder_node);
+ }
+
+ if (!DecodeMetaInfo(*roots_d_value, &model_meta_info_map_,
+ &model_sync_transaction_version_))
+ return false;
+
+ std::string sync_transaction_version_str;
+ if (roots_d_value->GetString(kSyncTransactionVersion,
+ &sync_transaction_version_str) &&
+ !base::StringToInt64(sync_transaction_version_str,
+ &model_sync_transaction_version_))
+ return false;
+
+ // Need to reset the type as decoding resets the type to FOLDER. Similarly
+ // we need to reset the title as the title is persisted and restored from
+ // the file.
+ bb_node->set_type(BookmarkNode::BOOKMARK_BAR);
+ other_folder_node->set_type(BookmarkNode::OTHER_NODE);
+ mobile_folder_node->set_type(BookmarkNode::MOBILE);
+ bb_node->SetTitle(l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_FOLDER_NAME));
+ other_folder_node->SetTitle(
+ l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_OTHER_FOLDER_NAME));
+ mobile_folder_node->SetTitle(
+ l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_MOBILE_FOLDER_NAME));
+
+ return true;
+}
+
+bool BookmarkCodec::DecodeChildren(const base::ListValue& child_value_list,
+ BookmarkNode* parent) {
+ for (size_t i = 0; i < child_value_list.GetSize(); ++i) {
+ const base::Value* child_value;
+ if (!child_value_list.Get(i, &child_value))
+ return false;
+
+ const base::DictionaryValue* child_d_value = nullptr;
+ if (!child_value->GetAsDictionary(&child_d_value))
+ return false;
+ DecodeNode(*child_d_value, parent, nullptr);
+ }
+ return true;
+}
+
+bool BookmarkCodec::DecodeNode(const base::DictionaryValue& value,
+ BookmarkNode* parent,
+ BookmarkNode* node) {
+ // If no |node| is specified, we'll create one and add it to the |parent|.
+ // Therefore, in that case, |parent| must be non-NULL.
+ if (!node && !parent) {
+ NOTREACHED();
+ return false;
+ }
+
+ std::string id_string;
+ int64_t id = 0;
+ if (ids_valid_) {
+ if (!value.GetString(kIdKey, &id_string) ||
+ !base::StringToInt64(id_string, &id) ||
+ ids_.count(id) != 0) {
+ ids_valid_ = false;
+ } else {
+ ids_.insert(id);
+ }
+ }
+
+ maximum_id_ = std::max(maximum_id_, id);
+
+ base::string16 title;
+ value.GetString(kNameKey, &title);
+
+ std::string date_added_string;
+ if (!value.GetString(kDateAddedKey, &date_added_string))
+ date_added_string = base::Int64ToString(Time::Now().ToInternalValue());
+ int64_t internal_time;
+ base::StringToInt64(date_added_string, &internal_time);
+
+ std::string type_string;
+ if (!value.GetString(kTypeKey, &type_string))
+ return false;
+
+ if (type_string != kTypeURL && type_string != kTypeFolder)
+ return false; // Unknown type.
+
+ if (type_string == kTypeURL) {
+ std::string url_string;
+ if (!value.GetString(kURLKey, &url_string))
+ return false;
+
+ GURL url = GURL(url_string);
+ if (!node && url.is_valid())
+ node = new BookmarkNode(id, url);
+ else
+ return false; // Node invalid.
+
+ if (parent)
+ parent->Add(node, parent->child_count());
+ node->set_type(BookmarkNode::URL);
+ UpdateChecksumWithUrlNode(id_string, title, url_string);
+ } else {
+ std::string last_modified_date;
+ if (!value.GetString(kDateModifiedKey, &last_modified_date))
+ last_modified_date = base::Int64ToString(Time::Now().ToInternalValue());
+
+ const base::Value* child_values;
+ if (!value.Get(kChildrenKey, &child_values))
+ return false;
+
+ if (child_values->GetType() != base::Value::TYPE_LIST)
+ return false;
+
+ if (!node) {
+ node = new BookmarkNode(id, GURL());
+ } else {
+ // If a new node is not created, explicitly assign ID to the existing one.
+ node->set_id(id);
+ }
+
+ node->set_type(BookmarkNode::FOLDER);
+ int64_t internal_time;
+ base::StringToInt64(last_modified_date, &internal_time);
+ node->set_date_folder_modified(Time::FromInternalValue(internal_time));
+
+ if (parent)
+ parent->Add(node, parent->child_count());
+
+ UpdateChecksumWithFolderNode(id_string, title);
+
+ const base::ListValue* child_l_values = nullptr;
+ if (!child_values->GetAsList(&child_l_values))
+ return false;
+ if (!DecodeChildren(*child_l_values, node))
+ return false;
+ }
+
+ node->SetTitle(title);
+ node->set_date_added(Time::FromInternalValue(internal_time));
+
+ int64_t sync_transaction_version = node->sync_transaction_version();
+ BookmarkNode::MetaInfoMap meta_info_map;
+ if (!DecodeMetaInfo(value, &meta_info_map, &sync_transaction_version))
+ return false;
+ node->SetMetaInfoMap(meta_info_map);
+
+ std::string sync_transaction_version_str;
+ if (value.GetString(kSyncTransactionVersion, &sync_transaction_version_str) &&
+ !base::StringToInt64(sync_transaction_version_str,
+ &sync_transaction_version))
+ return false;
+
+ node->set_sync_transaction_version(sync_transaction_version);
+
+ return true;
+}
+
+bool BookmarkCodec::DecodeMetaInfo(const base::DictionaryValue& value,
+ BookmarkNode::MetaInfoMap* meta_info_map,
+ int64_t* sync_transaction_version) {
+ DCHECK(meta_info_map);
+ DCHECK(sync_transaction_version);
+ meta_info_map->clear();
+
+ const base::Value* meta_info;
+ if (!value.Get(kMetaInfo, &meta_info))
+ return true;
+
+ scoped_ptr<base::Value> deserialized_holder;
+
+ // Meta info used to be stored as a serialized dictionary, so attempt to
+ // parse the value as one.
+ if (meta_info->IsType(base::Value::TYPE_STRING)) {
+ std::string meta_info_str;
+ meta_info->GetAsString(&meta_info_str);
+ JSONStringValueDeserializer deserializer(meta_info_str);
+ deserialized_holder = deserializer.Deserialize(nullptr, nullptr);
+ if (!deserialized_holder)
+ return false;
+ meta_info = deserialized_holder.get();
+ }
+ // meta_info is now either the kMetaInfo node, or the deserialized node if it
+ // was stored as a string. Either way it should now be a (possibly nested)
+ // dictionary of meta info values.
+ const base::DictionaryValue* meta_info_dict;
+ if (!meta_info->GetAsDictionary(&meta_info_dict))
+ return false;
+ DecodeMetaInfoHelper(*meta_info_dict, std::string(), meta_info_map);
+
+ // Previously sync transaction version was stored in the meta info field
+ // using this key. If the key is present when decoding, set the sync
+ // transaction version to its value, then delete the field.
+ if (deserialized_holder) {
+ const char kBookmarkTransactionVersionKey[] = "sync.transaction_version";
+ BookmarkNode::MetaInfoMap::iterator it =
+ meta_info_map->find(kBookmarkTransactionVersionKey);
+ if (it != meta_info_map->end()) {
+ base::StringToInt64(it->second, sync_transaction_version);
+ meta_info_map->erase(it);
+ }
+ }
+
+ return true;
+}
+
+void BookmarkCodec::DecodeMetaInfoHelper(
+ const base::DictionaryValue& dict,
+ const std::string& prefix,
+ BookmarkNode::MetaInfoMap* meta_info_map) {
+ for (base::DictionaryValue::Iterator it(dict); !it.IsAtEnd(); it.Advance()) {
+ if (it.value().IsType(base::Value::TYPE_DICTIONARY)) {
+ const base::DictionaryValue* subdict;
+ it.value().GetAsDictionary(&subdict);
+ DecodeMetaInfoHelper(*subdict, prefix + it.key() + ".", meta_info_map);
+ } else if (it.value().IsType(base::Value::TYPE_STRING)) {
+ it.value().GetAsString(&(*meta_info_map)[prefix + it.key()]);
+ }
+ }
+}
+
+void BookmarkCodec::ReassignIDs(BookmarkNode* bb_node,
+ BookmarkNode* other_node,
+ BookmarkNode* mobile_node) {
+ maximum_id_ = 0;
+ ReassignIDsHelper(bb_node);
+ ReassignIDsHelper(other_node);
+ ReassignIDsHelper(mobile_node);
+ ids_reassigned_ = true;
+}
+
+void BookmarkCodec::ReassignIDsHelper(BookmarkNode* node) {
+ DCHECK(node);
+ node->set_id(++maximum_id_);
+ for (int i = 0; i < node->child_count(); ++i)
+ ReassignIDsHelper(node->GetChild(i));
+}
+
+void BookmarkCodec::UpdateChecksum(const std::string& str) {
+ base::MD5Update(&md5_context_, str);
+}
+
+void BookmarkCodec::UpdateChecksum(const base::string16& str) {
+ base::MD5Update(&md5_context_,
+ base::StringPiece(
+ reinterpret_cast<const char*>(str.data()),
+ str.length() * sizeof(str[0])));
+}
+
+void BookmarkCodec::UpdateChecksumWithUrlNode(const std::string& id,
+ const base::string16& title,
+ const std::string& url) {
+ DCHECK(base::IsStringUTF8(url));
+ UpdateChecksum(id);
+ UpdateChecksum(title);
+ UpdateChecksum(kTypeURL);
+ UpdateChecksum(url);
+}
+
+void BookmarkCodec::UpdateChecksumWithFolderNode(const std::string& id,
+ const base::string16& title) {
+ UpdateChecksum(id);
+ UpdateChecksum(title);
+ UpdateChecksum(kTypeFolder);
+}
+
+void BookmarkCodec::InitializeChecksum() {
+ base::MD5Init(&md5_context_);
+}
+
+void BookmarkCodec::FinalizeChecksum() {
+ base::MD5Digest digest;
+ base::MD5Final(&digest, &md5_context_);
+ computed_checksum_ = base::MD5DigestToBase16(digest);
+}
+
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/browser/bookmark_codec.h b/chromium/components/bookmarks/browser/bookmark_codec.h
new file mode 100644
index 00000000000..8b42d3940bb
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_codec.h
@@ -0,0 +1,212 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_CODEC_H_
+#define COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_CODEC_H_
+
+#include <stdint.h>
+#include <set>
+#include <string>
+
+#include "base/macros.h"
+#include "base/md5.h"
+#include "base/strings/string16.h"
+#include "components/bookmarks/browser/bookmark_node.h"
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+class Value;
+}
+
+namespace bookmarks {
+
+class BookmarkModel;
+
+// BookmarkCodec is responsible for encoding and decoding the BookmarkModel
+// into JSON values. The encoded values are written to disk via the
+// BookmarkStorage.
+class BookmarkCodec {
+ public:
+ // Creates an instance of the codec. During decoding, if the IDs in the file
+ // are not unique, we will reassign IDs to make them unique. There are no
+ // guarantees on how the IDs are reassigned or about doing minimal
+ // reassignments to achieve uniqueness.
+ BookmarkCodec();
+ ~BookmarkCodec();
+
+ // Encodes the model to a JSON value. It's up to the caller to delete the
+ // returned object. This is invoked to encode the contents of the bookmark bar
+ // model and is currently a convenience to invoking Encode that takes the
+ // bookmark bar node and other folder node.
+ base::Value* Encode(BookmarkModel* model);
+
+ // Encodes the bookmark bar and other folders returning the JSON value. It's
+ // up to the caller to delete the returned object.
+ base::Value* Encode(const BookmarkNode* bookmark_bar_node,
+ const BookmarkNode* other_folder_node,
+ const BookmarkNode* mobile_folder_node,
+ const BookmarkNode::MetaInfoMap* model_meta_info_map,
+ int64_t sync_transaction_version);
+
+ // Decodes the previously encoded value to the specified nodes as well as
+ // setting |max_node_id| to the greatest node id. Returns true on success,
+ // false otherwise. If there is an error (such as unexpected version) all
+ // children are removed from the bookmark bar and other folder nodes. On exit
+ // |max_node_id| is set to the max id of the nodes.
+ bool Decode(BookmarkNode* bb_node,
+ BookmarkNode* other_folder_node,
+ BookmarkNode* mobile_folder_node,
+ int64_t* max_node_id,
+ const base::Value& value);
+
+ // Returns the checksum computed during last encoding/decoding call.
+ const std::string& computed_checksum() const { return computed_checksum_; }
+
+ // Returns the checksum that's stored in the file. After a call to Encode,
+ // the computed and stored checksums are the same since the computed checksum
+ // is stored to the file. After a call to decode, the computed checksum can
+ // differ from the stored checksum if the file contents were changed by the
+ // user.
+ const std::string& stored_checksum() const { return stored_checksum_; }
+
+ // Return meta info of bookmark model root.
+ const BookmarkNode::MetaInfoMap& model_meta_info_map() const {
+ return model_meta_info_map_;
+ }
+
+ // Return the sync transaction version of the bookmark model root.
+ int64_t model_sync_transaction_version() const {
+ return model_sync_transaction_version_;
+ }
+
+ // Returns whether the IDs were reassigned during decoding. Always returns
+ // false after encoding.
+ bool ids_reassigned() const { return ids_reassigned_; }
+
+ // Names of the various keys written to the Value.
+ static const char kRootsKey[];
+ static const char kRootFolderNameKey[];
+ static const char kOtherBookmarkFolderNameKey[];
+ static const char kMobileBookmarkFolderNameKey[];
+ static const char kVersionKey[];
+ static const char kChecksumKey[];
+ static const char kIdKey[];
+ static const char kTypeKey[];
+ static const char kNameKey[];
+ static const char kDateAddedKey[];
+ static const char kURLKey[];
+ static const char kDateModifiedKey[];
+ static const char kChildrenKey[];
+ static const char kMetaInfo[];
+ static const char kSyncTransactionVersion[];
+
+ // Possible values for kTypeKey.
+ static const char kTypeURL[];
+ static const char kTypeFolder[];
+
+ private:
+ // Encodes node and all its children into a Value object and returns it.
+ // The caller takes ownership of the returned object.
+ base::Value* EncodeNode(const BookmarkNode* node);
+
+ // Encodes the given meta info into a Value object and returns it. The caller
+ // takes ownership of the returned object.
+ base::Value* EncodeMetaInfo(const BookmarkNode::MetaInfoMap& meta_info_map);
+
+ // Helper to perform decoding.
+ bool DecodeHelper(BookmarkNode* bb_node,
+ BookmarkNode* other_folder_node,
+ BookmarkNode* mobile_folder_node,
+ const base::Value& value);
+
+ // Decodes the children of the specified node. Returns true on success.
+ bool DecodeChildren(const base::ListValue& child_value_list,
+ BookmarkNode* parent);
+
+ // Reassigns bookmark IDs for all nodes.
+ void ReassignIDs(BookmarkNode* bb_node,
+ BookmarkNode* other_node,
+ BookmarkNode* mobile_node);
+
+ // Helper to recursively reassign IDs.
+ void ReassignIDsHelper(BookmarkNode* node);
+
+ // Decodes the supplied node from the supplied value. Child nodes are
+ // created appropriately by way of DecodeChildren. If node is NULL a new
+ // node is created and added to parent (parent must then be non-NULL),
+ // otherwise node is used.
+ bool DecodeNode(const base::DictionaryValue& value,
+ BookmarkNode* parent,
+ BookmarkNode* node);
+
+ // Decodes the meta info from the supplied value. If the meta info contains
+ // a "sync.transaction_version" key, the value of that field will be stored
+ // in the sync_transaction_version variable, then deleted. This is for
+ // backward-compatibility reasons.
+ // meta_info_map and sync_transaction_version must not be NULL.
+ bool DecodeMetaInfo(const base::DictionaryValue& value,
+ BookmarkNode::MetaInfoMap* meta_info_map,
+ int64_t* sync_transaction_version);
+
+ // Decodes the meta info from the supplied sub-node dictionary. The values
+ // found will be inserted in meta_info_map with the given prefix added to the
+ // start of their keys.
+ void DecodeMetaInfoHelper(const base::DictionaryValue& dict,
+ const std::string& prefix,
+ BookmarkNode::MetaInfoMap* meta_info_map);
+
+ // Updates the check-sum with the given string.
+ void UpdateChecksum(const std::string& str);
+ void UpdateChecksum(const base::string16& str);
+
+ // Updates the check-sum with the given contents of URL/folder bookmark node.
+ // NOTE: These functions take in individual properties of a bookmark node
+ // instead of taking in a BookmarkNode for efficiency so that we don't convert
+ // various data-types to UTF16 strings multiple times - once for serializing
+ // and once for computing the check-sum.
+ // The url parameter should be a valid UTF8 string.
+ void UpdateChecksumWithUrlNode(const std::string& id,
+ const base::string16& title,
+ const std::string& url);
+ void UpdateChecksumWithFolderNode(const std::string& id,
+ const base::string16& title);
+
+ // Initializes/Finalizes the checksum.
+ void InitializeChecksum();
+ void FinalizeChecksum();
+
+ // Whether or not IDs were reassigned by the codec.
+ bool ids_reassigned_;
+
+ // Whether or not IDs are valid. This is initially true, but set to false
+ // if an id is missing or not unique.
+ bool ids_valid_;
+
+ // Contains the id of each of the nodes found in the file. Used to determine
+ // if we have duplicates.
+ std::set<int64_t> ids_;
+
+ // MD5 context used to compute MD5 hash of all bookmark data.
+ base::MD5Context md5_context_;
+
+ // Checksums.
+ std::string computed_checksum_;
+ std::string stored_checksum_;
+
+ // Maximum ID assigned when decoding data.
+ int64_t maximum_id_;
+
+ // Meta info set on bookmark model root.
+ BookmarkNode::MetaInfoMap model_meta_info_map_;
+
+ // Sync transaction version set on bookmark model root.
+ int64_t model_sync_transaction_version_;
+
+ DISALLOW_COPY_AND_ASSIGN(BookmarkCodec);
+};
+
+} // namespace bookmarks
+
+#endif // COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_CODEC_H_
diff --git a/chromium/components/bookmarks/browser/bookmark_codec_unittest.cc b/chromium/components/bookmarks/browser/bookmark_codec_unittest.cc
new file mode 100644
index 00000000000..51e4c188678
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_codec_unittest.cc
@@ -0,0 +1,478 @@
+// Copyright 2014 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 "components/bookmarks/browser/bookmark_codec.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/test/test_bookmark_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::ASCIIToUTF16;
+
+namespace bookmarks {
+namespace {
+
+const char kUrl1Title[] = "url1";
+const char kUrl1Url[] = "http://www.url1.com";
+const char kUrl2Title[] = "url2";
+const char kUrl2Url[] = "http://www.url2.com";
+const char kUrl3Title[] = "url3";
+const char kUrl3Url[] = "http://www.url3.com";
+const char kUrl4Title[] = "url4";
+const char kUrl4Url[] = "http://www.url4.com";
+const char kFolder1Title[] = "folder1";
+const char kFolder2Title[] = "folder2";
+
+const base::FilePath& GetTestDataDir() {
+ CR_DEFINE_STATIC_LOCAL(base::FilePath, dir, ());
+ if (dir.empty()) {
+ PathService::Get(base::DIR_SOURCE_ROOT, &dir);
+ dir = dir.AppendASCII("components");
+ dir = dir.AppendASCII("test");
+ dir = dir.AppendASCII("data");
+ }
+ return dir;
+}
+
+// Helper to get a mutable bookmark node.
+BookmarkNode* AsMutable(const BookmarkNode* node) {
+ return const_cast<BookmarkNode*>(node);
+}
+
+// Helper to verify the two given bookmark nodes.
+void AssertNodesEqual(const BookmarkNode* expected,
+ const BookmarkNode* actual) {
+ ASSERT_TRUE(expected);
+ ASSERT_TRUE(actual);
+ EXPECT_EQ(expected->id(), actual->id());
+ EXPECT_EQ(expected->GetTitle(), actual->GetTitle());
+ EXPECT_EQ(expected->type(), actual->type());
+ EXPECT_TRUE(expected->date_added() == actual->date_added());
+ if (expected->is_url()) {
+ EXPECT_EQ(expected->url(), actual->url());
+ } else {
+ EXPECT_TRUE(expected->date_folder_modified() ==
+ actual->date_folder_modified());
+ ASSERT_EQ(expected->child_count(), actual->child_count());
+ for (int i = 0; i < expected->child_count(); ++i)
+ AssertNodesEqual(expected->GetChild(i), actual->GetChild(i));
+ }
+}
+
+// Verifies that the two given bookmark models are the same.
+void AssertModelsEqual(BookmarkModel* expected, BookmarkModel* actual) {
+ ASSERT_NO_FATAL_FAILURE(AssertNodesEqual(expected->bookmark_bar_node(),
+ actual->bookmark_bar_node()));
+ ASSERT_NO_FATAL_FAILURE(
+ AssertNodesEqual(expected->other_node(), actual->other_node()));
+ ASSERT_NO_FATAL_FAILURE(
+ AssertNodesEqual(expected->mobile_node(), actual->mobile_node()));
+}
+
+} // namespace
+
+class BookmarkCodecTest : public testing::Test {
+ protected:
+ // Helpers to create bookmark models with different data.
+ BookmarkModel* CreateTestModel1() {
+ scoped_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
+ const BookmarkNode* bookmark_bar = model->bookmark_bar_node();
+ model->AddURL(bookmark_bar, 0, ASCIIToUTF16(kUrl1Title), GURL(kUrl1Url));
+ return model.release();
+ }
+ BookmarkModel* CreateTestModel2() {
+ scoped_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
+ const BookmarkNode* bookmark_bar = model->bookmark_bar_node();
+ model->AddURL(bookmark_bar, 0, ASCIIToUTF16(kUrl1Title), GURL(kUrl1Url));
+ model->AddURL(bookmark_bar, 1, ASCIIToUTF16(kUrl2Title), GURL(kUrl2Url));
+ return model.release();
+ }
+ BookmarkModel* CreateTestModel3() {
+ scoped_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
+ const BookmarkNode* bookmark_bar = model->bookmark_bar_node();
+ model->AddURL(bookmark_bar, 0, ASCIIToUTF16(kUrl1Title), GURL(kUrl1Url));
+ const BookmarkNode* folder1 =
+ model->AddFolder(bookmark_bar, 1, ASCIIToUTF16(kFolder1Title));
+ model->AddURL(folder1, 0, ASCIIToUTF16(kUrl2Title), GURL(kUrl2Url));
+ return model.release();
+ }
+
+ void GetBookmarksBarChildValue(base::Value* value,
+ size_t index,
+ base::DictionaryValue** result_value) {
+ ASSERT_EQ(base::Value::TYPE_DICTIONARY, value->GetType());
+
+ base::DictionaryValue* d_value = nullptr;
+ value->GetAsDictionary(&d_value);
+ base::Value* roots;
+ ASSERT_TRUE(d_value->Get(BookmarkCodec::kRootsKey, &roots));
+ ASSERT_EQ(base::Value::TYPE_DICTIONARY, roots->GetType());
+
+ base::DictionaryValue* roots_d_value = nullptr;
+ roots->GetAsDictionary(&roots_d_value);
+ base::Value* bb_value;
+ ASSERT_TRUE(
+ roots_d_value->Get(BookmarkCodec::kRootFolderNameKey, &bb_value));
+ ASSERT_EQ(base::Value::TYPE_DICTIONARY, bb_value->GetType());
+
+ base::DictionaryValue* bb_d_value = nullptr;
+ bb_value->GetAsDictionary(&bb_d_value);
+ base::Value* bb_children_value;
+ ASSERT_TRUE(
+ bb_d_value->Get(BookmarkCodec::kChildrenKey, &bb_children_value));
+ ASSERT_EQ(base::Value::TYPE_LIST, bb_children_value->GetType());
+
+ base::ListValue* bb_children_l_value = nullptr;
+ bb_children_value->GetAsList(&bb_children_l_value);
+ base::Value* child_value;
+ ASSERT_TRUE(bb_children_l_value->Get(index, &child_value));
+ ASSERT_EQ(base::Value::TYPE_DICTIONARY, child_value->GetType());
+
+ child_value->GetAsDictionary(result_value);
+ }
+
+ base::Value* EncodeHelper(BookmarkModel* model, std::string* checksum) {
+ BookmarkCodec encoder;
+ // Computed and stored checksums should be empty.
+ EXPECT_EQ("", encoder.computed_checksum());
+ EXPECT_EQ("", encoder.stored_checksum());
+
+ scoped_ptr<base::Value> value(encoder.Encode(model));
+ const std::string& computed_checksum = encoder.computed_checksum();
+ const std::string& stored_checksum = encoder.stored_checksum();
+
+ // Computed and stored checksums should not be empty and should be equal.
+ EXPECT_FALSE(computed_checksum.empty());
+ EXPECT_FALSE(stored_checksum.empty());
+ EXPECT_EQ(computed_checksum, stored_checksum);
+
+ *checksum = computed_checksum;
+ return value.release();
+ }
+
+ bool Decode(BookmarkCodec* codec,
+ BookmarkModel* model,
+ const base::Value& value) {
+ int64_t max_id;
+ bool result = codec->Decode(AsMutable(model->bookmark_bar_node()),
+ AsMutable(model->other_node()),
+ AsMutable(model->mobile_node()),
+ &max_id,
+ value);
+ model->set_next_node_id(max_id);
+ AsMutable(model->root_node())->SetMetaInfoMap(codec->model_meta_info_map());
+ AsMutable(model->root_node())
+ ->set_sync_transaction_version(codec->model_sync_transaction_version());
+
+ return result;
+ }
+
+ BookmarkModel* DecodeHelper(const base::Value& value,
+ const std::string& expected_stored_checksum,
+ std::string* computed_checksum,
+ bool expected_changes) {
+ BookmarkCodec decoder;
+ // Computed and stored checksums should be empty.
+ EXPECT_EQ("", decoder.computed_checksum());
+ EXPECT_EQ("", decoder.stored_checksum());
+
+ scoped_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
+ EXPECT_TRUE(Decode(&decoder, model.get(), value));
+
+ *computed_checksum = decoder.computed_checksum();
+ const std::string& stored_checksum = decoder.stored_checksum();
+
+ // Computed and stored checksums should not be empty.
+ EXPECT_FALSE(computed_checksum->empty());
+ EXPECT_FALSE(stored_checksum.empty());
+
+ // Stored checksum should be as expected.
+ EXPECT_EQ(expected_stored_checksum, stored_checksum);
+
+ // The two checksums should be equal if expected_changes is true; otherwise
+ // they should be different.
+ if (expected_changes)
+ EXPECT_NE(*computed_checksum, stored_checksum);
+ else
+ EXPECT_EQ(*computed_checksum, stored_checksum);
+
+ return model.release();
+ }
+
+ void CheckIDs(const BookmarkNode* node, std::set<int64_t>* assigned_ids) {
+ DCHECK(node);
+ int64_t node_id = node->id();
+ EXPECT_TRUE(assigned_ids->find(node_id) == assigned_ids->end());
+ assigned_ids->insert(node_id);
+ for (int i = 0; i < node->child_count(); ++i)
+ CheckIDs(node->GetChild(i), assigned_ids);
+ }
+
+ void ExpectIDsUnique(BookmarkModel* model) {
+ std::set<int64_t> assigned_ids;
+ CheckIDs(model->bookmark_bar_node(), &assigned_ids);
+ CheckIDs(model->other_node(), &assigned_ids);
+ CheckIDs(model->mobile_node(), &assigned_ids);
+ }
+};
+
+TEST_F(BookmarkCodecTest, ChecksumEncodeDecodeTest) {
+ scoped_ptr<BookmarkModel> model_to_encode(CreateTestModel1());
+ std::string enc_checksum;
+ scoped_ptr<base::Value> value(
+ EncodeHelper(model_to_encode.get(), &enc_checksum));
+
+ EXPECT_TRUE(value.get() != NULL);
+
+ std::string dec_checksum;
+ scoped_ptr<BookmarkModel> decoded_model(
+ DecodeHelper(*value.get(), enc_checksum, &dec_checksum, false));
+}
+
+TEST_F(BookmarkCodecTest, ChecksumEncodeIdenticalModelsTest) {
+ // Encode two identical models and make sure the check-sums are same as long
+ // as the data is the same.
+ scoped_ptr<BookmarkModel> model1(CreateTestModel1());
+ std::string enc_checksum1;
+ scoped_ptr<base::Value> value1(EncodeHelper(model1.get(), &enc_checksum1));
+ EXPECT_TRUE(value1.get() != NULL);
+
+ scoped_ptr<BookmarkModel> model2(CreateTestModel1());
+ std::string enc_checksum2;
+ scoped_ptr<base::Value> value2(EncodeHelper(model2.get(), &enc_checksum2));
+ EXPECT_TRUE(value2.get() != NULL);
+
+ ASSERT_EQ(enc_checksum1, enc_checksum2);
+}
+
+TEST_F(BookmarkCodecTest, ChecksumManualEditTest) {
+ scoped_ptr<BookmarkModel> model_to_encode(CreateTestModel1());
+ std::string enc_checksum;
+ scoped_ptr<base::Value> value(
+ EncodeHelper(model_to_encode.get(), &enc_checksum));
+
+ EXPECT_TRUE(value.get() != NULL);
+
+ // Change something in the encoded value before decoding it.
+ base::DictionaryValue* child1_value;
+ GetBookmarksBarChildValue(value.get(), 0, &child1_value);
+ std::string title;
+ ASSERT_TRUE(child1_value->GetString(BookmarkCodec::kNameKey, &title));
+ child1_value->SetString(BookmarkCodec::kNameKey, title + "1");
+
+ std::string dec_checksum;
+ scoped_ptr<BookmarkModel> decoded_model1(
+ DecodeHelper(*value.get(), enc_checksum, &dec_checksum, true));
+
+ // Undo the change and make sure the checksum is same as original.
+ child1_value->SetString(BookmarkCodec::kNameKey, title);
+ scoped_ptr<BookmarkModel> decoded_model2(
+ DecodeHelper(*value.get(), enc_checksum, &dec_checksum, false));
+}
+
+TEST_F(BookmarkCodecTest, ChecksumManualEditIDsTest) {
+ scoped_ptr<BookmarkModel> model_to_encode(CreateTestModel3());
+
+ // The test depends on existence of multiple children under bookmark bar, so
+ // make sure that's the case.
+ int bb_child_count = model_to_encode->bookmark_bar_node()->child_count();
+ ASSERT_GT(bb_child_count, 1);
+
+ std::string enc_checksum;
+ scoped_ptr<base::Value> value(
+ EncodeHelper(model_to_encode.get(), &enc_checksum));
+
+ EXPECT_TRUE(value.get() != NULL);
+
+ // Change IDs for all children of bookmark bar to be 1.
+ base::DictionaryValue* child_value;
+ for (int i = 0; i < bb_child_count; ++i) {
+ GetBookmarksBarChildValue(value.get(), i, &child_value);
+ std::string id;
+ ASSERT_TRUE(child_value->GetString(BookmarkCodec::kIdKey, &id));
+ child_value->SetString(BookmarkCodec::kIdKey, "1");
+ }
+
+ std::string dec_checksum;
+ scoped_ptr<BookmarkModel> decoded_model(
+ DecodeHelper(*value.get(), enc_checksum, &dec_checksum, true));
+
+ ExpectIDsUnique(decoded_model.get());
+
+ // add a few extra nodes to bookmark model and make sure IDs are still uniuqe.
+ const BookmarkNode* bb_node = decoded_model->bookmark_bar_node();
+ decoded_model->AddURL(
+ bb_node, 0, ASCIIToUTF16("new url1"), GURL("http://newurl1.com"));
+ decoded_model->AddURL(
+ bb_node, 0, ASCIIToUTF16("new url2"), GURL("http://newurl2.com"));
+
+ ExpectIDsUnique(decoded_model.get());
+}
+
+TEST_F(BookmarkCodecTest, PersistIDsTest) {
+ scoped_ptr<BookmarkModel> model_to_encode(CreateTestModel3());
+ BookmarkCodec encoder;
+ scoped_ptr<base::Value> model_value(encoder.Encode(model_to_encode.get()));
+
+ scoped_ptr<BookmarkModel> decoded_model(TestBookmarkClient::CreateModel());
+ BookmarkCodec decoder;
+ ASSERT_TRUE(Decode(&decoder, decoded_model.get(), *model_value.get()));
+ ASSERT_NO_FATAL_FAILURE(
+ AssertModelsEqual(model_to_encode.get(), decoded_model.get()));
+
+ // Add a couple of more items to the decoded bookmark model and make sure
+ // ID persistence is working properly.
+ const BookmarkNode* bookmark_bar = decoded_model->bookmark_bar_node();
+ decoded_model->AddURL(bookmark_bar,
+ bookmark_bar->child_count(),
+ ASCIIToUTF16(kUrl3Title),
+ GURL(kUrl3Url));
+ const BookmarkNode* folder2_node = decoded_model->AddFolder(
+ bookmark_bar, bookmark_bar->child_count(), ASCIIToUTF16(kFolder2Title));
+ decoded_model->AddURL(
+ folder2_node, 0, ASCIIToUTF16(kUrl4Title), GURL(kUrl4Url));
+
+ BookmarkCodec encoder2;
+ scoped_ptr<base::Value> model_value2(encoder2.Encode(decoded_model.get()));
+
+ scoped_ptr<BookmarkModel> decoded_model2(TestBookmarkClient::CreateModel());
+ BookmarkCodec decoder2;
+ ASSERT_TRUE(Decode(&decoder2, decoded_model2.get(), *model_value2.get()));
+ ASSERT_NO_FATAL_FAILURE(
+ AssertModelsEqual(decoded_model.get(), decoded_model2.get()));
+}
+
+TEST_F(BookmarkCodecTest, CanDecodeModelWithoutMobileBookmarks) {
+ base::FilePath test_file =
+ GetTestDataDir().AppendASCII("bookmarks/model_without_sync.json");
+ ASSERT_TRUE(base::PathExists(test_file));
+
+ JSONFileValueDeserializer deserializer(test_file);
+ scoped_ptr<base::Value> root = deserializer.Deserialize(NULL, NULL);
+
+ scoped_ptr<BookmarkModel> decoded_model(TestBookmarkClient::CreateModel());
+ BookmarkCodec decoder;
+ ASSERT_TRUE(Decode(&decoder, decoded_model.get(), *root.get()));
+ ExpectIDsUnique(decoded_model.get());
+
+ const BookmarkNode* bbn = decoded_model->bookmark_bar_node();
+ ASSERT_EQ(1, bbn->child_count());
+
+ const BookmarkNode* child = bbn->GetChild(0);
+ EXPECT_EQ(BookmarkNode::FOLDER, child->type());
+ EXPECT_EQ(ASCIIToUTF16("Folder A"), child->GetTitle());
+ ASSERT_EQ(1, child->child_count());
+
+ child = child->GetChild(0);
+ EXPECT_EQ(BookmarkNode::URL, child->type());
+ EXPECT_EQ(ASCIIToUTF16("Bookmark Manager"), child->GetTitle());
+
+ const BookmarkNode* other = decoded_model->other_node();
+ ASSERT_EQ(1, other->child_count());
+
+ child = other->GetChild(0);
+ EXPECT_EQ(BookmarkNode::FOLDER, child->type());
+ EXPECT_EQ(ASCIIToUTF16("Folder B"), child->GetTitle());
+ ASSERT_EQ(1, child->child_count());
+
+ child = child->GetChild(0);
+ EXPECT_EQ(BookmarkNode::URL, child->type());
+ EXPECT_EQ(ASCIIToUTF16("Get started with Google Chrome"), child->GetTitle());
+
+ ASSERT_TRUE(decoded_model->mobile_node() != NULL);
+}
+
+TEST_F(BookmarkCodecTest, EncodeAndDecodeMetaInfo) {
+ // Add meta info and encode.
+ scoped_ptr<BookmarkModel> model(CreateTestModel1());
+ model->SetNodeMetaInfo(model->root_node(), "model_info", "value1");
+ model->SetNodeMetaInfo(
+ model->bookmark_bar_node()->GetChild(0), "node_info", "value2");
+ std::string checksum;
+ scoped_ptr<base::Value> value(EncodeHelper(model.get(), &checksum));
+ ASSERT_TRUE(value.get() != NULL);
+
+ // Decode and check for meta info.
+ model.reset(DecodeHelper(*value, checksum, &checksum, false));
+ std::string meta_value;
+ EXPECT_TRUE(model->root_node()->GetMetaInfo("model_info", &meta_value));
+ EXPECT_EQ("value1", meta_value);
+ EXPECT_FALSE(model->root_node()->GetMetaInfo("other_key", &meta_value));
+ const BookmarkNode* bbn = model->bookmark_bar_node();
+ ASSERT_EQ(1, bbn->child_count());
+ const BookmarkNode* child = bbn->GetChild(0);
+ EXPECT_TRUE(child->GetMetaInfo("node_info", &meta_value));
+ EXPECT_EQ("value2", meta_value);
+ EXPECT_FALSE(child->GetMetaInfo("other_key", &meta_value));
+}
+
+TEST_F(BookmarkCodecTest, EncodeAndDecodeSyncTransactionVersion) {
+ // Add sync transaction version and encode.
+ scoped_ptr<BookmarkModel> model(CreateTestModel2());
+ model->SetNodeSyncTransactionVersion(model->root_node(), 1);
+ const BookmarkNode* bbn = model->bookmark_bar_node();
+ model->SetNodeSyncTransactionVersion(bbn->GetChild(1), 42);
+
+ std::string checksum;
+ scoped_ptr<base::Value> value(EncodeHelper(model.get(), &checksum));
+ ASSERT_TRUE(value.get() != NULL);
+
+ // Decode and verify.
+ model.reset(DecodeHelper(*value, checksum, &checksum, false));
+ EXPECT_EQ(1, model->root_node()->sync_transaction_version());
+ bbn = model->bookmark_bar_node();
+ EXPECT_EQ(42, bbn->GetChild(1)->sync_transaction_version());
+ EXPECT_EQ(BookmarkNode::kInvalidSyncTransactionVersion,
+ bbn->GetChild(0)->sync_transaction_version());
+}
+
+// Verifies that we can still decode the old codec format after changing the
+// way meta info is stored.
+TEST_F(BookmarkCodecTest, CanDecodeMetaInfoAsString) {
+ base::FilePath test_file =
+ GetTestDataDir().AppendASCII("bookmarks/meta_info_as_string.json");
+ ASSERT_TRUE(base::PathExists(test_file));
+
+ JSONFileValueDeserializer deserializer(test_file);
+ scoped_ptr<base::Value> root = deserializer.Deserialize(NULL, NULL);
+
+ scoped_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
+ BookmarkCodec decoder;
+ ASSERT_TRUE(Decode(&decoder, model.get(), *root.get()));
+
+ EXPECT_EQ(1, model->root_node()->sync_transaction_version());
+ const BookmarkNode* bbn = model->bookmark_bar_node();
+ EXPECT_EQ(BookmarkNode::kInvalidSyncTransactionVersion,
+ bbn->GetChild(0)->sync_transaction_version());
+ EXPECT_EQ(42, bbn->GetChild(1)->sync_transaction_version());
+
+ const char kSyncTransactionVersionKey[] = "sync.transaction_version";
+ const char kNormalKey[] = "key";
+ const char kNestedKey[] = "nested.key";
+ std::string meta_value;
+ EXPECT_FALSE(
+ model->root_node()->GetMetaInfo(kSyncTransactionVersionKey, &meta_value));
+ EXPECT_FALSE(
+ bbn->GetChild(1)->GetMetaInfo(kSyncTransactionVersionKey, &meta_value));
+ EXPECT_TRUE(bbn->GetChild(0)->GetMetaInfo(kNormalKey, &meta_value));
+ EXPECT_EQ("value", meta_value);
+ EXPECT_TRUE(bbn->GetChild(1)->GetMetaInfo(kNormalKey, &meta_value));
+ EXPECT_EQ("value2", meta_value);
+ EXPECT_TRUE(bbn->GetChild(0)->GetMetaInfo(kNestedKey, &meta_value));
+ EXPECT_EQ("value3", meta_value);
+}
+
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/browser/bookmark_expanded_state_tracker.cc b/chromium/components/bookmarks/browser/bookmark_expanded_state_tracker.cc
new file mode 100644
index 00000000000..427f2d3c446
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_expanded_state_tracker.cc
@@ -0,0 +1,116 @@
+// Copyright 2014 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 "components/bookmarks/browser/bookmark_expanded_state_tracker.h"
+
+#include <stdint.h>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/browser/bookmark_utils.h"
+#include "components/bookmarks/common/bookmark_pref_names.h"
+#include "components/prefs/pref_service.h"
+
+namespace bookmarks {
+
+BookmarkExpandedStateTracker::BookmarkExpandedStateTracker(
+ BookmarkModel* bookmark_model,
+ PrefService* pref_service)
+ : bookmark_model_(bookmark_model),
+ pref_service_(pref_service) {
+ bookmark_model->AddObserver(this);
+}
+
+BookmarkExpandedStateTracker::~BookmarkExpandedStateTracker() {
+}
+
+void BookmarkExpandedStateTracker::SetExpandedNodes(const Nodes& nodes) {
+ UpdatePrefs(nodes);
+}
+
+BookmarkExpandedStateTracker::Nodes
+BookmarkExpandedStateTracker::GetExpandedNodes() {
+ Nodes nodes;
+ if (!bookmark_model_->loaded())
+ return nodes;
+
+ if (!pref_service_)
+ return nodes;
+
+ const base::ListValue* value =
+ pref_service_->GetList(prefs::kBookmarkEditorExpandedNodes);
+ if (!value)
+ return nodes;
+
+ bool changed = false;
+ for (base::ListValue::const_iterator i = value->begin();
+ i != value->end(); ++i) {
+ std::string value;
+ int64_t node_id;
+ const BookmarkNode* node;
+ if ((*i)->GetAsString(&value) && base::StringToInt64(value, &node_id) &&
+ (node = GetBookmarkNodeByID(bookmark_model_, node_id)) != NULL &&
+ node->is_folder()) {
+ nodes.insert(node);
+ } else {
+ changed = true;
+ }
+ }
+ if (changed)
+ UpdatePrefs(nodes);
+ return nodes;
+}
+
+void BookmarkExpandedStateTracker::BookmarkModelLoaded(BookmarkModel* model,
+ bool ids_reassigned) {
+ if (ids_reassigned) {
+ // If the ids change we can't trust the value in preferences and need to
+ // reset it.
+ SetExpandedNodes(Nodes());
+ }
+}
+
+void BookmarkExpandedStateTracker::BookmarkModelChanged() {
+}
+
+void BookmarkExpandedStateTracker::BookmarkModelBeingDeleted(
+ BookmarkModel* model) {
+ model->RemoveObserver(this);
+}
+
+void BookmarkExpandedStateTracker::BookmarkNodeRemoved(
+ BookmarkModel* model,
+ const BookmarkNode* parent,
+ int old_index,
+ const BookmarkNode* node,
+ const std::set<GURL>& removed_urls) {
+ if (!node->is_folder())
+ return; // Only care about folders.
+
+ // Ask for the nodes again, which removes any nodes that were deleted.
+ GetExpandedNodes();
+}
+
+void BookmarkExpandedStateTracker::BookmarkAllUserNodesRemoved(
+ BookmarkModel* model,
+ const std::set<GURL>& removed_urls) {
+ // Ask for the nodes again, which removes any nodes that were deleted.
+ GetExpandedNodes();
+}
+
+void BookmarkExpandedStateTracker::UpdatePrefs(const Nodes& nodes) {
+ if (!pref_service_)
+ return;
+
+ base::ListValue values;
+ for (Nodes::const_iterator i = nodes.begin(); i != nodes.end(); ++i) {
+ values.Set(values.GetSize(),
+ new base::StringValue(base::Int64ToString((*i)->id())));
+ }
+
+ pref_service_->Set(prefs::kBookmarkEditorExpandedNodes, values);
+}
+
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/browser/bookmark_expanded_state_tracker.h b/chromium/components/bookmarks/browser/bookmark_expanded_state_tracker.h
new file mode 100644
index 00000000000..92f8d5f5f3f
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_expanded_state_tracker.h
@@ -0,0 +1,60 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_EXPANDED_STATE_TRACKER_H_
+#define COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_EXPANDED_STATE_TRACKER_H_
+
+#include <set>
+
+#include "base/macros.h"
+#include "components/bookmarks/browser/base_bookmark_model_observer.h"
+
+class PrefService;
+
+namespace bookmarks {
+
+class BookmarkModel;
+class BookmarkNode;
+
+// BookmarkExpandedStateTracker is used to track a set of expanded nodes. The
+// nodes are persisted in preferences. If an expanded node is removed from the
+// model BookmarkExpandedStateTracker removes the node.
+class BookmarkExpandedStateTracker : public BaseBookmarkModelObserver {
+ public:
+ typedef std::set<const BookmarkNode*> Nodes;
+
+ BookmarkExpandedStateTracker(BookmarkModel* bookmark_model,
+ PrefService* pref_service);
+ ~BookmarkExpandedStateTracker() override;
+
+ // The set of expanded nodes.
+ void SetExpandedNodes(const Nodes& nodes);
+ Nodes GetExpandedNodes();
+
+ private:
+ // BaseBookmarkModelObserver:
+ void BookmarkModelLoaded(BookmarkModel* model, bool ids_reassigned) override;
+ void BookmarkModelChanged() override;
+ void BookmarkModelBeingDeleted(BookmarkModel* model) override;
+ void BookmarkNodeRemoved(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int old_index,
+ const BookmarkNode* node,
+ const std::set<GURL>& removed_urls) override;
+ void BookmarkAllUserNodesRemoved(BookmarkModel* model,
+ const std::set<GURL>& removed_urls) override;
+
+ // Updates the value for |prefs::kBookmarkEditorExpandedNodes| from
+ // GetExpandedNodes().
+ void UpdatePrefs(const Nodes& nodes);
+
+ BookmarkModel* bookmark_model_;
+ PrefService* pref_service_;
+
+ DISALLOW_COPY_AND_ASSIGN(BookmarkExpandedStateTracker);
+};
+
+} // namespace bookmarks
+
+#endif // COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_EXPANDED_STATE_TRACKER_H_
diff --git a/chromium/components/bookmarks/browser/bookmark_expanded_state_tracker_unittest.cc b/chromium/components/bookmarks/browser/bookmark_expanded_state_tracker_unittest.cc
new file mode 100644
index 00000000000..76c0160f3e6
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_expanded_state_tracker_unittest.cc
@@ -0,0 +1,102 @@
+// Copyright 2014 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 "components/bookmarks/browser/bookmark_expanded_state_tracker.h"
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/thread_task_runner_handle.h"
+#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/common/bookmark_pref_names.h"
+#include "components/bookmarks/test/bookmark_test_helpers.h"
+#include "components/bookmarks/test/test_bookmark_client.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace bookmarks {
+
+class BookmarkExpandedStateTrackerTest : public testing::Test {
+ public:
+ BookmarkExpandedStateTrackerTest();
+ ~BookmarkExpandedStateTrackerTest() override;
+
+ protected:
+ // testing::Test:
+ void SetUp() override;
+ void TearDown() override;
+
+ base::MessageLoop message_loop_;
+ TestingPrefServiceSimple prefs_;
+ scoped_ptr<BookmarkModel> model_;
+
+ DISALLOW_COPY_AND_ASSIGN(BookmarkExpandedStateTrackerTest);
+};
+
+BookmarkExpandedStateTrackerTest::BookmarkExpandedStateTrackerTest() {}
+
+BookmarkExpandedStateTrackerTest::~BookmarkExpandedStateTrackerTest() {}
+
+void BookmarkExpandedStateTrackerTest::SetUp() {
+ prefs_.registry()->RegisterListPref(prefs::kBookmarkEditorExpandedNodes,
+ new base::ListValue);
+ prefs_.registry()->RegisterListPref(prefs::kManagedBookmarks);
+ model_.reset(new BookmarkModel(make_scoped_ptr(new TestBookmarkClient())));
+ model_->Load(&prefs_, base::FilePath(),
+ base::ThreadTaskRunnerHandle::Get(),
+ base::ThreadTaskRunnerHandle::Get());
+ test::WaitForBookmarkModelToLoad(model_.get());
+}
+
+void BookmarkExpandedStateTrackerTest::TearDown() {
+ model_.reset();
+ message_loop_.RunUntilIdle();
+}
+
+// Various assertions for SetExpandedNodes.
+TEST_F(BookmarkExpandedStateTrackerTest, SetExpandedNodes) {
+ BookmarkExpandedStateTracker* tracker = model_->expanded_state_tracker();
+
+ // Should start out initially empty.
+ EXPECT_TRUE(tracker->GetExpandedNodes().empty());
+
+ BookmarkExpandedStateTracker::Nodes nodes;
+ nodes.insert(model_->bookmark_bar_node());
+ tracker->SetExpandedNodes(nodes);
+ EXPECT_EQ(nodes, tracker->GetExpandedNodes());
+
+ // Add a folder and mark it expanded.
+ const BookmarkNode* n1 = model_->AddFolder(
+ model_->bookmark_bar_node(), 0, base::ASCIIToUTF16("x"));
+ nodes.insert(n1);
+ tracker->SetExpandedNodes(nodes);
+ EXPECT_EQ(nodes, tracker->GetExpandedNodes());
+
+ // Remove the folder, which should remove it from the list of expanded nodes.
+ model_->Remove(model_->bookmark_bar_node()->GetChild(0));
+ nodes.erase(n1);
+ n1 = NULL;
+ EXPECT_EQ(nodes, tracker->GetExpandedNodes());
+}
+
+TEST_F(BookmarkExpandedStateTrackerTest, RemoveAllUserBookmarks) {
+ BookmarkExpandedStateTracker* tracker = model_->expanded_state_tracker();
+
+ // Add a folder and mark it expanded.
+ const BookmarkNode* n1 = model_->AddFolder(
+ model_->bookmark_bar_node(), 0, base::ASCIIToUTF16("x"));
+ BookmarkExpandedStateTracker::Nodes nodes;
+ nodes.insert(n1);
+ tracker->SetExpandedNodes(nodes);
+ // Verify that the node is present.
+ EXPECT_EQ(nodes, tracker->GetExpandedNodes());
+ // Call remove all.
+ model_->RemoveAllUserBookmarks();
+ // Verify node is not present.
+ EXPECT_TRUE(tracker->GetExpandedNodes().empty());
+}
+
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/browser/bookmark_index.cc b/chromium/components/bookmarks/browser/bookmark_index.cc
new file mode 100644
index 00000000000..f054022abe4
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_index.cc
@@ -0,0 +1,293 @@
+// Copyright 2014 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 "components/bookmarks/browser/bookmark_index.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+#include <functional>
+#include <iterator>
+#include <list>
+
+#include "base/i18n/case_conversion.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/strings/utf_offset_string_conversions.h"
+#include "build/build_config.h"
+#include "components/bookmarks/browser/bookmark_client.h"
+#include "components/bookmarks/browser/bookmark_match.h"
+#include "components/bookmarks/browser/bookmark_node.h"
+#include "components/bookmarks/browser/bookmark_utils.h"
+#include "components/query_parser/snippet.h"
+#include "third_party/icu/source/common/unicode/normalizer2.h"
+#include "third_party/icu/source/common/unicode/utypes.h"
+
+namespace bookmarks {
+
+typedef BookmarkClient::NodeTypedCountPair NodeTypedCountPair;
+typedef BookmarkClient::NodeTypedCountPairs NodeTypedCountPairs;
+
+namespace {
+
+// Returns a normalized version of the UTF16 string |text|. If it fails to
+// normalize the string, returns |text| itself as a best-effort.
+base::string16 Normalize(const base::string16& text) {
+ UErrorCode status = U_ZERO_ERROR;
+ const icu::Normalizer2* normalizer2 =
+ icu::Normalizer2::getInstance(nullptr, "nfkc", UNORM2_COMPOSE, status);
+ if (U_FAILURE(status)) {
+ // Log and crash right away to capture the error code in the crash report.
+ LOG(FATAL) << "failed to create a normalizer: " << u_errorName(status);
+ }
+ icu::UnicodeString unicode_text(
+ text.data(), static_cast<int32_t>(text.length()));
+ icu::UnicodeString unicode_normalized_text;
+ normalizer2->normalize(unicode_text, unicode_normalized_text, status);
+ if (U_FAILURE(status)) {
+ // This should not happen. Log the error and fall back.
+ LOG(ERROR) << "normalization failed: " << u_errorName(status);
+ return text;
+ }
+ return base::string16(unicode_normalized_text.getBuffer(),
+ unicode_normalized_text.length());
+}
+
+// Sort functor for NodeTypedCountPairs. We sort in decreasing order of typed
+// count so that the best matches will always be added to the results.
+struct NodeTypedCountPairSortFunctor {
+ bool operator()(const NodeTypedCountPair& a,
+ const NodeTypedCountPair& b) const {
+ return a.second > b.second;
+ }
+};
+
+// Extract the const Node* stored in a BookmarkClient::NodeTypedCountPair.
+struct NodeTypedCountPairExtractNodeFunctor {
+ const BookmarkNode* operator()(const NodeTypedCountPair& pair) const {
+ return pair.first;
+ }
+};
+
+} // namespace
+
+BookmarkIndex::BookmarkIndex(BookmarkClient* client)
+ : client_(client) {
+ DCHECK(client_);
+}
+
+BookmarkIndex::~BookmarkIndex() {
+}
+
+void BookmarkIndex::Add(const BookmarkNode* node) {
+ if (!node->is_url())
+ return;
+ std::vector<base::string16> terms =
+ ExtractQueryWords(Normalize(node->GetTitle()));
+ for (size_t i = 0; i < terms.size(); ++i)
+ RegisterNode(terms[i], node);
+ terms =
+ ExtractQueryWords(CleanUpUrlForMatching(node->url(), nullptr));
+ for (size_t i = 0; i < terms.size(); ++i)
+ RegisterNode(terms[i], node);
+}
+
+void BookmarkIndex::Remove(const BookmarkNode* node) {
+ if (!node->is_url())
+ return;
+
+ std::vector<base::string16> terms =
+ ExtractQueryWords(Normalize(node->GetTitle()));
+ for (size_t i = 0; i < terms.size(); ++i)
+ UnregisterNode(terms[i], node);
+ terms =
+ ExtractQueryWords(CleanUpUrlForMatching(node->url(), nullptr));
+ for (size_t i = 0; i < terms.size(); ++i)
+ UnregisterNode(terms[i], node);
+}
+
+void BookmarkIndex::GetBookmarksMatching(
+ const base::string16& input_query,
+ size_t max_count,
+ query_parser::MatchingAlgorithm matching_algorithm,
+ std::vector<BookmarkMatch>* results) {
+ const base::string16 query = Normalize(input_query);
+ std::vector<base::string16> terms = ExtractQueryWords(query);
+ if (terms.empty())
+ return;
+
+ NodeSet matches;
+ for (size_t i = 0; i < terms.size(); ++i) {
+ if (!GetBookmarksMatchingTerm(
+ terms[i], i == 0, matching_algorithm, &matches)) {
+ return;
+ }
+ }
+
+ Nodes sorted_nodes;
+ SortMatches(matches, &sorted_nodes);
+
+ // We use a QueryParser to fill in match positions for us. It's not the most
+ // efficient way to go about this, but by the time we get here we know what
+ // matches and so this shouldn't be performance critical.
+ query_parser::QueryParser parser;
+ ScopedVector<query_parser::QueryNode> query_nodes;
+ parser.ParseQueryNodes(query, matching_algorithm, &query_nodes.get());
+
+ // The highest typed counts should be at the beginning of the results vector
+ // so that the best matches will always be included in the results. The loop
+ // that calculates result relevance in HistoryContentsProvider::ConvertResults
+ // will run backwards to assure higher relevance will be attributed to the
+ // best matches.
+ for (Nodes::const_iterator i = sorted_nodes.begin();
+ i != sorted_nodes.end() && results->size() < max_count;
+ ++i)
+ AddMatchToResults(*i, &parser, query_nodes.get(), results);
+}
+
+void BookmarkIndex::SortMatches(const NodeSet& matches,
+ Nodes* sorted_nodes) const {
+ sorted_nodes->reserve(matches.size());
+ if (client_->SupportsTypedCountForNodes()) {
+ NodeTypedCountPairs node_typed_counts;
+ client_->GetTypedCountForNodes(matches, &node_typed_counts);
+ std::sort(node_typed_counts.begin(),
+ node_typed_counts.end(),
+ NodeTypedCountPairSortFunctor());
+ std::transform(node_typed_counts.begin(),
+ node_typed_counts.end(),
+ std::back_inserter(*sorted_nodes),
+ NodeTypedCountPairExtractNodeFunctor());
+ } else {
+ sorted_nodes->insert(sorted_nodes->end(), matches.begin(), matches.end());
+ }
+}
+
+void BookmarkIndex::AddMatchToResults(
+ const BookmarkNode* node,
+ query_parser::QueryParser* parser,
+ const query_parser::QueryNodeStarVector& query_nodes,
+ std::vector<BookmarkMatch>* results) {
+ // Check that the result matches the query. The previous search
+ // was a simple per-word search, while the more complex matching
+ // of QueryParser may filter it out. For example, the query
+ // ["thi"] will match the bookmark titled [Thinking], but since
+ // ["thi"] is quoted we don't want to do a prefix match.
+ query_parser::QueryWordVector title_words, url_words;
+ const base::string16 lower_title =
+ base::i18n::ToLower(Normalize(node->GetTitle()));
+ parser->ExtractQueryWords(lower_title, &title_words);
+ base::OffsetAdjuster::Adjustments adjustments;
+ parser->ExtractQueryWords(
+ CleanUpUrlForMatching(node->url(), &adjustments),
+ &url_words);
+ query_parser::Snippet::MatchPositions title_matches, url_matches;
+ for (size_t i = 0; i < query_nodes.size(); ++i) {
+ const bool has_title_matches =
+ query_nodes[i]->HasMatchIn(title_words, &title_matches);
+ const bool has_url_matches =
+ query_nodes[i]->HasMatchIn(url_words, &url_matches);
+ if (!has_title_matches && !has_url_matches)
+ return;
+ query_parser::QueryParser::SortAndCoalesceMatchPositions(&title_matches);
+ query_parser::QueryParser::SortAndCoalesceMatchPositions(&url_matches);
+ }
+ BookmarkMatch match;
+ if (lower_title.length() == node->GetTitle().length()) {
+ // Only use title matches if the lowercase string is the same length
+ // as the original string, otherwise the matches are meaningless.
+ // TODO(mpearson): revise match positions appropriately.
+ match.title_match_positions.swap(title_matches);
+ }
+ // Now that we're done processing this entry, correct the offsets of the
+ // matches in |url_matches| so they point to offsets in the original URL
+ // spec, not the cleaned-up URL string that we used for matching.
+ std::vector<size_t> offsets =
+ BookmarkMatch::OffsetsFromMatchPositions(url_matches);
+ base::OffsetAdjuster::UnadjustOffsets(adjustments, &offsets);
+ url_matches =
+ BookmarkMatch::ReplaceOffsetsInMatchPositions(url_matches, offsets);
+ match.url_match_positions.swap(url_matches);
+ match.node = node;
+ results->push_back(match);
+}
+
+bool BookmarkIndex::GetBookmarksMatchingTerm(
+ const base::string16& term,
+ bool first_term,
+ query_parser::MatchingAlgorithm matching_algorithm,
+ NodeSet* matches) {
+ Index::const_iterator i = index_.lower_bound(term);
+ if (i == index_.end())
+ return false;
+
+ if (!query_parser::QueryParser::IsWordLongEnoughForPrefixSearch(
+ term, matching_algorithm)) {
+ // Term is too short for prefix match, compare using exact match.
+ if (i->first != term)
+ return false; // No bookmarks with this term.
+
+ if (first_term) {
+ (*matches) = i->second;
+ return true;
+ }
+ *matches = base::STLSetIntersection<NodeSet>(i->second, *matches);
+ } else {
+ // Loop through index adding all entries that start with term to
+ // |prefix_matches|.
+ NodeSet tmp_prefix_matches;
+ // If this is the first term, then store the result directly in |matches|
+ // to avoid calling stl intersection (which requires a copy).
+ NodeSet* prefix_matches = first_term ? matches : &tmp_prefix_matches;
+ while (i != index_.end() &&
+ i->first.size() >= term.size() &&
+ term.compare(0, term.size(), i->first, 0, term.size()) == 0) {
+#if !defined(OS_ANDROID)
+ prefix_matches->insert(i->second.begin(), i->second.end());
+#else
+ // Work around a bug in the implementation of std::set::insert in the STL
+ // used on android (http://crbug.com/367050).
+ for (NodeSet::const_iterator n = i->second.begin(); n != i->second.end();
+ ++n)
+ prefix_matches->insert(prefix_matches->end(), *n);
+#endif
+ ++i;
+ }
+ if (!first_term)
+ *matches = base::STLSetIntersection<NodeSet>(*prefix_matches, *matches);
+ }
+ return !matches->empty();
+}
+
+std::vector<base::string16> BookmarkIndex::ExtractQueryWords(
+ const base::string16& query) {
+ std::vector<base::string16> terms;
+ if (query.empty())
+ return std::vector<base::string16>();
+ query_parser::QueryParser parser;
+ parser.ParseQueryWords(base::i18n::ToLower(query),
+ query_parser::MatchingAlgorithm::DEFAULT,
+ &terms);
+ return terms;
+}
+
+void BookmarkIndex::RegisterNode(const base::string16& term,
+ const BookmarkNode* node) {
+ index_[term].insert(node);
+}
+
+void BookmarkIndex::UnregisterNode(const base::string16& term,
+ const BookmarkNode* node) {
+ Index::iterator i = index_.find(term);
+ if (i == index_.end()) {
+ // We can get here if the node has the same term more than once. For
+ // example, a bookmark with the title 'foo foo' would end up here.
+ return;
+ }
+ i->second.erase(node);
+ if (i->second.empty())
+ index_.erase(i);
+}
+
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/browser/bookmark_index.h b/chromium/components/bookmarks/browser/bookmark_index.h
new file mode 100644
index 00000000000..a5f9bbfe519
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_index.h
@@ -0,0 +1,94 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_INDEX_H_
+#define COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_INDEX_H_
+
+#include <stddef.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "components/query_parser/query_parser.h"
+
+namespace bookmarks {
+
+class BookmarkClient;
+class BookmarkNode;
+struct BookmarkMatch;
+
+// BookmarkIndex maintains an index of the titles and URLs of bookmarks for
+// quick look up. BookmarkIndex is owned and maintained by BookmarkModel, you
+// shouldn't need to interact directly with BookmarkIndex.
+//
+// BookmarkIndex maintains the index (index_) as a map of sets. The map (type
+// Index) maps from a lower case string to the set (type NodeSet) of
+// BookmarkNodes that contain that string in their title or URL.
+class BookmarkIndex {
+ public:
+ BookmarkIndex(BookmarkClient* client);
+ ~BookmarkIndex();
+
+ // Invoked when a bookmark has been added to the model.
+ void Add(const BookmarkNode* node);
+
+ // Invoked when a bookmark has been removed from the model.
+ void Remove(const BookmarkNode* node);
+
+ // Returns up to |max_count| of bookmarks containing each term from the text
+ // |query| in either the title or the URL.
+ void GetBookmarksMatching(const base::string16& query,
+ size_t max_count,
+ query_parser::MatchingAlgorithm matching_algorithm,
+ std::vector<BookmarkMatch>* results);
+
+ private:
+ typedef std::vector<const BookmarkNode*> Nodes;
+ typedef std::set<const BookmarkNode*> NodeSet;
+ typedef std::map<base::string16, NodeSet> Index;
+
+ // Constructs |sorted_nodes| by taking the matches in |matches| and sorting
+ // them in decreasing order of typed count (if supported by the client) and
+ // deduping them.
+ void SortMatches(const NodeSet& matches, Nodes* sorted_nodes) const;
+
+ // Add |node| to |results| if the node matches the query.
+ void AddMatchToResults(
+ const BookmarkNode* node,
+ query_parser::QueryParser* parser,
+ const query_parser::QueryNodeStarVector& query_nodes,
+ std::vector<BookmarkMatch>* results);
+
+ // Populates |matches| for the specified term. If |first_term| is true, this
+ // is the first term in the query. Returns true if there is at least one node
+ // matching the term.
+ bool GetBookmarksMatchingTerm(
+ const base::string16& term,
+ bool first_term,
+ query_parser::MatchingAlgorithm matching_algorithm,
+ NodeSet* matches);
+
+ // Returns the set of query words from |query|.
+ std::vector<base::string16> ExtractQueryWords(const base::string16& query);
+
+ // Adds |node| to |index_|.
+ void RegisterNode(const base::string16& term, const BookmarkNode* node);
+
+ // Removes |node| from |index_|.
+ void UnregisterNode(const base::string16& term, const BookmarkNode* node);
+
+ Index index_;
+
+ BookmarkClient* const client_;
+
+ DISALLOW_COPY_AND_ASSIGN(BookmarkIndex);
+};
+
+} // namespace bookmarks
+
+#endif // COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_INDEX_H_
diff --git a/chromium/components/bookmarks/browser/bookmark_index_unittest.cc b/chromium/components/bookmarks/browser/bookmark_index_unittest.cc
new file mode 100644
index 00000000000..8a365a4d1b1
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_index_unittest.cc
@@ -0,0 +1,562 @@
+// Copyright 2014 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 "components/bookmarks/browser/bookmark_index.h"
+
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/bookmarks/browser/bookmark_match.h"
+#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/test/bookmark_test_helpers.h"
+#include "components/bookmarks/test/test_bookmark_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::ASCIIToUTF16;
+using base::UTF8ToUTF16;
+
+namespace bookmarks {
+namespace {
+
+const char kAboutBlankURL[] = "about:blank";
+
+class BookmarkClientMock : public TestBookmarkClient {
+ public:
+ BookmarkClientMock(const std::map<GURL, int>& typed_count_map)
+ : typed_count_map_(typed_count_map) {}
+
+ bool SupportsTypedCountForNodes() override { return true; }
+
+ void GetTypedCountForNodes(
+ const NodeSet& nodes,
+ NodeTypedCountPairs* node_typed_count_pairs) override {
+ for (NodeSet::const_iterator it = nodes.begin(); it != nodes.end(); ++it) {
+ const BookmarkNode* node = *it;
+ std::map<GURL, int>::const_iterator found =
+ typed_count_map_.find(node->url());
+ if (found == typed_count_map_.end())
+ continue;
+
+ node_typed_count_pairs->push_back(std::make_pair(node, found->second));
+ }
+ }
+
+ private:
+ const std::map<GURL, int> typed_count_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(BookmarkClientMock);
+};
+
+class BookmarkIndexTest : public testing::Test {
+ public:
+ BookmarkIndexTest() : model_(TestBookmarkClient::CreateModel()) {}
+
+ typedef std::pair<std::string, std::string> TitleAndURL;
+
+ void AddBookmarks(const char** titles, const char** urls, size_t count) {
+ // The pair is (title, url).
+ std::vector<TitleAndURL> bookmarks;
+ for (size_t i = 0; i < count; ++i) {
+ TitleAndURL bookmark(titles[i], urls[i]);
+ bookmarks.push_back(bookmark);
+ }
+ AddBookmarks(bookmarks);
+ }
+
+ void AddBookmarks(const std::vector<TitleAndURL>& bookmarks) {
+ for (size_t i = 0; i < bookmarks.size(); ++i) {
+ model_->AddURL(model_->other_node(), static_cast<int>(i),
+ ASCIIToUTF16(bookmarks[i].first),
+ GURL(bookmarks[i].second));
+ }
+ }
+
+ void ExpectMatches(const std::string& query,
+ const char** expected_titles,
+ size_t expected_count) {
+ std::vector<std::string> title_vector;
+ for (size_t i = 0; i < expected_count; ++i)
+ title_vector.push_back(expected_titles[i]);
+ ExpectMatches(query, query_parser::MatchingAlgorithm::DEFAULT,
+ title_vector);
+ }
+
+ void ExpectMatches(const std::string& query,
+ query_parser::MatchingAlgorithm matching_algorithm,
+ const std::vector<std::string>& expected_titles) {
+ std::vector<BookmarkMatch> matches;
+ model_->GetBookmarksMatching(ASCIIToUTF16(query), 1000, matching_algorithm,
+ &matches);
+ ASSERT_EQ(expected_titles.size(), matches.size());
+ for (size_t i = 0; i < expected_titles.size(); ++i) {
+ bool found = false;
+ for (size_t j = 0; j < matches.size(); ++j) {
+ if (ASCIIToUTF16(expected_titles[i]) == matches[j].node->GetTitle()) {
+ matches.erase(matches.begin() + j);
+ found = true;
+ break;
+ }
+ }
+ ASSERT_TRUE(found);
+ }
+ }
+
+ void ExtractMatchPositions(const std::string& string,
+ BookmarkMatch::MatchPositions* matches) {
+ for (const base::StringPiece& match :
+ base::SplitStringPiece(string, ":",
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
+ std::vector<base::StringPiece> chunks = base::SplitStringPiece(
+ match, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ ASSERT_EQ(2U, chunks.size());
+ matches->push_back(BookmarkMatch::MatchPosition());
+ int chunks0, chunks1;
+ EXPECT_TRUE(base::StringToInt(chunks[0], &chunks0));
+ EXPECT_TRUE(base::StringToInt(chunks[1], &chunks1));
+ matches->back().first = chunks0;
+ matches->back().second = chunks1;
+ }
+ }
+
+ void ExpectMatchPositions(
+ const BookmarkMatch::MatchPositions& actual_positions,
+ const BookmarkMatch::MatchPositions& expected_positions) {
+ ASSERT_EQ(expected_positions.size(), actual_positions.size());
+ for (size_t i = 0; i < expected_positions.size(); ++i) {
+ EXPECT_EQ(expected_positions[i].first, actual_positions[i].first);
+ EXPECT_EQ(expected_positions[i].second, actual_positions[i].second);
+ }
+ }
+
+ protected:
+ scoped_ptr<BookmarkModel> model_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BookmarkIndexTest);
+};
+
+// Various permutations with differing input, queries and output that exercises
+// all query paths.
+TEST_F(BookmarkIndexTest, GetBookmarksMatching) {
+ struct TestData {
+ const std::string titles;
+ const std::string query;
+ const std::string expected;
+ } data[] = {
+ // Trivial test case of only one term, exact match.
+ { "a;b", "A", "a" },
+
+ // Two terms, exact matches.
+ { "a b;b", "a b", "a b" },
+
+ // Prefix match, one term.
+ { "abcd;abc;b", "abc", "abcd;abc" },
+
+ // Prefix match, multiple terms.
+ { "abcd cdef;abcd;abcd cdefg", "abc cde", "abcd cdef;abcd cdefg"},
+
+ // Exact and prefix match.
+ { "ab cdef;abcd;abcd cdefg", "ab cdef", "ab cdef"},
+
+ // Exact and prefix match.
+ { "ab cdef ghij;ab;cde;cdef;ghi;cdef ab;ghij ab",
+ "ab cde ghi",
+ "ab cdef ghij"},
+
+ // Title with term multiple times.
+ { "ab ab", "ab", "ab ab"},
+
+ // Make sure quotes don't do a prefix match.
+ { "think", "\"thi\"", ""},
+
+ // Prefix matches against multiple candidates.
+ { "abc1 abc2 abc3 abc4", "abc", "abc1 abc2 abc3 abc4"},
+
+ // Multiple prefix matches (with a lot of redundancy) against multiple
+ // candidates.
+ { "abc1 abc2 abc3 abc4 def1 def2 def3 def4",
+ "abc def abc def abc def abc def abc def",
+ "abc1 abc2 abc3 abc4 def1 def2 def3 def4"},
+
+ // Prefix match on the first term.
+ { "abc", "a", "" },
+
+ // Prefix match on subsequent terms.
+ { "abc def", "abc d", "" },
+ };
+ for (size_t i = 0; i < arraysize(data); ++i) {
+ std::vector<TitleAndURL> bookmarks;
+ for (const std::string& title : base::SplitString(
+ data[i].titles, ";",
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
+ TitleAndURL bookmark(title, kAboutBlankURL);
+ bookmarks.push_back(bookmark);
+ }
+ AddBookmarks(bookmarks);
+
+ std::vector<std::string> expected;
+ if (!data[i].expected.empty()) {
+ expected = base::SplitString(data[i].expected, ";",
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ }
+
+ ExpectMatches(data[i].query, query_parser::MatchingAlgorithm::DEFAULT,
+ expected);
+
+ model_ = TestBookmarkClient::CreateModel();
+ }
+}
+
+TEST_F(BookmarkIndexTest, GetBookmarksMatchingAlwaysPrefixSearch) {
+ struct TestData {
+ const std::string titles;
+ const std::string query;
+ const std::string expected;
+ } data[] = {
+ // Trivial test case of only one term, exact match.
+ { "z;y", "Z", "z" },
+
+ // Prefix match, one term.
+ { "abcd;abc;b", "abc", "abcd;abc" },
+
+ // Prefix match, multiple terms.
+ { "abcd cdef;abcd;abcd cdefg", "abc cde", "abcd cdef;abcd cdefg" },
+
+ // Exact and prefix match.
+ { "ab cdef ghij;ab;cde;cdef;ghi;cdef ab;ghij ab",
+ "ab cde ghi",
+ "ab cdef ghij" },
+
+ // Title with term multiple times.
+ { "ab ab", "ab", "ab ab" },
+
+ // Make sure quotes don't do a prefix match.
+ { "think", "\"thi\"", "" },
+
+ // Prefix matches against multiple candidates.
+ { "abc1 abc2 abc3 abc4", "abc", "abc1 abc2 abc3 abc4" },
+
+ // Prefix match on the first term.
+ { "abc", "a", "abc" },
+
+ // Prefix match on subsequent terms.
+ { "abc def", "abc d", "abc def" },
+
+ // Exact and prefix match.
+ { "ab cdef;abcd;abcd cdefg", "ab cdef", "ab cdef;abcd cdefg" },
+ };
+ for (size_t i = 0; i < arraysize(data); ++i) {
+ std::vector<TitleAndURL> bookmarks;
+ for (const std::string& title : base::SplitString(
+ data[i].titles, ";",
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
+ TitleAndURL bookmark(title, kAboutBlankURL);
+ bookmarks.push_back(bookmark);
+ }
+ AddBookmarks(bookmarks);
+
+ std::vector<std::string> expected;
+ if (!data[i].expected.empty()) {
+ expected = base::SplitString(data[i].expected, ";",
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ }
+
+ ExpectMatches(data[i].query,
+ query_parser::MatchingAlgorithm::ALWAYS_PREFIX_SEARCH,
+ expected);
+
+ model_ = TestBookmarkClient::CreateModel();
+ }
+}
+
+// Analogous to GetBookmarksMatching, this test tests various permutations
+// of title, URL, and input to see if the title/URL matches the input as
+// expected.
+TEST_F(BookmarkIndexTest, GetBookmarksMatchingWithURLs) {
+ struct TestData {
+ const std::string query;
+ const std::string title;
+ const std::string url;
+ const bool should_be_retrieved;
+ } data[] = {
+ // Test single-word inputs. Include both exact matches and prefix matches.
+ { "foo", "Foo", "http://www.bar.com/", true },
+ { "foo", "Foodie", "http://www.bar.com/", true },
+ { "foo", "Bar", "http://www.foo.com/", true },
+ { "foo", "Bar", "http://www.foodie.com/", true },
+ { "foo", "Foo", "http://www.foo.com/", true },
+ { "foo", "Bar", "http://www.bar.com/", false },
+ { "foo", "Bar", "http://www.bar.com/blah/foo/blah-again/ ", true },
+ { "foo", "Bar", "http://www.bar.com/blah/foodie/blah-again/ ", true },
+ { "foo", "Bar", "http://www.bar.com/blah-foo/blah-again/ ", true },
+ { "foo", "Bar", "http://www.bar.com/blah-foodie/blah-again/ ", true },
+ { "foo", "Bar", "http://www.bar.com/blahafoo/blah-again/ ", false },
+
+ // Test multi-word inputs.
+ { "foo bar", "Foo Bar", "http://baz.com/", true },
+ { "foo bar", "Foodie Bar", "http://baz.com/", true },
+ { "bar foo", "Foo Bar", "http://baz.com/", true },
+ { "bar foo", "Foodie Barly", "http://baz.com/", true },
+ { "foo bar", "Foo Baz", "http://baz.com/", false },
+ { "foo bar", "Foo Baz", "http://bar.com/", true },
+ { "foo bar", "Foo Baz", "http://barly.com/", true },
+ { "foo bar", "Foodie Baz", "http://barly.com/", true },
+ { "bar foo", "Foo Baz", "http://bar.com/", true },
+ { "bar foo", "Foo Baz", "http://barly.com/", true },
+ { "foo bar", "Baz Bar", "http://blah.com/foo", true },
+ { "foo bar", "Baz Barly", "http://blah.com/foodie", true },
+ { "foo bar", "Baz Bur", "http://blah.com/foo/bar", true },
+ { "foo bar", "Baz Bur", "http://blah.com/food/barly", true },
+ { "foo bar", "Baz Bur", "http://bar.com/blah/foo", true },
+ { "foo bar", "Baz Bur", "http://barly.com/blah/food", true },
+ { "foo bar", "Baz Bur", "http://bar.com/blah/flub", false },
+ { "foo bar", "Baz Bur", "http://foo.com/blah/flub", false }
+ };
+
+ for (size_t i = 0; i < arraysize(data); ++i) {
+ model_ = TestBookmarkClient::CreateModel();
+ std::vector<TitleAndURL> bookmarks;
+ bookmarks.push_back(TitleAndURL(data[i].title, data[i].url));
+ AddBookmarks(bookmarks);
+
+ std::vector<std::string> expected;
+ if (data[i].should_be_retrieved)
+ expected.push_back(data[i].title);
+
+ ExpectMatches(data[i].query, query_parser::MatchingAlgorithm::DEFAULT,
+ expected);
+ }
+}
+
+TEST_F(BookmarkIndexTest, Normalization) {
+ struct TestData {
+ const char* const title;
+ const char* const query;
+ } data[] = {
+ { "fooa\xcc\x88-test", "foo\xc3\xa4-test" },
+ { "fooa\xcc\x88-test", "fooa\xcc\x88-test" },
+ { "fooa\xcc\x88-test", "foo\xc3\xa4" },
+ { "fooa\xcc\x88-test", "fooa\xcc\x88" },
+ { "fooa\xcc\x88-test", "foo" },
+ { "foo\xc3\xa4-test", "foo\xc3\xa4-test" },
+ { "foo\xc3\xa4-test", "fooa\xcc\x88-test" },
+ { "foo\xc3\xa4-test", "foo\xc3\xa4" },
+ { "foo\xc3\xa4-test", "fooa\xcc\x88" },
+ { "foo\xc3\xa4-test", "foo" },
+ { "foo", "foo" }
+ };
+
+ GURL url(kAboutBlankURL);
+ for (size_t i = 0; i < arraysize(data); ++i) {
+ model_->AddURL(model_->other_node(), 0, UTF8ToUTF16(data[i].title), url);
+ std::vector<BookmarkMatch> matches;
+ model_->GetBookmarksMatching(UTF8ToUTF16(data[i].query), 10, &matches);
+ EXPECT_EQ(1u, matches.size());
+ model_ = TestBookmarkClient::CreateModel();
+ }
+}
+
+// Makes sure match positions are updated appropriately for title matches.
+TEST_F(BookmarkIndexTest, MatchPositionsTitles) {
+ struct TestData {
+ const std::string title;
+ const std::string query;
+ const std::string expected_title_match_positions;
+ } data[] = {
+ // Trivial test case of only one term, exact match.
+ { "a", "A", "0,1" },
+ { "foo bar", "bar", "4,7" },
+ { "fooey bark", "bar foo", "0,3:6,9" },
+ // Non-trivial tests.
+ { "foobar foo", "foobar foo", "0,6:7,10" },
+ { "foobar foo", "foo foobar", "0,6:7,10" },
+ { "foobar foobar", "foobar foo", "0,6:7,13" },
+ { "foobar foobar", "foo foobar", "0,6:7,13" },
+ };
+ for (size_t i = 0; i < arraysize(data); ++i) {
+ std::vector<TitleAndURL> bookmarks;
+ TitleAndURL bookmark(data[i].title, kAboutBlankURL);
+ bookmarks.push_back(bookmark);
+ AddBookmarks(bookmarks);
+
+ std::vector<BookmarkMatch> matches;
+ model_->GetBookmarksMatching(ASCIIToUTF16(data[i].query), 1000, &matches);
+ ASSERT_EQ(1U, matches.size());
+
+ BookmarkMatch::MatchPositions expected_title_matches;
+ ExtractMatchPositions(data[i].expected_title_match_positions,
+ &expected_title_matches);
+ ExpectMatchPositions(matches[0].title_match_positions,
+ expected_title_matches);
+
+ model_ = TestBookmarkClient::CreateModel();
+ }
+}
+
+// Makes sure match positions are updated appropriately for URL matches.
+TEST_F(BookmarkIndexTest, MatchPositionsURLs) {
+ // The encoded stuff between /wiki/ and the # is 第二次世界大戦
+ const std::string ja_wiki_url = "http://ja.wikipedia.org/wiki/%E7%AC%AC%E4"
+ "%BA%8C%E6%AC%A1%E4%B8%96%E7%95%8C%E5%A4%A7%E6%88%A6#.E3.83.B4.E3.82.A7"
+ ".E3.83.AB.E3.82.B5.E3.82.A4.E3.83.A6.E4.BD.93.E5.88.B6";
+ struct TestData {
+ const std::string query;
+ const std::string url;
+ const std::string expected_url_match_positions;
+ } data[] = {
+ { "foo", "http://www.foo.com/", "11,14" },
+ { "foo", "http://www.foodie.com/", "11,14" },
+ { "foo", "http://www.foofoo.com/", "11,14" },
+ { "www", "http://www.foo.com/", "7,10" },
+ { "foo", "http://www.foodie.com/blah/foo/fi", "11,14:27,30" },
+ { "foo", "http://www.blah.com/blah/foo/fi", "25,28" },
+ { "foo www", "http://www.foodie.com/blah/foo/fi", "7,10:11,14:27,30" },
+ { "www foo", "http://www.foodie.com/blah/foo/fi", "7,10:11,14:27,30" },
+ { "www bla", "http://www.foodie.com/blah/foo/fi", "7,10:22,25" },
+ { "http", "http://www.foo.com/", "0,4" },
+ { "http www", "http://www.foo.com/", "0,4:7,10" },
+ { "http foo", "http://www.foo.com/", "0,4:11,14" },
+ { "http foo", "http://www.bar.com/baz/foodie/hi", "0,4:23,26" },
+ { "第二次", ja_wiki_url, "29,56" },
+ { "ja 第二次", ja_wiki_url, "7,9:29,56" },
+ { "第二次 E3.8", ja_wiki_url, "29,56:94,98:103,107:"
+ "112,116:121,125:"
+ "130,134:139,143" }
+ };
+
+ for (size_t i = 0; i < arraysize(data); ++i) {
+ model_ = TestBookmarkClient::CreateModel();
+ std::vector<TitleAndURL> bookmarks;
+ TitleAndURL bookmark("123456", data[i].url);
+ bookmarks.push_back(bookmark);
+ AddBookmarks(bookmarks);
+
+ std::vector<BookmarkMatch> matches;
+ model_->GetBookmarksMatching(UTF8ToUTF16(data[i].query), 1000, &matches);
+ ASSERT_EQ(1U, matches.size()) << data[i].url << data[i].query;
+
+ BookmarkMatch::MatchPositions expected_url_matches;
+ ExtractMatchPositions(data[i].expected_url_match_positions,
+ &expected_url_matches);
+ ExpectMatchPositions(matches[0].url_match_positions, expected_url_matches);
+ }
+}
+
+// Makes sure index is updated when a node is removed.
+TEST_F(BookmarkIndexTest, Remove) {
+ const char* titles[] = { "a", "b" };
+ const char* urls[] = {kAboutBlankURL, kAboutBlankURL};
+ AddBookmarks(titles, urls, arraysize(titles));
+
+ // Remove the node and make sure we don't get back any results.
+ model_->Remove(model_->other_node()->GetChild(0));
+ ExpectMatches("A", NULL, 0U);
+}
+
+// Makes sure index is updated when a node's title is changed.
+TEST_F(BookmarkIndexTest, ChangeTitle) {
+ const char* titles[] = { "a", "b" };
+ const char* urls[] = {kAboutBlankURL, kAboutBlankURL};
+ AddBookmarks(titles, urls, arraysize(titles));
+
+ // Remove the node and make sure we don't get back any results.
+ const char* expected[] = { "blah" };
+ model_->SetTitle(model_->other_node()->GetChild(0), ASCIIToUTF16("blah"));
+ ExpectMatches("BlAh", expected, arraysize(expected));
+}
+
+// Makes sure index is updated when a node's URL is changed.
+TEST_F(BookmarkIndexTest, ChangeURL) {
+ const char* titles[] = { "a", "b" };
+ const char* urls[] = {"http://fizz",
+ "http://fuzz"};
+ AddBookmarks(titles, urls, arraysize(titles));
+
+ const char* expected[] = { "a" };
+ model_->SetURL(model_->other_node()->GetChild(0), GURL("http://blah"));
+ ExpectMatches("blah", expected, arraysize(expected));
+}
+
+// Makes sure no more than max queries is returned.
+TEST_F(BookmarkIndexTest, HonorMax) {
+ const char* titles[] = { "abcd", "abcde" };
+ const char* urls[] = {kAboutBlankURL, kAboutBlankURL};
+ AddBookmarks(titles, urls, arraysize(titles));
+
+ std::vector<BookmarkMatch> matches;
+ model_->GetBookmarksMatching(ASCIIToUTF16("ABc"), 1, &matches);
+ EXPECT_EQ(1U, matches.size());
+}
+
+// Makes sure if the lower case string of a bookmark title is more characters
+// than the upper case string no match positions are returned.
+TEST_F(BookmarkIndexTest, EmptyMatchOnMultiwideLowercaseString) {
+ const BookmarkNode* n1 = model_->AddURL(model_->other_node(), 0,
+ base::WideToUTF16(L"\u0130 i"),
+ GURL("http://www.google.com"));
+
+ std::vector<BookmarkMatch> matches;
+ model_->GetBookmarksMatching(ASCIIToUTF16("i"), 100, &matches);
+ ASSERT_EQ(1U, matches.size());
+ EXPECT_EQ(n1, matches[0].node);
+ EXPECT_TRUE(matches[0].title_match_positions.empty());
+}
+
+TEST_F(BookmarkIndexTest, GetResultsSortedByTypedCount) {
+ struct TestData {
+ const GURL url;
+ const char* title;
+ const int typed_count;
+ } data[] = {
+ { GURL("http://www.google.com/"), "Google", 100 },
+ { GURL("http://maps.google.com/"), "Google Maps", 40 },
+ { GURL("http://docs.google.com/"), "Google Docs", 50 },
+ { GURL("http://reader.google.com/"), "Google Reader", 80 },
+ };
+
+ std::map<GURL, int> typed_count_map;
+ for (size_t i = 0; i < arraysize(data); ++i)
+ typed_count_map.insert(std::make_pair(data[i].url, data[i].typed_count));
+
+ scoped_ptr<BookmarkModel> model = TestBookmarkClient::CreateModelWithClient(
+ make_scoped_ptr(new BookmarkClientMock(typed_count_map)));
+
+ for (size_t i = 0; i < arraysize(data); ++i)
+ // Populate the BookmarkIndex.
+ model->AddURL(
+ model->other_node(), i, UTF8ToUTF16(data[i].title), data[i].url);
+
+ // Populate match nodes.
+ std::vector<BookmarkMatch> matches;
+ model->GetBookmarksMatching(ASCIIToUTF16("google"), 4, &matches);
+
+ // The resulting order should be:
+ // 1. Google (google.com) 100
+ // 2. Google Reader (google.com/reader) 80
+ // 3. Google Docs (docs.google.com) 50
+ // 4. Google Maps (maps.google.com) 40
+ ASSERT_EQ(4U, matches.size());
+ EXPECT_EQ(data[0].url, matches[0].node->url());
+ EXPECT_EQ(data[3].url, matches[1].node->url());
+ EXPECT_EQ(data[2].url, matches[2].node->url());
+ EXPECT_EQ(data[1].url, matches[3].node->url());
+
+ matches.clear();
+ // Select top two matches.
+ model->GetBookmarksMatching(ASCIIToUTF16("google"), 2, &matches);
+
+ ASSERT_EQ(2U, matches.size());
+ EXPECT_EQ(data[0].url, matches[0].node->url());
+ EXPECT_EQ(data[3].url, matches[1].node->url());
+}
+
+} // namespace
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/browser/bookmark_match.cc b/chromium/components/bookmarks/browser/bookmark_match.cc
new file mode 100644
index 00000000000..f813cbbbb7f
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_match.cc
@@ -0,0 +1,50 @@
+// Copyright 2014 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 "components/bookmarks/browser/bookmark_match.h"
+
+#include "base/logging.h"
+#include "base/strings/string16.h"
+
+namespace bookmarks {
+
+BookmarkMatch::BookmarkMatch() : node(NULL) {}
+
+BookmarkMatch::BookmarkMatch(const BookmarkMatch& other) = default;
+
+BookmarkMatch::~BookmarkMatch() {}
+
+// static
+std::vector<size_t> BookmarkMatch::OffsetsFromMatchPositions(
+ const MatchPositions& match_positions) {
+ std::vector<size_t> offsets;
+ for (MatchPositions::const_iterator i = match_positions.begin();
+ i != match_positions.end(); ++i) {
+ offsets.push_back(i->first);
+ offsets.push_back(i->second);
+ }
+ return offsets;
+}
+
+// static
+BookmarkMatch::MatchPositions BookmarkMatch::ReplaceOffsetsInMatchPositions(
+ const MatchPositions& match_positions,
+ const std::vector<size_t>& offsets) {
+ DCHECK_EQ(2 * match_positions.size(), offsets.size());
+ MatchPositions new_match_positions;
+ std::vector<size_t>::const_iterator offset_iter = offsets.begin();
+ for (MatchPositions::const_iterator match_iter = match_positions.begin();
+ match_iter != match_positions.end(); ++match_iter, ++offset_iter) {
+ const size_t begin = *offset_iter;
+ ++offset_iter;
+ const size_t end = *offset_iter;
+ if ((begin != base::string16::npos) && (end != base::string16::npos)) {
+ const MatchPosition new_match_position(begin, end);
+ new_match_positions.push_back(new_match_position);
+ }
+ }
+ return new_match_positions;
+}
+
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/browser/bookmark_match.h b/chromium/components/bookmarks/browser/bookmark_match.h
new file mode 100644
index 00000000000..ce658939ca8
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_match.h
@@ -0,0 +1,51 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_TITLE_MATCH_H_
+#define COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_TITLE_MATCH_H_
+
+#include <stddef.h>
+
+#include <cstddef>
+#include <utility>
+#include <vector>
+
+namespace bookmarks {
+
+class BookmarkNode;
+
+struct BookmarkMatch {
+ // Each MatchPosition is the [begin, end) positions of a match within a
+ // string.
+ typedef std::pair<size_t, size_t> MatchPosition;
+ typedef std::vector<MatchPosition> MatchPositions;
+
+ BookmarkMatch();
+ BookmarkMatch(const BookmarkMatch& other);
+ ~BookmarkMatch();
+
+ // Extracts and returns the offsets from |match_positions|.
+ static std::vector<size_t> OffsetsFromMatchPositions(
+ const MatchPositions& match_positions);
+
+ // Replaces the offsets in |match_positions| with those given in |offsets|,
+ // deleting any which are npos, and returns the updated list of match
+ // positions.
+ static MatchPositions ReplaceOffsetsInMatchPositions(
+ const MatchPositions& match_positions,
+ const std::vector<size_t>& offsets);
+
+ // The matching node of a query.
+ const BookmarkNode* node;
+
+ // Location of the matching words in the title of the node.
+ MatchPositions title_match_positions;
+
+ // Location of the matching words in the URL of the node.
+ MatchPositions url_match_positions;
+};
+
+} // namespace bookmarks
+
+#endif // COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_TITLE_MATCH_H_
diff --git a/chromium/components/bookmarks/browser/bookmark_model.cc b/chromium/components/bookmarks/browser/bookmark_model.cc
new file mode 100644
index 00000000000..3343af0be6d
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_model.cc
@@ -0,0 +1,1118 @@
+// Copyright 2014 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 "components/bookmarks/browser/bookmark_model.h"
+
+#include <algorithm>
+#include <functional>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/i18n/string_compare.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/profiler/scoped_tracker.h"
+#include "base/strings/string_util.h"
+#include "components/bookmarks/browser/bookmark_expanded_state_tracker.h"
+#include "components/bookmarks/browser/bookmark_index.h"
+#include "components/bookmarks/browser/bookmark_match.h"
+#include "components/bookmarks/browser/bookmark_model_observer.h"
+#include "components/bookmarks/browser/bookmark_node_data.h"
+#include "components/bookmarks/browser/bookmark_storage.h"
+#include "components/bookmarks/browser/bookmark_undo_delegate.h"
+#include "components/bookmarks/browser/bookmark_utils.h"
+#include "components/favicon_base/favicon_types.h"
+#include "grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/favicon_size.h"
+
+using base::Time;
+
+namespace bookmarks {
+
+namespace {
+
+// Helper to get a mutable bookmark node.
+BookmarkNode* AsMutable(const BookmarkNode* node) {
+ return const_cast<BookmarkNode*>(node);
+}
+
+// Helper to get a mutable permanent bookmark node.
+BookmarkPermanentNode* AsMutable(const BookmarkPermanentNode* node) {
+ return const_cast<BookmarkPermanentNode*>(node);
+}
+
+// Comparator used when sorting permanent nodes. Nodes that are initially
+// visible are sorted before nodes that are initially hidden.
+class VisibilityComparator {
+ public:
+ explicit VisibilityComparator(BookmarkClient* client) : client_(client) {}
+
+ // Returns true if |n1| preceeds |n2|.
+ bool operator()(const BookmarkPermanentNode* n1,
+ const BookmarkPermanentNode* n2) {
+ bool n1_visible = client_->IsPermanentNodeVisible(n1);
+ bool n2_visible = client_->IsPermanentNodeVisible(n2);
+ return n1_visible != n2_visible && n1_visible;
+ }
+
+ private:
+ BookmarkClient* client_;
+};
+
+// Comparator used when sorting bookmarks. Folders are sorted first, then
+// bookmarks.
+class SortComparator {
+ public:
+ explicit SortComparator(icu::Collator* collator) : collator_(collator) {}
+
+ // Returns true if |n1| preceeds |n2|.
+ bool operator()(const BookmarkNode* n1, const BookmarkNode* n2) {
+ if (n1->type() == n2->type()) {
+ // Types are the same, compare the names.
+ if (!collator_)
+ return n1->GetTitle() < n2->GetTitle();
+ return base::i18n::CompareString16WithCollator(
+ *collator_, n1->GetTitle(), n2->GetTitle()) == UCOL_LESS;
+ }
+ // Types differ, sort such that folders come first.
+ return n1->is_folder();
+ }
+
+ private:
+ icu::Collator* collator_;
+};
+
+// Delegate that does nothing.
+class EmptyUndoDelegate : public BookmarkUndoDelegate {
+ public:
+ EmptyUndoDelegate() {}
+ ~EmptyUndoDelegate() override {}
+
+ private:
+ // BookmarkUndoDelegate:
+ void SetUndoProvider(BookmarkUndoProvider* provider) override {}
+ void OnBookmarkNodeRemoved(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int index,
+ scoped_ptr<BookmarkNode> node) override {}
+
+ DISALLOW_COPY_AND_ASSIGN(EmptyUndoDelegate);
+};
+
+} // namespace
+
+// BookmarkModel --------------------------------------------------------------
+
+BookmarkModel::BookmarkModel(scoped_ptr<BookmarkClient> client)
+ : client_(std::move(client)),
+ loaded_(false),
+ root_(GURL()),
+ bookmark_bar_node_(NULL),
+ other_node_(NULL),
+ mobile_node_(NULL),
+ next_node_id_(1),
+ observers_(
+ base::ObserverList<BookmarkModelObserver>::NOTIFY_EXISTING_ONLY),
+ loaded_signal_(true, false),
+ extensive_changes_(0),
+ undo_delegate_(nullptr),
+ empty_undo_delegate_(new EmptyUndoDelegate) {
+ DCHECK(client_);
+ client_->Init(this);
+}
+
+BookmarkModel::~BookmarkModel() {
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ BookmarkModelBeingDeleted(this));
+
+ if (store_.get()) {
+ // 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.
+ store_->BookmarkModelDeleted();
+ }
+}
+
+void BookmarkModel::Shutdown() {
+ if (loaded_)
+ return;
+
+ // See comment in HistoryService::ShutdownOnUIThread where this is invoked for
+ // details. It is also called when the BookmarkModel is deleted.
+ loaded_signal_.Signal();
+}
+
+void BookmarkModel::Load(
+ PrefService* pref_service,
+ const base::FilePath& profile_path,
+ const scoped_refptr<base::SequencedTaskRunner>& io_task_runner,
+ const scoped_refptr<base::SequencedTaskRunner>& ui_task_runner) {
+ if (store_.get()) {
+ // If the store is non-null, it means Load was already invoked. Load should
+ // only be invoked once.
+ NOTREACHED();
+ return;
+ }
+
+ expanded_state_tracker_.reset(
+ new BookmarkExpandedStateTracker(this, pref_service));
+
+ // Load the bookmarks. BookmarkStorage notifies us when done.
+ store_.reset(new BookmarkStorage(this, profile_path, io_task_runner.get()));
+ store_->LoadBookmarks(CreateLoadDetails(), ui_task_runner);
+}
+
+const BookmarkNode* BookmarkModel::GetParentForNewNodes() {
+ std::vector<const BookmarkNode*> nodes =
+ GetMostRecentlyModifiedUserFolders(this, 1);
+ DCHECK(!nodes.empty()); // This list is always padded with default folders.
+ return nodes[0];
+}
+
+void BookmarkModel::AddObserver(BookmarkModelObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void BookmarkModel::RemoveObserver(BookmarkModelObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void BookmarkModel::BeginExtensiveChanges() {
+ if (++extensive_changes_ == 1) {
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ ExtensiveBookmarkChangesBeginning(this));
+ }
+}
+
+void BookmarkModel::EndExtensiveChanges() {
+ --extensive_changes_;
+ DCHECK_GE(extensive_changes_, 0);
+ if (extensive_changes_ == 0) {
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ ExtensiveBookmarkChangesEnded(this));
+ }
+}
+
+void BookmarkModel::BeginGroupedChanges() {
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ GroupedBookmarkChangesBeginning(this));
+}
+
+void BookmarkModel::EndGroupedChanges() {
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ GroupedBookmarkChangesEnded(this));
+}
+
+void BookmarkModel::Remove(const BookmarkNode* node) {
+ DCHECK(loaded_);
+ DCHECK(node);
+ DCHECK(!is_root_node(node));
+ RemoveAndDeleteNode(AsMutable(node));
+}
+
+void BookmarkModel::RemoveAllUserBookmarks() {
+ std::set<GURL> removed_urls;
+ struct RemoveNodeData {
+ RemoveNodeData(const BookmarkNode* parent, int index, BookmarkNode* node)
+ : parent(parent), index(index), node(node) {}
+
+ const BookmarkNode* parent;
+ int index;
+ BookmarkNode* node;
+ };
+ std::vector<RemoveNodeData> removed_node_data_list;
+
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ OnWillRemoveAllUserBookmarks(this));
+
+ BeginExtensiveChanges();
+ // Skip deleting permanent nodes. Permanent bookmark nodes are the root and
+ // its immediate children. For removing all non permanent nodes just remove
+ // all children of non-root permanent nodes.
+ {
+ base::AutoLock url_lock(url_lock_);
+ for (int i = 0; i < root_.child_count(); ++i) {
+ const BookmarkNode* permanent_node = root_.GetChild(i);
+
+ if (!client_->CanBeEditedByUser(permanent_node))
+ continue;
+
+ for (int j = permanent_node->child_count() - 1; j >= 0; --j) {
+ BookmarkNode* child_node = AsMutable(permanent_node->GetChild(j));
+ RemoveNodeAndGetRemovedUrls(child_node, &removed_urls);
+ removed_node_data_list.push_back(
+ RemoveNodeData(permanent_node, j, child_node));
+ }
+ }
+ }
+ EndExtensiveChanges();
+ if (store_.get())
+ store_->ScheduleSave();
+
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ BookmarkAllUserNodesRemoved(this, removed_urls));
+
+ BeginGroupedChanges();
+ for (const auto& removed_node_data : removed_node_data_list) {
+ undo_delegate()->OnBookmarkNodeRemoved(
+ this,
+ removed_node_data.parent,
+ removed_node_data.index,
+ scoped_ptr<BookmarkNode>(removed_node_data.node));
+ }
+ EndGroupedChanges();
+}
+
+void BookmarkModel::Move(const BookmarkNode* node,
+ const BookmarkNode* new_parent,
+ int index) {
+ if (!loaded_ || !node || !IsValidIndex(new_parent, index, true) ||
+ is_root_node(new_parent) || is_permanent_node(node)) {
+ NOTREACHED();
+ return;
+ }
+
+ if (new_parent->HasAncestor(node)) {
+ // Can't make an ancestor of the node be a child of the node.
+ NOTREACHED();
+ return;
+ }
+
+ const BookmarkNode* old_parent = node->parent();
+ int old_index = old_parent->GetIndexOf(node);
+
+ if (old_parent == new_parent &&
+ (index == old_index || index == old_index + 1)) {
+ // Node is already in this position, nothing to do.
+ return;
+ }
+
+ SetDateFolderModified(new_parent, Time::Now());
+
+ if (old_parent == new_parent && index > old_index)
+ index--;
+ BookmarkNode* mutable_new_parent = AsMutable(new_parent);
+ mutable_new_parent->Add(AsMutable(node), index);
+
+ if (store_.get())
+ store_->ScheduleSave();
+
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ BookmarkNodeMoved(this, old_parent, old_index,
+ new_parent, index));
+}
+
+void BookmarkModel::Copy(const BookmarkNode* node,
+ const BookmarkNode* new_parent,
+ int index) {
+ if (!loaded_ || !node || !IsValidIndex(new_parent, index, true) ||
+ is_root_node(new_parent) || is_permanent_node(node)) {
+ NOTREACHED();
+ return;
+ }
+
+ if (new_parent->HasAncestor(node)) {
+ // Can't make an ancestor of the node be a child of the node.
+ NOTREACHED();
+ return;
+ }
+
+ SetDateFolderModified(new_parent, Time::Now());
+ BookmarkNodeData drag_data(node);
+ // CloneBookmarkNode will use BookmarkModel methods to do the job, so we
+ // don't need to send notifications here.
+ CloneBookmarkNode(this, drag_data.elements, new_parent, index, true);
+
+ if (store_.get())
+ store_->ScheduleSave();
+}
+
+const gfx::Image& BookmarkModel::GetFavicon(const BookmarkNode* node) {
+ DCHECK(node);
+ if (node->favicon_state() == BookmarkNode::INVALID_FAVICON) {
+ BookmarkNode* mutable_node = AsMutable(node);
+ LoadFavicon(mutable_node,
+ client_->PreferTouchIcon() ? favicon_base::TOUCH_ICON
+ : favicon_base::FAVICON);
+ }
+ return node->favicon();
+}
+
+favicon_base::IconType BookmarkModel::GetFaviconType(const BookmarkNode* node) {
+ DCHECK(node);
+ return node->favicon_type();
+}
+
+void BookmarkModel::SetTitle(const BookmarkNode* node,
+ const base::string16& title) {
+ DCHECK(node);
+
+ if (node->GetTitle() == title)
+ return;
+
+ if (is_permanent_node(node) && !client_->CanSetPermanentNodeTitle(node)) {
+ NOTREACHED();
+ return;
+ }
+
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ OnWillChangeBookmarkNode(this, node));
+
+ // The title index doesn't support changing the title, instead we remove then
+ // add it back.
+ index_->Remove(node);
+ AsMutable(node)->SetTitle(title);
+ index_->Add(node);
+
+ if (store_.get())
+ store_->ScheduleSave();
+
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ BookmarkNodeChanged(this, node));
+}
+
+void BookmarkModel::SetURL(const BookmarkNode* node, const GURL& url) {
+ DCHECK(node && !node->is_folder());
+
+ if (node->url() == url)
+ return;
+
+ BookmarkNode* mutable_node = AsMutable(node);
+ mutable_node->InvalidateFavicon();
+ CancelPendingFaviconLoadRequests(mutable_node);
+
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ OnWillChangeBookmarkNode(this, node));
+
+ {
+ base::AutoLock url_lock(url_lock_);
+ RemoveNodeFromInternalMaps(mutable_node);
+ mutable_node->set_url(url);
+ AddNodeToInternalMaps(mutable_node);
+ }
+
+ if (store_.get())
+ store_->ScheduleSave();
+
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ BookmarkNodeChanged(this, node));
+}
+
+void BookmarkModel::SetNodeMetaInfo(const BookmarkNode* node,
+ const std::string& key,
+ const std::string& value) {
+ std::string old_value;
+ if (node->GetMetaInfo(key, &old_value) && old_value == value)
+ return;
+
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ OnWillChangeBookmarkMetaInfo(this, node));
+
+ if (AsMutable(node)->SetMetaInfo(key, value) && store_.get())
+ store_->ScheduleSave();
+
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ BookmarkMetaInfoChanged(this, node));
+}
+
+void BookmarkModel::SetNodeMetaInfoMap(
+ const BookmarkNode* node,
+ const BookmarkNode::MetaInfoMap& meta_info_map) {
+ const BookmarkNode::MetaInfoMap* old_meta_info_map = node->GetMetaInfoMap();
+ if ((!old_meta_info_map && meta_info_map.empty()) ||
+ (old_meta_info_map && meta_info_map == *old_meta_info_map))
+ return;
+
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ OnWillChangeBookmarkMetaInfo(this, node));
+
+ AsMutable(node)->SetMetaInfoMap(meta_info_map);
+ if (store_.get())
+ store_->ScheduleSave();
+
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ BookmarkMetaInfoChanged(this, node));
+}
+
+void BookmarkModel::DeleteNodeMetaInfo(const BookmarkNode* node,
+ const std::string& key) {
+ const BookmarkNode::MetaInfoMap* meta_info_map = node->GetMetaInfoMap();
+ if (!meta_info_map || meta_info_map->find(key) == meta_info_map->end())
+ return;
+
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ OnWillChangeBookmarkMetaInfo(this, node));
+
+ if (AsMutable(node)->DeleteMetaInfo(key) && store_.get())
+ store_->ScheduleSave();
+
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ BookmarkMetaInfoChanged(this, node));
+}
+
+void BookmarkModel::AddNonClonedKey(const std::string& key) {
+ non_cloned_keys_.insert(key);
+}
+
+void BookmarkModel::SetNodeSyncTransactionVersion(
+ const BookmarkNode* node,
+ int64_t sync_transaction_version) {
+ DCHECK(client_->CanSyncNode(node));
+
+ if (sync_transaction_version == node->sync_transaction_version())
+ return;
+
+ AsMutable(node)->set_sync_transaction_version(sync_transaction_version);
+ if (store_.get())
+ store_->ScheduleSave();
+}
+
+void BookmarkModel::OnFaviconsChanged(const std::set<GURL>& page_urls,
+ const GURL& icon_url) {
+ std::set<const BookmarkNode*> to_update;
+ for (const GURL& page_url : page_urls) {
+ std::vector<const BookmarkNode*> nodes;
+ GetNodesByURL(page_url, &nodes);
+ to_update.insert(nodes.begin(), nodes.end());
+ }
+
+ if (!icon_url.is_empty()) {
+ // Log Histogram to determine how often |icon_url| is non empty in
+ // practice.
+ // TODO(pkotwicz): Do something more efficient if |icon_url| is non-empty
+ // many times a day for each user.
+ UMA_HISTOGRAM_BOOLEAN("Bookmarks.OnFaviconsChangedIconURL", true);
+
+ base::AutoLock url_lock(url_lock_);
+ for (const BookmarkNode* node : nodes_ordered_by_url_set_) {
+ if (icon_url == node->icon_url())
+ to_update.insert(node);
+ }
+ }
+
+ for (const BookmarkNode* node : to_update) {
+ // Rerequest the favicon.
+ BookmarkNode* mutable_node = AsMutable(node);
+ mutable_node->InvalidateFavicon();
+ CancelPendingFaviconLoadRequests(mutable_node);
+ FOR_EACH_OBSERVER(BookmarkModelObserver,
+ observers_,
+ BookmarkNodeFaviconChanged(this, node));
+ }
+}
+
+void BookmarkModel::SetDateAdded(const BookmarkNode* node, Time date_added) {
+ DCHECK(node && !is_permanent_node(node));
+
+ if (node->date_added() == date_added)
+ return;
+
+ AsMutable(node)->set_date_added(date_added);
+
+ // Syncing might result in dates newer than the folder's last modified date.
+ if (date_added > node->parent()->date_folder_modified()) {
+ // Will trigger store_->ScheduleSave().
+ SetDateFolderModified(node->parent(), date_added);
+ } else if (store_.get()) {
+ store_->ScheduleSave();
+ }
+}
+
+void BookmarkModel::GetNodesByURL(const GURL& url,
+ std::vector<const BookmarkNode*>* nodes) {
+ base::AutoLock url_lock(url_lock_);
+ BookmarkNode tmp_node(url);
+ NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.find(&tmp_node);
+ while (i != nodes_ordered_by_url_set_.end() && (*i)->url() == url) {
+ nodes->push_back(*i);
+ ++i;
+ }
+}
+
+const BookmarkNode* BookmarkModel::GetMostRecentlyAddedUserNodeForURL(
+ const GURL& url) {
+ std::vector<const BookmarkNode*> nodes;
+ GetNodesByURL(url, &nodes);
+ std::sort(nodes.begin(), nodes.end(), &MoreRecentlyAdded);
+
+ // Look for the first node that the user can edit.
+ for (size_t i = 0; i < nodes.size(); ++i) {
+ if (client_->CanBeEditedByUser(nodes[i]))
+ return nodes[i];
+ }
+
+ return NULL;
+}
+
+bool BookmarkModel::HasBookmarks() {
+ base::AutoLock url_lock(url_lock_);
+ return !nodes_ordered_by_url_set_.empty();
+}
+
+bool BookmarkModel::IsBookmarked(const GURL& url) {
+ base::AutoLock url_lock(url_lock_);
+ return IsBookmarkedNoLock(url);
+}
+
+void BookmarkModel::GetBookmarks(
+ std::vector<BookmarkModel::URLAndTitle>* bookmarks) {
+ base::AutoLock url_lock(url_lock_);
+ const GURL* last_url = NULL;
+ for (NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.begin();
+ i != nodes_ordered_by_url_set_.end(); ++i) {
+ const GURL* url = &((*i)->url());
+ // Only add unique URLs.
+ if (!last_url || *url != *last_url) {
+ BookmarkModel::URLAndTitle bookmark;
+ bookmark.url = *url;
+ bookmark.title = (*i)->GetTitle();
+ bookmarks->push_back(bookmark);
+ }
+ last_url = url;
+ }
+}
+
+void BookmarkModel::BlockTillLoaded() {
+ loaded_signal_.Wait();
+}
+
+const BookmarkNode* BookmarkModel::AddFolder(const BookmarkNode* parent,
+ int index,
+ const base::string16& title) {
+ return AddFolderWithMetaInfo(parent, index, title, NULL);
+}
+const BookmarkNode* BookmarkModel::AddFolderWithMetaInfo(
+ const BookmarkNode* parent,
+ int index,
+ const base::string16& title,
+ const BookmarkNode::MetaInfoMap* meta_info) {
+ if (!loaded_ || is_root_node(parent) || !IsValidIndex(parent, index, true)) {
+ // Can't add to the root.
+ NOTREACHED();
+ return NULL;
+ }
+
+ BookmarkNode* new_node = new BookmarkNode(generate_next_node_id(), GURL());
+ new_node->set_date_folder_modified(Time::Now());
+ // Folders shouldn't have line breaks in their titles.
+ new_node->SetTitle(title);
+ new_node->set_type(BookmarkNode::FOLDER);
+ if (meta_info)
+ new_node->SetMetaInfoMap(*meta_info);
+
+ return AddNode(AsMutable(parent), index, new_node);
+}
+
+const BookmarkNode* BookmarkModel::AddURL(const BookmarkNode* parent,
+ int index,
+ const base::string16& title,
+ const GURL& url) {
+ return AddURLWithCreationTimeAndMetaInfo(
+ parent,
+ index,
+ title,
+ url,
+ Time::Now(),
+ NULL);
+}
+
+const BookmarkNode* BookmarkModel::AddURLWithCreationTimeAndMetaInfo(
+ const BookmarkNode* parent,
+ int index,
+ const base::string16& title,
+ const GURL& url,
+ const Time& creation_time,
+ const BookmarkNode::MetaInfoMap* meta_info) {
+ if (!loaded_ || !url.is_valid() || is_root_node(parent) ||
+ !IsValidIndex(parent, index, true)) {
+ NOTREACHED();
+ return NULL;
+ }
+
+ // Syncing may result in dates newer than the last modified date.
+ if (creation_time > parent->date_folder_modified())
+ SetDateFolderModified(parent, creation_time);
+
+ BookmarkNode* new_node = new BookmarkNode(generate_next_node_id(), url);
+ new_node->SetTitle(title);
+ new_node->set_date_added(creation_time);
+ new_node->set_type(BookmarkNode::URL);
+ if (meta_info)
+ new_node->SetMetaInfoMap(*meta_info);
+
+ return AddNode(AsMutable(parent), index, new_node);
+}
+
+void BookmarkModel::SortChildren(const BookmarkNode* parent) {
+ DCHECK(client_->CanBeEditedByUser(parent));
+
+ if (!parent || !parent->is_folder() || is_root_node(parent) ||
+ parent->child_count() <= 1) {
+ return;
+ }
+
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ OnWillReorderBookmarkNode(this, parent));
+
+ UErrorCode error = U_ZERO_ERROR;
+ scoped_ptr<icu::Collator> collator(icu::Collator::createInstance(error));
+ if (U_FAILURE(error))
+ collator.reset(NULL);
+ BookmarkNode* mutable_parent = AsMutable(parent);
+ std::sort(mutable_parent->children().begin(),
+ mutable_parent->children().end(),
+ SortComparator(collator.get()));
+
+ if (store_.get())
+ store_->ScheduleSave();
+
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ BookmarkNodeChildrenReordered(this, parent));
+}
+
+void BookmarkModel::ReorderChildren(
+ const BookmarkNode* parent,
+ const std::vector<const BookmarkNode*>& ordered_nodes) {
+ DCHECK(client_->CanBeEditedByUser(parent));
+
+ // Ensure that all children in |parent| are in |ordered_nodes|.
+ DCHECK_EQ(static_cast<size_t>(parent->child_count()), ordered_nodes.size());
+ for (size_t i = 0; i < ordered_nodes.size(); ++i)
+ DCHECK_EQ(parent, ordered_nodes[i]->parent());
+
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ OnWillReorderBookmarkNode(this, parent));
+
+ AsMutable(parent)->SetChildren(
+ *(reinterpret_cast<const std::vector<BookmarkNode*>*>(&ordered_nodes)));
+
+ if (store_.get())
+ store_->ScheduleSave();
+
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ BookmarkNodeChildrenReordered(this, parent));
+}
+
+void BookmarkModel::SetDateFolderModified(const BookmarkNode* parent,
+ const Time time) {
+ DCHECK(parent);
+ AsMutable(parent)->set_date_folder_modified(time);
+
+ if (store_.get())
+ store_->ScheduleSave();
+}
+
+void BookmarkModel::ResetDateFolderModified(const BookmarkNode* node) {
+ SetDateFolderModified(node, Time());
+}
+
+void BookmarkModel::GetBookmarksMatching(const base::string16& text,
+ size_t max_count,
+ std::vector<BookmarkMatch>* matches) {
+ GetBookmarksMatching(text, max_count,
+ query_parser::MatchingAlgorithm::DEFAULT, matches);
+}
+
+void BookmarkModel::GetBookmarksMatching(
+ const base::string16& text,
+ size_t max_count,
+ query_parser::MatchingAlgorithm matching_algorithm,
+ std::vector<BookmarkMatch>* matches) {
+ if (!loaded_)
+ return;
+
+ index_->GetBookmarksMatching(text, max_count, matching_algorithm, matches);
+}
+
+void BookmarkModel::ClearStore() {
+ store_.reset();
+}
+
+void BookmarkModel::SetPermanentNodeVisible(BookmarkNode::Type type,
+ bool value) {
+ BookmarkPermanentNode* node = AsMutable(PermanentNode(type));
+ node->set_visible(value || client_->IsPermanentNodeVisible(node));
+}
+
+const BookmarkPermanentNode* BookmarkModel::PermanentNode(
+ BookmarkNode::Type type) {
+ DCHECK(loaded_);
+ switch (type) {
+ case BookmarkNode::BOOKMARK_BAR:
+ return bookmark_bar_node_;
+ case BookmarkNode::OTHER_NODE:
+ return other_node_;
+ case BookmarkNode::MOBILE:
+ return mobile_node_;
+ default:
+ NOTREACHED();
+ return NULL;
+ }
+}
+
+void BookmarkModel::RestoreRemovedNode(const BookmarkNode* parent,
+ int index,
+ scoped_ptr<BookmarkNode> scoped_node) {
+ BookmarkNode* node = scoped_node.release();
+ AddNode(AsMutable(parent), index, node);
+
+ // We might be restoring a folder node that have already contained a set of
+ // child nodes. We need to notify all of them.
+ NotifyNodeAddedForAllDescendents(node);
+}
+
+void BookmarkModel::NotifyNodeAddedForAllDescendents(const BookmarkNode* node) {
+ for (int i = 0; i < node->child_count(); ++i) {
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ BookmarkNodeAdded(this, node, i));
+ NotifyNodeAddedForAllDescendents(node->GetChild(i));
+ }
+}
+
+bool BookmarkModel::IsBookmarkedNoLock(const GURL& url) {
+ BookmarkNode tmp_node(url);
+ return (nodes_ordered_by_url_set_.find(&tmp_node) !=
+ nodes_ordered_by_url_set_.end());
+}
+
+void BookmarkModel::RemoveNode(BookmarkNode* node,
+ std::set<GURL>* removed_urls) {
+ if (!loaded_ || !node || is_permanent_node(node)) {
+ NOTREACHED();
+ return;
+ }
+
+ url_lock_.AssertAcquired();
+ if (node->is_url()) {
+ RemoveNodeFromInternalMaps(node);
+ removed_urls->insert(node->url());
+ }
+
+ CancelPendingFaviconLoadRequests(node);
+
+ // Recurse through children.
+ for (int i = node->child_count() - 1; i >= 0; --i)
+ RemoveNode(node->GetChild(i), removed_urls);
+}
+
+void BookmarkModel::DoneLoading(scoped_ptr<BookmarkLoadDetails> details) {
+ DCHECK(details);
+ if (loaded_) {
+ // We should only ever be loaded once.
+ NOTREACHED();
+ return;
+ }
+
+ // TODO(robliao): Remove ScopedTracker below once https://crbug.com/467179
+ // is fixed.
+ tracked_objects::ScopedTracker tracking_profile1(
+ FROM_HERE_WITH_EXPLICIT_FUNCTION("467179 BookmarkModel::DoneLoading1"));
+
+ next_node_id_ = details->max_id();
+ if (details->computed_checksum() != details->stored_checksum() ||
+ details->ids_reassigned()) {
+ // TODO(robliao): Remove ScopedTracker below once https://crbug.com/467179
+ // is fixed.
+ tracked_objects::ScopedTracker tracking_profile2(
+ FROM_HERE_WITH_EXPLICIT_FUNCTION("467179 BookmarkModel::DoneLoading2"));
+
+ // If bookmarks file changed externally, the IDs may have changed
+ // externally. In that case, the decoder may have reassigned IDs to make
+ // them unique. So when the file has changed externally, we should save the
+ // bookmarks file to persist new IDs.
+ if (store_.get())
+ store_->ScheduleSave();
+ }
+ bookmark_bar_node_ = details->release_bb_node();
+ other_node_ = details->release_other_folder_node();
+ mobile_node_ = details->release_mobile_folder_node();
+ index_.reset(details->release_index());
+
+ // Get any extra nodes and take ownership of them at the |root_|.
+ std::vector<BookmarkPermanentNode*> extra_nodes;
+ details->release_extra_nodes(&extra_nodes);
+
+ // TODO(robliao): Remove ScopedTracker below once https://crbug.com/467179
+ // is fixed.
+ tracked_objects::ScopedTracker tracking_profile3(
+ FROM_HERE_WITH_EXPLICIT_FUNCTION("467179 BookmarkModel::DoneLoading3"));
+
+ // WARNING: order is important here, various places assume the order is
+ // constant (but can vary between embedders with the initial visibility
+ // of permanent nodes).
+ std::vector<BookmarkPermanentNode*> root_children;
+ root_children.push_back(bookmark_bar_node_);
+ root_children.push_back(other_node_);
+ root_children.push_back(mobile_node_);
+ for (size_t i = 0; i < extra_nodes.size(); ++i)
+ root_children.push_back(extra_nodes[i]);
+
+ // TODO(robliao): Remove ScopedTracker below once https://crbug.com/467179
+ // is fixed.
+ tracked_objects::ScopedTracker tracking_profile4(
+ FROM_HERE_WITH_EXPLICIT_FUNCTION("467179 BookmarkModel::DoneLoading4"));
+
+ std::stable_sort(root_children.begin(),
+ root_children.end(),
+ VisibilityComparator(client_.get()));
+ for (size_t i = 0; i < root_children.size(); ++i)
+ root_.Add(root_children[i], static_cast<int>(i));
+
+ root_.SetMetaInfoMap(details->model_meta_info_map());
+ root_.set_sync_transaction_version(details->model_sync_transaction_version());
+
+ // TODO(robliao): Remove ScopedTracker below once https://crbug.com/467179
+ // is fixed.
+ tracked_objects::ScopedTracker tracking_profile5(
+ FROM_HERE_WITH_EXPLICIT_FUNCTION("467179 BookmarkModel::DoneLoading5"));
+
+ {
+ base::AutoLock url_lock(url_lock_);
+ // Update nodes_ordered_by_url_set_ from the nodes.
+ PopulateNodesByURL(&root_);
+ }
+
+ loaded_ = true;
+
+ loaded_signal_.Signal();
+
+ // TODO(robliao): Remove ScopedTracker below once https://crbug.com/467179
+ // is fixed.
+ tracked_objects::ScopedTracker tracking_profile6(
+ FROM_HERE_WITH_EXPLICIT_FUNCTION("467179 BookmarkModel::DoneLoading6"));
+
+ // Notify our direct observers.
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ BookmarkModelLoaded(this, details->ids_reassigned()));
+}
+
+void BookmarkModel::RemoveAndDeleteNode(BookmarkNode* delete_me) {
+ scoped_ptr<BookmarkNode> node(delete_me);
+
+ const BookmarkNode* parent = node->parent();
+ DCHECK(parent);
+ int index = parent->GetIndexOf(node.get());
+ DCHECK_NE(-1, index);
+
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ OnWillRemoveBookmarks(this, parent, index, node.get()));
+
+ std::set<GURL> removed_urls;
+ {
+ base::AutoLock url_lock(url_lock_);
+ RemoveNodeAndGetRemovedUrls(node.get(), &removed_urls);
+ }
+
+ if (store_.get())
+ store_->ScheduleSave();
+
+ FOR_EACH_OBSERVER(
+ BookmarkModelObserver,
+ observers_,
+ BookmarkNodeRemoved(this, parent, index, node.get(), removed_urls));
+
+ undo_delegate()->OnBookmarkNodeRemoved(this, parent, index, std::move(node));
+}
+
+void BookmarkModel::RemoveNodeFromInternalMaps(BookmarkNode* node) {
+ index_->Remove(node);
+ // NOTE: this is called in such a way that url_lock_ is already held. As
+ // such, this doesn't explicitly grab the lock.
+ url_lock_.AssertAcquired();
+ NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.find(node);
+ DCHECK(i != nodes_ordered_by_url_set_.end());
+ // i points to the first node with the URL, advance until we find the
+ // node we're removing.
+ while (*i != node)
+ ++i;
+ nodes_ordered_by_url_set_.erase(i);
+}
+
+void BookmarkModel::RemoveNodeAndGetRemovedUrls(BookmarkNode* node,
+ std::set<GURL>* removed_urls) {
+ // NOTE: this method should be always called with |url_lock_| held.
+ // This method does not explicitly acquires a lock.
+ url_lock_.AssertAcquired();
+ DCHECK(removed_urls);
+ BookmarkNode* parent = node->parent();
+ DCHECK(parent);
+ parent->Remove(node);
+ RemoveNode(node, removed_urls);
+ // RemoveNode adds an entry to removed_urls for each node of type URL. As we
+ // allow duplicates we need to remove any entries that are still bookmarked.
+ for (std::set<GURL>::iterator i = removed_urls->begin();
+ i != removed_urls->end();) {
+ if (IsBookmarkedNoLock(*i)) {
+ // When we erase the iterator pointing at the erasee is
+ // invalidated, so using i++ here within the "erase" call is
+ // important as it advances the iterator before passing the
+ // old value through to erase.
+ removed_urls->erase(i++);
+ } else {
+ ++i;
+ }
+ }
+}
+
+BookmarkNode* BookmarkModel::AddNode(BookmarkNode* parent,
+ int index,
+ BookmarkNode* node) {
+ parent->Add(node, index);
+
+ if (store_.get())
+ store_->ScheduleSave();
+
+ {
+ base::AutoLock url_lock(url_lock_);
+ AddNodeToInternalMaps(node);
+ }
+
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ BookmarkNodeAdded(this, parent, index));
+
+ return node;
+}
+
+void BookmarkModel::AddNodeToInternalMaps(BookmarkNode* node) {
+ url_lock_.AssertAcquired();
+ if (node->is_url()) {
+ index_->Add(node);
+ nodes_ordered_by_url_set_.insert(node);
+ }
+ for (int i = 0; i < node->child_count(); ++i)
+ AddNodeToInternalMaps(node->GetChild(i));
+}
+
+bool BookmarkModel::IsValidIndex(const BookmarkNode* parent,
+ int index,
+ bool allow_end) {
+ return (parent && parent->is_folder() &&
+ (index >= 0 && (index < parent->child_count() ||
+ (allow_end && index == parent->child_count()))));
+}
+
+BookmarkPermanentNode* BookmarkModel::CreatePermanentNode(
+ BookmarkNode::Type type) {
+ DCHECK(type == BookmarkNode::BOOKMARK_BAR ||
+ type == BookmarkNode::OTHER_NODE ||
+ type == BookmarkNode::MOBILE);
+ BookmarkPermanentNode* node =
+ new BookmarkPermanentNode(generate_next_node_id());
+ node->set_type(type);
+ node->set_visible(client_->IsPermanentNodeVisible(node));
+
+ int title_id;
+ switch (type) {
+ case BookmarkNode::BOOKMARK_BAR:
+ title_id = IDS_BOOKMARK_BAR_FOLDER_NAME;
+ break;
+ case BookmarkNode::OTHER_NODE:
+ title_id = IDS_BOOKMARK_BAR_OTHER_FOLDER_NAME;
+ break;
+ case BookmarkNode::MOBILE:
+ title_id = IDS_BOOKMARK_BAR_MOBILE_FOLDER_NAME;
+ break;
+ default:
+ NOTREACHED();
+ title_id = IDS_BOOKMARK_BAR_FOLDER_NAME;
+ break;
+ }
+ node->SetTitle(l10n_util::GetStringUTF16(title_id));
+ return node;
+}
+
+void BookmarkModel::OnFaviconDataAvailable(
+ BookmarkNode* node,
+ favicon_base::IconType icon_type,
+ const favicon_base::FaviconImageResult& image_result) {
+ DCHECK(node);
+ node->set_favicon_load_task_id(base::CancelableTaskTracker::kBadTaskId);
+ node->set_favicon_state(BookmarkNode::LOADED_FAVICON);
+ if (!image_result.image.IsEmpty()) {
+ node->set_favicon_type(icon_type);
+ node->set_favicon(image_result.image);
+ node->set_icon_url(image_result.icon_url);
+ FaviconLoaded(node);
+ } else if (icon_type == favicon_base::TOUCH_ICON) {
+ // Couldn't load the touch icon, fallback to the regular favicon.
+ DCHECK(client_->PreferTouchIcon());
+ LoadFavicon(node, favicon_base::FAVICON);
+ }
+}
+
+void BookmarkModel::LoadFavicon(BookmarkNode* node,
+ favicon_base::IconType icon_type) {
+ if (node->is_folder())
+ return;
+
+ DCHECK(node->url().is_valid());
+ node->set_favicon_state(BookmarkNode::LOADING_FAVICON);
+ base::CancelableTaskTracker::TaskId taskId =
+ client_->GetFaviconImageForPageURL(
+ node->url(),
+ icon_type,
+ base::Bind(
+ &BookmarkModel::OnFaviconDataAvailable,
+ base::Unretained(this),
+ node,
+ icon_type),
+ &cancelable_task_tracker_);
+ if (taskId != base::CancelableTaskTracker::kBadTaskId)
+ node->set_favicon_load_task_id(taskId);
+}
+
+void BookmarkModel::FaviconLoaded(const BookmarkNode* node) {
+ FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
+ BookmarkNodeFaviconChanged(this, node));
+}
+
+void BookmarkModel::CancelPendingFaviconLoadRequests(BookmarkNode* node) {
+ if (node->favicon_load_task_id() != base::CancelableTaskTracker::kBadTaskId) {
+ cancelable_task_tracker_.TryCancel(node->favicon_load_task_id());
+ node->set_favicon_load_task_id(base::CancelableTaskTracker::kBadTaskId);
+ }
+}
+
+void BookmarkModel::PopulateNodesByURL(BookmarkNode* 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->child_count(); ++i)
+ PopulateNodesByURL(node->GetChild(i));
+}
+
+int64_t BookmarkModel::generate_next_node_id() {
+ return next_node_id_++;
+}
+
+scoped_ptr<BookmarkLoadDetails> BookmarkModel::CreateLoadDetails() {
+ BookmarkPermanentNode* bb_node =
+ CreatePermanentNode(BookmarkNode::BOOKMARK_BAR);
+ BookmarkPermanentNode* other_node =
+ CreatePermanentNode(BookmarkNode::OTHER_NODE);
+ BookmarkPermanentNode* mobile_node =
+ CreatePermanentNode(BookmarkNode::MOBILE);
+ return scoped_ptr<BookmarkLoadDetails>(new BookmarkLoadDetails(
+ bb_node,
+ other_node,
+ mobile_node,
+ client_->GetLoadExtraNodesCallback(),
+ new BookmarkIndex(client_.get()),
+ next_node_id_));
+}
+
+void BookmarkModel::SetUndoDelegate(BookmarkUndoDelegate* undo_delegate) {
+ undo_delegate_ = undo_delegate;
+ if (undo_delegate_)
+ undo_delegate_->SetUndoProvider(this);
+}
+
+BookmarkUndoDelegate* BookmarkModel::undo_delegate() const {
+ return undo_delegate_ ? undo_delegate_ : empty_undo_delegate_.get();
+}
+
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/browser/bookmark_model.h b/chromium/components/bookmarks/browser/bookmark_model.h
new file mode 100644
index 00000000000..089f95ddec6
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_model.h
@@ -0,0 +1,474 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_MODEL_H_
+#define COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_MODEL_H_
+
+#include <stddef.h>
+#include <stdint.h>
+#include <map>
+#include <set>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "base/strings/string16.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "components/bookmarks/browser/bookmark_client.h"
+#include "components/bookmarks/browser/bookmark_node.h"
+#include "components/bookmarks/browser/bookmark_undo_provider.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "ui/gfx/image/image.h"
+#include "url/gurl.h"
+
+class PrefService;
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+}
+
+namespace favicon_base {
+struct FaviconImageResult;
+}
+
+namespace query_parser {
+enum class MatchingAlgorithm;
+}
+
+namespace bookmarks {
+
+class BookmarkCodecTest;
+class BookmarkExpandedStateTracker;
+class BookmarkIndex;
+class BookmarkLoadDetails;
+class BookmarkModelObserver;
+class BookmarkStorage;
+class BookmarkUndoDelegate;
+class ScopedGroupBookmarkActions;
+class TestBookmarkClient;
+struct BookmarkMatch;
+
+// BookmarkModel --------------------------------------------------------------
+
+// BookmarkModel provides a directed acyclic graph of URLs and folders.
+// Three graphs are provided for the three entry points: those on the 'bookmarks
+// bar', those in the 'other bookmarks' folder and those in the 'mobile' folder.
+//
+// An observer may be attached to observe relevant events.
+//
+// You should NOT directly create a BookmarkModel, instead go through the
+// BookmarkModelFactory.
+class BookmarkModel : public BookmarkUndoProvider,
+ public KeyedService {
+ public:
+ struct URLAndTitle {
+ GURL url;
+ base::string16 title;
+ };
+
+ explicit BookmarkModel(scoped_ptr<BookmarkClient> client);
+ ~BookmarkModel() override;
+
+ // KeyedService:
+ void Shutdown() override;
+
+ // Loads the bookmarks. This is called upon creation of the
+ // BookmarkModel. You need not invoke this directly.
+ // All load operations will be executed on |io_task_runner| and the completion
+ // callback will be called from |ui_task_runner|.
+ void Load(PrefService* pref_service,
+ const base::FilePath& profile_path,
+ const scoped_refptr<base::SequencedTaskRunner>& io_task_runner,
+ const scoped_refptr<base::SequencedTaskRunner>& ui_task_runner);
+
+ // Returns true if the model finished loading.
+ bool loaded() const { return loaded_; }
+
+ // Returns the root node. The 'bookmark bar' node and 'other' node are
+ // children of the root node.
+ const BookmarkNode* root_node() const { return &root_; }
+
+ // Returns the 'bookmark bar' node. This is NULL until loaded.
+ const BookmarkNode* bookmark_bar_node() const { return bookmark_bar_node_; }
+
+ // Returns the 'other' node. This is NULL until loaded.
+ const BookmarkNode* other_node() const { return other_node_; }
+
+ // Returns the 'mobile' node. This is NULL until loaded.
+ const BookmarkNode* mobile_node() const { return mobile_node_; }
+
+ bool is_root_node(const BookmarkNode* node) const { return node == &root_; }
+
+ // Returns whether the given |node| is one of the permanent nodes - root node,
+ // 'bookmark bar' node, 'other' node or 'mobile' node, or one of the root
+ // nodes supplied by the |client_|.
+ bool is_permanent_node(const BookmarkNode* node) const {
+ return node && (node == &root_ || node->parent() == &root_);
+ }
+
+ // Returns the parent the last node was added to. This never returns NULL
+ // (as long as the model is loaded).
+ const BookmarkNode* GetParentForNewNodes();
+
+ void AddObserver(BookmarkModelObserver* observer);
+ void RemoveObserver(BookmarkModelObserver* observer);
+
+ // Notifies the observers that an extensive set of changes is about to happen,
+ // such as during import or sync, so they can delay any expensive UI updates
+ // until it's finished.
+ void BeginExtensiveChanges();
+ void EndExtensiveChanges();
+
+ // Returns true if this bookmark model is currently in a mode where extensive
+ // changes might happen, such as for import and sync. This is helpful for
+ // observers that are created after the mode has started, and want to check
+ // state during their own initializer, such as the NTP.
+ bool IsDoingExtensiveChanges() const { return extensive_changes_ > 0; }
+
+ // Removes |node| from the model and deletes it. Removing a folder node
+ // recursively removes all nodes. Observers are notified immediately.
+ void Remove(const BookmarkNode* node);
+
+ // Removes all the non-permanent bookmark nodes that are editable by the user.
+ // Observers are only notified when all nodes have been removed. There is no
+ // notification for individual node removals.
+ void RemoveAllUserBookmarks();
+
+ // Moves |node| to |new_parent| and inserts it at the given |index|.
+ void Move(const BookmarkNode* node,
+ const BookmarkNode* new_parent,
+ int index);
+
+ // Inserts a copy of |node| into |new_parent| at |index|.
+ void Copy(const BookmarkNode* node,
+ const BookmarkNode* new_parent,
+ int index);
+
+ // Returns the favicon for |node|. If the favicon has not yet been
+ // loaded it is loaded and the observer of the model notified when done.
+ const gfx::Image& GetFavicon(const BookmarkNode* node);
+
+ // Returns the type of the favicon for |node|. If the favicon has not yet
+ // been loaded, it returns |favicon_base::INVALID_ICON|.
+ favicon_base::IconType GetFaviconType(const BookmarkNode* node);
+
+ // Sets the title of |node|.
+ void SetTitle(const BookmarkNode* node, const base::string16& title);
+
+ // Sets the URL of |node|.
+ void SetURL(const BookmarkNode* node, const GURL& url);
+
+ // Sets the date added time of |node|.
+ void SetDateAdded(const BookmarkNode* node, base::Time date_added);
+
+ // Returns the set of nodes with the |url|.
+ void GetNodesByURL(const GURL& url, std::vector<const BookmarkNode*>* nodes);
+
+ // Returns the most recently added user node for the |url|; urls from any
+ // nodes that are not editable by the user are never returned by this call.
+ // Returns NULL if |url| is not bookmarked.
+ const BookmarkNode* GetMostRecentlyAddedUserNodeForURL(const GURL& url);
+
+ // Returns true if there are bookmarks, otherwise returns false.
+ // This method is thread safe.
+ bool HasBookmarks();
+
+ // Returns true if the specified URL is bookmarked.
+ //
+ // If not on the main thread you *must* invoke BlockTillLoaded first.
+ bool IsBookmarked(const GURL& url);
+
+ // Returns, by reference in |bookmarks|, the set of bookmarked urls and their
+ // titles. This returns the unique set of URLs. For example, if two bookmarks
+ // reference the same URL only one entry is added not matter the titles are
+ // same or not.
+ //
+ // If not on the main thread you *must* invoke BlockTillLoaded first.
+ void GetBookmarks(std::vector<BookmarkModel::URLAndTitle>* urls);
+
+ // Blocks until loaded. This is intended for usage on a thread other than
+ // the main thread.
+ void BlockTillLoaded();
+
+ // Adds a new folder node at the specified position.
+ const BookmarkNode* AddFolder(const BookmarkNode* parent,
+ int index,
+ const base::string16& title);
+
+ // Adds a new folder with meta info.
+ const BookmarkNode* AddFolderWithMetaInfo(
+ const BookmarkNode* parent,
+ int index,
+ const base::string16& title,
+ const BookmarkNode::MetaInfoMap* meta_info);
+
+ // Adds a url at the specified position.
+ const BookmarkNode* AddURL(const BookmarkNode* parent,
+ int index,
+ const base::string16& title,
+ const GURL& url);
+
+ // Adds a url with a specific creation date and meta info.
+ const BookmarkNode* AddURLWithCreationTimeAndMetaInfo(
+ const BookmarkNode* parent,
+ int index,
+ const base::string16& title,
+ const GURL& url,
+ const base::Time& creation_time,
+ const BookmarkNode::MetaInfoMap* meta_info);
+
+ // Sorts the children of |parent|, notifying observers by way of the
+ // BookmarkNodeChildrenReordered method.
+ void SortChildren(const BookmarkNode* parent);
+
+ // Order the children of |parent| as specified in |ordered_nodes|. This
+ // function should only be used to reorder the child nodes of |parent| and
+ // is not meant to move nodes between different parent. Notifies observers
+ // using the BookmarkNodeChildrenReordered method.
+ void ReorderChildren(const BookmarkNode* parent,
+ const std::vector<const BookmarkNode*>& ordered_nodes);
+
+ // Sets the date when the folder was modified.
+ void SetDateFolderModified(const BookmarkNode* node, const base::Time time);
+
+ // Resets the 'date modified' time of the node to 0. This is used during
+ // importing to exclude the newly created folders from showing up in the
+ // combobox of most recently modified folders.
+ void ResetDateFolderModified(const BookmarkNode* node);
+
+ // Returns up to |max_count| of bookmarks containing each term from |text|
+ // in either the title or the URL. It uses default matching algorithm.
+ void GetBookmarksMatching(const base::string16& text,
+ size_t max_count,
+ std::vector<BookmarkMatch>* matches);
+
+ // Returns up to |max_count| of bookmarks containing each term from |text|
+ // in either the title or the URL.
+ void GetBookmarksMatching(const base::string16& text,
+ size_t max_count,
+ query_parser::MatchingAlgorithm matching_algorithm,
+ std::vector<BookmarkMatch>* matches);
+
+ // Sets the store to NULL, making it so the BookmarkModel does not persist
+ // any changes to disk. This is only useful during testing to speed up
+ // testing.
+ void ClearStore();
+
+ // Returns the next node ID.
+ int64_t next_node_id() const { return next_node_id_; }
+
+ // Returns the object responsible for tracking the set of expanded nodes in
+ // the bookmark editor.
+ BookmarkExpandedStateTracker* expanded_state_tracker() {
+ return expanded_state_tracker_.get();
+ }
+
+ // Sets the visibility of one of the permanent nodes (unless the node must
+ // always be visible, see |BookmarkClient::IsPermanentNodeVisible| for more
+ // details). This is set by sync.
+ void SetPermanentNodeVisible(BookmarkNode::Type type, bool value);
+
+ // Returns the permanent node of type |type|.
+ const BookmarkPermanentNode* PermanentNode(BookmarkNode::Type type);
+
+ // Sets/deletes meta info of |node|.
+ void SetNodeMetaInfo(const BookmarkNode* node,
+ const std::string& key,
+ const std::string& value);
+ void SetNodeMetaInfoMap(const BookmarkNode* node,
+ const BookmarkNode::MetaInfoMap& meta_info_map);
+ void DeleteNodeMetaInfo(const BookmarkNode* node,
+ const std::string& key);
+
+ // Adds |key| to the set of meta info keys that are not copied when a node is
+ // cloned.
+ void AddNonClonedKey(const std::string& key);
+
+ // Returns the set of meta info keys that should not be copied when a node is
+ // cloned.
+ const std::set<std::string>& non_cloned_keys() const {
+ return non_cloned_keys_;
+ }
+
+ // Sets the sync transaction version of |node|.
+ void SetNodeSyncTransactionVersion(const BookmarkNode* node,
+ int64_t sync_transaction_version);
+
+ // Notify BookmarkModel that the favicons for the given page URLs (e.g.
+ // http://www.google.com) and the given icon URL (e.g.
+ // http://www.google.com/favicon.ico) have changed. It is valid to call
+ // OnFaviconsChanged() with non-empty |page_urls| and an empty |icon_url| and
+ // vice versa.
+ void OnFaviconsChanged(const std::set<GURL>& page_urls,
+ const GURL& icon_url);
+
+ // Returns the client used by this BookmarkModel.
+ BookmarkClient* client() const { return client_.get(); }
+
+ void SetUndoDelegate(BookmarkUndoDelegate* undo_delegate);
+
+ private:
+ friend class BookmarkCodecTest;
+ friend class BookmarkModelFaviconTest;
+ friend class BookmarkStorage;
+ friend class ScopedGroupBookmarkActions;
+ friend class TestBookmarkClient;
+
+ // Used to order BookmarkNodes by URL.
+ class NodeURLComparator {
+ public:
+ bool operator()(const BookmarkNode* n1, const BookmarkNode* n2) const {
+ return n1->url() < n2->url();
+ }
+ };
+
+ // BookmarkUndoProvider:
+ void RestoreRemovedNode(const BookmarkNode* parent,
+ int index,
+ scoped_ptr<BookmarkNode> node) override;
+
+ // Notifies the observers for adding every descedent of |node|.
+ void NotifyNodeAddedForAllDescendents(const BookmarkNode* node);
+
+ // Implementation of IsBookmarked. Before calling this the caller must obtain
+ // a lock on |url_lock_|.
+ bool IsBookmarkedNoLock(const GURL& url);
+
+ // Removes the node from internal maps and recurses through all children. If
+ // the node is a url, its url is added to removed_urls.
+ //
+ // This does NOT delete the node.
+ void RemoveNode(BookmarkNode* node, std::set<GURL>* removed_urls);
+
+ // Invoked when loading is finished. Sets |loaded_| and notifies observers.
+ // BookmarkModel takes ownership of |details|.
+ void DoneLoading(scoped_ptr<BookmarkLoadDetails> details);
+
+ // Populates |nodes_ordered_by_url_set_| from root.
+ void PopulateNodesByURL(BookmarkNode* node);
+
+ // Removes the node from its parent, but does not delete it. No notifications
+ // are sent. |removed_urls| is populated with the urls which no longer have
+ // any bookmarks associated with them.
+ // This method should be called after acquiring |url_lock_|.
+ void RemoveNodeAndGetRemovedUrls(BookmarkNode* node,
+ std::set<GURL>* removed_urls);
+
+ // Removes the node from its parent, sends notification, and deletes it.
+ // type specifies how the node should be removed.
+ void RemoveAndDeleteNode(BookmarkNode* delete_me);
+
+ // Remove |node| from |nodes_ordered_by_url_set_| and |index_|.
+ void RemoveNodeFromInternalMaps(BookmarkNode* node);
+
+ // Adds the |node| at |parent| in the specified |index| and notifies its
+ // observers.
+ BookmarkNode* AddNode(BookmarkNode* parent,
+ int index,
+ BookmarkNode* node);
+
+ // Adds the |node| to |nodes_ordered_by_url_set_| and |index_|.
+ void AddNodeToInternalMaps(BookmarkNode* node);
+
+ // Returns true if the parent and index are valid.
+ bool IsValidIndex(const BookmarkNode* parent, int index, bool allow_end);
+
+ // Creates one of the possible permanent nodes (bookmark bar node, other node
+ // and mobile node) from |type|.
+ BookmarkPermanentNode* CreatePermanentNode(BookmarkNode::Type type);
+
+ // Notification that a favicon has finished loading. If we can decode the
+ // favicon, FaviconLoaded is invoked.
+ void OnFaviconDataAvailable(
+ BookmarkNode* node,
+ favicon_base::IconType icon_type,
+ const favicon_base::FaviconImageResult& image_result);
+
+ // Invoked from the node to load the favicon. Requests the favicon from the
+ // favicon service.
+ void LoadFavicon(BookmarkNode* node, favicon_base::IconType icon_type);
+
+ // Called to notify the observers that the favicon has been loaded.
+ void FaviconLoaded(const BookmarkNode* node);
+
+ // If we're waiting on a favicon for node, the load request is canceled.
+ void CancelPendingFaviconLoadRequests(BookmarkNode* node);
+
+ // Notifies the observers that a set of changes initiated by a single user
+ // action is about to happen and has completed.
+ void BeginGroupedChanges();
+ void EndGroupedChanges();
+
+ // Generates and returns the next node ID.
+ int64_t generate_next_node_id();
+
+ // Sets the maximum node ID to the given value.
+ // This is used by BookmarkCodec to report the maximum ID after it's done
+ // decoding since during decoding codec assigns node IDs.
+ void set_next_node_id(int64_t id) { next_node_id_ = id; }
+
+ // Creates and returns a new BookmarkLoadDetails. It's up to the caller to
+ // delete the returned object.
+ scoped_ptr<BookmarkLoadDetails> CreateLoadDetails();
+
+ BookmarkUndoDelegate* undo_delegate() const;
+
+ scoped_ptr<BookmarkClient> client_;
+
+ // Whether the initial set of data has been loaded.
+ bool loaded_;
+
+ // The root node. This contains the bookmark bar node, the 'other' node and
+ // the mobile node as children.
+ BookmarkNode root_;
+
+ BookmarkPermanentNode* bookmark_bar_node_;
+ BookmarkPermanentNode* other_node_;
+ BookmarkPermanentNode* mobile_node_;
+
+ // The maximum ID assigned to the bookmark nodes in the model.
+ int64_t next_node_id_;
+
+ // The observers.
+ base::ObserverList<BookmarkModelObserver> observers_;
+
+ // 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::multiset<BookmarkNode*, NodeURLComparator> NodesOrderedByURLSet;
+ NodesOrderedByURLSet nodes_ordered_by_url_set_;
+ base::Lock url_lock_;
+
+ // Used for loading favicons.
+ base::CancelableTaskTracker cancelable_task_tracker_;
+
+ // Reads/writes bookmarks to disk.
+ scoped_ptr<BookmarkStorage> store_;
+
+ scoped_ptr<BookmarkIndex> index_;
+
+ base::WaitableEvent loaded_signal_;
+
+ // See description of IsDoingExtensiveChanges above.
+ int extensive_changes_;
+
+ scoped_ptr<BookmarkExpandedStateTracker> expanded_state_tracker_;
+
+ std::set<std::string> non_cloned_keys_;
+
+ BookmarkUndoDelegate* undo_delegate_;
+ scoped_ptr<BookmarkUndoDelegate> empty_undo_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(BookmarkModel);
+};
+
+} // namespace bookmarks
+
+#endif // COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_MODEL_H_
diff --git a/chromium/components/bookmarks/browser/bookmark_model_observer.h b/chromium/components/bookmarks/browser/bookmark_model_observer.h
new file mode 100644
index 00000000000..a946d532ac8
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_model_observer.h
@@ -0,0 +1,143 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_MODEL_OBSERVER_H_
+#define COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_MODEL_OBSERVER_H_
+
+#include <set>
+
+class GURL;
+
+namespace bookmarks {
+
+class BookmarkModel;
+class BookmarkNode;
+
+// Observer for the BookmarkModel.
+class BookmarkModelObserver {
+ public:
+ // Invoked when the model has finished loading. |ids_reassigned| mirrors
+ // that of BookmarkLoadDetails::ids_reassigned. See it for details.
+ virtual void BookmarkModelLoaded(BookmarkModel* model,
+ bool ids_reassigned) = 0;
+
+ // Invoked from the destructor of the BookmarkModel.
+ virtual void BookmarkModelBeingDeleted(BookmarkModel* model) {}
+
+ // Invoked when a node has moved.
+ virtual void BookmarkNodeMoved(BookmarkModel* model,
+ const BookmarkNode* old_parent,
+ int old_index,
+ const BookmarkNode* new_parent,
+ int new_index) = 0;
+
+ // Invoked when a node has been added.
+ virtual void BookmarkNodeAdded(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int index) = 0;
+
+ // Invoked prior to removing a node from the model. When a node is removed
+ // it's descendants are implicitly removed from the model as
+ // well. Notification is only sent for the node itself, not any
+ // descendants. For example, if folder 'A' has the children 'A1' and 'A2',
+ // model->Remove('A') generates a single notification for 'A'; no notification
+ // is sent for 'A1' or 'A2'.
+ //
+ // |parent| the parent of the node that will be removed.
+ // |old_index| the index of the node about to be removed in |parent|.
+ // |node| is the node to be removed.
+ virtual void OnWillRemoveBookmarks(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int old_index,
+ const BookmarkNode* node) {}
+
+ // Invoked after a node has been removed from the model. Removing a node
+ // implicitly removes all descendants. Notification is only sent for the node
+ // that BookmarkModel::Remove() is invoked on. See description of
+ // OnWillRemoveBookmarks() for details.
+ //
+ // |parent| the parent of the node that was removed.
+ // |old_index| the index of the removed node in |parent| before it was
+ // removed.
+ // |node| the node that was removed.
+ // |no_longer_bookmarked| contains the urls of any nodes that are no longer
+ // bookmarked as a result of the removal.
+ virtual void BookmarkNodeRemoved(
+ BookmarkModel* model,
+ const BookmarkNode* parent,
+ int old_index,
+ const BookmarkNode* node,
+ const std::set<GURL>& no_longer_bookmarked) = 0;
+
+ // Invoked before the title or url of a node is changed.
+ virtual void OnWillChangeBookmarkNode(BookmarkModel* model,
+ const BookmarkNode* node) {}
+
+ // Invoked when the title or url of a node changes.
+ virtual void BookmarkNodeChanged(BookmarkModel* model,
+ const BookmarkNode* node) = 0;
+
+ // Invoked before the metainfo of a node is changed.
+ virtual void OnWillChangeBookmarkMetaInfo(BookmarkModel* model,
+ const BookmarkNode* node) {}
+
+ // Invoked when the metainfo on a node changes.
+ virtual void BookmarkMetaInfoChanged(BookmarkModel* model,
+ const BookmarkNode* node) {}
+
+ // Invoked when a favicon has been loaded or changed.
+ virtual void BookmarkNodeFaviconChanged(BookmarkModel* model,
+ const BookmarkNode* node) = 0;
+
+ // Invoked before the direct children of |node| have been reordered in some
+ // way, such as sorted.
+ virtual void OnWillReorderBookmarkNode(BookmarkModel* model,
+ const BookmarkNode* node) {}
+
+ // Invoked when the children (just direct children, not descendants) of
+ // |node| have been reordered in some way, such as sorted.
+ virtual void BookmarkNodeChildrenReordered(BookmarkModel* model,
+ const BookmarkNode* node) = 0;
+
+ // Invoked before an extensive set of model changes is about to begin.
+ // This tells UI intensive observers to wait until the updates finish to
+ // update themselves.
+ // These methods should only be used for imports and sync.
+ // Observers should still respond to BookmarkNodeRemoved immediately,
+ // to avoid holding onto stale node pointers.
+ virtual void ExtensiveBookmarkChangesBeginning(BookmarkModel* model) {}
+
+ // Invoked after an extensive set of model changes has ended.
+ // This tells observers to update themselves if they were waiting for the
+ // update to finish.
+ virtual void ExtensiveBookmarkChangesEnded(BookmarkModel* model) {}
+
+ // Invoked before all non-permanent bookmark nodes that are editable by
+ // the user are removed.
+ virtual void OnWillRemoveAllUserBookmarks(BookmarkModel* model) {}
+
+ // Invoked when all non-permanent bookmark nodes that are editable by the
+ // user have been removed.
+ // |removed_urls| is populated with the urls which no longer have any
+ // bookmarks associated with them.
+ virtual void BookmarkAllUserNodesRemoved(
+ BookmarkModel* model,
+ const std::set<GURL>& removed_urls) = 0;
+
+ // Invoked before a set of model changes that is initiated by a single user
+ // action. For example, this is called a single time when pasting from the
+ // clipboard before each pasted bookmark is added to the bookmark model.
+ virtual void GroupedBookmarkChangesBeginning(BookmarkModel* model) {}
+
+ // Invoked after a set of model changes triggered by a single user action has
+ // ended.
+ virtual void GroupedBookmarkChangesEnded(BookmarkModel* model) {}
+
+ protected:
+ virtual ~BookmarkModelObserver() {}
+};
+
+} // namespace bookmarks
+
+#endif // COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_MODEL_OBSERVER_H_
diff --git a/chromium/components/bookmarks/browser/bookmark_model_unittest.cc b/chromium/components/bookmarks/browser/bookmark_model_unittest.cc
new file mode 100644
index 00000000000..906766596b5
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_model_unittest.cc
@@ -0,0 +1,1403 @@
+// Copyright 2014 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 "components/bookmarks/browser/bookmark_model.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <set>
+#include <string>
+#include <utility>
+
+#include "base/base_paths.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/containers/hash_tables.h"
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "components/bookmarks/browser/bookmark_model_observer.h"
+#include "components/bookmarks/browser/bookmark_undo_delegate.h"
+#include "components/bookmarks/browser/bookmark_utils.h"
+#include "components/bookmarks/test/bookmark_test_helpers.h"
+#include "components/bookmarks/test/test_bookmark_client.h"
+#include "components/favicon_base/favicon_callback.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/models/tree_node_iterator.h"
+#include "ui/base/models/tree_node_model.h"
+#include "ui/gfx/image/image.h"
+#include "url/gurl.h"
+
+using base::ASCIIToUTF16;
+using base::Time;
+using base::TimeDelta;
+
+namespace bookmarks {
+namespace {
+
+// Test cases used to test the removal of extra whitespace when adding
+// a new folder/bookmark or updating a title of a folder/bookmark.
+// Note that whitespace characters are all replaced with spaces, but spaces are
+// not collapsed or trimmed.
+static struct {
+ const std::string input_title;
+ const std::string expected_title;
+} url_whitespace_test_cases[] = {
+ {"foobar", "foobar"},
+ // Newlines.
+ {"foo\nbar", "foo bar"},
+ {"foo\n\nbar", "foo bar"},
+ {"foo\n\n\nbar", "foo bar"},
+ {"foo\r\nbar", "foo bar"},
+ {"foo\r\n\r\nbar", "foo bar"},
+ {"\nfoo\nbar\n", " foo bar "},
+ // Spaces should not collapse.
+ {"foo bar", "foo bar"},
+ {" foo bar ", " foo bar "},
+ {" foo bar ", " foo bar "},
+ // Tabs.
+ {"\tfoo\tbar\t", " foo bar "},
+ {"\tfoo bar\t", " foo bar "},
+ // Mixed cases.
+ {"\tfoo\nbar\t", " foo bar "},
+ {"\tfoo\r\nbar\t", " foo bar "},
+ {" foo\tbar\n", " foo bar "},
+ {"\t foo \t bar \t", " foo bar "},
+ {"\n foo\r\n\tbar\n \t", " foo bar "},
+};
+
+// Test cases used to test the removal of extra whitespace when adding
+// a new folder/bookmark or updating a title of a folder/bookmark.
+static struct {
+ const std::string input_title;
+ const std::string expected_title;
+} title_whitespace_test_cases[] = {
+ {"foobar", "foobar"},
+ // Newlines.
+ {"foo\nbar", "foo bar"},
+ {"foo\n\nbar", "foo bar"},
+ {"foo\n\n\nbar", "foo bar"},
+ {"foo\r\nbar", "foo bar"},
+ {"foo\r\n\r\nbar", "foo bar"},
+ {"\nfoo\nbar\n", " foo bar "},
+ // Spaces.
+ {"foo bar", "foo bar"},
+ {" foo bar ", " foo bar "},
+ {" foo bar ", " foo bar "},
+ // Tabs.
+ {"\tfoo\tbar\t", " foo bar "},
+ {"\tfoo bar\t", " foo bar "},
+ // Mixed cases.
+ {"\tfoo\nbar\t", " foo bar "},
+ {"\tfoo\r\nbar\t", " foo bar "},
+ {" foo\tbar\n", " foo bar "},
+ {"\t foo \t bar \t", " foo bar "},
+ {"\n foo\r\n\tbar\n \t", " foo bar "},
+};
+
+// Helper to get a mutable bookmark node.
+BookmarkNode* AsMutable(const BookmarkNode* node) {
+ return const_cast<BookmarkNode*>(node);
+}
+
+void SwapDateAdded(BookmarkNode* n1, BookmarkNode* n2) {
+ Time tmp = n1->date_added();
+ n1->set_date_added(n2->date_added());
+ n2->set_date_added(tmp);
+}
+
+// See comment in PopulateNodeFromString.
+using TestNode = ui::TreeNodeWithValue<BookmarkNode::Type>;
+
+// Does the work of PopulateNodeFromString. index gives the index of the current
+// element in description to process.
+void PopulateNodeImpl(const std::vector<std::string>& description,
+ size_t* index,
+ TestNode* parent) {
+ while (*index < description.size()) {
+ const std::string& element = description[*index];
+ (*index)++;
+ if (element == "[") {
+ // Create a new folder and recurse to add all the children.
+ // Folders are given a unique named by way of an ever increasing integer
+ // value. The folders need not have a name, but one is assigned to help
+ // in debugging.
+ static int next_folder_id = 1;
+ TestNode* new_node = new TestNode(base::IntToString16(next_folder_id++),
+ BookmarkNode::FOLDER);
+ parent->Add(new_node, parent->child_count());
+ PopulateNodeImpl(description, index, new_node);
+ } else if (element == "]") {
+ // End the current folder.
+ return;
+ } else {
+ // Add a new URL.
+
+ // All tokens must be space separated. If there is a [ or ] in the name it
+ // likely means a space was forgotten.
+ DCHECK(element.find('[') == std::string::npos);
+ DCHECK(element.find(']') == std::string::npos);
+ parent->Add(new TestNode(base::UTF8ToUTF16(element), BookmarkNode::URL),
+ parent->child_count());
+ }
+ }
+}
+
+// Creates and adds nodes to parent based on description. description consists
+// of the following tokens (all space separated):
+// [ : creates a new USER_FOLDER node. All elements following the [ until the
+// next balanced ] is encountered are added as children to the node.
+// ] : closes the last folder created by [ so that any further nodes are added
+// to the current folders parent.
+// text: creates a new URL node.
+// For example, "a [b] c" creates the following nodes:
+// a 1 c
+// |
+// b
+// In words: a node of type URL with the title a, followed by a folder node with
+// the title 1 having the single child of type url with name b, followed by
+// the url node with the title c.
+//
+// NOTE: each name must be unique, and folders are assigned a unique title by
+// way of an increasing integer.
+void PopulateNodeFromString(const std::string& description, TestNode* parent) {
+ std::vector<std::string> elements = base::SplitString(
+ description, base::kWhitespaceASCII,
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+ size_t index = 0;
+ PopulateNodeImpl(elements, &index, parent);
+}
+
+// Populates the BookmarkNode with the children of parent.
+void PopulateBookmarkNode(TestNode* parent,
+ BookmarkModel* model,
+ const BookmarkNode* bb_node) {
+ for (int i = 0; i < parent->child_count(); ++i) {
+ TestNode* child = parent->GetChild(i);
+ if (child->value == BookmarkNode::FOLDER) {
+ const BookmarkNode* new_bb_node =
+ model->AddFolder(bb_node, i, child->GetTitle());
+ PopulateBookmarkNode(child, model, new_bb_node);
+ } else {
+ model->AddURL(bb_node, i, child->GetTitle(),
+ GURL("http://" + base::UTF16ToASCII(child->GetTitle())));
+ }
+ }
+}
+
+// Verifies the contents of the bookmark bar node match the contents of the
+// TestNode.
+void VerifyModelMatchesNode(TestNode* expected, const BookmarkNode* actual) {
+ ASSERT_EQ(expected->child_count(), actual->child_count());
+ for (int i = 0; i < expected->child_count(); ++i) {
+ TestNode* expected_child = expected->GetChild(i);
+ const BookmarkNode* actual_child = actual->GetChild(i);
+ ASSERT_EQ(expected_child->GetTitle(), actual_child->GetTitle());
+ if (expected_child->value == BookmarkNode::FOLDER) {
+ ASSERT_TRUE(actual_child->type() == BookmarkNode::FOLDER);
+ // Recurse throught children.
+ VerifyModelMatchesNode(expected_child, actual_child);
+ } else {
+ // No need to check the URL, just the title is enough.
+ ASSERT_TRUE(actual_child->is_url());
+ }
+ }
+}
+
+void VerifyNoDuplicateIDs(BookmarkModel* model) {
+ ui::TreeNodeIterator<const BookmarkNode> it(model->root_node());
+ base::hash_set<int64_t> ids;
+ while (it.has_next())
+ ASSERT_TRUE(ids.insert(it.Next()->id()).second);
+}
+
+class BookmarkModelTest : public testing::Test,
+ public BookmarkModelObserver,
+ public BookmarkUndoDelegate {
+ public:
+ struct ObserverDetails {
+ ObserverDetails() {
+ Set(NULL, NULL, -1, -1);
+ }
+
+ void Set(const BookmarkNode* node1,
+ const BookmarkNode* node2,
+ int index1,
+ int index2) {
+ node1_ = node1;
+ node2_ = node2;
+ index1_ = index1;
+ index2_ = index2;
+ }
+
+ void ExpectEquals(const BookmarkNode* node1,
+ const BookmarkNode* node2,
+ int index1,
+ int index2) {
+ EXPECT_EQ(node1_, node1);
+ EXPECT_EQ(node2_, node2);
+ EXPECT_EQ(index1_, index1);
+ EXPECT_EQ(index2_, index2);
+ }
+
+ private:
+ const BookmarkNode* node1_;
+ const BookmarkNode* node2_;
+ int index1_;
+ int index2_;
+ };
+
+ struct NodeRemovalDetail {
+ NodeRemovalDetail(const BookmarkNode* parent,
+ int index,
+ const BookmarkNode* node)
+ : parent_node_id(parent->id()), index(index), node_id(node->id()) {}
+
+ bool operator==(const NodeRemovalDetail& other) const {
+ return parent_node_id == other.parent_node_id &&
+ index == other.index &&
+ node_id == other.node_id;
+ }
+
+ int64_t parent_node_id;
+ int index;
+ int64_t node_id;
+ };
+
+ BookmarkModelTest() : model_(TestBookmarkClient::CreateModel()) {
+ model_->AddObserver(this);
+ ClearCounts();
+ }
+
+ void BookmarkModelLoaded(BookmarkModel* model, bool ids_reassigned) override {
+ // We never load from the db, so that this should never get invoked.
+ NOTREACHED();
+ }
+
+ void BookmarkNodeMoved(BookmarkModel* model,
+ const BookmarkNode* old_parent,
+ int old_index,
+ const BookmarkNode* new_parent,
+ int new_index) override {
+ ++moved_count_;
+ observer_details_.Set(old_parent, new_parent, old_index, new_index);
+ }
+
+ void BookmarkNodeAdded(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int index) override {
+ ++added_count_;
+ observer_details_.Set(parent, NULL, index, -1);
+ }
+
+ void OnWillRemoveBookmarks(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int old_index,
+ const BookmarkNode* node) override {
+ ++before_remove_count_;
+ }
+
+ void SetUndoProvider(BookmarkUndoProvider* provider) override {}
+
+ void BookmarkNodeRemoved(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int old_index,
+ const BookmarkNode* node,
+ const std::set<GURL>& removed_urls) override {
+ ++removed_count_;
+ observer_details_.Set(parent, NULL, old_index, -1);
+ }
+
+ void BookmarkNodeChanged(BookmarkModel* model,
+ const BookmarkNode* node) override {
+ ++changed_count_;
+ observer_details_.Set(node, NULL, -1, -1);
+ }
+
+ void OnWillChangeBookmarkNode(BookmarkModel* model,
+ const BookmarkNode* node) override {
+ ++before_change_count_;
+ }
+
+ void BookmarkNodeChildrenReordered(BookmarkModel* model,
+ const BookmarkNode* node) override {
+ ++reordered_count_;
+ }
+
+ void OnWillReorderBookmarkNode(BookmarkModel* model,
+ const BookmarkNode* node) override {
+ ++before_reorder_count_;
+ }
+
+ void BookmarkNodeFaviconChanged(BookmarkModel* model,
+ const BookmarkNode* node) override {
+ // We never attempt to load favicons, so that this method never
+ // gets invoked.
+ }
+
+ void ExtensiveBookmarkChangesBeginning(BookmarkModel* model) override {
+ ++extensive_changes_beginning_count_;
+ }
+
+ void ExtensiveBookmarkChangesEnded(BookmarkModel* model) override {
+ ++extensive_changes_ended_count_;
+ }
+
+ void BookmarkAllUserNodesRemoved(
+ BookmarkModel* model,
+ const std::set<GURL>& removed_urls) override {
+ ++all_bookmarks_removed_;
+ }
+
+ void OnWillRemoveAllUserBookmarks(BookmarkModel* model) override {
+ ++before_remove_all_count_;
+ }
+
+ void GroupedBookmarkChangesBeginning(BookmarkModel* model) override {
+ ++grouped_changes_beginning_count_;
+ }
+
+ void GroupedBookmarkChangesEnded(BookmarkModel* model) override {
+ ++grouped_changes_ended_count_;
+ }
+
+ void OnBookmarkNodeRemoved(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int index,
+ scoped_ptr<BookmarkNode> node) override {
+ node_removal_details_.push_back(
+ NodeRemovalDetail(parent, index, node.get()));
+ }
+
+ void ClearCounts() {
+ added_count_ = moved_count_ = removed_count_ = changed_count_ =
+ reordered_count_ = extensive_changes_beginning_count_ =
+ extensive_changes_ended_count_ = all_bookmarks_removed_ =
+ before_remove_count_ = before_change_count_ = before_reorder_count_ =
+ before_remove_all_count_ = grouped_changes_beginning_count_ =
+ grouped_changes_ended_count_ = 0;
+ }
+
+ void AssertObserverCount(int added_count,
+ int moved_count,
+ int removed_count,
+ int changed_count,
+ int reordered_count,
+ int before_remove_count,
+ int before_change_count,
+ int before_reorder_count,
+ int before_remove_all_count) {
+ EXPECT_EQ(added_count, added_count_);
+ EXPECT_EQ(moved_count, moved_count_);
+ EXPECT_EQ(removed_count, removed_count_);
+ EXPECT_EQ(changed_count, changed_count_);
+ EXPECT_EQ(reordered_count, reordered_count_);
+ EXPECT_EQ(before_remove_count, before_remove_count_);
+ EXPECT_EQ(before_change_count, before_change_count_);
+ EXPECT_EQ(before_reorder_count, before_reorder_count_);
+ EXPECT_EQ(before_remove_all_count, before_remove_all_count_);
+ }
+
+ void AssertExtensiveChangesObserverCount(
+ int extensive_changes_beginning_count,
+ int extensive_changes_ended_count) {
+ EXPECT_EQ(extensive_changes_beginning_count,
+ extensive_changes_beginning_count_);
+ EXPECT_EQ(extensive_changes_ended_count, extensive_changes_ended_count_);
+ }
+
+ void AssertGroupedChangesObserverCount(
+ int grouped_changes_beginning_count,
+ int grouped_changes_ended_count) {
+ EXPECT_EQ(grouped_changes_beginning_count,
+ grouped_changes_beginning_count_);
+ EXPECT_EQ(grouped_changes_ended_count, grouped_changes_ended_count_);
+ }
+
+ int AllNodesRemovedObserverCount() const { return all_bookmarks_removed_; }
+
+ BookmarkPermanentNode* ReloadModelWithExtraNode() {
+ model_->RemoveObserver(this);
+
+ BookmarkPermanentNode* extra_node = new BookmarkPermanentNode(100);
+ BookmarkPermanentNodeList extra_nodes;
+ extra_nodes.push_back(extra_node);
+
+ scoped_ptr<TestBookmarkClient> client(new TestBookmarkClient);
+ client->SetExtraNodesToLoad(std::move(extra_nodes));
+
+ model_ = TestBookmarkClient::CreateModelWithClient(std::move(client));
+ model_->AddObserver(this);
+ ClearCounts();
+
+ if (model_->root_node()->GetIndexOf(extra_node) == -1)
+ ADD_FAILURE();
+
+ return extra_node;
+ }
+
+ protected:
+ scoped_ptr<BookmarkModel> model_;
+ ObserverDetails observer_details_;
+ std::vector<NodeRemovalDetail> node_removal_details_;
+
+ private:
+ int added_count_;
+ int moved_count_;
+ int removed_count_;
+ int changed_count_;
+ int reordered_count_;
+ int extensive_changes_beginning_count_;
+ int extensive_changes_ended_count_;
+ int all_bookmarks_removed_;
+ int before_remove_count_;
+ int before_change_count_;
+ int before_reorder_count_;
+ int before_remove_all_count_;
+ int grouped_changes_beginning_count_;
+ int grouped_changes_ended_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(BookmarkModelTest);
+};
+
+TEST_F(BookmarkModelTest, InitialState) {
+ const BookmarkNode* bb_node = model_->bookmark_bar_node();
+ ASSERT_TRUE(bb_node != NULL);
+ EXPECT_EQ(0, bb_node->child_count());
+ EXPECT_EQ(BookmarkNode::BOOKMARK_BAR, bb_node->type());
+
+ const BookmarkNode* other_node = model_->other_node();
+ ASSERT_TRUE(other_node != NULL);
+ EXPECT_EQ(0, other_node->child_count());
+ EXPECT_EQ(BookmarkNode::OTHER_NODE, other_node->type());
+
+ const BookmarkNode* mobile_node = model_->mobile_node();
+ ASSERT_TRUE(mobile_node != NULL);
+ EXPECT_EQ(0, mobile_node->child_count());
+ EXPECT_EQ(BookmarkNode::MOBILE, mobile_node->type());
+
+ EXPECT_TRUE(bb_node->id() != other_node->id());
+ EXPECT_TRUE(bb_node->id() != mobile_node->id());
+ EXPECT_TRUE(other_node->id() != mobile_node->id());
+}
+
+TEST_F(BookmarkModelTest, AddURL) {
+ const BookmarkNode* root = model_->bookmark_bar_node();
+ const base::string16 title(ASCIIToUTF16("foo"));
+ const GURL url("http://foo.com");
+
+ const BookmarkNode* new_node = model_->AddURL(root, 0, title, url);
+ AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0);
+ observer_details_.ExpectEquals(root, NULL, 0, -1);
+
+ ASSERT_EQ(1, root->child_count());
+ ASSERT_EQ(title, new_node->GetTitle());
+ ASSERT_TRUE(url == new_node->url());
+ ASSERT_EQ(BookmarkNode::URL, new_node->type());
+ ASSERT_TRUE(new_node == model_->GetMostRecentlyAddedUserNodeForURL(url));
+
+ EXPECT_TRUE(new_node->id() != root->id() &&
+ new_node->id() != model_->other_node()->id() &&
+ new_node->id() != model_->mobile_node()->id());
+}
+
+TEST_F(BookmarkModelTest, AddURLWithUnicodeTitle) {
+ const BookmarkNode* root = model_->bookmark_bar_node();
+ const base::string16 title(base::WideToUTF16(
+ L"\u767e\u5ea6\u4e00\u4e0b\uff0c\u4f60\u5c31\u77e5\u9053"));
+ const GURL url("https://www.baidu.com/");
+
+ const BookmarkNode* new_node = model_->AddURL(root, 0, title, url);
+ AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0);
+ observer_details_.ExpectEquals(root, NULL, 0, -1);
+
+ ASSERT_EQ(1, root->child_count());
+ ASSERT_EQ(title, new_node->GetTitle());
+ ASSERT_TRUE(url == new_node->url());
+ ASSERT_EQ(BookmarkNode::URL, new_node->type());
+ ASSERT_TRUE(new_node == model_->GetMostRecentlyAddedUserNodeForURL(url));
+
+ EXPECT_TRUE(new_node->id() != root->id() &&
+ new_node->id() != model_->other_node()->id() &&
+ new_node->id() != model_->mobile_node()->id());
+}
+
+TEST_F(BookmarkModelTest, AddURLWithWhitespaceTitle) {
+ for (size_t i = 0; i < arraysize(url_whitespace_test_cases); ++i) {
+ const BookmarkNode* root = model_->bookmark_bar_node();
+ const base::string16 title(
+ ASCIIToUTF16(url_whitespace_test_cases[i].input_title));
+ const GURL url("http://foo.com");
+
+ const BookmarkNode* new_node = model_->AddURL(root, i, title, url);
+
+ int size = i + 1;
+ EXPECT_EQ(size, root->child_count());
+ EXPECT_EQ(ASCIIToUTF16(url_whitespace_test_cases[i].expected_title),
+ new_node->GetTitle());
+ EXPECT_EQ(BookmarkNode::URL, new_node->type());
+ }
+}
+
+TEST_F(BookmarkModelTest, AddURLWithCreationTimeAndMetaInfo) {
+ const BookmarkNode* root = model_->bookmark_bar_node();
+ const base::string16 title(ASCIIToUTF16("foo"));
+ const GURL url("http://foo.com");
+ const Time time = Time::Now() - TimeDelta::FromDays(1);
+ BookmarkNode::MetaInfoMap meta_info;
+ meta_info["foo"] = "bar";
+
+ const BookmarkNode* new_node = model_->AddURLWithCreationTimeAndMetaInfo(
+ root, 0, title, url, time, &meta_info);
+ AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0);
+ observer_details_.ExpectEquals(root, NULL, 0, -1);
+
+ ASSERT_EQ(1, root->child_count());
+ ASSERT_EQ(title, new_node->GetTitle());
+ ASSERT_TRUE(url == new_node->url());
+ ASSERT_EQ(BookmarkNode::URL, new_node->type());
+ ASSERT_EQ(time, new_node->date_added());
+ ASSERT_TRUE(new_node->GetMetaInfoMap());
+ ASSERT_EQ(meta_info, *new_node->GetMetaInfoMap());
+ ASSERT_TRUE(new_node == model_->GetMostRecentlyAddedUserNodeForURL(url));
+
+ EXPECT_TRUE(new_node->id() != root->id() &&
+ new_node->id() != model_->other_node()->id() &&
+ new_node->id() != model_->mobile_node()->id());
+}
+
+TEST_F(BookmarkModelTest, AddURLToMobileBookmarks) {
+ const BookmarkNode* root = model_->mobile_node();
+ const base::string16 title(ASCIIToUTF16("foo"));
+ const GURL url("http://foo.com");
+
+ const BookmarkNode* new_node = model_->AddURL(root, 0, title, url);
+ AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0);
+ observer_details_.ExpectEquals(root, NULL, 0, -1);
+
+ ASSERT_EQ(1, root->child_count());
+ ASSERT_EQ(title, new_node->GetTitle());
+ ASSERT_TRUE(url == new_node->url());
+ ASSERT_EQ(BookmarkNode::URL, new_node->type());
+ ASSERT_TRUE(new_node == model_->GetMostRecentlyAddedUserNodeForURL(url));
+
+ EXPECT_TRUE(new_node->id() != root->id() &&
+ new_node->id() != model_->other_node()->id() &&
+ new_node->id() != model_->mobile_node()->id());
+}
+
+TEST_F(BookmarkModelTest, AddFolder) {
+ const BookmarkNode* root = model_->bookmark_bar_node();
+ const base::string16 title(ASCIIToUTF16("foo"));
+
+ const BookmarkNode* new_node = model_->AddFolder(root, 0, title);
+ AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0);
+ observer_details_.ExpectEquals(root, NULL, 0, -1);
+
+ ASSERT_EQ(1, root->child_count());
+ ASSERT_EQ(title, new_node->GetTitle());
+ ASSERT_EQ(BookmarkNode::FOLDER, new_node->type());
+
+ EXPECT_TRUE(new_node->id() != root->id() &&
+ new_node->id() != model_->other_node()->id() &&
+ new_node->id() != model_->mobile_node()->id());
+
+ // Add another folder, just to make sure folder_ids are incremented correctly.
+ ClearCounts();
+ model_->AddFolder(root, 0, title);
+ AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0);
+ observer_details_.ExpectEquals(root, NULL, 0, -1);
+}
+
+TEST_F(BookmarkModelTest, AddFolderWithWhitespaceTitle) {
+ for (size_t i = 0; i < arraysize(title_whitespace_test_cases); ++i) {
+ const BookmarkNode* root = model_->bookmark_bar_node();
+ const base::string16 title(
+ ASCIIToUTF16(title_whitespace_test_cases[i].input_title));
+
+ const BookmarkNode* new_node = model_->AddFolder(root, i, title);
+
+ int size = i + 1;
+ EXPECT_EQ(size, root->child_count());
+ EXPECT_EQ(ASCIIToUTF16(title_whitespace_test_cases[i].expected_title),
+ new_node->GetTitle());
+ EXPECT_EQ(BookmarkNode::FOLDER, new_node->type());
+ }
+}
+
+TEST_F(BookmarkModelTest, RemoveURL) {
+ const BookmarkNode* root = model_->bookmark_bar_node();
+ const base::string16 title(ASCIIToUTF16("foo"));
+ const GURL url("http://foo.com");
+ model_->AddURL(root, 0, title, url);
+ ClearCounts();
+
+ model_->Remove(root->GetChild(0));
+ ASSERT_EQ(0, root->child_count());
+ AssertObserverCount(0, 0, 1, 0, 0, 1, 0, 0, 0);
+ observer_details_.ExpectEquals(root, NULL, 0, -1);
+
+ // Make sure there is no mapping for the URL.
+ ASSERT_TRUE(model_->GetMostRecentlyAddedUserNodeForURL(url) == NULL);
+}
+
+TEST_F(BookmarkModelTest, RemoveFolder) {
+ const BookmarkNode* root = model_->bookmark_bar_node();
+ const BookmarkNode* folder = model_->AddFolder(root, 0, ASCIIToUTF16("foo"));
+
+ ClearCounts();
+
+ // Add a URL as a child.
+ const base::string16 title(ASCIIToUTF16("foo"));
+ const GURL url("http://foo.com");
+ model_->AddURL(folder, 0, title, url);
+
+ ClearCounts();
+
+ // Now remove the folder.
+ model_->Remove(root->GetChild(0));
+ ASSERT_EQ(0, root->child_count());
+ AssertObserverCount(0, 0, 1, 0, 0, 1, 0, 0, 0);
+ observer_details_.ExpectEquals(root, NULL, 0, -1);
+
+ // Make sure there is no mapping for the URL.
+ ASSERT_TRUE(model_->GetMostRecentlyAddedUserNodeForURL(url) == NULL);
+}
+
+TEST_F(BookmarkModelTest, RemoveAllUserBookmarks) {
+ const BookmarkNode* bookmark_bar_node = model_->bookmark_bar_node();
+
+ ClearCounts();
+
+ // Add a url to bookmark bar.
+ base::string16 title(ASCIIToUTF16("foo"));
+ GURL url("http://foo.com");
+ const BookmarkNode* url_node =
+ model_->AddURL(bookmark_bar_node, 0, title, url);
+
+ // Add a folder with child URL.
+ const BookmarkNode* folder = model_->AddFolder(bookmark_bar_node, 0, title);
+ model_->AddURL(folder, 0, title, url);
+
+ AssertObserverCount(3, 0, 0, 0, 0, 0, 0, 0, 0);
+ ClearCounts();
+
+ int permanent_node_count = model_->root_node()->child_count();
+
+ NodeRemovalDetail expected_node_removal_details[] = {
+ NodeRemovalDetail(bookmark_bar_node, 1, url_node),
+ NodeRemovalDetail(bookmark_bar_node, 0, folder),
+ };
+
+ model_->SetUndoDelegate(this);
+ model_->RemoveAllUserBookmarks();
+
+ EXPECT_EQ(0, bookmark_bar_node->child_count());
+ // No permanent node should be removed.
+ EXPECT_EQ(permanent_node_count, model_->root_node()->child_count());
+ // No individual BookmarkNodeRemoved events are fired, so removed count
+ // should be 0.
+ AssertObserverCount(0, 0, 0, 0, 0, 0, 0, 0, 1);
+ AssertExtensiveChangesObserverCount(1, 1);
+ AssertGroupedChangesObserverCount(1, 1);
+ EXPECT_EQ(1, AllNodesRemovedObserverCount());
+ EXPECT_EQ(1, AllNodesRemovedObserverCount());
+ ASSERT_EQ(2u, node_removal_details_.size());
+ EXPECT_EQ(expected_node_removal_details[0], node_removal_details_[0]);
+ EXPECT_EQ(expected_node_removal_details[1], node_removal_details_[1]);
+}
+
+TEST_F(BookmarkModelTest, SetTitle) {
+ const BookmarkNode* root = model_->bookmark_bar_node();
+ base::string16 title(ASCIIToUTF16("foo"));
+ const GURL url("http://foo.com");
+ const BookmarkNode* node = model_->AddURL(root, 0, title, url);
+
+ ClearCounts();
+
+ title = ASCIIToUTF16("foo2");
+ model_->SetTitle(node, title);
+ AssertObserverCount(0, 0, 0, 1, 0, 0, 1, 0, 0);
+ observer_details_.ExpectEquals(node, NULL, -1, -1);
+ EXPECT_EQ(title, node->GetTitle());
+}
+
+TEST_F(BookmarkModelTest, SetTitleWithWhitespace) {
+ for (size_t i = 0; i < arraysize(title_whitespace_test_cases); ++i) {
+ const BookmarkNode* root = model_->bookmark_bar_node();
+ base::string16 title(ASCIIToUTF16("dummy"));
+ const GURL url("http://foo.com");
+ const BookmarkNode* node = model_->AddURL(root, 0, title, url);
+
+ title = ASCIIToUTF16(title_whitespace_test_cases[i].input_title);
+ model_->SetTitle(node, title);
+ EXPECT_EQ(ASCIIToUTF16(title_whitespace_test_cases[i].expected_title),
+ node->GetTitle());
+ }
+}
+
+TEST_F(BookmarkModelTest, SetURL) {
+ const BookmarkNode* root = model_->bookmark_bar_node();
+ const base::string16 title(ASCIIToUTF16("foo"));
+ GURL url("http://foo.com");
+ const BookmarkNode* node = model_->AddURL(root, 0, title, url);
+
+ ClearCounts();
+
+ url = GURL("http://foo2.com");
+ model_->SetURL(node, url);
+ AssertObserverCount(0, 0, 0, 1, 0, 0, 1, 0, 0);
+ observer_details_.ExpectEquals(node, NULL, -1, -1);
+ EXPECT_EQ(url, node->url());
+}
+
+TEST_F(BookmarkModelTest, SetDateAdded) {
+ const BookmarkNode* root = model_->bookmark_bar_node();
+ const base::string16 title(ASCIIToUTF16("foo"));
+ GURL url("http://foo.com");
+ const BookmarkNode* node = model_->AddURL(root, 0, title, url);
+
+ ClearCounts();
+
+ base::Time new_time = base::Time::Now() + base::TimeDelta::FromMinutes(20);
+ model_->SetDateAdded(node, new_time);
+ AssertObserverCount(0, 0, 0, 0, 0, 0, 0, 0, 0);
+ EXPECT_EQ(new_time, node->date_added());
+ EXPECT_EQ(new_time, model_->bookmark_bar_node()->date_folder_modified());
+}
+
+TEST_F(BookmarkModelTest, Move) {
+ const BookmarkNode* root = model_->bookmark_bar_node();
+ const base::string16 title(ASCIIToUTF16("foo"));
+ const GURL url("http://foo.com");
+ const BookmarkNode* node = model_->AddURL(root, 0, title, url);
+ const BookmarkNode* folder1 = model_->AddFolder(root, 0, ASCIIToUTF16("foo"));
+ ClearCounts();
+
+ model_->Move(node, folder1, 0);
+
+ AssertObserverCount(0, 1, 0, 0, 0, 0, 0, 0, 0);
+ observer_details_.ExpectEquals(root, folder1, 1, 0);
+ EXPECT_TRUE(folder1 == node->parent());
+ EXPECT_EQ(1, root->child_count());
+ EXPECT_EQ(folder1, root->GetChild(0));
+ EXPECT_EQ(1, folder1->child_count());
+ EXPECT_EQ(node, folder1->GetChild(0));
+
+ // And remove the folder.
+ ClearCounts();
+ model_->Remove(root->GetChild(0));
+ AssertObserverCount(0, 0, 1, 0, 0, 1, 0, 0, 0);
+ observer_details_.ExpectEquals(root, NULL, 0, -1);
+ EXPECT_TRUE(model_->GetMostRecentlyAddedUserNodeForURL(url) == NULL);
+ EXPECT_EQ(0, root->child_count());
+}
+
+TEST_F(BookmarkModelTest, NonMovingMoveCall) {
+ const BookmarkNode* root = model_->bookmark_bar_node();
+ const base::string16 title(ASCIIToUTF16("foo"));
+ const GURL url("http://foo.com");
+ const base::Time old_date(base::Time::Now() - base::TimeDelta::FromDays(1));
+
+ const BookmarkNode* node = model_->AddURL(root, 0, title, url);
+ model_->SetDateFolderModified(root, old_date);
+
+ // Since |node| is already at the index 0 of |root|, this is no-op.
+ model_->Move(node, root, 0);
+
+ // Check that the modification date is kept untouched.
+ EXPECT_EQ(old_date, root->date_folder_modified());
+}
+
+TEST_F(BookmarkModelTest, Copy) {
+ const BookmarkNode* root = model_->bookmark_bar_node();
+ static const std::string model_string("a 1:[ b c ] d 2:[ e f g ] h ");
+ test::AddNodesFromModelString(model_.get(), root, model_string);
+
+ // Validate initial model.
+ std::string actual_model_string = test::ModelStringFromNode(root);
+ EXPECT_EQ(model_string, actual_model_string);
+
+ // Copy 'd' to be after '1:b': URL item from bar to folder.
+ const BookmarkNode* node_to_copy = root->GetChild(2);
+ const BookmarkNode* destination = root->GetChild(1);
+ model_->Copy(node_to_copy, destination, 1);
+ actual_model_string = test::ModelStringFromNode(root);
+ EXPECT_EQ("a 1:[ b d c ] d 2:[ e f g ] h ", actual_model_string);
+
+ // Copy '1:d' to be after 'a': URL item from folder to bar.
+ const BookmarkNode* folder = root->GetChild(1);
+ node_to_copy = folder->GetChild(1);
+ model_->Copy(node_to_copy, root, 1);
+ actual_model_string = test::ModelStringFromNode(root);
+ EXPECT_EQ("a d 1:[ b d c ] d 2:[ e f g ] h ", actual_model_string);
+
+ // Copy '1' to be after '2:e': Folder from bar to folder.
+ node_to_copy = root->GetChild(2);
+ destination = root->GetChild(4);
+ model_->Copy(node_to_copy, destination, 1);
+ actual_model_string = test::ModelStringFromNode(root);
+ EXPECT_EQ("a d 1:[ b d c ] d 2:[ e 1:[ b d c ] f g ] h ",
+ actual_model_string);
+
+ // Copy '2:1' to be after '2:f': Folder within same folder.
+ folder = root->GetChild(4);
+ node_to_copy = folder->GetChild(1);
+ model_->Copy(node_to_copy, folder, 3);
+ actual_model_string = test::ModelStringFromNode(root);
+ EXPECT_EQ("a d 1:[ b d c ] d 2:[ e 1:[ b d c ] f 1:[ b d c ] g ] h ",
+ actual_model_string);
+
+ // Copy first 'd' to be after 'h': URL item within the bar.
+ node_to_copy = root->GetChild(1);
+ model_->Copy(node_to_copy, root, 6);
+ actual_model_string = test::ModelStringFromNode(root);
+ EXPECT_EQ("a d 1:[ b d c ] d 2:[ e 1:[ b d c ] f 1:[ b d c ] g ] h d ",
+ actual_model_string);
+
+ // Copy '2' to be after 'a': Folder within the bar.
+ node_to_copy = root->GetChild(4);
+ model_->Copy(node_to_copy, root, 1);
+ actual_model_string = test::ModelStringFromNode(root);
+ EXPECT_EQ("a 2:[ e 1:[ b d c ] f 1:[ b d c ] g ] d 1:[ b d c ] "
+ "d 2:[ e 1:[ b d c ] f 1:[ b d c ] g ] h d ",
+ actual_model_string);
+}
+
+// Tests that adding a URL to a folder updates the last modified time.
+TEST_F(BookmarkModelTest, ParentForNewNodes) {
+ ASSERT_EQ(model_->bookmark_bar_node(), model_->GetParentForNewNodes());
+
+ const base::string16 title(ASCIIToUTF16("foo"));
+ const GURL url("http://foo.com");
+
+ model_->AddURL(model_->other_node(), 0, title, url);
+ ASSERT_EQ(model_->other_node(), model_->GetParentForNewNodes());
+}
+
+// Tests that adding a URL to a folder updates the last modified time.
+TEST_F(BookmarkModelTest, ParentForNewMobileNodes) {
+ ASSERT_EQ(model_->bookmark_bar_node(), model_->GetParentForNewNodes());
+
+ const base::string16 title(ASCIIToUTF16("foo"));
+ const GURL url("http://foo.com");
+
+ model_->AddURL(model_->mobile_node(), 0, title, url);
+ ASSERT_EQ(model_->mobile_node(), model_->GetParentForNewNodes());
+}
+
+// Make sure recently modified stays in sync when adding a URL.
+TEST_F(BookmarkModelTest, MostRecentlyModifiedFolders) {
+ // Add a folder.
+ const BookmarkNode* folder =
+ model_->AddFolder(model_->other_node(), 0, ASCIIToUTF16("foo"));
+ // Add a URL to it.
+ model_->AddURL(folder, 0, ASCIIToUTF16("blah"), GURL("http://foo.com"));
+
+ // Make sure folder is in the most recently modified.
+ std::vector<const BookmarkNode*> most_recent_folders =
+ GetMostRecentlyModifiedUserFolders(model_.get(), 1);
+ ASSERT_EQ(1U, most_recent_folders.size());
+ ASSERT_EQ(folder, most_recent_folders[0]);
+
+ // Nuke the folder and do another fetch, making sure folder isn't in the
+ // returned list.
+ model_->Remove(folder->parent()->GetChild(0));
+ most_recent_folders = GetMostRecentlyModifiedUserFolders(model_.get(), 1);
+ ASSERT_EQ(1U, most_recent_folders.size());
+ ASSERT_TRUE(most_recent_folders[0] != folder);
+}
+
+// Make sure MostRecentlyAddedEntries stays in sync.
+TEST_F(BookmarkModelTest, MostRecentlyAddedEntries) {
+ // Add a couple of nodes such that the following holds for the time of the
+ // nodes: n1 > n2 > n3 > n4.
+ Time base_time = Time::Now();
+ BookmarkNode* n1 = AsMutable(model_->AddURL(model_->bookmark_bar_node(),
+ 0,
+ ASCIIToUTF16("blah"),
+ GURL("http://foo.com/0")));
+ BookmarkNode* n2 = AsMutable(model_->AddURL(model_->bookmark_bar_node(),
+ 1,
+ ASCIIToUTF16("blah"),
+ GURL("http://foo.com/1")));
+ BookmarkNode* n3 = AsMutable(model_->AddURL(model_->bookmark_bar_node(),
+ 2,
+ ASCIIToUTF16("blah"),
+ GURL("http://foo.com/2")));
+ BookmarkNode* n4 = AsMutable(model_->AddURL(model_->bookmark_bar_node(),
+ 3,
+ ASCIIToUTF16("blah"),
+ GURL("http://foo.com/3")));
+ n1->set_date_added(base_time + TimeDelta::FromDays(4));
+ n2->set_date_added(base_time + TimeDelta::FromDays(3));
+ n3->set_date_added(base_time + TimeDelta::FromDays(2));
+ n4->set_date_added(base_time + TimeDelta::FromDays(1));
+
+ // Make sure order is honored.
+ std::vector<const BookmarkNode*> recently_added;
+ GetMostRecentlyAddedEntries(model_.get(), 2, &recently_added);
+ ASSERT_EQ(2U, recently_added.size());
+ ASSERT_TRUE(n1 == recently_added[0]);
+ ASSERT_TRUE(n2 == recently_added[1]);
+
+ // swap 1 and 2, then check again.
+ recently_added.clear();
+ SwapDateAdded(n1, n2);
+ GetMostRecentlyAddedEntries(model_.get(), 4, &recently_added);
+ ASSERT_EQ(4U, recently_added.size());
+ ASSERT_TRUE(n2 == recently_added[0]);
+ ASSERT_TRUE(n1 == recently_added[1]);
+ ASSERT_TRUE(n3 == recently_added[2]);
+ ASSERT_TRUE(n4 == recently_added[3]);
+}
+
+// Makes sure GetMostRecentlyAddedUserNodeForURL stays in sync.
+TEST_F(BookmarkModelTest, GetMostRecentlyAddedUserNodeForURL) {
+ // Add a couple of nodes such that the following holds for the time of the
+ // nodes: n1 > n2
+ Time base_time = Time::Now();
+ const GURL url("http://foo.com/0");
+ BookmarkNode* n1 = AsMutable(model_->AddURL(
+ model_->bookmark_bar_node(), 0, ASCIIToUTF16("blah"), url));
+ BookmarkNode* n2 = AsMutable(model_->AddURL(
+ model_->bookmark_bar_node(), 1, ASCIIToUTF16("blah"), url));
+ n1->set_date_added(base_time + TimeDelta::FromDays(4));
+ n2->set_date_added(base_time + TimeDelta::FromDays(3));
+
+ // Make sure order is honored.
+ ASSERT_EQ(n1, model_->GetMostRecentlyAddedUserNodeForURL(url));
+
+ // swap 1 and 2, then check again.
+ SwapDateAdded(n1, n2);
+ ASSERT_EQ(n2, model_->GetMostRecentlyAddedUserNodeForURL(url));
+}
+
+// Makes sure GetBookmarks removes duplicates.
+TEST_F(BookmarkModelTest, GetBookmarksWithDups) {
+ const GURL url("http://foo.com/0");
+ const base::string16 title(ASCIIToUTF16("blah"));
+ model_->AddURL(model_->bookmark_bar_node(), 0, title, url);
+ model_->AddURL(model_->bookmark_bar_node(), 1, title, url);
+
+ std::vector<BookmarkModel::URLAndTitle> bookmarks;
+ model_->GetBookmarks(&bookmarks);
+ ASSERT_EQ(1U, bookmarks.size());
+ EXPECT_EQ(url, bookmarks[0].url);
+ EXPECT_EQ(title, bookmarks[0].title);
+
+ model_->AddURL(model_->bookmark_bar_node(), 2, ASCIIToUTF16("Title2"), url);
+ // Only one returned, even titles are different.
+ bookmarks.clear();
+ model_->GetBookmarks(&bookmarks);
+ EXPECT_EQ(1U, bookmarks.size());
+}
+
+TEST_F(BookmarkModelTest, HasBookmarks) {
+ const GURL url("http://foo.com/");
+ model_->AddURL(model_->bookmark_bar_node(), 0, ASCIIToUTF16("bar"), url);
+
+ EXPECT_TRUE(model_->HasBookmarks());
+}
+
+// http://crbug.com/450464
+TEST_F(BookmarkModelTest, DISABLED_Sort) {
+ // Populate the bookmark bar node with nodes for 'B', 'a', 'd' and 'C'.
+ // 'C' and 'a' are folders.
+ TestNode bbn;
+ PopulateNodeFromString("B [ a ] d [ a ]", &bbn);
+ const BookmarkNode* parent = model_->bookmark_bar_node();
+ PopulateBookmarkNode(&bbn, model_.get(), parent);
+
+ BookmarkNode* child1 = AsMutable(parent->GetChild(1));
+ child1->SetTitle(ASCIIToUTF16("a"));
+ delete child1->Remove(child1->GetChild(0));
+ BookmarkNode* child3 = AsMutable(parent->GetChild(3));
+ child3->SetTitle(ASCIIToUTF16("C"));
+ delete child3->Remove(child3->GetChild(0));
+
+ ClearCounts();
+
+ // Sort the children of the bookmark bar node.
+ model_->SortChildren(parent);
+
+ // Make sure we were notified.
+ AssertObserverCount(0, 0, 0, 0, 1, 0, 0, 1, 0);
+
+ // Make sure the order matches (remember, 'a' and 'C' are folders and
+ // come first).
+ EXPECT_EQ(parent->GetChild(0)->GetTitle(), ASCIIToUTF16("a"));
+ EXPECT_EQ(parent->GetChild(1)->GetTitle(), ASCIIToUTF16("C"));
+ EXPECT_EQ(parent->GetChild(2)->GetTitle(), ASCIIToUTF16("B"));
+ EXPECT_EQ(parent->GetChild(3)->GetTitle(), ASCIIToUTF16("d"));
+}
+
+TEST_F(BookmarkModelTest, Reorder) {
+ // Populate the bookmark bar node with nodes 'A', 'B', 'C' and 'D'.
+ TestNode bbn;
+ PopulateNodeFromString("A B C D", &bbn);
+ BookmarkNode* parent = AsMutable(model_->bookmark_bar_node());
+ PopulateBookmarkNode(&bbn, model_.get(), parent);
+
+ ClearCounts();
+
+ // Reorder bar node's bookmarks in reverse order.
+ std::vector<const BookmarkNode*> new_order;
+ new_order.push_back(parent->GetChild(3));
+ new_order.push_back(parent->GetChild(2));
+ new_order.push_back(parent->GetChild(1));
+ new_order.push_back(parent->GetChild(0));
+ model_->ReorderChildren(parent, new_order);
+
+ // Make sure we were notified.
+ AssertObserverCount(0, 0, 0, 0, 1, 0, 0, 1, 0);
+
+ // Make sure the order matches is correct (it should be reversed).
+ ASSERT_EQ(4, parent->child_count());
+ EXPECT_EQ("D", base::UTF16ToASCII(parent->GetChild(0)->GetTitle()));
+ EXPECT_EQ("C", base::UTF16ToASCII(parent->GetChild(1)->GetTitle()));
+ EXPECT_EQ("B", base::UTF16ToASCII(parent->GetChild(2)->GetTitle()));
+ EXPECT_EQ("A", base::UTF16ToASCII(parent->GetChild(3)->GetTitle()));
+}
+
+TEST_F(BookmarkModelTest, NodeVisibility) {
+ // Mobile node invisible by default
+ EXPECT_TRUE(model_->bookmark_bar_node()->IsVisible());
+ EXPECT_TRUE(model_->other_node()->IsVisible());
+ EXPECT_FALSE(model_->mobile_node()->IsVisible());
+
+ // Visibility of permanent node can only be changed if they are not
+ // forced to be visible by the client.
+ model_->SetPermanentNodeVisible(BookmarkNode::BOOKMARK_BAR, false);
+ EXPECT_TRUE(model_->bookmark_bar_node()->IsVisible());
+ model_->SetPermanentNodeVisible(BookmarkNode::OTHER_NODE, false);
+ EXPECT_TRUE(model_->other_node()->IsVisible());
+ model_->SetPermanentNodeVisible(BookmarkNode::MOBILE, true);
+ EXPECT_TRUE(model_->mobile_node()->IsVisible());
+ model_->SetPermanentNodeVisible(BookmarkNode::MOBILE, false);
+ EXPECT_FALSE(model_->mobile_node()->IsVisible());
+
+ // Arbitrary node should be visible
+ TestNode bbn;
+ PopulateNodeFromString("B", &bbn);
+ const BookmarkNode* parent = model_->mobile_node();
+ PopulateBookmarkNode(&bbn, model_.get(), parent);
+ EXPECT_TRUE(parent->GetChild(0)->IsVisible());
+
+ // Mobile folder should be visible now that it has a child.
+ EXPECT_TRUE(model_->mobile_node()->IsVisible());
+}
+
+TEST_F(BookmarkModelTest, MobileNodeVisibileWithChildren) {
+ const BookmarkNode* root = model_->mobile_node();
+ const base::string16 title(ASCIIToUTF16("foo"));
+ const GURL url("http://foo.com");
+
+ model_->AddURL(root, 0, title, url);
+ EXPECT_TRUE(model_->mobile_node()->IsVisible());
+}
+
+TEST_F(BookmarkModelTest, ExtensiveChangesObserver) {
+ AssertExtensiveChangesObserverCount(0, 0);
+ EXPECT_FALSE(model_->IsDoingExtensiveChanges());
+ model_->BeginExtensiveChanges();
+ EXPECT_TRUE(model_->IsDoingExtensiveChanges());
+ AssertExtensiveChangesObserverCount(1, 0);
+ model_->EndExtensiveChanges();
+ EXPECT_FALSE(model_->IsDoingExtensiveChanges());
+ AssertExtensiveChangesObserverCount(1, 1);
+}
+
+TEST_F(BookmarkModelTest, MultipleExtensiveChangesObserver) {
+ AssertExtensiveChangesObserverCount(0, 0);
+ EXPECT_FALSE(model_->IsDoingExtensiveChanges());
+ model_->BeginExtensiveChanges();
+ EXPECT_TRUE(model_->IsDoingExtensiveChanges());
+ AssertExtensiveChangesObserverCount(1, 0);
+ model_->BeginExtensiveChanges();
+ EXPECT_TRUE(model_->IsDoingExtensiveChanges());
+ AssertExtensiveChangesObserverCount(1, 0);
+ model_->EndExtensiveChanges();
+ EXPECT_TRUE(model_->IsDoingExtensiveChanges());
+ AssertExtensiveChangesObserverCount(1, 0);
+ model_->EndExtensiveChanges();
+ EXPECT_FALSE(model_->IsDoingExtensiveChanges());
+ AssertExtensiveChangesObserverCount(1, 1);
+}
+
+// Verifies that IsBookmarked is true if any bookmark matches the given URL,
+// and that IsBookmarkedByUser is true only if at least one of the matching
+// bookmarks can be edited by the user.
+TEST_F(BookmarkModelTest, IsBookmarked) {
+ // Reload the model with an extra node that is not editable by the user.
+ BookmarkPermanentNode* extra_node = ReloadModelWithExtraNode();
+
+ // "google.com" is a "user" bookmark.
+ model_->AddURL(model_->other_node(), 0, base::ASCIIToUTF16("User"),
+ GURL("http://google.com"));
+ // "youtube.com" is not.
+ model_->AddURL(extra_node, 0, base::ASCIIToUTF16("Extra"),
+ GURL("http://youtube.com"));
+
+ EXPECT_TRUE(model_->IsBookmarked(GURL("http://google.com")));
+ EXPECT_TRUE(model_->IsBookmarked(GURL("http://youtube.com")));
+ EXPECT_FALSE(model_->IsBookmarked(GURL("http://reddit.com")));
+
+ EXPECT_TRUE(IsBookmarkedByUser(model_.get(), GURL("http://google.com")));
+ EXPECT_FALSE(IsBookmarkedByUser(model_.get(), GURL("http://youtube.com")));
+ EXPECT_FALSE(IsBookmarkedByUser(model_.get(), GURL("http://reddit.com")));
+}
+
+// Verifies that GetMostRecentlyAddedUserNodeForURL skips bookmarks that
+// are not owned by the user.
+TEST_F(BookmarkModelTest, GetMostRecentlyAddedUserNodeForURLSkipsManagedNodes) {
+ // Reload the model with an extra node that is not editable by the user.
+ BookmarkPermanentNode* extra_node = ReloadModelWithExtraNode();
+
+ const base::string16 title = base::ASCIIToUTF16("Title");
+ const BookmarkNode* user_parent = model_->other_node();
+ const BookmarkNode* managed_parent = extra_node;
+ const GURL url("http://google.com");
+
+ // |url| is not bookmarked yet.
+ EXPECT_TRUE(model_->GetMostRecentlyAddedUserNodeForURL(url) == NULL);
+
+ // Having a managed node doesn't count.
+ model_->AddURL(managed_parent, 0, title, url);
+ EXPECT_TRUE(model_->GetMostRecentlyAddedUserNodeForURL(url) == NULL);
+
+ // Now add a user node.
+ const BookmarkNode* user = model_->AddURL(user_parent, 0, title, url);
+ EXPECT_EQ(user, model_->GetMostRecentlyAddedUserNodeForURL(url));
+
+ // Having a more recent managed node doesn't count either.
+ const BookmarkNode* managed = model_->AddURL(managed_parent, 0, title, url);
+ EXPECT_GE(managed->date_added(), user->date_added());
+ EXPECT_EQ(user, model_->GetMostRecentlyAddedUserNodeForURL(url));
+}
+
+TEST(BookmarkNodeTest, NodeMetaInfo) {
+ GURL url;
+ BookmarkNode node(url);
+ EXPECT_FALSE(node.GetMetaInfoMap());
+
+ EXPECT_TRUE(node.SetMetaInfo("key1", "value1"));
+ std::string out_value;
+ EXPECT_TRUE(node.GetMetaInfo("key1", &out_value));
+ EXPECT_EQ("value1", out_value);
+ EXPECT_FALSE(node.SetMetaInfo("key1", "value1"));
+
+ EXPECT_FALSE(node.GetMetaInfo("key2.subkey1", &out_value));
+ EXPECT_TRUE(node.SetMetaInfo("key2.subkey1", "value2"));
+ EXPECT_TRUE(node.GetMetaInfo("key2.subkey1", &out_value));
+ EXPECT_EQ("value2", out_value);
+
+ EXPECT_FALSE(node.GetMetaInfo("key2.subkey2.leaf", &out_value));
+ EXPECT_TRUE(node.SetMetaInfo("key2.subkey2.leaf", ""));
+ EXPECT_TRUE(node.GetMetaInfo("key2.subkey2.leaf", &out_value));
+ EXPECT_EQ("", out_value);
+
+ EXPECT_TRUE(node.DeleteMetaInfo("key1"));
+ EXPECT_TRUE(node.DeleteMetaInfo("key2.subkey1"));
+ EXPECT_TRUE(node.DeleteMetaInfo("key2.subkey2.leaf"));
+ EXPECT_FALSE(node.DeleteMetaInfo("key3"));
+ EXPECT_FALSE(node.GetMetaInfo("key1", &out_value));
+ EXPECT_FALSE(node.GetMetaInfo("key2.subkey1", &out_value));
+ EXPECT_FALSE(node.GetMetaInfo("key2.subkey2", &out_value));
+ EXPECT_FALSE(node.GetMetaInfo("key2.subkey2.leaf", &out_value));
+ EXPECT_FALSE(node.GetMetaInfoMap());
+}
+
+// Creates a set of nodes in the bookmark model, and checks that the loaded
+// structure is what we first created.
+TEST(BookmarkModelTest2, CreateAndRestore) {
+ struct TestData {
+ // Structure of the children of the bookmark model node.
+ const std::string bbn_contents;
+ // Structure of the children of the other node.
+ const std::string other_contents;
+ // Structure of the children of the synced node.
+ const std::string mobile_contents;
+ } data[] = {
+ // See PopulateNodeFromString for a description of these strings.
+ { "", "" },
+ { "a", "b" },
+ { "a [ b ]", "" },
+ { "", "[ b ] a [ c [ d e [ f ] ] ]" },
+ { "a [ b ]", "" },
+ { "a b c [ d e [ f ] ]", "g h i [ j k [ l ] ]"},
+ };
+ scoped_ptr<BookmarkModel> model;
+ for (size_t i = 0; i < arraysize(data); ++i) {
+ model = TestBookmarkClient::CreateModel();
+
+ TestNode bbn;
+ PopulateNodeFromString(data[i].bbn_contents, &bbn);
+ PopulateBookmarkNode(&bbn, model.get(), model->bookmark_bar_node());
+
+ TestNode other;
+ PopulateNodeFromString(data[i].other_contents, &other);
+ PopulateBookmarkNode(&other, model.get(), model->other_node());
+
+ TestNode mobile;
+ PopulateNodeFromString(data[i].mobile_contents, &mobile);
+ PopulateBookmarkNode(&mobile, model.get(), model->mobile_node());
+
+ VerifyModelMatchesNode(&bbn, model->bookmark_bar_node());
+ VerifyModelMatchesNode(&other, model->other_node());
+ VerifyModelMatchesNode(&mobile, model->mobile_node());
+ VerifyNoDuplicateIDs(model.get());
+ }
+}
+
+} // namespace
+
+class BookmarkModelFaviconTest : public testing::Test,
+ public BookmarkModelObserver {
+ public:
+ BookmarkModelFaviconTest() : model_(TestBookmarkClient::CreateModel()) {
+ model_->AddObserver(this);
+ }
+
+ // Emulates the favicon getting asynchronously loaded. In production, the
+ // favicon is asynchronously loaded when BookmarkModel::GetFavicon() is
+ // called.
+ void OnFaviconLoaded(BookmarkNode* node, const GURL& icon_url) {
+ SkBitmap bitmap;
+ bitmap.allocN32Pixels(16, 16);
+ bitmap.eraseColor(SK_ColorBLUE);
+ gfx::Image image = gfx::Image::CreateFrom1xBitmap(bitmap);
+
+ favicon_base::FaviconImageResult image_result;
+ image_result.image = image;
+ image_result.icon_url = icon_url;
+ model_->OnFaviconDataAvailable(node, favicon_base::IconType::FAVICON,
+ image_result);
+ }
+
+ bool WasNodeUpdated(const BookmarkNode* node) {
+ return std::find(updated_nodes_.begin(), updated_nodes_.end(), node) !=
+ updated_nodes_.end();
+ }
+
+ void ClearUpdatedNodes() {
+ updated_nodes_.clear();
+ }
+
+ protected:
+ void BookmarkModelLoaded(BookmarkModel* model, bool ids_reassigned) override {
+ }
+
+ void BookmarkNodeMoved(BookmarkModel* model,
+ const BookmarkNode* old_parent,
+ int old_index,
+ const BookmarkNode* new_parent,
+ int new_index) override {}
+
+ void BookmarkNodeAdded(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int index) override {}
+
+ void BookmarkNodeRemoved(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int old_index,
+ const BookmarkNode* node,
+ const std::set<GURL>& removed_urls) override {}
+
+ void BookmarkNodeChanged(BookmarkModel* model,
+ const BookmarkNode* node) override {}
+
+ void BookmarkNodeFaviconChanged(BookmarkModel* model,
+ const BookmarkNode* node) override {
+ updated_nodes_.push_back(node);
+ }
+
+ void BookmarkNodeChildrenReordered(BookmarkModel* model,
+ const BookmarkNode* node) override {}
+
+ void BookmarkAllUserNodesRemoved(
+ BookmarkModel* model,
+ const std::set<GURL>& removed_urls) override {
+ }
+
+ scoped_ptr<BookmarkModel> model_;
+ std::vector<const BookmarkNode*> updated_nodes_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BookmarkModelFaviconTest);
+};
+
+// Test that BookmarkModel::OnFaviconsChanged() sends a notification that the
+// favicon changed to each BookmarkNode which has either a matching page URL
+// (e.g. http://www.google.com) or a matching icon URL
+// (e.g. http://www.google.com/favicon.ico).
+TEST_F(BookmarkModelFaviconTest, FaviconsChangedObserver) {
+ const BookmarkNode* root = model_->bookmark_bar_node();
+ base::string16 kTitle(ASCIIToUTF16("foo"));
+ GURL kPageURL1("http://www.google.com");
+ GURL kPageURL2("http://www.google.ca");
+ GURL kPageURL3("http://www.amazon.com");
+ GURL kFaviconURL12("http://www.google.com/favicon.ico");
+ GURL kFaviconURL3("http://www.amazon.com/favicon.ico");
+
+ const BookmarkNode* node1 = model_->AddURL(root, 0, kTitle, kPageURL1);
+ const BookmarkNode* node2 = model_->AddURL(root, 0, kTitle, kPageURL2);
+ const BookmarkNode* node3 = model_->AddURL(root, 0, kTitle, kPageURL3);
+ const BookmarkNode* node4 = model_->AddURL(root, 0, kTitle, kPageURL3);
+
+ {
+ OnFaviconLoaded(AsMutable(node1), kFaviconURL12);
+ OnFaviconLoaded(AsMutable(node2), kFaviconURL12);
+ OnFaviconLoaded(AsMutable(node3), kFaviconURL3);
+ OnFaviconLoaded(AsMutable(node4), kFaviconURL3);
+
+ ClearUpdatedNodes();
+ std::set<GURL> changed_page_urls;
+ changed_page_urls.insert(kPageURL2);
+ changed_page_urls.insert(kPageURL3);
+ model_->OnFaviconsChanged(changed_page_urls, GURL());
+ ASSERT_EQ(3u, updated_nodes_.size());
+ EXPECT_TRUE(WasNodeUpdated(node2));
+ EXPECT_TRUE(WasNodeUpdated(node3));
+ EXPECT_TRUE(WasNodeUpdated(node4));
+ }
+
+ {
+ // Reset the favicon data because BookmarkModel::OnFaviconsChanged() clears
+ // the BookmarkNode's favicon data for all of the BookmarkNodes whose
+ // favicon data changed.
+ OnFaviconLoaded(AsMutable(node1), kFaviconURL12);
+ OnFaviconLoaded(AsMutable(node2), kFaviconURL12);
+ OnFaviconLoaded(AsMutable(node3), kFaviconURL3);
+ OnFaviconLoaded(AsMutable(node4), kFaviconURL3);
+
+ ClearUpdatedNodes();
+ model_->OnFaviconsChanged(std::set<GURL>(), kFaviconURL12);
+ ASSERT_EQ(2u, updated_nodes_.size());
+ EXPECT_TRUE(WasNodeUpdated(node1));
+ EXPECT_TRUE(WasNodeUpdated(node2));
+ }
+
+ {
+ OnFaviconLoaded(AsMutable(node1), kFaviconURL12);
+ OnFaviconLoaded(AsMutable(node2), kFaviconURL12);
+ OnFaviconLoaded(AsMutable(node3), kFaviconURL3);
+ OnFaviconLoaded(AsMutable(node4), kFaviconURL3);
+
+ ClearUpdatedNodes();
+ std::set<GURL> changed_page_urls;
+ changed_page_urls.insert(kPageURL1);
+ model_->OnFaviconsChanged(changed_page_urls, kFaviconURL12);
+ ASSERT_EQ(2u, updated_nodes_.size());
+ EXPECT_TRUE(WasNodeUpdated(node1));
+ EXPECT_TRUE(WasNodeUpdated(node2));
+ }
+}
+
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/browser/bookmark_node.cc b/chromium/components/bookmarks/browser/bookmark_node.cc
new file mode 100644
index 00000000000..9574cf1ea5b
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_node.cc
@@ -0,0 +1,136 @@
+// Copyright 2014 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 "components/bookmarks/browser/bookmark_node.h"
+
+#include <map>
+#include <string>
+
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+
+namespace bookmarks {
+
+namespace {
+
+// Whitespace characters to strip from bookmark titles.
+const base::char16 kInvalidChars[] = {
+ '\n', '\r', '\t',
+ 0x2028, // Line separator
+ 0x2029, // Paragraph separator
+ 0
+};
+
+} // namespace
+
+// BookmarkNode ---------------------------------------------------------------
+
+const int64_t BookmarkNode::kInvalidSyncTransactionVersion = -1;
+
+BookmarkNode::BookmarkNode(const GURL& url)
+ : url_(url) {
+ Initialize(0);
+}
+
+BookmarkNode::BookmarkNode(int64_t id, const GURL& url) : url_(url) {
+ Initialize(id);
+}
+
+BookmarkNode::~BookmarkNode() {
+}
+
+void BookmarkNode::SetTitle(const base::string16& title) {
+ // Replace newlines and other problematic whitespace characters in
+ // folder/bookmark names with spaces.
+ base::string16 trimmed_title;
+ base::ReplaceChars(title, kInvalidChars, base::ASCIIToUTF16(" "),
+ &trimmed_title);
+ ui::TreeNode<BookmarkNode>::SetTitle(trimmed_title);
+}
+
+bool BookmarkNode::IsVisible() const {
+ return true;
+}
+
+bool BookmarkNode::GetMetaInfo(const std::string& key,
+ std::string* value) const {
+ if (!meta_info_map_)
+ return false;
+
+ MetaInfoMap::const_iterator it = meta_info_map_->find(key);
+ if (it == meta_info_map_->end())
+ return false;
+
+ *value = it->second;
+ return true;
+}
+
+bool BookmarkNode::SetMetaInfo(const std::string& key,
+ const std::string& value) {
+ if (!meta_info_map_)
+ meta_info_map_.reset(new MetaInfoMap);
+
+ MetaInfoMap::iterator it = meta_info_map_->find(key);
+ if (it == meta_info_map_->end()) {
+ (*meta_info_map_)[key] = value;
+ return true;
+ }
+ // Key already in map, check if the value has changed.
+ if (it->second == value)
+ return false;
+ it->second = value;
+ return true;
+}
+
+bool BookmarkNode::DeleteMetaInfo(const std::string& key) {
+ if (!meta_info_map_)
+ return false;
+ bool erased = meta_info_map_->erase(key) != 0;
+ if (meta_info_map_->empty())
+ meta_info_map_.reset();
+ return erased;
+}
+
+void BookmarkNode::SetMetaInfoMap(const MetaInfoMap& meta_info_map) {
+ if (meta_info_map.empty())
+ meta_info_map_.reset();
+ else
+ meta_info_map_.reset(new MetaInfoMap(meta_info_map));
+}
+
+const BookmarkNode::MetaInfoMap* BookmarkNode::GetMetaInfoMap() const {
+ return meta_info_map_.get();
+}
+
+void BookmarkNode::Initialize(int64_t id) {
+ id_ = id;
+ type_ = url_.is_empty() ? FOLDER : URL;
+ date_added_ = base::Time::Now();
+ favicon_type_ = favicon_base::INVALID_ICON;
+ favicon_state_ = INVALID_FAVICON;
+ favicon_load_task_id_ = base::CancelableTaskTracker::kBadTaskId;
+ meta_info_map_.reset();
+ sync_transaction_version_ = kInvalidSyncTransactionVersion;
+}
+
+void BookmarkNode::InvalidateFavicon() {
+ icon_url_ = GURL();
+ favicon_ = gfx::Image();
+ favicon_type_ = favicon_base::INVALID_ICON;
+ favicon_state_ = INVALID_FAVICON;
+}
+
+// BookmarkPermanentNode -------------------------------------------------------
+
+BookmarkPermanentNode::BookmarkPermanentNode(int64_t id)
+ : BookmarkNode(id, GURL()), visible_(true) {}
+
+BookmarkPermanentNode::~BookmarkPermanentNode() {
+}
+
+bool BookmarkPermanentNode::IsVisible() const {
+ return visible_ || !empty();
+}
+
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/browser/bookmark_node.h b/chromium/components/bookmarks/browser/bookmark_node.h
new file mode 100644
index 00000000000..bfd83026b7e
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_node.h
@@ -0,0 +1,217 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_NODE_H_
+#define COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_NODE_H_
+
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/task/cancelable_task_tracker.h"
+#include "base/time/time.h"
+#include "components/favicon_base/favicon_types.h"
+#include "ui/base/models/tree_node_model.h"
+#include "ui/gfx/image/image.h"
+#include "url/gurl.h"
+
+namespace bookmarks {
+
+class BookmarkModel;
+
+// BookmarkNode ---------------------------------------------------------------
+
+// BookmarkNode contains information about a starred entry: title, URL, favicon,
+// id and type. BookmarkNodes are returned from BookmarkModel.
+class BookmarkNode : public ui::TreeNode<BookmarkNode> {
+ public:
+ enum Type {
+ URL,
+ FOLDER,
+ BOOKMARK_BAR,
+ OTHER_NODE,
+ MOBILE
+ };
+
+ enum FaviconState {
+ INVALID_FAVICON,
+ LOADING_FAVICON,
+ LOADED_FAVICON,
+ };
+
+ typedef std::map<std::string, std::string> MetaInfoMap;
+
+ static const int64_t kInvalidSyncTransactionVersion;
+
+ // Creates a new node with an id of 0 and |url|.
+ explicit BookmarkNode(const GURL& url);
+ // Creates a new node with |id| and |url|.
+ BookmarkNode(int64_t id, const GURL& url);
+
+ ~BookmarkNode() override;
+
+ // Set the node's internal title. Note that this neither invokes observers
+ // nor updates any bookmark model this node may be in. For that functionality,
+ // BookmarkModel::SetTitle(..) should be used instead.
+ void SetTitle(const base::string16& title) override;
+
+ // Returns an unique id for this node.
+ // For bookmark nodes that are managed by the bookmark model, the IDs are
+ // persisted across sessions.
+ int64_t id() const { return id_; }
+ void set_id(int64_t id) { id_ = id; }
+
+ const GURL& url() const { return url_; }
+ void set_url(const GURL& url) { url_ = url; }
+
+ // Returns the favicon's URL. Returns an empty URL if there is no favicon
+ // associated with this bookmark.
+ const GURL& icon_url() const { return icon_url_; }
+
+ Type type() const { return type_; }
+ void set_type(Type type) { type_ = type; }
+
+ // Returns the time the node was added.
+ const base::Time& date_added() const { return date_added_; }
+ void set_date_added(const base::Time& date) { date_added_ = date; }
+
+ // Returns the last time the folder was modified. This is only maintained
+ // for folders (including the bookmark bar and other folder).
+ const base::Time& date_folder_modified() const {
+ return date_folder_modified_;
+ }
+ void set_date_folder_modified(const base::Time& date) {
+ date_folder_modified_ = date;
+ }
+
+ // Convenience for testing if this node represents a folder. A folder is a
+ // node whose type is not URL.
+ bool is_folder() const { return type_ != URL; }
+ bool is_url() const { return type_ == URL; }
+
+ bool is_favicon_loaded() const { return favicon_state_ == LOADED_FAVICON; }
+
+ // Accessor method for controlling the visibility of a bookmark node/sub-tree.
+ // Note that visibility is not propagated down the tree hierarchy so if a
+ // parent node is marked as invisible, a child node may return "Visible". This
+ // function is primarily useful when traversing the model to generate a UI
+ // representation but we may want to suppress some nodes.
+ virtual bool IsVisible() const;
+
+ // Gets/sets/deletes value of |key| in the meta info represented by
+ // |meta_info_str_|. Return true if key is found in meta info for gets or
+ // meta info is changed indeed for sets/deletes.
+ bool GetMetaInfo(const std::string& key, std::string* value) const;
+ bool SetMetaInfo(const std::string& key, const std::string& value);
+ bool DeleteMetaInfo(const std::string& key);
+ void SetMetaInfoMap(const MetaInfoMap& meta_info_map);
+ // Returns NULL if there are no values in the map.
+ const MetaInfoMap* GetMetaInfoMap() const;
+
+ void set_sync_transaction_version(int64_t sync_transaction_version) {
+ sync_transaction_version_ = sync_transaction_version;
+ }
+ int64_t sync_transaction_version() const { return sync_transaction_version_; }
+
+ // TODO(sky): Consider adding last visit time here, it'll greatly simplify
+ // HistoryContentsProvider.
+
+ private:
+ friend class BookmarkModel;
+
+ // A helper function to initialize various fields during construction.
+ void Initialize(int64_t id);
+
+ // Called when the favicon becomes invalid.
+ void InvalidateFavicon();
+
+ // Sets the favicon's URL.
+ void set_icon_url(const GURL& icon_url) {
+ icon_url_ = icon_url;
+ }
+
+ // Returns the favicon. In nearly all cases you should use the method
+ // BookmarkModel::GetFavicon rather than this one as it takes care of
+ // loading the favicon if it isn't already loaded.
+ const gfx::Image& favicon() const { return favicon_; }
+ void set_favicon(const gfx::Image& icon) { favicon_ = icon; }
+
+ favicon_base::IconType favicon_type() const { return favicon_type_; }
+ void set_favicon_type(favicon_base::IconType type) { favicon_type_ = type; }
+
+ FaviconState favicon_state() const { return favicon_state_; }
+ void set_favicon_state(FaviconState state) { favicon_state_ = state; }
+
+ base::CancelableTaskTracker::TaskId favicon_load_task_id() const {
+ return favicon_load_task_id_;
+ }
+ void set_favicon_load_task_id(base::CancelableTaskTracker::TaskId id) {
+ favicon_load_task_id_ = id;
+ }
+
+ // The unique identifier for this node.
+ int64_t id_;
+
+ // The URL of this node. BookmarkModel maintains maps off this URL, so changes
+ // to the URL must be done through the BookmarkModel.
+ GURL url_;
+
+ // The type of this node. See enum above.
+ Type type_;
+
+ // Date of when this node was created.
+ base::Time date_added_;
+
+ // Date of the last modification. Only used for folders.
+ base::Time date_folder_modified_;
+
+ // The favicon of this node.
+ gfx::Image favicon_;
+
+ // The type of favicon currently loaded.
+ favicon_base::IconType favicon_type_;
+
+ // The URL of the node's favicon.
+ GURL icon_url_;
+
+ // The loading state of the favicon.
+ FaviconState favicon_state_;
+
+ // If not base::CancelableTaskTracker::kBadTaskId, it indicates
+ // we're loading the
+ // favicon and the task is tracked by CancelabelTaskTracker.
+ base::CancelableTaskTracker::TaskId favicon_load_task_id_;
+
+ // A map that stores arbitrary meta information about the node.
+ scoped_ptr<MetaInfoMap> meta_info_map_;
+
+ // The sync transaction version. Defaults to kInvalidSyncTransactionVersion.
+ int64_t sync_transaction_version_;
+
+ DISALLOW_COPY_AND_ASSIGN(BookmarkNode);
+};
+
+// BookmarkPermanentNode -------------------------------------------------------
+
+// Node used for the permanent folders (excluding the root).
+class BookmarkPermanentNode : public BookmarkNode {
+ public:
+ explicit BookmarkPermanentNode(int64_t id);
+ ~BookmarkPermanentNode() override;
+
+ // WARNING: this code is used for other projects. Contact noyau@ for details.
+ void set_visible(bool value) { visible_ = value; }
+
+ // BookmarkNode overrides:
+ bool IsVisible() const override;
+
+ private:
+ bool visible_;
+
+ DISALLOW_COPY_AND_ASSIGN(BookmarkPermanentNode);
+};
+
+} // namespace bookmarks
+
+#endif // COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_NODE_H_
diff --git a/chromium/components/bookmarks/browser/bookmark_node_data.cc b/chromium/components/bookmarks/browser/bookmark_node_data.cc
new file mode 100644
index 00000000000..e634fd3c7b8
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_node_data.cc
@@ -0,0 +1,308 @@
+// Copyright 2014 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 "components/bookmarks/browser/bookmark_node_data.h"
+
+#include <string>
+
+#include "base/pickle.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "components/bookmarks/browser/bookmark_utils.h"
+#include "ui/base/clipboard/scoped_clipboard_writer.h"
+
+namespace bookmarks {
+
+const char BookmarkNodeData::kClipboardFormatString[] =
+ "chromium/x-bookmark-entries";
+
+BookmarkNodeData::Element::Element() : is_url(false), id_(0) {
+}
+
+BookmarkNodeData::Element::Element(const BookmarkNode* node)
+ : is_url(node->is_url()),
+ url(node->url()),
+ title(node->GetTitle()),
+ date_added(node->date_added()),
+ date_folder_modified(node->date_folder_modified()),
+ id_(node->id()) {
+ if (node->GetMetaInfoMap())
+ meta_info_map = *node->GetMetaInfoMap();
+ for (int i = 0; i < node->child_count(); ++i)
+ children.push_back(Element(node->GetChild(i)));
+}
+
+BookmarkNodeData::Element::Element(const Element& other) = default;
+
+BookmarkNodeData::Element::~Element() {
+}
+
+void BookmarkNodeData::Element::WriteToPickle(base::Pickle* pickle) const {
+ pickle->WriteBool(is_url);
+ pickle->WriteString(url.spec());
+ pickle->WriteString16(title);
+ pickle->WriteInt64(id_);
+ pickle->WriteUInt32(static_cast<uint32_t>(meta_info_map.size()));
+ for (BookmarkNode::MetaInfoMap::const_iterator it = meta_info_map.begin();
+ it != meta_info_map.end(); ++it) {
+ pickle->WriteString(it->first);
+ pickle->WriteString(it->second);
+ }
+ if (!is_url) {
+ pickle->WriteUInt32(static_cast<uint32_t>(children.size()));
+ for (std::vector<Element>::const_iterator i = children.begin();
+ i != children.end(); ++i) {
+ i->WriteToPickle(pickle);
+ }
+ }
+}
+
+bool BookmarkNodeData::Element::ReadFromPickle(base::PickleIterator* iterator) {
+ std::string url_spec;
+ if (!iterator->ReadBool(&is_url) ||
+ !iterator->ReadString(&url_spec) ||
+ !iterator->ReadString16(&title) ||
+ !iterator->ReadInt64(&id_)) {
+ return false;
+ }
+ url = GURL(url_spec);
+ date_added = base::Time();
+ date_folder_modified = base::Time();
+ meta_info_map.clear();
+ uint32_t meta_field_count;
+ if (!iterator->ReadUInt32(&meta_field_count))
+ return false;
+ for (size_t i = 0; i < meta_field_count; ++i) {
+ std::string key;
+ std::string value;
+ if (!iterator->ReadString(&key) ||
+ !iterator->ReadString(&value)) {
+ return false;
+ }
+ meta_info_map[key] = value;
+ }
+ children.clear();
+ if (!is_url) {
+ uint32_t children_count;
+ if (!iterator->ReadUInt32(&children_count))
+ return false;
+ children.reserve(children_count);
+ for (size_t i = 0; i < children_count; ++i) {
+ children.push_back(Element());
+ if (!children.back().ReadFromPickle(iterator))
+ return false;
+ }
+ }
+ return true;
+}
+
+// BookmarkNodeData -----------------------------------------------------------
+
+BookmarkNodeData::BookmarkNodeData() {
+}
+
+BookmarkNodeData::BookmarkNodeData(const BookmarkNodeData& other) = default;
+
+BookmarkNodeData::BookmarkNodeData(const BookmarkNode* node) {
+ elements.push_back(Element(node));
+}
+
+BookmarkNodeData::BookmarkNodeData(
+ const std::vector<const BookmarkNode*>& nodes) {
+ ReadFromVector(nodes);
+}
+
+BookmarkNodeData::~BookmarkNodeData() {
+}
+
+#if !defined(OS_MACOSX)
+// static
+bool BookmarkNodeData::ClipboardContainsBookmarks() {
+ return ui::Clipboard::GetForCurrentThread()->IsFormatAvailable(
+ ui::Clipboard::GetFormatType(kClipboardFormatString),
+ ui::CLIPBOARD_TYPE_COPY_PASTE);
+}
+#endif
+
+bool BookmarkNodeData::ReadFromVector(
+ const std::vector<const BookmarkNode*>& nodes) {
+ Clear();
+
+ if (nodes.empty())
+ return false;
+
+ for (size_t i = 0; i < nodes.size(); ++i)
+ elements.push_back(Element(nodes[i]));
+
+ return true;
+}
+
+bool BookmarkNodeData::ReadFromTuple(const GURL& url,
+ const base::string16& title) {
+ Clear();
+
+ if (!url.is_valid())
+ return false;
+
+ Element element;
+ element.title = title;
+ element.url = url;
+ element.is_url = true;
+
+ elements.push_back(element);
+
+ return true;
+}
+
+#if !defined(OS_MACOSX)
+void BookmarkNodeData::WriteToClipboard(ui::ClipboardType clipboard_type) {
+ DCHECK(clipboard_type == ui::CLIPBOARD_TYPE_COPY_PASTE ||
+ clipboard_type == ui::CLIPBOARD_TYPE_SELECTION);
+ ui::ScopedClipboardWriter scw(clipboard_type);
+
+ // If there is only one element and it is a URL, write the URL to the
+ // clipboard.
+ if (has_single_url()) {
+ const base::string16& title = elements[0].title;
+ const std::string url = elements[0].url.spec();
+
+ scw.WriteBookmark(title, url);
+
+ // Don't call scw.WriteHyperlink() here, since some rich text editors will
+ // change fonts when such data is pasted in; besides, most such editors
+ // auto-linkify at some point anyway.
+
+ // Also write the URL to the clipboard as text so that it can be pasted
+ // into text fields. We use WriteText instead of WriteURL because we don't
+ // want to clobber the X clipboard when the user copies out of the omnibox
+ // on Linux (on Windows and Mac, there is no difference between these
+ // functions).
+ scw.WriteText(base::UTF8ToUTF16(url));
+ } else {
+ // We have either more than one URL, a folder, or a combination of URLs
+ // and folders.
+ base::string16 text;
+ for (size_t i = 0; i < size(); i++) {
+ text += i == 0 ? base::ASCIIToUTF16("") : base::ASCIIToUTF16("\n");
+ if (!elements[i].is_url) {
+ // Then it's a folder. Only copy the name of the folder.
+ const base::string16 title = elements[i].title;
+ text += title;
+ } else {
+ const base::string16 url = base::UTF8ToUTF16(elements[i].url.spec());
+ text += url;
+ }
+ }
+ scw.WriteText(text);
+ }
+
+ base::Pickle pickle;
+ WriteToPickle(base::FilePath(), &pickle);
+ scw.WritePickledData(pickle,
+ ui::Clipboard::GetFormatType(kClipboardFormatString));
+}
+
+bool BookmarkNodeData::ReadFromClipboard(ui::ClipboardType type) {
+ DCHECK_EQ(type, ui::CLIPBOARD_TYPE_COPY_PASTE);
+ std::string data;
+ ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
+ clipboard->ReadData(ui::Clipboard::GetFormatType(kClipboardFormatString),
+ &data);
+
+ if (!data.empty()) {
+ base::Pickle pickle(data.data(), static_cast<int>(data.size()));
+ if (ReadFromPickle(&pickle))
+ return true;
+ }
+
+ base::string16 title;
+ std::string url;
+ clipboard->ReadBookmark(&title, &url);
+ if (!url.empty()) {
+ Element element;
+ element.is_url = true;
+ element.url = GURL(url);
+ element.title = title;
+
+ elements.clear();
+ elements.push_back(element);
+ return true;
+ }
+
+ return false;
+}
+#endif
+
+void BookmarkNodeData::WriteToPickle(const base::FilePath& profile_path,
+ base::Pickle* pickle) const {
+ profile_path.WriteToPickle(pickle);
+ pickle->WriteUInt32(static_cast<uint32_t>(size()));
+
+ for (size_t i = 0; i < size(); ++i)
+ elements[i].WriteToPickle(pickle);
+}
+
+bool BookmarkNodeData::ReadFromPickle(base::Pickle* pickle) {
+ base::PickleIterator data_iterator(*pickle);
+ uint32_t element_count;
+ if (profile_path_.ReadFromPickle(&data_iterator) &&
+ data_iterator.ReadUInt32(&element_count)) {
+ std::vector<Element> tmp_elements;
+ tmp_elements.resize(element_count);
+ for (size_t i = 0; i < element_count; ++i) {
+ if (!tmp_elements[i].ReadFromPickle(&data_iterator)) {
+ return false;
+ }
+ }
+ elements.swap(tmp_elements);
+ }
+
+ return true;
+}
+
+std::vector<const BookmarkNode*> BookmarkNodeData::GetNodes(
+ BookmarkModel* model,
+ const base::FilePath& profile_path) const {
+ std::vector<const BookmarkNode*> nodes;
+
+ if (!IsFromProfilePath(profile_path))
+ return nodes;
+
+ for (size_t i = 0; i < size(); ++i) {
+ const BookmarkNode* node = GetBookmarkNodeByID(model, elements[i].id_);
+ if (!node) {
+ nodes.clear();
+ return nodes;
+ }
+ nodes.push_back(node);
+ }
+ return nodes;
+}
+
+const BookmarkNode* BookmarkNodeData::GetFirstNode(
+ BookmarkModel* model,
+ const base::FilePath& profile_path) const {
+ std::vector<const BookmarkNode*> nodes = GetNodes(model, profile_path);
+ return nodes.size() == 1 ? nodes[0] : NULL;
+}
+
+void BookmarkNodeData::Clear() {
+ profile_path_.clear();
+ elements.clear();
+}
+
+void BookmarkNodeData::SetOriginatingProfilePath(
+ const base::FilePath& profile_path) {
+ DCHECK(profile_path_.empty());
+ profile_path_ = profile_path;
+}
+
+bool BookmarkNodeData::IsFromProfilePath(
+ const base::FilePath& profile_path) const {
+ // An empty path means the data is not associated with any profile.
+ return !profile_path_.empty() && profile_path_ == profile_path;
+}
+
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/browser/bookmark_node_data.h b/chromium/components/bookmarks/browser/bookmark_node_data.h
new file mode 100644
index 00000000000..cbad37cd9a9
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_node_data.h
@@ -0,0 +1,193 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_NODE_DATA_H_
+#define COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_NODE_DATA_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "components/bookmarks/browser/bookmark_node.h"
+#include "ui/base/clipboard/clipboard_types.h"
+#include "url/gurl.h"
+
+#if defined(TOOLKIT_VIEWS)
+#include "ui/base/clipboard/clipboard.h"
+#endif
+
+namespace base {
+class Pickle;
+class PickleIterator;
+}
+
+#if defined(TOOLKIT_VIEWS)
+namespace ui {
+class OSExchangeData;
+}
+#endif
+
+namespace bookmarks {
+
+class BookmarkModel;
+
+// BookmarkNodeData is used to represent the following:
+//
+// . A single URL.
+// . A single node from the bookmark model.
+// . A set of nodes from the bookmark model.
+//
+// BookmarkNodeData is used by bookmark related views to represent a dragged
+// bookmark or bookmarks.
+//
+// Typical usage when writing data for a drag is:
+// BookmarkNodeData data(node_user_is_dragging);
+// data.Write(os_exchange_data_for_drag);
+//
+// Typical usage to read is:
+// BookmarkNodeData data;
+// if (data.Read(os_exchange_data))
+// // data is valid, contents are in elements.
+
+struct BookmarkNodeData {
+ // Element represents a single node.
+ struct Element {
+ Element();
+ explicit Element(const BookmarkNode* node);
+ Element(const Element& other);
+ ~Element();
+
+ // If true, this element represents a URL.
+ bool is_url;
+
+ // The URL, only valid if is_url is true.
+ GURL url;
+
+ // Title of the entry, used for both urls and folders.
+ base::string16 title;
+
+ // Date of when this node was created.
+ base::Time date_added;
+
+ // Date of the last modification. Only used for folders.
+ base::Time date_folder_modified;
+
+ // Children, only used for non-URL nodes.
+ std::vector<Element> children;
+
+ // Meta info for the bookmark node.
+ BookmarkNode::MetaInfoMap meta_info_map;
+
+ int64_t id() const { return id_; }
+
+ private:
+ friend struct BookmarkNodeData;
+
+ // For reading/writing this Element.
+ void WriteToPickle(base::Pickle* pickle) const;
+ bool ReadFromPickle(base::PickleIterator* iterator);
+
+ // ID of the node.
+ int64_t id_;
+ };
+
+ // The MIME type for the clipboard format for BookmarkNodeData.
+ static const char kClipboardFormatString[];
+
+ BookmarkNodeData();
+ BookmarkNodeData(const BookmarkNodeData& other);
+
+ // Created a BookmarkNodeData populated from the arguments.
+ explicit BookmarkNodeData(const BookmarkNode* node);
+ explicit BookmarkNodeData(const std::vector<const BookmarkNode*>& nodes);
+
+ ~BookmarkNodeData();
+
+#if defined(TOOLKIT_VIEWS)
+ static const ui::Clipboard::FormatType& GetBookmarkFormatType();
+#endif
+
+ static bool ClipboardContainsBookmarks();
+
+ // Reads bookmarks from the given vector.
+ bool ReadFromVector(const std::vector<const BookmarkNode*>& nodes);
+
+ // Creates a single-bookmark DragData from url/title pair.
+ bool ReadFromTuple(const GURL& url, const base::string16& title);
+
+ // Writes bookmarks to the specified clipboard.
+ void WriteToClipboard(ui::ClipboardType type);
+
+ // Reads bookmarks from the specified clipboard. Prefers data written via
+ // WriteToClipboard() but will also attempt to read a plain bookmark.
+ bool ReadFromClipboard(ui::ClipboardType type);
+
+#if defined(TOOLKIT_VIEWS)
+ // Writes elements to data. If there is only one element and it is a URL
+ // the URL and title are written to the clipboard in a format other apps can
+ // use.
+ // |profile_path| is used to identify which profile the data came from. Use an
+ // empty path to indicate that the data is not associated with any profile.
+ void Write(const base::FilePath& profile_path,
+ ui::OSExchangeData* data) const;
+
+ // Restores this data from the clipboard, returning true on success.
+ bool Read(const ui::OSExchangeData& data);
+#endif
+
+ // Writes the data for a drag to |pickle|.
+ void WriteToPickle(const base::FilePath& profile_path,
+ base::Pickle* pickle) const;
+
+ // Reads the data for a drag from a |pickle|.
+ bool ReadFromPickle(base::Pickle* pickle);
+
+ // Returns the nodes represented by this DragData. If this DragData was
+ // created from the same profile then the nodes from the model are returned.
+ // If the nodes can't be found (may have been deleted), an empty vector is
+ // returned.
+ std::vector<const BookmarkNode*> GetNodes(
+ BookmarkModel* model,
+ const base::FilePath& profile_path) const;
+
+ // Convenience for getting the first node. Returns NULL if the data doesn't
+ // match any nodes or there is more than one node.
+ const BookmarkNode* GetFirstNode(BookmarkModel* model,
+ const base::FilePath& profile_path) const;
+
+ // Do we contain valid data?
+ bool is_valid() const { return !elements.empty(); }
+
+ // Returns true if there is a single url.
+ bool has_single_url() const { return size() == 1 && elements[0].is_url; }
+
+ // Number of elements.
+ size_t size() const { return elements.size(); }
+
+ // Clears the data.
+ void Clear();
+
+ // Sets |profile_path_|. This is useful for the constructors/readers that
+ // don't set it. This should only be called if the profile path is not
+ // already set.
+ void SetOriginatingProfilePath(const base::FilePath& profile_path);
+
+ // Returns true if this data is from the specified profile path.
+ bool IsFromProfilePath(const base::FilePath& profile_path) const;
+
+ // The actual elements written to the clipboard.
+ std::vector<Element> elements;
+
+ private:
+ // Path of the profile we originated from.
+ base::FilePath profile_path_;
+};
+
+} // namespace bookmarks
+
+#endif // COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_NODE_DATA_H_
diff --git a/chromium/components/bookmarks/browser/bookmark_node_data_ios.cc b/chromium/components/bookmarks/browser/bookmark_node_data_ios.cc
new file mode 100644
index 00000000000..44c45cbfe27
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_node_data_ios.cc
@@ -0,0 +1,26 @@
+// Copyright 2014 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 "components/bookmarks/browser/bookmark_node_data.h"
+
+#include "base/logging.h"
+
+namespace bookmarks {
+
+// static
+bool BookmarkNodeData::ClipboardContainsBookmarks() {
+ NOTREACHED();
+ return false;
+}
+
+void BookmarkNodeData::WriteToClipboard(ui::ClipboardType type) {
+ NOTREACHED();
+}
+
+bool BookmarkNodeData::ReadFromClipboard(ui::ClipboardType type) {
+ NOTREACHED();
+ return false;
+}
+
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/browser/bookmark_node_data_mac.cc b/chromium/components/bookmarks/browser/bookmark_node_data_mac.cc
new file mode 100644
index 00000000000..2dde55e777f
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_node_data_mac.cc
@@ -0,0 +1,30 @@
+// Copyright 2014 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 "components/bookmarks/browser/bookmark_node_data.h"
+
+#include "components/bookmarks/browser/bookmark_pasteboard_helper_mac.h"
+
+namespace bookmarks {
+
+// static
+bool BookmarkNodeData::ClipboardContainsBookmarks() {
+ return PasteboardContainsBookmarks(ui::CLIPBOARD_TYPE_COPY_PASTE);
+}
+
+void BookmarkNodeData::WriteToClipboard(ui::ClipboardType type) {
+ WriteBookmarksToPasteboard(type, elements, profile_path_);
+}
+
+bool BookmarkNodeData::ReadFromClipboard(ui::ClipboardType type) {
+ base::FilePath file_path;
+ if (ReadBookmarksFromPasteboard(type, elements, &file_path)) {
+ profile_path_ = file_path;
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/browser/bookmark_node_data_unittest.cc b/chromium/components/bookmarks/browser/bookmark_node_data_unittest.cc
new file mode 100644
index 00000000000..fc457ce608e
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_node_data_unittest.cc
@@ -0,0 +1,406 @@
+// Copyright 2014 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 "components/bookmarks/browser/bookmark_node_data.h"
+
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/test/bookmark_test_helpers.h"
+#include "components/bookmarks/test/test_bookmark_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/clipboard/clipboard.h"
+#include "ui/base/dragdrop/os_exchange_data.h"
+#include "ui/events/platform/platform_event_source.h"
+#include "url/gurl.h"
+
+using base::ASCIIToUTF16;
+
+namespace bookmarks {
+
+class BookmarkNodeDataTest : public testing::Test {
+ public:
+ BookmarkNodeDataTest() {}
+
+ void SetUp() override {
+ event_source_ = ui::PlatformEventSource::CreateDefault();
+ model_ = TestBookmarkClient::CreateModel();
+ test::WaitForBookmarkModelToLoad(model_.get());
+ bool success = profile_dir_.CreateUniqueTempDir();
+ ASSERT_TRUE(success);
+ }
+
+ void TearDown() override {
+ model_.reset();
+ event_source_.reset();
+ bool success = profile_dir_.Delete();
+ ASSERT_TRUE(success);
+ ui::Clipboard::DestroyClipboardForCurrentThread();
+ }
+
+ const base::FilePath& GetProfilePath() const { return profile_dir_.path(); }
+
+ BookmarkModel* model() { return model_.get(); }
+
+ protected:
+ ui::Clipboard& clipboard() { return *ui::Clipboard::GetForCurrentThread(); }
+
+ private:
+ base::ScopedTempDir profile_dir_;
+ scoped_ptr<BookmarkModel> model_;
+ scoped_ptr<ui::PlatformEventSource> event_source_;
+ base::MessageLoopForUI loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(BookmarkNodeDataTest);
+};
+
+namespace {
+
+ui::OSExchangeData::Provider* CloneProvider(const ui::OSExchangeData& data) {
+ return data.provider().Clone();
+}
+
+} // namespace
+
+// Makes sure BookmarkNodeData is initially invalid.
+TEST_F(BookmarkNodeDataTest, InitialState) {
+ BookmarkNodeData data;
+ EXPECT_FALSE(data.is_valid());
+}
+
+// Makes sure reading bogus data leaves the BookmarkNodeData invalid.
+TEST_F(BookmarkNodeDataTest, BogusRead) {
+ ui::OSExchangeData data;
+ BookmarkNodeData drag_data;
+ EXPECT_FALSE(drag_data.Read(ui::OSExchangeData(CloneProvider(data))));
+ EXPECT_FALSE(drag_data.is_valid());
+}
+
+// Writes a URL to the clipboard and make sure BookmarkNodeData can correctly
+// read it.
+TEST_F(BookmarkNodeDataTest, JustURL) {
+ const GURL url("http://google.com");
+ const base::string16 title(ASCIIToUTF16("google.com"));
+
+ ui::OSExchangeData data;
+ data.SetURL(url, title);
+
+ BookmarkNodeData drag_data;
+ EXPECT_TRUE(drag_data.Read(ui::OSExchangeData(CloneProvider(data))));
+ EXPECT_TRUE(drag_data.is_valid());
+ ASSERT_EQ(1u, drag_data.size());
+ EXPECT_TRUE(drag_data.elements[0].is_url);
+ EXPECT_EQ(url, drag_data.elements[0].url);
+ EXPECT_EQ(title, drag_data.elements[0].title);
+ EXPECT_TRUE(drag_data.elements[0].date_added.is_null());
+ EXPECT_TRUE(drag_data.elements[0].date_folder_modified.is_null());
+ EXPECT_EQ(0u, drag_data.elements[0].children.size());
+}
+
+TEST_F(BookmarkNodeDataTest, URL) {
+ // Write a single node representing a URL to the clipboard.
+ const BookmarkNode* root = model()->bookmark_bar_node();
+ GURL url(GURL("http://foo.com"));
+ const base::string16 title(ASCIIToUTF16("foo.com"));
+ const BookmarkNode* node = model()->AddURL(root, 0, title, url);
+ BookmarkNodeData drag_data(node);
+ EXPECT_TRUE(drag_data.is_valid());
+ ASSERT_EQ(1u, drag_data.size());
+ EXPECT_TRUE(drag_data.elements[0].is_url);
+ EXPECT_EQ(url, drag_data.elements[0].url);
+ EXPECT_EQ(title, drag_data.elements[0].title);
+ EXPECT_EQ(node->date_added(), drag_data.elements[0].date_added);
+ EXPECT_EQ(node->date_folder_modified(),
+ drag_data.elements[0].date_folder_modified);
+ ui::OSExchangeData data;
+ drag_data.Write(GetProfilePath(), &data);
+
+ // Now read the data back in.
+ ui::OSExchangeData data2(CloneProvider(data));
+ BookmarkNodeData read_data;
+ EXPECT_TRUE(read_data.Read(data2));
+ EXPECT_TRUE(read_data.is_valid());
+ ASSERT_EQ(1u, read_data.size());
+ EXPECT_TRUE(read_data.elements[0].is_url);
+ EXPECT_EQ(url, read_data.elements[0].url);
+ EXPECT_EQ(title, read_data.elements[0].title);
+ EXPECT_TRUE(read_data.elements[0].date_added.is_null());
+ EXPECT_TRUE(read_data.elements[0].date_folder_modified.is_null());
+ EXPECT_TRUE(read_data.GetFirstNode(model(), GetProfilePath()) == node);
+
+ // Make sure asking for the node with a different profile returns NULL.
+ base::ScopedTempDir other_profile_dir;
+ EXPECT_TRUE(other_profile_dir.CreateUniqueTempDir());
+ EXPECT_TRUE(read_data.GetFirstNode(model(), other_profile_dir.path()) ==
+ NULL);
+
+ // Writing should also put the URL and title on the clipboard.
+ GURL read_url;
+ base::string16 read_title;
+ EXPECT_TRUE(data2.GetURLAndTitle(
+ ui::OSExchangeData::CONVERT_FILENAMES, &read_url, &read_title));
+ EXPECT_EQ(url, read_url);
+ EXPECT_EQ(title, read_title);
+}
+
+// Tests writing a folder to the clipboard.
+TEST_F(BookmarkNodeDataTest, Folder) {
+ const BookmarkNode* root = model()->bookmark_bar_node();
+ const BookmarkNode* g1 = model()->AddFolder(root, 0, ASCIIToUTF16("g1"));
+ model()->AddFolder(g1, 0, ASCIIToUTF16("g11"));
+ const BookmarkNode* g12 = model()->AddFolder(g1, 0, ASCIIToUTF16("g12"));
+
+ BookmarkNodeData drag_data(g12);
+ EXPECT_TRUE(drag_data.is_valid());
+ ASSERT_EQ(1u, drag_data.size());
+ EXPECT_EQ(g12->GetTitle(), drag_data.elements[0].title);
+ EXPECT_FALSE(drag_data.elements[0].is_url);
+ EXPECT_EQ(g12->date_added(), drag_data.elements[0].date_added);
+ EXPECT_EQ(g12->date_folder_modified(),
+ drag_data.elements[0].date_folder_modified);
+
+ ui::OSExchangeData data;
+ drag_data.Write(GetProfilePath(), &data);
+
+ // Now read the data back in.
+ ui::OSExchangeData data2(CloneProvider(data));
+ BookmarkNodeData read_data;
+ EXPECT_TRUE(read_data.Read(data2));
+ EXPECT_TRUE(read_data.is_valid());
+ ASSERT_EQ(1u, read_data.size());
+ EXPECT_EQ(g12->GetTitle(), read_data.elements[0].title);
+ EXPECT_FALSE(read_data.elements[0].is_url);
+ EXPECT_TRUE(read_data.elements[0].date_added.is_null());
+ EXPECT_TRUE(read_data.elements[0].date_folder_modified.is_null());
+
+ // We should get back the same node when asking for the same profile.
+ const BookmarkNode* r_g12 = read_data.GetFirstNode(model(), GetProfilePath());
+ EXPECT_TRUE(g12 == r_g12);
+
+ // A different profile should return NULL for the node.
+ base::ScopedTempDir other_profile_dir;
+ EXPECT_TRUE(other_profile_dir.CreateUniqueTempDir());
+ EXPECT_TRUE(read_data.GetFirstNode(model(), other_profile_dir.path()) ==
+ NULL);
+}
+
+// Tests reading/writing a folder with children.
+TEST_F(BookmarkNodeDataTest, FolderWithChild) {
+ const BookmarkNode* root = model()->bookmark_bar_node();
+ const BookmarkNode* folder = model()->AddFolder(root, 0, ASCIIToUTF16("g1"));
+
+ GURL url(GURL("http://foo.com"));
+ const base::string16 title(ASCIIToUTF16("blah2"));
+
+ model()->AddURL(folder, 0, title, url);
+
+ BookmarkNodeData drag_data(folder);
+
+ ui::OSExchangeData data;
+ drag_data.Write(GetProfilePath(), &data);
+
+ // Now read the data back in.
+ ui::OSExchangeData data2(CloneProvider(data));
+ BookmarkNodeData read_data;
+ EXPECT_TRUE(read_data.Read(data2));
+ ASSERT_EQ(1u, read_data.size());
+ ASSERT_EQ(1u, read_data.elements[0].children.size());
+ const BookmarkNodeData::Element& read_child =
+ read_data.elements[0].children[0];
+
+ EXPECT_TRUE(read_child.is_url);
+ EXPECT_EQ(title, read_child.title);
+ EXPECT_EQ(url, read_child.url);
+ EXPECT_TRUE(read_data.elements[0].date_added.is_null());
+ EXPECT_TRUE(read_data.elements[0].date_folder_modified.is_null());
+ EXPECT_TRUE(read_child.is_url);
+
+ // And make sure we get the node back.
+ const BookmarkNode* r_folder =
+ read_data.GetFirstNode(model(), GetProfilePath());
+ EXPECT_TRUE(folder == r_folder);
+}
+
+// Tests reading/writing of multiple nodes.
+TEST_F(BookmarkNodeDataTest, MultipleNodes) {
+ const BookmarkNode* root = model()->bookmark_bar_node();
+ const BookmarkNode* folder = model()->AddFolder(root, 0, ASCIIToUTF16("g1"));
+
+ GURL url(GURL("http://foo.com"));
+ const base::string16 title(ASCIIToUTF16("blah2"));
+
+ const BookmarkNode* url_node = model()->AddURL(folder, 0, title, url);
+
+ // Write the nodes to the clipboard.
+ std::vector<const BookmarkNode*> nodes;
+ nodes.push_back(folder);
+ nodes.push_back(url_node);
+ BookmarkNodeData drag_data(nodes);
+ ui::OSExchangeData data;
+ drag_data.Write(GetProfilePath(), &data);
+
+ // Read the data back in.
+ ui::OSExchangeData data2(CloneProvider(data));
+ BookmarkNodeData read_data;
+ EXPECT_TRUE(read_data.Read(data2));
+ EXPECT_TRUE(read_data.is_valid());
+ ASSERT_EQ(2u, read_data.size());
+ ASSERT_EQ(1u, read_data.elements[0].children.size());
+ EXPECT_TRUE(read_data.elements[0].date_added.is_null());
+ EXPECT_TRUE(read_data.elements[0].date_folder_modified.is_null());
+
+ const BookmarkNodeData::Element& read_folder = read_data.elements[0];
+ EXPECT_FALSE(read_folder.is_url);
+ EXPECT_EQ(ASCIIToUTF16("g1"), read_folder.title);
+ EXPECT_EQ(1u, read_folder.children.size());
+
+ const BookmarkNodeData::Element& read_url = read_data.elements[1];
+ EXPECT_TRUE(read_url.is_url);
+ EXPECT_EQ(title, read_url.title);
+ EXPECT_EQ(0u, read_url.children.size());
+
+ // And make sure we get the node back.
+ std::vector<const BookmarkNode*> read_nodes =
+ read_data.GetNodes(model(), GetProfilePath());
+ ASSERT_EQ(2u, read_nodes.size());
+ EXPECT_TRUE(read_nodes[0] == folder);
+ EXPECT_TRUE(read_nodes[1] == url_node);
+
+ // Asking for the first node should return NULL with more than one element
+ // present.
+ EXPECT_TRUE(read_data.GetFirstNode(model(), GetProfilePath()) == NULL);
+}
+
+TEST_F(BookmarkNodeDataTest, WriteToClipboardURL) {
+ BookmarkNodeData data;
+ GURL url(GURL("http://foo.com"));
+ const base::string16 title(ASCIIToUTF16("blah"));
+
+ data.ReadFromTuple(url, title);
+ data.WriteToClipboard(ui::CLIPBOARD_TYPE_COPY_PASTE);
+
+ // Now read the data back in.
+ base::string16 clipboard_result;
+ clipboard().ReadText(ui::CLIPBOARD_TYPE_COPY_PASTE, &clipboard_result);
+ EXPECT_EQ(base::UTF8ToUTF16(url.spec()), clipboard_result);
+}
+
+TEST_F(BookmarkNodeDataTest, WriteToClipboardMultipleURLs) {
+ BookmarkNodeData data;
+ const BookmarkNode* root = model()->bookmark_bar_node();
+ GURL url(GURL("http://foo.com"));
+ const base::string16 title(ASCIIToUTF16("blah"));
+ GURL url2(GURL("http://bar.com"));
+ const base::string16 title2(ASCIIToUTF16("blah2"));
+ const BookmarkNode* url_node = model()->AddURL(root, 0, title, url);
+ const BookmarkNode* url_node2 = model()->AddURL(root, 1, title2, url2);
+ std::vector<const BookmarkNode*> nodes;
+ nodes.push_back(url_node);
+ nodes.push_back(url_node2);
+
+ data.ReadFromVector(nodes);
+ data.WriteToClipboard(ui::CLIPBOARD_TYPE_COPY_PASTE);
+
+ // Now read the data back in.
+ base::string16 combined_text;
+ base::string16 new_line = base::ASCIIToUTF16("\n");
+ combined_text = base::UTF8ToUTF16(url.spec()) + new_line
+ + base::UTF8ToUTF16(url2.spec());
+ base::string16 clipboard_result;
+ clipboard().ReadText(ui::CLIPBOARD_TYPE_COPY_PASTE, &clipboard_result);
+ EXPECT_EQ(combined_text, clipboard_result);
+}
+
+TEST_F(BookmarkNodeDataTest, WriteToClipboardEmptyFolder) {
+ BookmarkNodeData data;
+ const BookmarkNode* root = model()->bookmark_bar_node();
+ const BookmarkNode* folder = model()->AddFolder(root, 0, ASCIIToUTF16("g1"));
+ std::vector<const BookmarkNode*> nodes;
+ nodes.push_back(folder);
+
+ data.ReadFromVector(nodes);
+ data.WriteToClipboard(ui::CLIPBOARD_TYPE_COPY_PASTE);
+
+ // Now read the data back in.
+ base::string16 clipboard_result;
+ clipboard().ReadText(ui::CLIPBOARD_TYPE_COPY_PASTE, &clipboard_result);
+ EXPECT_EQ(base::ASCIIToUTF16("g1"), clipboard_result);
+}
+
+TEST_F(BookmarkNodeDataTest, WriteToClipboardFolderWithChildren) {
+ BookmarkNodeData data;
+ const BookmarkNode* root = model()->bookmark_bar_node();
+ const BookmarkNode* folder = model()->AddFolder(root, 0, ASCIIToUTF16("g1"));
+ GURL url(GURL("http://foo.com"));
+ const base::string16 title(ASCIIToUTF16("blah"));
+ model()->AddURL(folder, 0, title, url);
+ std::vector<const BookmarkNode*> nodes;
+ nodes.push_back(folder);
+
+ data.ReadFromVector(nodes);
+ data.WriteToClipboard(ui::CLIPBOARD_TYPE_COPY_PASTE);
+
+ // Now read the data back in.
+ base::string16 clipboard_result;
+ clipboard().ReadText(ui::CLIPBOARD_TYPE_COPY_PASTE, &clipboard_result);
+ EXPECT_EQ(base::ASCIIToUTF16("g1"), clipboard_result);
+}
+
+TEST_F(BookmarkNodeDataTest, WriteToClipboardFolderAndURL) {
+ BookmarkNodeData data;
+ GURL url(GURL("http://foo.com"));
+ const base::string16 title(ASCIIToUTF16("blah"));
+ const BookmarkNode* root = model()->bookmark_bar_node();
+ const BookmarkNode* url_node = model()->AddURL(root, 0, title, url);
+ const BookmarkNode* folder = model()->AddFolder(root, 0, ASCIIToUTF16("g1"));
+ std::vector<const BookmarkNode*> nodes;
+ nodes.push_back(url_node);
+ nodes.push_back(folder);
+
+ data.ReadFromVector(nodes);
+ data.WriteToClipboard(ui::CLIPBOARD_TYPE_COPY_PASTE);
+
+ // Now read the data back in.
+ base::string16 combined_text;
+ base::string16 new_line = base::ASCIIToUTF16("\n");
+ base::string16 folder_title = ASCIIToUTF16("g1");
+ combined_text = base::ASCIIToUTF16(url.spec()) + new_line + folder_title;
+ base::string16 clipboard_result;
+ clipboard().ReadText(ui::CLIPBOARD_TYPE_COPY_PASTE, &clipboard_result);
+ EXPECT_EQ(combined_text, clipboard_result);
+}
+
+// Tests reading/writing of meta info.
+TEST_F(BookmarkNodeDataTest, MetaInfo) {
+ // Create a node containing meta info.
+ const BookmarkNode* node = model()->AddURL(model()->other_node(),
+ 0,
+ ASCIIToUTF16("foo bar"),
+ GURL("http://www.google.com"));
+ model()->SetNodeMetaInfo(node, "somekey", "somevalue");
+ model()->SetNodeMetaInfo(node, "someotherkey", "someothervalue");
+
+ BookmarkNodeData node_data(node);
+ ui::OSExchangeData data;
+ node_data.Write(GetProfilePath(), &data);
+
+ // Read the data back in.
+ ui::OSExchangeData data2(CloneProvider(data));
+ BookmarkNodeData read_data;
+ EXPECT_TRUE(read_data.Read(data2));
+ EXPECT_TRUE(read_data.is_valid());
+ ASSERT_EQ(1u, read_data.size());
+
+ // Verify that the read data contains the same meta info.
+ BookmarkNode::MetaInfoMap meta_info_map = read_data.elements[0].meta_info_map;
+ EXPECT_EQ(2u, meta_info_map.size());
+ EXPECT_EQ("somevalue", meta_info_map["somekey"]);
+ EXPECT_EQ("someothervalue", meta_info_map["someotherkey"]);
+}
+
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/browser/bookmark_node_data_views.cc b/chromium/components/bookmarks/browser/bookmark_node_data_views.cc
new file mode 100644
index 00000000000..a56c0d2d842
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_node_data_views.cc
@@ -0,0 +1,68 @@
+// Copyright 2014 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 "components/bookmarks/browser/bookmark_node_data.h"
+
+#include "base/logging.h"
+#include "base/pickle.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/base/dragdrop/os_exchange_data.h"
+#include "url/url_constants.h"
+
+namespace bookmarks {
+
+// static
+const ui::Clipboard::FormatType& BookmarkNodeData::GetBookmarkFormatType() {
+ CR_DEFINE_STATIC_LOCAL(
+ ui::Clipboard::FormatType,
+ format,
+ (ui::Clipboard::GetFormatType(BookmarkNodeData::kClipboardFormatString)));
+
+ return format;
+}
+
+void BookmarkNodeData::Write(const base::FilePath& profile_path,
+ ui::OSExchangeData* data) const {
+ DCHECK(data);
+
+ // If there is only one element and it is a URL, write the URL to the
+ // clipboard.
+ if (has_single_url()) {
+ if (elements[0].url.SchemeIs(url::kJavaScriptScheme)) {
+ data->SetString(base::UTF8ToUTF16(elements[0].url.spec()));
+ } else {
+ data->SetURL(elements[0].url, elements[0].title);
+ }
+ }
+
+ base::Pickle data_pickle;
+ WriteToPickle(profile_path, &data_pickle);
+
+ data->SetPickledData(GetBookmarkFormatType(), data_pickle);
+}
+
+bool BookmarkNodeData::Read(const ui::OSExchangeData& data) {
+ elements.clear();
+
+ profile_path_.clear();
+
+ if (data.HasCustomFormat(GetBookmarkFormatType())) {
+ base::Pickle drag_data_pickle;
+ if (data.GetPickledData(GetBookmarkFormatType(), &drag_data_pickle)) {
+ if (!ReadFromPickle(&drag_data_pickle))
+ return false;
+ }
+ } else {
+ // See if there is a URL on the clipboard.
+ GURL url;
+ base::string16 title;
+ if (data.GetURLAndTitle(
+ ui::OSExchangeData::CONVERT_FILENAMES, &url, &title))
+ ReadFromTuple(url, title);
+ }
+
+ return is_valid();
+}
+
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/browser/bookmark_pasteboard_helper_mac.h b/chromium/components/bookmarks/browser/bookmark_pasteboard_helper_mac.h
new file mode 100644
index 00000000000..839fd510b3a
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_pasteboard_helper_mac.h
@@ -0,0 +1,55 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_PASTEBOARD_HELPER_MAC_H_
+#define COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_PASTEBOARD_HELPER_MAC_H_
+
+#include "components/bookmarks/browser/bookmark_node_data.h"
+
+#if defined(__OBJC__)
+@class NSPasteboardItem;
+@class NSString;
+#endif // __OBJC__
+
+namespace base {
+class FilePath;
+}
+
+namespace bookmarks {
+
+// This set of functions lets C++ code interact with the cocoa pasteboard and
+// dragging methods.
+
+#if defined(__OBJC__)
+// Creates a NSPasteboardItem that contains all the data for the bookmarks.
+NSPasteboardItem* PasteboardItemFromBookmarks(
+ const std::vector<BookmarkNodeData::Element>& elements,
+ const base::FilePath& profile_path);
+#endif // __OBJC__
+
+// Writes a set of bookmark elements from a profile to the specified pasteboard.
+void WriteBookmarksToPasteboard(
+ ui::ClipboardType type,
+ const std::vector<BookmarkNodeData::Element>& elements,
+ const base::FilePath& profile_path);
+
+// Reads a set of bookmark elements from the specified pasteboard.
+bool ReadBookmarksFromPasteboard(
+ ui::ClipboardType type,
+ std::vector<BookmarkNodeData::Element>& elements,
+ base::FilePath* profile_path);
+
+// Returns true if the specified pasteboard contains any sort of bookmark
+// elements. It currently does not consider a plaintext url a valid bookmark.
+bool PasteboardContainsBookmarks(ui::ClipboardType type);
+
+} // namespace bookmarks
+
+#if defined(__OBJC__)
+// Pasteboard type for dictionary containing bookmark structure consisting
+// of individual bookmark nodes and/or bookmark folders.
+extern "C" NSString* const kBookmarkDictionaryListPboardType;
+#endif // __OBJC__
+
+#endif // COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_PASTEBOARD_HELPER_MAC_H_
diff --git a/chromium/components/bookmarks/browser/bookmark_pasteboard_helper_mac.mm b/chromium/components/bookmarks/browser/bookmark_pasteboard_helper_mac.mm
new file mode 100644
index 00000000000..c37f806ea1c
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_pasteboard_helper_mac.mm
@@ -0,0 +1,310 @@
+// Copyright 2014 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 "components/bookmarks/browser/bookmark_pasteboard_helper_mac.h"
+
+#import <Cocoa/Cocoa.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/files/file_path.h"
+#include "base/strings/sys_string_conversions.h"
+#include "components/bookmarks/browser/bookmark_node.h"
+#include "ui/base/clipboard/clipboard.h"
+#include "ui/base/clipboard/clipboard_util_mac.h"
+
+NSString* const kBookmarkDictionaryListPboardType =
+ @"com.google.chrome.BookmarkDictionaryListPboardType";
+
+namespace bookmarks {
+
+namespace {
+
+// Pasteboard type used to store profile path to determine which profile
+// a set of bookmarks came from.
+NSString* const kChromiumProfilePathPboardType =
+ @"com.google.chrome.ChromiumProfilePathPboardType";
+
+// Internal bookmark ID for a bookmark node. Used only when moving inside
+// of one profile.
+NSString* const kChromiumBookmarkId = @"ChromiumBookmarkId";
+
+// Internal bookmark meta info dictionary for a bookmark node.
+NSString* const kChromiumBookmarkMetaInfo = @"ChromiumBookmarkMetaInfo";
+
+// Keys for the type of node in BookmarkDictionaryListPboardType.
+NSString* const kWebBookmarkType = @"WebBookmarkType";
+
+NSString* const kWebBookmarkTypeList = @"WebBookmarkTypeList";
+
+NSString* const kWebBookmarkTypeLeaf = @"WebBookmarkTypeLeaf";
+
+BookmarkNode::MetaInfoMap MetaInfoMapFromDictionary(NSDictionary* dictionary) {
+ BookmarkNode::MetaInfoMap meta_info_map;
+
+ for (NSString* key in dictionary) {
+ meta_info_map[base::SysNSStringToUTF8(key)] =
+ base::SysNSStringToUTF8([dictionary objectForKey:key]);
+ }
+
+ return meta_info_map;
+}
+
+void ConvertPlistToElements(NSArray* input,
+ std::vector<BookmarkNodeData::Element>& elements) {
+ NSUInteger len = [input count];
+ for (NSUInteger i = 0; i < len; ++i) {
+ NSDictionary* pboardBookmark = [input objectAtIndex:i];
+ scoped_ptr<BookmarkNode> new_node(new BookmarkNode(GURL()));
+ int64_t node_id =
+ [[pboardBookmark objectForKey:kChromiumBookmarkId] longLongValue];
+ new_node->set_id(node_id);
+
+ NSDictionary* metaInfoDictionary =
+ [pboardBookmark objectForKey:kChromiumBookmarkMetaInfo];
+ if (metaInfoDictionary)
+ new_node->SetMetaInfoMap(MetaInfoMapFromDictionary(metaInfoDictionary));
+
+ BOOL is_folder = [[pboardBookmark objectForKey:kWebBookmarkType]
+ isEqualToString:kWebBookmarkTypeList];
+ if (is_folder) {
+ new_node->set_type(BookmarkNode::FOLDER);
+ NSString* title = [pboardBookmark objectForKey:@"Title"];
+ new_node->SetTitle(base::SysNSStringToUTF16(title));
+ } else {
+ new_node->set_type(BookmarkNode::URL);
+ NSDictionary* uriDictionary =
+ [pboardBookmark objectForKey:@"URIDictionary"];
+ NSString* title = [uriDictionary objectForKey:@"title"];
+ NSString* urlString = [pboardBookmark objectForKey:@"URLString"];
+ new_node->SetTitle(base::SysNSStringToUTF16(title));
+ new_node->set_url(GURL(base::SysNSStringToUTF8(urlString)));
+ }
+ BookmarkNodeData::Element e = BookmarkNodeData::Element(new_node.get());
+ if (is_folder) {
+ ConvertPlistToElements([pboardBookmark objectForKey:@"Children"],
+ e.children);
+ }
+ elements.push_back(e);
+ }
+}
+
+bool ReadBookmarkDictionaryListPboardType(
+ NSPasteboard* pb,
+ std::vector<BookmarkNodeData::Element>& elements) {
+ NSString* uti = ui::ClipboardUtil::UTIForPasteboardType(
+ kBookmarkDictionaryListPboardType);
+ NSArray* bookmarks = [pb propertyListForType:uti];
+ if (!bookmarks)
+ return false;
+ ConvertPlistToElements(bookmarks, elements);
+ return true;
+}
+
+bool ReadWebURLsWithTitlesPboardType(
+ NSPasteboard* pb,
+ std::vector<BookmarkNodeData::Element>& elements) {
+ NSArray* urlsArr = nil;
+ NSArray* titlesArr = nil;
+ if (!ui::ClipboardUtil::URLsAndTitlesFromPasteboard(pb, &urlsArr, &titlesArr))
+ return false;
+
+ NSUInteger len = [titlesArr count];
+ for (NSUInteger i = 0; i < len; ++i) {
+ base::string16 title =
+ base::SysNSStringToUTF16([titlesArr objectAtIndex:i]);
+ std::string url = base::SysNSStringToUTF8([urlsArr objectAtIndex:i]);
+ if (!url.empty()) {
+ BookmarkNodeData::Element element;
+ element.is_url = true;
+ element.url = GURL(url);
+ element.title = title;
+ elements.push_back(element);
+ }
+ }
+ return true;
+}
+
+NSDictionary* DictionaryFromBookmarkMetaInfo(
+ const BookmarkNode::MetaInfoMap& meta_info_map) {
+ NSMutableDictionary* dictionary = [NSMutableDictionary dictionary];
+
+ for (BookmarkNode::MetaInfoMap::const_iterator it = meta_info_map.begin();
+ it != meta_info_map.end(); ++it) {
+ [dictionary setObject:base::SysUTF8ToNSString(it->second)
+ forKey:base::SysUTF8ToNSString(it->first)];
+ }
+
+ return dictionary;
+}
+
+NSArray* GetPlistForBookmarkList(
+ const std::vector<BookmarkNodeData::Element>& elements) {
+ NSMutableArray* plist = [NSMutableArray array];
+ for (size_t i = 0; i < elements.size(); ++i) {
+ BookmarkNodeData::Element element = elements[i];
+ NSDictionary* metaInfoDictionary =
+ DictionaryFromBookmarkMetaInfo(element.meta_info_map);
+ if (element.is_url) {
+ NSString* title = base::SysUTF16ToNSString(element.title);
+ NSString* url = base::SysUTF8ToNSString(element.url.spec());
+ int64_t elementId = element.id();
+ NSNumber* idNum = [NSNumber numberWithLongLong:elementId];
+ NSDictionary* uriDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
+ title, @"title", nil];
+ NSDictionary* object = [NSDictionary dictionaryWithObjectsAndKeys:
+ uriDictionary, @"URIDictionary",
+ url, @"URLString",
+ kWebBookmarkTypeLeaf, kWebBookmarkType,
+ idNum, kChromiumBookmarkId,
+ metaInfoDictionary, kChromiumBookmarkMetaInfo,
+ nil];
+ [plist addObject:object];
+ } else {
+ NSString* title = base::SysUTF16ToNSString(element.title);
+ NSArray* children = GetPlistForBookmarkList(element.children);
+ int64_t elementId = element.id();
+ NSNumber* idNum = [NSNumber numberWithLongLong:elementId];
+ NSDictionary* object = [NSDictionary dictionaryWithObjectsAndKeys:
+ title, @"Title",
+ children, @"Children",
+ kWebBookmarkTypeList, kWebBookmarkType,
+ idNum, kChromiumBookmarkId,
+ metaInfoDictionary, kChromiumBookmarkMetaInfo,
+ nil];
+ [plist addObject:object];
+ }
+ }
+ return plist;
+}
+
+void WriteBookmarkDictionaryListPboardType(
+ NSPasteboardItem* item,
+ const std::vector<BookmarkNodeData::Element>& elements) {
+ NSArray* plist = GetPlistForBookmarkList(elements);
+ NSString* uti = ui::ClipboardUtil::UTIForPasteboardType(
+ kBookmarkDictionaryListPboardType);
+ [item setPropertyList:plist forType:uti];
+}
+
+void FillFlattenedArraysForBookmarks(
+ const std::vector<BookmarkNodeData::Element>& elements,
+ NSMutableArray* url_titles,
+ NSMutableArray* urls,
+ NSMutableArray* toplevel_string_data) {
+ for (const BookmarkNodeData::Element& element : elements) {
+ NSString* title = base::SysUTF16ToNSString(element.title);
+ if (element.is_url) {
+ NSString* url = base::SysUTF8ToNSString(element.url.spec());
+ [url_titles addObject:title];
+ [urls addObject:url];
+ if (toplevel_string_data)
+ [toplevel_string_data addObject:url];
+ } else {
+ if (toplevel_string_data)
+ [toplevel_string_data addObject:title];
+ FillFlattenedArraysForBookmarks(element.children, url_titles, urls, nil);
+ }
+ }
+}
+
+base::scoped_nsobject<NSPasteboardItem> WriteSimplifiedBookmarkTypes(
+ const std::vector<BookmarkNodeData::Element>& elements) {
+ NSMutableArray* url_titles = [NSMutableArray array];
+ NSMutableArray* urls = [NSMutableArray array];
+ NSMutableArray* toplevel_string_data = [NSMutableArray array];
+ FillFlattenedArraysForBookmarks(
+ elements, url_titles, urls, toplevel_string_data);
+
+ base::scoped_nsobject<NSPasteboardItem> item;
+ if ([urls count] > 0) {
+ if ([urls count] == 1) {
+ item = ui::ClipboardUtil::PasteboardItemFromUrl([urls firstObject],
+ [url_titles firstObject]);
+ } else {
+ item = ui::ClipboardUtil::PasteboardItemFromUrls(urls, url_titles);
+ }
+ }
+
+ if (!item) {
+ item = [[NSPasteboardItem alloc] init];
+ }
+
+ [item setString:[toplevel_string_data componentsJoinedByString:@"\n"]
+ forType:NSPasteboardTypeString];
+ return item;
+}
+
+NSPasteboard* PasteboardFromType(ui::ClipboardType type) {
+ NSString* type_string = nil;
+ switch (type) {
+ case ui::CLIPBOARD_TYPE_COPY_PASTE:
+ type_string = NSGeneralPboard;
+ break;
+ case ui::CLIPBOARD_TYPE_DRAG:
+ type_string = NSDragPboard;
+ break;
+ case ui::CLIPBOARD_TYPE_SELECTION:
+ NOTREACHED();
+ break;
+ }
+
+ return [NSPasteboard pasteboardWithName:type_string];
+}
+
+} // namespace
+
+NSPasteboardItem* PasteboardItemFromBookmarks(
+ const std::vector<BookmarkNodeData::Element>& elements,
+ const base::FilePath& profile_path) {
+ base::scoped_nsobject<NSPasteboardItem> item =
+ WriteSimplifiedBookmarkTypes(elements);
+
+ WriteBookmarkDictionaryListPboardType(item, elements);
+
+ NSString* uti =
+ ui::ClipboardUtil::UTIForPasteboardType(kChromiumProfilePathPboardType);
+ [item setString:base::SysUTF8ToNSString(profile_path.value()) forType:uti];
+ return [[item retain] autorelease];
+}
+
+void WriteBookmarksToPasteboard(
+ ui::ClipboardType type,
+ const std::vector<BookmarkNodeData::Element>& elements,
+ const base::FilePath& profile_path) {
+ if (elements.empty())
+ return;
+
+ NSPasteboardItem* item = PasteboardItemFromBookmarks(elements, profile_path);
+ NSPasteboard* pb = PasteboardFromType(type);
+ [pb clearContents];
+ [pb writeObjects:@[ item ]];
+}
+
+bool ReadBookmarksFromPasteboard(
+ ui::ClipboardType type,
+ std::vector<BookmarkNodeData::Element>& elements,
+ base::FilePath* profile_path) {
+ NSPasteboard* pb = PasteboardFromType(type);
+
+ elements.clear();
+ NSString* uti =
+ ui::ClipboardUtil::UTIForPasteboardType(kChromiumProfilePathPboardType);
+ NSString* profile = [pb stringForType:uti];
+ *profile_path = base::FilePath(base::SysNSStringToUTF8(profile));
+ return ReadBookmarkDictionaryListPboardType(pb, elements) ||
+ ReadWebURLsWithTitlesPboardType(pb, elements);
+}
+
+bool PasteboardContainsBookmarks(ui::ClipboardType type) {
+ NSPasteboard* pb = PasteboardFromType(type);
+
+ NSArray* availableTypes = @[
+ ui::ClipboardUtil::UTIForWebURLsAndTitles(),
+ ui::ClipboardUtil::UTIForPasteboardType(kBookmarkDictionaryListPboardType)
+ ];
+ return [pb availableTypeFromArray:availableTypes] != nil;
+}
+
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/browser/bookmark_storage.cc b/chromium/components/bookmarks/browser/bookmark_storage.cc
new file mode 100644
index 00000000000..3a184206cfb
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_storage.cc
@@ -0,0 +1,233 @@
+// Copyright 2014 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 "components/bookmarks/browser/bookmark_storage.h"
+
+#include <stddef.h>
+#include <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_util.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/sequenced_task_runner.h"
+#include "base/time/time.h"
+#include "components/bookmarks/browser/bookmark_codec.h"
+#include "components/bookmarks/browser/bookmark_index.h"
+#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/common/bookmark_constants.h"
+
+using base::TimeTicks;
+
+namespace bookmarks {
+
+namespace {
+
+// Extension used for backup files (copy of main file created during startup).
+const base::FilePath::CharType kBackupExtension[] = FILE_PATH_LITERAL("bak");
+
+// How often we save.
+const int kSaveDelayMS = 2500;
+
+void BackupCallback(const base::FilePath& path) {
+ base::FilePath backup_path = path.ReplaceExtension(kBackupExtension);
+ base::CopyFile(path, backup_path);
+}
+
+// Adds node to the model's index, recursing through all children as well.
+void AddBookmarksToIndex(BookmarkLoadDetails* details,
+ BookmarkNode* node) {
+ if (node->is_url()) {
+ if (node->url().is_valid())
+ details->index()->Add(node);
+ } else {
+ for (int i = 0; i < node->child_count(); ++i)
+ AddBookmarksToIndex(details, node->GetChild(i));
+ }
+}
+
+void LoadCallback(const base::FilePath& path,
+ const base::WeakPtr<BookmarkStorage>& storage,
+ scoped_ptr<BookmarkLoadDetails> details,
+ base::SequencedTaskRunner* task_runner) {
+ bool load_index = false;
+ bool bookmark_file_exists = base::PathExists(path);
+ if (bookmark_file_exists) {
+ JSONFileValueDeserializer deserializer(path);
+ scoped_ptr<base::Value> root = deserializer.Deserialize(NULL, NULL);
+
+ if (root.get()) {
+ // Building the index can take a while, so we do it on the background
+ // thread.
+ int64_t max_node_id = 0;
+ BookmarkCodec codec;
+ TimeTicks start_time = TimeTicks::Now();
+ codec.Decode(details->bb_node(), details->other_folder_node(),
+ details->mobile_folder_node(), &max_node_id, *root.get());
+ details->set_max_id(std::max(max_node_id, details->max_id()));
+ details->set_computed_checksum(codec.computed_checksum());
+ details->set_stored_checksum(codec.stored_checksum());
+ details->set_ids_reassigned(codec.ids_reassigned());
+ details->set_model_meta_info_map(codec.model_meta_info_map());
+ details->set_model_sync_transaction_version(
+ codec.model_sync_transaction_version());
+ UMA_HISTOGRAM_TIMES("Bookmarks.DecodeTime",
+ TimeTicks::Now() - start_time);
+
+ load_index = true;
+ }
+ }
+
+ // Load any extra root nodes now, after the IDs have been potentially
+ // reassigned.
+ details->LoadExtraNodes();
+
+ // Load the index if there are any bookmarks in the extra nodes.
+ const BookmarkPermanentNodeList& extra_nodes = details->extra_nodes();
+ for (size_t i = 0; i < extra_nodes.size(); ++i) {
+ if (!extra_nodes[i]->empty()) {
+ load_index = true;
+ break;
+ }
+ }
+
+ if (load_index) {
+ TimeTicks start_time = TimeTicks::Now();
+ AddBookmarksToIndex(details.get(), details->bb_node());
+ AddBookmarksToIndex(details.get(), details->other_folder_node());
+ AddBookmarksToIndex(details.get(), details->mobile_folder_node());
+ for (size_t i = 0; i < extra_nodes.size(); ++i)
+ AddBookmarksToIndex(details.get(), extra_nodes[i]);
+ UMA_HISTOGRAM_TIMES("Bookmarks.CreateBookmarkIndexTime",
+ TimeTicks::Now() - start_time);
+ }
+
+ task_runner->PostTask(FROM_HERE,
+ base::Bind(&BookmarkStorage::OnLoadFinished, storage,
+ base::Passed(&details)));
+}
+
+} // namespace
+
+// BookmarkLoadDetails ---------------------------------------------------------
+
+BookmarkLoadDetails::BookmarkLoadDetails(
+ BookmarkPermanentNode* bb_node,
+ BookmarkPermanentNode* other_folder_node,
+ BookmarkPermanentNode* mobile_folder_node,
+ const LoadExtraCallback& load_extra_callback,
+ BookmarkIndex* index,
+ int64_t max_id)
+ : bb_node_(bb_node),
+ other_folder_node_(other_folder_node),
+ mobile_folder_node_(mobile_folder_node),
+ load_extra_callback_(load_extra_callback),
+ index_(index),
+ model_sync_transaction_version_(
+ BookmarkNode::kInvalidSyncTransactionVersion),
+ max_id_(max_id),
+ ids_reassigned_(false) {}
+
+BookmarkLoadDetails::~BookmarkLoadDetails() {
+}
+
+void BookmarkLoadDetails::LoadExtraNodes() {
+ if (!load_extra_callback_.is_null())
+ extra_nodes_ = load_extra_callback_.Run(&max_id_);
+}
+
+// BookmarkStorage -------------------------------------------------------------
+
+BookmarkStorage::BookmarkStorage(
+ BookmarkModel* model,
+ const base::FilePath& profile_path,
+ base::SequencedTaskRunner* sequenced_task_runner)
+ : model_(model),
+ writer_(profile_path.Append(kBookmarksFileName),
+ sequenced_task_runner,
+ base::TimeDelta::FromMilliseconds(kSaveDelayMS)),
+ sequenced_task_runner_(sequenced_task_runner),
+ weak_factory_(this) {
+}
+
+BookmarkStorage::~BookmarkStorage() {
+ if (writer_.HasPendingWrite())
+ writer_.DoScheduledWrite();
+}
+
+void BookmarkStorage::LoadBookmarks(
+ scoped_ptr<BookmarkLoadDetails> details,
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner) {
+ sequenced_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&LoadCallback, writer_.path(), weak_factory_.GetWeakPtr(),
+ base::Passed(&details), base::RetainedRef(task_runner)));
+}
+
+void BookmarkStorage::ScheduleSave() {
+ switch (backup_state_) {
+ case BACKUP_NONE:
+ backup_state_ = BACKUP_DISPATCHED;
+ sequenced_task_runner_->PostTaskAndReply(
+ FROM_HERE, base::Bind(&BackupCallback, writer_.path()),
+ base::Bind(&BookmarkStorage::OnBackupFinished,
+ weak_factory_.GetWeakPtr()));
+ return;
+ case BACKUP_DISPATCHED:
+ // Currently doing a backup which will call this function when done.
+ return;
+ case BACKUP_ATTEMPTED:
+ writer_.ScheduleWrite(this);
+ return;
+ }
+ NOTREACHED();
+}
+
+void BookmarkStorage::OnBackupFinished() {
+ backup_state_ = BACKUP_ATTEMPTED;
+ ScheduleSave();
+}
+
+void BookmarkStorage::BookmarkModelDeleted() {
+ // We need to save now as otherwise by the time SaveNow is invoked
+ // the model is gone.
+ if (writer_.HasPendingWrite())
+ SaveNow();
+ model_ = NULL;
+}
+
+bool BookmarkStorage::SerializeData(std::string* output) {
+ BookmarkCodec codec;
+ scoped_ptr<base::Value> value(codec.Encode(model_));
+ JSONStringValueSerializer serializer(output);
+ serializer.set_pretty_print(true);
+ return serializer.Serialize(*(value.get()));
+}
+
+void BookmarkStorage::OnLoadFinished(scoped_ptr<BookmarkLoadDetails> details) {
+ if (!model_)
+ return;
+
+ model_->DoneLoading(std::move(details));
+}
+
+bool BookmarkStorage::SaveNow() {
+ if (!model_ || !model_->loaded()) {
+ // We should only get here if we have a valid model and it's finished
+ // loading.
+ NOTREACHED();
+ return false;
+ }
+
+ scoped_ptr<std::string> data(new std::string);
+ if (!SerializeData(data.get()))
+ return false;
+ writer_.WriteNow(std::move(data));
+ return true;
+}
+
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/browser/bookmark_storage.h b/chromium/components/bookmarks/browser/bookmark_storage.h
new file mode 100644
index 00000000000..cc72f675cde
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_storage.h
@@ -0,0 +1,213 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_STORAGE_H_
+#define COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_STORAGE_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/files/file_path.h"
+#include "base/files/important_file_writer.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/weak_ptr.h"
+#include "components/bookmarks/browser/bookmark_node.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace bookmarks {
+
+class BookmarkIndex;
+class BookmarkModel;
+
+// A list of BookmarkPermanentNodes that owns them.
+typedef ScopedVector<BookmarkPermanentNode> BookmarkPermanentNodeList;
+
+// A callback that generates a BookmarkPermanentNodeList, given a max ID to
+// use. The max ID argument will be updated after any new nodes have been
+// created and assigned IDs.
+typedef base::Callback<BookmarkPermanentNodeList(int64_t*)> LoadExtraCallback;
+
+// BookmarkLoadDetails is used by BookmarkStorage when loading bookmarks.
+// BookmarkModel creates a BookmarkLoadDetails and passes it (including
+// ownership) to BookmarkStorage. BookmarkStorage loads the bookmarks (and
+// index) in the background thread, then calls back to the BookmarkModel (on
+// the main thread) when loading is done, passing ownership back to the
+// BookmarkModel. While loading BookmarkModel does not maintain references to
+// the contents of the BookmarkLoadDetails, this ensures we don't have any
+// threading problems.
+class BookmarkLoadDetails {
+ public:
+ BookmarkLoadDetails(BookmarkPermanentNode* bb_node,
+ BookmarkPermanentNode* other_folder_node,
+ BookmarkPermanentNode* mobile_folder_node,
+ const LoadExtraCallback& load_extra_callback,
+ BookmarkIndex* index,
+ int64_t max_id);
+ ~BookmarkLoadDetails();
+
+ void LoadExtraNodes();
+
+ BookmarkPermanentNode* bb_node() { return bb_node_.get(); }
+ BookmarkPermanentNode* release_bb_node() { return bb_node_.release(); }
+ BookmarkPermanentNode* mobile_folder_node() {
+ return mobile_folder_node_.get();
+ }
+ BookmarkPermanentNode* release_mobile_folder_node() {
+ return mobile_folder_node_.release();
+ }
+ BookmarkPermanentNode* other_folder_node() {
+ return other_folder_node_.get();
+ }
+ BookmarkPermanentNode* release_other_folder_node() {
+ return other_folder_node_.release();
+ }
+ const BookmarkPermanentNodeList& extra_nodes() {
+ return extra_nodes_;
+ }
+ void release_extra_nodes(std::vector<BookmarkPermanentNode*>* extra_nodes) {
+ extra_nodes_.release(extra_nodes);
+ }
+ BookmarkIndex* index() { return index_.get(); }
+ BookmarkIndex* release_index() { return index_.release(); }
+
+ const BookmarkNode::MetaInfoMap& model_meta_info_map() const {
+ return model_meta_info_map_;
+ }
+ void set_model_meta_info_map(const BookmarkNode::MetaInfoMap& meta_info_map) {
+ model_meta_info_map_ = meta_info_map;
+ }
+
+ int64_t model_sync_transaction_version() const {
+ return model_sync_transaction_version_;
+ }
+ void set_model_sync_transaction_version(int64_t sync_transaction_version) {
+ model_sync_transaction_version_ = sync_transaction_version;
+ }
+
+ // Max id of the nodes.
+ void set_max_id(int64_t max_id) { max_id_ = max_id; }
+ int64_t max_id() const { return max_id_; }
+
+ // Computed checksum.
+ void set_computed_checksum(const std::string& value) {
+ computed_checksum_ = value;
+ }
+ const std::string& computed_checksum() const { return computed_checksum_; }
+
+ // Stored checksum.
+ void set_stored_checksum(const std::string& value) {
+ stored_checksum_ = value;
+ }
+ const std::string& stored_checksum() const { return stored_checksum_; }
+
+ // Whether ids were reassigned. IDs are reassigned during decoding if the
+ // checksum of the file doesn't match, some IDs are missing or not
+ // unique. Basically, if the user modified the bookmarks directly we'll
+ // reassign the ids to ensure they are unique.
+ void set_ids_reassigned(bool value) { ids_reassigned_ = value; }
+ bool ids_reassigned() const { return ids_reassigned_; }
+
+ private:
+ scoped_ptr<BookmarkPermanentNode> bb_node_;
+ scoped_ptr<BookmarkPermanentNode> other_folder_node_;
+ scoped_ptr<BookmarkPermanentNode> mobile_folder_node_;
+ LoadExtraCallback load_extra_callback_;
+ BookmarkPermanentNodeList extra_nodes_;
+ scoped_ptr<BookmarkIndex> index_;
+ BookmarkNode::MetaInfoMap model_meta_info_map_;
+ int64_t model_sync_transaction_version_;
+ int64_t max_id_;
+ std::string computed_checksum_;
+ std::string stored_checksum_;
+ bool ids_reassigned_;
+
+ DISALLOW_COPY_AND_ASSIGN(BookmarkLoadDetails);
+};
+
+// BookmarkStorage handles reading/write the bookmark bar model. The
+// BookmarkModel uses the BookmarkStorage to load bookmarks from disk, as well
+// as notifying the BookmarkStorage every time the model changes.
+//
+// Internally BookmarkStorage uses BookmarkCodec to do the actual read/write.
+class BookmarkStorage : public base::ImportantFileWriter::DataSerializer {
+ public:
+ // Creates a BookmarkStorage for the specified model. The data will be loaded
+ // from and saved to a location derived from |profile_path|. The IO code will
+ // be executed as a task in |sequenced_task_runner|.
+ BookmarkStorage(BookmarkModel* model,
+ const base::FilePath& profile_path,
+ base::SequencedTaskRunner* sequenced_task_runner);
+ ~BookmarkStorage() override;
+
+ // Loads the bookmarks into the model, notifying the model when done. This
+ // takes ownership of |details| and send the |OnLoadFinished| callback from
+ // a task in |task_runner|. See BookmarkLoadDetails for details.
+ void LoadBookmarks(
+ scoped_ptr<BookmarkLoadDetails> details,
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner);
+
+ // Schedules saving the bookmark bar model to disk.
+ void ScheduleSave();
+
+ // Notification the bookmark bar model is going to be deleted. If there is
+ // a pending save, it is saved immediately.
+ void BookmarkModelDeleted();
+
+ // Callback from backend after loading the bookmark file.
+ void OnLoadFinished(scoped_ptr<BookmarkLoadDetails> details);
+
+ // ImportantFileWriter::DataSerializer implementation.
+ bool SerializeData(std::string* output) override;
+
+ private:
+ // The state of the bookmark file backup. We lazily backup this file in order
+ // to reduce disk writes until absolutely necessary. Will also leave the
+ // backup unchanged if the browser starts & quits w/o changing bookmarks.
+ enum BackupState {
+ // No attempt has yet been made to backup the bookmarks file.
+ BACKUP_NONE,
+ // A request to backup the bookmarks file has been posted, but not yet
+ // fulfilled.
+ BACKUP_DISPATCHED,
+ // The bookmarks file has been backed up (or at least attempted).
+ BACKUP_ATTEMPTED
+ };
+
+ // Serializes the data and schedules save using ImportantFileWriter.
+ // Returns true on successful serialization.
+ bool SaveNow();
+
+ // Callback from backend after creation of backup file.
+ void OnBackupFinished();
+
+ // The model. The model is NULL once BookmarkModelDeleted has been invoked.
+ BookmarkModel* model_;
+
+ // Helper to write bookmark data safely.
+ base::ImportantFileWriter writer_;
+
+ // The state of the backup file creation which is created lazily just before
+ // the first scheduled save.
+ BackupState backup_state_ = BACKUP_NONE;
+
+ // Sequenced task runner where file I/O operations will be performed at.
+ scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner_;
+
+ base::WeakPtrFactory<BookmarkStorage> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(BookmarkStorage);
+};
+
+} // namespace bookmarks
+
+#endif // COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_STORAGE_H_
diff --git a/chromium/components/bookmarks/browser/bookmark_undo_delegate.h b/chromium/components/bookmarks/browser/bookmark_undo_delegate.h
new file mode 100644
index 00000000000..f0e2d7fe517
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_undo_delegate.h
@@ -0,0 +1,35 @@
+// Copyright 2015 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.
+
+#ifndef COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_UNDO_DELEGATE_H_
+#define COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_UNDO_DELEGATE_H_
+
+#include "base/memory/scoped_ptr.h"
+
+namespace bookmarks {
+
+class BookmarkModel;
+class BookmarkNode;
+class BookmarkUndoProvider;
+
+// Delegate to handle bookmark change events in order to support undo when
+// requested.
+class BookmarkUndoDelegate {
+ public:
+ virtual ~BookmarkUndoDelegate() {}
+
+ // Sets the provider that will do the undo work.
+ virtual void SetUndoProvider(BookmarkUndoProvider* provider) = 0;
+
+ // Called when |node| was removed from |parent| at position |index|.
+ virtual void OnBookmarkNodeRemoved(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int index,
+ scoped_ptr<BookmarkNode> node) = 0;
+};
+
+
+} // namespace bookmarks
+
+#endif // COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_UNDO_DELEGATE_H_
diff --git a/chromium/components/bookmarks/browser/bookmark_undo_provider.h b/chromium/components/bookmarks/browser/bookmark_undo_provider.h
new file mode 100644
index 00000000000..a76602b1a73
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_undo_provider.h
@@ -0,0 +1,29 @@
+// Copyright 2015 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.
+
+#ifndef COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_UNDO_PROVIDER_H_
+#define COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_UNDO_PROVIDER_H_
+
+#include "base/memory/scoped_ptr.h"
+
+namespace bookmarks {
+
+class BookmarkNode;
+
+// The interface for providing undo support.
+class BookmarkUndoProvider {
+ public:
+ // Restores the previously removed |node| at |parent| in the specified
+ // |index|.
+ virtual void RestoreRemovedNode(const BookmarkNode* parent,
+ int index,
+ scoped_ptr<BookmarkNode> node) = 0;
+
+ protected:
+ virtual ~BookmarkUndoProvider() {}
+};
+
+} // namespace bookmarks
+
+#endif // COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_UNDO_PROVIDER_H_
diff --git a/chromium/components/bookmarks/browser/bookmark_utils.cc b/chromium/components/bookmarks/browser/bookmark_utils.cc
new file mode 100644
index 00000000000..6fef26dca62
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_utils.cc
@@ -0,0 +1,572 @@
+// Copyright 2014 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 "components/bookmarks/browser/bookmark_utils.h"
+
+#include <stdint.h>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/containers/hash_tables.h"
+#include "base/files/file_path.h"
+#include "base/i18n/case_conversion.h"
+#include "base/i18n/string_search.h"
+#include "base/macros.h"
+#include "base/metrics/user_metrics_action.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "components/bookmarks/browser/bookmark_client.h"
+#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/browser/scoped_group_bookmark_actions.h"
+#include "components/bookmarks/common/bookmark_pref_names.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_service.h"
+#include "components/query_parser/query_parser.h"
+#include "components/url_formatter/url_formatter.h"
+#include "ui/base/clipboard/clipboard.h"
+#include "ui/base/models/tree_node_iterator.h"
+#include "url/gurl.h"
+
+using base::Time;
+
+namespace bookmarks {
+
+namespace {
+
+// The maximum length of URL or title returned by the Cleanup functions.
+const size_t kCleanedUpUrlMaxLength = 1024u;
+const size_t kCleanedUpTitleMaxLength = 1024u;
+
+void CloneBookmarkNodeImpl(BookmarkModel* model,
+ const BookmarkNodeData::Element& element,
+ const BookmarkNode* parent,
+ int index_to_add_at,
+ bool reset_node_times) {
+ // Make sure to not copy non clonable keys.
+ BookmarkNode::MetaInfoMap meta_info_map = element.meta_info_map;
+ for (const std::string& key : model->non_cloned_keys())
+ meta_info_map.erase(key);
+
+ if (element.is_url) {
+ Time date_added = reset_node_times ? Time::Now() : element.date_added;
+ DCHECK(!date_added.is_null());
+
+ model->AddURLWithCreationTimeAndMetaInfo(parent,
+ index_to_add_at,
+ element.title,
+ element.url,
+ date_added,
+ &meta_info_map);
+ } else {
+ const BookmarkNode* cloned_node = model->AddFolderWithMetaInfo(
+ parent, index_to_add_at, element.title, &meta_info_map);
+ if (!reset_node_times) {
+ DCHECK(!element.date_folder_modified.is_null());
+ model->SetDateFolderModified(cloned_node, element.date_folder_modified);
+ }
+ for (int i = 0; i < static_cast<int>(element.children.size()); ++i)
+ CloneBookmarkNodeImpl(model, element.children[i], cloned_node, i,
+ reset_node_times);
+ }
+}
+
+// Comparison function that compares based on date modified of the two nodes.
+bool MoreRecentlyModified(const BookmarkNode* n1, const BookmarkNode* n2) {
+ return n1->date_folder_modified() > n2->date_folder_modified();
+}
+
+// Returns true if |text| contains each string in |words|. This is used when
+// searching for bookmarks.
+bool DoesBookmarkTextContainWords(const base::string16& text,
+ const std::vector<base::string16>& words) {
+ for (size_t i = 0; i < words.size(); ++i) {
+ if (!base::i18n::StringSearchIgnoringCaseAndAccents(
+ words[i], text, NULL, NULL)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Returns true if |node|s title or url contains the strings in |words|.
+bool DoesBookmarkContainWords(const BookmarkNode* node,
+ const std::vector<base::string16>& words) {
+ return DoesBookmarkTextContainWords(node->GetTitle(), words) ||
+ DoesBookmarkTextContainWords(base::UTF8ToUTF16(node->url().spec()),
+ words) ||
+ DoesBookmarkTextContainWords(
+ url_formatter::FormatUrl(
+ node->url(), url_formatter::kFormatUrlOmitNothing,
+ net::UnescapeRule::NORMAL, NULL, NULL, NULL),
+ words);
+}
+
+// This is used with a tree iterator to skip subtrees which are not visible.
+bool PruneInvisibleFolders(const BookmarkNode* node) {
+ return !node->IsVisible();
+}
+
+// This traces parents up to root, determines if node is contained in a
+// selected folder.
+bool HasSelectedAncestor(BookmarkModel* model,
+ const std::vector<const BookmarkNode*>& selected_nodes,
+ const BookmarkNode* node) {
+ if (!node || model->is_permanent_node(node))
+ return false;
+
+ for (size_t i = 0; i < selected_nodes.size(); ++i)
+ if (node->id() == selected_nodes[i]->id())
+ return true;
+
+ return HasSelectedAncestor(model, selected_nodes, node->parent());
+}
+
+const BookmarkNode* GetNodeByID(const BookmarkNode* node, int64_t id) {
+ if (node->id() == id)
+ return node;
+
+ for (int i = 0, child_count = node->child_count(); i < child_count; ++i) {
+ const BookmarkNode* result = GetNodeByID(node->GetChild(i), id);
+ if (result)
+ return result;
+ }
+ return NULL;
+}
+
+// Attempts to shorten a URL safely (i.e., by preventing the end of the URL
+// from being in the middle of an escape sequence) to no more than
+// kCleanedUpUrlMaxLength characters, returning the result.
+std::string TruncateUrl(const std::string& url) {
+ if (url.length() <= kCleanedUpUrlMaxLength)
+ return url;
+
+ // If we're in the middle of an escape sequence, truncate just before it.
+ if (url[kCleanedUpUrlMaxLength - 1] == '%')
+ return url.substr(0, kCleanedUpUrlMaxLength - 1);
+ if (url[kCleanedUpUrlMaxLength - 2] == '%')
+ return url.substr(0, kCleanedUpUrlMaxLength - 2);
+
+ return url.substr(0, kCleanedUpUrlMaxLength);
+}
+
+// Returns the URL from the clipboard. If there is no URL an empty URL is
+// returned.
+GURL GetUrlFromClipboard() {
+ base::string16 url_text;
+#if !defined(OS_IOS)
+ ui::Clipboard::GetForCurrentThread()->ReadText(ui::CLIPBOARD_TYPE_COPY_PASTE,
+ &url_text);
+#endif
+ return GURL(url_text);
+}
+
+class VectorIterator {
+ public:
+ explicit VectorIterator(std::vector<const BookmarkNode*>* nodes)
+ : nodes_(nodes), current_(nodes->begin()) {}
+ bool has_next() { return (current_ != nodes_->end()); }
+ const BookmarkNode* Next() {
+ const BookmarkNode* result = *current_;
+ ++current_;
+ return result;
+ }
+
+ private:
+ std::vector<const BookmarkNode*>* nodes_;
+ std::vector<const BookmarkNode*>::iterator current_;
+
+ DISALLOW_COPY_AND_ASSIGN(VectorIterator);
+};
+
+template <class type>
+void GetBookmarksMatchingPropertiesImpl(
+ type& iterator,
+ BookmarkModel* model,
+ const QueryFields& query,
+ const std::vector<base::string16>& query_words,
+ size_t max_count,
+ std::vector<const BookmarkNode*>* nodes) {
+ while (iterator.has_next()) {
+ const BookmarkNode* node = iterator.Next();
+ if ((!query_words.empty() &&
+ !DoesBookmarkContainWords(node, query_words)) ||
+ model->is_permanent_node(node)) {
+ continue;
+ }
+ if (query.title && node->GetTitle() != *query.title)
+ continue;
+
+ nodes->push_back(node);
+ if (nodes->size() == max_count)
+ return;
+ }
+}
+
+} // namespace
+
+QueryFields::QueryFields() {}
+QueryFields::~QueryFields() {}
+
+void CloneBookmarkNode(BookmarkModel* model,
+ const std::vector<BookmarkNodeData::Element>& elements,
+ const BookmarkNode* parent,
+ int index_to_add_at,
+ bool reset_node_times) {
+ if (!parent->is_folder() || !model) {
+ NOTREACHED();
+ return;
+ }
+ for (size_t i = 0; i < elements.size(); ++i) {
+ CloneBookmarkNodeImpl(model, elements[i], parent,
+ index_to_add_at + static_cast<int>(i),
+ reset_node_times);
+ }
+}
+
+void CopyToClipboard(BookmarkModel* model,
+ const std::vector<const BookmarkNode*>& nodes,
+ bool remove_nodes) {
+ if (nodes.empty())
+ return;
+
+ // Create array of selected nodes with descendants filtered out.
+ std::vector<const BookmarkNode*> filtered_nodes;
+ for (size_t i = 0; i < nodes.size(); ++i)
+ if (!HasSelectedAncestor(model, nodes, nodes[i]->parent()))
+ filtered_nodes.push_back(nodes[i]);
+
+ BookmarkNodeData(filtered_nodes).
+ WriteToClipboard(ui::CLIPBOARD_TYPE_COPY_PASTE);
+
+ if (remove_nodes) {
+ ScopedGroupBookmarkActions group_cut(model);
+ for (size_t i = 0; i < filtered_nodes.size(); ++i) {
+ int index = filtered_nodes[i]->parent()->GetIndexOf(filtered_nodes[i]);
+ if (index > -1)
+ model->Remove(filtered_nodes[i]);
+ }
+ }
+}
+
+// Updates |title| such that |url| and |title| pair are unique among the
+// children of |parent|.
+void MakeTitleUnique(const BookmarkModel* model,
+ const BookmarkNode* parent,
+ const GURL& url,
+ base::string16* title) {
+ base::hash_set<base::string16> titles;
+ base::string16 original_title_lower = base::i18n::ToLower(*title);
+ for (int i = 0; i < parent->child_count(); i++) {
+ const BookmarkNode* node = parent->GetChild(i);
+ if (node->is_url() && (url == node->url()) &&
+ base::StartsWith(base::i18n::ToLower(node->GetTitle()),
+ original_title_lower,
+ base::CompareCase::SENSITIVE)) {
+ titles.insert(node->GetTitle());
+ }
+ }
+
+ if (titles.find(*title) == titles.end())
+ return;
+
+ for (size_t i = 0; i < titles.size(); i++) {
+ const base::string16 new_title(*title +
+ base::ASCIIToUTF16(base::StringPrintf(
+ " (%lu)", (unsigned long)(i + 1))));
+ if (titles.find(new_title) == titles.end()) {
+ *title = new_title;
+ return;
+ }
+ }
+ NOTREACHED();
+}
+
+void PasteFromClipboard(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int index) {
+ if (!parent)
+ return;
+
+ BookmarkNodeData bookmark_data;
+ if (!bookmark_data.ReadFromClipboard(ui::CLIPBOARD_TYPE_COPY_PASTE)) {
+ GURL url = GetUrlFromClipboard();
+ if (!url.is_valid())
+ return;
+ BookmarkNode node(url);
+ node.SetTitle(base::ASCIIToUTF16(url.spec()));
+ bookmark_data = BookmarkNodeData(&node);
+ }
+ if (index == -1)
+ index = parent->child_count();
+ ScopedGroupBookmarkActions group_paste(model);
+
+ if (bookmark_data.size() == 1 &&
+ model->IsBookmarked(bookmark_data.elements[0].url)) {
+ MakeTitleUnique(model,
+ parent,
+ bookmark_data.elements[0].url,
+ &bookmark_data.elements[0].title);
+ }
+
+ CloneBookmarkNode(model, bookmark_data.elements, parent, index, true);
+}
+
+bool CanPasteFromClipboard(BookmarkModel* model, const BookmarkNode* node) {
+ if (!node || !model->client()->CanBeEditedByUser(node))
+ return false;
+ return (BookmarkNodeData::ClipboardContainsBookmarks() ||
+ GetUrlFromClipboard().is_valid());
+}
+
+std::vector<const BookmarkNode*> GetMostRecentlyModifiedUserFolders(
+ BookmarkModel* model,
+ size_t max_count) {
+ std::vector<const BookmarkNode*> nodes;
+ ui::TreeNodeIterator<const BookmarkNode> iterator(
+ model->root_node(), base::Bind(&PruneInvisibleFolders));
+
+ while (iterator.has_next()) {
+ const BookmarkNode* parent = iterator.Next();
+ if (!model->client()->CanBeEditedByUser(parent))
+ continue;
+ if (parent->is_folder() && parent->date_folder_modified() > Time()) {
+ if (max_count == 0) {
+ nodes.push_back(parent);
+ } else {
+ std::vector<const BookmarkNode*>::iterator i =
+ std::upper_bound(nodes.begin(), nodes.end(), parent,
+ &MoreRecentlyModified);
+ if (nodes.size() < max_count || i != nodes.end()) {
+ nodes.insert(i, parent);
+ while (nodes.size() > max_count)
+ nodes.pop_back();
+ }
+ }
+ } // else case, the root node, which we don't care about or imported nodes
+ // (which have a time of 0).
+ }
+
+ if (nodes.size() < max_count) {
+ // Add the permanent nodes if there is space. The permanent nodes are the
+ // only children of the root_node.
+ const BookmarkNode* root_node = model->root_node();
+
+ for (int i = 0; i < root_node->child_count(); ++i) {
+ const BookmarkNode* node = root_node->GetChild(i);
+ if (node->IsVisible() && model->client()->CanBeEditedByUser(node) &&
+ std::find(nodes.begin(), nodes.end(), node) == nodes.end()) {
+ nodes.push_back(node);
+
+ if (nodes.size() == max_count)
+ break;
+ }
+ }
+ }
+ return nodes;
+}
+
+void GetMostRecentlyAddedEntries(BookmarkModel* model,
+ size_t count,
+ std::vector<const BookmarkNode*>* nodes) {
+ ui::TreeNodeIterator<const BookmarkNode> iterator(model->root_node());
+ while (iterator.has_next()) {
+ const BookmarkNode* node = iterator.Next();
+ if (node->is_url()) {
+ std::vector<const BookmarkNode*>::iterator insert_position =
+ std::upper_bound(nodes->begin(), nodes->end(), node,
+ &MoreRecentlyAdded);
+ if (nodes->size() < count || insert_position != nodes->end()) {
+ nodes->insert(insert_position, node);
+ while (nodes->size() > count)
+ nodes->pop_back();
+ }
+ }
+ }
+}
+
+bool MoreRecentlyAdded(const BookmarkNode* n1, const BookmarkNode* n2) {
+ return n1->date_added() > n2->date_added();
+}
+
+void GetBookmarksMatchingProperties(BookmarkModel* model,
+ const QueryFields& query,
+ size_t max_count,
+ std::vector<const BookmarkNode*>* nodes) {
+ std::vector<base::string16> query_words;
+ query_parser::QueryParser parser;
+ if (query.word_phrase_query) {
+ parser.ParseQueryWords(base::i18n::ToLower(*query.word_phrase_query),
+ query_parser::MatchingAlgorithm::DEFAULT,
+ &query_words);
+ if (query_words.empty())
+ return;
+ }
+
+ if (query.url) {
+ // Shortcut into the BookmarkModel if searching for URL.
+ GURL url(*query.url);
+ std::vector<const BookmarkNode*> url_matched_nodes;
+ if (url.is_valid())
+ model->GetNodesByURL(url, &url_matched_nodes);
+ VectorIterator iterator(&url_matched_nodes);
+ GetBookmarksMatchingPropertiesImpl<VectorIterator>(
+ iterator, model, query, query_words, max_count, nodes);
+ } else {
+ ui::TreeNodeIterator<const BookmarkNode> iterator(model->root_node());
+ GetBookmarksMatchingPropertiesImpl<
+ ui::TreeNodeIterator<const BookmarkNode>>(
+ iterator, model, query, query_words, max_count, nodes);
+ }
+}
+
+void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
+ registry->RegisterBooleanPref(
+ prefs::kShowBookmarkBar,
+ false,
+ user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+ registry->RegisterBooleanPref(prefs::kEditBookmarksEnabled, true);
+ registry->RegisterBooleanPref(
+ prefs::kShowAppsShortcutInBookmarkBar,
+ true,
+ user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+ registry->RegisterBooleanPref(
+ prefs::kShowManagedBookmarksInBookmarkBar,
+ true,
+ user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+ RegisterManagedBookmarksPrefs(registry);
+}
+
+void RegisterManagedBookmarksPrefs(PrefRegistrySimple* registry) {
+ // Don't sync this, as otherwise, due to a limitation in sync, it
+ // will cause a deadlock (see http://crbug.com/97955). If we truly
+ // want to sync the expanded state of folders, it should be part of
+ // bookmark sync itself (i.e., a property of the sync folder nodes).
+ registry->RegisterListPref(prefs::kBookmarkEditorExpandedNodes,
+ new base::ListValue);
+ registry->RegisterListPref(prefs::kManagedBookmarks);
+ registry->RegisterStringPref(
+ prefs::kManagedBookmarksFolderName, std::string());
+ registry->RegisterListPref(prefs::kSupervisedBookmarks);
+}
+
+const BookmarkNode* GetParentForNewNodes(
+ const BookmarkNode* parent,
+ const std::vector<const BookmarkNode*>& selection,
+ int* index) {
+ const BookmarkNode* real_parent = parent;
+
+ if (selection.size() == 1 && selection[0]->is_folder())
+ real_parent = selection[0];
+
+ if (index) {
+ if (selection.size() == 1 && selection[0]->is_url()) {
+ *index = real_parent->GetIndexOf(selection[0]) + 1;
+ if (*index == 0) {
+ // Node doesn't exist in parent, add to end.
+ NOTREACHED();
+ *index = real_parent->child_count();
+ }
+ } else {
+ *index = real_parent->child_count();
+ }
+ }
+
+ return real_parent;
+}
+
+void DeleteBookmarkFolders(BookmarkModel* model,
+ const std::vector<int64_t>& ids) {
+ // Remove the folders that were removed. This has to be done after all the
+ // other changes have been committed.
+ for (std::vector<int64_t>::const_iterator iter = ids.begin();
+ iter != ids.end();
+ ++iter) {
+ const BookmarkNode* node = GetBookmarkNodeByID(model, *iter);
+ if (!node)
+ continue;
+ model->Remove(node);
+ }
+}
+
+void AddIfNotBookmarked(BookmarkModel* model,
+ const GURL& url,
+ const base::string16& title) {
+ if (IsBookmarkedByUser(model, url))
+ return; // Nothing to do, a user bookmark with that url already exists.
+ model->client()->RecordAction(base::UserMetricsAction("BookmarkAdded"));
+ const BookmarkNode* parent = model->GetParentForNewNodes();
+ model->AddURL(parent, parent->child_count(), title, url);
+}
+
+void RemoveAllBookmarks(BookmarkModel* model, const GURL& url) {
+ std::vector<const BookmarkNode*> bookmarks;
+ model->GetNodesByURL(url, &bookmarks);
+
+ // Remove all the user bookmarks.
+ for (size_t i = 0; i < bookmarks.size(); ++i) {
+ const BookmarkNode* node = bookmarks[i];
+ int index = node->parent()->GetIndexOf(node);
+ if (index > -1 && model->client()->CanBeEditedByUser(node))
+ model->Remove(node);
+ }
+}
+
+base::string16 CleanUpUrlForMatching(
+ const GURL& gurl,
+ base::OffsetAdjuster::Adjustments* adjustments) {
+ base::OffsetAdjuster::Adjustments tmp_adjustments;
+ return base::i18n::ToLower(url_formatter::FormatUrlWithAdjustments(
+ GURL(TruncateUrl(gurl.spec())),
+ url_formatter::kFormatUrlOmitUsernamePassword,
+ net::UnescapeRule::SPACES | net::UnescapeRule::PATH_SEPARATORS |
+ net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS,
+ nullptr, nullptr, adjustments ? adjustments : &tmp_adjustments));
+}
+
+base::string16 CleanUpTitleForMatching(const base::string16& title) {
+ return base::i18n::ToLower(title.substr(0u, kCleanedUpTitleMaxLength));
+}
+
+bool CanAllBeEditedByUser(BookmarkClient* client,
+ const std::vector<const BookmarkNode*>& nodes) {
+ for (size_t i = 0; i < nodes.size(); ++i) {
+ if (!client->CanBeEditedByUser(nodes[i]))
+ return false;
+ }
+ return true;
+}
+
+bool IsBookmarkedByUser(BookmarkModel* model, const GURL& url) {
+ std::vector<const BookmarkNode*> nodes;
+ model->GetNodesByURL(url, &nodes);
+ for (size_t i = 0; i < nodes.size(); ++i) {
+ if (model->client()->CanBeEditedByUser(nodes[i]))
+ return true;
+ }
+ return false;
+}
+
+const BookmarkNode* GetBookmarkNodeByID(const BookmarkModel* model,
+ int64_t id) {
+ // TODO(sky): TreeNode needs a method that visits all nodes using a predicate.
+ return GetNodeByID(model->root_node(), id);
+}
+
+bool IsDescendantOf(const BookmarkNode* node, const BookmarkNode* root) {
+ return node && node->HasAncestor(root);
+}
+
+bool HasDescendantsOf(const std::vector<const BookmarkNode*>& list,
+ const BookmarkNode* root) {
+ for (const BookmarkNode* node : list) {
+ if (IsDescendantOf(node, root))
+ return true;
+ }
+ return false;
+}
+
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/browser/bookmark_utils.h b/chromium/components/bookmarks/browser/bookmark_utils.h
new file mode 100644
index 00000000000..0a3a3730f9d
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_utils.h
@@ -0,0 +1,167 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_UTILS_H_
+#define COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_UTILS_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "base/strings/utf_offset_string_conversions.h"
+#include "components/bookmarks/browser/bookmark_node_data.h"
+#include "components/prefs/pref_registry_simple.h"
+
+class GURL;
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+// A collection of bookmark utility functions used by various parts of the UI
+// that show bookmarks (bookmark manager, bookmark bar view, ...) and other
+// systems that involve indexing and searching bookmarks.
+namespace bookmarks {
+
+class BookmarkClient;
+class BookmarkModel;
+class BookmarkNode;
+
+// Fields to use when finding matching bookmarks.
+struct QueryFields {
+ QueryFields();
+ ~QueryFields();
+
+ scoped_ptr<base::string16> word_phrase_query;
+ scoped_ptr<base::string16> url;
+ scoped_ptr<base::string16> title;
+};
+
+// Clones bookmark node, adding newly created nodes to |parent| starting at
+// |index_to_add_at|. If |reset_node_times| is true cloned bookmarks and
+// folders will receive new creation times and folder modification times
+// instead of using the values stored in |elements|.
+void CloneBookmarkNode(BookmarkModel* model,
+ const std::vector<BookmarkNodeData::Element>& elements,
+ const BookmarkNode* parent,
+ int index_to_add_at,
+ bool reset_node_times);
+
+// Copies nodes onto the clipboard. If |remove_nodes| is true the nodes are
+// removed after copied to the clipboard. The nodes are copied in such a way
+// that if pasted again copies are made.
+void CopyToClipboard(BookmarkModel* model,
+ const std::vector<const BookmarkNode*>& nodes,
+ bool remove_nodes);
+
+// Pastes from the clipboard. The new nodes are added to |parent|, unless
+// |parent| is null in which case this does nothing. The nodes are inserted
+// at |index|. If |index| is -1 the nodes are added to the end.
+void PasteFromClipboard(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int index);
+
+// Returns true if the user can copy from the pasteboard.
+bool CanPasteFromClipboard(BookmarkModel* model, const BookmarkNode* node);
+
+// Returns a vector containing up to |max_count| of the most recently modified
+// user folders. This never returns an empty vector.
+std::vector<const BookmarkNode*> GetMostRecentlyModifiedUserFolders(
+ BookmarkModel* model, size_t max_count);
+
+// Returns the most recently added bookmarks. This does not return folders,
+// only nodes of type url.
+void GetMostRecentlyAddedEntries(BookmarkModel* model,
+ size_t count,
+ std::vector<const BookmarkNode*>* nodes);
+
+// Returns true if |n1| was added more recently than |n2|.
+bool MoreRecentlyAdded(const BookmarkNode* n1, const BookmarkNode* n2);
+
+// Returns up to |max_count| bookmarks from |model| whose url or title contain
+// the text |query.word_phrase_query| and exactly match |query.url| and
+// |query.title|, for all of the preceding fields that are not NULL.
+void GetBookmarksMatchingProperties(BookmarkModel* model,
+ const QueryFields& query,
+ size_t max_count,
+ std::vector<const BookmarkNode*>* nodes);
+
+// Register user preferences for Bookmarks Bar.
+void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+// Register managed bookmarks preferences.
+void RegisterManagedBookmarksPrefs(PrefRegistrySimple* registry);
+
+// Returns the parent for newly created folders/bookmarks. If |selection| has
+// one element and it is a folder, |selection[0]| is returned, otherwise
+// |parent| is returned. If |index| is non-null it is set to the index newly
+// added nodes should be added at.
+const BookmarkNode* GetParentForNewNodes(
+ const BookmarkNode* parent,
+ const std::vector<const BookmarkNode*>& selection,
+ int* index);
+
+// Deletes the bookmark folders for the given list of |ids|.
+void DeleteBookmarkFolders(BookmarkModel* model,
+ const std::vector<int64_t>& ids);
+
+// If there are no user bookmarks for url, a bookmark is created.
+void AddIfNotBookmarked(BookmarkModel* model,
+ const GURL& url,
+ const base::string16& title);
+
+// Removes all bookmarks for the given |url|.
+void RemoveAllBookmarks(BookmarkModel* model, const GURL& url);
+
+// Truncates an overly-long URL, unescapes it and interprets the
+// characters as UTF-8 (both via url_formatter::FormatUrl()), and
+// lower-cases it, returning the result. |adjustments|, if non-NULL, is
+// set to reflect the transformations the URL spec underwent to become the
+// return value. If a caller computes offsets (e.g., for the position
+// of matched text) in this cleaned-up string, it can use |adjustments|
+// to calculate the location of these offsets in the original string
+// (via base::OffsetAdjuster::UnadjustOffsets()). This is useful if later
+// the original string gets formatted in a different way for displaying.
+// In this case, knowing the offsets in the original string will allow them
+// to be properly translated to offsets in the newly-formatted string.
+//
+// The unescaping done by this function makes it possible to match substrings
+// that were originally escaped for navigation; for example, if the user
+// searched for "a&p", the query would be escaped as "a%26p", so without
+// unescaping, an input string of "a&p" would no longer match this URL. Note
+// that the resulting unescaped URL may not be directly navigable (which is
+// why it was escaped to begin with).
+base::string16 CleanUpUrlForMatching(
+ const GURL& gurl,
+ base::OffsetAdjuster::Adjustments* adjustments);
+
+// Returns the lower-cased title, possibly truncated if the original title
+// is overly-long.
+base::string16 CleanUpTitleForMatching(const base::string16& title);
+
+// Returns true if all the |nodes| can be edited by the user,
+// as determined by BookmarkClient::CanBeEditedByUser().
+bool CanAllBeEditedByUser(BookmarkClient* client,
+ const std::vector<const BookmarkNode*>& nodes);
+
+// Returns true if |url| has a bookmark in the |model| that can be edited
+// by the user.
+bool IsBookmarkedByUser(BookmarkModel* model, const GURL& url);
+
+// Returns the node with |id|, or NULL if there is no node with |id|.
+const BookmarkNode* GetBookmarkNodeByID(const BookmarkModel* model, int64_t id);
+
+// Returns true if |node| is a descendant of |root|.
+bool IsDescendantOf(const BookmarkNode* node, const BookmarkNode* root);
+
+// Returns true if any node in |list| is a descendant of |root|.
+bool HasDescendantsOf(const std::vector<const BookmarkNode*>& list,
+ const BookmarkNode* root);
+
+} // namespace bookmarks
+
+#endif // COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_UTILS_H_
diff --git a/chromium/components/bookmarks/browser/bookmark_utils_unittest.cc b/chromium/components/bookmarks/browser/bookmark_utils_unittest.cc
new file mode 100644
index 00000000000..f27676d8041
--- /dev/null
+++ b/chromium/components/bookmarks/browser/bookmark_utils_unittest.cc
@@ -0,0 +1,606 @@
+// Copyright 2014 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 "components/bookmarks/browser/bookmark_utils.h"
+
+#include <stddef.h>
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "components/bookmarks/browser/base_bookmark_model_observer.h"
+#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/browser/bookmark_node_data.h"
+#include "components/bookmarks/test/test_bookmark_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/clipboard/clipboard.h"
+#include "ui/base/clipboard/scoped_clipboard_writer.h"
+
+using base::ASCIIToUTF16;
+using std::string;
+
+namespace bookmarks {
+namespace {
+
+class BookmarkUtilsTest : public testing::Test,
+ public BaseBookmarkModelObserver {
+ public:
+ BookmarkUtilsTest()
+ : grouped_changes_beginning_count_(0),
+ grouped_changes_ended_count_(0) {}
+ ~BookmarkUtilsTest() override {}
+
+// Copy and paste is not yet supported on iOS. http://crbug.com/228147
+#if !defined(OS_IOS)
+ void TearDown() override {
+ ui::Clipboard::DestroyClipboardForCurrentThread();
+ }
+#endif // !defined(OS_IOS)
+
+ // Certain user actions require multiple changes to the bookmark model,
+ // however these modifications need to be atomic for the undo framework. The
+ // BaseBookmarkModelObserver is used to inform the boundaries of the user
+ // action. For example, when multiple bookmarks are cut to the clipboard we
+ // expect one call each to GroupedBookmarkChangesBeginning/Ended.
+ void ExpectGroupedChangeCount(int expected_beginning_count,
+ int expected_ended_count) {
+ // The undo framework is not used under Android. Thus the group change
+ // events will not be fired and so should not be tested for Android.
+#if !defined(OS_ANDROID)
+ EXPECT_EQ(grouped_changes_beginning_count_, expected_beginning_count);
+ EXPECT_EQ(grouped_changes_ended_count_, expected_ended_count);
+#endif
+ }
+
+ private:
+ // BaseBookmarkModelObserver:
+ void BookmarkModelChanged() override {}
+
+ void GroupedBookmarkChangesBeginning(BookmarkModel* model) override {
+ ++grouped_changes_beginning_count_;
+ }
+
+ void GroupedBookmarkChangesEnded(BookmarkModel* model) override {
+ ++grouped_changes_ended_count_;
+ }
+
+ int grouped_changes_beginning_count_;
+ int grouped_changes_ended_count_;
+
+ // Clipboard requires a message loop.
+ base::MessageLoopForUI loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(BookmarkUtilsTest);
+};
+
+TEST_F(BookmarkUtilsTest, GetBookmarksMatchingPropertiesWordPhraseQuery) {
+ scoped_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
+ const BookmarkNode* node1 = model->AddURL(model->other_node(),
+ 0,
+ ASCIIToUTF16("foo bar"),
+ GURL("http://www.google.com"));
+ const BookmarkNode* node2 = model->AddURL(model->other_node(),
+ 0,
+ ASCIIToUTF16("baz buz"),
+ GURL("http://www.cnn.com"));
+ const BookmarkNode* folder1 =
+ model->AddFolder(model->other_node(), 0, ASCIIToUTF16("foo"));
+ std::vector<const BookmarkNode*> nodes;
+ QueryFields query;
+ query.word_phrase_query.reset(new base::string16);
+ // No nodes are returned for empty string.
+ *query.word_phrase_query = ASCIIToUTF16("");
+ GetBookmarksMatchingProperties(model.get(), query, 100, &nodes);
+ EXPECT_TRUE(nodes.empty());
+ nodes.clear();
+
+ // No nodes are returned for space-only string.
+ *query.word_phrase_query = ASCIIToUTF16(" ");
+ GetBookmarksMatchingProperties(model.get(), query, 100, &nodes);
+ EXPECT_TRUE(nodes.empty());
+ nodes.clear();
+
+ // Node "foo bar" and folder "foo" are returned in search results.
+ *query.word_phrase_query = ASCIIToUTF16("foo");
+ GetBookmarksMatchingProperties(model.get(), query, 100, &nodes);
+ ASSERT_EQ(2U, nodes.size());
+ EXPECT_TRUE(nodes[0] == folder1);
+ EXPECT_TRUE(nodes[1] == node1);
+ nodes.clear();
+
+ // Ensure url matches return in search results.
+ *query.word_phrase_query = ASCIIToUTF16("cnn");
+ GetBookmarksMatchingProperties(model.get(), query, 100, &nodes);
+ ASSERT_EQ(1U, nodes.size());
+ EXPECT_TRUE(nodes[0] == node2);
+ nodes.clear();
+
+ // Ensure folder "foo" is not returned in more specific search.
+ *query.word_phrase_query = ASCIIToUTF16("foo bar");
+ GetBookmarksMatchingProperties(model.get(), query, 100, &nodes);
+ ASSERT_EQ(1U, nodes.size());
+ EXPECT_TRUE(nodes[0] == node1);
+ nodes.clear();
+
+ // Bookmark Bar and Other Bookmarks are not returned in search results.
+ *query.word_phrase_query = ASCIIToUTF16("Bookmark");
+ GetBookmarksMatchingProperties(model.get(), query, 100, &nodes);
+ ASSERT_EQ(0U, nodes.size());
+ nodes.clear();
+}
+
+// Check exact matching against a URL query.
+TEST_F(BookmarkUtilsTest, GetBookmarksMatchingPropertiesUrl) {
+ scoped_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
+ const BookmarkNode* node1 = model->AddURL(model->other_node(),
+ 0,
+ ASCIIToUTF16("Google"),
+ GURL("https://www.google.com/"));
+ model->AddURL(model->other_node(),
+ 0,
+ ASCIIToUTF16("Google Calendar"),
+ GURL("https://www.google.com/calendar"));
+
+ model->AddFolder(model->other_node(), 0, ASCIIToUTF16("Folder"));
+
+ std::vector<const BookmarkNode*> nodes;
+ QueryFields query;
+ query.url.reset(new base::string16);
+ *query.url = ASCIIToUTF16("https://www.google.com/");
+ GetBookmarksMatchingProperties(model.get(), query, 100, &nodes);
+ ASSERT_EQ(1U, nodes.size());
+ EXPECT_TRUE(nodes[0] == node1);
+ nodes.clear();
+
+ *query.url = ASCIIToUTF16("calendar");
+ GetBookmarksMatchingProperties(model.get(), query, 100, &nodes);
+ ASSERT_EQ(0U, nodes.size());
+ nodes.clear();
+
+ // Empty URL should not match folders.
+ *query.url = ASCIIToUTF16("");
+ GetBookmarksMatchingProperties(model.get(), query, 100, &nodes);
+ ASSERT_EQ(0U, nodes.size());
+ nodes.clear();
+}
+
+// Check exact matching against a title query.
+TEST_F(BookmarkUtilsTest, GetBookmarksMatchingPropertiesTitle) {
+ scoped_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
+ const BookmarkNode* node1 = model->AddURL(model->other_node(),
+ 0,
+ ASCIIToUTF16("Google"),
+ GURL("https://www.google.com/"));
+ model->AddURL(model->other_node(),
+ 0,
+ ASCIIToUTF16("Google Calendar"),
+ GURL("https://www.google.com/calendar"));
+
+ const BookmarkNode* folder1 =
+ model->AddFolder(model->other_node(), 0, ASCIIToUTF16("Folder"));
+
+ std::vector<const BookmarkNode*> nodes;
+ QueryFields query;
+ query.title.reset(new base::string16);
+ *query.title = ASCIIToUTF16("Google");
+ GetBookmarksMatchingProperties(model.get(), query, 100, &nodes);
+ ASSERT_EQ(1U, nodes.size());
+ EXPECT_TRUE(nodes[0] == node1);
+ nodes.clear();
+
+ *query.title = ASCIIToUTF16("Calendar");
+ GetBookmarksMatchingProperties(model.get(), query, 100, &nodes);
+ ASSERT_EQ(0U, nodes.size());
+ nodes.clear();
+
+ // Title should match folders.
+ *query.title = ASCIIToUTF16("Folder");
+ GetBookmarksMatchingProperties(model.get(), query, 100, &nodes);
+ ASSERT_EQ(1U, nodes.size());
+ EXPECT_TRUE(nodes[0] == folder1);
+ nodes.clear();
+}
+
+// Check matching against a query with multiple predicates.
+TEST_F(BookmarkUtilsTest, GetBookmarksMatchingPropertiesConjunction) {
+ scoped_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
+ const BookmarkNode* node1 = model->AddURL(model->other_node(),
+ 0,
+ ASCIIToUTF16("Google"),
+ GURL("https://www.google.com/"));
+ model->AddURL(model->other_node(),
+ 0,
+ ASCIIToUTF16("Google Calendar"),
+ GURL("https://www.google.com/calendar"));
+
+ model->AddFolder(model->other_node(), 0, ASCIIToUTF16("Folder"));
+
+ std::vector<const BookmarkNode*> nodes;
+ QueryFields query;
+
+ // Test all fields matching.
+ query.word_phrase_query.reset(new base::string16(ASCIIToUTF16("www")));
+ query.url.reset(new base::string16(ASCIIToUTF16("https://www.google.com/")));
+ query.title.reset(new base::string16(ASCIIToUTF16("Google")));
+ GetBookmarksMatchingProperties(model.get(), query, 100, &nodes);
+ ASSERT_EQ(1U, nodes.size());
+ EXPECT_TRUE(nodes[0] == node1);
+ nodes.clear();
+
+ scoped_ptr<base::string16>* fields[] = {
+ &query.word_phrase_query, &query.url, &query.title };
+
+ // Test two fields matching.
+ for (size_t i = 0; i < arraysize(fields); i++) {
+ scoped_ptr<base::string16> original_value(fields[i]->release());
+ GetBookmarksMatchingProperties(model.get(), query, 100, &nodes);
+ ASSERT_EQ(1U, nodes.size());
+ EXPECT_TRUE(nodes[0] == node1);
+ nodes.clear();
+ fields[i]->reset(original_value.release());
+ }
+
+ // Test two fields matching with one non-matching field.
+ for (size_t i = 0; i < arraysize(fields); i++) {
+ scoped_ptr<base::string16> original_value(fields[i]->release());
+ fields[i]->reset(new base::string16(ASCIIToUTF16("fjdkslafjkldsa")));
+ GetBookmarksMatchingProperties(model.get(), query, 100, &nodes);
+ ASSERT_EQ(0U, nodes.size());
+ nodes.clear();
+ fields[i]->reset(original_value.release());
+ }
+}
+
+// Copy and paste is not yet supported on iOS. http://crbug.com/228147
+#if !defined(OS_IOS)
+TEST_F(BookmarkUtilsTest, PasteBookmarkFromURL) {
+ scoped_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
+ const base::string16 url_text = ASCIIToUTF16("http://www.google.com/");
+ const BookmarkNode* new_folder = model->AddFolder(
+ model->bookmark_bar_node(), 0, ASCIIToUTF16("New_Folder"));
+
+ // Write blank text to clipboard.
+ {
+ ui::ScopedClipboardWriter clipboard_writer(ui::CLIPBOARD_TYPE_COPY_PASTE);
+ clipboard_writer.WriteText(base::string16());
+ }
+ // Now we shouldn't be able to paste from the clipboard.
+ EXPECT_FALSE(CanPasteFromClipboard(model.get(), new_folder));
+
+ // Write some valid url to the clipboard.
+ {
+ ui::ScopedClipboardWriter clipboard_writer(ui::CLIPBOARD_TYPE_COPY_PASTE);
+ clipboard_writer.WriteText(url_text);
+ }
+ // Now we should be able to paste from the clipboard.
+ EXPECT_TRUE(CanPasteFromClipboard(model.get(), new_folder));
+
+ PasteFromClipboard(model.get(), new_folder, 0);
+ ASSERT_EQ(1, new_folder->child_count());
+
+ // Url for added node should be same as url_text.
+ EXPECT_EQ(url_text, ASCIIToUTF16(new_folder->GetChild(0)->url().spec()));
+}
+
+TEST_F(BookmarkUtilsTest, CopyPaste) {
+ scoped_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
+ const BookmarkNode* node = model->AddURL(model->other_node(),
+ 0,
+ ASCIIToUTF16("foo bar"),
+ GURL("http://www.google.com"));
+
+ // Copy a node to the clipboard.
+ std::vector<const BookmarkNode*> nodes;
+ nodes.push_back(node);
+ CopyToClipboard(model.get(), nodes, false);
+
+ // And make sure we can paste a bookmark from the clipboard.
+ EXPECT_TRUE(CanPasteFromClipboard(model.get(), model->bookmark_bar_node()));
+
+ // Write some text to the clipboard.
+ {
+ ui::ScopedClipboardWriter clipboard_writer(
+ ui::CLIPBOARD_TYPE_COPY_PASTE);
+ clipboard_writer.WriteText(ASCIIToUTF16("foo"));
+ }
+
+ // Now we shouldn't be able to paste from the clipboard.
+ EXPECT_FALSE(CanPasteFromClipboard(model.get(), model->bookmark_bar_node()));
+}
+
+// Test for updating title such that url and title pair are unique among the
+// children of parent.
+TEST_F(BookmarkUtilsTest, MakeTitleUnique) {
+ scoped_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
+ const base::string16 url_text = ASCIIToUTF16("http://www.google.com/");
+ const base::string16 title_text = ASCIIToUTF16("foobar");
+ const BookmarkNode* bookmark_bar_node = model->bookmark_bar_node();
+
+ const BookmarkNode* node =
+ model->AddURL(bookmark_bar_node, 0, title_text, GURL(url_text));
+
+ EXPECT_EQ(url_text,
+ ASCIIToUTF16(bookmark_bar_node->GetChild(0)->url().spec()));
+ EXPECT_EQ(title_text, bookmark_bar_node->GetChild(0)->GetTitle());
+
+ // Copy a node to the clipboard.
+ std::vector<const BookmarkNode*> nodes;
+ nodes.push_back(node);
+ CopyToClipboard(model.get(), nodes, false);
+
+ // Now we should be able to paste from the clipboard.
+ EXPECT_TRUE(CanPasteFromClipboard(model.get(), bookmark_bar_node));
+
+ PasteFromClipboard(model.get(), bookmark_bar_node, 1);
+ ASSERT_EQ(2, bookmark_bar_node->child_count());
+
+ // Url for added node should be same as url_text.
+ EXPECT_EQ(url_text,
+ ASCIIToUTF16(bookmark_bar_node->GetChild(1)->url().spec()));
+ // Title for added node should be numeric subscript suffix with copied node
+ // title.
+ EXPECT_EQ(ASCIIToUTF16("foobar (1)"),
+ bookmark_bar_node->GetChild(1)->GetTitle());
+}
+
+TEST_F(BookmarkUtilsTest, CopyPasteMetaInfo) {
+ scoped_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
+ const BookmarkNode* node = model->AddURL(model->other_node(),
+ 0,
+ ASCIIToUTF16("foo bar"),
+ GURL("http://www.google.com"));
+ model->SetNodeMetaInfo(node, "somekey", "somevalue");
+ model->SetNodeMetaInfo(node, "someotherkey", "someothervalue");
+
+ // Copy a node to the clipboard.
+ std::vector<const BookmarkNode*> nodes;
+ nodes.push_back(node);
+ CopyToClipboard(model.get(), nodes, false);
+
+ // Paste node to a different folder.
+ const BookmarkNode* folder =
+ model->AddFolder(model->bookmark_bar_node(), 0, ASCIIToUTF16("Folder"));
+ EXPECT_EQ(0, folder->child_count());
+
+ // And make sure we can paste a bookmark from the clipboard.
+ EXPECT_TRUE(CanPasteFromClipboard(model.get(), folder));
+
+ PasteFromClipboard(model.get(), folder, 0);
+ ASSERT_EQ(1, folder->child_count());
+
+ // Verify that the pasted node contains the same meta info.
+ const BookmarkNode* pasted = folder->GetChild(0);
+ ASSERT_TRUE(pasted->GetMetaInfoMap());
+ EXPECT_EQ(2u, pasted->GetMetaInfoMap()->size());
+ std::string value;
+ EXPECT_TRUE(pasted->GetMetaInfo("somekey", &value));
+ EXPECT_EQ("somevalue", value);
+ EXPECT_TRUE(pasted->GetMetaInfo("someotherkey", &value));
+ EXPECT_EQ("someothervalue", value);
+}
+
+#if defined(OS_LINUX) || defined(OS_MACOSX)
+// http://crbug.com/396472
+#define MAYBE_CutToClipboard DISABLED_CutToClipboard
+#else
+#define MAYBE_CutToClipboard CutToClipboard
+#endif
+TEST_F(BookmarkUtilsTest, MAYBE_CutToClipboard) {
+ scoped_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
+ model->AddObserver(this);
+
+ base::string16 title(ASCIIToUTF16("foo"));
+ GURL url("http://foo.com");
+ const BookmarkNode* n1 = model->AddURL(model->other_node(), 0, title, url);
+ const BookmarkNode* n2 = model->AddURL(model->other_node(), 1, title, url);
+
+ // Cut the nodes to the clipboard.
+ std::vector<const BookmarkNode*> nodes;
+ nodes.push_back(n1);
+ nodes.push_back(n2);
+ CopyToClipboard(model.get(), nodes, true);
+
+ // Make sure the nodes were removed.
+ EXPECT_EQ(0, model->other_node()->child_count());
+
+ // Make sure observers were notified the set of changes should be grouped.
+ ExpectGroupedChangeCount(1, 1);
+
+ // And make sure we can paste from the clipboard.
+ EXPECT_TRUE(CanPasteFromClipboard(model.get(), model->other_node()));
+}
+
+TEST_F(BookmarkUtilsTest, PasteNonEditableNodes) {
+ // Load a model with an extra node that is not editable.
+ scoped_ptr<TestBookmarkClient> client(new TestBookmarkClient());
+ BookmarkPermanentNode* extra_node = new BookmarkPermanentNode(100);
+ BookmarkPermanentNodeList extra_nodes;
+ extra_nodes.push_back(extra_node);
+ client->SetExtraNodesToLoad(std::move(extra_nodes));
+
+ scoped_ptr<BookmarkModel> model(
+ TestBookmarkClient::CreateModelWithClient(std::move(client)));
+ const BookmarkNode* node = model->AddURL(model->other_node(),
+ 0,
+ ASCIIToUTF16("foo bar"),
+ GURL("http://www.google.com"));
+
+ // Copy a node to the clipboard.
+ std::vector<const BookmarkNode*> nodes;
+ nodes.push_back(node);
+ CopyToClipboard(model.get(), nodes, false);
+
+ // And make sure we can paste a bookmark from the clipboard.
+ EXPECT_TRUE(CanPasteFromClipboard(model.get(), model->bookmark_bar_node()));
+
+ // But it can't be pasted into a non-editable folder.
+ BookmarkClient* upcast = model->client();
+ EXPECT_FALSE(upcast->CanBeEditedByUser(extra_node));
+ EXPECT_FALSE(CanPasteFromClipboard(model.get(), extra_node));
+}
+#endif // !defined(OS_IOS)
+
+TEST_F(BookmarkUtilsTest, GetParentForNewNodes) {
+ scoped_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
+ // This tests the case where selection contains one item and that item is a
+ // folder.
+ std::vector<const BookmarkNode*> nodes;
+ nodes.push_back(model->bookmark_bar_node());
+ int index = -1;
+ const BookmarkNode* real_parent =
+ GetParentForNewNodes(model->bookmark_bar_node(), nodes, &index);
+ EXPECT_EQ(real_parent, model->bookmark_bar_node());
+ EXPECT_EQ(0, index);
+
+ nodes.clear();
+
+ // This tests the case where selection contains one item and that item is an
+ // url.
+ const BookmarkNode* page1 = model->AddURL(model->bookmark_bar_node(),
+ 0,
+ ASCIIToUTF16("Google"),
+ GURL("http://google.com"));
+ nodes.push_back(page1);
+ real_parent = GetParentForNewNodes(model->bookmark_bar_node(), nodes, &index);
+ EXPECT_EQ(real_parent, model->bookmark_bar_node());
+ EXPECT_EQ(1, index);
+
+ // This tests the case where selection has more than one item.
+ const BookmarkNode* folder1 =
+ model->AddFolder(model->bookmark_bar_node(), 1, ASCIIToUTF16("Folder 1"));
+ nodes.push_back(folder1);
+ real_parent = GetParentForNewNodes(model->bookmark_bar_node(), nodes, &index);
+ EXPECT_EQ(real_parent, model->bookmark_bar_node());
+ EXPECT_EQ(2, index);
+
+ // This tests the case where selection doesn't contain any items.
+ nodes.clear();
+ real_parent = GetParentForNewNodes(model->bookmark_bar_node(), nodes, &index);
+ EXPECT_EQ(real_parent, model->bookmark_bar_node());
+ EXPECT_EQ(2, index);
+}
+
+// Verifies that meta info is copied when nodes are cloned.
+TEST_F(BookmarkUtilsTest, CloneMetaInfo) {
+ scoped_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
+ // Add a node containing meta info.
+ const BookmarkNode* node = model->AddURL(model->other_node(),
+ 0,
+ ASCIIToUTF16("foo bar"),
+ GURL("http://www.google.com"));
+ model->SetNodeMetaInfo(node, "somekey", "somevalue");
+ model->SetNodeMetaInfo(node, "someotherkey", "someothervalue");
+
+ // Clone node to a different folder.
+ const BookmarkNode* folder =
+ model->AddFolder(model->bookmark_bar_node(), 0, ASCIIToUTF16("Folder"));
+ std::vector<BookmarkNodeData::Element> elements;
+ BookmarkNodeData::Element node_data(node);
+ elements.push_back(node_data);
+ EXPECT_EQ(0, folder->child_count());
+ CloneBookmarkNode(model.get(), elements, folder, 0, false);
+ ASSERT_EQ(1, folder->child_count());
+
+ // Verify that the cloned node contains the same meta info.
+ const BookmarkNode* clone = folder->GetChild(0);
+ ASSERT_TRUE(clone->GetMetaInfoMap());
+ EXPECT_EQ(2u, clone->GetMetaInfoMap()->size());
+ std::string value;
+ EXPECT_TRUE(clone->GetMetaInfo("somekey", &value));
+ EXPECT_EQ("somevalue", value);
+ EXPECT_TRUE(clone->GetMetaInfo("someotherkey", &value));
+ EXPECT_EQ("someothervalue", value);
+}
+
+// Verifies that meta info fields in the non cloned set are not copied when
+// cloning a bookmark.
+TEST_F(BookmarkUtilsTest, CloneBookmarkResetsNonClonedKey) {
+ scoped_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
+ model->AddNonClonedKey("foo");
+ const BookmarkNode* parent = model->other_node();
+ const BookmarkNode* node = model->AddURL(
+ parent, 0, ASCIIToUTF16("title"), GURL("http://www.google.com"));
+ model->SetNodeMetaInfo(node, "foo", "ignored value");
+ model->SetNodeMetaInfo(node, "bar", "kept value");
+ std::vector<BookmarkNodeData::Element> elements;
+ BookmarkNodeData::Element node_data(node);
+ elements.push_back(node_data);
+
+ // Cloning a bookmark should clear the non cloned key.
+ CloneBookmarkNode(model.get(), elements, parent, 0, true);
+ ASSERT_EQ(2, parent->child_count());
+ std::string value;
+ EXPECT_FALSE(parent->GetChild(0)->GetMetaInfo("foo", &value));
+
+ // Other keys should still be cloned.
+ EXPECT_TRUE(parent->GetChild(0)->GetMetaInfo("bar", &value));
+ EXPECT_EQ("kept value", value);
+}
+
+// Verifies that meta info fields in the non cloned set are not copied when
+// cloning a folder.
+TEST_F(BookmarkUtilsTest, CloneFolderResetsNonClonedKey) {
+ scoped_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
+ model->AddNonClonedKey("foo");
+ const BookmarkNode* parent = model->other_node();
+ const BookmarkNode* node = model->AddFolder(parent, 0, ASCIIToUTF16("title"));
+ model->SetNodeMetaInfo(node, "foo", "ignored value");
+ model->SetNodeMetaInfo(node, "bar", "kept value");
+ std::vector<BookmarkNodeData::Element> elements;
+ BookmarkNodeData::Element node_data(node);
+ elements.push_back(node_data);
+
+ // Cloning a folder should clear the non cloned key.
+ CloneBookmarkNode(model.get(), elements, parent, 0, true);
+ ASSERT_EQ(2, parent->child_count());
+ std::string value;
+ EXPECT_FALSE(parent->GetChild(0)->GetMetaInfo("foo", &value));
+
+ // Other keys should still be cloned.
+ EXPECT_TRUE(parent->GetChild(0)->GetMetaInfo("bar", &value));
+ EXPECT_EQ("kept value", value);
+}
+
+TEST_F(BookmarkUtilsTest, RemoveAllBookmarks) {
+ // Load a model with an extra node that is not editable.
+ scoped_ptr<TestBookmarkClient> client(new TestBookmarkClient());
+ BookmarkPermanentNode* extra_node = new BookmarkPermanentNode(100);
+ BookmarkPermanentNodeList extra_nodes;
+ extra_nodes.push_back(extra_node);
+ client->SetExtraNodesToLoad(std::move(extra_nodes));
+
+ scoped_ptr<BookmarkModel> model(
+ TestBookmarkClient::CreateModelWithClient(std::move(client)));
+ EXPECT_TRUE(model->bookmark_bar_node()->empty());
+ EXPECT_TRUE(model->other_node()->empty());
+ EXPECT_TRUE(model->mobile_node()->empty());
+ EXPECT_TRUE(extra_node->empty());
+
+ const base::string16 title = base::ASCIIToUTF16("Title");
+ const GURL url("http://google.com");
+ model->AddURL(model->bookmark_bar_node(), 0, title, url);
+ model->AddURL(model->other_node(), 0, title, url);
+ model->AddURL(model->mobile_node(), 0, title, url);
+ model->AddURL(extra_node, 0, title, url);
+
+ std::vector<const BookmarkNode*> nodes;
+ model->GetNodesByURL(url, &nodes);
+ ASSERT_EQ(4u, nodes.size());
+
+ RemoveAllBookmarks(model.get(), url);
+
+ nodes.clear();
+ model->GetNodesByURL(url, &nodes);
+ ASSERT_EQ(1u, nodes.size());
+ EXPECT_TRUE(model->bookmark_bar_node()->empty());
+ EXPECT_TRUE(model->other_node()->empty());
+ EXPECT_TRUE(model->mobile_node()->empty());
+ EXPECT_EQ(1, extra_node->child_count());
+}
+
+} // namespace
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/browser/scoped_group_bookmark_actions.cc b/chromium/components/bookmarks/browser/scoped_group_bookmark_actions.cc
new file mode 100644
index 00000000000..f962e2961d6
--- /dev/null
+++ b/chromium/components/bookmarks/browser/scoped_group_bookmark_actions.cc
@@ -0,0 +1,22 @@
+// Copyright 2014 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 "components/bookmarks/browser/scoped_group_bookmark_actions.h"
+
+#include "components/bookmarks/browser/bookmark_model.h"
+
+namespace bookmarks {
+
+ScopedGroupBookmarkActions::ScopedGroupBookmarkActions(BookmarkModel* model)
+ : model_(model) {
+ if (model_)
+ model_->BeginGroupedChanges();
+}
+
+ScopedGroupBookmarkActions::~ScopedGroupBookmarkActions() {
+ if (model_)
+ model_->EndGroupedChanges();
+}
+
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/browser/scoped_group_bookmark_actions.h b/chromium/components/bookmarks/browser/scoped_group_bookmark_actions.h
new file mode 100644
index 00000000000..c2f09978439
--- /dev/null
+++ b/chromium/components/bookmarks/browser/scoped_group_bookmark_actions.h
@@ -0,0 +1,28 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_BOOKMARKS_BROWSER_SCOPED_GROUP_BOOKMARK_ACTIONS_H_
+#define COMPONENTS_BOOKMARKS_BROWSER_SCOPED_GROUP_BOOKMARK_ACTIONS_H_
+
+#include "base/macros.h"
+
+namespace bookmarks {
+
+class BookmarkModel;
+
+// Scopes the grouping of a set of changes into one undoable action.
+class ScopedGroupBookmarkActions {
+ public:
+ explicit ScopedGroupBookmarkActions(BookmarkModel* model);
+ ~ScopedGroupBookmarkActions();
+
+ private:
+ BookmarkModel* model_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedGroupBookmarkActions);
+};
+
+} // namespace bookmarks
+
+#endif // COMPONENTS_BOOKMARKS_BROWSER_SCOPED_GROUP_BOOKMARK_ACTIONS_H_
diff --git a/chromium/components/bookmarks/browser/startup_task_runner_service.cc b/chromium/components/bookmarks/browser/startup_task_runner_service.cc
new file mode 100644
index 00000000000..5b9b74139a6
--- /dev/null
+++ b/chromium/components/bookmarks/browser/startup_task_runner_service.cc
@@ -0,0 +1,36 @@
+// Copyright (c) 2013 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 "components/bookmarks/browser/startup_task_runner_service.h"
+
+#include "base/deferred_sequenced_task_runner.h"
+#include "base/logging.h"
+#include "base/sequenced_task_runner.h"
+
+namespace bookmarks {
+
+StartupTaskRunnerService::StartupTaskRunnerService(
+ const scoped_refptr<base::SequencedTaskRunner>& io_task_runner)
+ : io_task_runner_(io_task_runner) {
+ DCHECK(io_task_runner_);
+}
+
+StartupTaskRunnerService::~StartupTaskRunnerService() {
+}
+
+scoped_refptr<base::DeferredSequencedTaskRunner>
+ StartupTaskRunnerService::GetBookmarkTaskRunner() {
+ DCHECK(CalledOnValidThread());
+ if (!bookmark_task_runner_) {
+ bookmark_task_runner_ =
+ new base::DeferredSequencedTaskRunner(io_task_runner_);
+ }
+ return bookmark_task_runner_;
+}
+
+void StartupTaskRunnerService::StartDeferredTaskRunners() {
+ GetBookmarkTaskRunner()->Start();
+}
+
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/browser/startup_task_runner_service.h b/chromium/components/bookmarks/browser/startup_task_runner_service.h
new file mode 100644
index 00000000000..b39819af135
--- /dev/null
+++ b/chromium/components/bookmarks/browser/startup_task_runner_service.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2013 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.
+
+#ifndef COMPONENTS_BOOKMARKS_BROWSER_STARTUP_TASK_RUNNER_SERVICE_H_
+#define COMPONENTS_BOOKMARKS_BROWSER_STARTUP_TASK_RUNNER_SERVICE_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/non_thread_safe.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+namespace base {
+class DeferredSequencedTaskRunner;
+class SequencedTaskRunner;
+} // namespace base
+
+namespace bookmarks {
+
+// This service manages the startup task runners.
+class StartupTaskRunnerService : public base::NonThreadSafe,
+ public KeyedService {
+ public:
+ explicit StartupTaskRunnerService(
+ const scoped_refptr<base::SequencedTaskRunner>& io_task_runner);
+ ~StartupTaskRunnerService() override;
+
+ // Returns sequenced task runner where all bookmarks I/O operations are
+ // performed.
+ // This method should only be called from the UI thread.
+ // Note: Using a separate task runner per profile service gives a better
+ // management of the sequence in which the task are started in order to avoid
+ // congestion during start-up (e.g the caller may decide to start loading the
+ // bookmarks only after the history finished).
+ scoped_refptr<base::DeferredSequencedTaskRunner> GetBookmarkTaskRunner();
+
+ // Starts the task runners that are deferred during start-up.
+ void StartDeferredTaskRunners();
+
+ private:
+ scoped_refptr<base::SequencedTaskRunner> io_task_runner_;
+ scoped_refptr<base::DeferredSequencedTaskRunner> bookmark_task_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(StartupTaskRunnerService);
+};
+
+} // namespace bookmarks
+
+#endif // COMPONENTS_BOOKMARKS_BROWSER_STARTUP_TASK_RUNNER_SERVICE_H_
diff --git a/chromium/components/bookmarks/common/BUILD.gn b/chromium/components/bookmarks/common/BUILD.gn
new file mode 100644
index 00000000000..2ba1bd9c411
--- /dev/null
+++ b/chromium/components/bookmarks/common/BUILD.gn
@@ -0,0 +1,19 @@
+# Copyright 2014 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.
+
+source_set("common") {
+ sources = [
+ "bookmark_constants.cc",
+ "bookmark_constants.h",
+ "bookmark_pref_names.cc",
+ "bookmark_pref_names.h",
+ ]
+
+ deps = [
+ "//base",
+ ]
+ if (is_android) {
+ deps += [ "//components/bookmarks/common/android" ]
+ }
+}
diff --git a/chromium/components/bookmarks/common/android/BUILD.gn b/chromium/components/bookmarks/common/android/BUILD.gn
new file mode 100644
index 00000000000..69bb7f9ea68
--- /dev/null
+++ b/chromium/components/bookmarks/common/android/BUILD.gn
@@ -0,0 +1,44 @@
+# Copyright 2014 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.
+
+import("//build/config/android/rules.gni")
+
+# GYP: //components/bookmarks.gyp:bookmarks_browser (android part)
+source_set("android") {
+ sources = [
+ "bookmark_id.cc",
+ "bookmark_id.h",
+ "bookmark_type.h",
+ "component_jni_registrar.cc",
+ "component_jni_registrar.h",
+ ]
+ deps = [
+ ":bookmarks_jni_headers",
+ "//base:base",
+ ]
+}
+
+# GYP: //components/bookmarks.gyp:bookmarks_java
+android_library("bookmarks_java") {
+ deps = [
+ "//base:base_java",
+ ]
+ srcjar_deps = [ ":bookmark_type_javagen" ]
+ java_files = [ "java/src/org/chromium/components/bookmarks/BookmarkId.java" ]
+}
+
+# GYP: //components/bookmarks.gyp:bookmarks_jni_headers
+generate_jni("bookmarks_jni_headers") {
+ jni_package = "components/bookmarks"
+ sources = [
+ "java/src/org/chromium/components/bookmarks/BookmarkId.java",
+ ]
+}
+
+# GYP: //components/bookmarks.gyp:bookmarks_type_java
+java_cpp_enum("bookmark_type_javagen") {
+ sources = [
+ "bookmark_type.h",
+ ]
+}
diff --git a/chromium/components/bookmarks/common/android/OWNERS b/chromium/components/bookmarks/common/android/OWNERS
new file mode 100644
index 00000000000..48b0da2eaae
--- /dev/null
+++ b/chromium/components/bookmarks/common/android/OWNERS
@@ -0,0 +1,2 @@
+tedchoc@chromium.org
+kkimlabs@chromium.org
diff --git a/chromium/components/bookmarks/common/android/bookmark_id.cc b/chromium/components/bookmarks/common/android/bookmark_id.cc
new file mode 100644
index 00000000000..5cac9eeaff4
--- /dev/null
+++ b/chromium/components/bookmarks/common/android/bookmark_id.cc
@@ -0,0 +1,30 @@
+// Copyright 2014 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 "components/bookmarks/common/android/bookmark_id.h"
+
+#include "jni/BookmarkId_jni.h"
+
+namespace bookmarks {
+namespace android {
+
+long JavaBookmarkIdGetId(JNIEnv* env, jobject obj) {
+ return Java_BookmarkId_getId(env, obj);
+}
+
+int JavaBookmarkIdGetType(JNIEnv* env, jobject obj) {
+ return Java_BookmarkId_getType(env, obj);
+}
+
+base::android::ScopedJavaLocalRef<jobject> JavaBookmarkIdCreateBookmarkId(
+ JNIEnv* env, jlong id, jint type) {
+ return Java_BookmarkId_createBookmarkId(env, id, type);
+}
+
+bool RegisterBookmarkId(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+} // namespace android
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/common/android/bookmark_id.h b/chromium/components/bookmarks/common/android/bookmark_id.h
new file mode 100644
index 00000000000..af7ae721d5b
--- /dev/null
+++ b/chromium/components/bookmarks/common/android/bookmark_id.h
@@ -0,0 +1,30 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_BOOKMARKS_COMMON_ANDROID_BOOKMARK_ID_H_
+#define COMPONENTS_BOOKMARKS_COMMON_ANDROID_BOOKMARK_ID_H_
+
+#include <jni.h>
+
+#include "base/android/scoped_java_ref.h"
+
+namespace bookmarks {
+namespace android {
+
+// See BookmarkId#getId
+long JavaBookmarkIdGetId(JNIEnv* env, jobject obj);
+
+// See BookmarkId#getType
+int JavaBookmarkIdGetType(JNIEnv* env, jobject obj);
+
+// See BookmarkId#createBookmarkId
+base::android::ScopedJavaLocalRef<jobject> JavaBookmarkIdCreateBookmarkId(
+ JNIEnv* env, jlong id, jint type);
+
+bool RegisterBookmarkId(JNIEnv* env);
+
+} // namespace android
+} // namespace bookmarks
+
+#endif // COMPONENTS_BOOKMARKS_COMMON_ANDROID_BOOKMARK_ID_H_
diff --git a/chromium/components/bookmarks/common/android/bookmark_type.h b/chromium/components/bookmarks/common/android/bookmark_type.h
new file mode 100644
index 00000000000..75d21386333
--- /dev/null
+++ b/chromium/components/bookmarks/common/android/bookmark_type.h
@@ -0,0 +1,19 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_BOOKMARKS_COMMON_ANDROID_BOOKMARK_TYPE_H_
+#define COMPONENTS_BOOKMARKS_COMMON_ANDROID_BOOKMARK_TYPE_H_
+
+namespace bookmarks {
+
+// A Java counterpart will be generated for this enum.
+// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.components.bookmarks
+enum BookmarkType {
+ BOOKMARK_TYPE_NORMAL,
+ BOOKMARK_TYPE_PARTNER,
+};
+
+}
+
+#endif // COMPONENTS_BOOKMARKS_COMMON_ANDROID_BOOKMARK_TYPE_H_
diff --git a/chromium/components/bookmarks/common/bookmark_constants.cc b/chromium/components/bookmarks/common/bookmark_constants.cc
new file mode 100644
index 00000000000..6c2e5a6d542
--- /dev/null
+++ b/chromium/components/bookmarks/common/bookmark_constants.cc
@@ -0,0 +1,13 @@
+// Copyright 2014 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 "components/bookmarks/common/bookmark_constants.h"
+
+#define FPL FILE_PATH_LITERAL
+
+namespace bookmarks {
+
+const base::FilePath::CharType kBookmarksFileName[] = FPL("Bookmarks");
+
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/common/bookmark_constants.h b/chromium/components/bookmarks/common/bookmark_constants.h
new file mode 100644
index 00000000000..7b1a8f54476
--- /dev/null
+++ b/chromium/components/bookmarks/common/bookmark_constants.h
@@ -0,0 +1,16 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_BOOKMARKS_COMMON_BOOKMARK_CONSTANTS_H_
+#define COMPONENTS_BOOKMARKS_COMMON_BOOKMARK_CONSTANTS_H_
+
+#include "base/files/file_path.h"
+
+namespace bookmarks {
+
+extern const base::FilePath::CharType kBookmarksFileName[];
+
+} // namespace bookmarks
+
+#endif // COMPONENTS_BOOKMARKS_COMMON_BOOKMARK_CONSTANTS_H_
diff --git a/chromium/components/bookmarks/common/bookmark_pref_names.cc b/chromium/components/bookmarks/common/bookmark_pref_names.cc
new file mode 100644
index 00000000000..f9746aff998
--- /dev/null
+++ b/chromium/components/bookmarks/common/bookmark_pref_names.cc
@@ -0,0 +1,43 @@
+// Copyright 2014 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 "components/bookmarks/common/bookmark_pref_names.h"
+
+namespace bookmarks {
+namespace prefs {
+
+// Boolean which specifies the ids of the bookmark nodes that are expanded in
+// the bookmark editor.
+const char kBookmarkEditorExpandedNodes[] = "bookmark_editor.expanded_nodes";
+
+// Modifying bookmarks is completely disabled when this is set to false.
+const char kEditBookmarksEnabled[] = "bookmarks.editing_enabled";
+
+// A list of bookmarks to include in a Managed Bookmarks root node. Each
+// list item is a dictionary containing a "name" and an "url" entry, detailing
+// the bookmark name and target URL respectively.
+const char kManagedBookmarks[] = "bookmarks.managed_bookmarks";
+
+// String which specifies the Managed Bookmarks folder name
+const char kManagedBookmarksFolderName[] =
+ "bookmarks.managed_bookmarks_folder_name";
+
+// Boolean which specifies whether the apps shortcut is visible on the bookmark
+// bar.
+const char kShowAppsShortcutInBookmarkBar[] = "bookmark_bar.show_apps_shortcut";
+
+// Boolean which specifies whether the Managed Bookmarks folder is visible on
+// the bookmark bar.
+const char kShowManagedBookmarksInBookmarkBar[] =
+ "bookmark_bar.show_managed_bookmarks";
+
+// Boolean which specifies whether the bookmark bar is visible on all tabs.
+const char kShowBookmarkBar[] = "bookmark_bar.show_on_all_tabs";
+
+// A list of bookmarks to include in a Supervised Bookmarks root node. Behaves
+// like kManagedBookmarks.
+const char kSupervisedBookmarks[] = "bookmarks.supervised_bookmarks";
+
+} // namespace prefs
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/common/bookmark_pref_names.h b/chromium/components/bookmarks/common/bookmark_pref_names.h
new file mode 100644
index 00000000000..9c5b609c77a
--- /dev/null
+++ b/chromium/components/bookmarks/common/bookmark_pref_names.h
@@ -0,0 +1,25 @@
+// Copyright 2014 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.
+
+// Constants for the names of various bookmarks preferences.
+
+#ifndef COMPONENTS_BOOKMARKS_COMMON_BOOKMARK_PREF_NAMES_H_
+#define COMPONENTS_BOOKMARKS_COMMON_BOOKMARK_PREF_NAMES_H_
+
+namespace bookmarks {
+namespace prefs {
+
+extern const char kBookmarkEditorExpandedNodes[];
+extern const char kEditBookmarksEnabled[];
+extern const char kManagedBookmarks[];
+extern const char kManagedBookmarksFolderName[];
+extern const char kShowAppsShortcutInBookmarkBar[];
+extern const char kShowManagedBookmarksInBookmarkBar[];
+extern const char kShowBookmarkBar[];
+extern const char kSupervisedBookmarks[];
+
+} // namespace prefs
+} // namespace bookmarks
+
+#endif // COMPONENTS_BOOKMARKS_COMMON_BOOKMARK_PREF_NAMES_H_
diff --git a/chromium/components/bookmarks/managed/BUILD.gn b/chromium/components/bookmarks/managed/BUILD.gn
new file mode 100644
index 00000000000..f2f27659a61
--- /dev/null
+++ b/chromium/components/bookmarks/managed/BUILD.gn
@@ -0,0 +1,48 @@
+# Copyright 2015 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.
+
+source_set("managed") {
+ sources = [
+ "managed_bookmark_service.cc",
+ "managed_bookmark_service.h",
+ "managed_bookmark_util.cc",
+ "managed_bookmark_util.h",
+ "managed_bookmarks_tracker.cc",
+ "managed_bookmarks_tracker.h",
+ ]
+
+ deps = [
+ "//base",
+ "//components/bookmarks/browser",
+ "//components/bookmarks/common",
+ "//components/keyed_service/core",
+ "//components/prefs",
+ "//components/strings",
+ "//ui/base",
+ "//url",
+ ]
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [
+ "managed_bookmarks_tracker_unittest.cc",
+ ]
+
+ configs += [ "//build/config/compiler:no_size_t_to_int_warning" ]
+
+ deps = [
+ ":managed",
+ "//base",
+ "//components/bookmarks/browser",
+ "//components/bookmarks/common",
+ "//components/bookmarks/test",
+ "//components/prefs:test_support",
+ "//components/strings",
+ "//testing/gmock",
+ "//testing/gtest",
+ "//ui/base",
+ "//url",
+ ]
+}
diff --git a/chromium/components/bookmarks/managed/managed_bookmark_service.cc b/chromium/components/bookmarks/managed/managed_bookmark_service.cc
new file mode 100644
index 00000000000..066df72ea9a
--- /dev/null
+++ b/chromium/components/bookmarks/managed/managed_bookmark_service.cc
@@ -0,0 +1,186 @@
+// Copyright 2015 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 "components/bookmarks/managed/managed_bookmark_service.h"
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/scoped_vector.h"
+#include "base/strings/string16.h"
+#include "base/values.h"
+#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/browser/bookmark_utils.h"
+#include "components/bookmarks/managed/managed_bookmarks_tracker.h"
+#include "grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace bookmarks {
+namespace {
+
+// BookmarkPermanentNodeLoader initializes a BookmarkPermanentNode from a JSON
+// representation, title id and starting node id.
+class BookmarkPermanentNodeLoader {
+ public:
+ BookmarkPermanentNodeLoader(scoped_ptr<BookmarkPermanentNode> node,
+ scoped_ptr<base::ListValue> initial_bookmarks,
+ int title_id)
+ : node_(std::move(node)),
+ initial_bookmarks_(std::move(initial_bookmarks)),
+ title_id_(title_id) {
+ DCHECK(node_);
+ }
+
+ ~BookmarkPermanentNodeLoader() {}
+
+ // Initializes |node_| from |initial_bookmarks_| and |title_id_| and returns
+ // it. The ids are assigned starting at |next_node_id| and the value is
+ // updated as a side-effect.
+ scoped_ptr<BookmarkPermanentNode> Load(int64_t* next_node_id) {
+ node_->set_id(*next_node_id);
+ *next_node_id = ManagedBookmarksTracker::LoadInitial(
+ node_.get(), initial_bookmarks_.get(), node_->id() + 1);
+ node_->set_visible(!node_->empty());
+ node_->SetTitle(l10n_util::GetStringUTF16(title_id_));
+ return std::move(node_);
+ }
+
+ private:
+ scoped_ptr<BookmarkPermanentNode> node_;
+ scoped_ptr<base::ListValue> initial_bookmarks_;
+ int title_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(BookmarkPermanentNodeLoader);
+};
+
+// Returns a list of initialized BookmarkPermanentNodes using |next_node_id| to
+// start assigning id. |next_node_id| is updated as a side effect of calling
+// this method.
+BookmarkPermanentNodeList LoadExtraNodes(
+ ScopedVector<BookmarkPermanentNodeLoader> loaders,
+ int64_t* next_node_id) {
+ BookmarkPermanentNodeList extra_nodes;
+ for (const auto& loader : loaders)
+ extra_nodes.push_back(loader->Load(next_node_id).release());
+ return extra_nodes;
+}
+
+} // namespace
+
+ManagedBookmarkService::ManagedBookmarkService(
+ PrefService* prefs,
+ const GetManagementDomainCallback& callback)
+ : prefs_(prefs),
+ bookmark_model_(nullptr),
+ managed_domain_callback_(callback),
+ managed_node_(nullptr),
+ supervised_node_(nullptr) {}
+
+ManagedBookmarkService::~ManagedBookmarkService() {
+ DCHECK(!bookmark_model_);
+}
+
+void ManagedBookmarkService::BookmarkModelCreated(
+ BookmarkModel* bookmark_model) {
+ DCHECK(bookmark_model);
+ DCHECK(!bookmark_model_);
+ bookmark_model_ = bookmark_model;
+ bookmark_model_->AddObserver(this);
+
+ managed_bookmarks_tracker_.reset(new ManagedBookmarksTracker(
+ bookmark_model_, prefs_, false /* is_supervised */,
+ managed_domain_callback_));
+
+ GetManagementDomainCallback unbound_callback;
+ supervised_bookmarks_tracker_.reset(new ManagedBookmarksTracker(
+ bookmark_model_, prefs_, true /* is_supervised */, unbound_callback));
+}
+
+LoadExtraCallback ManagedBookmarkService::GetLoadExtraNodesCallback() {
+ // Create two BookmarkPermanentNode with a temporary id of 0. They will be
+ // populated and assigned proper ids in the LoadExtraNodes callback. Until
+ // then, they are owned by the returned closure.
+ scoped_ptr<BookmarkPermanentNode> managed(new BookmarkPermanentNode(0));
+ scoped_ptr<BookmarkPermanentNode> supervised(new BookmarkPermanentNode(0));
+
+ managed_node_ = managed.get();
+ supervised_node_ = supervised.get();
+
+ ScopedVector<BookmarkPermanentNodeLoader> loaders;
+ loaders.push_back(new BookmarkPermanentNodeLoader(
+ std::move(managed),
+ managed_bookmarks_tracker_->GetInitialManagedBookmarks(),
+ IDS_BOOKMARK_BAR_MANAGED_FOLDER_DEFAULT_NAME));
+ loaders.push_back(new BookmarkPermanentNodeLoader(
+ std::move(supervised),
+ supervised_bookmarks_tracker_->GetInitialManagedBookmarks(),
+ IDS_BOOKMARK_BAR_SUPERVISED_FOLDER_DEFAULT_NAME));
+
+ return base::Bind(&LoadExtraNodes, base::Passed(&loaders));
+}
+
+bool ManagedBookmarkService::CanSetPermanentNodeTitle(
+ const BookmarkNode* node) {
+ // |managed_node_| can have its title updated if the user signs in or out,
+ // since the name of the managed domain can appear in it. Also both
+ // |managed_node_| and |supervised_node_| can have their title updated on
+ // locale changes (http://crbug.com/459448).
+ if (node == managed_node_ || node == supervised_node_)
+ return true;
+ return !IsDescendantOf(node, managed_node_) &&
+ !IsDescendantOf(node, supervised_node_);
+}
+
+bool ManagedBookmarkService::CanSyncNode(const BookmarkNode* node) {
+ return !IsDescendantOf(node, managed_node_) &&
+ !IsDescendantOf(node, supervised_node_);
+}
+
+bool ManagedBookmarkService::CanBeEditedByUser(const BookmarkNode* node) {
+ return !IsDescendantOf(node, managed_node_) &&
+ !IsDescendantOf(node, supervised_node_);
+}
+
+void ManagedBookmarkService::Shutdown() {
+ Cleanup();
+}
+
+void ManagedBookmarkService::BookmarkModelChanged() {}
+
+void ManagedBookmarkService::BookmarkModelLoaded(BookmarkModel* bookmark_model,
+ bool ids_reassigned) {
+ BaseBookmarkModelObserver::BookmarkModelLoaded(bookmark_model,
+ ids_reassigned);
+ // Start tracking the managed and supervised bookmarks. This will detect any
+ // changes that may have occurred while the initial managed and supervised
+ // bookmarks were being loaded on the background.
+ managed_bookmarks_tracker_->Init(managed_node_);
+ supervised_bookmarks_tracker_->Init(supervised_node_);
+}
+
+void ManagedBookmarkService::BookmarkModelBeingDeleted(
+ BookmarkModel* bookmark_model) {
+ Cleanup();
+}
+
+void ManagedBookmarkService::Cleanup() {
+ if (bookmark_model_) {
+ bookmark_model_->RemoveObserver(this);
+ bookmark_model_ = nullptr;
+ }
+
+ managed_bookmarks_tracker_.reset();
+ supervised_bookmarks_tracker_.reset();
+
+ managed_node_ = nullptr;
+ supervised_node_ = nullptr;
+}
+
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/managed/managed_bookmark_service.h b/chromium/components/bookmarks/managed/managed_bookmark_service.h
new file mode 100644
index 00000000000..db24b2bf5e3
--- /dev/null
+++ b/chromium/components/bookmarks/managed/managed_bookmark_service.h
@@ -0,0 +1,102 @@
+// Copyright 2015 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.
+
+#ifndef COMPONENTS_BOOKMARKS_MANAGED_MANAGED_BOOKMARK_SERVICE_H_
+#define COMPONENTS_BOOKMARKS_MANAGED_MANAGED_BOOKMARK_SERVICE_H_
+
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/bookmarks/browser/base_bookmark_model_observer.h"
+#include "components/bookmarks/browser/bookmark_node.h"
+#include "components/bookmarks/browser/bookmark_storage.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+class PrefService;
+
+namespace bookmarks {
+
+class BookmarkModel;
+class ManagedBookmarksTracker;
+
+// ManagedBookmarkService manages the bookmark folder controlled by enterprise
+// policy or custodian of supervised users.
+class ManagedBookmarkService : public KeyedService,
+ public BaseBookmarkModelObserver {
+ public:
+ typedef base::Callback<std::string()> GetManagementDomainCallback;
+
+ ManagedBookmarkService(PrefService* prefs,
+ const GetManagementDomainCallback& callback);
+ ~ManagedBookmarkService() override;
+
+ // Called upon creation of the BookmarkModel.
+ void BookmarkModelCreated(BookmarkModel* bookmark_model);
+
+ // Returns a task that will be used to load any additional root nodes. This
+ // task will be invoked in the Profile's IO task runner.
+ LoadExtraCallback GetLoadExtraNodesCallback();
+
+ // Returns true if the |node| can have its title updated.
+ bool CanSetPermanentNodeTitle(const BookmarkNode* node);
+
+ // Returns true if |node| should sync.
+ bool CanSyncNode(const BookmarkNode* node);
+
+ // Returns true if |node| can be edited by the user.
+ // TODO(joaodasilva): the model should check this more aggressively, and
+ // should give the client a means to temporarily disable those checks.
+ // http://crbug.com/49598
+ bool CanBeEditedByUser(const BookmarkNode* node);
+
+ // Top-level managed bookmarks folder, defined by an enterprise policy; may be
+ // null.
+ const BookmarkNode* managed_node() { return managed_node_; }
+
+ // Top-level supervised bookmarks folder, defined by the custodian of a
+ // supervised user; may be null.
+ const BookmarkNode* supervised_node() { return supervised_node_; }
+
+ private:
+ // KeyedService implementation.
+ void Shutdown() override;
+
+ // BaseBookmarkModelObserver implementation.
+ void BookmarkModelChanged() override;
+
+ // BookmarkModelObserver implementation.
+ void BookmarkModelLoaded(BookmarkModel* bookmark_model,
+ bool ids_reassigned) override;
+ void BookmarkModelBeingDeleted(BookmarkModel* bookmark_model) override;
+
+ // Cleanup, called when service is shutdown or when BookmarkModel is being
+ // destroyed.
+ void Cleanup();
+
+ // Pointer to the PrefService. Must outlive ManagedBookmarkService.
+ PrefService* prefs_;
+
+ // Pointer to the BookmarkModel; may be null. Only valid between the calls to
+ // BookmarkModelCreated() and to BookmarkModelBeingDestroyed().
+ BookmarkModel* bookmark_model_;
+
+ // Managed bookmarks are defined by an enterprise policy. The lifetime of the
+ // BookmarkPermanentNode is controlled by BookmarkModel.
+ scoped_ptr<ManagedBookmarksTracker> managed_bookmarks_tracker_;
+ GetManagementDomainCallback managed_domain_callback_;
+ BookmarkPermanentNode* managed_node_;
+
+ // Supervised bookmarks are defined by the custodian of a supervised user. The
+ // lifetime of the BookmarkPermanentNode is controlled by BookmarkModel.
+ scoped_ptr<ManagedBookmarksTracker> supervised_bookmarks_tracker_;
+ BookmarkPermanentNode* supervised_node_;
+
+ DISALLOW_COPY_AND_ASSIGN(ManagedBookmarkService);
+};
+
+} // namespace bookmarks
+
+#endif // COMPONENTS_BOOKMARKS_MANAGED_MANAGED_BOOKMARK_SERVICE_H_
diff --git a/chromium/components/bookmarks/managed/managed_bookmark_util.cc b/chromium/components/bookmarks/managed/managed_bookmark_util.cc
new file mode 100644
index 00000000000..d19d941e78d
--- /dev/null
+++ b/chromium/components/bookmarks/managed/managed_bookmark_util.cc
@@ -0,0 +1,33 @@
+// Copyright 2015 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 "components/bookmarks/managed/managed_bookmark_util.h"
+
+#include "components/bookmarks/browser/bookmark_node.h"
+#include "components/bookmarks/managed/managed_bookmark_service.h"
+
+namespace bookmarks {
+
+bool IsPermanentNode(const BookmarkPermanentNode* node,
+ ManagedBookmarkService* managed_bookmark_service) {
+ BookmarkNode::Type type = node->type();
+ if (type == BookmarkNode::BOOKMARK_BAR ||
+ type == BookmarkNode::OTHER_NODE ||
+ type == BookmarkNode::MOBILE) {
+ return true;
+ }
+
+ return IsManagedNode(node, managed_bookmark_service);
+}
+
+bool IsManagedNode(const BookmarkPermanentNode* node,
+ ManagedBookmarkService* managed_bookmark_service) {
+ if (!managed_bookmark_service)
+ return false;
+
+ return node == managed_bookmark_service->managed_node() ||
+ node == managed_bookmark_service->supervised_node();
+}
+
+} // namespace bookmarks
diff --git a/chromium/components/bookmarks/managed/managed_bookmark_util.h b/chromium/components/bookmarks/managed/managed_bookmark_util.h
new file mode 100644
index 00000000000..9b3ec1815fc
--- /dev/null
+++ b/chromium/components/bookmarks/managed/managed_bookmark_util.h
@@ -0,0 +1,23 @@
+// Copyright 2015 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.
+
+#ifndef COMPONENTS_BOOKMARKS_MANAGED_MANAGED_BOOKMARK_UTIL_H_
+#define COMPONENTS_BOOKMARKS_MANAGED_MANAGED_BOOKMARK_UTIL_H_
+
+namespace bookmarks {
+
+class BookmarkPermanentNode;
+class ManagedBookmarkService;
+
+// Returns whether |node| is a permanent node.
+bool IsPermanentNode(const BookmarkPermanentNode* node,
+ ManagedBookmarkService* managed_bookmark_service);
+
+// Returns whether |node| is a managed node.
+bool IsManagedNode(const BookmarkPermanentNode* node,
+ ManagedBookmarkService* managed_bookmark_service);
+
+} // namespace bookmarks
+
+#endif // COMPONENTS_BOOKMARKS_MANAGED_MANAGED_BOOKMARK_UTIL_H_
diff --git a/chromium/components/bookmarks/managed/managed_bookmarks_tracker.cc b/chromium/components/bookmarks/managed/managed_bookmarks_tracker.cc
new file mode 100644
index 00000000000..1c1813392df
--- /dev/null
+++ b/chromium/components/bookmarks/managed/managed_bookmarks_tracker.cc
@@ -0,0 +1,214 @@
+// Copyright 2014 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 "components/bookmarks/managed/managed_bookmarks_tracker.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/browser/bookmark_node.h"
+#include "components/bookmarks/common/bookmark_pref_names.h"
+#include "components/prefs/pref_service.h"
+#include "grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "url/gurl.h"
+
+namespace bookmarks {
+
+const char ManagedBookmarksTracker::kName[] = "name";
+const char ManagedBookmarksTracker::kUrl[] = "url";
+const char ManagedBookmarksTracker::kChildren[] = "children";
+const char ManagedBookmarksTracker::kFolderName[] = "toplevel_name";
+
+ManagedBookmarksTracker::ManagedBookmarksTracker(
+ BookmarkModel* model,
+ PrefService* prefs,
+ bool is_supervised,
+ const GetManagementDomainCallback& callback)
+ : model_(model),
+ is_supervised_(is_supervised),
+ managed_node_(NULL),
+ prefs_(prefs),
+ get_management_domain_callback_(callback) {
+}
+
+ManagedBookmarksTracker::~ManagedBookmarksTracker() {}
+
+scoped_ptr<base::ListValue>
+ManagedBookmarksTracker::GetInitialManagedBookmarks() {
+ const base::ListValue* list = prefs_->GetList(GetPrefName());
+ return make_scoped_ptr(list->DeepCopy());
+}
+
+// static
+int64_t ManagedBookmarksTracker::LoadInitial(BookmarkNode* folder,
+ const base::ListValue* list,
+ int64_t next_node_id) {
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ // Extract the data for the next bookmark from the |list|.
+ base::string16 title;
+ GURL url;
+ const base::ListValue* children = NULL;
+ if (!LoadBookmark(list, i, &title, &url, &children))
+ continue;
+
+ BookmarkNode* child = new BookmarkNode(next_node_id++, url);
+ child->SetTitle(title);
+ folder->Add(child, folder->child_count());
+ if (children) {
+ child->set_type(BookmarkNode::FOLDER);
+ child->set_date_folder_modified(base::Time::Now());
+ next_node_id = LoadInitial(child, children, next_node_id);
+ } else {
+ child->set_type(BookmarkNode::URL);
+ child->set_date_added(base::Time::Now());
+ }
+ }
+
+ return next_node_id;
+}
+
+void ManagedBookmarksTracker::Init(BookmarkPermanentNode* managed_node) {
+ managed_node_ = managed_node;
+ registrar_.Init(prefs_);
+ registrar_.Add(GetPrefName(),
+ base::Bind(&ManagedBookmarksTracker::ReloadManagedBookmarks,
+ base::Unretained(this)));
+ // Reload now just in case something changed since the initial load started.
+ // Note that if we track managed bookmarks rather than supervised bookmarks,
+ // then we must not load them until cloud policy system has been fully
+ // initialized (which will make our preference a managed preference).
+ const bool are_managed_bookmarks_available =
+ is_supervised_ || prefs_->IsManagedPreference(GetPrefName());
+ if (are_managed_bookmarks_available)
+ ReloadManagedBookmarks();
+}
+
+// static
+const char* ManagedBookmarksTracker::GetPrefName(bool is_supervised) {
+ return is_supervised ? prefs::kSupervisedBookmarks
+ : prefs::kManagedBookmarks;
+}
+
+const char* ManagedBookmarksTracker::GetPrefName() const {
+ return GetPrefName(is_supervised_);
+}
+
+base::string16 ManagedBookmarksTracker::GetBookmarksFolderTitle() const {
+ if (is_supervised_) {
+ return l10n_util::GetStringUTF16(
+ IDS_BOOKMARK_BAR_SUPERVISED_FOLDER_DEFAULT_NAME);
+ } else {
+ std::string name = prefs_->GetString(prefs::kManagedBookmarksFolderName);
+ if (!name.empty())
+ return base::UTF8ToUTF16(name);
+
+ const std::string domain = get_management_domain_callback_.Run();
+ if (domain.empty()) {
+ return l10n_util::GetStringUTF16(
+ IDS_BOOKMARK_BAR_MANAGED_FOLDER_DEFAULT_NAME);
+ } else {
+ return l10n_util::GetStringFUTF16(
+ IDS_BOOKMARK_BAR_MANAGED_FOLDER_DOMAIN_NAME,
+ base::UTF8ToUTF16(domain));
+ }
+ }
+}
+
+void ManagedBookmarksTracker::ReloadManagedBookmarks() {
+ // In case the user just signed into or out of the account.
+ model_->SetTitle(managed_node_, GetBookmarksFolderTitle());
+
+ // Recursively update all the managed bookmarks and folders.
+ const base::ListValue* list = prefs_->GetList(GetPrefName());
+ UpdateBookmarks(managed_node_, list);
+
+ // The managed bookmarks folder isn't visible when that pref isn't present.
+ managed_node_->set_visible(!managed_node_->empty());
+}
+
+void ManagedBookmarksTracker::UpdateBookmarks(const BookmarkNode* folder,
+ const base::ListValue* list) {
+ int folder_index = 0;
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ // Extract the data for the next bookmark from the |list|.
+ base::string16 title;
+ GURL url;
+ const base::ListValue* children = NULL;
+ if (!LoadBookmark(list, i, &title, &url, &children)) {
+ // Skip this bookmark from |list| but don't advance |folder_index|.
+ continue;
+ }
+
+ // Look for a bookmark at |folder_index| or ahead that matches the current
+ // bookmark from the pref.
+ const BookmarkNode* existing = NULL;
+ for (int k = folder_index; k < folder->child_count(); ++k) {
+ const BookmarkNode* node = folder->GetChild(k);
+ if (node->GetTitle() == title &&
+ ((children && node->is_folder()) ||
+ (!children && node->url() == url))) {
+ existing = node;
+ break;
+ }
+ }
+
+ if (existing) {
+ // Reuse the existing node. The Move() is a nop if |existing| is already
+ // at |folder_index|.
+ model_->Move(existing, folder, folder_index);
+ if (children)
+ UpdateBookmarks(existing, children);
+ } else {
+ // Create a new node for this bookmark now.
+ if (children) {
+ const BookmarkNode* sub =
+ model_->AddFolder(folder, folder_index, title);
+ UpdateBookmarks(sub, children);
+ } else {
+ model_->AddURL(folder, folder_index, title, url);
+ }
+ }
+
+ // The |folder_index| index of |folder| has been updated, so advance it.
+ ++folder_index;
+ }
+
+ // Remove any extra children of |folder| that haven't been reused.
+ while (folder->child_count() != folder_index)
+ model_->Remove(folder->GetChild(folder_index));
+}
+
+// static
+bool ManagedBookmarksTracker::LoadBookmark(const base::ListValue* list,
+ size_t index,
+ base::string16* title,
+ GURL* url,
+ const base::ListValue** children) {
+ std::string spec;
+ *url = GURL();
+ *children = NULL;
+ const base::DictionaryValue* dict = NULL;
+ if (!list->GetDictionary(index, &dict) ||
+ !dict->GetString(kName, title) ||
+ (!dict->GetString(kUrl, &spec) &&
+ !dict->GetList(kChildren, children))) {
+ // Should never happen after policy validation.
+ NOTREACHED();
+ return false;
+ }
+ if (!*children) {
+ *url = GURL(spec);
+ DCHECK(url->is_valid());
+ }
+ return true;
+}
+
+} // namespace policy
diff --git a/chromium/components/bookmarks/managed/managed_bookmarks_tracker.h b/chromium/components/bookmarks/managed/managed_bookmarks_tracker.h
new file mode 100644
index 00000000000..4586aa3c8ff
--- /dev/null
+++ b/chromium/components/bookmarks/managed/managed_bookmarks_tracker.h
@@ -0,0 +1,98 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_BOOKMARKS_MANAGED_MANAGED_BOOKMARKS_TRACKER_H_
+#define COMPONENTS_BOOKMARKS_MANAGED_MANAGED_BOOKMARKS_TRACKER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "components/prefs/pref_change_registrar.h"
+
+class GURL;
+class PrefService;
+
+namespace base {
+class ListValue;
+}
+
+namespace bookmarks {
+
+class BookmarkModel;
+class BookmarkNode;
+class BookmarkPermanentNode;
+
+// Tracks either the Managed Bookmarks pref (set by policy) or the Supervised
+// Bookmarks pref (set for a supervised user by their custodian) and makes the
+// managed_node()/supervised_node() in the BookmarkModel follow the
+// policy/custodian-defined bookmark tree.
+class ManagedBookmarksTracker {
+ public:
+ typedef base::Callback<std::string()> GetManagementDomainCallback;
+
+ // Shared constants used in the policy configuration.
+ static const char kName[];
+ static const char kUrl[];
+ static const char kChildren[];
+ static const char kFolderName[];
+
+ // If |is_supervised| is true, this will track supervised bookmarks rather
+ // than managed bookmarks.
+ ManagedBookmarksTracker(BookmarkModel* model,
+ PrefService* prefs,
+ bool is_supervised,
+ const GetManagementDomainCallback& callback);
+ ~ManagedBookmarksTracker();
+
+ // Returns the initial list of managed bookmarks, which can be passed to
+ // LoadInitial() to do the initial load.
+ scoped_ptr<base::ListValue> GetInitialManagedBookmarks();
+
+ // Loads the initial managed/supervised bookmarks in |list| into |folder|.
+ // New nodes will be assigned IDs starting at |next_node_id|.
+ // Returns the next node ID to use.
+ static int64_t LoadInitial(BookmarkNode* folder,
+ const base::ListValue* list,
+ int64_t next_node_id);
+
+ // Starts tracking the pref for updates to the managed/supervised bookmarks.
+ // Should be called after loading the initial bookmarks.
+ void Init(BookmarkPermanentNode* managed_node);
+
+ bool is_supervised() const { return is_supervised_; }
+
+ // Public for testing.
+ static const char* GetPrefName(bool is_supervised);
+
+ private:
+ const char* GetPrefName() const;
+ base::string16 GetBookmarksFolderTitle() const;
+
+ void ReloadManagedBookmarks();
+
+ void UpdateBookmarks(const BookmarkNode* folder, const base::ListValue* list);
+ static bool LoadBookmark(const base::ListValue* list,
+ size_t index,
+ base::string16* title,
+ GURL* url,
+ const base::ListValue** children);
+
+ BookmarkModel* model_;
+ const bool is_supervised_;
+ BookmarkPermanentNode* managed_node_;
+ PrefService* prefs_;
+ PrefChangeRegistrar registrar_;
+ GetManagementDomainCallback get_management_domain_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ManagedBookmarksTracker);
+};
+
+} // namespace bookmarks
+
+#endif // COMPONENTS_BOOKMARKS_MANAGED_MANAGED_BOOKMARKS_TRACKER_H_
+
diff --git a/chromium/components/bookmarks/managed/managed_bookmarks_tracker_unittest.cc b/chromium/components/bookmarks/managed/managed_bookmarks_tracker_unittest.cc
new file mode 100644
index 00000000000..d03502501ad
--- /dev/null
+++ b/chromium/components/bookmarks/managed/managed_bookmarks_tracker_unittest.cc
@@ -0,0 +1,360 @@
+// Copyright 2014 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 "components/bookmarks/managed/managed_bookmarks_tracker.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/thread_task_runner_handle.h"
+#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/browser/bookmark_model_observer.h"
+#include "components/bookmarks/browser/bookmark_node.h"
+#include "components/bookmarks/browser/bookmark_utils.h"
+#include "components/bookmarks/common/bookmark_pref_names.h"
+#include "components/bookmarks/test/bookmark_test_helpers.h"
+#include "components/bookmarks/test/mock_bookmark_model_observer.h"
+#include "components/bookmarks/test/test_bookmark_client.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "grit/components_strings.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "url/gurl.h"
+
+using testing::Mock;
+using testing::_;
+
+namespace bookmarks {
+
+class ManagedBookmarksTrackerTest : public testing::Test {
+ public:
+ ManagedBookmarksTrackerTest() : managed_node_(NULL) {}
+ ~ManagedBookmarksTrackerTest() override {}
+
+ void SetUp() override {
+ RegisterManagedBookmarksPrefs(prefs_.registry());
+ }
+
+ void TearDown() override {
+ if (model_)
+ model_->RemoveObserver(&observer_);
+ loop_.RunUntilIdle();
+ }
+
+ void CreateModel(bool is_supervised) {
+ // Simulate the creation of the managed node by the BookmarkClient.
+ BookmarkPermanentNode* managed_node = new BookmarkPermanentNode(100);
+ ManagedBookmarksTracker::LoadInitial(
+ managed_node,
+ prefs_.GetList(ManagedBookmarksTracker::GetPrefName(is_supervised)),
+ 101);
+ managed_node->set_visible(!managed_node->empty());
+ managed_node->SetTitle(l10n_util::GetStringUTF16(
+ is_supervised ? IDS_BOOKMARK_BAR_SUPERVISED_FOLDER_DEFAULT_NAME
+ : IDS_BOOKMARK_BAR_MANAGED_FOLDER_DEFAULT_NAME));
+
+ BookmarkPermanentNodeList extra_nodes;
+ extra_nodes.push_back(managed_node);
+
+ scoped_ptr<TestBookmarkClient> client(new TestBookmarkClient);
+ client->SetExtraNodesToLoad(std::move(extra_nodes));
+ model_.reset(new BookmarkModel(std::move(client)));
+
+ model_->AddObserver(&observer_);
+ EXPECT_CALL(observer_, BookmarkModelLoaded(model_.get(), _));
+ model_->Load(&prefs_, base::FilePath(),
+ base::ThreadTaskRunnerHandle::Get(),
+ base::ThreadTaskRunnerHandle::Get());
+ test::WaitForBookmarkModelToLoad(model_.get());
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ TestBookmarkClient* client_ptr =
+ static_cast<TestBookmarkClient*>(model_->client());
+ ASSERT_EQ(1u, client_ptr->extra_nodes().size());
+ managed_node_ = client_ptr->extra_nodes()[0];
+ ASSERT_EQ(managed_node, managed_node_);
+
+ managed_bookmarks_tracker_.reset(new ManagedBookmarksTracker(
+ model_.get(),
+ &prefs_,
+ is_supervised,
+ base::Bind(&ManagedBookmarksTrackerTest::GetManagementDomain)));
+ managed_bookmarks_tracker_->Init(managed_node_);
+ }
+
+ const BookmarkNode* managed_node() {
+ return managed_node_;
+ }
+
+ bool IsManaged(const BookmarkNode* node) {
+ return node && node->HasAncestor(managed_node_);
+ }
+
+ static base::DictionaryValue* CreateBookmark(const std::string& title,
+ const std::string& url) {
+ EXPECT_TRUE(GURL(url).is_valid());
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("name", title);
+ dict->SetString("url", GURL(url).spec());
+ return dict;
+ }
+
+ static base::DictionaryValue* CreateFolder(const std::string& title,
+ base::ListValue* children) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("name", title);
+ dict->Set("children", children);
+ return dict;
+ }
+
+ static base::ListValue* CreateTestTree() {
+ base::ListValue* folder = new base::ListValue();
+ base::ListValue* empty = new base::ListValue();
+ folder->Append(CreateFolder("Empty", empty));
+ folder->Append(CreateBookmark("Youtube", "http://youtube.com/"));
+
+ base::ListValue* list = new base::ListValue();
+ list->Append(CreateBookmark("Google", "http://google.com/"));
+ list->Append(CreateFolder("Folder", folder));
+
+ return list;
+ }
+
+ static std::string GetManagementDomain() {
+ return std::string();
+ }
+
+ static std::string GetManagedFolderTitle() {
+ return l10n_util::GetStringUTF8(
+ IDS_BOOKMARK_BAR_MANAGED_FOLDER_DEFAULT_NAME);
+ }
+
+ static base::DictionaryValue* CreateExpectedTree() {
+ return CreateFolder(GetManagedFolderTitle(), CreateTestTree());
+ }
+
+ static bool NodeMatchesValue(const BookmarkNode* node,
+ const base::DictionaryValue* dict) {
+ base::string16 title;
+ if (!dict->GetString("name", &title) || node->GetTitle() != title)
+ return false;
+
+ if (node->is_folder()) {
+ const base::ListValue* children = NULL;
+ if (!dict->GetList("children", &children) ||
+ node->child_count() != static_cast<int>(children->GetSize())) {
+ return false;
+ }
+ for (int i = 0; i < node->child_count(); ++i) {
+ const base::DictionaryValue* child = NULL;
+ if (!children->GetDictionary(i, &child) ||
+ !NodeMatchesValue(node->GetChild(i), child)) {
+ return false;
+ }
+ }
+ } else if (node->is_url()) {
+ std::string url;
+ if (!dict->GetString("url", &url) || node->url() != GURL(url))
+ return false;
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ base::MessageLoop loop_;
+ TestingPrefServiceSimple prefs_;
+ scoped_ptr<BookmarkModel> model_;
+ MockBookmarkModelObserver observer_;
+ BookmarkPermanentNode* managed_node_;
+ scoped_ptr<ManagedBookmarksTracker> managed_bookmarks_tracker_;
+};
+
+TEST_F(ManagedBookmarksTrackerTest, Empty) {
+ CreateModel(false /* is_supervised */);
+ EXPECT_TRUE(model_->bookmark_bar_node()->empty());
+ EXPECT_TRUE(model_->other_node()->empty());
+ EXPECT_TRUE(managed_node()->empty());
+ EXPECT_FALSE(managed_node()->IsVisible());
+}
+
+TEST_F(ManagedBookmarksTrackerTest, LoadInitial) {
+ // Set a policy before loading the model.
+ prefs_.SetManagedPref(prefs::kManagedBookmarks, CreateTestTree());
+ CreateModel(false /* is_supervised */);
+ EXPECT_TRUE(model_->bookmark_bar_node()->empty());
+ EXPECT_TRUE(model_->other_node()->empty());
+ EXPECT_FALSE(managed_node()->empty());
+ EXPECT_TRUE(managed_node()->IsVisible());
+
+ scoped_ptr<base::DictionaryValue> expected(CreateExpectedTree());
+ EXPECT_TRUE(NodeMatchesValue(managed_node(), expected.get()));
+}
+
+TEST_F(ManagedBookmarksTrackerTest, LoadInitialWithTitle) {
+ // Set the managed folder title.
+ const char kExpectedFolderName[] = "foo";
+ prefs_.SetString(prefs::kManagedBookmarksFolderName, kExpectedFolderName);
+ // Set a policy before loading the model.
+ prefs_.SetManagedPref(prefs::kManagedBookmarks, CreateTestTree());
+ CreateModel(false /* is_supervised */);
+ EXPECT_TRUE(model_->bookmark_bar_node()->empty());
+ EXPECT_TRUE(model_->other_node()->empty());
+ EXPECT_FALSE(managed_node()->empty());
+ EXPECT_TRUE(managed_node()->IsVisible());
+
+ scoped_ptr<base::DictionaryValue> expected(
+ CreateFolder(kExpectedFolderName, CreateTestTree()));
+ EXPECT_TRUE(NodeMatchesValue(managed_node(), expected.get()));
+}
+
+TEST_F(ManagedBookmarksTrackerTest, SupervisedTrackerIgnoresManagedPref) {
+ prefs_.SetManagedPref(prefs::kManagedBookmarks, CreateTestTree());
+ CreateModel(true /* is_supervised */);
+ EXPECT_TRUE(managed_node()->empty());
+ EXPECT_FALSE(managed_node()->IsVisible());
+}
+
+TEST_F(ManagedBookmarksTrackerTest, SupervisedTrackerHandlesSupervisedPref) {
+ prefs_.SetManagedPref(prefs::kSupervisedBookmarks, CreateTestTree());
+ CreateModel(true /* is_supervised */);
+ EXPECT_FALSE(managed_node()->empty());
+ EXPECT_TRUE(managed_node()->IsVisible());
+ // Don't bother checking the actual contents, the non-supervised tests cover
+ // that already.
+}
+
+TEST_F(ManagedBookmarksTrackerTest, SwapNodes) {
+ prefs_.SetManagedPref(prefs::kManagedBookmarks, CreateTestTree());
+ CreateModel(false /* is_supervised */);
+
+ // Swap the Google bookmark with the Folder.
+ scoped_ptr<base::ListValue> updated(CreateTestTree());
+ scoped_ptr<base::Value> removed;
+ ASSERT_TRUE(updated->Remove(0, &removed));
+ updated->Append(removed.release());
+
+ // These two nodes should just be swapped.
+ const BookmarkNode* parent = managed_node();
+ EXPECT_CALL(observer_, BookmarkNodeMoved(model_.get(), parent, 1, parent, 0));
+ prefs_.SetManagedPref(prefs::kManagedBookmarks, updated->DeepCopy());
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ // Verify the final tree.
+ scoped_ptr<base::DictionaryValue> expected(
+ CreateFolder(GetManagedFolderTitle(), updated.release()));
+ EXPECT_TRUE(NodeMatchesValue(managed_node(), expected.get()));
+}
+
+TEST_F(ManagedBookmarksTrackerTest, RemoveNode) {
+ prefs_.SetManagedPref(prefs::kManagedBookmarks, CreateTestTree());
+ CreateModel(false /* is_supervised */);
+
+ // Remove the Folder.
+ scoped_ptr<base::ListValue> updated(CreateTestTree());
+ ASSERT_TRUE(updated->Remove(1, NULL));
+
+ const BookmarkNode* parent = managed_node();
+ EXPECT_CALL(observer_, BookmarkNodeRemoved(model_.get(), parent, 1, _, _));
+ prefs_.SetManagedPref(prefs::kManagedBookmarks, updated->DeepCopy());
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ // Verify the final tree.
+ scoped_ptr<base::DictionaryValue> expected(
+ CreateFolder(GetManagedFolderTitle(), updated.release()));
+ EXPECT_TRUE(NodeMatchesValue(managed_node(), expected.get()));
+}
+
+TEST_F(ManagedBookmarksTrackerTest, CreateNewNodes) {
+ prefs_.SetManagedPref(prefs::kManagedBookmarks, CreateTestTree());
+ CreateModel(false /* is_supervised */);
+
+ // Put all the nodes inside another folder.
+ scoped_ptr<base::ListValue> updated(new base::ListValue);
+ updated->Append(CreateFolder("Container", CreateTestTree()));
+
+ EXPECT_CALL(observer_, BookmarkNodeAdded(model_.get(), _, _)).Times(5);
+ // The remaining nodes have been pushed to positions 1 and 2; they'll both be
+ // removed when at position 1.
+ const BookmarkNode* parent = managed_node();
+ EXPECT_CALL(observer_, BookmarkNodeRemoved(model_.get(), parent, 1, _, _))
+ .Times(2);
+ prefs_.SetManagedPref(prefs::kManagedBookmarks, updated->DeepCopy());
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ // Verify the final tree.
+ scoped_ptr<base::DictionaryValue> expected(
+ CreateFolder(GetManagedFolderTitle(), updated.release()));
+ EXPECT_TRUE(NodeMatchesValue(managed_node(), expected.get()));
+}
+
+TEST_F(ManagedBookmarksTrackerTest, RemoveAll) {
+ prefs_.SetManagedPref(prefs::kManagedBookmarks, CreateTestTree());
+ CreateModel(false /* is_supervised */);
+ EXPECT_TRUE(managed_node()->IsVisible());
+
+ // Remove the policy.
+ const BookmarkNode* parent = managed_node();
+ EXPECT_CALL(observer_, BookmarkNodeRemoved(model_.get(), parent, 0, _, _))
+ .Times(2);
+ prefs_.RemoveManagedPref(prefs::kManagedBookmarks);
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ EXPECT_TRUE(managed_node()->empty());
+ EXPECT_FALSE(managed_node()->IsVisible());
+}
+
+TEST_F(ManagedBookmarksTrackerTest, IsManaged) {
+ prefs_.SetManagedPref(prefs::kManagedBookmarks, CreateTestTree());
+ CreateModel(false /* is_supervised */);
+
+ EXPECT_FALSE(IsManaged(model_->root_node()));
+ EXPECT_FALSE(IsManaged(model_->bookmark_bar_node()));
+ EXPECT_FALSE(IsManaged(model_->other_node()));
+ EXPECT_FALSE(IsManaged(model_->mobile_node()));
+ EXPECT_TRUE(IsManaged(managed_node()));
+
+ const BookmarkNode* parent = managed_node();
+ ASSERT_EQ(2, parent->child_count());
+ EXPECT_TRUE(IsManaged(parent->GetChild(0)));
+ EXPECT_TRUE(IsManaged(parent->GetChild(1)));
+
+ parent = parent->GetChild(1);
+ ASSERT_EQ(2, parent->child_count());
+ EXPECT_TRUE(IsManaged(parent->GetChild(0)));
+ EXPECT_TRUE(IsManaged(parent->GetChild(1)));
+}
+
+TEST_F(ManagedBookmarksTrackerTest, RemoveAllUserBookmarksDoesntRemoveManaged) {
+ prefs_.SetManagedPref(prefs::kManagedBookmarks, CreateTestTree());
+ CreateModel(false /* is_supervised */);
+ EXPECT_EQ(2, managed_node()->child_count());
+
+ EXPECT_CALL(observer_,
+ BookmarkNodeAdded(model_.get(), model_->bookmark_bar_node(), 0));
+ EXPECT_CALL(observer_,
+ BookmarkNodeAdded(model_.get(), model_->bookmark_bar_node(), 1));
+ model_->AddURL(model_->bookmark_bar_node(),
+ 0,
+ base::ASCIIToUTF16("Test"),
+ GURL("http://google.com/"));
+ model_->AddFolder(
+ model_->bookmark_bar_node(), 1, base::ASCIIToUTF16("Test Folder"));
+ EXPECT_EQ(2, model_->bookmark_bar_node()->child_count());
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ EXPECT_CALL(observer_, BookmarkAllUserNodesRemoved(model_.get(), _));
+ model_->RemoveAllUserBookmarks();
+ EXPECT_EQ(2, managed_node()->child_count());
+ EXPECT_EQ(0, model_->bookmark_bar_node()->child_count());
+ Mock::VerifyAndClearExpectations(&observer_);
+}
+
+} // namespace bookmarks