Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(52)

Side by Side Diff: chrome/browser/bookmarks/bookmark_tag_model.cc

Issue 26894002: Experimental bookmark model based on tags. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2013 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 "chrome/browser/bookmarks/bookmark_tag_model.h"
6
7 #include "base/json/json_string_value_serializer.h"
8 #include "base/observer_list.h"
9 #include "base/strings/string_util.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
12 #include "chrome/browser/bookmarks/bookmark_tag_model_observer.h"
13 #include "ui/base/models/tree_node_iterator.h"
14
15 namespace {
16 // The key used to store the tag list in the metainfo of a bookmark.
17 const char* TAG_KEY = "TAG_KEY";
18
19 // Predicates to sort bookmarks.
20 bool CompareBookmarkTitles(const BookmarkNode* a, const BookmarkNode* b) {
21 return a->GetTitle() < b->GetTitle();
22 }
23 bool CompareBookmarkUrl(const BookmarkNode* a, const BookmarkNode* b) {
24 return a->url() < b->url();
25 }
26 bool CompareBookmarkCreation(const BookmarkNode* a, const BookmarkNode* b) {
27 return a->date_added() < b->date_added();
28 }
29
30 // Comparator to sort tags by usage.
31 struct TagComparator {
32 TagComparator(std::map<BookmarkTag, unsigned int>& tags) : tags_(tags) {
33 }
34 ~TagComparator() {}
35
36 bool operator()(BookmarkTag a, BookmarkTag b) {
37 return (tags_[a] < tags_[b]);
38 }
39
40 std::map<BookmarkTag, unsigned int>& tags_;
41 };
42 } // namespace
43
44 BookmarkTagModel::BookmarkTagModel(BookmarkModel* bookmark_model)
45 : bookmark_model_(bookmark_model),
46 loaded_(false),
47 observers_(ObserverList<BookmarkTagModelObserver>::NOTIFY_EXISTING_ONLY),
48 inhibit_change_notifications_(false) {
49 bookmark_model_->AddObserver(this);
50 if (bookmark_model_->loaded())
51 Load();
52 }
53
54 BookmarkTagModel::~BookmarkTagModel() {
55 if (bookmark_model_)
Yaron 2013/10/11 10:50:41 Can this ever fail? Seems like the c-tor guarantee
noyau (Ping after 24h) 2013/10/11 12:09:21 The bookmark model notifies its observer when it i
56 bookmark_model_->RemoveObserver(this);
57 }
58
59 #pragma mark BookmarkModel forwarding.
60
61 void BookmarkTagModel::AddObserver(BookmarkTagModelObserver* observer) {
62 observers_.AddObserver(observer);
63 }
64
65 void BookmarkTagModel::RemoveObserver(BookmarkTagModelObserver* observer) {
66 observers_.RemoveObserver(observer);
67 }
68
69 void BookmarkTagModel::BeginExtensiveChanges() {
70 DCHECK(bookmark_model_);
Yaron 2013/10/11 10:50:41 These DCHECKS all seem unnecessary for reasons abo
noyau (Ping after 24h) 2013/10/11 12:09:21 These DCHECK are necessary for reasons above :)
71 bookmark_model_->BeginExtensiveChanges();
72 }
73
74 void BookmarkTagModel::EndExtensiveChanges() {
75 DCHECK(bookmark_model_);
76 bookmark_model_->EndExtensiveChanges();
77 }
78
79 bool BookmarkTagModel::IsDoingExtensiveChanges() const {
80 DCHECK(bookmark_model_);
81 return bookmark_model_->IsDoingExtensiveChanges();
82 }
83
84 void BookmarkTagModel::Remove(const BookmarkNode* bookmark) {
85 DCHECK(bookmark_model_);
86 DCHECK(loaded_);
87 const BookmarkNode *parent = bookmark->parent();
88 bookmark_model_->Remove(parent, parent->GetIndexOf(bookmark));
89 }
90
91 void BookmarkTagModel::RemoveAll() {
92 DCHECK(bookmark_model_);
93 DCHECK(loaded_);
94 bookmark_model_->RemoveAll();
95 }
96
97 const gfx::Image& BookmarkTagModel::GetFavicon(const BookmarkNode* bookmark) {
98 DCHECK(bookmark_model_);
99 DCHECK(loaded_);
100 return bookmark_model_->GetFavicon(bookmark);
101 }
102
103 void BookmarkTagModel::SetTitle(const BookmarkNode* bookmark,
104 const string16& title) {
105 DCHECK(bookmark_model_);
106 DCHECK(loaded_);
107 bookmark_model_->SetTitle(bookmark, title);
108 }
109
110 void BookmarkTagModel::SetURL(const BookmarkNode* bookmark, const GURL& url) {
111 DCHECK(bookmark_model_);
112 DCHECK(loaded_);
113 bookmark_model_->SetURL(bookmark, url);
114 }
115
116 void BookmarkTagModel::SetDateAdded(const BookmarkNode* bookmark,
117 base::Time date_added) {
118 DCHECK(bookmark_model_);
119 DCHECK(loaded_);
120 bookmark_model_->SetDateAdded(bookmark, date_added);
121 }
122
123 const BookmarkNode*
124 BookmarkTagModel::GetMostRecentlyAddedBookmarkForURL(const GURL& url) {
125 DCHECK(bookmark_model_);
126 DCHECK(loaded_);
127 return bookmark_model_->GetMostRecentlyAddedNodeForURL(url);
128 }
129
130 #pragma mark Tags.
131
132 const BookmarkNode* BookmarkTagModel::AddURL(
133 const string16& title,
134 const GURL& url,
135 const std::set<BookmarkTag>& tags) {
136 DCHECK(bookmark_model_);
137 DCHECK(loaded_);
138
139 inhibit_change_notifications_ = true;
140 const BookmarkNode* parent = bookmark_model_->GetParentForNewNodes();
141 const BookmarkNode* bookmark = bookmark_model_->AddURL(
Yaron 2013/10/11 10:50:41 I'm pretty sure the underlying BookmarkModel will
noyau (Ping after 24h) 2013/10/11 12:09:21 Yes, it will, and should. This class relies on the
142 parent, 0, title, url);
143 AddTagsToBookmark(tags, bookmark);
144 inhibit_change_notifications_ = false;
145
146 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
147 BookmarkNodeAdded(this, bookmark));
148
149 return bookmark;
150 }
151
152 std::set<BookmarkTag>
153 BookmarkTagModel::AllTagsForBookmark(const BookmarkNode* bookmark) {
154 DCHECK(loaded_);
155 return bookmark_to_tags_[bookmark];
156 }
157
158 void BookmarkTagModel::AddTagsToBookmark(
159 const std::set<BookmarkTag>& tags, const BookmarkNode* bookmark) {
160 std::set<BookmarkTag> all_tags(AllTagsForBookmark(bookmark));
161 for (std::set<BookmarkTag>::iterator it = tags.begin();
162 it != tags.end(); ++it) {
163 BookmarkTag trimmed_tag = CollapseWhitespace(*it, true);
164 if (trimmed_tag.empty())
165 continue;
166 all_tags.insert(trimmed_tag);
167 }
168 ReplaceTagsOnBookmark(all_tags, bookmark);
169 }
170
171 void BookmarkTagModel::AddTagsToBookmarks(
172 const std::set<BookmarkTag>& tags,
173 const std::set<const BookmarkNode*>& bookmarks) {
174 for (std::set<const BookmarkNode*>::iterator it = bookmarks.begin();
175 it != bookmarks.end(); ++it) {
176 AddTagsToBookmark(tags, *it);
177 }
178 }
179
180 void BookmarkTagModel::RemoveTagsFromBookmark(
181 const std::set<BookmarkTag>& tags,
182 const BookmarkNode* bookmark) {
183 std::set<BookmarkTag> all_tags(AllTagsForBookmark(bookmark));
184 for (std::set<BookmarkTag>::iterator it = tags.begin();
185 it != tags.end(); ++it) {
186 all_tags.erase(*it);
187 }
188 ReplaceTagsOnBookmark(all_tags, bookmark);
189 }
190
191 void BookmarkTagModel::RemoveTagsFromBookmarks(
192 const std::set<BookmarkTag>& tags,
193 const std::set<const BookmarkNode*>& bookmarks){
194 for (std::set<const BookmarkNode*>::iterator it = bookmarks.begin();
195 it != bookmarks.end(); ++it) {
196 RemoveTagsFromBookmark(tags, *it);
197 }
198 }
199
200 std::vector<const BookmarkNode*> BookmarkTagModel::BookmarksForTags(
201 const std::set<BookmarkTag>& tags,
202 BookmarkTagModel::BookmarkOrdering ordering) {
203 DCHECK(loaded_);
204 std::set<const BookmarkNode*> bookmarks;
205 for (std::set<BookmarkTag>::iterator it = tags.begin();
206 it != tags.end(); ++it) {
207 const std::set<const BookmarkNode*> subset(tag_to_bookmarks_[*it]);
208 bookmarks.insert(subset.begin(), subset.end());
noyau (Ping after 24h) 2013/10/11 12:09:21 GCC on Android fail to compile this insert() with
209 }
210
211 std::vector<const BookmarkNode*> sorted_bookmarks(bookmarks.begin(),
212 bookmarks.end());
213 switch (ordering) {
214 case UNSORTED_BOOKMARK_ORDERING:
215 break;
216 case TITLE_BOOKMARK_ORDERING:
217 std::sort(sorted_bookmarks.begin(), sorted_bookmarks.end(),
218 CompareBookmarkTitles);
219 break;
220 case URL_BOOKMARK_ORDERING:
221 std::sort(sorted_bookmarks.begin(), sorted_bookmarks.end(),
222 CompareBookmarkUrl);
223 break;
224 case CREATION_TIME_BOOKMARK_ORDERING:
225 std::sort(sorted_bookmarks.begin(), sorted_bookmarks.end(),
226 CompareBookmarkCreation);
227 break;
228 default:
229 NOTREACHED();
230 }
231 return sorted_bookmarks;
232 }
233
234 std::vector<const BookmarkNode*> BookmarkTagModel::BookmarksForTag(
235 const BookmarkTag& tag, BookmarkOrdering ordering) {
236 DCHECK(!tag.empty());
237 std::set<BookmarkTag> tagset;
238 tagset.insert(tag);
239 return BookmarksForTags(tagset, ordering);
240 }
241
242 std::vector<BookmarkTag> BookmarkTagModel::TagsRelatedToTag(
243 const BookmarkTag& tag, BookmarkTagModel::TagOrdering ordering) {
244 DCHECK(loaded_);
245 std::map<BookmarkTag, unsigned int> tags;
246
247 if (tag.empty()) {
248 // Returns all the tags.
249 for (std::map<const BookmarkTag, std::set<const BookmarkNode*> >::iterator
250 it = tag_to_bookmarks_.begin(); it != tag_to_bookmarks_.end(); ++it) {
251 tags[it->first] = it->second.size();
252 }
253 } else {
254 std::vector<const BookmarkNode*> bookmarks(
255 BookmarksForTag(tag, UNSORTED_BOOKMARK_ORDERING));
256
257 for (std::vector<const BookmarkNode*>::iterator it = bookmarks.begin();
258 it != bookmarks.end(); ++it) {
259 std::set<BookmarkTag> subset(bookmark_to_tags_[*it]);
260 for (std::set<BookmarkTag>::iterator tag_it = subset.begin();
261 tag_it != subset.end(); ++tag_it) {
262 tags[*tag_it] += 1;
263 }
264 }
265 tags.erase(tag); // A tag is not related to itself.
266 }
267
268 // There is no keys() method on std::map. Nobody thought it might be useful?
269 std::vector<BookmarkTag> sorted_tags;
270 for (std::map<BookmarkTag, unsigned int>::iterator it = tags.begin();
271 it != tags.end(); ++it) {
272 sorted_tags.push_back(it->first);
273 }
274
275 switch (ordering) {
276 case UNSORTED_TAG_ORDERING:
277 case ALPHABETICAL_TAG_ORDERING:
278 break; // std::map is already sorting its keys.
279 case MOST_USED_TAG_ORDERING:
280 std::sort(sorted_tags.begin(), sorted_tags.end(), TagComparator(tags));
281 break;
282 default:
283 NOTREACHED();
284 }
285 return sorted_tags;
286 }
287
288 #pragma mark Private methods.
rlarocque 2013/10/11 18:06:25 What does this do?
noyau (Ping after 24h) 2013/10/14 23:59:39 Mac tools read those and use the text as a separat
289
290 std::set<BookmarkTag> BookmarkTagModel::ExtractTagsFromBookmark(
291 const BookmarkNode *bookmark) {
292 DCHECK(bookmark_model_);
293 // This is awful BTW. Metainfo is itself an encoded JSON, and here we decode
294 // another layer.
295
296 // Retrieve the encodedData from the bookmark. If there is no encoded data
297 // at all returns the name of all the ancestors as separate tags.
298 std::string encoded;
299 if (!bookmark->GetMetaInfo(TAG_KEY, &encoded)) {
300 std::set<BookmarkTag> tags;
301 const BookmarkNode* folder = bookmark->parent();
302 while (folder && folder->type() == BookmarkNode::FOLDER) {
303 BookmarkTag trimmed_tag = CollapseWhitespace(folder->GetTitle(), true);
304 if (!trimmed_tag.empty())
305 tags.insert(trimmed_tag);
306 folder = folder->parent();
307 }
308 return tags;
309 }
310
311 // Decode into a base::Value. If the data is not encoded properly as a list
312 // return an empty result.
313 JSONStringValueSerializer serializer(&encoded);
314 int error_code = 0;
315 std::string error_message;
316 base::Value* result = serializer.Deserialize(&error_code, &error_message);
noyau (Ping after 24h) 2013/10/11 12:09:21 Memory leak fixed (Thanks memory bot!)
317
318 if (error_code || !result->IsType(base::Value::TYPE_LIST))
319 return std::set<BookmarkTag>();
320
321 base::ListValue* list = NULL;
322 if (!result->GetAsList(&list) || list->empty())
323 return std::set<BookmarkTag>();
324
325 // Build the set.
326 std::set<BookmarkTag> return_value;
327
328 for (base::ListValue::iterator it = list->begin();
329 it != list->end(); ++it) {
330 base::Value* item = *it;
331 BookmarkTag tag;
332 if (!item->GetAsString(&tag))
333 continue;
334 return_value.insert(tag);
335 }
336 return return_value;
337 }
338
339 void BookmarkTagModel::ReplaceTagsOnBookmark(
340 const std::set<BookmarkTag>& tags, const BookmarkNode *bookmark) {
341 DCHECK(bookmark_model_);
342 DCHECK(loaded_);
343
344 // Build a ListValue.
345 std::vector<BookmarkTag> tag_vector(tags.begin(), tags.end());
346 base::ListValue list;
347 list.AppendStrings(tag_vector);
348
349 // Encodes it.
350 std::string encoded;
351 JSONStringValueSerializer serializer(&encoded);
352
353 // Pushes it in the bookmark's metainfo.
354 serializer.Serialize(list);
355 bookmark_model_->SetNodeMetaInfo(bookmark, TAG_KEY, encoded);
356 }
357
358 void BookmarkTagModel::Load() {
359 DCHECK(bookmark_model_);
360 DCHECK(!loaded_);
361 ui::TreeNodeIterator<const BookmarkNode> iterator(
362 bookmark_model_->root_node());
363 while (iterator.has_next())
364 LoadBookmark(iterator.Next());
365 loaded_ = true;
366 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
367 Loaded(this));
368 }
369
370 void BookmarkTagModel::LoadBookmark(const BookmarkNode* bookmark) {
371 DCHECK(bookmark_model_);
372 if (bookmark->is_url()) {
373 std::set<BookmarkTag> tags(ExtractTagsFromBookmark(bookmark));
374
375 bookmark_to_tags_[bookmark] = tags;
376 for (std::set<BookmarkTag>::iterator it = tags.begin();
377 it != tags.end(); ++it) {
378 tag_to_bookmarks_[*it].insert(bookmark);
379 }
380 }
381 }
382
383 void BookmarkTagModel::RemoveBookmark(const BookmarkNode* bookmark) {
384 DCHECK(bookmark_model_);
385 if (bookmark->is_url()) {
386 std::set<BookmarkTag> tags(bookmark_to_tags_[bookmark]);
387 bookmark_to_tags_.erase(bookmark);
388
389 for (std::set<BookmarkTag>::iterator it = tags.begin();
390 it != tags.end(); ++it) {
391 tag_to_bookmarks_[*it].erase(bookmark);
392 // Remove the tags no longer used.
393 if (!tag_to_bookmarks_[*it].size())
394 tag_to_bookmarks_.erase(*it);
395 }
396 }
397 }
398
399 #pragma mark BookmarkModelObserver.
400
401 // Invoked when the model has finished loading.
402 void BookmarkTagModel::Loaded(BookmarkModel* model, bool ids_reassigned) {
403 Load();
404 };
405
406 // Invoked from the destructor of the BookmarkModel.
407 void BookmarkTagModel::BookmarkModelBeingDeleted(BookmarkModel* model) {
408 DCHECK(bookmark_model_);
409 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
410 BookmarkTagModelBeingDeleted(this));
411 bookmark_model_ = NULL;
412 observers_.Clear();
413 }
414
415 // Invoked when a node has moved.
416 void BookmarkTagModel::BookmarkNodeMoved(BookmarkModel* model,
417 const BookmarkNode* old_parent,
418 int old_index,
419 const BookmarkNode* new_parent,
420 int new_index) {
421 DCHECK(loaded_);
422 const BookmarkNode* bookmark = new_parent->GetChild(new_index);
423 std::string encoded;
424 if (!bookmark->GetMetaInfo(TAG_KEY, &encoded)) {
425 // The bookmark moved and the system currently use its ancestors name as a
426 // poor approximation for tags.
427 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
428 OnWillChangeBookmarkTags(this, bookmark));
429 RemoveBookmark(bookmark);
430 LoadBookmark(bookmark);
431 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
432 BookmarkTagsChanged(this, bookmark));
433 }
434 };
435
436 // Invoked when a node has been added.
437 void BookmarkTagModel::BookmarkNodeAdded(BookmarkModel* model,
438 const BookmarkNode* parent,
439 int index) {
440 DCHECK(loaded_);
441 const BookmarkNode* bookmark = parent->GetChild(index);
442 if (!bookmark->is_url())
443 return;
444 LoadBookmark(bookmark);
445
446 if (!inhibit_change_notifications_)
447 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
448 BookmarkNodeAdded(this, bookmark));
449 }
450
451 // Invoked before a node is removed.
452 // |parent| the parent of the node that will be removed.
453 // |old_index| the index of the node about to be removed in |parent|.
454 // |node| is the node to be removed.
455 void BookmarkTagModel::OnWillRemoveBookmarks(BookmarkModel* model,
456 const BookmarkNode* parent,
457 int old_index,
458 const BookmarkNode* node) {
459 DCHECK(loaded_);
460 RemoveBookmark(node);
461 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
462 OnWillRemoveBookmarks(this, node));
463 }
464
465 // Invoked when a node has been removed, the item may still be starred though.
466 // |parent| the parent of the node that was removed.
467 // |old_index| the index of the removed node in |parent| before it was
468 // removed.
469 // |node| is the node that was removed.
470 void BookmarkTagModel::BookmarkNodeRemoved(BookmarkModel* model,
471 const BookmarkNode* parent,
472 int old_index,
473 const BookmarkNode* node) {
474 DCHECK(loaded_);
475 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
476 BookmarkNodeRemoved(this, node));
477 }
478
479 // Invoked before the title or url of a node is changed.
480 void BookmarkTagModel::OnWillChangeBookmarkNode(BookmarkModel* model,
481 const BookmarkNode* node) {
482 DCHECK(loaded_);
483 if (!inhibit_change_notifications_)
484 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
485 OnWillChangeBookmarkNode(this, node));
486 }
487
488 // Invoked when the title or url of a node changes.
489 void BookmarkTagModel::BookmarkNodeChanged(BookmarkModel* model,
490 const BookmarkNode* node) {
491 DCHECK(loaded_);
492 if (!inhibit_change_notifications_)
493 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
494 BookmarkNodeChanged(this, node));
495 }
496
497 // Invoked before the metainfo of a node is changed.
498 void BookmarkTagModel::OnWillChangeBookmarkMetaInfo(BookmarkModel* model,
499 const BookmarkNode* node) {
500 DCHECK(loaded_);
501 if (!inhibit_change_notifications_)
502 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
503 OnWillChangeBookmarkTags(this, node));
504 }
505
506 // Invoked when the metainfo on a node changes.
507 void BookmarkTagModel::BookmarkMetaInfoChanged(BookmarkModel* model,
508 const BookmarkNode* node) {
509 DCHECK(loaded_);
510 RemoveBookmark(node);
511 LoadBookmark(node);
512 if (!inhibit_change_notifications_)
513 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
514 BookmarkTagsChanged(this, node));
515 }
516
517 // Invoked when a favicon has been loaded or changed.
518 void BookmarkTagModel::BookmarkNodeFaviconChanged(BookmarkModel* model,
519 const BookmarkNode* node) {
520 DCHECK(loaded_);
521 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
522 BookmarkNodeFaviconChanged(this, node));
523 }
524
525 // Invoked before the direct children of |node| have been reordered in some
526 // way, such as sorted.
527 void BookmarkTagModel::OnWillReorderBookmarkNode(BookmarkModel* model,
528 const BookmarkNode* node) {
529 // This model doesn't care.
530 }
531
532 // Invoked when the children (just direct children, not descendants) of
533 // |node| have been reordered in some way, such as sorted.
534 void BookmarkTagModel::BookmarkNodeChildrenReordered(BookmarkModel* model,
535 const BookmarkNode* node) {
536 // This model doesn't care.
537 }
538
539 // Invoked before an extensive set of model changes is about to begin.
540 // This tells UI intensive observers to wait until the updates finish to
541 // update themselves.
542 // These methods should only be used for imports and sync.
543 // Observers should still respond to BookmarkNodeRemoved immediately,
544 // to avoid holding onto stale node pointers.
545 void BookmarkTagModel::ExtensiveBookmarkChangesBeginning(BookmarkModel* model) {
546 DCHECK(loaded_);
547 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
548 ExtensiveBookmarkChangesBeginning(this));
549 }
550
551 // Invoked after an extensive set of model changes has ended.
552 // This tells observers to update themselves if they were waiting for the
553 // update to finish.
554 void BookmarkTagModel::ExtensiveBookmarkChangesEnded(BookmarkModel* model) {
555 DCHECK(loaded_);
556 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
557 ExtensiveBookmarkChangesEnded(this));
558 }
559
560 // Invoked before all non-permanent bookmark nodes are removed.
561 void BookmarkTagModel::OnWillRemoveAllBookmarks(BookmarkModel* model) {
562 DCHECK(loaded_);
563 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
564 OnWillRemoveAllBookmarks(this));
565 }
566
567 // Invoked when all non-permanent bookmark nodes have been removed.
568 void BookmarkTagModel::BookmarkAllNodesRemoved(BookmarkModel* model){
569 DCHECK(loaded_);
570 tag_to_bookmarks_.clear();
571 bookmark_to_tags_.clear();
572 FOR_EACH_OBSERVER(BookmarkTagModelObserver, observers_,
573 BookmarkAllNodesRemoved(this));
574 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698