• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
operator <<(std::ostream & os,const AutocompleteResult::const_iterator & it)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:
TestProvider(int relevance,const string16 & prefix)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 
set_listener(ACProviderListener * listener)46   void set_listener(ACProviderListener* listener) {
47     listener_ = listener;
48   }
49 
50  private:
~TestProvider()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 
Start(const AutocompleteInput & input,bool minimal_changes)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 
Run()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 
AddResults(int start_at,int num)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 
ResetControllerWithTestProviders(bool same_destinations)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::
ResetControllerWithTestProvidersWithKeywordAndSearchProviders()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 
RunTest()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 
RunExactKeymatchTest(bool allow_exact_keyword_match)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 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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.
TEST_F(AutocompleteProviderTest,Query)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 
TEST_F(AutocompleteProviderTest,RemoveDuplicates)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 
TEST_F(AutocompleteProviderTest,AllowExactKeywordMatch)264 TEST_F(AutocompleteProviderTest, AllowExactKeywordMatch) {
265   ResetControllerWithTestProvidersWithKeywordAndSearchProviders();
266   RunExactKeymatchTest(true);
267   RunExactKeymatchTest(false);
268 }
269 
270 typedef TestingBrowserProcessTest AutocompleteTest;
271 
TEST_F(AutocompleteTest,InputType)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@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@foo.c"), AutocompleteInput::URL },
306     { ASCIIToUTF16("user:pass@foo.com"), AutocompleteInput::URL },
307     { ASCIIToUTF16("user:pass@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@1.2:45"), AutocompleteInput::UNKNOWN },
313     { ASCIIToUTF16("user:pass@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@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@foo.com"), AutocompleteInput::URL },
340     { ASCIIToUTF16("http://user@foo.com"), AutocompleteInput::URL },
341     { ASCIIToUTF16("http:user:pass@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 
TEST_F(AutocompleteTest,InputTypeWithDesiredTLD)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.
TEST_F(AutocompleteTest,InputCrash)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.
TEST(AutocompleteMatch,MoreRelevant)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 
TEST(AutocompleteInput,ParseForEmphasizeComponent)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