First attempt on Shortcuts provider - previously selected URLs are given boost in this provider.
Still not implemented: permanent storage of the data, so each restart of the Chrome starts the provider with a clean slate.
The scores will probably need to be adjusted as well.
TEST=unit-tested
BUG=none
Review URL: http://codereview.chromium.org/7035018

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@90980 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/autocomplete/autocomplete.cc b/chrome/browser/autocomplete/autocomplete.cc
index 9fe2688..40ad6d26 100644
--- a/chrome/browser/autocomplete/autocomplete.cc
+++ b/chrome/browser/autocomplete/autocomplete.cc
@@ -22,6 +22,7 @@
 #include "chrome/browser/autocomplete/history_url_provider.h"
 #include "chrome/browser/autocomplete/keyword_provider.h"
 #include "chrome/browser/autocomplete/search_provider.h"
+#include "chrome/browser/autocomplete/shortcuts_provider.h"
 #include "chrome/browser/bookmarks/bookmark_model.h"
 #include "chrome/browser/external_protocol/external_protocol_handler.h"
 #include "chrome/browser/net/url_fixer_upper.h"
@@ -791,6 +792,9 @@
                          switches::kDisableHistoryQuickProvider);
   if (hqp_enabled)
     providers_.push_back(new HistoryQuickProvider(this, profile));
+  if (CommandLine::ForCurrentProcess()->HasSwitch(
+      switches::kEnableShortcutsProvider))
+    providers_.push_back(new ShortcutsProvider(this, profile));
   if (!CommandLine::ForCurrentProcess()->HasSwitch(
       switches::kDisableHistoryURLProvider))
     providers_.push_back(new HistoryURLProvider(this, profile));
