shortcuts_backend.cc revision 23730a6e56a168d1879203e4b3819bb36e3d8f1f
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/autocomplete/shortcuts_backend.h" 6 7#include <map> 8#include <string> 9#include <vector> 10 11#include "base/bind.h" 12#include "base/bind_helpers.h" 13#include "base/guid.h" 14#include "base/i18n/case_conversion.h" 15#include "base/strings/string_util.h" 16#include "chrome/browser/autocomplete/autocomplete_match.h" 17#include "chrome/browser/autocomplete/autocomplete_result.h" 18#include "chrome/browser/chrome_notification_types.h" 19#include "chrome/browser/history/history_notifications.h" 20#include "chrome/browser/history/history_service.h" 21#include "chrome/browser/history/shortcuts_database.h" 22#include "chrome/browser/omnibox/omnibox_log.h" 23#include "chrome/browser/profiles/profile.h" 24#include "chrome/common/chrome_constants.h" 25#include "content/public/browser/browser_thread.h" 26#include "content/public/browser/notification_details.h" 27#include "content/public/browser/notification_source.h" 28#include "extensions/common/extension.h" 29 30using content::BrowserThread; 31 32namespace { 33 34// Takes Match classification vector and removes all matched positions, 35// compacting repetitions if necessary. 36std::string StripMatchMarkers(const ACMatchClassifications& matches) { 37 ACMatchClassifications unmatched; 38 for (ACMatchClassifications::const_iterator i(matches.begin()); 39 i != matches.end(); ++i) { 40 AutocompleteMatch::AddLastClassificationIfNecessary( 41 &unmatched, i->offset, i->style & ~ACMatchClassification::MATCH); 42 } 43 return AutocompleteMatch::ClassificationsToString(unmatched); 44} 45 46// Normally shortcuts have the same match type as the original match they were 47// created from, but for certain match types, we should modify the shortcut's 48// type slightly to reflect that the origin of the shortcut is historical. 49AutocompleteMatch::Type GetTypeForShortcut(AutocompleteMatch::Type type) { 50 switch (type) { 51 case AutocompleteMatchType::URL_WHAT_YOU_TYPED: 52 case AutocompleteMatchType::NAVSUGGEST: 53 return AutocompleteMatchType::HISTORY_URL; 54 55 case AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED: 56 case AutocompleteMatchType::SEARCH_SUGGEST: 57 case AutocompleteMatchType::SEARCH_SUGGEST_ENTITY: 58 case AutocompleteMatchType::SEARCH_SUGGEST_INFINITE: 59 case AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED: 60 case AutocompleteMatchType::SEARCH_SUGGEST_PROFILE: 61 return AutocompleteMatchType::SEARCH_HISTORY; 62 63 default: 64 return type; 65 } 66} 67 68} // namespace 69 70 71// ShortcutsBackend ----------------------------------------------------------- 72 73ShortcutsBackend::ShortcutsBackend(Profile* profile, bool suppress_db) 74 : current_state_(NOT_INITIALIZED), 75 no_db_access_(suppress_db) { 76 if (!suppress_db) { 77 db_ = new history::ShortcutsDatabase( 78 profile->GetPath().Append(chrome::kShortcutsDatabaseName)); 79 } 80 // |profile| can be NULL in tests. 81 if (profile) { 82 notification_registrar_.Add( 83 this, chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED, 84 content::Source<Profile>(profile)); 85 notification_registrar_.Add( 86 this, chrome::NOTIFICATION_HISTORY_URLS_DELETED, 87 content::Source<Profile>(profile)); 88 } 89} 90 91bool ShortcutsBackend::Init() { 92 if (current_state_ != NOT_INITIALIZED) 93 return false; 94 95 if (no_db_access_) { 96 current_state_ = INITIALIZED; 97 return true; 98 } 99 100 current_state_ = INITIALIZING; 101 return BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 102 base::Bind(&ShortcutsBackend::InitInternal, this)); 103} 104 105bool ShortcutsBackend::DeleteShortcutsWithURL(const GURL& shortcut_url) { 106 return initialized() && DeleteShortcutsWithURL(shortcut_url, true); 107} 108 109void ShortcutsBackend::AddObserver(ShortcutsBackendObserver* obs) { 110 observer_list_.AddObserver(obs); 111} 112 113void ShortcutsBackend::RemoveObserver(ShortcutsBackendObserver* obs) { 114 observer_list_.RemoveObserver(obs); 115} 116 117void ShortcutsBackend::AddOrUpdateShortcut(const base::string16& text, 118 const AutocompleteMatch& match) { 119 const base::string16 text_lowercase(base::i18n::ToLower(text)); 120 const base::Time now(base::Time::Now()); 121 for (ShortcutMap::const_iterator it( 122 shortcuts_map_.lower_bound(text_lowercase)); 123 it != shortcuts_map_.end() && 124 StartsWith(it->first, text_lowercase, true); ++it) { 125 if (match.destination_url == it->second.match_core.destination_url) { 126 UpdateShortcut(history::ShortcutsDatabase::Shortcut( 127 it->second.id, text, MatchToMatchCore(match), now, 128 it->second.number_of_hits + 1)); 129 return; 130 } 131 } 132 AddShortcut(history::ShortcutsDatabase::Shortcut( 133 base::GenerateGUID(), text, MatchToMatchCore(match), now, 1)); 134} 135 136ShortcutsBackend::~ShortcutsBackend() { 137} 138 139// static 140history::ShortcutsDatabase::Shortcut::MatchCore 141 ShortcutsBackend::MatchToMatchCore(const AutocompleteMatch& match) { 142 return history::ShortcutsDatabase::Shortcut::MatchCore( 143 match.fill_into_edit, match.destination_url, match.contents, 144 StripMatchMarkers(match.contents_class), match.description, 145 StripMatchMarkers(match.description_class), match.transition, 146 GetTypeForShortcut(match.type), match.keyword); 147} 148 149void ShortcutsBackend::ShutdownOnUIThread() { 150 DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::UI) || 151 BrowserThread::CurrentlyOn(BrowserThread::UI)); 152 notification_registrar_.RemoveAll(); 153} 154 155void ShortcutsBackend::Observe(int type, 156 const content::NotificationSource& source, 157 const content::NotificationDetails& details) { 158 if (!initialized()) 159 return; 160 161 if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED) { 162 // When an extension is unloaded, we want to remove any Shortcuts associated 163 // with it. 164 DeleteShortcutsWithURL(content::Details<extensions::UnloadedExtensionInfo>( 165 details)->extension->url(), false); 166 return; 167 } 168 169 DCHECK_EQ(chrome::NOTIFICATION_HISTORY_URLS_DELETED, type); 170 const history::URLsDeletedDetails* deleted_details = 171 content::Details<const history::URLsDeletedDetails>(details).ptr(); 172 if (deleted_details->all_history) 173 DeleteAllShortcuts(); 174 const history::URLRows& rows(deleted_details->rows); 175 history::ShortcutsDatabase::ShortcutIDs shortcut_ids; 176 177 for (GuidMap::const_iterator it(guid_map_.begin()); it != guid_map_.end(); 178 ++it) { 179 if (std::find_if( 180 rows.begin(), rows.end(), history::URLRow::URLRowHasURL( 181 it->second->second.match_core.destination_url)) != rows.end()) 182 shortcut_ids.push_back(it->first); 183 } 184 DeleteShortcutsWithIDs(shortcut_ids); 185} 186 187void ShortcutsBackend::InitInternal() { 188 DCHECK(current_state_ == INITIALIZING); 189 db_->Init(); 190 history::ShortcutsDatabase::GuidToShortcutMap shortcuts; 191 db_->LoadShortcuts(&shortcuts); 192 temp_shortcuts_map_.reset(new ShortcutMap); 193 temp_guid_map_.reset(new GuidMap); 194 for (history::ShortcutsDatabase::GuidToShortcutMap::const_iterator it( 195 shortcuts.begin()); it != shortcuts.end(); ++it) { 196 (*temp_guid_map_)[it->first] = temp_shortcuts_map_->insert( 197 std::make_pair(base::i18n::ToLower(it->second.text), it->second)); 198 } 199 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 200 base::Bind(&ShortcutsBackend::InitCompleted, this)); 201} 202 203void ShortcutsBackend::InitCompleted() { 204 temp_guid_map_->swap(guid_map_); 205 temp_shortcuts_map_->swap(shortcuts_map_); 206 temp_shortcuts_map_.reset(NULL); 207 temp_guid_map_.reset(NULL); 208 current_state_ = INITIALIZED; 209 FOR_EACH_OBSERVER(ShortcutsBackendObserver, observer_list_, 210 OnShortcutsLoaded()); 211} 212 213bool ShortcutsBackend::AddShortcut( 214 const history::ShortcutsDatabase::Shortcut& shortcut) { 215 if (!initialized()) 216 return false; 217 DCHECK(guid_map_.find(shortcut.id) == guid_map_.end()); 218 guid_map_[shortcut.id] = shortcuts_map_.insert( 219 std::make_pair(base::i18n::ToLower(shortcut.text), shortcut)); 220 FOR_EACH_OBSERVER(ShortcutsBackendObserver, observer_list_, 221 OnShortcutsChanged()); 222 return no_db_access_ || 223 BrowserThread::PostTask( 224 BrowserThread::DB, FROM_HERE, 225 base::Bind(base::IgnoreResult( 226 &history::ShortcutsDatabase::AddShortcut), 227 db_.get(), shortcut)); 228} 229 230bool ShortcutsBackend::UpdateShortcut( 231 const history::ShortcutsDatabase::Shortcut& shortcut) { 232 if (!initialized()) 233 return false; 234 GuidMap::iterator it(guid_map_.find(shortcut.id)); 235 if (it != guid_map_.end()) 236 shortcuts_map_.erase(it->second); 237 guid_map_[shortcut.id] = shortcuts_map_.insert( 238 std::make_pair(base::i18n::ToLower(shortcut.text), shortcut)); 239 FOR_EACH_OBSERVER(ShortcutsBackendObserver, observer_list_, 240 OnShortcutsChanged()); 241 return no_db_access_ || 242 BrowserThread::PostTask( 243 BrowserThread::DB, FROM_HERE, 244 base::Bind(base::IgnoreResult( 245 &history::ShortcutsDatabase::UpdateShortcut), 246 db_.get(), shortcut)); 247} 248 249bool ShortcutsBackend::DeleteShortcutsWithIDs( 250 const history::ShortcutsDatabase::ShortcutIDs& shortcut_ids) { 251 if (!initialized()) 252 return false; 253 for (size_t i = 0; i < shortcut_ids.size(); ++i) { 254 GuidMap::iterator it(guid_map_.find(shortcut_ids[i])); 255 if (it != guid_map_.end()) { 256 shortcuts_map_.erase(it->second); 257 guid_map_.erase(it); 258 } 259 } 260 FOR_EACH_OBSERVER(ShortcutsBackendObserver, observer_list_, 261 OnShortcutsChanged()); 262 return no_db_access_ || 263 BrowserThread::PostTask( 264 BrowserThread::DB, FROM_HERE, 265 base::Bind(base::IgnoreResult( 266 &history::ShortcutsDatabase::DeleteShortcutsWithIDs), 267 db_.get(), shortcut_ids)); 268} 269 270bool ShortcutsBackend::DeleteShortcutsWithURL(const GURL& url, 271 bool exact_match) { 272 const std::string& url_spec = url.spec(); 273 history::ShortcutsDatabase::ShortcutIDs shortcut_ids; 274 for (GuidMap::iterator it(guid_map_.begin()); it != guid_map_.end(); ) { 275 if (exact_match ? 276 (it->second->second.match_core.destination_url == url) : 277 StartsWithASCII(it->second->second.match_core.destination_url.spec(), 278 url_spec, true)) { 279 shortcut_ids.push_back(it->first); 280 shortcuts_map_.erase(it->second); 281 guid_map_.erase(it++); 282 } else { 283 ++it; 284 } 285 } 286 FOR_EACH_OBSERVER(ShortcutsBackendObserver, observer_list_, 287 OnShortcutsChanged()); 288 return no_db_access_ || 289 BrowserThread::PostTask( 290 BrowserThread::DB, FROM_HERE, 291 base::Bind(base::IgnoreResult( 292 &history::ShortcutsDatabase::DeleteShortcutsWithURL), 293 db_.get(), url_spec)); 294} 295 296bool ShortcutsBackend::DeleteAllShortcuts() { 297 if (!initialized()) 298 return false; 299 shortcuts_map_.clear(); 300 guid_map_.clear(); 301 FOR_EACH_OBSERVER(ShortcutsBackendObserver, observer_list_, 302 OnShortcutsChanged()); 303 return no_db_access_ || 304 BrowserThread::PostTask( 305 BrowserThread::DB, FROM_HERE, 306 base::Bind(base::IgnoreResult( 307 &history::ShortcutsDatabase::DeleteAllShortcuts), 308 db_.get())); 309} 310