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_provider.h"
      6 
      7 #include <math.h>
      8 
      9 #include <algorithm>
     10 #include <functional>
     11 #include <set>
     12 #include <string>
     13 #include <vector>
     14 
     15 #include "base/memory/ref_counted.h"
     16 #include "base/message_loop/message_loop.h"
     17 #include "base/prefs/pref_service.h"
     18 #include "base/strings/stringprintf.h"
     19 #include "base/strings/utf_string_conversions.h"
     20 #include "chrome/browser/autocomplete/autocomplete_input.h"
     21 #include "chrome/browser/autocomplete/autocomplete_match.h"
     22 #include "chrome/browser/autocomplete/autocomplete_provider.h"
     23 #include "chrome/browser/autocomplete/autocomplete_provider_listener.h"
     24 #include "chrome/browser/autocomplete/autocomplete_result.h"
     25 #include "chrome/browser/chrome_notification_types.h"
     26 #include "chrome/browser/history/history_service.h"
     27 #include "chrome/browser/history/in_memory_url_index.h"
     28 #include "chrome/browser/history/shortcuts_backend.h"
     29 #include "chrome/browser/history/shortcuts_backend_factory.h"
     30 #include "chrome/browser/history/url_database.h"
     31 #include "chrome/common/pref_names.h"
     32 #include "chrome/test/base/testing_profile.h"
     33 #include "content/public/browser/notification_service.h"
     34 #include "content/public/test/test_browser_thread.h"
     35 #include "extensions/common/extension.h"
     36 #include "extensions/common/extension_builder.h"
     37 #include "extensions/common/value_builder.h"
     38 #include "testing/gtest/include/gtest/gtest.h"
     39 
     40 
     41 // TestShortcutInfo -----------------------------------------------------------
     42 
     43 namespace {
     44 
     45 struct TestShortcutInfo {
     46   std::string guid;
     47   std::string text;
     48   std::string fill_into_edit;
     49   std::string destination_url;
     50   std::string contents;
     51   std::string contents_class;
     52   std::string description;
     53   std::string description_class;
     54   content::PageTransition transition;
     55   AutocompleteMatch::Type type;
     56   std::string keyword;
     57   int days_from_now;
     58   int number_of_hits;
     59 } shortcut_test_db[] = {
     60   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E0", "goog", "www.google.com",
     61     "http://www.google.com/", "Google", "0,1,4,0", "Google", "0,3,4,1",
     62     content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "", 1,
     63     100 },
     64   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E1", "slash", "slashdot.org",
     65     "http://slashdot.org/", "slashdot.org", "0,3,5,1",
     66     "Slashdot - News for nerds, stuff that matters", "0,2,5,0",
     67     content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "", 0,
     68     100 },
     69   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E2", "news", "slashdot.org",
     70     "http://slashdot.org/", "slashdot.org", "0,1",
     71     "Slashdot - News for nerds, stuff that matters", "0,0,11,2,15,0",
     72     content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "", 0,
     73     5 },
     74   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E3", "news", "sports.yahoo.com",
     75     "http://sports.yahoo.com/", "sports.yahoo.com", "0,1",
     76     "Yahoo! Sports - Sports News, Scores, Rumors, Fantasy Games, and more",
     77     "0,0,23,2,27,0", content::PAGE_TRANSITION_TYPED,
     78     AutocompleteMatchType::HISTORY_TITLE, "", 2, 5 },
     79   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E4", "news weather",
     80     "www.cnn.com/index.html", "http://www.cnn.com/index.html",
     81     "www.cnn.com/index.html", "0,1",
     82     "CNN.com - Breaking News, U.S., World, Weather, Entertainment & Video",
     83     "0,0,19,2,23,0,38,2,45,0", content::PAGE_TRANSITION_TYPED,
     84     AutocompleteMatchType::HISTORY_TITLE, "", 1, 10 },
     85   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E5", "nhl scores", "sports.yahoo.com",
     86     "http://sports.yahoo.com/", "sports.yahoo.com", "0,1",
     87     "Yahoo! Sports - Sports News, Scores, Rumors, Fantasy Games, and more",
     88     "0,0,29,2,35,0", content::PAGE_TRANSITION_TYPED,
     89     AutocompleteMatchType::HISTORY_BODY, "", 1, 10 },
     90   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E6", "nhl scores",
     91     "www.nhl.com/scores/index.html", "http://www.nhl.com/scores/index.html",
     92     "www.nhl.com/scores/index.html", "0,1,4,3,7,1",
     93     "January 13, 2010 - NHL.com - Scores", "0,0,19,2,22,0,29,2,35,0",
     94     content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "", 5,
     95     1 },
     96   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E7", "just", "www.testsite.com/a.html",
     97     "http://www.testsite.com/a.html", "www.testsite.com/a.html", "0,1",
     98     "Test - site - just a test", "0,0,14,2,18,0",
     99     content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "", 5,
    100     1 },
    101   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E8", "just", "www.testsite.com/b.html",
    102     "http://www.testsite.com/b.html", "www.testsite.com/b.html", "0,1",
    103     "Test - site - just a test", "0,0,14,2,18,0",
    104     content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "", 5,
    105     2 },
    106   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E9", "just", "www.testsite.com/c.html",
    107     "http://www.testsite.com/c.html", "www.testsite.com/c.html", "0,1",
    108     "Test - site - just a test", "0,0,14,2,18,0",
    109     content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "", 8,
    110     1 },
    111   { "BD85DBA2-8C29-49F9-84AE-48E1E90880EA", "just a", "www.testsite.com/d.html",
    112     "http://www.testsite.com/d.html", "www.testsite.com/d.html", "0,1",
    113     "Test - site - just a test", "0,0,14,2,18,0",
    114     content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "",
    115     12, 1 },
    116   { "BD85DBA2-8C29-49F9-84AE-48E1E90880EB", "just a t",
    117     "www.testsite.com/e.html", "http://www.testsite.com/e.html",
    118     "www.testsite.com/e.html", "0,1", "Test - site - just a test",
    119     "0,0,14,2,18,0", content::PAGE_TRANSITION_TYPED,
    120     AutocompleteMatchType::HISTORY_TITLE, "", 12, 1 },
    121   { "BD85DBA2-8C29-49F9-84AE-48E1E90880EC", "just a te",
    122     "www.testsite.com/f.html", "http://www.testsite.com/f.html",
    123     "www.testsite.com/f.html", "0,1", "Test - site - just a test",
    124     "0,0,14,2,18,0", content::PAGE_TRANSITION_TYPED,
    125     AutocompleteMatchType::HISTORY_TITLE, "", 12, 1 },
    126   { "BD85DBA2-8C29-49F9-84AE-48E1E90880ED", "ago", "www.daysagotest.com/a.html",
    127     "http://www.daysagotest.com/a.html", "www.daysagotest.com/a.html",
    128     "0,1,8,3,11,1", "Test - site", "0,0", content::PAGE_TRANSITION_TYPED,
    129     AutocompleteMatchType::HISTORY_URL, "", 1, 1 },
    130   { "BD85DBA2-8C29-49F9-84AE-48E1E90880EE", "ago", "www.daysagotest.com/b.html",
    131     "http://www.daysagotest.com/b.html", "www.daysagotest.com/b.html",
    132     "0,1,8,3,11,1", "Test - site", "0,0", content::PAGE_TRANSITION_TYPED,
    133     AutocompleteMatchType::HISTORY_URL, "", 2, 1 },
    134   { "BD85DBA2-8C29-49F9-84AE-48E1E90880EF", "ago", "www.daysagotest.com/c.html",
    135     "http://www.daysagotest.com/c.html", "www.daysagotest.com/c.html",
    136     "0,1,8,3,11,1", "Test - site", "0,0", content::PAGE_TRANSITION_TYPED,
    137     AutocompleteMatchType::HISTORY_URL, "", 3, 1 },
    138   { "BD85DBA2-8C29-49F9-84AE-48E1E90880F0", "ago", "www.daysagotest.com/d.html",
    139     "http://www.daysagotest.com/d.html", "www.daysagotest.com/d.html",
    140     "0,1,8,3,11,1", "Test - site", "0,0", content::PAGE_TRANSITION_TYPED,
    141     AutocompleteMatchType::HISTORY_URL, "", 4, 1 },
    142   { "BD85DBA2-8C29-49F9-84AE-48E1E90880F1", "echo echo", "echo echo",
    143     "chrome-extension://cedabbhfglmiikkmdgcpjdkocfcmbkee/?q=echo",
    144     "Run Echo command: echo", "0,0", "Echo", "0,4",
    145     content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::EXTENSION_APP,
    146     "echo", 1, 1 },
    147   { "BD85DBA2-8C29-49F9-84AE-48E1E90880F2", "abcdef.com", "http://abcdef.com",
    148     "http://abcdef.com/", "Abcdef", "0,1,4,0", "Abcdef", "0,3,4,1",
    149     content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "", 1,
    150     100 },
    151   { "BD85DBA2-8C29-49F9-84AE-48E1E90880F3", "query", "query",
    152     "https://www.google.com/search?q=query", "query", "0,0",
    153     "Google Search", "0,4", content::PAGE_TRANSITION_GENERATED,
    154     AutocompleteMatchType::SEARCH_HISTORY, "", 1, 100 },
    155   { "BD85DBA2-8C29-49F9-84AE-48E1E90880F4", "word", "www.word",
    156     "https://www.google.com/search?q=www.word", "www.word", "0,0",
    157     "Google Search", "0,4", content::PAGE_TRANSITION_GENERATED,
    158     AutocompleteMatchType::SEARCH_HISTORY, "", 1, 100 },
    159   { "BD85DBA2-8C29-49F9-84AE-48E1E90880F5", "about:o", "chrome://omnibox",
    160     "chrome://omnibox/", "about:omnibox", "0,3,10,1", "", "",
    161     content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::NAVSUGGEST, "",
    162     1, 100 },
    163   { "BD85DBA2-8C29-49F9-84AE-48E1E90880F6", "www/real sp",
    164     "http://www/real space/long-url-with-space.html",
    165     "http://www/real%20space/long-url-with-space.html",
    166     "www/real space/long-url-with-space.html", "0,3,11,1",
    167     "Page With Space; Input with Space", "0,0",
    168     content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "",
    169     1, 100 },
    170 };
    171 
    172 }  // namespace
    173 
    174 
    175 // ClassifyTest ---------------------------------------------------------------
    176 
    177 // Helper class to make running tests of ClassifyAllMatchesInString() more
    178 // convenient.
    179 class ClassifyTest {
    180  public:
    181   ClassifyTest(const base::string16& text, ACMatchClassifications matches);
    182   ~ClassifyTest();
    183 
    184   ACMatchClassifications RunTest(const base::string16& find_text);
    185 
    186  private:
    187   const base::string16 text_;
    188   const ACMatchClassifications matches_;
    189 };
    190 
    191 ClassifyTest::ClassifyTest(const base::string16& text,
    192                            ACMatchClassifications matches)
    193     : text_(text),
    194       matches_(matches) {
    195 }
    196 
    197 ClassifyTest::~ClassifyTest() {
    198 }
    199 
    200 ACMatchClassifications ClassifyTest::RunTest(const base::string16& find_text) {
    201   return ShortcutsProvider::ClassifyAllMatchesInString(find_text,
    202       ShortcutsProvider::CreateWordMapForString(find_text), text_, matches_);
    203 }
    204 
    205 namespace history {
    206 
    207 
    208 // ShortcutsProviderTest ------------------------------------------------------
    209 
    210 class ShortcutsProviderTest : public testing::Test,
    211                               public AutocompleteProviderListener {
    212  public:
    213   ShortcutsProviderTest();
    214 
    215   // AutocompleteProviderListener:
    216   virtual void OnProviderUpdate(bool updated_matches) OVERRIDE;
    217 
    218  protected:
    219   typedef std::pair<std::string, bool> ExpectedURLAndAllowedToBeDefault;
    220   typedef std::vector<ExpectedURLAndAllowedToBeDefault> ExpectedURLs;
    221 
    222   class SetShouldContain
    223       : public std::unary_function<const ExpectedURLAndAllowedToBeDefault&,
    224                                    std::set<std::string> > {
    225    public:
    226     explicit SetShouldContain(const ACMatches& matched_urls);
    227 
    228     void operator()(const ExpectedURLAndAllowedToBeDefault& expected);
    229     std::set<ExpectedURLAndAllowedToBeDefault> Leftovers() const {
    230         return matches_;
    231     }
    232 
    233    private:
    234     std::set<ExpectedURLAndAllowedToBeDefault> matches_;
    235   };
    236 
    237   virtual void SetUp();
    238   virtual void TearDown();
    239 
    240   // Fills test data into the provider.
    241   void FillData(TestShortcutInfo* db, size_t db_size);
    242 
    243   // Runs an autocomplete query on |text| with the provided
    244   // |prevent_inline_autocomplete| setting and checks to see that the returned
    245   // results' destination URLs match those provided. |expected_urls| does not
    246   // need to be in sorted order, but |expected_top_result| should be the top
    247   // match, and it should have inline autocompletion
    248   // |top_result_inline_autocompletion|.
    249   void RunTest(const base::string16 text,
    250                bool prevent_inline_autocomplete,
    251                const ExpectedURLs& expected_urls,
    252                std::string expected_top_result,
    253                base::string16 top_result_inline_autocompletion);
    254 
    255   // Passthrough to the private function in provider_.
    256   int CalculateScore(const std::string& terms,
    257                      const ShortcutsBackend::Shortcut& shortcut,
    258                      int max_relevance);
    259 
    260   base::MessageLoopForUI message_loop_;
    261   content::TestBrowserThread ui_thread_;
    262   content::TestBrowserThread file_thread_;
    263 
    264   TestingProfile profile_;
    265 
    266   ACMatches ac_matches_;  // The resulting matches after running RunTest.
    267 
    268   scoped_refptr<ShortcutsBackend> backend_;
    269   scoped_refptr<ShortcutsProvider> provider_;
    270 };
    271 
    272 ShortcutsProviderTest::ShortcutsProviderTest()
    273     : ui_thread_(content::BrowserThread::UI, &message_loop_),
    274       file_thread_(content::BrowserThread::FILE, &message_loop_) {
    275 }
    276 
    277 void ShortcutsProviderTest::OnProviderUpdate(bool updated_matches) {}
    278 
    279 void ShortcutsProviderTest::SetUp() {
    280   ShortcutsBackendFactory::GetInstance()->SetTestingFactoryAndUse(
    281       &profile_, &ShortcutsBackendFactory::BuildProfileNoDatabaseForTesting);
    282   backend_ = ShortcutsBackendFactory::GetForProfile(&profile_);
    283   ASSERT_TRUE(backend_.get());
    284   ASSERT_TRUE(profile_.CreateHistoryService(true, false));
    285   provider_ = new ShortcutsProvider(this, &profile_);
    286   FillData(shortcut_test_db, arraysize(shortcut_test_db));
    287 }
    288 
    289 void ShortcutsProviderTest::TearDown() {
    290   // Run all pending tasks or else some threads hold on to the message loop
    291   // and prevent it from being deleted.
    292   message_loop_.RunUntilIdle();
    293   provider_ = NULL;
    294 }
    295 
    296 void ShortcutsProviderTest::FillData(TestShortcutInfo* db, size_t db_size) {
    297   DCHECK(provider_.get());
    298   size_t expected_size = backend_->shortcuts_map().size() + db_size;
    299   for (size_t i = 0; i < db_size; ++i) {
    300     const TestShortcutInfo& cur = db[i];
    301     ShortcutsBackend::Shortcut shortcut(
    302         cur.guid, ASCIIToUTF16(cur.text),
    303         ShortcutsBackend::Shortcut::MatchCore(
    304             ASCIIToUTF16(cur.fill_into_edit), GURL(cur.destination_url),
    305             ASCIIToUTF16(cur.contents),
    306             AutocompleteMatch::ClassificationsFromString(cur.contents_class),
    307             ASCIIToUTF16(cur.description),
    308             AutocompleteMatch::ClassificationsFromString(cur.description_class),
    309             cur.transition, cur.type, ASCIIToUTF16(cur.keyword)),
    310         base::Time::Now() - base::TimeDelta::FromDays(cur.days_from_now),
    311         cur.number_of_hits);
    312     backend_->AddShortcut(shortcut);
    313   }
    314   EXPECT_EQ(expected_size, backend_->shortcuts_map().size());
    315 }
    316 
    317 ShortcutsProviderTest::SetShouldContain::SetShouldContain(
    318     const ACMatches& matched_urls) {
    319   for (ACMatches::const_iterator iter = matched_urls.begin();
    320        iter != matched_urls.end(); ++iter)
    321     matches_.insert(ExpectedURLAndAllowedToBeDefault(
    322         iter->destination_url.spec(), iter->allowed_to_be_default_match));
    323 }
    324 
    325 void ShortcutsProviderTest::SetShouldContain::operator()(
    326     const ExpectedURLAndAllowedToBeDefault& expected) {
    327   EXPECT_EQ(1U, matches_.erase(expected));
    328 }
    329 
    330 void ShortcutsProviderTest::RunTest(
    331     const base::string16 text,
    332     bool prevent_inline_autocomplete,
    333     const ExpectedURLs& expected_urls,
    334     std::string expected_top_result,
    335     base::string16 top_result_inline_autocompletion) {
    336   base::MessageLoop::current()->RunUntilIdle();
    337   AutocompleteInput input(text, base::string16::npos, base::string16(), GURL(),
    338                           AutocompleteInput::INVALID_SPEC,
    339                           prevent_inline_autocomplete, false, true,
    340                           AutocompleteInput::ALL_MATCHES);
    341   provider_->Start(input, false);
    342   EXPECT_TRUE(provider_->done());
    343 
    344   ac_matches_ = provider_->matches();
    345 
    346   // We should have gotten back at most AutocompleteProvider::kMaxMatches.
    347   EXPECT_LE(ac_matches_.size(), AutocompleteProvider::kMaxMatches);
    348 
    349   // If the number of expected and actual matches aren't equal then we need
    350   // test no further, but let's do anyway so that we know which URLs failed.
    351   EXPECT_EQ(expected_urls.size(), ac_matches_.size());
    352 
    353   // Verify that all expected URLs were found and that all found URLs
    354   // were expected.
    355   std::set<ExpectedURLAndAllowedToBeDefault> Leftovers =
    356       for_each(expected_urls.begin(), expected_urls.end(),
    357                SetShouldContain(ac_matches_)).Leftovers();
    358   EXPECT_EQ(0U, Leftovers.size());
    359 
    360   // See if we got the expected top scorer.
    361   if (!ac_matches_.empty()) {
    362     std::partial_sort(ac_matches_.begin(), ac_matches_.begin() + 1,
    363                       ac_matches_.end(), AutocompleteMatch::MoreRelevant);
    364     EXPECT_EQ(expected_top_result, ac_matches_[0].destination_url.spec());
    365     EXPECT_EQ(top_result_inline_autocompletion,
    366               ac_matches_[0].inline_autocompletion);
    367   }
    368 }
    369 
    370 int ShortcutsProviderTest::CalculateScore(
    371     const std::string& terms,
    372     const ShortcutsBackend::Shortcut& shortcut,
    373     int max_relevance) {
    374   return provider_->CalculateScore(ASCIIToUTF16(terms), shortcut,
    375                                    max_relevance);
    376 }
    377 
    378 
    379 // Actual tests ---------------------------------------------------------------
    380 
    381 TEST_F(ShortcutsProviderTest, SimpleSingleMatch) {
    382   base::string16 text(ASCIIToUTF16("go"));
    383   std::string expected_url("http://www.google.com/");
    384   ExpectedURLs expected_urls;
    385   expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(expected_url, true));
    386   RunTest(text, false, expected_urls, expected_url, ASCIIToUTF16("ogle.com"));
    387 
    388   // Same test with prevent inline autocomplete.
    389   expected_urls.clear();
    390   expected_urls.push_back(
    391       ExpectedURLAndAllowedToBeDefault(expected_url, false));
    392   // The match will have an |inline_autocompletion| set, but the value will not
    393   // be used because |allowed_to_be_default_match| will be false.
    394   RunTest(text, true, expected_urls, expected_url, ASCIIToUTF16("ogle.com"));
    395 
    396   // A pair of analogous tests where the shortcut ends at the end of
    397   // |fill_into_edit|.  This exercises the inline autocompletion and default
    398   // match code.
    399   text = ASCIIToUTF16("abcdef.com");
    400   expected_url = "http://abcdef.com/";
    401   expected_urls.clear();
    402   expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(expected_url, true));
    403   RunTest(text, false, expected_urls, expected_url, base::string16());
    404   // With prevent inline autocomplete, the suggestion should be the same
    405   // (because there is no completion).
    406   RunTest(text, true, expected_urls, expected_url, base::string16());
    407 
    408   // Another test, simply for a query match type, not a navigation URL match
    409   // type.
    410   text = ASCIIToUTF16("que");
    411   expected_url = "https://www.google.com/search?q=query";
    412   expected_urls.clear();
    413   expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(expected_url, true));
    414   RunTest(text, false, expected_urls, expected_url, ASCIIToUTF16("ry"));
    415 
    416   // Same test with prevent inline autocomplete.
    417   expected_urls.clear();
    418   expected_urls.push_back(
    419       ExpectedURLAndAllowedToBeDefault(expected_url, false));
    420   // The match will have an |inline_autocompletion| set, but the value will not
    421   // be used because |allowed_to_be_default_match| will be false.
    422   RunTest(text, true, expected_urls, expected_url, ASCIIToUTF16("ry"));
    423 
    424   // A pair of analogous tests where the shortcut ends at the end of
    425   // |fill_into_edit|.  This exercises the inline autocompletion and default
    426   // match code.
    427   text = ASCIIToUTF16("query");
    428   expected_urls.clear();
    429   expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(expected_url, true));
    430   RunTest(text, false, expected_urls, expected_url, base::string16());
    431   // With prevent inline autocomplete, the suggestion should be the same
    432   // (because there is no completion).
    433   RunTest(text, true, expected_urls, expected_url, base::string16());
    434 
    435   // Now the shortcut ends at the end of |fill_into_edit| but has a
    436   // non-droppable prefix.  ("www.", for instance, is not droppable for
    437   // queries.)
    438   text = ASCIIToUTF16("word");
    439   expected_url = "https://www.google.com/search?q=www.word";
    440   expected_urls.clear();
    441   expected_urls.push_back(
    442       ExpectedURLAndAllowedToBeDefault(expected_url, false));
    443   RunTest(text, false, expected_urls, expected_url, base::string16());
    444 }
    445 
    446 // These tests are like those in SimpleSingleMatch but more complex,
    447 // involving URLs that need to be fixed up to match properly.
    448 TEST_F(ShortcutsProviderTest, TrickySingleMatch) {
    449   // Test that about: URLs are fixed up/transformed to chrome:// URLs.
    450   base::string16 text(ASCIIToUTF16("about:o"));
    451   std::string expected_url("chrome://omnibox/");
    452   ExpectedURLs expected_urls;
    453   expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(expected_url, true));
    454   RunTest(text, false, expected_urls, expected_url, ASCIIToUTF16("mnibox"));
    455 
    456   // Same test with prevent inline autocomplete.
    457   expected_urls.clear();
    458   expected_urls.push_back(
    459       ExpectedURLAndAllowedToBeDefault(expected_url, false));
    460   // The match will have an |inline_autocompletion| set, but the value will not
    461   // be used because |allowed_to_be_default_match| will be false.
    462   RunTest(text, true, expected_urls, expected_url, ASCIIToUTF16("mnibox"));
    463 
    464   // Test that an input with a space can match URLs with a (escaped) space.
    465   // This would fail if we didn't try to lookup the un-fixed-up string.
    466   text = ASCIIToUTF16("www/real sp");
    467   expected_url = "http://www/real%20space/long-url-with-space.html";
    468   expected_urls.clear();
    469   expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(expected_url, true));
    470   RunTest(text, false, expected_urls, expected_url,
    471           ASCIIToUTF16("ace/long-url-with-space.html"));
    472 
    473   // Same test with prevent inline autocomplete.
    474   expected_urls.clear();
    475   expected_urls.push_back(
    476       ExpectedURLAndAllowedToBeDefault(expected_url, false));
    477   // The match will have an |inline_autocompletion| set, but the value will not
    478   // be used because |allowed_to_be_default_match| will be false.
    479   RunTest(text, true, expected_urls, expected_url,
    480           ASCIIToUTF16("ace/long-url-with-space.html"));
    481 }
    482 
    483 TEST_F(ShortcutsProviderTest, MultiMatch) {
    484   base::string16 text(ASCIIToUTF16("NEWS"));
    485   ExpectedURLs expected_urls;
    486   // Scores high because of completion length.
    487   expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
    488       "http://slashdot.org/", false));
    489   // Scores high because of visit count.
    490   expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
    491       "http://sports.yahoo.com/", false));
    492   // Scores high because of visit count but less match span,
    493   // which is more important.
    494   expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
    495       "http://www.cnn.com/index.html", false));
    496   RunTest(text, false, expected_urls, "http://slashdot.org/", base::string16());
    497 }
    498 
    499 TEST_F(ShortcutsProviderTest, TypedCountMatches) {
    500   base::string16 text(ASCIIToUTF16("just"));
    501   ExpectedURLs expected_urls;
    502   expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
    503       "http://www.testsite.com/b.html", false));
    504   expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
    505       "http://www.testsite.com/a.html", false));
    506   expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
    507       "http://www.testsite.com/c.html", false));
    508   RunTest(text, false, expected_urls, "http://www.testsite.com/b.html",
    509           base::string16());
    510 }
    511 
    512 TEST_F(ShortcutsProviderTest, FragmentLengthMatches) {
    513   base::string16 text(ASCIIToUTF16("just a"));
    514   ExpectedURLs expected_urls;
    515   expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
    516       "http://www.testsite.com/d.html", false));
    517   expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
    518       "http://www.testsite.com/e.html", false));
    519   expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
    520       "http://www.testsite.com/f.html", false));
    521   RunTest(text, false, expected_urls, "http://www.testsite.com/d.html",
    522           base::string16());
    523 }
    524 
    525 TEST_F(ShortcutsProviderTest, DaysAgoMatches) {
    526   base::string16 text(ASCIIToUTF16("ago"));
    527   ExpectedURLs expected_urls;
    528   expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
    529       "http://www.daysagotest.com/a.html", false));
    530   expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
    531       "http://www.daysagotest.com/b.html", false));
    532   expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
    533       "http://www.daysagotest.com/c.html", false));
    534   RunTest(text, false, expected_urls, "http://www.daysagotest.com/a.html",
    535           base::string16());
    536 }
    537 
    538 TEST_F(ShortcutsProviderTest, ClassifyAllMatchesInString) {
    539   ACMatchClassifications matches =
    540       AutocompleteMatch::ClassificationsFromString("0,0");
    541   ClassifyTest classify_test(ASCIIToUTF16("A man, a plan, a canal Panama"),
    542                              matches);
    543 
    544   ACMatchClassifications spans_a = classify_test.RunTest(ASCIIToUTF16("man"));
    545   // ACMatch spans should be: '--MMM------------------------'
    546   EXPECT_EQ("0,0,2,2,5,0", AutocompleteMatch::ClassificationsToString(spans_a));
    547 
    548   ACMatchClassifications spans_b = classify_test.RunTest(ASCIIToUTF16("man p"));
    549   // ACMatch spans should be: '--MMM----M-------------M-----'
    550   EXPECT_EQ("0,0,2,2,5,0,9,2,10,0,23,2,24,0",
    551             AutocompleteMatch::ClassificationsToString(spans_b));
    552 
    553   ACMatchClassifications spans_c =
    554       classify_test.RunTest(ASCIIToUTF16("man plan panama"));
    555   // ACMatch spans should be:'--MMM----MMMM----------MMMMMM'
    556   EXPECT_EQ("0,0,2,2,5,0,9,2,13,0,23,2",
    557             AutocompleteMatch::ClassificationsToString(spans_c));
    558 
    559   ClassifyTest classify_test2(ASCIIToUTF16("Yahoo! Sports - Sports News, "
    560       "Scores, Rumors, Fantasy Games, and more"), matches);
    561 
    562   ACMatchClassifications spans_d = classify_test2.RunTest(ASCIIToUTF16("ne"));
    563   // ACMatch spans should match first two letters of the "news".
    564   EXPECT_EQ("0,0,23,2,25,0",
    565             AutocompleteMatch::ClassificationsToString(spans_d));
    566 
    567   ACMatchClassifications spans_e =
    568       classify_test2.RunTest(ASCIIToUTF16("news r"));
    569   EXPECT_EQ("0,0,10,2,11,0,19,2,20,0,23,2,27,0,32,2,33,0,37,2,38,0,41,2,42,0,"
    570             "66,2,67,0", AutocompleteMatch::ClassificationsToString(spans_e));
    571 
    572   matches = AutocompleteMatch::ClassificationsFromString("0,1");
    573   ClassifyTest classify_test3(ASCIIToUTF16("livescore.goal.com"), matches);
    574 
    575   ACMatchClassifications spans_f = classify_test3.RunTest(ASCIIToUTF16("go"));
    576   // ACMatch spans should match first two letters of the "goal".
    577   EXPECT_EQ("0,1,10,3,12,1",
    578             AutocompleteMatch::ClassificationsToString(spans_f));
    579 
    580   matches = AutocompleteMatch::ClassificationsFromString("0,0,13,1");
    581   ClassifyTest classify_test4(ASCIIToUTF16("Email login: mail.somecorp.com"),
    582                               matches);
    583 
    584   ACMatchClassifications spans_g = classify_test4.RunTest(ASCIIToUTF16("ail"));
    585   EXPECT_EQ("0,0,2,2,5,0,13,1,14,3,17,1",
    586             AutocompleteMatch::ClassificationsToString(spans_g));
    587 
    588   ACMatchClassifications spans_h =
    589       classify_test4.RunTest(ASCIIToUTF16("lo log"));
    590   EXPECT_EQ("0,0,6,2,9,0,13,1",
    591             AutocompleteMatch::ClassificationsToString(spans_h));
    592 
    593   ACMatchClassifications spans_i =
    594       classify_test4.RunTest(ASCIIToUTF16("ail em"));
    595   // 'Email' and 'ail' should be matched.
    596   EXPECT_EQ("0,2,5,0,13,1,14,3,17,1",
    597             AutocompleteMatch::ClassificationsToString(spans_i));
    598 
    599   // Some web sites do not have a description.  If the string being searched is
    600   // empty, the classifications must also be empty: http://crbug.com/148647
    601   // Extra parens in the next line hack around C++03's "most vexing parse".
    602   class ClassifyTest classify_test5((base::string16()),
    603                                     ACMatchClassifications());
    604   ACMatchClassifications spans_j = classify_test5.RunTest(ASCIIToUTF16("man"));
    605   ASSERT_EQ(0U, spans_j.size());
    606 
    607   // Matches which end at beginning of classification merge properly.
    608   matches = AutocompleteMatch::ClassificationsFromString("0,4,9,0");
    609   ClassifyTest classify_test6(ASCIIToUTF16("html password example"), matches);
    610 
    611   // Extra space in the next string avoids having the string be a prefix of the
    612   // text above, which would allow for two different valid classification sets,
    613   // one of which uses two spans (the first of which would mark all of "html
    614   // pass" as a match) and one which uses four (which marks the individual words
    615   // as matches but not the space between them).  This way only the latter is
    616   // valid.
    617   ACMatchClassifications spans_k =
    618       classify_test6.RunTest(ASCIIToUTF16("html  pass"));
    619   EXPECT_EQ("0,6,4,4,5,6,9,0",
    620             AutocompleteMatch::ClassificationsToString(spans_k));
    621 
    622   // Multiple matches with both beginning and end at beginning of
    623   // classifications merge properly.
    624   matches = AutocompleteMatch::ClassificationsFromString("0,1,11,0");
    625   ClassifyTest classify_test7(ASCIIToUTF16("http://a.co is great"), matches);
    626 
    627   ACMatchClassifications spans_l =
    628       classify_test7.RunTest(ASCIIToUTF16("ht co"));
    629   EXPECT_EQ("0,3,2,1,9,3,11,0",
    630             AutocompleteMatch::ClassificationsToString(spans_l));
    631 }
    632 
    633 TEST_F(ShortcutsProviderTest, CalculateScore) {
    634   ShortcutsBackend::Shortcut shortcut(
    635       std::string(), ASCIIToUTF16("test"),
    636       ShortcutsBackend::Shortcut::MatchCore(
    637           ASCIIToUTF16("www.test.com"), GURL("http://www.test.com"),
    638           ASCIIToUTF16("www.test.com"),
    639           AutocompleteMatch::ClassificationsFromString("0,1,4,3,8,1"),
    640           ASCIIToUTF16("A test"),
    641           AutocompleteMatch::ClassificationsFromString("0,0,2,2"),
    642           content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL,
    643           base::string16()),
    644       base::Time::Now(), 1);
    645 
    646   // Maximal score.
    647   const int max_relevance = AutocompleteResult::kLowestDefaultScore - 1;
    648   const int kMaxScore = CalculateScore("test", shortcut, max_relevance);
    649 
    650   // Score decreases as percent of the match is decreased.
    651   int score_three_quarters = CalculateScore("tes", shortcut, max_relevance);
    652   EXPECT_LT(score_three_quarters, kMaxScore);
    653   int score_one_half = CalculateScore("te", shortcut, max_relevance);
    654   EXPECT_LT(score_one_half, score_three_quarters);
    655   int score_one_quarter = CalculateScore("t", shortcut, max_relevance);
    656   EXPECT_LT(score_one_quarter, score_one_half);
    657 
    658   // Should decay with time - one week.
    659   shortcut.last_access_time = base::Time::Now() - base::TimeDelta::FromDays(7);
    660   int score_week_old = CalculateScore("test", shortcut, max_relevance);
    661   EXPECT_LT(score_week_old, kMaxScore);
    662 
    663   // Should decay more in two weeks.
    664   shortcut.last_access_time = base::Time::Now() - base::TimeDelta::FromDays(14);
    665   int score_two_weeks_old = CalculateScore("test", shortcut, max_relevance);
    666   EXPECT_LT(score_two_weeks_old, score_week_old);
    667 
    668   // But not if it was activly clicked on. 2 hits slow decaying power.
    669   shortcut.number_of_hits = 2;
    670   shortcut.last_access_time = base::Time::Now() - base::TimeDelta::FromDays(14);
    671   int score_popular_two_weeks_old =
    672       CalculateScore("test", shortcut, max_relevance);
    673   EXPECT_LT(score_two_weeks_old, score_popular_two_weeks_old);
    674   // But still decayed.
    675   EXPECT_LT(score_popular_two_weeks_old, kMaxScore);
    676 
    677   // 3 hits slow decaying power even more.
    678   shortcut.number_of_hits = 3;
    679   shortcut.last_access_time = base::Time::Now() - base::TimeDelta::FromDays(14);
    680   int score_more_popular_two_weeks_old =
    681       CalculateScore("test", shortcut, max_relevance);
    682   EXPECT_LT(score_two_weeks_old, score_more_popular_two_weeks_old);
    683   EXPECT_LT(score_popular_two_weeks_old, score_more_popular_two_weeks_old);
    684   // But still decayed.
    685   EXPECT_LT(score_more_popular_two_weeks_old, kMaxScore);
    686 }
    687 
    688 TEST_F(ShortcutsProviderTest, DeleteMatch) {
    689   TestShortcutInfo shortcuts_to_test_delete[] = {
    690     { "BD85DBA2-8C29-49F9-84AE-48E1E90881F1", "delete", "www.deletetest.com/1",
    691       "http://www.deletetest.com/1", "http://www.deletetest.com/1", "0,2",
    692       "Erase this shortcut!", "0,0", content::PAGE_TRANSITION_TYPED,
    693       AutocompleteMatchType::HISTORY_URL, "", 1, 1},
    694     { "BD85DBA2-8C29-49F9-84AE-48E1E90881F2", "erase", "www.deletetest.com/1",
    695       "http://www.deletetest.com/1", "http://www.deletetest.com/1", "0,2",
    696       "Erase this shortcut!", "0,0", content::PAGE_TRANSITION_TYPED,
    697       AutocompleteMatchType::HISTORY_TITLE, "", 1, 1},
    698     { "BD85DBA2-8C29-49F9-84AE-48E1E90881F3", "keep", "www.deletetest.com/1/2",
    699       "http://www.deletetest.com/1/2", "http://www.deletetest.com/1/2", "0,2",
    700       "Keep this shortcut!", "0,0", content::PAGE_TRANSITION_TYPED,
    701       AutocompleteMatchType::HISTORY_TITLE, "", 1, 1},
    702     { "BD85DBA2-8C29-49F9-84AE-48E1E90881F4", "delete", "www.deletetest.com/2",
    703       "http://www.deletetest.com/2", "http://www.deletetest.com/2", "0,2",
    704       "Erase this shortcut!", "0,0", content::PAGE_TRANSITION_TYPED,
    705       AutocompleteMatchType::HISTORY_URL, "", 1, 1},
    706   };
    707 
    708   size_t original_shortcuts_count = backend_->shortcuts_map().size();
    709 
    710   FillData(shortcuts_to_test_delete, arraysize(shortcuts_to_test_delete));
    711 
    712   EXPECT_EQ(original_shortcuts_count + 4, backend_->shortcuts_map().size());
    713   EXPECT_FALSE(backend_->shortcuts_map().end() ==
    714                backend_->shortcuts_map().find(ASCIIToUTF16("delete")));
    715   EXPECT_FALSE(backend_->shortcuts_map().end() ==
    716                backend_->shortcuts_map().find(ASCIIToUTF16("erase")));
    717 
    718   AutocompleteMatch match(
    719       provider_.get(), 1200, true, AutocompleteMatchType::HISTORY_TITLE);
    720 
    721   match.destination_url = GURL(shortcuts_to_test_delete[0].destination_url);
    722   match.contents = ASCIIToUTF16(shortcuts_to_test_delete[0].contents);
    723   match.description = ASCIIToUTF16(shortcuts_to_test_delete[0].description);
    724 
    725   provider_->DeleteMatch(match);
    726 
    727   // shortcuts_to_test_delete[0] and shortcuts_to_test_delete[1] should be
    728   // deleted, but not shortcuts_to_test_delete[2] or
    729   // shortcuts_to_test_delete[3], which have different URLs.
    730   EXPECT_EQ(original_shortcuts_count + 2, backend_->shortcuts_map().size());
    731   EXPECT_FALSE(backend_->shortcuts_map().end() ==
    732                backend_->shortcuts_map().find(ASCIIToUTF16("delete")));
    733   EXPECT_TRUE(backend_->shortcuts_map().end() ==
    734               backend_->shortcuts_map().find(ASCIIToUTF16("erase")));
    735 
    736   match.destination_url = GURL(shortcuts_to_test_delete[3].destination_url);
    737   match.contents = ASCIIToUTF16(shortcuts_to_test_delete[3].contents);
    738   match.description = ASCIIToUTF16(shortcuts_to_test_delete[3].description);
    739 
    740   provider_->DeleteMatch(match);
    741   EXPECT_EQ(original_shortcuts_count + 1, backend_->shortcuts_map().size());
    742   EXPECT_TRUE(backend_->shortcuts_map().end() ==
    743               backend_->shortcuts_map().find(ASCIIToUTF16("delete")));
    744 }
    745 
    746 TEST_F(ShortcutsProviderTest, Extension) {
    747   // Try an input string that matches an extension URL.
    748   base::string16 text(ASCIIToUTF16("echo"));
    749   std::string expected_url(
    750       "chrome-extension://cedabbhfglmiikkmdgcpjdkocfcmbkee/?q=echo");
    751   ExpectedURLs expected_urls;
    752   expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
    753       expected_url, true));
    754   RunTest(text, false, expected_urls, expected_url, ASCIIToUTF16(" echo"));
    755 
    756   // Claim the extension has been unloaded.
    757   scoped_refptr<const extensions::Extension> extension =
    758       extensions::ExtensionBuilder()
    759           .SetManifest(extensions::DictionaryBuilder()
    760               .Set("name", "Echo")
    761               .Set("version", "1.0"))
    762           .SetID("cedabbhfglmiikkmdgcpjdkocfcmbkee")
    763           .Build();
    764   extensions::UnloadedExtensionInfo details(
    765       extension.get(), extensions::UnloadedExtensionInfo::REASON_UNINSTALL);
    766   content::NotificationService::current()->Notify(
    767       chrome::NOTIFICATION_EXTENSION_UNLOADED,
    768       content::Source<Profile>(&profile_),
    769       content::Details<extensions::UnloadedExtensionInfo>(&details));
    770 
    771   // Now the URL should have disappeared.
    772   RunTest(text, false, ExpectedURLs(), std::string(), base::string16());
    773 }
    774 
    775 }  // namespace history
    776