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/builtin_provider.h"
6
7#include "base/format_macros.h"
8#include "base/strings/stringprintf.h"
9#include "base/strings/utf_string_conversions.h"
10#include "chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.h"
11#include "chrome/common/url_constants.h"
12#include "components/metrics/proto/omnibox_event.pb.h"
13#include "components/omnibox/autocomplete_input.h"
14#include "components/omnibox/autocomplete_match.h"
15#include "components/omnibox/autocomplete_provider.h"
16#include "testing/gtest/include/gtest/gtest.h"
17#include "url/gurl.h"
18
19using base::ASCIIToUTF16;
20
21class BuiltinProviderTest : public testing::Test {
22 protected:
23  struct TestData {
24    const base::string16 input;
25    const size_t num_results;
26    const GURL output[3];
27  };
28
29  BuiltinProviderTest() : provider_(NULL) {}
30  virtual ~BuiltinProviderTest() {}
31
32  virtual void SetUp() OVERRIDE { provider_ = new BuiltinProvider(); }
33  virtual void TearDown() OVERRIDE { provider_ = NULL; }
34
35  void RunTest(const TestData cases[], size_t num_cases) {
36    ACMatches matches;
37    for (size_t i = 0; i < num_cases; ++i) {
38      SCOPED_TRACE(base::StringPrintf(
39          "case %" PRIuS ": %s", i, base::UTF16ToUTF8(cases[i].input).c_str()));
40      const AutocompleteInput input(cases[i].input, base::string16::npos,
41                                    base::string16(), GURL(),
42                                    metrics::OmniboxEventProto::INVALID_SPEC,
43                                    true, false, true, true,
44                                    ChromeAutocompleteSchemeClassifier(NULL));
45      provider_->Start(input, false);
46      EXPECT_TRUE(provider_->done());
47      matches = provider_->matches();
48      EXPECT_EQ(cases[i].num_results, matches.size());
49      if (matches.size() == cases[i].num_results) {
50        for (size_t j = 0; j < cases[i].num_results; ++j) {
51          EXPECT_EQ(cases[i].output[j], matches[j].destination_url);
52          EXPECT_FALSE(matches[j].allowed_to_be_default_match);
53        }
54      }
55    }
56  }
57
58 private:
59  scoped_refptr<BuiltinProvider> provider_;
60
61  DISALLOW_COPY_AND_ASSIGN(BuiltinProviderTest);
62};
63
64#if !defined(OS_ANDROID)
65TEST_F(BuiltinProviderTest, TypingScheme) {
66  const base::string16 kAbout = ASCIIToUTF16(url::kAboutScheme);
67  const base::string16 kChrome = ASCIIToUTF16(content::kChromeUIScheme);
68  const base::string16 kSeparator1 = ASCIIToUTF16(":");
69  const base::string16 kSeparator2 = ASCIIToUTF16(":/");
70  const base::string16 kSeparator3 =
71      ASCIIToUTF16(url::kStandardSchemeSeparator);
72
73  // These default URLs should correspond with those in BuiltinProvider::Start.
74  const GURL kURL1 = GURL(chrome::kChromeUIChromeURLsURL);
75  const GURL kURL2 = GURL(chrome::kChromeUISettingsURL);
76  const GURL kURL3 = GURL(chrome::kChromeUIVersionURL);
77
78  TestData typing_scheme_cases[] = {
79    // Typing an unrelated scheme should give nothing.
80    {ASCIIToUTF16("h"),        0, {}},
81    {ASCIIToUTF16("http"),     0, {}},
82    {ASCIIToUTF16("file"),     0, {}},
83    {ASCIIToUTF16("abouz"),    0, {}},
84    {ASCIIToUTF16("aboutt"),   0, {}},
85    {ASCIIToUTF16("aboutt:"),  0, {}},
86    {ASCIIToUTF16("chroma"),   0, {}},
87    {ASCIIToUTF16("chromee"),  0, {}},
88    {ASCIIToUTF16("chromee:"), 0, {}},
89
90    // Typing a portion of about:// should give the default urls.
91    {kAbout.substr(0, 1),      3, {kURL1, kURL2, kURL3}},
92    {ASCIIToUTF16("A"),        3, {kURL1, kURL2, kURL3}},
93    {kAbout,                   3, {kURL1, kURL2, kURL3}},
94    {kAbout + kSeparator1,     3, {kURL1, kURL2, kURL3}},
95    {kAbout + kSeparator2,     3, {kURL1, kURL2, kURL3}},
96    {kAbout + kSeparator3,     3, {kURL1, kURL2, kURL3}},
97    {ASCIIToUTF16("aBoUT://"), 3, {kURL1, kURL2, kURL3}},
98
99    // Typing a portion of chrome:// should give the default urls.
100    {kChrome.substr(0, 1),      3, {kURL1, kURL2, kURL3}},
101    {ASCIIToUTF16("C"),         3, {kURL1, kURL2, kURL3}},
102    {kChrome,                   3, {kURL1, kURL2, kURL3}},
103    {kChrome + kSeparator1,     3, {kURL1, kURL2, kURL3}},
104    {kChrome + kSeparator2,     3, {kURL1, kURL2, kURL3}},
105    {kChrome + kSeparator3,     3, {kURL1, kURL2, kURL3}},
106    {ASCIIToUTF16("ChRoMe://"), 3, {kURL1, kURL2, kURL3}},
107  };
108
109  RunTest(typing_scheme_cases, arraysize(typing_scheme_cases));
110}
111#else // Android uses a subset of the URLs
112TEST_F(BuiltinProviderTest, TypingScheme) {
113  const base::string16 kAbout = ASCIIToUTF16(url::kAboutScheme);
114  const base::string16 kChrome = ASCIIToUTF16(content::kChromeUIScheme);
115  const base::string16 kSeparator1 = ASCIIToUTF16(":");
116  const base::string16 kSeparator2 = ASCIIToUTF16(":/");
117  const base::string16 kSeparator3 =
118      ASCIIToUTF16(url::kStandardSchemeSeparator);
119
120  // These default URLs should correspond with those in BuiltinProvider::Start.
121  const GURL kURL1 = GURL(chrome::kChromeUIChromeURLsURL);
122  const GURL kURL2 = GURL(chrome::kChromeUIVersionURL);
123
124  TestData typing_scheme_cases[] = {
125    // Typing an unrelated scheme should give nothing.
126    {ASCIIToUTF16("h"),        0, {}},
127    {ASCIIToUTF16("http"),     0, {}},
128    {ASCIIToUTF16("file"),     0, {}},
129    {ASCIIToUTF16("abouz"),    0, {}},
130    {ASCIIToUTF16("aboutt"),   0, {}},
131    {ASCIIToUTF16("aboutt:"),  0, {}},
132    {ASCIIToUTF16("chroma"),   0, {}},
133    {ASCIIToUTF16("chromee"),  0, {}},
134    {ASCIIToUTF16("chromee:"), 0, {}},
135
136    // Typing a portion of about:// should give the default urls.
137    {kAbout.substr(0, 1),      2, {kURL1, kURL2}},
138    {ASCIIToUTF16("A"),        2, {kURL1, kURL2}},
139    {kAbout,                   2, {kURL1, kURL2}},
140    {kAbout + kSeparator1,     2, {kURL1, kURL2}},
141    {kAbout + kSeparator2,     2, {kURL1, kURL2}},
142    {kAbout + kSeparator3,     2, {kURL1, kURL2}},
143    {ASCIIToUTF16("aBoUT://"), 2, {kURL1, kURL2}},
144
145    // Typing a portion of chrome:// should give the default urls.
146    {kChrome.substr(0, 1),      2, {kURL1, kURL2}},
147    {ASCIIToUTF16("C"),         2, {kURL1, kURL2}},
148    {kChrome,                   2, {kURL1, kURL2}},
149    {kChrome + kSeparator1,     2, {kURL1, kURL2}},
150    {kChrome + kSeparator2,     2, {kURL1, kURL2}},
151    {kChrome + kSeparator3,     2, {kURL1, kURL2}},
152    {ASCIIToUTF16("ChRoMe://"), 2, {kURL1, kURL2}},
153  };
154
155  RunTest(typing_scheme_cases, arraysize(typing_scheme_cases));
156}
157#endif
158
159TEST_F(BuiltinProviderTest, NonChromeURLs) {
160  TestData non_chrome_url_cases[] = {
161    // Typing an unrelated scheme should give nothing.
162    {ASCIIToUTF16("g@rb@g3"),                      0, {}},
163    {ASCIIToUTF16("www.google.com"),               0, {}},
164    {ASCIIToUTF16("http:www.google.com"),          0, {}},
165    {ASCIIToUTF16("http://www.google.com"),        0, {}},
166    {ASCIIToUTF16("file:filename"),                0, {}},
167    {ASCIIToUTF16("scheme:"),                      0, {}},
168    {ASCIIToUTF16("scheme://"),                    0, {}},
169    {ASCIIToUTF16("scheme://host"),                0, {}},
170    {ASCIIToUTF16("scheme:host/path?query#ref"),   0, {}},
171    {ASCIIToUTF16("scheme://host/path?query#ref"), 0, {}},
172  };
173
174  RunTest(non_chrome_url_cases, arraysize(non_chrome_url_cases));
175}
176
177TEST_F(BuiltinProviderTest, ChromeURLs) {
178  const base::string16 kAbout = ASCIIToUTF16(url::kAboutScheme);
179  const base::string16 kChrome = ASCIIToUTF16(content::kChromeUIScheme);
180  const base::string16 kSeparator1 = ASCIIToUTF16(":");
181  const base::string16 kSeparator2 = ASCIIToUTF16(":/");
182  const base::string16 kSeparator3 =
183      ASCIIToUTF16(url::kStandardSchemeSeparator);
184
185  // This makes assumptions about the chrome URLs listed by the BuiltinProvider.
186  // Currently they are derived from chrome::kChromeHostURLs[].
187  const base::string16 kHostM1 =
188      ASCIIToUTF16(content::kChromeUIMediaInternalsHost);
189  const base::string16 kHostM2 =
190      ASCIIToUTF16(chrome::kChromeUIMemoryHost);
191  const base::string16 kHostM3 =
192      ASCIIToUTF16(chrome::kChromeUIMemoryInternalsHost);
193  const GURL kURLM1 = GURL(kChrome + kSeparator3 + kHostM1);
194  const GURL kURLM2 = GURL(kChrome + kSeparator3 + kHostM2);
195  const GURL kURLM3 = GURL(kChrome + kSeparator3 + kHostM3);
196
197  TestData chrome_url_cases[] = {
198    // Typing an about URL with an unknown host should give nothing.
199    {kAbout + kSeparator1 + ASCIIToUTF16("host"), 0, {}},
200    {kAbout + kSeparator2 + ASCIIToUTF16("host"), 0, {}},
201    {kAbout + kSeparator3 + ASCIIToUTF16("host"), 0, {}},
202
203    // Typing a chrome URL with an unknown host should give nothing.
204    {kChrome + kSeparator1 + ASCIIToUTF16("host"), 0, {}},
205    {kChrome + kSeparator2 + ASCIIToUTF16("host"), 0, {}},
206    {kChrome + kSeparator3 + ASCIIToUTF16("host"), 0, {}},
207
208    // Typing an about URL should provide matching URLs.
209    {kAbout + kSeparator1 + kHostM1.substr(0, 1), 3, {kURLM1, kURLM2, kURLM3}},
210    {kAbout + kSeparator2 + kHostM1.substr(0, 2), 3, {kURLM1, kURLM2, kURLM3}},
211    {kAbout + kSeparator3 + kHostM1.substr(0, 3), 1, {kURLM1}},
212    {kAbout + kSeparator3 + kHostM2.substr(0, 3), 2, {kURLM2, kURLM3}},
213    {kAbout + kSeparator3 + kHostM1,              1, {kURLM1}},
214    {kAbout + kSeparator2 + kHostM2,              2, {kURLM2, kURLM3}},
215    {kAbout + kSeparator2 + kHostM3,              1, {kURLM3}},
216
217    // Typing a chrome URL should provide matching URLs.
218    {kChrome + kSeparator1 + kHostM1.substr(0, 1), 3, {kURLM1, kURLM2, kURLM3}},
219    {kChrome + kSeparator2 + kHostM1.substr(0, 2), 3, {kURLM1, kURLM2, kURLM3}},
220    {kChrome + kSeparator3 + kHostM1.substr(0, 3), 1, {kURLM1}},
221    {kChrome + kSeparator3 + kHostM2.substr(0, 3), 2, {kURLM2, kURLM3}},
222    {kChrome + kSeparator3 + kHostM1,              1, {kURLM1}},
223    {kChrome + kSeparator2 + kHostM2,              2, {kURLM2, kURLM3}},
224    {kChrome + kSeparator2 + kHostM3,              1, {kURLM3}},
225  };
226
227  RunTest(chrome_url_cases, arraysize(chrome_url_cases));
228}
229
230TEST_F(BuiltinProviderTest, AboutBlank) {
231  const base::string16 kAbout = ASCIIToUTF16(url::kAboutScheme);
232  const base::string16 kChrome = ASCIIToUTF16(content::kChromeUIScheme);
233  const base::string16 kAboutBlank = ASCIIToUTF16(url::kAboutBlankURL);
234  const base::string16 kBlank = ASCIIToUTF16("blank");
235  const base::string16 kSeparator1 =
236      ASCIIToUTF16(url::kStandardSchemeSeparator);
237  const base::string16 kSeparator2 = ASCIIToUTF16(":///");
238  const base::string16 kSeparator3 = ASCIIToUTF16(";///");
239
240  const GURL kURLBlob = GURL(kChrome + kSeparator1 +
241                             ASCIIToUTF16(content::kChromeUIBlobInternalsHost));
242  const GURL kURLBlank = GURL(kAboutBlank);
243
244  TestData about_blank_cases[] = {
245    // Typing an about:blank prefix should yield about:blank, among other URLs.
246    {kAboutBlank.substr(0, 8), 2, {kURLBlank, kURLBlob}},
247    {kAboutBlank.substr(0, 9), 1, {kURLBlank}},
248
249    // Using any separator that is supported by fixup should yield about:blank.
250    // For now, BuiltinProvider does not suggest url-what-you-typed matches for
251    // for about:blank; check "about:blan" and "about;blan" substrings instead.
252    {kAbout + kSeparator2.substr(0, 1) + kBlank.substr(0, 4), 1, {kURLBlank}},
253    {kAbout + kSeparator2.substr(0, 2) + kBlank,              1, {kURLBlank}},
254    {kAbout + kSeparator2.substr(0, 3) + kBlank,              1, {kURLBlank}},
255    {kAbout + kSeparator2 + kBlank,                           1, {kURLBlank}},
256    {kAbout + kSeparator3.substr(0, 1) + kBlank.substr(0, 4), 1, {kURLBlank}},
257    {kAbout + kSeparator3.substr(0, 2) + kBlank,              1, {kURLBlank}},
258    {kAbout + kSeparator3.substr(0, 3) + kBlank,              1, {kURLBlank}},
259    {kAbout + kSeparator3 + kBlank,                           1, {kURLBlank}},
260
261    // Using the chrome scheme should not yield about:blank.
262    {kChrome + kSeparator1.substr(0, 1) + kBlank, 0, {}},
263    {kChrome + kSeparator1.substr(0, 2) + kBlank, 0, {}},
264    {kChrome + kSeparator1.substr(0, 3) + kBlank, 0, {}},
265    {kChrome + kSeparator1 + kBlank,              0, {}},
266
267    // Adding trailing text should not yield about:blank.
268    {kAboutBlank + ASCIIToUTF16("/"),  0, {}},
269    {kAboutBlank + ASCIIToUTF16("/p"), 0, {}},
270    {kAboutBlank + ASCIIToUTF16("x"),  0, {}},
271    {kAboutBlank + ASCIIToUTF16("?q"), 0, {}},
272    {kAboutBlank + ASCIIToUTF16("#r"), 0, {}},
273
274    // Interrupting "blank" with conflicting text should not yield about:blank.
275    {kAboutBlank.substr(0, 9) + ASCIIToUTF16("/"),  0, {}},
276    {kAboutBlank.substr(0, 9) + ASCIIToUTF16("/p"), 0, {}},
277    {kAboutBlank.substr(0, 9) + ASCIIToUTF16("x"),  0, {}},
278    {kAboutBlank.substr(0, 9) + ASCIIToUTF16("?q"), 0, {}},
279    {kAboutBlank.substr(0, 9) + ASCIIToUTF16("#r"), 0, {}},
280  };
281
282  RunTest(about_blank_cases, arraysize(about_blank_cases));
283}
284
285#if !defined(OS_ANDROID)
286// Disabled on Android where we use native UI instead of chrome://settings.
287TEST_F(BuiltinProviderTest, ChromeSettingsSubpages) {
288  // This makes assumptions about the chrome URLs listed by the BuiltinProvider.
289  // Currently they are derived from chrome::kChromeHostURLs[].
290  const base::string16 kSettings = ASCIIToUTF16(chrome::kChromeUISettingsURL);
291  const base::string16 kDefaultPage1 = ASCIIToUTF16(chrome::kAutofillSubPage);
292  const base::string16 kDefaultPage2 =
293      ASCIIToUTF16(chrome::kClearBrowserDataSubPage);
294  const GURL kDefaultURL1 = GURL(kSettings + kDefaultPage1);
295  const GURL kDefaultURL2 = GURL(kSettings + kDefaultPage2);
296  const base::string16 kPage1 = ASCIIToUTF16(chrome::kSearchEnginesSubPage);
297  const base::string16 kPage2 = ASCIIToUTF16(chrome::kSyncSetupSubPage);
298  const GURL kURL1 = GURL(kSettings + kPage1);
299  const GURL kURL2 = GURL(kSettings + kPage2);
300
301  TestData settings_subpage_cases[] = {
302    // Typing the settings path should show settings and the first two subpages.
303    {kSettings, 3, {GURL(kSettings), kDefaultURL1, kDefaultURL2}},
304
305    // Typing a subpage path should return the appropriate results.
306    {kSettings + kPage1.substr(0, 1),                   2, {kURL1, kURL2}},
307    {kSettings + kPage1.substr(0, 2),                   1, {kURL1}},
308    {kSettings + kPage1.substr(0, kPage1.length() - 1), 1, {kURL1}},
309    {kSettings + kPage1,                                1, {kURL1}},
310    {kSettings + kPage2,                                1, {kURL2}},
311  };
312
313  RunTest(settings_subpage_cases, arraysize(settings_subpage_cases));
314}
315#endif
316