diff --git a/chrome/browser/autocomplete/shortcuts_provider.cc b/chrome/browser/autocomplete/shortcuts_provider.cc
new file mode 100644
index 0000000..14d9102
--- /dev/null
+++ b/chrome/browser/autocomplete/shortcuts_provider.cc
@@ -0,0 +1,399 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/autocomplete/shortcuts_provider.h"
+
+#include <algorithm>
+#include <cmath>
+#include <map>
+#include <vector>
+
+#include "base/i18n/break_iterator.h"
+#include "base/i18n/case_conversion.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "base/time.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/history/history.h"
+#include "chrome/browser/history/history_notifications.h"
+#include "chrome/browser/net/url_fixer_upper.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/url_constants.h"
+#include "content/common/notification_details.h"
+#include "content/common/notification_source.h"
+#include "content/common/notification_type.h"
+#include "googleurl/src/url_parse.h"
+#include "googleurl/src/url_util.h"
+#include "net/base/escape.h"
+#include "net/base/net_util.h"
+
+namespace {
+// Adds match at the end if and only if its style is different from the last
+// match.
+void AddLastMatchIfNeeded(ACMatchClassifications* matches,
+                          int position,
+                          int style) {
+  DCHECK(matches);
+  if (matches->empty() || matches->back().style != style)
+    matches->push_back(ACMatchClassification(position, style));
+}
+
+// Takes Match classification vector and removes all matched positions,
+// compacting repetitions if necessary.
+void StripMatchMarkersFromClassifications(ACMatchClassifications* matches) {
+  DCHECK(matches);
+  ACMatchClassifications unmatched;
+  for (ACMatchClassifications::iterator i = matches->begin();
+       i != matches->end(); ++i) {
+    AddLastMatchIfNeeded(&unmatched, i->offset,
+        i->style & ~ACMatchClassification::MATCH);
+  }
+  matches->swap(unmatched);
+}
+
+class RemoveMatchPredicate {
+ public:
+  explicit RemoveMatchPredicate(const std::set<GURL>& urls)
+      : urls_(urls) {
+  }
+  bool operator()(AutocompleteMatch match) {
+    return urls_.find(match.destination_url) != urls_.end();
+  }
+ private:
+  // Lifetime of the object is less than the lifetime of passed |urls|, so
+  // it is safe to store reference.
+  const std::set<GURL>& urls_;
+};
+
+}  // namespace
+
+// A match needs to score at least 1200 to be default, so set the max below
+// this. For ease of unit testing, make it divisible by 4 (since some tests
+// check for half or quarter of the max score).
+const int ShortcutsProvider::kMaxScore = 1196;
+
+ShortcutsProvider::ShortcutsProvider(ACProviderListener* listener,
+                                     Profile* profile)
+    : AutocompleteProvider(listener, profile, "ShortcutsProvider"),
+      languages_(profile_->GetPrefs()->GetString(prefs::kAcceptLanguages)) {
+  notification_registrar_.Add(this, NotificationType::OMNIBOX_OPENED_URL,
+                              Source<Profile>(profile));
+  notification_registrar_.Add(this, NotificationType::HISTORY_URLS_DELETED,
+                              Source<Profile>(profile));
+}
+
+ShortcutsProvider::~ShortcutsProvider() {
+}
+
+void ShortcutsProvider::Start(const AutocompleteInput& input,
+                              bool minimal_changes) {
+  matches_.clear();
+
+  if (input.type() == AutocompleteInput::INVALID)
+    return;
+
+  base::TimeTicks start_time = base::TimeTicks::Now();
+  GetMatches(input);
+  if (input.text().length() < 6) {
+    base::TimeTicks end_time = base::TimeTicks::Now();
+    std::string name = "ShortcutsProvider.QueryIndexTime." +
+        base::IntToString(input.text().size());
+    base::Histogram* counter = base::Histogram::FactoryGet(
+        name, 1, 1000, 50, base::Histogram::kUmaTargetedHistogramFlag);
+    counter->Add(static_cast<int>((end_time - start_time).InMilliseconds()));
+  }
+  UpdateStarredStateOfMatches();
+}
+
+void ShortcutsProvider::DeleteMatch(const AutocompleteMatch& match) {
+  // When a user deletes a match, he probably means for the URL to disappear out
+  // of history entirely. So nuke all shortcuts that map to this URL.
+  std::set<GURL> url;
+  url.insert(match.destination_url);
+  // Immediately delete matches and shortcuts with the URL, so we can update the
+  // controller synchronously.
+  DeleteShortcutsWithURLs(url);
+  DeleteMatchesWithURLs(url);
+
+  // Delete the match from the history DB. This will eventually result in a
+  // second call to DeleteShortcutsWithURLs(), which is harmless.
+  HistoryService* const history_service =
+      profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
+
+  DCHECK(history_service && match.destination_url.is_valid());
+  history_service->DeleteURL(match.destination_url);
+}
+
+void ShortcutsProvider::Observe(NotificationType type,
+                                const NotificationSource& source,
+                                const NotificationDetails& details) {
+  if (type == NotificationType::HISTORY_URLS_DELETED) {
+    const std::set<GURL>& urls =
+        Details<const history::URLsDeletedDetails>(details)->urls;
+    DeleteShortcutsWithURLs(urls);
+    return;
+  }
+
+  DCHECK(type == NotificationType::OMNIBOX_OPENED_URL);
+
+  AutocompleteLog* log = Details<AutocompleteLog>(details).ptr();
+  string16 text_lowercase(base::i18n::ToLower(log->text));
+
+  int number_of_hits = 1;
+  const AutocompleteMatch& match(log->result.match_at(log->selected_index));
+  for (ShortcutMap::iterator it = FindFirstMatch(text_lowercase);
+       it != shortcuts_map_.end() &&
+           StartsWith(it->first, text_lowercase, true); ++it) {
+    if (match.destination_url == it->second.url) {
+      number_of_hits = it->second.number_of_hits + 1;
+      shortcuts_map_.erase(it);
+      break;
+    }
+  }
+  Shortcut shortcut(log->text, match.destination_url, match.contents,
+      match.contents_class, match.description, match.description_class);
+  shortcut.number_of_hits = number_of_hits;
+  shortcuts_map_.insert(std::make_pair(text_lowercase, shortcut));
+  // TODO(georgey): Update db here.
+}
+
+void ShortcutsProvider::DeleteMatchesWithURLs(const std::set<GURL>& urls) {
+  remove_if(matches_.begin(), matches_.end(), RemoveMatchPredicate(urls));
+  listener_->OnProviderUpdate(true);
+}
+
+void ShortcutsProvider::DeleteShortcutsWithURLs(const std::set<GURL>& urls) {
+  for (ShortcutMap::iterator it = shortcuts_map_.begin();
+       it != shortcuts_map_.end();) {
+    if (urls.find(it->second.url) != urls.end())
+      shortcuts_map_.erase(it++);
+    else
+      ++it;
+  }
+  // TODO(georgey): Update db here.
+}
+
+void ShortcutsProvider::GetMatches(const AutocompleteInput& input) {
+  // Get the URLs from the shortcuts database with keys that partially or
+  // completely match the search term.
+  string16 term_string(base::i18n::ToLower(input.text()));
+  DCHECK(!term_string.empty());
+
+  for (ShortcutMap::iterator it = FindFirstMatch(term_string);
+       it != shortcuts_map_.end() &&
+            StartsWith(it->first, term_string, true); ++it)
+    matches_.push_back(ShortcutToACMatch(input, term_string, it));
+  std::partial_sort(matches_.begin(),
+      matches_.begin() +
+          std::min(AutocompleteProvider::kMaxMatches, matches_.size()),
+      matches_.end(), &AutocompleteMatch::MoreRelevant);
+  if (matches_.size() > AutocompleteProvider::kMaxMatches) {
+    matches_.erase(matches_.begin() + AutocompleteProvider::kMaxMatches,
+                   matches_.end());
+  }
+}
+
+AutocompleteMatch ShortcutsProvider::ShortcutToACMatch(
+    const AutocompleteInput& input,
+    const string16& term_string,
+    ShortcutMap::iterator it) {
+  AutocompleteMatch match(this, CalculateScore(term_string, it->second),
+                          true, AutocompleteMatch::HISTORY_TITLE);
+  match.destination_url = it->second.url;
+  DCHECK(match.destination_url.is_valid());
+  match.fill_into_edit = UTF8ToUTF16(it->second.url.spec());
+
+  match.contents = it->second.contents;
+  match.contents_class = ClassifyAllMatchesInString(term_string,
+                                                    match.contents,
+                                                    it->second.contents_class);
+
+  match.description = it->second.description;
+  match.description_class = ClassifyAllMatchesInString(
+      term_string, match.description, it->second.description_class);
+
+  return match;
+}
+
+// static
+ACMatchClassifications ShortcutsProvider::ClassifyAllMatchesInString(
+    const string16& find_text,
+    const string16& text,
+    const ACMatchClassifications& original_matches) {
+  DCHECK(!original_matches.empty());
+  DCHECK(!find_text.empty());
+
+  base::i18n::BreakIterator term_iter(find_text,
+                                      base::i18n::BreakIterator::BREAK_WORD);
+  if (!term_iter.Init())
+    return original_matches;
+
+  std::vector<string16> terms;
+  while (term_iter.Advance()) {
+    if (term_iter.IsWord())
+      terms.push_back(term_iter.GetString());
+  }
+  // Sort the strings so that longer strings appear after their prefixes, if
+  // any.
+  std::sort(terms.begin(), terms.end());
+
+  // Find the earliest match of any word in |find_text| in the |text|. Add to
+  // |matches|. Move pointer after match. Repeat until all matches are found.
+  string16 text_lowercase(base::i18n::ToLower(text));
+  ACMatchClassifications matches;
+  // |matches| should start at the position 0, if the matched text start from
+  // the position 0, this will be poped from the vector further down.
+  matches.push_back(ACMatchClassification(0, ACMatchClassification::NONE));
+  for (size_t last_position = 0; last_position < text_lowercase.length();) {
+    size_t match_start = text_lowercase.length();
+    size_t match_end = last_position;
+
+    for (size_t i = 0; i < terms.size(); ++i) {
+      size_t match = text_lowercase.find(terms[i], last_position);
+      // Use <= in conjunction with the sort() call above so that longer strings
+      // are matched in preference to their prefixes.
+      if (match != string16::npos && match <= match_start) {
+        match_start = match;
+        match_end = match + terms[i].length();
+      }
+    }
+
+    if (match_start >= match_end)
+      break;
+
+    // Collapse adjacent ranges into one.
+    if (!matches.empty() && matches.back().offset == match_start)
+      matches.pop_back();
+
+    AddLastMatchIfNeeded(&matches, match_start, ACMatchClassification::MATCH);
+    if (match_end < text_lowercase.length())
+      AddLastMatchIfNeeded(&matches, match_end, ACMatchClassification::NONE);
+
+    last_position = match_end;
+  }
+
+  // Merge matches with highlight data.
+  if (matches.empty())
+    return original_matches;
+
+  ACMatchClassifications output;
+  for (ACMatchClassifications::const_iterator i = original_matches.begin(),
+       j = matches.begin(); i != original_matches.end();) {
+    AddLastMatchIfNeeded(&output, std::max(i->offset, j->offset),
+                         i->style | j->style);
+    if ((j + 1) == matches.end() || (((i + 1) != original_matches.end()) &&
+        ((j + 1)->offset > (i + 1)->offset)))
+      ++i;
+    else
+      ++j;
+  }
+
+  return output;
+}
+
+ShortcutsProvider::ShortcutMap::iterator ShortcutsProvider::FindFirstMatch(
+    const string16& keyword) {
+  ShortcutMap::iterator it = shortcuts_map_.lower_bound(keyword);
+  // Lower bound not necessarily matches the keyword, check for item pointed by
+  // the lower bound iterator to at least start with keyword.
+  return ((it == shortcuts_map_.end()) ||
+    StartsWith(it->first, keyword, true)) ? it : shortcuts_map_.end();
+}
+
+// static
+int ShortcutsProvider::CalculateScore(const string16& terms,
+                                      const Shortcut& shortcut) {
+  DCHECK(!terms.empty());
+  DCHECK_LE(terms.length(), shortcut.text.length());
+
+  // The initial score is based on how much of the shortcut the user has typed.
+  double base_score = kMaxScore * static_cast<double>(terms.length()) /
+      shortcut.text.length();
+
+  // Then we decay this by half each week.
+  const double kLn2 = 0.6931471805599453;
+  base::TimeDelta time_passed = base::Time::Now() - shortcut.last_access_time;
+  // Clamp to 0 in case time jumps backwards (e.g. due to DST).
+  double decay_exponent = std::max(0.0, kLn2 * static_cast<double>(
+      time_passed.InMicroseconds()) / base::Time::kMicrosecondsPerWeek);
+
+  // We modulate the decay factor based on how many times the shortcut has been
+  // used. Newly created shortcuts decay at full speed; otherwise, decaying by
+  // half takes |n| times as much time, where n increases by
+  // (1.0 / each 5 additional hits), up to a maximum of 5x as long.
+  const double kMaxDecaySpeedDivisor = 5.0;
+  const double kNumUsesPerDecaySpeedDivisorIncrement = 5.0;
+  double decay_divisor = std::min(kMaxDecaySpeedDivisor,
+      (shortcut.number_of_hits + kNumUsesPerDecaySpeedDivisorIncrement - 1) /
+      kNumUsesPerDecaySpeedDivisorIncrement);
+
+  return static_cast<int>((base_score / exp(decay_exponent / decay_divisor)) +
+      0.5);
+}
+
+// static
+string16 ShortcutsProvider::SpansToString(const ACMatchClassifications& value) {
+  string16 spans;
+  string16 comma(ASCIIToUTF16(","));
+  for (size_t i = 0; i < value.size(); ++i) {
+    if (i)
+      spans.append(comma);
+    spans.append(base::IntToString16(value[i].offset));
+    spans.append(comma);
+    spans.append(base::IntToString16(value[i].style));
+  }
+  return spans;
+}
+
+// static
+ACMatchClassifications ShortcutsProvider::SpansFromString(
+    const string16& value) {
+  ACMatchClassifications spans;
+  std::vector<string16> tokens;
+  Tokenize(value, ASCIIToUTF16(","), &tokens);
+  // The number of tokens should be even.
+  if ((tokens.size() & 1) == 1) {
+    NOTREACHED();
+    return spans;
+  }
+  for (size_t i = 0; i < tokens.size(); i += 2) {
+    int span_start = 0;
+    int span_type = ACMatchClassification::NONE;
+    if (!base::StringToInt(tokens[i], &span_start) ||
+        !base::StringToInt(tokens[i + 1], &span_type)) {
+      NOTREACHED();
+      return spans;
+    }
+    spans.push_back(ACMatchClassification(span_start, span_type));
+  }
+  return spans;
+}
+
+ShortcutsProvider::Shortcut::Shortcut(
+    const string16& text,
+    const GURL& url,
+    const string16& contents,
+    const ACMatchClassifications& in_contents_class,
+    const string16& description,
+    const ACMatchClassifications& in_description_class)
+    : text(text),
+      url(url),
+      contents(contents),
+      contents_class(in_contents_class),
+      description(description),
+      description_class(in_description_class),
+      last_access_time(base::Time::Now()),
+      number_of_hits(1) {
+  StripMatchMarkersFromClassifications(&contents_class);
+  StripMatchMarkersFromClassifications(&description_class);
+}
+
+ShortcutsProvider::Shortcut::Shortcut()
+    : last_access_time(base::Time::Now()),
+      number_of_hits(0) {
+}
diff --git a/chrome/browser/autocomplete/shortcuts_provider.h b/chrome/browser/autocomplete/shortcuts_provider.h
new file mode 100644
index 0000000..74fd3a5
--- /dev/null
+++ b/chrome/browser/autocomplete/shortcuts_provider.h
@@ -0,0 +1,130 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_AUTOCOMPLETE_SHORTCUTS_PROVIDER_H_
+#define CHROME_BROWSER_AUTOCOMPLETE_SHORTCUTS_PROVIDER_H_
+#pragma once
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/time.h"
+#include "chrome/browser/autocomplete/autocomplete_match.h"
+#include "chrome/browser/autocomplete/history_provider.h"
+#include "content/common/notification_observer.h"
+#include "content/common/notification_registrar.h"
+
+class Profile;
+struct AutocompleteLog;
+
+// Provider of recently autocompleted links. Provides autocomplete suggestions
+// from previously selected suggestions. The more often a user selects a
+// suggestion for a given search term the higher will be that suggestion's
+// ranking for future uses of that search term.
+class ShortcutsProvider : public AutocompleteProvider,
+                          public NotificationObserver {
+ public:
+  ShortcutsProvider(ACProviderListener* listener, Profile* profile);
+  virtual ~ShortcutsProvider();
+
+  // Performs the autocompletion synchronously. Since no asynch completion is
+  // performed |minimal_changes| is ignored.
+  virtual void Start(const AutocompleteInput& input,
+                     bool minimal_changes) OVERRIDE;
+
+  virtual void DeleteMatch(const AutocompleteMatch& match) OVERRIDE;
+
+ private:
+  friend class ShortcutsProviderTest;
+  FRIEND_TEST_ALL_PREFIXES(ShortcutsProviderTest, ClassifyAllMatchesInString);
+  FRIEND_TEST_ALL_PREFIXES(ShortcutsProviderTest, CalculateScore);
+  FRIEND_TEST_ALL_PREFIXES(ShortcutsProviderTest, DeleteMatch);
+
+  // The following struct encapsulates one previously selected omnibox shortcut.
+  struct Shortcut {
+    Shortcut(const string16& text,
+             const GURL& url,
+             const string16& contents,
+             const ACMatchClassifications& contents_class,
+             const string16& description,
+             const ACMatchClassifications& description_class);
+    // Required for STL, we don't use this directly.
+    Shortcut();
+
+    string16 text;  // The user's original input string.
+    GURL url;       // The corresponding destination URL.
+
+    // Contents and description from the original match, along with their
+    // corresponding markup. We need these in order to correctly mark which
+    // parts are URLs, dim, etc. However, we strip all MATCH classifications
+    // from these since we'll mark the matching portions ourselves as we match
+    // the user's current typing against these Shortcuts.
+    string16 contents;
+    ACMatchClassifications contents_class;
+    string16 description;
+    ACMatchClassifications description_class;
+
+    base::Time last_access_time;  // Last time shortcut was selected.
+    int number_of_hits;           // How many times shortcut was selected.
+  };
+
+  // Maps the original match (|text| in the Shortcut) to Shortcut for quick
+  // search.
+  typedef std::multimap<string16, Shortcut> ShortcutMap;
+
+  // Clamp relevance scores to ensure none of our matches will become the
+  // default. This prevents us from having to worry about inline autocompletion.
+  static const int kMaxScore;
+
+  // NotificationObserver:
+  virtual void Observe(NotificationType type,
+                       const NotificationSource& source,
+                       const NotificationDetails& details) OVERRIDE;
+
+  void DeleteMatchesWithURLs(const std::set<GURL>& urls);
+  void DeleteShortcutsWithURLs(const std::set<GURL>& urls);
+
+  // Performs the autocomplete matching and scoring.
+  void GetMatches(const AutocompleteInput& input);
+
+  AutocompleteMatch ShortcutToACMatch(const AutocompleteInput& input,
+                                      const string16& terms,
+                                      ShortcutMap::iterator it);
+
+  // Given |text| and a corresponding base set of classifications
+  // |original_class|, adds ACMatchClassification::MATCH markers for all
+  // instances of the words from |find_text| within |text| and returns the
+  // resulting classifications.
+  //
+  // For example, given the |text|
+  // "Sports and News at sports.somesite.com - visit us!" and |original_class|
+  // {{0, NONE}, {18, URL}, {37, NONE}} (marking "sports.somesite.com" as a
+  // URL), calling with |find_text| set to "sp ew" would return
+  // {{0, MATCH}, {2, NONE}, {12, MATCH}, {14, NONE}, {18, URL|MATCH},
+  // {20, URL}, {37, NONE}}
+  static ACMatchClassifications ClassifyAllMatchesInString(
+      const string16& find_text,
+      const string16& text,
+      const ACMatchClassifications& original_class);
+
+  // Returns iterator to first item in |shortcuts_map_| matching |keyword|.
+  // Returns shortcuts_map_.end() if there are no matches.
+  ShortcutMap::iterator FindFirstMatch(const string16& keyword);
+
+  static int CalculateScore(const string16& terms, const Shortcut& shortcut);
+
+  // Helpers dealing with database update.
+  // Converts spans vector to comma-separated string.
+  static string16 SpansToString(const ACMatchClassifications& value);
+  // Coverts comma-separated unsigned integer values into spans vector.
+  static ACMatchClassifications SpansFromString(const string16& value);
+
+  std::string languages_;
+  NotificationRegistrar notification_registrar_;
+  ShortcutMap shortcuts_map_;
+};
+
+#endif  // CHROME_BROWSER_AUTOCOMPLETE_SHORTCUTS_PROVIDER_H_
diff --git a/chrome/browser/autocomplete/shortcuts_provider_unittest.cc b/chrome/browser/autocomplete/shortcuts_provider_unittest.cc
new file mode 100644
index 0000000..7db7df3
--- /dev/null
+++ b/chrome/browser/autocomplete/shortcuts_provider_unittest.cc
@@ -0,0 +1,586 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/autocomplete/shortcuts_provider.h"
+
+#include <algorithm>
+#include <functional>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "base/stringprintf.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/autocomplete/autocomplete.h"
+#include "chrome/browser/autocomplete/autocomplete_match.h"
+#include "chrome/browser/history/history.h"
+#include "chrome/browser/history/in_memory_url_index.h"
+#include "chrome/browser/history/url_database.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/testing_browser_process.h"
+#include "chrome/test/testing_browser_process_test.h"
+#include "chrome/test/testing_profile.h"
+#include "content/browser/browser_thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::Time;
+using base::TimeDelta;
+
+struct TestShortcutInfo {
+  std::string url;
+  std::string title;  // The text that orginally was searched for.
+  std::string contents;
+  std::string contents_class;
+  std::string description;
+  std::string description_class;
+  int typed_count;
+  int days_from_now;
+} shortcut_test_db[] = {
+  { "http://www.google.com/", "goog",
+    "Google", "0,1,4,0", "Google", "0,3,4,1", 100, 1 },
+  { "http://slashdot.org/", "slash",
+    "slashdot.org", "0,3,5,1",
+    "Slashdot - News for nerds, stuff that matters", "0,2,5,0", 100, 0},
+  { "http://slashdot.org/", "news",
+    "slashdot.org", "0,1",
+    "Slashdot - News for nerds, stuff that matters", "0,0,11,2,15,0", 5, 0},
+  { "http://sports.yahoo.com/", "news",
+    "sports.yahoo.com", "0,1",
+    "Yahoo! Sports - Sports News, Scores, Rumors, Fantasy Games, and more",
+    "0,0,23,2,27,0", 5, 2},
+  { "http://www.cnn.com/index.html", "news weather",
+    "www.cnn.com/index.html", "0,1",
+    "CNN.com - Breaking News, U.S., World, Weather, Entertainment & Video",
+    "0,0,19,2,23,0,38,2,45,0", 10, 1},
+  { "http://sports.yahoo.com/", "nhl scores",
+    "sports.yahoo.com", "0,1",
+    "Yahoo! Sports - Sports News, Scores, Rumors, Fantasy Games, and more",
+    "0,0,29,2,35,0", 10, 1},
+  { "http://www.nhl.com/scores/index.html", "nhl scores",
+    "www.nhl.com/scores/index.html", "0,1,4,3,7,1",
+    "January 13, 2010 - NHL.com - Scores", "0,0,19,2,22,0,29,2,35,0", 1, 5},
+  { "http://www.testsite.com/a.html", "just",
+    "www.testsite.com/a.html", "0,1",
+    "Test - site - just a test", "0,0,14,2,18,0", 1, 5},
+  { "http://www.testsite.com/b.html", "just",
+    "www.testsite.com/b.html", "0,1",
+    "Test - site - just a test", "0,0,14,2,18,0", 2, 5},
+  { "http://www.testsite.com/c.html", "just",
+    "www.testsite.com/c.html", "0,1",
+    "Test - site - just a test", "0,0,14,2,18,0", 1, 8},
+  { "http://www.testsite.com/d.html", "just a",
+    "www.testsite.com/d.html", "0,1",
+    "Test - site - just a test", "0,0,14,2,18,0", 1, 12},
+  { "http://www.testsite.com/e.html", "just a t",
+    "www.testsite.com/e.html", "0,1",
+    "Test - site - just a test", "0,0,14,2,18,0", 1, 12},
+  { "http://www.testsite.com/f.html", "just a te",
+    "www.testsite.com/f.html", "0,1",
+    "Test - site - just a test", "0,0,14,2,18,0", 1, 12},
+  { "http://www.daysagotest.com/a.html", "ago",
+    "www.daysagotest.com/a.html", "0,1,8,3,11,1",
+    "Test - site", "0,0", 1, 1},
+  { "http://www.daysagotest.com/b.html", "ago",
+    "www.daysagotest.com/b.html", "0,1,8,3,11,1",
+    "Test - site", "0,0", 1, 2},
+  { "http://www.daysagotest.com/c.html", "ago",
+    "www.daysagotest.com/c.html", "0,1,8,3,11,1",
+    "Test - site", "0,0", 1, 3},
+  { "http://www.daysagotest.com/d.html", "ago",
+    "www.daysagotest.com/d.html", "0,1,8,3,11,1",
+    "Test - site", "0,0", 1, 4},
+};
+
+class ShortcutsProviderTest : public TestingBrowserProcessTest,
+                              public ACProviderListener {
+ public:
+  ShortcutsProviderTest();
+
+  // ACProviderListener
+  virtual void OnProviderUpdate(bool updated_matches);
+
+ protected:
+  void SetUp();
+  void TearDown();
+
+  // Fills test data into the provider.
+  void FillData();
+
+  // Runs an autocomplete query on |text| and checks to see that the returned
+  // results' destination URLs match those provided. |expected_urls| does not
+  // need to be in sorted order.
+  void RunTest(const string16 text,
+               std::vector<std::string> expected_urls,
+               std::string expected_top_result);
+
+  MessageLoopForUI message_loop_;
+  BrowserThread ui_thread_;
+  BrowserThread file_thread_;
+
+  scoped_ptr<TestingProfile> profile_;
+
+  ACMatches ac_matches_;  // The resulting matches after running RunTest.
+
+  scoped_refptr<ShortcutsProvider> provider_;
+};
+
+ShortcutsProviderTest::ShortcutsProviderTest()
+    : ui_thread_(BrowserThread::UI, &message_loop_),
+      file_thread_(BrowserThread::FILE, &message_loop_) {
+}
+
+void ShortcutsProviderTest::OnProviderUpdate(bool updated_matches) {
+}
+
+void ShortcutsProviderTest::SetUp() {
+  profile_.reset(new TestingProfile());
+  profile_->CreateHistoryService(true, false);
+  provider_ = new ShortcutsProvider(this, profile_.get());
+  FillData();
+}
+
+void ShortcutsProviderTest::TearDown() {
+  provider_ = NULL;
+}
+
+void ShortcutsProviderTest::FillData() {
+  DCHECK(provider_.get());
+  provider_->shortcuts_map_.clear();
+  for (size_t i = 0; i < arraysize(shortcut_test_db); ++i) {
+    const TestShortcutInfo& cur = shortcut_test_db[i];
+    const GURL current_url(cur.url);
+    Time visit_time = Time::Now() - TimeDelta::FromDays(cur.days_from_now);
+    ShortcutsProvider::Shortcut shortcut(
+        ASCIIToUTF16(cur.title),
+        current_url,
+        ASCIIToUTF16(cur.contents),
+        provider_->SpansFromString(ASCIIToUTF16(cur.contents_class)),
+        ASCIIToUTF16(cur.description),
+        provider_->SpansFromString(ASCIIToUTF16(cur.description_class)));
+    shortcut.last_access_time = visit_time;
+    provider_->shortcuts_map_.insert(
+        std::pair<string16, ShortcutsProvider::Shortcut>(
+            ASCIIToUTF16(cur.title), shortcut));
+  }
+}
+
+class SetShouldContain : public std::unary_function<const std::string&,
+                                                    std::set<std::string> > {
+ public:
+  explicit SetShouldContain(const ACMatches& matched_urls) {
+    for (ACMatches::const_iterator iter = matched_urls.begin();
+         iter != matched_urls.end(); ++iter)
+      matches_.insert(iter->destination_url.spec());
+  }
+
+  void operator()(const std::string& expected) {
+    EXPECT_EQ(1U, matches_.erase(expected));
+  }
+
+  std::set<std::string> Leftovers() const { return matches_; }
+
+ private:
+  std::set<std::string> matches_;
+};
+
+void ShortcutsProviderTest::RunTest(const string16 text,
+                                   std::vector<std::string> expected_urls,
+                                   std::string expected_top_result) {
+  std::sort(expected_urls.begin(), expected_urls.end());
+
+  MessageLoop::current()->RunAllPending();
+  AutocompleteInput input(text, string16(), false, false, true,
+                          AutocompleteInput::ALL_MATCHES);
+  provider_->Start(input, false);
+  EXPECT_TRUE(provider_->done());
+
+  ac_matches_ = provider_->matches();
+
+  // We should have gotten back at most AutocompleteProvider::kMaxMatches.
+  EXPECT_LE(ac_matches_.size(), AutocompleteProvider::kMaxMatches);
+
+  // If the number of expected and actual matches aren't equal then we need
+  // test no further, but let's do anyway so that we know which URLs failed.
+  EXPECT_EQ(expected_urls.size(), ac_matches_.size());
+
+  // Verify that all expected URLs were found and that all found URLs
+  // were expected.
+  std::set<std::string> Leftovers =
+      for_each(expected_urls.begin(), expected_urls.end(),
+               SetShouldContain(ac_matches_)).Leftovers();
+  EXPECT_EQ(0U, Leftovers.size());
+
+  // See if we got the expected top scorer.
+  if (!ac_matches_.empty()) {
+    std::partial_sort(ac_matches_.begin(), ac_matches_.begin() + 1,
+                      ac_matches_.end(), AutocompleteMatch::MoreRelevant);
+    EXPECT_EQ(expected_top_result, ac_matches_[0].destination_url.spec());
+  }
+}
+
+TEST_F(ShortcutsProviderTest, SimpleSingleMatch) {
+  string16 text(ASCIIToUTF16("go"));
+  std::string expected_url("http://www.google.com/");
+  std::vector<std::string> expected_urls;
+  expected_urls.push_back(expected_url);
+  RunTest(text, expected_urls, expected_url);
+}
+
+TEST_F(ShortcutsProviderTest, MultiMatch) {
+  string16 text(ASCIIToUTF16("NEWS"));
+  std::vector<std::string> expected_urls;
+  // Scores high because of completion length.
+  expected_urls.push_back("http://slashdot.org/");
+  // Scores high because of visit count.
+  expected_urls.push_back("http://sports.yahoo.com/");
+  // Scores high because of visit count but less match span,
+  // which is more important.
+  expected_urls.push_back("http://www.cnn.com/index.html");
+  RunTest(text, expected_urls, "http://slashdot.org/");
+}
+
+TEST_F(ShortcutsProviderTest, VisitCountMatches) {
+  string16 text(ASCIIToUTF16("just"));
+  std::vector<std::string> expected_urls;
+  expected_urls.push_back("http://www.testsite.com/b.html");
+  expected_urls.push_back("http://www.testsite.com/a.html");
+  expected_urls.push_back("http://www.testsite.com/c.html");
+  RunTest(text, expected_urls, "http://www.testsite.com/b.html");
+}
+
+TEST_F(ShortcutsProviderTest, TypedCountMatches) {
+  string16 text(ASCIIToUTF16("just a"));
+  std::vector<std::string> expected_urls;
+  expected_urls.push_back("http://www.testsite.com/d.html");
+  expected_urls.push_back("http://www.testsite.com/e.html");
+  expected_urls.push_back("http://www.testsite.com/f.html");
+  RunTest(text, expected_urls, "http://www.testsite.com/d.html");
+}
+
+TEST_F(ShortcutsProviderTest, DaysAgoMatches) {
+  string16 text(ASCIIToUTF16("ago"));
+  std::vector<std::string> expected_urls;
+  expected_urls.push_back("http://www.daysagotest.com/a.html");
+  expected_urls.push_back("http://www.daysagotest.com/b.html");
+  expected_urls.push_back("http://www.daysagotest.com/c.html");
+  RunTest(text, expected_urls, "http://www.daysagotest.com/a.html");
+}
+
+TEST_F(ShortcutsProviderTest, ClassifyAllMatchesInString) {
+  string16 data(ASCIIToUTF16("A man, a plan, a canal Panama"));
+  ACMatchClassifications matches;
+  // Non-matched text does not have special attributes.
+  matches.push_back(ACMatchClassification(0, ACMatchClassification::NONE));
+
+  ACMatchClassifications spans_a =
+      ShortcutsProvider::ClassifyAllMatchesInString(ASCIIToUTF16("man"),
+                                                    data,
+                                                    matches);
+  // ACMatch spans should be: '--MMM------------------------'
+  ASSERT_EQ(3U, spans_a.size());
+  EXPECT_EQ(0U, spans_a[0].offset);
+  EXPECT_EQ(ACMatchClassification::NONE, spans_a[0].style);
+  EXPECT_EQ(2U, spans_a[1].offset);
+  EXPECT_EQ(ACMatchClassification::MATCH, spans_a[1].style);
+  EXPECT_EQ(5U, spans_a[2].offset);
+  EXPECT_EQ(ACMatchClassification::NONE, spans_a[2].style);
+
+  ACMatchClassifications spans_b =
+      ShortcutsProvider::ClassifyAllMatchesInString(ASCIIToUTF16("man p"),
+                                                    data,
+                                                    matches);
+  // ACMatch spans should be: '--MMM----M-------------M-----'
+  ASSERT_EQ(7U, spans_b.size());
+  EXPECT_EQ(0U, spans_b[0].offset);
+  EXPECT_EQ(ACMatchClassification::NONE, spans_b[0].style);
+  EXPECT_EQ(2U, spans_b[1].offset);
+  EXPECT_EQ(ACMatchClassification::MATCH, spans_b[1].style);
+  EXPECT_EQ(5U, spans_b[2].offset);
+  EXPECT_EQ(ACMatchClassification::NONE, spans_b[2].style);
+  EXPECT_EQ(9U, spans_b[3].offset);
+  EXPECT_EQ(ACMatchClassification::MATCH, spans_b[3].style);
+  EXPECT_EQ(10U, spans_b[4].offset);
+  EXPECT_EQ(ACMatchClassification::NONE, spans_b[4].style);
+  EXPECT_EQ(23U, spans_b[5].offset);
+  EXPECT_EQ(ACMatchClassification::MATCH, spans_b[5].style);
+  EXPECT_EQ(24U, spans_b[6].offset);
+  EXPECT_EQ(ACMatchClassification::NONE, spans_b[6].style);
+
+  ACMatchClassifications spans_c =
+      ShortcutsProvider::ClassifyAllMatchesInString(
+          ASCIIToUTF16("man plan panama"), data, matches);
+  // ACMatch spans should be:'--MMM----MMMM----------MMMMMM'
+  ASSERT_EQ(6U, spans_c.size());
+  EXPECT_EQ(0U, spans_c[0].offset);
+  EXPECT_EQ(ACMatchClassification::NONE, spans_c[0].style);
+  EXPECT_EQ(2U, spans_c[1].offset);
+  EXPECT_EQ(ACMatchClassification::MATCH, spans_c[1].style);
+  EXPECT_EQ(5U, spans_c[2].offset);
+  EXPECT_EQ(ACMatchClassification::NONE, spans_c[2].style);
+  EXPECT_EQ(9U, spans_c[3].offset);
+  EXPECT_EQ(ACMatchClassification::MATCH, spans_c[3].style);
+  EXPECT_EQ(13U, spans_c[4].offset);
+  EXPECT_EQ(ACMatchClassification::NONE, spans_c[4].style);
+  EXPECT_EQ(23U, spans_c[5].offset);
+  EXPECT_EQ(ACMatchClassification::MATCH, spans_c[5].style);
+
+  data = ASCIIToUTF16(
+      "Yahoo! Sports - Sports News, Scores, Rumors, Fantasy Games, and more");
+  matches.clear();
+  matches.push_back(ACMatchClassification(0, ACMatchClassification::NONE));
+
+  ACMatchClassifications spans_d =
+      ShortcutsProvider::ClassifyAllMatchesInString(ASCIIToUTF16("ne"),
+                                                    data,
+                                                    matches);
+  // ACMatch spans should match first two letters of the "news".
+  ASSERT_EQ(3U, spans_d.size());
+  EXPECT_EQ(0U, spans_d[0].offset);
+  EXPECT_EQ(ACMatchClassification::NONE, spans_d[0].style);
+  EXPECT_EQ(23U, spans_d[1].offset);
+  EXPECT_EQ(ACMatchClassification::MATCH, spans_d[1].style);
+  EXPECT_EQ(25U, spans_d[2].offset);
+  EXPECT_EQ(ACMatchClassification::NONE, spans_d[2].style);
+
+  ACMatchClassifications spans_e =
+      ShortcutsProvider::ClassifyAllMatchesInString(ASCIIToUTF16("news r"),
+                                                    data,
+                                                    matches);
+  // ACMatch spans should be the same as original matches.
+  ASSERT_EQ(15U, spans_e.size());
+  EXPECT_EQ(0U, spans_e[0].offset);
+  EXPECT_EQ(ACMatchClassification::NONE, spans_e[0].style);
+  // "r" in "Sports".
+  EXPECT_EQ(10U, spans_e[1].offset);
+  EXPECT_EQ(ACMatchClassification::MATCH, spans_e[1].style);
+  EXPECT_EQ(11U, spans_e[2].offset);
+  EXPECT_EQ(ACMatchClassification::NONE, spans_e[2].style);
+  // "r" in second "Sports".
+  EXPECT_EQ(19U, spans_e[3].offset);
+  EXPECT_EQ(ACMatchClassification::MATCH, spans_e[3].style);
+  EXPECT_EQ(20U, spans_e[4].offset);
+  EXPECT_EQ(ACMatchClassification::NONE, spans_e[4].style);
+  // "News".
+  EXPECT_EQ(23U, spans_e[5].offset);
+  EXPECT_EQ(ACMatchClassification::MATCH, spans_e[5].style);
+  EXPECT_EQ(27U, spans_e[6].offset);
+  EXPECT_EQ(ACMatchClassification::NONE, spans_e[6].style);
+  // "r" in "Scores".
+  EXPECT_EQ(32U, spans_e[7].offset);
+  EXPECT_EQ(ACMatchClassification::MATCH, spans_e[7].style);
+  EXPECT_EQ(33U, spans_e[8].offset);
+  EXPECT_EQ(ACMatchClassification::NONE, spans_e[8].style);
+  // First "r" in "Rumors".
+  EXPECT_EQ(37U, spans_e[9].offset);
+  EXPECT_EQ(ACMatchClassification::MATCH, spans_e[9].style);
+  EXPECT_EQ(38U, spans_e[10].offset);
+  EXPECT_EQ(ACMatchClassification::NONE, spans_e[10].style);
+  // Second "r" in "Rumors".
+  EXPECT_EQ(41U, spans_e[11].offset);
+  EXPECT_EQ(ACMatchClassification::MATCH, spans_e[11].style);
+  EXPECT_EQ(42U, spans_e[12].offset);
+  EXPECT_EQ(ACMatchClassification::NONE, spans_e[12].style);
+  // "r" in "more".
+  EXPECT_EQ(66U, spans_e[13].offset);
+  EXPECT_EQ(ACMatchClassification::MATCH, spans_e[13].style);
+  EXPECT_EQ(67U, spans_e[14].offset);
+  EXPECT_EQ(ACMatchClassification::NONE, spans_e[14].style);
+
+  data = ASCIIToUTF16("livescore.goal.com");
+  matches.clear();
+  // Matches for URL.
+  matches.push_back(ACMatchClassification(0, ACMatchClassification::URL));
+
+  ACMatchClassifications spans_f =
+      ShortcutsProvider::ClassifyAllMatchesInString(ASCIIToUTF16("go"),
+                                                    data,
+                                                    matches);
+  // ACMatch spans should match first two letters of the "goal".
+  ASSERT_EQ(3U, spans_f.size());
+  EXPECT_EQ(0U, spans_f[0].offset);
+  EXPECT_EQ(ACMatchClassification::URL, spans_f[0].style);
+  EXPECT_EQ(10U, spans_f[1].offset);
+  EXPECT_EQ(ACMatchClassification::URL | ACMatchClassification::MATCH,
+            spans_f[1].style);
+  EXPECT_EQ(12U, spans_f[2].offset);
+  EXPECT_EQ(ACMatchClassification::URL, spans_f[2].style);
+
+  data = ASCIIToUTF16("Email login: mail.somecorp.com");
+  matches.clear();
+  // Matches for URL.
+  matches.push_back(ACMatchClassification(0, ACMatchClassification::NONE));
+  matches.push_back(ACMatchClassification(13, ACMatchClassification::URL));
+
+  ACMatchClassifications spans_g =
+      ShortcutsProvider::ClassifyAllMatchesInString(ASCIIToUTF16("ail"),
+                                                    data,
+                                                    matches);
+  ASSERT_EQ(6U, spans_g.size());
+  EXPECT_EQ(0U, spans_g[0].offset);
+  EXPECT_EQ(ACMatchClassification::NONE, spans_g[0].style);
+  EXPECT_EQ(2U, spans_g[1].offset);
+  EXPECT_EQ(ACMatchClassification::MATCH, spans_g[1].style);
+  EXPECT_EQ(5U, spans_g[2].offset);
+  EXPECT_EQ(ACMatchClassification::NONE, spans_g[2].style);
+  EXPECT_EQ(13U, spans_g[3].offset);
+  EXPECT_EQ(ACMatchClassification::URL, spans_g[3].style);
+  EXPECT_EQ(14U, spans_g[4].offset);
+  EXPECT_EQ(ACMatchClassification::URL | ACMatchClassification::MATCH,
+            spans_g[4].style);
+  EXPECT_EQ(17U, spans_g[5].offset);
+  EXPECT_EQ(ACMatchClassification::URL, spans_g[5].style);
+
+  ACMatchClassifications spans_h =
+      ShortcutsProvider::ClassifyAllMatchesInString(ASCIIToUTF16("lo log"),
+                                                    data,
+                                                    matches);
+  ASSERT_EQ(4U, spans_h.size());
+  EXPECT_EQ(0U, spans_h[0].offset);
+  EXPECT_EQ(ACMatchClassification::NONE, spans_h[0].style);
+  EXPECT_EQ(6U, spans_h[1].offset);
+  EXPECT_EQ(ACMatchClassification::MATCH, spans_h[1].style);
+  EXPECT_EQ(9U, spans_h[2].offset);
+  EXPECT_EQ(ACMatchClassification::NONE, spans_h[2].style);
+  EXPECT_EQ(13U, spans_h[3].offset);
+  EXPECT_EQ(ACMatchClassification::URL, spans_h[3].style);
+
+  ACMatchClassifications spans_i =
+      ShortcutsProvider::ClassifyAllMatchesInString(ASCIIToUTF16("ail em"),
+                                                    data,
+                                                    matches);
+  // 'Email' and 'ail' should be matched.
+  ASSERT_EQ(5U, spans_i.size());
+  EXPECT_EQ(0U, spans_i[0].offset);
+  EXPECT_EQ(ACMatchClassification::MATCH, spans_i[0].style);
+  EXPECT_EQ(5U, spans_i[1].offset);
+  EXPECT_EQ(ACMatchClassification::NONE, spans_i[1].style);
+  EXPECT_EQ(13U, spans_i[2].offset);
+  EXPECT_EQ(ACMatchClassification::URL, spans_i[2].style);
+  EXPECT_EQ(14U, spans_i[3].offset);
+  EXPECT_EQ(ACMatchClassification::URL | ACMatchClassification::MATCH,
+            spans_i[3].style);
+  EXPECT_EQ(17U, spans_i[4].offset);
+  EXPECT_EQ(ACMatchClassification::URL, spans_i[4].style);
+}
+
+TEST_F(ShortcutsProviderTest, CalculateScore) {
+  ACMatchClassifications spans_content;
+  spans_content.push_back(ACMatchClassification(0, ACMatchClassification::URL));
+  spans_content.push_back(ACMatchClassification(
+      4, ACMatchClassification::MATCH | ACMatchClassification::URL));
+  spans_content.push_back(ACMatchClassification(8, ACMatchClassification::URL));
+  ACMatchClassifications spans_description;
+  spans_description.push_back(
+      ACMatchClassification(0, ACMatchClassification::NONE));
+  spans_description.push_back(
+      ACMatchClassification(2, ACMatchClassification::MATCH));
+  ShortcutsProvider::Shortcut shortcut(
+      ASCIIToUTF16("test"),
+      GURL("http://www.test.com"),
+      ASCIIToUTF16("www.test.com"),
+      spans_content,
+      ASCIIToUTF16("A test"),
+      spans_description);
+
+  // Yes, these tests could fail if CalculateScore() takes a lot of time,
+  // but even for the last test the time to change score by 1 is around
+  // two minutes, so if it fails because of timing we've got some problems.
+
+  // Maximal score.
+  shortcut.last_access_time = Time::Now();
+  EXPECT_EQ(ShortcutsProvider::CalculateScore(ASCIIToUTF16("test"), shortcut),
+            ShortcutsProvider::kMaxScore);
+
+  // Score decreases as percent of the match is decreased.
+  EXPECT_EQ(ShortcutsProvider::CalculateScore(ASCIIToUTF16("tes"), shortcut),
+            (ShortcutsProvider::kMaxScore / 4) * 3);
+  EXPECT_EQ(ShortcutsProvider::CalculateScore(ASCIIToUTF16("te"), shortcut),
+            ShortcutsProvider::kMaxScore / 2);
+  EXPECT_EQ(ShortcutsProvider::CalculateScore(ASCIIToUTF16("t"), shortcut),
+            ShortcutsProvider::kMaxScore / 4);
+
+  // Should decay twice in a week.
+  shortcut.last_access_time = Time::Now() - TimeDelta::FromDays(7);
+  EXPECT_EQ(ShortcutsProvider::CalculateScore(ASCIIToUTF16("test"), shortcut),
+            ShortcutsProvider::kMaxScore / 2);
+
+  // Should decay four times in two weeks.
+  shortcut.last_access_time = Time::Now() - TimeDelta::FromDays(14);
+  EXPECT_EQ(ShortcutsProvider::CalculateScore(ASCIIToUTF16("test"), shortcut),
+            ShortcutsProvider::kMaxScore / 4);
+
+  // But not if it was activly clicked on. 6 hits slow decaying power twice.
+  shortcut.number_of_hits = 6;
+  shortcut.last_access_time = Time::Now() - TimeDelta::FromDays(14);
+  EXPECT_EQ(ShortcutsProvider::CalculateScore(ASCIIToUTF16("test"), shortcut),
+            ShortcutsProvider::kMaxScore / 2);
+}
+
+TEST_F(ShortcutsProviderTest, DeleteMatch) {
+  TestShortcutInfo shortcuts_to_test_delete[3] = {
+    { "http://www.deletetest.com/1.html", "delete",
+      "http://www.deletetest.com/1.html", "0,2",
+      "Erase this shortcut!", "0,0", 1, 1},
+    { "http://www.deletetest.com/1.html", "erase",
+      "http://www.deletetest.com/1.html", "0,2",
+      "Erase this shortcut!", "0,0", 1, 1},
+    { "http://www.deletetest.com/2.html", "delete",
+      "http://www.deletetest.com/2.html", "0,2",
+      "Erase this shortcut!", "0,0", 1, 1},
+  };
+
+  size_t original_shortcuts_count = provider_->shortcuts_map_.size();
+
+  for (size_t i = 0; i < arraysize(shortcuts_to_test_delete); ++i) {
+    const TestShortcutInfo& cur = shortcuts_to_test_delete[i];
+    const GURL current_url(cur.url);
+    Time visit_time = Time::Now() - TimeDelta::FromDays(cur.days_from_now);
+    ShortcutsProvider::Shortcut shortcut(
+        ASCIIToUTF16(cur.title),
+        current_url,
+        ASCIIToUTF16(cur.contents),
+        provider_->SpansFromString(ASCIIToUTF16(cur.contents_class)),
+        ASCIIToUTF16(cur.description),
+        provider_->SpansFromString(ASCIIToUTF16(cur.description_class)));
+    shortcut.last_access_time = visit_time;
+    provider_->shortcuts_map_.insert(
+        std::pair<string16, ShortcutsProvider::Shortcut>(
+            ASCIIToUTF16(cur.title), shortcut));
+  }
+
+  EXPECT_EQ(original_shortcuts_count + 3, provider_->shortcuts_map_.size());
+  EXPECT_FALSE(provider_->shortcuts_map_.end() ==
+               provider_->shortcuts_map_.find(ASCIIToUTF16("delete")));
+  EXPECT_FALSE(provider_->shortcuts_map_.end() ==
+               provider_->shortcuts_map_.find(ASCIIToUTF16("erase")));
+
+  AutocompleteMatch match(provider_, 1200, true,
+                          AutocompleteMatch::HISTORY_TITLE);
+
+  match.destination_url = GURL(shortcuts_to_test_delete[0].url);
+  match.contents = ASCIIToUTF16(shortcuts_to_test_delete[0].contents);
+  match.description = ASCIIToUTF16(shortcuts_to_test_delete[0].description);
+
+  provider_->DeleteMatch(match);
+
+  // |shortcuts_to_test_delete[0]| and |shortcuts_to_test_delete[1]| should be
+  // deleted, but not |shortcuts_to_test_delete[2]| as it has different url.
+  EXPECT_EQ(original_shortcuts_count + 1, provider_->shortcuts_map_.size());
+  EXPECT_FALSE(provider_->shortcuts_map_.end() ==
+               provider_->shortcuts_map_.find(ASCIIToUTF16("delete")));
+  EXPECT_TRUE(provider_->shortcuts_map_.end() ==
+              provider_->shortcuts_map_.find(ASCIIToUTF16("erase")));
+
+  match.destination_url = GURL(shortcuts_to_test_delete[2].url);
+  match.contents = ASCIIToUTF16(shortcuts_to_test_delete[2].contents);
+  match.description = ASCIIToUTF16(shortcuts_to_test_delete[2].description);
+
+  provider_->DeleteMatch(match);
+  EXPECT_EQ(original_shortcuts_count, provider_->shortcuts_map_.size());
+  EXPECT_TRUE(provider_->shortcuts_map_.end() ==
+              provider_->shortcuts_map_.find(ASCIIToUTF16("delete")));
+}
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 379020a..5fbbf16 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -118,6 +118,8 @@
         'browser/autocomplete/keyword_provider.h',
         'browser/autocomplete/search_provider.cc',
         'browser/autocomplete/search_provider.h',
