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 <algorithm>
8
9#include "base/strings/string_util.h"
10#include "base/strings/utf_string_conversions.h"
11#include "chrome/browser/autocomplete/history_provider.h"
12#include "chrome/common/url_constants.h"
13#include "components/metrics/proto/omnibox_input_type.pb.h"
14#include "components/omnibox/autocomplete_input.h"
15#include "components/url_fixer/url_fixer.h"
16
17namespace {
18
19#if !defined(OS_ANDROID)
20// This list should be kept in sync with chrome/common/url_constants.h.
21// Only include useful sub-pages, confirmation alerts are not useful.
22const char* const kChromeSettingsSubPages[] = {
23  chrome::kAutofillSubPage,
24  chrome::kClearBrowserDataSubPage,
25  chrome::kContentSettingsSubPage,
26  chrome::kContentSettingsExceptionsSubPage,
27  chrome::kImportDataSubPage,
28  chrome::kLanguageOptionsSubPage,
29  chrome::kPasswordManagerSubPage,
30  chrome::kResetProfileSettingsSubPage,
31  chrome::kSearchEnginesSubPage,
32  chrome::kSyncSetupSubPage,
33#if defined(OS_CHROMEOS)
34  chrome::kInternetOptionsSubPage,
35#endif
36};
37#endif // !defined(OS_ANDROID)
38
39}  // namespace
40
41const int BuiltinProvider::kRelevance = 860;
42
43BuiltinProvider::BuiltinProvider()
44    : AutocompleteProvider(AutocompleteProvider::TYPE_BUILTIN) {
45  std::vector<std::string> builtins(
46      chrome::kChromeHostURLs,
47      chrome::kChromeHostURLs + chrome::kNumberOfChromeHostURLs);
48  std::sort(builtins.begin(), builtins.end());
49  for (std::vector<std::string>::iterator i(builtins.begin());
50       i != builtins.end(); ++i)
51    builtins_.push_back(base::ASCIIToUTF16(*i));
52
53#if !defined(OS_ANDROID)
54  base::string16 settings(base::ASCIIToUTF16(chrome::kChromeUISettingsHost) +
55                          base::ASCIIToUTF16("/"));
56  for (size_t i = 0; i < arraysize(kChromeSettingsSubPages); i++) {
57    builtins_.push_back(
58        settings + base::ASCIIToUTF16(kChromeSettingsSubPages[i]));
59  }
60#endif
61}
62
63void BuiltinProvider::Start(const AutocompleteInput& input,
64                            bool minimal_changes) {
65  matches_.clear();
66  if ((input.type() == metrics::OmniboxInputType::INVALID) ||
67      (input.type() == metrics::OmniboxInputType::FORCED_QUERY) ||
68      (input.type() == metrics::OmniboxInputType::QUERY))
69    return;
70
71  const size_t kAboutSchemeLength = strlen(url::kAboutScheme);
72  const base::string16 kAbout =
73      base::ASCIIToUTF16(url::kAboutScheme) +
74      base::ASCIIToUTF16(url::kStandardSchemeSeparator);
75  const base::string16 kChrome = base::ASCIIToUTF16(content::kChromeUIScheme) +
76      base::ASCIIToUTF16(url::kStandardSchemeSeparator);
77
78  const int kUrl = ACMatchClassification::URL;
79  const int kMatch = kUrl | ACMatchClassification::MATCH;
80
81  base::string16 text = input.text();
82  bool starting_chrome = StartsWith(kChrome, text, false);
83  if (starting_chrome || StartsWith(kAbout, text, false)) {
84    ACMatchClassifications styles;
85    // Highlight the input portion matching "chrome://"; or if the user has
86    // input "about:" (with optional slashes), highlight the whole "chrome://".
87    bool highlight = starting_chrome || text.length() > kAboutSchemeLength;
88    styles.push_back(ACMatchClassification(0, highlight ? kMatch : kUrl));
89    size_t offset = starting_chrome ? text.length() : kChrome.length();
90    if (highlight)
91      styles.push_back(ACMatchClassification(offset, kUrl));
92    // Include some common builtin chrome URLs as the user types the scheme.
93    AddMatch(base::ASCIIToUTF16(chrome::kChromeUIChromeURLsURL),
94             base::string16(), styles);
95#if !defined(OS_ANDROID)
96    AddMatch(base::ASCIIToUTF16(chrome::kChromeUISettingsURL),
97             base::string16(), styles);
98#endif
99    AddMatch(base::ASCIIToUTF16(chrome::kChromeUIVersionURL),
100             base::string16(), styles);
101  } else {
102    // Match input about: or chrome: URL input against builtin chrome URLs.
103    GURL url = url_fixer::FixupURL(base::UTF16ToUTF8(text), std::string());
104    // BuiltinProvider doesn't know how to suggest valid ?query or #fragment
105    // extensions to chrome: URLs.
106    if (url.SchemeIs(content::kChromeUIScheme) && url.has_host() &&
107        !url.has_query() && !url.has_ref()) {
108      // Suggest about:blank for substrings, taking URL fixup into account.
109      // Chrome does not support trailing slashes or paths for about:blank.
110      const base::string16 blank_host = base::ASCIIToUTF16("blank");
111      const base::string16 host = base::UTF8ToUTF16(url.host());
112      if (StartsWith(text, base::ASCIIToUTF16(url::kAboutScheme), false) &&
113          StartsWith(blank_host, host, false) && (url.path().length() <= 1) &&
114          !EndsWith(text, base::ASCIIToUTF16("/"), false)) {
115        ACMatchClassifications styles;
116        styles.push_back(ACMatchClassification(0, kMatch));
117        base::string16 match = base::ASCIIToUTF16(url::kAboutBlankURL);
118        // Measure the length of the matching host after the "about:" scheme.
119        const size_t corrected_length = kAboutSchemeLength + 1 + host.length();
120        if (blank_host.length() > host.length())
121          styles.push_back(ACMatchClassification(corrected_length, kUrl));
122        AddMatch(match, match.substr(corrected_length), styles);
123      }
124
125      // Include the path for sub-pages (e.g. "chrome://settings/browser").
126      base::string16 host_and_path = base::UTF8ToUTF16(url.host() + url.path());
127      base::TrimString(host_and_path, base::ASCIIToUTF16("/"), &host_and_path);
128      size_t match_length = kChrome.length() + host_and_path.length();
129      for (Builtins::const_iterator i(builtins_.begin());
130          (i != builtins_.end()) && (matches_.size() < kMaxMatches); ++i) {
131        if (StartsWith(*i, host_and_path, false)) {
132          ACMatchClassifications styles;
133          // Highlight the "chrome://" scheme, even for input "about:foo".
134          styles.push_back(ACMatchClassification(0, kMatch));
135          base::string16 match_string = kChrome + *i;
136          if (match_string.length() > match_length)
137            styles.push_back(ACMatchClassification(match_length, kUrl));
138          AddMatch(match_string, match_string.substr(match_length), styles);
139        }
140      }
141    }
142  }
143
144  for (size_t i = 0; i < matches_.size(); ++i)
145    matches_[i].relevance = kRelevance + matches_.size() - (i + 1);
146  if (!HistoryProvider::PreventInlineAutocomplete(input) &&
147      (matches_.size() == 1)) {
148    // If there's only one possible completion of the user's input and
149    // allowing completions is okay, give the match a high enough score to
150    // allow it to beat url-what-you-typed and be inlined.
151    matches_[0].relevance = 1250;
152    matches_[0].allowed_to_be_default_match = true;
153  }
154}
155
156BuiltinProvider::~BuiltinProvider() {}
157
158void BuiltinProvider::AddMatch(const base::string16& match_string,
159                               const base::string16& inline_completion,
160                               const ACMatchClassifications& styles) {
161  AutocompleteMatch match(this, kRelevance, false,
162                          AutocompleteMatchType::NAVSUGGEST);
163  match.fill_into_edit = match_string;
164  match.inline_autocompletion = inline_completion;
165  match.destination_url = GURL(match_string);
166  match.contents = match_string;
167  match.contents_class = styles;
168  matches_.push_back(match);
169}
170