1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/bookmarks/browser/bookmark_model.h"
6
7 #include <algorithm>
8 #include <functional>
9 #include <memory>
10 #include <string>
11 #include <utility>
12
13 #include "base/bind.h"
14 #include "base/bind_helpers.h"
15 #include "base/guid.h"
16 #include "base/i18n/string_compare.h"
17 #include "base/logging.h"
18 #include "base/macros.h"
19 #include "base/metrics/histogram_macros.h"
20 #include "base/strings/string_util.h"
21 #include "components/bookmarks/browser/bookmark_expanded_state_tracker.h"
22 #include "components/bookmarks/browser/bookmark_model_observer.h"
23 #include "components/bookmarks/browser/bookmark_node_data.h"
24 #include "components/bookmarks/browser/bookmark_storage.h"
25 #include "components/bookmarks/browser/bookmark_undo_delegate.h"
26 #include "components/bookmarks/browser/bookmark_utils.h"
27 #include "components/bookmarks/browser/model_loader.h"
28 #include "components/bookmarks/browser/titled_url_index.h"
29 #include "components/bookmarks/browser/titled_url_match.h"
30 #include "components/bookmarks/browser/typed_count_sorter.h"
31 #include "components/bookmarks/browser/url_and_title.h"
32 #include "components/bookmarks/browser/url_index.h"
33 #include "components/bookmarks/common/bookmark_constants.h"
34 #include "components/favicon_base/favicon_types.h"
35 #include "components/strings/grit/components_strings.h"
36 #include "ui/base/l10n/l10n_util.h"
37 #include "ui/gfx/favicon_size.h"
38
39 using base::Time;
40
41 namespace bookmarks {
42
43 namespace {
44
45 // Helper to get a mutable bookmark node.
AsMutable(const BookmarkNode * node)46 BookmarkNode* AsMutable(const BookmarkNode* node) {
47 return const_cast<BookmarkNode*>(node);
48 }
49
50 // Comparator used when sorting permanent nodes. Nodes that are initially
51 // visible are sorted before nodes that are initially hidden.
52 class VisibilityComparator {
53 public:
VisibilityComparator(BookmarkClient * client)54 explicit VisibilityComparator(BookmarkClient* client) : client_(client) {}
55
56 // Returns true if |n1| precedes |n2|.
operator ()(const std::unique_ptr<BookmarkNode> & n1,const std::unique_ptr<BookmarkNode> & n2)57 bool operator()(const std::unique_ptr<BookmarkNode>& n1,
58 const std::unique_ptr<BookmarkNode>& n2) {
59 DCHECK(n1->is_permanent_node());
60 DCHECK(n2->is_permanent_node());
61 bool n1_visible = client_->IsPermanentNodeVisibleWhenEmpty(n1->type());
62 bool n2_visible = client_->IsPermanentNodeVisibleWhenEmpty(n2->type());
63 return n1_visible != n2_visible && n1_visible;
64 }
65
66 private:
67 BookmarkClient* client_;
68 };
69
70 // Comparator used when sorting bookmarks. Folders are sorted first, then
71 // bookmarks.
72 class SortComparator {
73 public:
SortComparator(icu::Collator * collator)74 explicit SortComparator(icu::Collator* collator) : collator_(collator) {}
75
76 // Returns true if |n1| precedes |n2|.
operator ()(const std::unique_ptr<BookmarkNode> & n1,const std::unique_ptr<BookmarkNode> & n2)77 bool operator()(const std::unique_ptr<BookmarkNode>& n1,
78 const std::unique_ptr<BookmarkNode>& n2) {
79 if (n1->type() == n2->type()) {
80 // Types are the same, compare the names.
81 if (!collator_)
82 return n1->GetTitle() < n2->GetTitle();
83 return base::i18n::CompareString16WithCollator(
84 *collator_, n1->GetTitle(), n2->GetTitle()) == UCOL_LESS;
85 }
86 // Types differ, sort such that folders come first.
87 return n1->is_folder();
88 }
89
90 private:
91 icu::Collator* collator_;
92 };
93
94 // Delegate that does nothing.
95 class EmptyUndoDelegate : public BookmarkUndoDelegate {
96 public:
EmptyUndoDelegate()97 EmptyUndoDelegate() {}
~EmptyUndoDelegate()98 ~EmptyUndoDelegate() override {}
99
100 private:
101 // BookmarkUndoDelegate:
SetUndoProvider(BookmarkUndoProvider * provider)102 void SetUndoProvider(BookmarkUndoProvider* provider) override {}
OnBookmarkNodeRemoved(BookmarkModel * model,const BookmarkNode * parent,size_t index,std::unique_ptr<BookmarkNode> node)103 void OnBookmarkNodeRemoved(BookmarkModel* model,
104 const BookmarkNode* parent,
105 size_t index,
106 std::unique_ptr<BookmarkNode> node) override {}
107
108 DISALLOW_COPY_AND_ASSIGN(EmptyUndoDelegate);
109 };
110
111 #if DCHECK_IS_ON()
AddGuidsToIndexRecursive(const BookmarkNode * node,std::set<std::string> * guid_index)112 void AddGuidsToIndexRecursive(const BookmarkNode* node,
113 std::set<std::string>* guid_index) {
114 bool success = guid_index->insert(node->guid()).second;
115 DCHECK(success);
116
117 // Recurse through children.
118 for (size_t i = node->children().size(); i > 0; --i)
119 AddGuidsToIndexRecursive(node->children()[i - 1].get(), guid_index);
120 }
121 #endif // DCHECK_IS_ON()
122
123 } // namespace
124
125 // BookmarkModel --------------------------------------------------------------
126
BookmarkModel(std::unique_ptr<BookmarkClient> client)127 BookmarkModel::BookmarkModel(std::unique_ptr<BookmarkClient> client)
128 : client_(std::move(client)),
129 owned_root_(std::make_unique<BookmarkNode>(/*id=*/0,
130 BookmarkNode::RootNodeGuid(),
131 GURL())),
132 root_(owned_root_.get()),
133 observers_(base::ObserverListPolicy::EXISTING_ONLY),
134 empty_undo_delegate_(std::make_unique<EmptyUndoDelegate>()) {
135 DCHECK(client_);
136 client_->Init(this);
137 }
138
~BookmarkModel()139 BookmarkModel::~BookmarkModel() {
140 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
141
142 for (BookmarkModelObserver& observer : observers_)
143 observer.BookmarkModelBeingDeleted(this);
144
145 if (store_) {
146 // The store maintains a reference back to us. We need to tell it we're gone
147 // so that it doesn't try and invoke a method back on us again.
148 store_->BookmarkModelDeleted();
149 }
150 }
151
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)152 void BookmarkModel::Load(
153 PrefService* pref_service,
154 const base::FilePath& profile_path,
155 const scoped_refptr<base::SequencedTaskRunner>& io_task_runner,
156 const scoped_refptr<base::SequencedTaskRunner>& ui_task_runner) {
157 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
158 // If the store is non-null, it means Load was already invoked. Load should
159 // only be invoked once.
160 DCHECK(!store_);
161
162 expanded_state_tracker_ =
163 std::make_unique<BookmarkExpandedStateTracker>(this, pref_service);
164
165 store_ = std::make_unique<BookmarkStorage>(this, profile_path,
166 io_task_runner.get());
167 // Creating ModelLoader schedules the load on |io_task_runner|.
168 model_loader_ = ModelLoader::Create(
169 profile_path.Append(kBookmarksFileName), io_task_runner.get(),
170 std::make_unique<BookmarkLoadDetails>(client_.get()),
171 base::BindOnce(&BookmarkModel::DoneLoading, AsWeakPtr()));
172 }
173
model_loader()174 scoped_refptr<ModelLoader> BookmarkModel::model_loader() {
175 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
176 return model_loader_;
177 }
178
AddObserver(BookmarkModelObserver * observer)179 void BookmarkModel::AddObserver(BookmarkModelObserver* observer) {
180 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
181 observers_.AddObserver(observer);
182 }
183
RemoveObserver(BookmarkModelObserver * observer)184 void BookmarkModel::RemoveObserver(BookmarkModelObserver* observer) {
185 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
186 observers_.RemoveObserver(observer);
187 }
188
BeginExtensiveChanges()189 void BookmarkModel::BeginExtensiveChanges() {
190 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
191 if (++extensive_changes_ == 1) {
192 for (BookmarkModelObserver& observer : observers_)
193 observer.ExtensiveBookmarkChangesBeginning(this);
194 }
195 }
196
EndExtensiveChanges()197 void BookmarkModel::EndExtensiveChanges() {
198 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
199 --extensive_changes_;
200 DCHECK_GE(extensive_changes_, 0);
201 if (extensive_changes_ == 0) {
202 for (BookmarkModelObserver& observer : observers_)
203 observer.ExtensiveBookmarkChangesEnded(this);
204 }
205 }
206
BeginGroupedChanges()207 void BookmarkModel::BeginGroupedChanges() {
208 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
209 for (BookmarkModelObserver& observer : observers_)
210 observer.GroupedBookmarkChangesBeginning(this);
211 }
212
EndGroupedChanges()213 void BookmarkModel::EndGroupedChanges() {
214 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
215 for (BookmarkModelObserver& observer : observers_)
216 observer.GroupedBookmarkChangesEnded(this);
217 }
218
Remove(const BookmarkNode * node)219 void BookmarkModel::Remove(const BookmarkNode* node) {
220 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
221 DCHECK(loaded_);
222 DCHECK(node);
223 DCHECK(!is_root_node(node));
224 const BookmarkNode* parent = node->parent();
225 DCHECK(parent);
226 size_t index = size_t{parent->GetIndexOf(node)};
227 DCHECK_NE(size_t{-1}, index);
228
229 // Removing a permanent node is problematic and can cause crashes elsewhere
230 // that are difficult to trace back.
231 CHECK(!is_permanent_node(node)) << "for type " << node->type();
232
233 for (BookmarkModelObserver& observer : observers_)
234 observer.OnWillRemoveBookmarks(this, parent, index, node);
235
236 std::set<GURL> removed_urls;
237 std::unique_ptr<BookmarkNode> owned_node =
238 url_index_->Remove(AsMutable(node), &removed_urls);
239 RemoveNodeFromIndexRecursive(owned_node.get());
240
241 if (store_)
242 store_->ScheduleSave();
243
244 for (BookmarkModelObserver& observer : observers_)
245 observer.BookmarkNodeRemoved(this, parent, index, node, removed_urls);
246
247 undo_delegate()->OnBookmarkNodeRemoved(this, parent, index,
248 std::move(owned_node));
249 }
250
RemoveAllUserBookmarks()251 void BookmarkModel::RemoveAllUserBookmarks() {
252 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
253 std::set<GURL> removed_urls;
254 struct RemoveNodeData {
255 const BookmarkNode* parent;
256 int index;
257 std::unique_ptr<BookmarkNode> node;
258 };
259 std::vector<RemoveNodeData> removed_node_data_list;
260
261 for (BookmarkModelObserver& observer : observers_)
262 observer.OnWillRemoveAllUserBookmarks(this);
263
264 BeginExtensiveChanges();
265 // Skip deleting permanent nodes. Permanent bookmark nodes are the root and
266 // its immediate children. For removing all non permanent nodes just remove
267 // all children of non-root permanent nodes.
268 {
269 for (const auto& permanent_node : root_->children()) {
270 if (!client_->CanBeEditedByUser(permanent_node.get()))
271 continue;
272
273 for (size_t j = permanent_node->children().size(); j > 0; --j) {
274 std::unique_ptr<BookmarkNode> node = url_index_->Remove(
275 permanent_node->children()[j - 1].get(), &removed_urls);
276 RemoveNodeFromIndexRecursive(node.get());
277 removed_node_data_list.push_back(
278 {permanent_node.get(), j - 1, std::move(node)});
279 }
280 }
281 }
282 EndExtensiveChanges();
283 if (store_)
284 store_->ScheduleSave();
285
286 for (BookmarkModelObserver& observer : observers_)
287 observer.BookmarkAllUserNodesRemoved(this, removed_urls);
288
289 BeginGroupedChanges();
290 for (auto& removed_node_data : removed_node_data_list) {
291 undo_delegate()->OnBookmarkNodeRemoved(this, removed_node_data.parent,
292 removed_node_data.index,
293 std::move(removed_node_data.node));
294 }
295 EndGroupedChanges();
296 }
297
Move(const BookmarkNode * node,const BookmarkNode * new_parent,size_t index)298 void BookmarkModel::Move(const BookmarkNode* node,
299 const BookmarkNode* new_parent,
300 size_t index) {
301 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
302 DCHECK(loaded_);
303 DCHECK(node);
304 DCHECK(IsValidIndex(new_parent, index, true));
305 DCHECK(!is_root_node(new_parent));
306 DCHECK(!is_permanent_node(node));
307 DCHECK(!new_parent->HasAncestor(node));
308
309 const BookmarkNode* old_parent = node->parent();
310 size_t old_index = old_parent->GetIndexOf(node);
311
312 if (old_parent == new_parent &&
313 (index == old_index || index == old_index + 1)) {
314 // Node is already in this position, nothing to do.
315 return;
316 }
317
318 SetDateFolderModified(new_parent, Time::Now());
319
320 if (old_parent == new_parent && index > old_index)
321 index--;
322
323 BookmarkNode* mutable_old_parent = AsMutable(old_parent);
324 std::unique_ptr<BookmarkNode> owned_node =
325 mutable_old_parent->Remove(old_index);
326 BookmarkNode* mutable_new_parent = AsMutable(new_parent);
327 mutable_new_parent->Add(std::move(owned_node), index);
328
329 if (store_)
330 store_->ScheduleSave();
331
332 for (BookmarkModelObserver& observer : observers_)
333 observer.BookmarkNodeMoved(this, old_parent, old_index, new_parent, index);
334 }
335
Copy(const BookmarkNode * node,const BookmarkNode * new_parent,size_t index)336 void BookmarkModel::Copy(const BookmarkNode* node,
337 const BookmarkNode* new_parent,
338 size_t index) {
339 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
340 DCHECK(loaded_);
341 DCHECK(node);
342 DCHECK(IsValidIndex(new_parent, index, true));
343 DCHECK(!is_root_node(new_parent));
344 DCHECK(!is_permanent_node(node));
345 DCHECK(!new_parent->HasAncestor(node));
346
347 SetDateFolderModified(new_parent, Time::Now());
348 BookmarkNodeData drag_data(node);
349 // CloneBookmarkNode will use BookmarkModel methods to do the job, so we
350 // don't need to send notifications here.
351 CloneBookmarkNode(this, drag_data.elements, new_parent, index, true);
352
353 if (store_)
354 store_->ScheduleSave();
355 }
356
GetFavicon(const BookmarkNode * node)357 const gfx::Image& BookmarkModel::GetFavicon(const BookmarkNode* node) {
358 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
359 DCHECK(node);
360 if (node->favicon_state() == BookmarkNode::INVALID_FAVICON) {
361 BookmarkNode* mutable_node = AsMutable(node);
362 LoadFavicon(mutable_node, client_->PreferTouchIcon()
363 ? favicon_base::IconType::kTouchIcon
364 : favicon_base::IconType::kFavicon);
365 }
366 return node->favicon();
367 }
368
GetFaviconType(const BookmarkNode * node)369 favicon_base::IconType BookmarkModel::GetFaviconType(const BookmarkNode* node) {
370 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
371 DCHECK(node);
372 return node->favicon_type();
373 }
374
SetTitle(const BookmarkNode * node,const base::string16 & title)375 void BookmarkModel::SetTitle(const BookmarkNode* node,
376 const base::string16& title) {
377 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
378 DCHECK(node);
379
380 if (node->GetTitle() == title)
381 return;
382
383 if (is_permanent_node(node) && !client_->CanSetPermanentNodeTitle(node)) {
384 NOTREACHED();
385 return;
386 }
387
388 for (BookmarkModelObserver& observer : observers_)
389 observer.OnWillChangeBookmarkNode(this, node);
390
391 // The title index doesn't support changing the title, instead we remove then
392 // add it back. Only do this for URL nodes. A directory node can have its
393 // title changed but should be excluded from the index.
394 if (node->is_url())
395 titled_url_index_->Remove(node);
396 url_index_->SetTitle(AsMutable(node), title);
397 if (node->is_url())
398 titled_url_index_->Add(node);
399
400 if (store_)
401 store_->ScheduleSave();
402
403 for (BookmarkModelObserver& observer : observers_)
404 observer.BookmarkNodeChanged(this, node);
405 }
406
SetURL(const BookmarkNode * node,const GURL & url)407 void BookmarkModel::SetURL(const BookmarkNode* node, const GURL& url) {
408 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
409 DCHECK(node);
410 DCHECK(!node->is_folder());
411
412 if (node->url() == url)
413 return;
414
415 BookmarkNode* mutable_node = AsMutable(node);
416 mutable_node->InvalidateFavicon();
417 CancelPendingFaviconLoadRequests(mutable_node);
418
419 for (BookmarkModelObserver& observer : observers_)
420 observer.OnWillChangeBookmarkNode(this, node);
421
422 // The title index doesn't support changing the URL, instead we remove then
423 // add it back.
424 titled_url_index_->Remove(mutable_node);
425 url_index_->SetUrl(mutable_node, url);
426 titled_url_index_->Add(mutable_node);
427
428 if (store_)
429 store_->ScheduleSave();
430
431 for (BookmarkModelObserver& observer : observers_)
432 observer.BookmarkNodeChanged(this, node);
433 }
434
SetNodeMetaInfo(const BookmarkNode * node,const std::string & key,const std::string & value)435 void BookmarkModel::SetNodeMetaInfo(const BookmarkNode* node,
436 const std::string& key,
437 const std::string& value) {
438 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
439
440 std::string old_value;
441 if (node->GetMetaInfo(key, &old_value) && old_value == value)
442 return;
443
444 for (BookmarkModelObserver& observer : observers_)
445 observer.OnWillChangeBookmarkMetaInfo(this, node);
446
447 if (AsMutable(node)->SetMetaInfo(key, value) && store_.get())
448 store_->ScheduleSave();
449
450 for (BookmarkModelObserver& observer : observers_)
451 observer.BookmarkMetaInfoChanged(this, node);
452 }
453
SetNodeMetaInfoMap(const BookmarkNode * node,const BookmarkNode::MetaInfoMap & meta_info_map)454 void BookmarkModel::SetNodeMetaInfoMap(
455 const BookmarkNode* node,
456 const BookmarkNode::MetaInfoMap& meta_info_map) {
457 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
458
459 const BookmarkNode::MetaInfoMap* old_meta_info_map = node->GetMetaInfoMap();
460 if ((!old_meta_info_map && meta_info_map.empty()) ||
461 (old_meta_info_map && meta_info_map == *old_meta_info_map))
462 return;
463
464 for (BookmarkModelObserver& observer : observers_)
465 observer.OnWillChangeBookmarkMetaInfo(this, node);
466
467 AsMutable(node)->SetMetaInfoMap(meta_info_map);
468 if (store_)
469 store_->ScheduleSave();
470
471 for (BookmarkModelObserver& observer : observers_)
472 observer.BookmarkMetaInfoChanged(this, node);
473 }
474
DeleteNodeMetaInfo(const BookmarkNode * node,const std::string & key)475 void BookmarkModel::DeleteNodeMetaInfo(const BookmarkNode* node,
476 const std::string& key) {
477 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
478
479 const BookmarkNode::MetaInfoMap* meta_info_map = node->GetMetaInfoMap();
480 if (!meta_info_map || meta_info_map->find(key) == meta_info_map->end())
481 return;
482
483 for (BookmarkModelObserver& observer : observers_)
484 observer.OnWillChangeBookmarkMetaInfo(this, node);
485
486 if (AsMutable(node)->DeleteMetaInfo(key) && store_.get())
487 store_->ScheduleSave();
488
489 for (BookmarkModelObserver& observer : observers_)
490 observer.BookmarkMetaInfoChanged(this, node);
491 }
492
AddNonClonedKey(const std::string & key)493 void BookmarkModel::AddNonClonedKey(const std::string& key) {
494 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
495 non_cloned_keys_.insert(key);
496 }
497
OnFaviconsChanged(const std::set<GURL> & page_urls,const GURL & icon_url)498 void BookmarkModel::OnFaviconsChanged(const std::set<GURL>& page_urls,
499 const GURL& icon_url) {
500 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
501
502 if (!loaded_)
503 return;
504
505 std::set<const BookmarkNode*> to_update;
506 for (const GURL& page_url : page_urls) {
507 std::vector<const BookmarkNode*> nodes;
508 GetNodesByURL(page_url, &nodes);
509 to_update.insert(nodes.begin(), nodes.end());
510 }
511
512 if (!icon_url.is_empty()) {
513 // Log Histogram to determine how often |icon_url| is non empty in
514 // practice.
515 // TODO(pkotwicz): Do something more efficient if |icon_url| is non-empty
516 // many times a day for each user.
517 UMA_HISTOGRAM_BOOLEAN("Bookmarks.OnFaviconsChangedIconURL", true);
518
519 url_index_->GetNodesWithIconUrl(icon_url, &to_update);
520 }
521
522 for (const BookmarkNode* node : to_update) {
523 // Rerequest the favicon.
524 BookmarkNode* mutable_node = AsMutable(node);
525 mutable_node->InvalidateFavicon();
526 CancelPendingFaviconLoadRequests(mutable_node);
527 for (BookmarkModelObserver& observer : observers_)
528 observer.BookmarkNodeFaviconChanged(this, node);
529 }
530 }
531
SetDateAdded(const BookmarkNode * node,Time date_added)532 void BookmarkModel::SetDateAdded(const BookmarkNode* node, Time date_added) {
533 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
534 DCHECK(node);
535 DCHECK(!is_permanent_node(node));
536
537 if (node->date_added() == date_added)
538 return;
539
540 AsMutable(node)->set_date_added(date_added);
541
542 // Syncing might result in dates newer than the folder's last modified date.
543 if (date_added > node->parent()->date_folder_modified()) {
544 // Will trigger store_->ScheduleSave().
545 SetDateFolderModified(node->parent(), date_added);
546 } else if (store_) {
547 store_->ScheduleSave();
548 }
549 }
550
GetNodesByURL(const GURL & url,std::vector<const BookmarkNode * > * nodes)551 void BookmarkModel::GetNodesByURL(const GURL& url,
552 std::vector<const BookmarkNode*>* nodes) {
553 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
554
555 if (url_index_)
556 url_index_->GetNodesByUrl(url, nodes);
557 }
558
GetMostRecentlyAddedUserNodeForURL(const GURL & url)559 const BookmarkNode* BookmarkModel::GetMostRecentlyAddedUserNodeForURL(
560 const GURL& url) {
561 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
562
563 std::vector<const BookmarkNode*> nodes;
564 GetNodesByURL(url, &nodes);
565 std::sort(nodes.begin(), nodes.end(), &MoreRecentlyAdded);
566
567 // Look for the first node that the user can edit.
568 for (size_t i = 0; i < nodes.size(); ++i) {
569 if (client_->CanBeEditedByUser(nodes[i]))
570 return nodes[i];
571 }
572
573 return nullptr;
574 }
575
HasBookmarks()576 bool BookmarkModel::HasBookmarks() {
577 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
578 return url_index_ && url_index_->HasBookmarks();
579 }
580
HasNoUserCreatedBookmarksOrFolders()581 bool BookmarkModel::HasNoUserCreatedBookmarksOrFolders() {
582 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
583 return bookmark_bar_node_->children().empty() &&
584 other_node_->children().empty() && mobile_node_->children().empty();
585 }
586
IsBookmarked(const GURL & url)587 bool BookmarkModel::IsBookmarked(const GURL& url) {
588 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
589 return url_index_ && url_index_->IsBookmarked(url);
590 }
591
GetBookmarks(std::vector<UrlAndTitle> * bookmarks)592 void BookmarkModel::GetBookmarks(std::vector<UrlAndTitle>* bookmarks) {
593 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
594 if (url_index_)
595 url_index_->GetBookmarks(bookmarks);
596 }
597
AddFolder(const BookmarkNode * parent,size_t index,const base::string16 & title,const BookmarkNode::MetaInfoMap * meta_info,base::Optional<std::string> guid)598 const BookmarkNode* BookmarkModel::AddFolder(
599 const BookmarkNode* parent,
600 size_t index,
601 const base::string16& title,
602 const BookmarkNode::MetaInfoMap* meta_info,
603 base::Optional<std::string> guid) {
604 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
605 DCHECK(loaded_);
606 DCHECK(parent);
607 DCHECK(parent->is_folder());
608 DCHECK(!is_root_node(parent));
609 DCHECK(IsValidIndex(parent, index, true));
610
611 if (guid)
612 DCHECK(base::IsValidGUIDOutputString(*guid));
613 else
614 guid = base::GenerateGUID();
615
616 auto new_node =
617 std::make_unique<BookmarkNode>(generate_next_node_id(), *guid, GURL());
618 new_node->set_date_folder_modified(Time::Now());
619 // Folders shouldn't have line breaks in their titles.
620 new_node->SetTitle(title);
621 if (meta_info)
622 new_node->SetMetaInfoMap(*meta_info);
623
624 return AddNode(AsMutable(parent), index, std::move(new_node));
625 }
626
AddURL(const BookmarkNode * parent,size_t index,const base::string16 & title,const GURL & url,const BookmarkNode::MetaInfoMap * meta_info,base::Optional<base::Time> creation_time,base::Optional<std::string> guid)627 const BookmarkNode* BookmarkModel::AddURL(
628 const BookmarkNode* parent,
629 size_t index,
630 const base::string16& title,
631 const GURL& url,
632 const BookmarkNode::MetaInfoMap* meta_info,
633 base::Optional<base::Time> creation_time,
634 base::Optional<std::string> guid) {
635 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
636 DCHECK(loaded_);
637 DCHECK(url.is_valid());
638 DCHECK(parent);
639 DCHECK(parent->is_folder());
640 DCHECK(!is_root_node(parent));
641 DCHECK(IsValidIndex(parent, index, true));
642
643 if (guid)
644 DCHECK(base::IsValidGUIDOutputString(*guid));
645 else
646 guid = base::GenerateGUID();
647
648 if (!creation_time)
649 creation_time = Time::Now();
650
651 // Syncing may result in dates newer than the last modified date.
652 if (*creation_time > parent->date_folder_modified())
653 SetDateFolderModified(parent, *creation_time);
654
655 auto new_node =
656 std::make_unique<BookmarkNode>(generate_next_node_id(), *guid, url);
657 new_node->SetTitle(title);
658 new_node->set_date_added(*creation_time);
659 if (meta_info)
660 new_node->SetMetaInfoMap(*meta_info);
661
662 return AddNode(AsMutable(parent), index, std::move(new_node));
663 }
664
SortChildren(const BookmarkNode * parent)665 void BookmarkModel::SortChildren(const BookmarkNode* parent) {
666 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
667 DCHECK(client_->CanBeEditedByUser(parent));
668
669 if (!parent || !parent->is_folder() || is_root_node(parent) ||
670 parent->children().size() <= 1) {
671 return;
672 }
673
674 for (BookmarkModelObserver& observer : observers_)
675 observer.OnWillReorderBookmarkNode(this, parent);
676
677 UErrorCode error = U_ZERO_ERROR;
678 std::unique_ptr<icu::Collator> collator(icu::Collator::createInstance(error));
679 if (U_FAILURE(error))
680 collator.reset(nullptr);
681 BookmarkNode* mutable_parent = AsMutable(parent);
682 std::sort(mutable_parent->children_.begin(), mutable_parent->children_.end(),
683 SortComparator(collator.get()));
684
685 if (store_)
686 store_->ScheduleSave();
687
688 for (BookmarkModelObserver& observer : observers_)
689 observer.BookmarkNodeChildrenReordered(this, parent);
690 }
691
ReorderChildren(const BookmarkNode * parent,const std::vector<const BookmarkNode * > & ordered_nodes)692 void BookmarkModel::ReorderChildren(
693 const BookmarkNode* parent,
694 const std::vector<const BookmarkNode*>& ordered_nodes) {
695 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
696 DCHECK(client_->CanBeEditedByUser(parent));
697
698 // Ensure that all children in |parent| are in |ordered_nodes|.
699 DCHECK_EQ(parent->children().size(), ordered_nodes.size());
700 for (const BookmarkNode* node : ordered_nodes)
701 DCHECK_EQ(parent, node->parent());
702
703 for (BookmarkModelObserver& observer : observers_)
704 observer.OnWillReorderBookmarkNode(this, parent);
705
706 if (ordered_nodes.size() > 1) {
707 std::map<const BookmarkNode*, int> order;
708 for (size_t i = 0; i < ordered_nodes.size(); ++i)
709 order[ordered_nodes[i]] = i;
710
711 std::vector<std::unique_ptr<BookmarkNode>> new_children(
712 ordered_nodes.size());
713 BookmarkNode* mutable_parent = AsMutable(parent);
714 for (auto& child : mutable_parent->children_) {
715 size_t new_location = order[child.get()];
716 new_children[new_location] = std::move(child);
717 }
718 mutable_parent->children_.swap(new_children);
719
720 if (store_)
721 store_->ScheduleSave();
722 }
723
724 for (BookmarkModelObserver& observer : observers_)
725 observer.BookmarkNodeChildrenReordered(this, parent);
726 }
727
SetDateFolderModified(const BookmarkNode * parent,const Time time)728 void BookmarkModel::SetDateFolderModified(const BookmarkNode* parent,
729 const Time time) {
730 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
731 DCHECK(parent);
732 AsMutable(parent)->set_date_folder_modified(time);
733
734 if (store_)
735 store_->ScheduleSave();
736 }
737
ResetDateFolderModified(const BookmarkNode * node)738 void BookmarkModel::ResetDateFolderModified(const BookmarkNode* node) {
739 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
740 SetDateFolderModified(node, Time());
741 }
742
GetBookmarksMatching(const base::string16 & text,size_t max_count,std::vector<TitledUrlMatch> * matches)743 void BookmarkModel::GetBookmarksMatching(const base::string16& text,
744 size_t max_count,
745 std::vector<TitledUrlMatch>* matches) {
746 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
747 GetBookmarksMatching(text, max_count,
748 query_parser::MatchingAlgorithm::DEFAULT, matches);
749 }
750
GetBookmarksMatching(const base::string16 & text,size_t max_count,query_parser::MatchingAlgorithm matching_algorithm,std::vector<TitledUrlMatch> * matches)751 void BookmarkModel::GetBookmarksMatching(
752 const base::string16& text,
753 size_t max_count,
754 query_parser::MatchingAlgorithm matching_algorithm,
755 std::vector<TitledUrlMatch>* matches) {
756 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
757
758 if (!loaded_)
759 return;
760
761 titled_url_index_->GetResultsMatching(text, max_count, matching_algorithm,
762 matches);
763 }
764
ClearStore()765 void BookmarkModel::ClearStore() {
766 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
767 store_.reset();
768 }
769
RestoreRemovedNode(const BookmarkNode * parent,size_t index,std::unique_ptr<BookmarkNode> node)770 void BookmarkModel::RestoreRemovedNode(const BookmarkNode* parent,
771 size_t index,
772 std::unique_ptr<BookmarkNode> node) {
773 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
774
775 BookmarkNode* node_ptr = node.get();
776 AddNode(AsMutable(parent), index, std::move(node));
777
778 // We might be restoring a folder node that have already contained a set of
779 // child nodes. We need to notify all of them.
780 NotifyNodeAddedForAllDescendents(node_ptr);
781 }
782
NotifyNodeAddedForAllDescendents(const BookmarkNode * node)783 void BookmarkModel::NotifyNodeAddedForAllDescendents(const BookmarkNode* node) {
784 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
785
786 for (size_t i = 0; i < node->children().size(); ++i) {
787 for (BookmarkModelObserver& observer : observers_)
788 observer.BookmarkNodeAdded(this, node, i);
789 NotifyNodeAddedForAllDescendents(node->children()[i].get());
790 }
791 }
792
RemoveNodeFromIndexRecursive(BookmarkNode * node)793 void BookmarkModel::RemoveNodeFromIndexRecursive(BookmarkNode* node) {
794 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
795 DCHECK(loaded_);
796 DCHECK(!is_permanent_node(node));
797
798 if (node->is_url())
799 titled_url_index_->Remove(node);
800
801 // Note that |guid_index_| is used for DCHECK-enabled builds only.
802 #if DCHECK_IS_ON()
803 DCHECK(guid_index_.erase(node->guid()))
804 << "Bookmark GUID missing in index: " << node->guid();
805 #endif // DCHECK_IS_ON()
806
807 CancelPendingFaviconLoadRequests(node);
808
809 // Recurse through children.
810 for (size_t i = node->children().size(); i > 0; --i)
811 RemoveNodeFromIndexRecursive(node->children()[i - 1].get());
812 }
813
DoneLoading(std::unique_ptr<BookmarkLoadDetails> details)814 void BookmarkModel::DoneLoading(std::unique_ptr<BookmarkLoadDetails> details) {
815 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
816 DCHECK(details);
817 DCHECK(!loaded_);
818
819 next_node_id_ = details->max_id();
820 if (details->computed_checksum() != details->stored_checksum() ||
821 details->ids_reassigned() || details->guids_reassigned()) {
822 // If bookmarks file changed externally, the IDs may have changed
823 // externally. In that case, the decoder may have reassigned IDs to make
824 // them unique. So when the file has changed externally, we should save the
825 // bookmarks file to persist such changes. The same applies if new GUIDs
826 // have been assigned to bookmarks.
827 if (store_)
828 store_->ScheduleSave();
829 }
830
831 titled_url_index_ = details->owned_index();
832 url_index_ = details->url_index();
833 root_ = details->root_node();
834 // See declaration for details on why |owned_root_| is reset.
835 owned_root_.reset();
836 bookmark_bar_node_ = details->bb_node();
837 other_node_ = details->other_folder_node();
838 mobile_node_ = details->mobile_folder_node();
839
840 #if DCHECK_IS_ON()
841 AddGuidsToIndexRecursive(root_, &guid_index_);
842 #endif // DCHECK_IS_ON()
843
844 titled_url_index_->SetNodeSorter(
845 std::make_unique<TypedCountSorter>(client_.get()));
846 // Sorting the permanent nodes has to happen on the main thread, so we do it
847 // here, after loading completes.
848 std::stable_sort(root_->children_.begin(), root_->children_.end(),
849 VisibilityComparator(client_.get()));
850
851 root_->SetMetaInfoMap(details->model_meta_info_map());
852
853 loaded_ = true;
854 client_->DecodeBookmarkSyncMetadata(
855 details->sync_metadata_str(),
856 store_ ? base::BindRepeating(&BookmarkStorage::ScheduleSave,
857 base::Unretained(store_.get()))
858 : base::DoNothing());
859
860 // Notify our direct observers.
861 for (BookmarkModelObserver& observer : observers_)
862 observer.BookmarkModelLoaded(this, details->ids_reassigned());
863 }
864
AddNode(BookmarkNode * parent,size_t index,std::unique_ptr<BookmarkNode> node)865 BookmarkNode* BookmarkModel::AddNode(BookmarkNode* parent,
866 size_t index,
867 std::unique_ptr<BookmarkNode> node) {
868 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
869
870 BookmarkNode* node_ptr = node.get();
871 url_index_->Add(parent, index, std::move(node));
872
873 if (store_)
874 store_->ScheduleSave();
875
876 AddNodeToIndexRecursive(node_ptr);
877
878 for (BookmarkModelObserver& observer : observers_)
879 observer.BookmarkNodeAdded(this, parent, index);
880
881 return node_ptr;
882 }
883
AddNodeToIndexRecursive(BookmarkNode * node)884 void BookmarkModel::AddNodeToIndexRecursive(BookmarkNode* node) {
885 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
886
887 if (node->is_url())
888 titled_url_index_->Add(node);
889
890 // The node's GUID must be unique. Note that |guid_index_| is used for
891 // DCHECK-enabled builds only.
892 #if DCHECK_IS_ON()
893 DCHECK(guid_index_.insert(node->guid()).second)
894 << "Duplicate bookmark GUID: " << node->guid();
895 #endif // DCHECK_IS_ON()
896
897 for (const auto& child : node->children())
898 AddNodeToIndexRecursive(child.get());
899 }
900
IsValidIndex(const BookmarkNode * parent,size_t index,bool allow_end)901 bool BookmarkModel::IsValidIndex(const BookmarkNode* parent,
902 size_t index,
903 bool allow_end) {
904 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
905 return parent && parent->is_folder() &&
906 (index < parent->children().size() ||
907 (allow_end && index == parent->children().size()));
908 }
909
OnFaviconDataAvailable(BookmarkNode * node,favicon_base::IconType icon_type,const favicon_base::FaviconImageResult & image_result)910 void BookmarkModel::OnFaviconDataAvailable(
911 BookmarkNode* node,
912 favicon_base::IconType icon_type,
913 const favicon_base::FaviconImageResult& image_result) {
914 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
915 DCHECK(node);
916
917 node->set_favicon_load_task_id(base::CancelableTaskTracker::kBadTaskId);
918 node->set_favicon_state(BookmarkNode::LOADED_FAVICON);
919 if (!image_result.image.IsEmpty()) {
920 node->set_favicon_type(icon_type);
921 node->set_favicon(image_result.image);
922 node->set_icon_url(image_result.icon_url);
923 FaviconLoaded(node);
924 } else if (icon_type == favicon_base::IconType::kTouchIcon) {
925 // Couldn't load the touch icon, fallback to the regular favicon.
926 DCHECK(client_->PreferTouchIcon());
927 LoadFavicon(node, favicon_base::IconType::kFavicon);
928 } else {
929 // No favicon available, but we still notify observers.
930 FaviconLoaded(node);
931 }
932 }
933
LoadFavicon(BookmarkNode * node,favicon_base::IconType icon_type)934 void BookmarkModel::LoadFavicon(BookmarkNode* node,
935 favicon_base::IconType icon_type) {
936 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
937
938 if (node->is_folder())
939 return;
940
941 DCHECK(node->url().is_valid());
942 node->set_favicon_state(BookmarkNode::LOADING_FAVICON);
943 base::CancelableTaskTracker::TaskId taskId =
944 client_->GetFaviconImageForPageURL(
945 node->url(), icon_type,
946 base::BindOnce(&BookmarkModel::OnFaviconDataAvailable,
947 base::Unretained(this), node, icon_type),
948 &cancelable_task_tracker_);
949 if (taskId != base::CancelableTaskTracker::kBadTaskId)
950 node->set_favicon_load_task_id(taskId);
951 }
952
FaviconLoaded(const BookmarkNode * node)953 void BookmarkModel::FaviconLoaded(const BookmarkNode* node) {
954 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
955 for (BookmarkModelObserver& observer : observers_)
956 observer.BookmarkNodeFaviconChanged(this, node);
957 }
958
CancelPendingFaviconLoadRequests(BookmarkNode * node)959 void BookmarkModel::CancelPendingFaviconLoadRequests(BookmarkNode* node) {
960 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
961 if (node->favicon_load_task_id() != base::CancelableTaskTracker::kBadTaskId) {
962 cancelable_task_tracker_.TryCancel(node->favicon_load_task_id());
963 node->set_favicon_load_task_id(base::CancelableTaskTracker::kBadTaskId);
964 }
965 }
966
generate_next_node_id()967 int64_t BookmarkModel::generate_next_node_id() {
968 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
969 DCHECK(loaded_);
970 return next_node_id_++;
971 }
972
SetUndoDelegate(BookmarkUndoDelegate * undo_delegate)973 void BookmarkModel::SetUndoDelegate(BookmarkUndoDelegate* undo_delegate) {
974 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
975 undo_delegate_ = undo_delegate;
976 if (undo_delegate_)
977 undo_delegate_->SetUndoProvider(this);
978 }
979
undo_delegate() const980 BookmarkUndoDelegate* BookmarkModel::undo_delegate() const {
981 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
982 return undo_delegate_ ? undo_delegate_ : empty_undo_delegate_.get();
983 }
984
985 } // namespace bookmarks
986