OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "components/enhanced_bookmarks/enhanced_bookmark_model.h" | 5 #include "components/enhanced_bookmarks/enhanced_bookmark_model.h" |
6 | 6 |
7 #include <iomanip> | 7 #include <iomanip> |
8 #include <sstream> | 8 #include <sstream> |
9 | 9 |
10 #include "base/base64.h" | 10 #include "base/base64.h" |
11 #include "base/logging.h" | 11 #include "base/logging.h" |
| 12 #include "base/message_loop/message_loop_proxy.h" |
12 #include "base/rand_util.h" | 13 #include "base/rand_util.h" |
13 #include "components/bookmarks/browser/bookmark_model.h" | 14 #include "components/bookmarks/browser/bookmark_model.h" |
14 #include "components/bookmarks/browser/bookmark_node.h" | 15 #include "components/bookmarks/browser/bookmark_node.h" |
| 16 #include "components/enhanced_bookmarks/enhanced_bookmark_model_observer.h" |
15 #include "components/enhanced_bookmarks/proto/metadata.pb.h" | 17 #include "components/enhanced_bookmarks/proto/metadata.pb.h" |
| 18 #include "ui/base/models/tree_node_iterator.h" |
16 #include "url/gurl.h" | 19 #include "url/gurl.h" |
17 | 20 |
18 namespace { | 21 namespace { |
19 const char* kBookmarkBarId = "f_bookmarks_bar"; | 22 const char* kBookmarkBarId = "f_bookmarks_bar"; |
20 | 23 |
21 const char* kIdKey = "stars.id"; | 24 const char* kIdKey = "stars.id"; |
22 const char* kImageDataKey = "stars.imageData"; | 25 const char* kImageDataKey = "stars.imageData"; |
23 const char* kNoteKey = "stars.note"; | 26 const char* kNoteKey = "stars.note"; |
| 27 const char* kOldIdKey = "stars.oldId"; |
24 const char* kPageDataKey = "stars.pageData"; | 28 const char* kPageDataKey = "stars.pageData"; |
25 const char* kUserEditKey = "stars.userEdit"; | |
26 const char* kVersionKey = "stars.version"; | 29 const char* kVersionKey = "stars.version"; |
27 | 30 |
28 const char* kFolderPrefix = "ebf_"; | |
29 const char* kBookmarkPrefix = "ebc_"; | 31 const char* kBookmarkPrefix = "ebc_"; |
30 | 32 |
31 // Helper method for working with bookmark metainfo. | 33 // Helper method for working with bookmark metainfo. |
32 std::string DataForMetaInfoField(const BookmarkNode* node, | 34 std::string DataForMetaInfoField(const BookmarkNode* node, |
33 const std::string& field) { | 35 const std::string& field) { |
34 std::string value; | 36 std::string value; |
35 if (!node->GetMetaInfo(field, &value)) | 37 if (!node->GetMetaInfo(field, &value)) |
36 return std::string(); | 38 return std::string(); |
37 | 39 |
38 std::string decoded; | 40 std::string decoded; |
(...skipping 16 matching lines...) Expand all Loading... |
55 return false; | 57 return false; |
56 | 58 |
57 *out_url = url; | 59 *out_url = url; |
58 *width = info.width(); | 60 *width = info.width(); |
59 *height = info.height(); | 61 *height = info.height(); |
60 return true; | 62 return true; |
61 } | 63 } |
62 | 64 |
63 // Generate a random remote id, with a prefix that depends on whether the node | 65 // Generate a random remote id, with a prefix that depends on whether the node |
64 // is a folder or a bookmark. | 66 // is a folder or a bookmark. |
65 std::string GenerateRemoteId(bool is_folder) { | 67 std::string GenerateRemoteId() { |
66 std::stringstream random_id; | 68 std::stringstream random_id; |
67 // Add prefix depending on whether the node is a folder or not. | 69 random_id << kBookmarkPrefix; |
68 if (is_folder) | |
69 random_id << kFolderPrefix; | |
70 else | |
71 random_id << kBookmarkPrefix; | |
72 | 70 |
73 // Generate 32 digit hex string random suffix. | 71 // Generate 32 digit hex string random suffix. |
74 random_id << std::hex << std::setfill('0') << std::setw(16); | 72 random_id << std::hex << std::setfill('0') << std::setw(16); |
75 random_id << base::RandUint64() << base::RandUint64(); | 73 random_id << base::RandUint64() << base::RandUint64(); |
76 return random_id.str(); | 74 return random_id.str(); |
77 } | 75 } |
78 } // namespace | 76 } // namespace |
79 | 77 |
80 namespace enhanced_bookmarks { | 78 namespace enhanced_bookmarks { |
81 | 79 |
82 EnhancedBookmarkModel::EnhancedBookmarkModel(BookmarkModel* bookmark_model, | 80 EnhancedBookmarkModel::EnhancedBookmarkModel(BookmarkModel* bookmark_model, |
83 const std::string& version) | 81 const std::string& version) |
84 : bookmark_model_(bookmark_model), version_(version) { | 82 : bookmark_model_(bookmark_model), |
| 83 loaded_(false), |
| 84 weak_ptr_factory_(this), |
| 85 version_(version) { |
| 86 bookmark_model_->AddObserver(this); |
| 87 if (bookmark_model_->loaded()) { |
| 88 InitializeIdMap(); |
| 89 loaded_ = true; |
| 90 } |
85 } | 91 } |
86 | 92 |
87 EnhancedBookmarkModel::~EnhancedBookmarkModel() { | 93 EnhancedBookmarkModel::~EnhancedBookmarkModel() { |
88 } | 94 } |
89 | 95 |
| 96 void EnhancedBookmarkModel::ShutDown() { |
| 97 FOR_EACH_OBSERVER(EnhancedBookmarkModelObserver, |
| 98 observers_, |
| 99 EnhancedBookmarkModelShuttingDown()); |
| 100 weak_ptr_factory_.InvalidateWeakPtrs(); |
| 101 bookmark_model_->RemoveObserver(this); |
| 102 bookmark_model_ = NULL; |
| 103 } |
| 104 |
| 105 void EnhancedBookmarkModel::AddObserver( |
| 106 EnhancedBookmarkModelObserver* observer) { |
| 107 observers_.AddObserver(observer); |
| 108 } |
| 109 |
| 110 void EnhancedBookmarkModel::RemoveObserver( |
| 111 EnhancedBookmarkModelObserver* observer) { |
| 112 observers_.RemoveObserver(observer); |
| 113 } |
| 114 |
90 // Moves |node| to |new_parent| and inserts it at the given |index|. | 115 // Moves |node| to |new_parent| and inserts it at the given |index|. |
91 void EnhancedBookmarkModel::Move(const BookmarkNode* node, | 116 void EnhancedBookmarkModel::Move(const BookmarkNode* node, |
92 const BookmarkNode* new_parent, | 117 const BookmarkNode* new_parent, |
93 int index) { | 118 int index) { |
94 // TODO(rfevang): Update meta info placement fields. | |
95 bookmark_model_->Move(node, new_parent, index); | 119 bookmark_model_->Move(node, new_parent, index); |
96 } | 120 } |
97 | 121 |
98 // Adds a new folder node at the specified position. | 122 // Adds a new folder node at the specified position. |
99 const BookmarkNode* EnhancedBookmarkModel::AddFolder( | 123 const BookmarkNode* EnhancedBookmarkModel::AddFolder( |
100 const BookmarkNode* parent, | 124 const BookmarkNode* parent, |
101 int index, | 125 int index, |
102 const base::string16& title) { | 126 const base::string16& title) { |
103 BookmarkNode::MetaInfoMap meta_info; | 127 return bookmark_model_->AddFolder(parent, index, title); |
104 meta_info[kIdKey] = GenerateRemoteId(true); | |
105 | |
106 // TODO(rfevang): Set meta info placement fields. | |
107 return bookmark_model_->AddFolderWithMetaInfo( | |
108 parent, index, title, &meta_info); | |
109 } | 128 } |
110 | 129 |
111 // Adds a url at the specified position. | 130 // Adds a url at the specified position. |
112 const BookmarkNode* EnhancedBookmarkModel::AddURL( | 131 const BookmarkNode* EnhancedBookmarkModel::AddURL( |
113 const BookmarkNode* parent, | 132 const BookmarkNode* parent, |
114 int index, | 133 int index, |
115 const base::string16& title, | 134 const base::string16& title, |
116 const GURL& url, | 135 const GURL& url, |
117 const base::Time& creation_time) { | 136 const base::Time& creation_time) { |
118 BookmarkNode::MetaInfoMap meta_info; | 137 BookmarkNode::MetaInfoMap meta_info; |
119 meta_info[kIdKey] = GenerateRemoteId(false); | 138 meta_info[kIdKey] = GenerateRemoteId(); |
120 | |
121 // TODO(rfevang): Set meta info placement fields. | |
122 return bookmark_model_->AddURLWithCreationTimeAndMetaInfo( | 139 return bookmark_model_->AddURLWithCreationTimeAndMetaInfo( |
123 parent, index, title, url, creation_time, &meta_info); | 140 parent, index, title, url, creation_time, &meta_info); |
124 } | 141 } |
125 | 142 |
126 std::string EnhancedBookmarkModel::GetRemoteId(const BookmarkNode* node) { | 143 std::string EnhancedBookmarkModel::GetRemoteId(const BookmarkNode* node) { |
127 if (node == bookmark_model_->bookmark_bar_node()) | 144 if (node == bookmark_model_->bookmark_bar_node()) |
128 return kBookmarkBarId; | 145 return kBookmarkBarId; |
129 | 146 |
130 // Permanent nodes other than the bookmarks bar don't have ids. | |
131 DCHECK(!bookmark_model_->is_permanent_node(node)); | |
132 | |
133 std::string id; | 147 std::string id; |
134 if (!node->GetMetaInfo(kIdKey, &id) || id.empty()) | 148 if (!node->GetMetaInfo(kIdKey, &id)) |
135 return SetRemoteId(node); | 149 return std::string(); |
136 return id; | 150 return id; |
137 } | 151 } |
138 | 152 |
139 std::string EnhancedBookmarkModel::SetRemoteId(const BookmarkNode* node) { | 153 const BookmarkNode* EnhancedBookmarkModel::BookmarkForRemoteId( |
140 std::string remote_id = GenerateRemoteId(node->is_folder()); | 154 const std::string& remote_id) { |
141 SetMetaInfo(node, kIdKey, remote_id, false); | 155 IdToNodeMap::iterator it = id_map_.find(remote_id); |
142 return remote_id; | 156 if (it != id_map_.end()) |
| 157 return it->second; |
| 158 return NULL; |
143 } | 159 } |
144 | 160 |
145 void EnhancedBookmarkModel::SetDescription(const BookmarkNode* node, | 161 void EnhancedBookmarkModel::SetDescription(const BookmarkNode* node, |
146 const std::string& description) { | 162 const std::string& description) { |
147 SetMetaInfo(node, kNoteKey, description, true); | 163 SetMetaInfo(node, kNoteKey, description); |
148 } | 164 } |
149 | 165 |
150 std::string EnhancedBookmarkModel::GetDescription(const BookmarkNode* node) { | 166 std::string EnhancedBookmarkModel::GetDescription(const BookmarkNode* node) { |
151 // First, look for a custom note set by the user. | 167 // First, look for a custom note set by the user. |
152 std::string description; | 168 std::string description; |
153 if (node->GetMetaInfo(kNoteKey, &description) && !description.empty()) | 169 if (node->GetMetaInfo(kNoteKey, &description) && !description.empty()) |
154 return description; | 170 return description; |
155 | 171 |
156 // If none are present, return the snippet. | 172 // If none are present, return the snippet. |
157 return GetSnippet(node); | 173 return GetSnippet(node); |
(...skipping 24 matching lines...) Expand all Loading... |
182 info->set_height(height); | 198 info->set_height(height); |
183 data.set_allocated_original_info(info.release()); | 199 data.set_allocated_original_info(info.release()); |
184 | 200 |
185 std::string output; | 201 std::string output; |
186 bool result = data.SerializePartialToString(&output); | 202 bool result = data.SerializePartialToString(&output); |
187 if (!result) | 203 if (!result) |
188 return false; | 204 return false; |
189 | 205 |
190 std::string encoded; | 206 std::string encoded; |
191 base::Base64Encode(output, &encoded); | 207 base::Base64Encode(output, &encoded); |
192 SetMetaInfo(node, kImageDataKey, encoded, true); | 208 SetMetaInfo(node, kImageDataKey, encoded); |
193 // Ensure that the bookmark has a stars.id, to trigger the server processing. | |
194 GetRemoteId(node); | |
195 return true; | 209 return true; |
196 } | 210 } |
197 | 211 |
198 bool EnhancedBookmarkModel::GetOriginalImage(const BookmarkNode* node, | 212 bool EnhancedBookmarkModel::GetOriginalImage(const BookmarkNode* node, |
199 GURL* url, | 213 GURL* url, |
200 int* width, | 214 int* width, |
201 int* height) { | 215 int* height) { |
202 std::string decoded(DataForMetaInfoField(node, kImageDataKey)); | 216 std::string decoded(DataForMetaInfoField(node, kImageDataKey)); |
203 if (decoded == "") | 217 if (decoded == "") |
204 return false; | 218 return false; |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
244 return std::string(); | 258 return std::string(); |
245 | 259 |
246 return data.snippet(); | 260 return data.snippet(); |
247 } | 261 } |
248 | 262 |
249 void EnhancedBookmarkModel::SetVersionSuffix( | 263 void EnhancedBookmarkModel::SetVersionSuffix( |
250 const std::string& version_suffix) { | 264 const std::string& version_suffix) { |
251 version_suffix_ = version_suffix; | 265 version_suffix_ = version_suffix; |
252 } | 266 } |
253 | 267 |
| 268 void EnhancedBookmarkModel::BookmarkModelChanged() { |
| 269 } |
| 270 |
| 271 void EnhancedBookmarkModel::BookmarkModelLoaded(BookmarkModel* model, |
| 272 bool ids_reassigned) { |
| 273 InitializeIdMap(); |
| 274 FOR_EACH_OBSERVER( |
| 275 EnhancedBookmarkModelObserver, observers_, EnhancedBookmarkModelLoaded()); |
| 276 } |
| 277 |
| 278 void EnhancedBookmarkModel::BookmarkNodeAdded(BookmarkModel* model, |
| 279 const BookmarkNode* parent, |
| 280 int index) { |
| 281 const BookmarkNode* node = parent->GetChild(index); |
| 282 AddToIdMap(node); |
| 283 ScheduleResetDuplicateRemoteIds(); |
| 284 FOR_EACH_OBSERVER( |
| 285 EnhancedBookmarkModelObserver, observers_, EnhancedBookmarkAdded(node)); |
| 286 } |
| 287 |
| 288 void EnhancedBookmarkModel::BookmarkNodeRemoved( |
| 289 BookmarkModel* model, |
| 290 const BookmarkNode* parent, |
| 291 int old_index, |
| 292 const BookmarkNode* node, |
| 293 const std::set<GURL>& removed_urls) { |
| 294 std::string remote_id = GetRemoteId(node); |
| 295 id_map_.erase(remote_id); |
| 296 FOR_EACH_OBSERVER( |
| 297 EnhancedBookmarkModelObserver, observers_, EnhancedBookmarkRemoved(node)); |
| 298 } |
| 299 |
| 300 void EnhancedBookmarkModel::OnWillChangeBookmarkMetaInfo( |
| 301 BookmarkModel* model, |
| 302 const BookmarkNode* node) { |
| 303 prev_remote_id_ = GetRemoteId(node); |
| 304 } |
| 305 |
| 306 void EnhancedBookmarkModel::BookmarkMetaInfoChanged(BookmarkModel* model, |
| 307 const BookmarkNode* node) { |
| 308 std::string remote_id = GetRemoteId(node); |
| 309 if (remote_id != prev_remote_id_) { |
| 310 id_map_.erase(prev_remote_id_); |
| 311 if (!remote_id.empty()) { |
| 312 AddToIdMap(node); |
| 313 ScheduleResetDuplicateRemoteIds(); |
| 314 } |
| 315 FOR_EACH_OBSERVER( |
| 316 EnhancedBookmarkModelObserver, |
| 317 observers_, |
| 318 EnhancedBookmarkRemoteIdChanged(node, prev_remote_id_, remote_id)); |
| 319 } |
| 320 } |
| 321 |
| 322 void EnhancedBookmarkModel::BookmarkAllUserNodesRemoved( |
| 323 BookmarkModel* model, |
| 324 const std::set<GURL>& removed_urls) { |
| 325 id_map_.clear(); |
| 326 // Re-initialize so non-user nodes with remote ids are present in the map. |
| 327 InitializeIdMap(); |
| 328 FOR_EACH_OBSERVER(EnhancedBookmarkModelObserver, |
| 329 observers_, |
| 330 EnhancedBookmarkAllUserNodesRemoved()); |
| 331 } |
| 332 |
| 333 void EnhancedBookmarkModel::InitializeIdMap() { |
| 334 ui::TreeNodeIterator<const BookmarkNode> iterator( |
| 335 bookmark_model_->root_node()); |
| 336 while (iterator.has_next()) { |
| 337 AddToIdMap(iterator.Next()); |
| 338 } |
| 339 ScheduleResetDuplicateRemoteIds(); |
| 340 } |
| 341 |
| 342 void EnhancedBookmarkModel::AddToIdMap(const BookmarkNode* node) { |
| 343 std::string remote_id = GetRemoteId(node); |
| 344 if (remote_id.empty()) |
| 345 return; |
| 346 |
| 347 // Try to insert the node. |
| 348 std::pair<IdToNodeMap::iterator, bool> result = |
| 349 id_map_.insert(make_pair(remote_id, node)); |
| 350 if (!result.second) { |
| 351 // Some node already had the same remote id, so add both nodes to the |
| 352 // to-be-reset set. |
| 353 nodes_to_reset_[result.first->second] = remote_id; |
| 354 nodes_to_reset_[node] = remote_id; |
| 355 } |
| 356 } |
| 357 |
| 358 void EnhancedBookmarkModel::ScheduleResetDuplicateRemoteIds() { |
| 359 if (!nodes_to_reset_.empty()) { |
| 360 base::MessageLoopProxy::current()->PostTask( |
| 361 FROM_HERE, |
| 362 base::Bind(&EnhancedBookmarkModel::ResetDuplicateRemoteIds, |
| 363 weak_ptr_factory_.GetWeakPtr())); |
| 364 } |
| 365 } |
| 366 |
| 367 void EnhancedBookmarkModel::ResetDuplicateRemoteIds() { |
| 368 for (NodeToIdMap::iterator it = nodes_to_reset_.begin(); |
| 369 it != nodes_to_reset_.end(); |
| 370 ++it) { |
| 371 BookmarkNode::MetaInfoMap meta_info; |
| 372 meta_info[kIdKey] = ""; |
| 373 meta_info[kOldIdKey] = it->second; |
| 374 SetMultipleMetaInfo(it->first, meta_info); |
| 375 } |
| 376 nodes_to_reset_.clear(); |
| 377 } |
| 378 |
254 void EnhancedBookmarkModel::SetMetaInfo(const BookmarkNode* node, | 379 void EnhancedBookmarkModel::SetMetaInfo(const BookmarkNode* node, |
255 const std::string& field, | 380 const std::string& field, |
256 const std::string& value, | 381 const std::string& value) { |
257 bool user_edit) { | |
258 DCHECK(!bookmark_model_->is_permanent_node(node)); | 382 DCHECK(!bookmark_model_->is_permanent_node(node)); |
259 | 383 |
260 BookmarkNode::MetaInfoMap meta_info; | 384 BookmarkNode::MetaInfoMap meta_info; |
261 const BookmarkNode::MetaInfoMap* old_meta_info = node->GetMetaInfoMap(); | 385 const BookmarkNode::MetaInfoMap* old_meta_info = node->GetMetaInfoMap(); |
262 if (old_meta_info) | 386 if (old_meta_info) |
263 meta_info.insert(old_meta_info->begin(), old_meta_info->end()); | 387 meta_info.insert(old_meta_info->begin(), old_meta_info->end()); |
264 | 388 |
265 // Don't update anything if the value to set is already there. | 389 // Don't update anything if the value to set is already there. |
266 BookmarkNode::MetaInfoMap::iterator it = meta_info.find(field); | 390 BookmarkNode::MetaInfoMap::iterator it = meta_info.find(field); |
267 if (it != meta_info.end() && it->second == value) | 391 if (it != meta_info.end() && it->second == value) |
268 return; | 392 return; |
269 | 393 |
270 meta_info[field] = value; | 394 meta_info[field] = value; |
271 meta_info[kVersionKey] = GetVersionString(); | 395 meta_info[kVersionKey] = GetVersionString(); |
272 meta_info[kUserEditKey] = user_edit ? "true" : "false"; | |
273 bookmark_model_->SetNodeMetaInfoMap(node, meta_info); | 396 bookmark_model_->SetNodeMetaInfoMap(node, meta_info); |
274 } | 397 } |
275 | 398 |
276 std::string EnhancedBookmarkModel::GetVersionString() { | 399 std::string EnhancedBookmarkModel::GetVersionString() { |
277 if (version_suffix_.empty()) | 400 if (version_suffix_.empty()) |
278 return version_; | 401 return version_; |
279 return version_ + '/' + version_suffix_; | 402 return version_ + '/' + version_suffix_; |
280 } | 403 } |
281 | 404 |
| 405 void EnhancedBookmarkModel::SetMultipleMetaInfo( |
| 406 const BookmarkNode* node, |
| 407 BookmarkNode::MetaInfoMap meta_info) { |
| 408 DCHECK(!bookmark_model_->is_permanent_node(node)); |
| 409 |
| 410 // Don't update anything if every value is already set correctly. |
| 411 if (node->GetMetaInfoMap()) { |
| 412 bool changed = false; |
| 413 const BookmarkNode::MetaInfoMap* old_meta_info = node->GetMetaInfoMap(); |
| 414 for (BookmarkNode::MetaInfoMap::iterator it = meta_info.begin(); |
| 415 it != meta_info.end(); |
| 416 ++it) { |
| 417 BookmarkNode::MetaInfoMap::const_iterator old_field = |
| 418 old_meta_info->find(it->first); |
| 419 if (old_field == old_meta_info->end() || |
| 420 old_field->second != it->second) { |
| 421 changed = true; |
| 422 break; |
| 423 } |
| 424 } |
| 425 if (!changed) |
| 426 return; |
| 427 |
| 428 // Fill in the values that aren't changing |
| 429 meta_info.insert(old_meta_info->begin(), old_meta_info->end()); |
| 430 } |
| 431 |
| 432 meta_info[kVersionKey] = GetVersionString(); |
| 433 bookmark_model_->SetNodeMetaInfoMap(node, meta_info); |
| 434 } |
| 435 |
282 bool EnhancedBookmarkModel::SetAllImages(const BookmarkNode* node, | 436 bool EnhancedBookmarkModel::SetAllImages(const BookmarkNode* node, |
283 const GURL& image_url, | 437 const GURL& image_url, |
284 int image_width, | 438 int image_width, |
285 int image_height, | 439 int image_height, |
286 const GURL& thumbnail_url, | 440 const GURL& thumbnail_url, |
287 int thumbnail_width, | 441 int thumbnail_width, |
288 int thumbnail_height) { | 442 int thumbnail_height) { |
289 DCHECK(node->is_url()); | 443 DCHECK(node->is_url()); |
290 DCHECK(image_url.is_valid() || image_url.is_empty()); | 444 DCHECK(image_url.is_valid() || image_url.is_empty()); |
291 DCHECK(thumbnail_url.is_valid() || thumbnail_url.is_empty()); | 445 DCHECK(thumbnail_url.is_valid() || thumbnail_url.is_empty()); |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
332 if (!result) | 486 if (!result) |
333 return false; | 487 return false; |
334 | 488 |
335 std::string encoded; | 489 std::string encoded; |
336 base::Base64Encode(output, &encoded); | 490 base::Base64Encode(output, &encoded); |
337 bookmark_model_->SetNodeMetaInfo(node, kImageDataKey, encoded); | 491 bookmark_model_->SetNodeMetaInfo(node, kImageDataKey, encoded); |
338 return true; | 492 return true; |
339 } | 493 } |
340 | 494 |
341 } // namespace enhanced_bookmarks | 495 } // namespace enhanced_bookmarks |
OLD | NEW |