text: // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "components/omnibox/browser/builtin_provider.h"
  
  #include <stddef.h>
  
  #include <algorithm>
  
  #include "base/strings/string_util.h"
  #include "base/strings/utf_string_conversions.h"
  #include "components/metrics/proto/omnibox_input_type.pb.h"
  #include "components/omnibox/browser/autocomplete_input.h"
  #include "components/omnibox/browser/autocomplete_provider_client.h"
  #include "components/omnibox/browser/history_provider.h"
  #include "components/url_formatter/url_fixer.h"
  
  const int BuiltinProvider::kRelevance = 860;
  
  BuiltinProvider::BuiltinProvider(AutocompleteProviderClient* client)
      : AutocompleteProvider(AutocompleteProvider::TYPE_BUILTIN),
        client_(client) {
    builtins_ = client_->GetBuiltinURLs();
  }
  
  void BuiltinProvider::Start(const AutocompleteInput& input,
                              bool minimal_changes) {
    matches_.clear();
    if (input.from_omnibox_focus() ||
        (input.type() == metrics::OmniboxInputType::INVALID) ||
        (input.type() == metrics::OmniboxInputType::QUERY))
      return;
  
    const size_t kAboutSchemeLength = strlen(url::kAboutScheme);
    const base::string16 kAbout =
        base::ASCIIToUTF16(url::kAboutScheme) +
        base::ASCIIToUTF16(url::kStandardSchemeSeparator);
    const base::string16 embedderAbout =
        base::UTF8ToUTF16(client_->GetEmbedderRepresentationOfAboutScheme()) +
        base::ASCIIToUTF16(url::kStandardSchemeSeparator);
  
    const int kUrl = ACMatchClassification::URL;
    const int kMatch = kUrl | ACMatchClassification::MATCH;
  
    const base::string16 text = input.text();
    bool starting_about = base::StartsWith(embedderAbout, text,
                                           base::CompareCase::INSENSITIVE_ASCII);
    if (starting_about ||
        base::StartsWith(kAbout, text, base::CompareCase::INSENSITIVE_ASCII)) {
      ACMatchClassifications styles;
      // Highlight the input portion matching |embedderAbout|; or if the user has
      // input "about:" (with optional slashes), highlight the whole
      // |embedderAbout|.
      bool highlight = starting_about || text.length() > kAboutSchemeLength;
      styles.push_back(ACMatchClassification(0, highlight ? kMatch : kUrl));
      size_t offset = starting_about ? text.length() : embedderAbout.length();
      if (highlight)
        styles.push_back(ACMatchClassification(offset, kUrl));
      // Include some common builtin URLs as the user types the scheme.
      for (base::string16 url : client_->GetBuiltinsToProvideAsUserTypes())
        AddMatch(url, base::string16(), styles);
    } else {
      // Match input about: or |embedderAbout| URL input against builtin URLs.
      GURL url = url_formatter::FixupURL(base::UTF16ToUTF8(text), std::string());
      const bool text_ends_with_slash =
          base::EndsWith(text, base::ASCIIToUTF16("/"),
                         base::CompareCase::SENSITIVE);
      // BuiltinProvider doesn't know how to suggest valid ?query or #fragment
      // extensions to builtin URLs.
      if (url.SchemeIs(client_->GetEmbedderRepresentationOfAboutScheme()) &&
          url.has_host() && !url.has_query() && !url.has_ref()) {
        // Suggest about:blank for substrings, taking URL fixup into account.
        // Chrome does not support trailing slashes or paths for about:blank.
        const base::string16 blank_host = base::ASCIIToUTF16("blank");
        const base::string16 host = base::UTF8ToUTF16(url.host());
        if (base::StartsWith(text, base::ASCIIToUTF16(url::kAboutScheme),
                             base::CompareCase::INSENSITIVE_ASCII) &&
            base::StartsWith(blank_host, host,
                             base::CompareCase::INSENSITIVE_ASCII) &&
            (url.path().length() <= 1) && !text_ends_with_slash) {
          ACMatchClassifications styles;
          styles.push_back(ACMatchClassification(0, kMatch));
          base::string16 match = base::ASCIIToUTF16(url::kAboutBlankURL);
          // Measure the length of the matching host after the "about:" scheme.
          const size_t corrected_length = kAboutSchemeLength + 1 + host.length();
          if (blank_host.length() > host.length())
            styles.push_back(ACMatchClassification(corrected_length, kUrl));
          AddMatch(match, match.substr(corrected_length), styles);
        }
  
        // Include the path for sub-pages (e.g. "chrome://settings/browser").
        base::string16 host_and_path = base::UTF8ToUTF16(url.host() + url.path());
        base::TrimString(host_and_path, base::ASCIIToUTF16("/"), &host_and_path);
        size_t match_length = embedderAbout.length() + host_and_path.length();
        for (Builtins::const_iterator i(builtins_.begin());
            (i != builtins_.end()) && (matches_.size() < kMaxMatches); ++i) {
          if (base::StartsWith(*i, host_and_path,
                               base::CompareCase::INSENSITIVE_ASCII)) {
            ACMatchClassifications styles;
            // Highlight |embedderAbout|, even for input "about:foo".
            styles.push_back(ACMatchClassification(0, kMatch));
            base::string16 match_string = embedderAbout + *i;
            if (match_string.length() > match_length)
              styles.push_back(ACMatchClassification(match_length, kUrl));
            // FixupURL() may have dropped a trailing slash on the user's input.
            // Ensure that in that case, we don't inline autocomplete unless the
            // autocompletion restores the slash.  This prevents us from e.g.
            // trying to add a 'y' to an input like "chrome://histor/".
            base::string16 inline_autocompletion(
                match_string.substr(match_length));
            if (text_ends_with_slash && !base::StartsWith(
                match_string.substr(match_length), base::ASCIIToUTF16("/"),
                base::CompareCase::INSENSITIVE_ASCII))
              inline_autocompletion = base::string16();
            AddMatch(match_string, inline_autocompletion, styles);
          }
        }
      }
    }
  
    for (size_t i = 0; i < matches_.size(); ++i)
      matches_[i].relevance = kRelevance + matches_.size() - (i + 1);
    if (!HistoryProvider::PreventInlineAutocomplete(input) &&
        (matches_.size() == 1) && !matches_[0].inline_autocompletion.empty()) {
      // If there's only one possible completion of the user's input and
      // allowing completions is okay, give the match a high enough score to
      // allow it to beat url-what-you-typed and be inlined.
      matches_[0].relevance = 1250;
      matches_[0].allowed_to_be_default_match = true;
    }
  }
  
  BuiltinProvider::~BuiltinProvider() {}
  
  void BuiltinProvider::AddMatch(const base::string16& match_string,
                                 const base::string16& inline_completion,
                                 const ACMatchClassifications& styles) {
    AutocompleteMatch match(this, kRelevance, false,
                            AutocompleteMatchType::NAVSUGGEST);
    match.fill_into_edit = match_string;
    match.inline_autocompletion = inline_completion;
    match.destination_url = GURL(match_string);
    match.contents = match_string;
    match.contents_class = styles;
    matches_.push_back(match);
  }
  