Home | History | Annotate | Download | only in autocomplete
      1 // Copyright (c) 2011 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 "base/memory/scoped_ptr.h"
      6 #include "base/message_loop.h"
      7 #include "base/string_number_conversions.h"
      8 #include "base/string_util.h"
      9 #include "base/utf_string_conversions.h"
     10 #include "chrome/browser/autocomplete/autocomplete.h"
     11 #include "chrome/browser/autocomplete/autocomplete_match.h"
     12 #include "chrome/browser/autocomplete/keyword_provider.h"
     13 #include "chrome/browser/autocomplete/search_provider.h"
     14 #include "chrome/browser/search_engines/template_url.h"
     15 #include "chrome/browser/search_engines/template_url_model.h"
     16 #include "chrome/test/testing_browser_process.h"
     17 #include "chrome/test/testing_browser_process_test.h"
     18 #include "chrome/test/testing_profile.h"
     19 #include "content/common/notification_observer.h"
     20 #include "content/common/notification_registrar.h"
     21 #include "content/common/notification_service.h"
     22 #include "testing/gtest/include/gtest/gtest.h"
     23 
     24 static std::ostream& operator<<(std::ostream& os,
     25                                 const AutocompleteResult::const_iterator& it) {
     26   return os << static_cast<const AutocompleteMatch*>(&(*it));
     27 }
     28 
     29 namespace {
     30 
     31 const size_t num_results_per_provider = 3;
     32 
     33 // Autocomplete provider that provides known results. Note that this is
     34 // refcounted so that it can also be a task on the message loop.
     35 class TestProvider : public AutocompleteProvider {
     36  public:
     37   TestProvider(int relevance, const string16& prefix)
     38       : AutocompleteProvider(NULL, NULL, ""),
     39         relevance_(relevance),
     40         prefix_(prefix) {
     41   }
     42 
     43   virtual void Start(const AutocompleteInput& input,
     44                      bool minimal_changes);
     45 
     46   void set_listener(ACProviderListener* listener) {
     47     listener_ = listener;
     48   }
     49 
     50  private:
     51   ~TestProvider() {}
     52 
     53   void Run();
     54 
     55   void AddResults(int start_at, int num);
     56 
     57   int relevance_;
     58   const string16 prefix_;
     59 };
     60 
     61 void TestProvider::Start(const AutocompleteInput& input,
     62                          bool minimal_changes) {
     63   if (minimal_changes)
     64     return;
     65 
     66   matches_.clear();
     67 
     68   // Generate one result synchronously, the rest later.
     69   AddResults(0, 1);
     70 
     71   if (input.matches_requested() == AutocompleteInput::ALL_MATCHES) {
     72     done_ = false;
     73     MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
     74         this, &TestProvider::Run));
     75   }
     76 }
     77 
     78 void TestProvider::Run() {
     79   DCHECK_GT(num_results_per_provider, 0U);
     80   AddResults(1, num_results_per_provider);
     81   done_ = true;
     82   DCHECK(listener_);
     83   listener_->OnProviderUpdate(true);
     84 }
     85 
     86 void TestProvider::AddResults(int start_at, int num) {
     87   for (int i = start_at; i < num; i++) {
     88     AutocompleteMatch match(this, relevance_ - i, false,
     89                             AutocompleteMatch::URL_WHAT_YOU_TYPED);
     90 
     91     match.fill_into_edit = prefix_ + UTF8ToUTF16(base::IntToString(i));
     92     match.destination_url = GURL(UTF16ToUTF8(match.fill_into_edit));
     93 
     94     match.contents = match.fill_into_edit;
     95     match.contents_class.push_back(
     96         ACMatchClassification(0, ACMatchClassification::NONE));
     97     match.description = match.fill_into_edit;
     98     match.description_class.push_back(
     99         ACMatchClassification(0, ACMatchClassification::NONE));
    100 
    101     matches_.push_back(match);
    102   }
    103 }
    104 
    105 class AutocompleteProviderTest : public testing::Test,
    106                                  public NotificationObserver {
    107  protected:
    108   void ResetControllerWithTestProviders(bool same_destinations);
    109 
    110   // Runs a query on the input "a", and makes sure both providers' input is
    111   // properly collected.
    112   void RunTest();
    113 
    114   void ResetControllerWithTestProvidersWithKeywordAndSearchProviders();
    115   void RunExactKeymatchTest(bool allow_exact_keyword_match);
    116 
    117   // These providers are owned by the controller once it's created.
    118   ACProviders providers_;
    119 
    120   AutocompleteResult result_;
    121 
    122  private:
    123   // NotificationObserver
    124   virtual void Observe(NotificationType type,
    125                        const NotificationSource& source,
    126                        const NotificationDetails& details);
    127 
    128   ScopedTestingBrowserProcess browser_process_;
    129 
    130   MessageLoopForUI message_loop_;
    131   scoped_ptr<AutocompleteController> controller_;
    132   NotificationRegistrar registrar_;
    133   TestingProfile profile_;
    134 };
    135 
    136 void AutocompleteProviderTest::ResetControllerWithTestProviders(
    137     bool same_destinations) {
    138   // Forget about any existing providers.  The controller owns them and will
    139   // Release() them below, when we delete it during the call to reset().
    140   providers_.clear();
    141 
    142   // Construct two new providers, with either the same or different prefixes.
    143   TestProvider* providerA = new TestProvider(num_results_per_provider,
    144                                              ASCIIToUTF16("http://a"));
    145   providerA->AddRef();
    146   providers_.push_back(providerA);
    147 
    148   TestProvider* providerB = new TestProvider(num_results_per_provider * 2,
    149       same_destinations ? ASCIIToUTF16("http://a") : ASCIIToUTF16("http://b"));
    150   providerB->AddRef();
    151   providers_.push_back(providerB);
    152 
    153   // Reset the controller to contain our new providers.
    154   AutocompleteController* controller = new AutocompleteController(providers_);
    155   controller_.reset(controller);
    156   providerA->set_listener(controller);
    157   providerB->set_listener(controller);
    158 
    159   // The providers don't complete synchronously, so listen for "result updated"
    160   // notifications.
    161   registrar_.Add(this, NotificationType::AUTOCOMPLETE_CONTROLLER_RESULT_READY,
    162                  NotificationService::AllSources());
    163 }
    164 
    165 void AutocompleteProviderTest::
    166     ResetControllerWithTestProvidersWithKeywordAndSearchProviders() {
    167   profile_.CreateTemplateURLModel();
    168 
    169   // Reset the default TemplateURL.
    170   TemplateURL* default_t_url = new TemplateURL();
    171   default_t_url->SetURL("http://defaultturl/{searchTerms}", 0, 0);
    172   TemplateURLModel* turl_model = profile_.GetTemplateURLModel();
    173   turl_model->Add(default_t_url);
    174   turl_model->SetDefaultSearchProvider(default_t_url);
    175   TemplateURLID default_provider_id = default_t_url->id();
    176   ASSERT_NE(0, default_provider_id);
    177 
    178   // Create another TemplateURL for KeywordProvider.
    179   TemplateURL* keyword_t_url = new TemplateURL();
    180   keyword_t_url->set_short_name(ASCIIToUTF16("k"));
    181   keyword_t_url->set_keyword(ASCIIToUTF16("k"));
    182   keyword_t_url->SetURL("http://keyword/{searchTerms}", 0, 0);
    183   profile_.GetTemplateURLModel()->Add(keyword_t_url);
    184   ASSERT_NE(0, keyword_t_url->id());
    185 
    186   // Forget about any existing providers.  The controller owns them and will
    187   // Release() them below, when we delete it during the call to reset().
    188   providers_.clear();
    189 
    190   // Create both a keyword and search provider, and add them in that order.
    191   // (Order is important; see comments in RunExactKeymatchTest().)
    192   AutocompleteProvider* keyword_provider = new KeywordProvider(NULL,
    193                                                                 &profile_);
    194   keyword_provider->AddRef();
    195   providers_.push_back(keyword_provider);
    196   AutocompleteProvider* search_provider = new SearchProvider(NULL, &profile_);
    197   search_provider->AddRef();
    198   providers_.push_back(search_provider);
    199 
    200   AutocompleteController* controller = new AutocompleteController(providers_);
    201   controller_.reset(controller);
    202 }
    203 
    204 void AutocompleteProviderTest::RunTest() {
    205   result_.Reset();
    206   controller_->Start(ASCIIToUTF16("a"), string16(), true, false, true,
    207                      AutocompleteInput::ALL_MATCHES);
    208 
    209   // The message loop will terminate when all autocomplete input has been
    210   // collected.
    211   MessageLoop::current()->Run();
    212 }
    213 
    214 void AutocompleteProviderTest::RunExactKeymatchTest(
    215     bool allow_exact_keyword_match) {
    216   // Send the controller input which exactly matches the keyword provider we
    217   // created in ResetControllerWithKeywordAndSearchProviders().  The default
    218   // match should thus be a keyword match iff |allow_exact_keyword_match| is
    219   // true.
    220   controller_->Start(ASCIIToUTF16("k test"), string16(), true, false,
    221                      allow_exact_keyword_match,
    222                      AutocompleteInput::SYNCHRONOUS_MATCHES);
    223   EXPECT_TRUE(controller_->done());
    224   // ResetControllerWithKeywordAndSearchProviders() adds the keyword provider
    225   // first, then the search provider.  So if the default match is a keyword
    226   // match, it will come from provider 0, otherwise from provider 1.
    227   EXPECT_EQ(providers_[allow_exact_keyword_match ? 0 : 1],
    228       controller_->result().default_match()->provider);
    229 }
    230 
    231 void AutocompleteProviderTest::Observe(NotificationType type,
    232                                        const NotificationSource& source,
    233                                        const NotificationDetails& details) {
    234   if (controller_->done()) {
    235     result_.CopyFrom(controller_->result());
    236     MessageLoop::current()->Quit();
    237   }
    238 }
    239 
    240 // Tests that the default selection is set properly when updating results.
    241 TEST_F(AutocompleteProviderTest, Query) {
    242   ResetControllerWithTestProviders(false);
    243   RunTest();
    244 
    245   // Make sure the default match gets set to the highest relevance match.  The
    246   // highest relevance matches should come from the second provider.
    247   EXPECT_EQ(num_results_per_provider * 2, result_.size());  // two providers
    248   ASSERT_NE(result_.end(), result_.default_match());
    249   EXPECT_EQ(providers_[1], result_.default_match()->provider);
    250 }
    251 
    252 TEST_F(AutocompleteProviderTest, RemoveDuplicates) {
    253   ResetControllerWithTestProviders(true);
    254   RunTest();
    255 
    256   // Make sure all the first provider's results were eliminated by the second
    257   // provider's.
    258   EXPECT_EQ(num_results_per_provider, result_.size());
    259   for (AutocompleteResult::const_iterator i(result_.begin());
    260        i != result_.end(); ++i)
    261     EXPECT_EQ(providers_[1], i->provider);
    262 }
    263 
    264 TEST_F(AutocompleteProviderTest, AllowExactKeywordMatch) {
    265   ResetControllerWithTestProvidersWithKeywordAndSearchProviders();
    266   RunExactKeymatchTest(true);
    267   RunExactKeymatchTest(false);
    268 }
    269 
    270 typedef TestingBrowserProcessTest AutocompleteTest;
    271 
    272 TEST_F(AutocompleteTest, InputType) {
    273   struct test_data {
    274     const string16 input;
    275     const AutocompleteInput::Type type;
    276   } input_cases[] = {
    277     { ASCIIToUTF16(""), AutocompleteInput::INVALID },
    278     { ASCIIToUTF16("?"), AutocompleteInput::FORCED_QUERY },
    279     { ASCIIToUTF16("?foo"), AutocompleteInput::FORCED_QUERY },
    280     { ASCIIToUTF16("?foo bar"), AutocompleteInput::FORCED_QUERY },
    281     { ASCIIToUTF16("?http://foo.com/bar"), AutocompleteInput::FORCED_QUERY },
    282     { ASCIIToUTF16("foo"), AutocompleteInput::UNKNOWN },
    283     { ASCIIToUTF16("foo.c"), AutocompleteInput::UNKNOWN },
    284     { ASCIIToUTF16("foo.com"), AutocompleteInput::URL },
    285     { ASCIIToUTF16("-.com"), AutocompleteInput::UNKNOWN },
    286     { ASCIIToUTF16("foo/bar"), AutocompleteInput::URL },
    287     { ASCIIToUTF16("foo;bar"), AutocompleteInput::QUERY },
    288     { ASCIIToUTF16("foo/bar baz"), AutocompleteInput::UNKNOWN },
    289     { ASCIIToUTF16("foo bar.com"), AutocompleteInput::QUERY },
    290     { ASCIIToUTF16("foo bar"), AutocompleteInput::QUERY },
    291     { ASCIIToUTF16("foo+bar"), AutocompleteInput::QUERY },
    292     { ASCIIToUTF16("foo+bar.com"), AutocompleteInput::UNKNOWN },
    293     { ASCIIToUTF16("\"foo:bar\""), AutocompleteInput::QUERY },
    294     { ASCIIToUTF16("link:foo.com"), AutocompleteInput::UNKNOWN },
    295     { ASCIIToUTF16("foo:81"), AutocompleteInput::URL },
    296     { ASCIIToUTF16("www.foo.com:81"), AutocompleteInput::URL },
    297     { ASCIIToUTF16("localhost:8080"), AutocompleteInput::URL },
    298     { ASCIIToUTF16("foo.com:123456"), AutocompleteInput::QUERY },
    299     { ASCIIToUTF16("foo.com:abc"), AutocompleteInput::QUERY },
    300     { ASCIIToUTF16("1.2.3.4:abc"), AutocompleteInput::QUERY },
    301     { ASCIIToUTF16("user (at) foo.com"), AutocompleteInput::UNKNOWN },
    302     { ASCIIToUTF16("user:pass@"), AutocompleteInput::UNKNOWN },
    303     { ASCIIToUTF16("user:pass@!foo.com"), AutocompleteInput::UNKNOWN },
    304     { ASCIIToUTF16("user:pass@foo"), AutocompleteInput::URL },
    305     { ASCIIToUTF16("user:pass (at) foo.c"), AutocompleteInput::URL },
    306     { ASCIIToUTF16("user:pass (at) foo.com"), AutocompleteInput::URL },
    307     { ASCIIToUTF16("user:pass (at) foo.com:81"), AutocompleteInput::URL },
    308     { ASCIIToUTF16("user:pass@foo:81"), AutocompleteInput::URL },
    309     { ASCIIToUTF16("1.2"), AutocompleteInput::UNKNOWN },
    310     { ASCIIToUTF16("1.2/45"), AutocompleteInput::UNKNOWN },
    311     { ASCIIToUTF16("1.2:45"), AutocompleteInput::UNKNOWN },
    312     { ASCIIToUTF16("user (at) 1.2:45"), AutocompleteInput::UNKNOWN },
    313     { ASCIIToUTF16("user:pass (at) 1.2:45"), AutocompleteInput::URL },
    314     { ASCIIToUTF16("ps/2 games"), AutocompleteInput::UNKNOWN },
    315     { ASCIIToUTF16("en.wikipedia.org/wiki/James Bond"),
    316         AutocompleteInput::URL },
    317     // In Chrome itself, mailto: will get handled by ShellExecute, but in
    318     // unittest mode, we don't have the data loaded in the external protocol
    319     // handler to know this.
    320     // { ASCIIToUTF16("mailto:abuse (at) foo.com"), AutocompleteInput::URL },
    321     { ASCIIToUTF16("view-source:http://www.foo.com/"), AutocompleteInput::URL },
    322     { ASCIIToUTF16("javascript:alert(\"Hey there!\");"),
    323         AutocompleteInput::URL },
    324 #if defined(OS_WIN)
    325     { ASCIIToUTF16("C:\\Program Files"), AutocompleteInput::URL },
    326     { ASCIIToUTF16("\\\\Server\\Folder\\File"), AutocompleteInput::URL },
    327 #endif  // defined(OS_WIN)
    328     { ASCIIToUTF16("http:foo"), AutocompleteInput::URL },
    329     { ASCIIToUTF16("http://foo"), AutocompleteInput::URL },
    330     { ASCIIToUTF16("http://foo.c"), AutocompleteInput::URL },
    331     { ASCIIToUTF16("http://foo.com"), AutocompleteInput::URL },
    332     { ASCIIToUTF16("http://foo_bar.com"), AutocompleteInput::URL },
    333     { ASCIIToUTF16("http://foo/bar baz"), AutocompleteInput::URL },
    334     { ASCIIToUTF16("http://-.com"), AutocompleteInput::UNKNOWN },
    335     { ASCIIToUTF16("http://_foo_.com"), AutocompleteInput::UNKNOWN },
    336     { ASCIIToUTF16("http://foo.com:abc"), AutocompleteInput::QUERY },
    337     { ASCIIToUTF16("http://foo.com:123456"), AutocompleteInput::QUERY },
    338     { ASCIIToUTF16("http://1.2.3.4:abc"), AutocompleteInput::QUERY },
    339     { ASCIIToUTF16("http:user (at) foo.com"), AutocompleteInput::URL },
    340     { ASCIIToUTF16("http://user@foo.com"), AutocompleteInput::URL },
    341     { ASCIIToUTF16("http:user:pass (at) foo.com"), AutocompleteInput::URL },
    342     { ASCIIToUTF16("http://user:pass@foo.com"), AutocompleteInput::URL },
    343     { ASCIIToUTF16("http://1.2"), AutocompleteInput::URL },
    344     { ASCIIToUTF16("http://1.2/45"), AutocompleteInput::URL },
    345     { ASCIIToUTF16("http:ps/2 games"), AutocompleteInput::URL },
    346     { ASCIIToUTF16("http://ps/2 games"), AutocompleteInput::URL },
    347     { ASCIIToUTF16("https://foo.com"), AutocompleteInput::URL },
    348     { ASCIIToUTF16("127.0.0.1"), AutocompleteInput::URL },
    349     { ASCIIToUTF16("127.0.1"), AutocompleteInput::UNKNOWN },
    350     { ASCIIToUTF16("127.0.1/"), AutocompleteInput::UNKNOWN },
    351     { ASCIIToUTF16("browser.tabs.closeButtons"), AutocompleteInput::UNKNOWN },
    352     { WideToUTF16(L"\u6d4b\u8bd5"), AutocompleteInput::UNKNOWN },
    353     { ASCIIToUTF16("[2001:]"), AutocompleteInput::QUERY },  // Not a valid IP
    354     { ASCIIToUTF16("[2001:dB8::1]"), AutocompleteInput::URL },
    355     { ASCIIToUTF16("192.168.0.256"),
    356         AutocompleteInput::QUERY },  // Invalid IPv4 literal.
    357     { ASCIIToUTF16("[foo.com]"),
    358         AutocompleteInput::QUERY },  // Invalid IPv6 literal.
    359   };
    360 
    361   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(input_cases); ++i) {
    362     SCOPED_TRACE(input_cases[i].input);
    363     AutocompleteInput input(input_cases[i].input, string16(), true, false,
    364                             true, AutocompleteInput::ALL_MATCHES);
    365     EXPECT_EQ(input_cases[i].type, input.type());
    366   }
    367 }
    368 
    369 TEST_F(AutocompleteTest, InputTypeWithDesiredTLD) {
    370   struct test_data {
    371     const string16 input;
    372     const AutocompleteInput::Type type;
    373   } input_cases[] = {
    374     { ASCIIToUTF16("401k"), AutocompleteInput::REQUESTED_URL },
    375     { ASCIIToUTF16("999999999999999"), AutocompleteInput::REQUESTED_URL },
    376   };
    377 
    378   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(input_cases); ++i) {
    379     AutocompleteInput input(input_cases[i].input, ASCIIToUTF16("com"), true,
    380                             false, true, AutocompleteInput::ALL_MATCHES);
    381     EXPECT_EQ(input_cases[i].type, input.type()) << "Input: " <<
    382         input_cases[i].input;
    383   }
    384 }
    385 
    386 // This tests for a regression where certain input in the omnibox caused us to
    387 // crash. As long as the test completes without crashing, we're fine.
    388 TEST_F(AutocompleteTest, InputCrash) {
    389   AutocompleteInput input(WideToUTF16(L"\uff65@s"), string16(), true, false,
    390                           true, AutocompleteInput::ALL_MATCHES);
    391 }
    392 
    393 // Test comparing matches relevance.
    394 TEST(AutocompleteMatch, MoreRelevant) {
    395   struct RelevantCases {
    396     int r1;
    397     int r2;
    398     bool expected_result;
    399   } cases[] = {
    400     {  10,   0, true  },
    401     {  10,  -5, true  },
    402     {  -5,  10, false },
    403     {   0,  10, false },
    404     { -10,  -5, false  },
    405     {  -5, -10, true },
    406   };
    407 
    408   AutocompleteMatch m1(NULL, 0, false, AutocompleteMatch::URL_WHAT_YOU_TYPED);
    409   AutocompleteMatch m2(NULL, 0, false, AutocompleteMatch::URL_WHAT_YOU_TYPED);
    410 
    411   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
    412     m1.relevance = cases[i].r1;
    413     m2.relevance = cases[i].r2;
    414     EXPECT_EQ(cases[i].expected_result,
    415               AutocompleteMatch::MoreRelevant(m1, m2));
    416   }
    417 }
    418 
    419 TEST(AutocompleteInput, ParseForEmphasizeComponent) {
    420   using url_parse::Component;
    421   Component kInvalidComponent(0, -1);
    422   struct test_data {
    423     const string16 input;
    424     const Component scheme;
    425     const Component host;
    426   } input_cases[] = {
    427     { ASCIIToUTF16(""), kInvalidComponent, kInvalidComponent },
    428     { ASCIIToUTF16("?"), kInvalidComponent, kInvalidComponent },
    429     { ASCIIToUTF16("?http://foo.com/bar"), kInvalidComponent,
    430         kInvalidComponent },
    431     { ASCIIToUTF16("foo/bar baz"), kInvalidComponent, Component(0, 3) },
    432     { ASCIIToUTF16("http://foo/bar baz"), Component(0, 4), Component(7, 3) },
    433     { ASCIIToUTF16("link:foo.com"), Component(0, 4), kInvalidComponent },
    434     { ASCIIToUTF16("www.foo.com:81"), kInvalidComponent, Component(0, 11) },
    435     { WideToUTF16(L"\u6d4b\u8bd5"), kInvalidComponent, Component(0, 2) },
    436     { ASCIIToUTF16("view-source:http://www.foo.com/"), Component(12, 4),
    437         Component(19, 11) },
    438     { ASCIIToUTF16("view-source:https://example.com/"),
    439       Component(12, 5), Component(20, 11) },
    440     { ASCIIToUTF16("view-source:www.foo.com"), kInvalidComponent,
    441         Component(12, 11) },
    442     { ASCIIToUTF16("view-source:"), Component(0, 11), kInvalidComponent },
    443     { ASCIIToUTF16("view-source:garbage"), kInvalidComponent,
    444         Component(12, 7) },
    445     { ASCIIToUTF16("view-source:http://http://foo"), Component(12, 4),
    446         Component(19, 4) },
    447     { ASCIIToUTF16("view-source:view-source:http://example.com/"),
    448         Component(12, 11), kInvalidComponent }
    449   };
    450 
    451   ScopedTestingBrowserProcess browser_process;
    452 
    453   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(input_cases); ++i) {
    454     Component scheme, host;
    455     AutocompleteInput::ParseForEmphasizeComponents(input_cases[i].input,
    456                                                    string16(),
    457                                                    &scheme,
    458                                                    &host);
    459     AutocompleteInput input(input_cases[i].input, string16(), true, false,
    460                             true, AutocompleteInput::ALL_MATCHES);
    461     EXPECT_EQ(input_cases[i].scheme.begin, scheme.begin) << "Input: " <<
    462         input_cases[i].input;
    463     EXPECT_EQ(input_cases[i].scheme.len, scheme.len) << "Input: " <<
    464         input_cases[i].input;
    465     EXPECT_EQ(input_cases[i].host.begin, host.begin) << "Input: " <<
    466         input_cases[i].input;
    467     EXPECT_EQ(input_cases[i].host.len, host.len) << "Input: " <<
    468         input_cases[i].input;
    469   }
    470 }
    471 
    472 }  // namespace
    473