1 // Copyright (c) 2012 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_utils.h" 6 7 #include <utility> 8 9 #include "base/basictypes.h" 10 #include "base/files/file_path.h" 11 #include "base/i18n/case_conversion.h" 12 #include "base/i18n/string_search.h" 13 #include "base/metrics/histogram.h" 14 #include "base/prefs/pref_service.h" 15 #include "base/strings/string16.h" 16 #include "base/strings/utf_string_conversions.h" 17 #include "base/time/time.h" 18 #include "chrome/browser/bookmarks/bookmark_model.h" 19 #include "chrome/browser/bookmarks/bookmark_node_data.h" 20 #include "chrome/browser/history/query_parser.h" 21 #include "chrome/common/pref_names.h" 22 #include "components/user_prefs/pref_registry_syncable.h" 23 #include "content/public/browser/user_metrics.h" 24 #include "net/base/net_util.h" 25 #include "ui/base/events/event.h" 26 #include "ui/base/models/tree_node_iterator.h" 27 28 using base::Time; 29 using content::UserMetricsAction; 30 31 namespace { 32 33 void CloneBookmarkNodeImpl(BookmarkModel* model, 34 const BookmarkNodeData::Element& element, 35 const BookmarkNode* parent, 36 int index_to_add_at) { 37 if (element.is_url) { 38 model->AddURL(parent, index_to_add_at, element.title, element.url); 39 } else { 40 const BookmarkNode* new_folder = model->AddFolder(parent, 41 index_to_add_at, 42 element.title); 43 for (int i = 0; i < static_cast<int>(element.children.size()); ++i) 44 CloneBookmarkNodeImpl(model, element.children[i], new_folder, i); 45 } 46 } 47 48 // Comparison function that compares based on date modified of the two nodes. 49 bool MoreRecentlyModified(const BookmarkNode* n1, const BookmarkNode* n2) { 50 return n1->date_folder_modified() > n2->date_folder_modified(); 51 } 52 53 // Returns true if |text| contains each string in |words|. This is used when 54 // searching for bookmarks. 55 bool DoesBookmarkTextContainWords(const string16& text, 56 const std::vector<string16>& words) { 57 for (size_t i = 0; i < words.size(); ++i) { 58 if (!base::i18n::StringSearchIgnoringCaseAndAccents( 59 words[i], text, NULL, NULL)) { 60 return false; 61 } 62 } 63 return true; 64 } 65 66 // Returns true if |node|s title or url contains the strings in |words|. 67 // |languages| argument is user's accept-language setting to decode IDN. 68 bool DoesBookmarkContainWords(const BookmarkNode* node, 69 const std::vector<string16>& words, 70 const std::string& languages) { 71 return 72 DoesBookmarkTextContainWords(node->GetTitle(), words) || 73 DoesBookmarkTextContainWords(UTF8ToUTF16(node->url().spec()), words) || 74 DoesBookmarkTextContainWords(net::FormatUrl( 75 node->url(), languages, net::kFormatUrlOmitNothing, 76 net::UnescapeRule::NORMAL, NULL, NULL, NULL), words); 77 } 78 79 } // namespace 80 81 namespace bookmark_utils { 82 83 void CloneBookmarkNode(BookmarkModel* model, 84 const std::vector<BookmarkNodeData::Element>& elements, 85 const BookmarkNode* parent, 86 int index_to_add_at) { 87 if (!parent->is_folder() || !model) { 88 NOTREACHED(); 89 return; 90 } 91 for (size_t i = 0; i < elements.size(); ++i) 92 CloneBookmarkNodeImpl(model, elements[i], parent, index_to_add_at + i); 93 } 94 95 96 void CopyToClipboard(BookmarkModel* model, 97 const std::vector<const BookmarkNode*>& nodes, 98 bool remove_nodes) { 99 if (nodes.empty()) 100 return; 101 102 BookmarkNodeData(nodes).WriteToClipboard(); 103 104 if (remove_nodes) { 105 for (size_t i = 0; i < nodes.size(); ++i) { 106 int index = nodes[i]->parent()->GetIndexOf(nodes[i]); 107 if (index > -1) 108 model->Remove(nodes[i]->parent(), index); 109 } 110 } 111 } 112 113 void PasteFromClipboard(BookmarkModel* model, 114 const BookmarkNode* parent, 115 int index) { 116 if (!parent) 117 return; 118 119 BookmarkNodeData bookmark_data; 120 if (!bookmark_data.ReadFromClipboard()) 121 return; 122 123 if (index == -1) 124 index = parent->child_count(); 125 CloneBookmarkNode(model, bookmark_data.elements, parent, index); 126 } 127 128 bool CanPasteFromClipboard(const BookmarkNode* node) { 129 if (!node) 130 return false; 131 return BookmarkNodeData::ClipboardContainsBookmarks(); 132 } 133 134 // This is used with a tree iterator to skip subtrees which are not visible. 135 static bool PruneInvisibleFolders(const BookmarkNode* node) { 136 return !node->IsVisible(); 137 } 138 139 std::vector<const BookmarkNode*> GetMostRecentlyModifiedFolders( 140 BookmarkModel* model, 141 size_t max_count) { 142 std::vector<const BookmarkNode*> nodes; 143 ui::TreeNodeIterator<const BookmarkNode> 144 iterator(model->root_node(), PruneInvisibleFolders); 145 146 while (iterator.has_next()) { 147 const BookmarkNode* parent = iterator.Next(); 148 if (parent->is_folder() && parent->date_folder_modified() > base::Time()) { 149 if (max_count == 0) { 150 nodes.push_back(parent); 151 } else { 152 std::vector<const BookmarkNode*>::iterator i = 153 std::upper_bound(nodes.begin(), nodes.end(), parent, 154 &MoreRecentlyModified); 155 if (nodes.size() < max_count || i != nodes.end()) { 156 nodes.insert(i, parent); 157 while (nodes.size() > max_count) 158 nodes.pop_back(); 159 } 160 } 161 } // else case, the root node, which we don't care about or imported nodes 162 // (which have a time of 0). 163 } 164 165 if (nodes.size() < max_count) { 166 // Add the permanent nodes if there is space. The permanent nodes are the 167 // only children of the root_node. 168 const BookmarkNode* root_node = model->root_node(); 169 170 for (int i = 0; i < root_node->child_count(); ++i) { 171 const BookmarkNode* node = root_node->GetChild(i); 172 if (node->IsVisible() && 173 std::find(nodes.begin(), nodes.end(), node) == nodes.end()) { 174 nodes.push_back(node); 175 176 if (nodes.size() == max_count) 177 break; 178 } 179 } 180 } 181 return nodes; 182 } 183 184 void GetMostRecentlyAddedEntries(BookmarkModel* model, 185 size_t count, 186 std::vector<const BookmarkNode*>* nodes) { 187 ui::TreeNodeIterator<const BookmarkNode> iterator(model->root_node()); 188 while (iterator.has_next()) { 189 const BookmarkNode* node = iterator.Next(); 190 if (node->is_url()) { 191 std::vector<const BookmarkNode*>::iterator insert_position = 192 std::upper_bound(nodes->begin(), nodes->end(), node, 193 &MoreRecentlyAdded); 194 if (nodes->size() < count || insert_position != nodes->end()) { 195 nodes->insert(insert_position, node); 196 while (nodes->size() > count) 197 nodes->pop_back(); 198 } 199 } 200 } 201 } 202 203 bool MoreRecentlyAdded(const BookmarkNode* n1, const BookmarkNode* n2) { 204 return n1->date_added() > n2->date_added(); 205 } 206 207 void GetBookmarksContainingText(BookmarkModel* model, 208 const string16& text, 209 size_t max_count, 210 const std::string& languages, 211 std::vector<const BookmarkNode*>* nodes) { 212 std::vector<string16> words; 213 QueryParser parser; 214 parser.ParseQueryWords(base::i18n::ToLower(text), &words); 215 if (words.empty()) 216 return; 217 218 ui::TreeNodeIterator<const BookmarkNode> iterator(model->root_node()); 219 while (iterator.has_next()) { 220 const BookmarkNode* node = iterator.Next(); 221 if (node->is_url() && DoesBookmarkContainWords(node, words, languages)) { 222 nodes->push_back(node); 223 if (nodes->size() == max_count) 224 return; 225 } 226 } 227 } 228 229 bool DoesBookmarkContainText(const BookmarkNode* node, 230 const string16& text, 231 const std::string& languages) { 232 std::vector<string16> words; 233 QueryParser parser; 234 parser.ParseQueryWords(base::i18n::ToLower(text), &words); 235 if (words.empty()) 236 return false; 237 238 return (node->is_url() && DoesBookmarkContainWords(node, words, languages)); 239 } 240 241 void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) { 242 registry->RegisterBooleanPref( 243 prefs::kShowBookmarkBar, 244 false, 245 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); 246 registry->RegisterBooleanPref( 247 prefs::kEditBookmarksEnabled, 248 true, 249 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 250 registry->RegisterBooleanPref( 251 prefs::kShowAppsShortcutInBookmarkBar, 252 true, 253 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); 254 } 255 256 const BookmarkNode* GetParentForNewNodes( 257 const BookmarkNode* parent, 258 const std::vector<const BookmarkNode*>& selection, 259 int* index) { 260 const BookmarkNode* real_parent = parent; 261 262 if (selection.size() == 1 && selection[0]->is_folder()) 263 real_parent = selection[0]; 264 265 if (index) { 266 if (selection.size() == 1 && selection[0]->is_url()) { 267 *index = real_parent->GetIndexOf(selection[0]) + 1; 268 if (*index == 0) { 269 // Node doesn't exist in parent, add to end. 270 NOTREACHED(); 271 *index = real_parent->child_count(); 272 } 273 } else { 274 *index = real_parent->child_count(); 275 } 276 } 277 278 return real_parent; 279 } 280 281 void DeleteBookmarkFolders(BookmarkModel* model, 282 const std::vector<int64>& ids) { 283 // Remove the folders that were removed. This has to be done after all the 284 // other changes have been committed. 285 for (std::vector<int64>::const_iterator iter = ids.begin(); 286 iter != ids.end(); 287 ++iter) { 288 const BookmarkNode* node = model->GetNodeByID(*iter); 289 if (!node) 290 continue; 291 const BookmarkNode* parent = node->parent(); 292 model->Remove(parent, parent->GetIndexOf(node)); 293 } 294 } 295 296 void AddIfNotBookmarked(BookmarkModel* model, 297 const GURL& url, 298 const string16& title) { 299 std::vector<const BookmarkNode*> bookmarks; 300 model->GetNodesByURL(url, &bookmarks); 301 if (!bookmarks.empty()) 302 return; // Nothing to do, a bookmark with that url already exists. 303 304 content::RecordAction(content::UserMetricsAction("BookmarkAdded")); 305 const BookmarkNode* parent = model->GetParentForNewNodes(); 306 model->AddURL(parent, parent->child_count(), title, url); 307 } 308 309 void RemoveAllBookmarks(BookmarkModel* model, const GURL& url) { 310 std::vector<const BookmarkNode*> bookmarks; 311 model->GetNodesByURL(url, &bookmarks); 312 313 // Remove all the bookmarks. 314 for (size_t i = 0; i < bookmarks.size(); ++i) { 315 const BookmarkNode* node = bookmarks[i]; 316 int index = node->parent()->GetIndexOf(node); 317 if (index > -1) 318 model->Remove(node->parent(), index); 319 } 320 } 321 322 void RecordBookmarkFolderOpen(BookmarkLaunchLocation location) { 323 if (location == LAUNCH_DETACHED_BAR || location == LAUNCH_ATTACHED_BAR) 324 content::RecordAction(UserMetricsAction("ClickedBookmarkBarFolder")); 325 } 326 327 void RecordBookmarkLaunch(BookmarkLaunchLocation location) { 328 if (location == LAUNCH_DETACHED_BAR || location == LAUNCH_ATTACHED_BAR) 329 content::RecordAction(UserMetricsAction("ClickedBookmarkBarURLButton")); 330 331 UMA_HISTOGRAM_ENUMERATION("Bookmarks.LaunchLocation", location, LAUNCH_LIMIT); 332 } 333 334 void RecordAppsPageOpen(BookmarkLaunchLocation location) { 335 if (location == LAUNCH_DETACHED_BAR || location == LAUNCH_ATTACHED_BAR) { 336 content::RecordAction( 337 UserMetricsAction("ClickedBookmarkBarAppsShortcutButton")); 338 } 339 } 340 341 } // namespace bookmark_utils 342