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