+        'browser/autocomplete/shortcuts_provider.cc',
+        'browser/autocomplete/shortcuts_provider.h',
         'browser/autocomplete_history_manager.cc',
         'browser/autocomplete_history_manager.h',
         'browser/autofill/address.cc',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 3bb2d8aa..d1da145c 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1252,6 +1252,7 @@
         'browser/autocomplete/history_url_provider_unittest.cc',
         'browser/autocomplete/keyword_provider_unittest.cc',
         'browser/autocomplete/search_provider_unittest.cc',
+        'browser/autocomplete/shortcuts_provider_unittest.cc',
         'browser/autocomplete_history_manager_unittest.cc',
         'browser/autofill/address_field_unittest.cc',
         'browser/autofill/address_unittest.cc',
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 914ed25..db7980ea 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -480,6 +480,9 @@
 // parameter to indicate if the provider should be the default.
 const char kEnableSearchProviderApiV2[]     = "enable-search-provider-api-v2";
 
+// Enable the use of the ShortcutsProvider for autocomplete results.
+const char kEnableShortcutsProvider[]       = "enable-shortcuts-provider";
+
 // On platforms that support it, enable smooth scroll animation.
 const char kEnableSmoothScrolling[]         = "enable-smooth-scrolling";
 
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index aa3dcd0..b330eafd 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -139,6 +139,7 @@
 extern const char kEnableRemoting[];
 extern const char kEnableResourceContentSettings[];
 extern const char kEnableSearchProviderApiV2[];
+extern const char kEnableShortcutsProvider[];
 extern const char kEnableSmoothScrolling[];
 extern const char kEnableSSLCachedInfo[];
 extern const char kEnableSync[];