| Index: chrome/browser/autocomplete/history_url_provider.cc
|
| ===================================================================
|
| --- chrome/browser/autocomplete/history_url_provider.cc (revision 96850)
|
| +++ chrome/browser/autocomplete/history_url_provider.cc (working copy)
|
| @@ -26,18 +26,141 @@
|
| #include "googleurl/src/url_util.h"
|
| #include "net/base/net_util.h"
|
|
|
| -using base::Time;
|
| -using base::TimeDelta;
|
| -using base::TimeTicks;
|
| -using history::Prefix;
|
| -using history::Prefixes;
|
| -using history::HistoryMatch;
|
| -using history::HistoryMatches;
|
| +namespace {
|
|
|
| -namespace history {
|
| +// Ensures that |matches| contains an entry for |info|, which may mean adding a
|
| +// new such entry (using |input_location| and |match_in_scheme|).
|
| +//
|
| +// If |promote| is true, this also ensures the entry is the first element in
|
| +// |matches|, moving or adding it to the front as appropriate. When |promote|
|
| +// is false, existing matches are left in place, and newly added matches are
|
| +// placed at the back.
|
| +void EnsureMatchPresent(const history::URLRow& info,
|
| + size_t input_location,
|
| + bool match_in_scheme,
|
| + history::HistoryMatches* matches,
|
| + bool promote) {
|
| + // |matches| may already have an entry for this.
|
| + for (history::HistoryMatches::iterator i(matches->begin());
|
| + i != matches->end(); ++i) {
|
| + if (i->url_info.url() == info.url()) {
|
| + // Rotate it to the front if the caller wishes.
|
| + if (promote)
|
| + std::rotate(matches->begin(), i, i + 1);
|
| + return;
|
| + }
|
| + }
|
|
|
| -// Returns true if |url| is just a host (e.g. "http://www.google.com/") and
|
| -// not some other subpage (e.g. "http://www.google.com/foo.html").
|
| + // No entry, so create one.
|
| + history::HistoryMatch match(info, input_location, match_in_scheme, true);
|
| + if (promote)
|
| + matches->push_front(match);
|
| + else
|
| + matches->push_back(match);
|
| +}
|
| +
|
| +// Given the user's |input| and a |match| created from it, reduce the match's
|
| +// URL to just a host. If this host still matches the user input, return it.
|
| +// Returns the empty string on failure.
|
| +GURL ConvertToHostOnly(const history::HistoryMatch& match,
|
| + const string16& input) {
|
| + // See if we should try to do host-only suggestions for this URL. Nonstandard
|
| + // schemes means there's no authority section, so suggesting the host name
|
| + // is useless. File URLs are standard, but host suggestion is not useful for
|
| + // them either.
|
| + const GURL& url = match.url_info.url();
|
| + if (!url.is_valid() || !url.IsStandard() || url.SchemeIsFile())
|
| + return GURL();
|
| +
|
| + // Transform to a host-only match. Bail if the host no longer matches the
|
| + // user input (e.g. because the user typed more than just a host).
|
| + GURL host = url.GetWithEmptyPath();
|
| + if ((host.spec().length() < (match.input_location + input.length())))
|
| + return GURL(); // User typing is longer than this host suggestion.
|
| +
|
| + const string16 spec = UTF8ToUTF16(host.spec());
|
| + if (spec.compare(match.input_location, input.length(), input))
|
| + return GURL(); // User typing is no longer a prefix.
|
| +
|
| + return host;
|
| +}
|
| +
|
| +// See if a shorter version of the best match should be created, and if so place
|
| +// it at the front of |matches|. This can suggest history URLs that are
|
| +// prefixes of the best match (if they've been visited enough, compared to the
|
| +// best match), or create host-only suggestions even when they haven't been
|
| +// visited before: if the user visited http://example.com/asdf once, we'll
|
| +// suggest http://example.com/ even if they've never been to it.
|
| +void PromoteOrCreateShorterSuggestion(
|
| + history::URLDatabase* db,
|
| + const HistoryURLProviderParams& params,
|
| + bool have_what_you_typed_match,
|
| + const AutocompleteMatch& what_you_typed_match,
|
| + history::HistoryMatches* matches) {
|
| + if (matches->empty())
|
| + return; // No matches, nothing to do.
|
| +
|
| + // Determine the base URL from which to search, and whether that URL could
|
| + // itself be added as a match. We can add the base iff it's not "effectively
|
| + // the same" as any "what you typed" match.
|
| + const history::HistoryMatch& match = matches->front();
|
| + GURL search_base = ConvertToHostOnly(match, params.input.text());
|
| + bool can_add_search_base_to_matches = !have_what_you_typed_match;
|
| + if (search_base.is_empty()) {
|
| + // Search from what the user typed when we couldn't reduce the best match
|
| + // to a host. Careful: use a substring of |match| here, rather than the
|
| + // first match in |params|, because they might have different prefixes. If
|
| + // the user typed "google.com", |what_you_typed_match| will hold
|
| + // "http://google.com/", but |match| might begin with
|
| + // "http://www.google.com/".
|
| + // TODO: this should be cleaned up, and is probably incorrect for IDN.
|
| + std::string new_match = match.url_info.url().possibly_invalid_spec().
|
| + substr(0, match.input_location + params.input.text().length());
|
| + search_base = GURL(new_match);
|
| + // TODO(mrossetti): There is a degenerate case where the following may
|
| + // cause a failure: http://www/~someword/fubar.html. Diagnose.
|
| + // See: http://crbug.com/50101
|
| + if (search_base.is_empty())
|
| + return; // Can't construct a valid URL from which to start a search.
|
| + } else if (!can_add_search_base_to_matches) {
|
| + can_add_search_base_to_matches =
|
| + (search_base != what_you_typed_match.destination_url);
|
| + }
|
| + if (search_base == match.url_info.url())
|
| + return; // Couldn't shorten |match|, so no range of URLs to search over.
|
| +
|
| + // Search the DB for short URLs between our base and |match|.
|
| + history::URLRow info(search_base);
|
| + bool promote = true;
|
| + // A short URL is only worth suggesting if it's been visited at least a third
|
| + // as often as the longer URL.
|
| + const int min_visit_count = ((match.url_info.visit_count() - 1) / 3) + 1;
|
| + // For stability between the in-memory and on-disk autocomplete passes, when
|
| + // the long URL has been typed before, only suggest shorter URLs that have
|
| + // also been typed. Otherwise, the on-disk pass could suggest a shorter URL
|
| + // (which hasn't been typed) that the in-memory pass doesn't know about,
|
| + // thereby making the top match, and thus the behavior of inline
|
| + // autocomplete, unstable.
|
| + const int min_typed_count = match.url_info.typed_count() ? 1 : 0;
|
| + if (!db->FindShortestURLFromBase(search_base.possibly_invalid_spec(),
|
| + match.url_info.url().possibly_invalid_spec(), min_visit_count,
|
| + min_typed_count, can_add_search_base_to_matches, &info)) {
|
| + if (!can_add_search_base_to_matches)
|
| + return; // Couldn't find anything and can't add the search base, bail.
|
| +
|
| + // Try to get info on the search base itself. Promote it to the top if the
|
| + // original best match isn't good enough to autocomplete.
|
| + db->GetRowForURL(search_base, &info);
|
| + promote = match.url_info.typed_count() <= 1;
|
| + }
|
| +
|
| + // Promote or add the desired URL to the list of matches.
|
| + EnsureMatchPresent(info, match.input_location, match.match_in_scheme,
|
| + matches, promote);
|
| +}
|
| +
|
| +// Returns true if |url| is just a host (e.g. "http://www.google.com/") and not
|
| +// some other subpage (e.g. "http://www.google.com/foo.html").
|
| bool IsHostOnly(const GURL& url) {
|
| DCHECK(url.is_valid());
|
| return (!url.has_path() || (url.path() == "/")) && !url.has_query() &&
|
| @@ -45,7 +168,8 @@
|
| }
|
|
|
| // Acts like the > operator for URLInfo classes.
|
| -bool CompareHistoryMatch(const HistoryMatch& a, const HistoryMatch& b) {
|
| +bool CompareHistoryMatch(const history::HistoryMatch& a,
|
| + const history::HistoryMatch& b) {
|
| // A URL that has been typed at all is better than one that has never been
|
| // typed. (Note "!"s on each side)
|
| if (!a.url_info.typed_count() != !b.url_info.typed_count())
|
| @@ -63,8 +187,8 @@
|
| // For URLs that have each been typed once, a host (alone) is better than a
|
| // page inside.
|
| if (a.url_info.typed_count() == 1) {
|
| - const bool a_is_host_only = history::IsHostOnly(a.url_info.url());
|
| - if (a_is_host_only != history::IsHostOnly(b.url_info.url()))
|
| + const bool a_is_host_only = IsHostOnly(a.url_info.url());
|
| + if (a_is_host_only != IsHostOnly(b.url_info.url()))
|
| return a_is_host_only;
|
| }
|
|
|
| @@ -76,29 +200,36 @@
|
| return a.url_info.last_visit() > b.url_info.last_visit();
|
| }
|
|
|
| -// Given the user's |input| and a |match| created from it, reduce the
|
| -// match's URL to just a host. If this host still matches the user input,
|
| -// return it. Returns the empty string on failure.
|
| -GURL ConvertToHostOnly(const HistoryMatch& match, const string16& input) {
|
| - // See if we should try to do host-only suggestions for this URL. Nonstandard
|
| - // schemes means there's no authority section, so suggesting the host name
|
| - // is useless. File URLs are standard, but host suggestion is not useful for
|
| - // them either.
|
| - const GURL& url = match.url_info.url();
|
| - if (!url.is_valid() || !url.IsStandard() || url.SchemeIsFile())
|
| - return GURL();
|
| -
|
| - // Transform to a host-only match. Bail if the host no longer matches the
|
| - // user input (e.g. because the user typed more than just a host).
|
| - GURL host = url.GetWithEmptyPath();
|
| - if ((host.spec().length() < (match.input_location + input.length())))
|
| - return GURL(); // User typing is longer than this host suggestion.
|
| -
|
| - const string16 spec = UTF8ToUTF16(host.spec());
|
| - if (spec.compare(match.input_location, input.length(), input))
|
| - return GURL(); // User typing is no longer a prefix.
|
| -
|
| - return host;
|
| +// Determines the confidence for a |match| when compared to all the |matches|.
|
| +// Returns a number in the range [0, 1].
|
| +float CalculateConfidence(const history::HistoryMatch& match,
|
| + const history::HistoryMatches& matches) {
|
| + // TODO(dominich): Take into account bookmarked page?
|
| + // TODO(dominich): See CompareHistoryMatch for more measures to include.
|
| + // Using typed count in place of visit count as:
|
| + // - It's a better indicator of what the user wants to open given that they
|
| + // are typing in the address bar (users tend to open certain URLs by typing
|
| + // and others by e.g. bookmarks, so visit_count is a good indicator of
|
| + // overall interest but a bad one for specifically omnibox interest).
|
| + // - Since the DB query is sorted by typed_count, the results may be
|
| + // effectively a random selection as far as visit_counts are concerned
|
| + // (meaning many high-visit_count-URLs may be present in one query and
|
| + // absent in a similar one), leading to wild swings in confidence for the
|
| + // same result across distinct queries.
|
| + float numerator = match.url_info.typed_count();
|
| + float denominator = 0.0f;
|
| + for (history::HistoryMatches::const_iterator it = matches.begin();
|
| + it != matches.end(); ++it) {
|
| + denominator += it->url_info.typed_count();
|
| + }
|
| + if (denominator < 1) {
|
| + numerator = match.url_info.visit_count();
|
| + for (history::HistoryMatches::const_iterator it = matches.begin();
|
| + it != matches.end(); ++it) {
|
| + denominator += it->url_info.visit_count();
|
| + }
|
| + }
|
| + return (denominator > 0.0f ? numerator / denominator : 0);
|
| }
|
|
|
| } // namespace history
|
| @@ -116,7 +247,8 @@
|
| dont_suggest_exact_input(false) {
|
| }
|
|
|
| -HistoryURLProviderParams::~HistoryURLProviderParams() {}
|
| +HistoryURLProviderParams::~HistoryURLProviderParams() {
|
| +}
|
|
|
| HistoryURLProvider::HistoryURLProvider(ACProviderListener* listener,
|
| Profile* profile)
|
| @@ -158,12 +290,12 @@
|
| if (!db) {
|
| params->failed = true;
|
| } else if (!params->cancel_flag.IsSet()) {
|
| - TimeTicks beginning_time = TimeTicks::Now();
|
| + base::TimeTicks beginning_time = base::TimeTicks::Now();
|
|
|
| DoAutocomplete(backend, db, params);
|
|
|
| UMA_HISTOGRAM_TIMES("Autocomplete.HistoryAsyncQueryTime",
|
| - TimeTicks::Now() - beginning_time);
|
| + base::TimeTicks::Now() - beginning_time);
|
| }
|
|
|
| // Return the results (if any) to the main thread.
|
| @@ -198,10 +330,10 @@
|
| // Get the matching URLs from the DB
|
| typedef std::vector<history::URLRow> URLRowVector;
|
| URLRowVector url_matches;
|
| - HistoryMatches history_matches;
|
| + history::HistoryMatches history_matches;
|
|
|
| - for (Prefixes::const_iterator i(prefixes_.begin()); i != prefixes_.end();
|
| - ++i) {
|
| + for (history::Prefixes::const_iterator i(prefixes_.begin());
|
| + i != prefixes_.end(); ++i) {
|
| if (params->cancel_flag.IsSet())
|
| return; // Canceled in the middle of a query, give up.
|
| // We only need kMaxMatches results in the end, but before we get there we
|
| @@ -214,9 +346,9 @@
|
| kMaxMatches * 2, (backend == NULL), &url_matches);
|
| for (URLRowVector::const_iterator j(url_matches.begin());
|
| j != url_matches.end(); ++j) {
|
| - const Prefix* best_prefix = BestPrefix(j->url(), string16());
|
| + const history::Prefix* best_prefix = BestPrefix(j->url(), string16());
|
| DCHECK(best_prefix != NULL);
|
| - history_matches.push_back(HistoryMatch(*j, i->prefix.length(),
|
| + history_matches.push_back(history::HistoryMatch(*j, i->prefix.length(),
|
| !i->num_components,
|
| i->num_components >= best_prefix->num_components));
|
| }
|
| @@ -265,7 +397,7 @@
|
|
|
| // Convert the history matches to autocomplete matches.
|
| for (size_t i = first_match; i < history_matches.size(); ++i) {
|
| - const HistoryMatch& match = history_matches[i];
|
| + const history::HistoryMatch& match = history_matches[i];
|
| DCHECK(!have_what_you_typed_match ||
|
| (match.url_info.url() !=
|
| GURL(params->matches.front().destination_url)));
|
| @@ -304,6 +436,153 @@
|
| listener_->OnProviderUpdate(true);
|
| }
|
|
|
| +HistoryURLProvider::~HistoryURLProvider() {
|
| + // Note: This object can get leaked on shutdown if there are pending
|
| + // requests on the database (which hold a reference to us). Normally, these
|
| + // messages get flushed for each thread. We do a round trip from main, to
|
| + // history, back to main while holding a reference. If the main thread
|
| + // completes before the history thread, the message to delegate back to the
|
| + // main thread will not run and the reference will leak. Therefore, don't do
|
| + // anything on destruction.
|
| +}
|
| +
|
| +// static
|
| +history::Prefixes HistoryURLProvider::GetPrefixes() {
|
| + // We'll complete text following these prefixes.
|
| + // NOTE: There's no requirement that these be in any particular order.
|
| + history::Prefixes prefixes;
|
| + prefixes.push_back(history::Prefix(ASCIIToUTF16("https://www."), 2));
|
| + prefixes.push_back(history::Prefix(ASCIIToUTF16("http://www."), 2));
|
| + prefixes.push_back(history::Prefix(ASCIIToUTF16("ftp://ftp."), 2));
|
| + prefixes.push_back(history::Prefix(ASCIIToUTF16("ftp://www."), 2));
|
| + prefixes.push_back(history::Prefix(ASCIIToUTF16("https://"), 1));
|
| + prefixes.push_back(history::Prefix(ASCIIToUTF16("http://"), 1));
|
| + prefixes.push_back(history::Prefix(ASCIIToUTF16("ftp://"), 1));
|
| + // Empty string catches within-scheme matches as well.
|
| + prefixes.push_back(history::Prefix(string16(), 0));
|
| + return prefixes;
|
| +}
|
| +
|
| +// static
|
| +int HistoryURLProvider::CalculateRelevance(AutocompleteInput::Type input_type,
|
| + MatchType match_type,
|
| + size_t match_number) {
|
| + switch (match_type) {
|
| + case INLINE_AUTOCOMPLETE:
|
| + return 1400;
|
| +
|
| + case WHAT_YOU_TYPED:
|
| + return 1200;
|
| +
|
| + default:
|
| + return 900 + static_cast<int>(match_number);
|
| + }
|
| +}
|
| +
|
| +void HistoryURLProvider::RunAutocompletePasses(
|
| + const AutocompleteInput& input,
|
| + bool fixup_input_and_run_pass_1) {
|
| + matches_.clear();
|
| +
|
| + if ((input.type() == AutocompleteInput::INVALID) ||
|
| + (input.type() == AutocompleteInput::FORCED_QUERY))
|
| + return;
|
| +
|
| + // Create a match for exactly what the user typed. This will only be used as
|
| + // a fallback in case we can't get the history service or URL DB; otherwise,
|
| + // we'll run this again in DoAutocomplete() and use that result instead.
|
| + const bool trim_http = !HasHTTPScheme(input.text());
|
| + // Don't do this for queries -- while we can sometimes mark up a match for
|
| + // this, it's not what the user wants, and just adds noise.
|
| + if ((input.type() != AutocompleteInput::QUERY) &&
|
| + input.canonicalized_url().is_valid())
|
| + matches_.push_back(SuggestExactInput(input, trim_http));
|
| +
|
| + // We'll need the history service to run both passes, so try to obtain it.
|
| + if (!profile_)
|
| + return;
|
| + HistoryService* const history_service =
|
| + profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
|
| + if (!history_service)
|
| + return;
|
| +
|
| + // Create the data structure for the autocomplete passes. We'll save this off
|
| + // onto the |params_| member for later deletion below if we need to run pass
|
| + // 2.
|
| + std::string languages(languages_);
|
| + if (languages.empty()) {
|
| + languages =
|
| + profile_->GetPrefs()->GetString(prefs::kAcceptLanguages);
|
| + }
|
| + scoped_ptr<HistoryURLProviderParams> params(
|
| + new HistoryURLProviderParams(input, trim_http, languages));
|
| +
|
| + params->prevent_inline_autocomplete =
|
| + PreventInlineAutocomplete(input);
|
| +
|
| + if (fixup_input_and_run_pass_1) {
|
| + // Do some fixup on the user input before matching against it, so we provide
|
| + // good results for local file paths, input with spaces, etc.
|
| + // NOTE: This purposefully doesn't take input.desired_tld() into account; if
|
| + // it did, then holding "ctrl" would change all the results from the
|
| + // HistoryURLProvider provider, not just the What You Typed Result.
|
| + const string16 fixed_text(FixupUserInput(input));
|
| + if (fixed_text.empty()) {
|
| + // Conceivably fixup could result in an empty string (although I don't
|
| + // have cases where this happens offhand). We can't do anything with
|
| + // empty input, so just bail; otherwise we'd crash later.
|
| + return;
|
| + }
|
| + params->input.set_text(fixed_text);
|
| +
|
| + // Pass 1: Get the in-memory URL database, and use it to find and promote
|
| + // the inline autocomplete match, if any.
|
| + history::URLDatabase* url_db = history_service->InMemoryDatabase();
|
| + // url_db can be NULL if it hasn't finished initializing (or failed to
|
| + // initialize). In this case all we can do is fall back on the second
|
| + // pass.
|
| + //
|
| + // TODO(pkasting): We should just block here until this loads. Any time
|
| + // someone unloads the history backend, we'll get inconsistent inline
|
| + // autocomplete behavior here.
|
| + if (url_db) {
|
| + DoAutocomplete(NULL, url_db, params.get());
|
| + // params->matches now has the matches we should expose to the provider.
|
| + // Pass 2 expects a "clean slate" set of matches.
|
| + matches_.clear();
|
| + matches_.swap(params->matches);
|
| + UpdateStarredStateOfMatches();
|
| + }
|
| + }
|
| +
|
| + // Pass 2: Ask the history service to call us back on the history thread,
|
| + // where we can read the full on-disk DB.
|
| + if (input.matches_requested() == AutocompleteInput::ALL_MATCHES) {
|
| + done_ = false;
|
| + params_ = params.release(); // This object will be destroyed in
|
| + // QueryComplete() once we're done with it.
|
| + history_service->ScheduleAutocomplete(this, params_);
|
| + }
|
| +}
|
| +
|
| +const history::Prefix* HistoryURLProvider::BestPrefix(
|
| + const GURL& url,
|
| + const string16& prefix_suffix) const {
|
| + const history::Prefix* best_prefix = NULL;
|
| + const string16 text(UTF8ToUTF16(url.spec()));
|
| + for (history::Prefixes::const_iterator i(prefixes_.begin());
|
| + i != prefixes_.end(); ++i) {
|
| + if ((best_prefix == NULL) ||
|
| + (i->num_components > best_prefix->num_components)) {
|
| + string16 prefix_with_suffix(i->prefix + prefix_suffix);
|
| + if ((text.length() >= prefix_with_suffix.length()) &&
|
| + !text.compare(0, prefix_with_suffix.length(), prefix_with_suffix))
|
| + best_prefix = &(*i);
|
| + }
|
| + }
|
| + return best_prefix;
|
| +}
|
| +
|
| AutocompleteMatch HistoryURLProvider::SuggestExactInput(
|
| const AutocompleteInput& input,
|
| bool trim_http) {
|
| @@ -337,7 +616,8 @@
|
| // This relies on match.destination_url being the non-prefix-trimmed version
|
| // of match.contents.
|
| match.contents = display_string;
|
| - const Prefix* best_prefix = BestPrefix(match.destination_url, input.text());
|
| + const history::Prefix* best_prefix =
|
| + BestPrefix(match.destination_url, input.text());
|
| // Because of the vagaries of GURL, it's possible for match.destination_url
|
| // to not contain the user's input at all. In this case don't mark anything
|
| // as a match.
|
| @@ -355,10 +635,11 @@
|
| return match;
|
| }
|
|
|
| -bool HistoryURLProvider::FixupExactSuggestion(history::URLDatabase* db,
|
| - const AutocompleteInput& input,
|
| - AutocompleteMatch* match,
|
| - HistoryMatches* matches) const {
|
| +bool HistoryURLProvider::FixupExactSuggestion(
|
| + history::URLDatabase* db,
|
| + const AutocompleteInput& input,
|
| + AutocompleteMatch* match,
|
| + history::HistoryMatches* matches) const {
|
| DCHECK(match != NULL);
|
| DCHECK(matches != NULL);
|
|
|
| @@ -419,8 +700,8 @@
|
|
|
| bool HistoryURLProvider::PromoteMatchForInlineAutocomplete(
|
| HistoryURLProviderParams* params,
|
| - const HistoryMatch& match,
|
| - const HistoryMatches& matches) {
|
| + const history::HistoryMatch& match,
|
| + const history::HistoryMatches& matches) {
|
| // Promote the first match if it's been typed at least n times, where n == 1
|
| // for "simple" (host-only) URLs and n == 2 for others. We set a higher bar
|
| // for these long URLs because it's less likely that users will want to visit
|
| @@ -429,7 +710,7 @@
|
| // hand, we wouldn't want to immediately start autocompleting it.
|
| if (!match.url_info.typed_count() ||
|
| ((match.url_info.typed_count() == 1) &&
|
| - !history::IsHostOnly(match.url_info.url())))
|
| + !IsHostOnly(match.url_info.url())))
|
| return false;
|
|
|
| // In the case where the user has typed "foo.com" and visited (but not typed)
|
| @@ -444,274 +725,9 @@
|
| return true;
|
| }
|
|
|
| -HistoryURLProvider::~HistoryURLProvider() {}
|
| -
|
| -// static
|
| -history::Prefixes HistoryURLProvider::GetPrefixes() {
|
| - // We'll complete text following these prefixes.
|
| - // NOTE: There's no requirement that these be in any particular order.
|
| - Prefixes prefixes;
|
| - prefixes.push_back(Prefix(ASCIIToUTF16("https://www."), 2));
|
| - prefixes.push_back(Prefix(ASCIIToUTF16("http://www."), 2));
|
| - prefixes.push_back(Prefix(ASCIIToUTF16("ftp://ftp."), 2));
|
| - prefixes.push_back(Prefix(ASCIIToUTF16("ftp://www."), 2));
|
| - prefixes.push_back(Prefix(ASCIIToUTF16("https://"), 1));
|
| - prefixes.push_back(Prefix(ASCIIToUTF16("http://"), 1));
|
| - prefixes.push_back(Prefix(ASCIIToUTF16("ftp://"), 1));
|
| - // Empty string catches within-scheme matches as well.
|
| - prefixes.push_back(Prefix(string16(), 0));
|
| - return prefixes;
|
| -}
|
| -
|
| -// static
|
| -int HistoryURLProvider::CalculateRelevance(AutocompleteInput::Type input_type,
|
| - MatchType match_type,
|
| - size_t match_number) {
|
| - switch (match_type) {
|
| - case INLINE_AUTOCOMPLETE:
|
| - return 1400;
|
| -
|
| - case WHAT_YOU_TYPED:
|
| - return 1200;
|
| -
|
| - default:
|
| - return 900 + static_cast<int>(match_number);
|
| - }
|
| -}
|
| -
|
| -// static
|
| -float HistoryURLProvider::CalculateConfidence(
|
| - const history::HistoryMatch& match,
|
| - const history::HistoryMatches& matches) {
|
| - // TODO(dominich): Take into account bookmarked page?
|
| - // TODO(dominich): See CompareHistoryMatch for more measures to include.
|
| - // Using typed count in place of visit count as:
|
| - // - It's a better indicator of what the user wants to open given that they
|
| - // are typing in the address bar (users tend to open certain URLs by typing
|
| - // and others by e.g. bookmarks, so visit_count is a good indicator of
|
| - // overall interest but a bad one for specifically omnibox interest).
|
| - // - Since the DB query is sorted by typed_count, the results may be
|
| - // effectively a random selection as far as visit_counts are concerned
|
| - // (meaning many high-visit_count-URLs may be present in one query and
|
| - // absent in a similar one), leading to wild swings in confidence for the
|
| - // same result across distinct queries.
|
| - float numerator = match.url_info.typed_count();
|
| - float denominator = 0.0f;
|
| - for (history::HistoryMatches::const_iterator it = matches.begin();
|
| - it != matches.end(); ++it) {
|
| - denominator += it->url_info.typed_count();
|
| - }
|
| - if (denominator < 1) {
|
| - numerator = match.url_info.visit_count();
|
| - for (history::HistoryMatches::const_iterator it = matches.begin();
|
| - it != matches.end(); ++it) {
|
| - denominator += it->url_info.visit_count();
|
| - }
|
| - }
|
| - return (denominator > 0.0f ? numerator / denominator : 0);
|
| -}
|
| -
|
| -// static
|
| -void HistoryURLProvider::PromoteOrCreateShorterSuggestion(
|
| - history::URLDatabase* db,
|
| - const HistoryURLProviderParams& params,
|
| - bool have_what_you_typed_match,
|
| - const AutocompleteMatch& what_you_typed_match,
|
| - HistoryMatches* matches) {
|
| - if (matches->empty())
|
| - return; // No matches, nothing to do.
|
| -
|
| - // Determine the base URL from which to search, and whether that URL could
|
| - // itself be added as a match. We can add the base iff it's not "effectively
|
| - // the same" as any "what you typed" match.
|
| - const HistoryMatch& match = matches->front();
|
| - GURL search_base = history::ConvertToHostOnly(match, params.input.text());
|
| - bool can_add_search_base_to_matches = !have_what_you_typed_match;
|
| - if (search_base.is_empty()) {
|
| - // Search from what the user typed when we couldn't reduce the best match
|
| - // to a host. Careful: use a substring of |match| here, rather than the
|
| - // first match in |params|, because they might have different prefixes. If
|
| - // the user typed "google.com", |what_you_typed_match| will hold
|
| - // "http://google.com/", but |match| might begin with
|
| - // "http://www.google.com/".
|
| - // TODO: this should be cleaned up, and is probably incorrect for IDN.
|
| - std::string new_match = match.url_info.url().possibly_invalid_spec().
|
| - substr(0, match.input_location + params.input.text().length());
|
| - search_base = GURL(new_match);
|
| - // TODO(mrossetti): There is a degenerate case where the following may
|
| - // cause a failure: http://www/~someword/fubar.html. Diagnose.
|
| - // See: http://crbug.com/50101
|
| - if (search_base.is_empty())
|
| - return; // Can't construct a valid URL from which to start a search.
|
| - } else if (!can_add_search_base_to_matches) {
|
| - can_add_search_base_to_matches =
|
| - (search_base != what_you_typed_match.destination_url);
|
| - }
|
| - if (search_base == match.url_info.url())
|
| - return; // Couldn't shorten |match|, so no range of URLs to search over.
|
| -
|
| - // Search the DB for short URLs between our base and |match|.
|
| - history::URLRow info(search_base);
|
| - bool promote = true;
|
| - // A short URL is only worth suggesting if it's been visited at least a third
|
| - // as often as the longer URL.
|
| - const int min_visit_count = ((match.url_info.visit_count() - 1) / 3) + 1;
|
| - // For stability between the in-memory and on-disk autocomplete passes, when
|
| - // the long URL has been typed before, only suggest shorter URLs that have
|
| - // also been typed. Otherwise, the on-disk pass could suggest a shorter URL
|
| - // (which hasn't been typed) that the in-memory pass doesn't know about,
|
| - // thereby making the top match, and thus the behavior of inline
|
| - // autocomplete, unstable.
|
| - const int min_typed_count = match.url_info.typed_count() ? 1 : 0;
|
| - if (!db->FindShortestURLFromBase(search_base.possibly_invalid_spec(),
|
| - match.url_info.url().possibly_invalid_spec(), min_visit_count,
|
| - min_typed_count, can_add_search_base_to_matches, &info)) {
|
| - if (!can_add_search_base_to_matches)
|
| - return; // Couldn't find anything and can't add the search base, bail.
|
| -
|
| - // Try to get info on the search base itself. Promote it to the top if the
|
| - // original best match isn't good enough to autocomplete.
|
| - db->GetRowForURL(search_base, &info);
|
| - promote = match.url_info.typed_count() <= 1;
|
| - }
|
| -
|
| - // Promote or add the desired URL to the list of matches.
|
| - EnsureMatchPresent(info, match.input_location, match.match_in_scheme,
|
| - matches, promote);
|
| -}
|
| -
|
| -// static
|
| -void HistoryURLProvider::EnsureMatchPresent(const history::URLRow& info,
|
| - size_t input_location,
|
| - bool match_in_scheme,
|
| - HistoryMatches* matches,
|
| - bool promote) {
|
| - // |matches| may already have an entry for this.
|
| - for (HistoryMatches::iterator i(matches->begin()); i != matches->end();
|
| - ++i) {
|
| - if (i->url_info.url() == info.url()) {
|
| - // Rotate it to the front if the caller wishes.
|
| - if (promote)
|
| - std::rotate(matches->begin(), i, i + 1);
|
| - return;
|
| - }
|
| - }
|
| -
|
| - // No entry, so create one.
|
| - HistoryMatch match(info, input_location, match_in_scheme, true);
|
| - if (promote)
|
| - matches->push_front(match);
|
| - else
|
| - matches->push_back(match);
|
| -}
|
| -
|
| -void HistoryURLProvider::RunAutocompletePasses(
|
| - const AutocompleteInput& input,
|
| - bool fixup_input_and_run_pass_1) {
|
| - matches_.clear();
|
| -
|
| - if ((input.type() == AutocompleteInput::INVALID) ||
|
| - (input.type() == AutocompleteInput::FORCED_QUERY))
|
| - return;
|
| -
|
| - // Create a match for exactly what the user typed. This will only be used as
|
| - // a fallback in case we can't get the history service or URL DB; otherwise,
|
| - // we'll run this again in DoAutocomplete() and use that result instead.
|
| - const bool trim_http = !HasHTTPScheme(input.text());
|
| - // Don't do this for queries -- while we can sometimes mark up a match for
|
| - // this, it's not what the user wants, and just adds noise.
|
| - if ((input.type() != AutocompleteInput::QUERY) &&
|
| - input.canonicalized_url().is_valid())
|
| - matches_.push_back(SuggestExactInput(input, trim_http));
|
| -
|
| - // We'll need the history service to run both passes, so try to obtain it.
|
| - if (!profile_)
|
| - return;
|
| - HistoryService* const history_service =
|
| - profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
|
| - if (!history_service)
|
| - return;
|
| -
|
| - // Create the data structure for the autocomplete passes. We'll save this off
|
| - // onto the |params_| member for later deletion below if we need to run pass
|
| - // 2.
|
| - std::string languages(languages_);
|
| - if (languages.empty()) {
|
| - languages =
|
| - profile_->GetPrefs()->GetString(prefs::kAcceptLanguages);
|
| - }
|
| - scoped_ptr<HistoryURLProviderParams> params(
|
| - new HistoryURLProviderParams(input, trim_http, languages));
|
| -
|
| - params->prevent_inline_autocomplete =
|
| - PreventInlineAutocomplete(input);
|
| -
|
| - if (fixup_input_and_run_pass_1) {
|
| - // Do some fixup on the user input before matching against it, so we provide
|
| - // good results for local file paths, input with spaces, etc.
|
| - // NOTE: This purposefully doesn't take input.desired_tld() into account; if
|
| - // it did, then holding "ctrl" would change all the results from the
|
| - // HistoryURLProvider provider, not just the What You Typed Result.
|
| - const string16 fixed_text(FixupUserInput(input));
|
| - if (fixed_text.empty()) {
|
| - // Conceivably fixup could result in an empty string (although I don't
|
| - // have cases where this happens offhand). We can't do anything with
|
| - // empty input, so just bail; otherwise we'd crash later.
|
| - return;
|
| - }
|
| - params->input.set_text(fixed_text);
|
| -
|
| - // Pass 1: Get the in-memory URL database, and use it to find and promote
|
| - // the inline autocomplete match, if any.
|
| - history::URLDatabase* url_db = history_service->InMemoryDatabase();
|
| - // url_db can be NULL if it hasn't finished initializing (or failed to
|
| - // initialize). In this case all we can do is fall back on the second
|
| - // pass.
|
| - //
|
| - // TODO(pkasting): We should just block here until this loads. Any time
|
| - // someone unloads the history backend, we'll get inconsistent inline
|
| - // autocomplete behavior here.
|
| - if (url_db) {
|
| - DoAutocomplete(NULL, url_db, params.get());
|
| - // params->matches now has the matches we should expose to the provider.
|
| - // Pass 2 expects a "clean slate" set of matches.
|
| - matches_.clear();
|
| - matches_.swap(params->matches);
|
| - UpdateStarredStateOfMatches();
|
| - }
|
| - }
|
| -
|
| - // Pass 2: Ask the history service to call us back on the history thread,
|
| - // where we can read the full on-disk DB.
|
| - if (input.matches_requested() == AutocompleteInput::ALL_MATCHES) {
|
| - done_ = false;
|
| - params_ = params.release(); // This object will be destroyed in
|
| - // QueryComplete() once we're done with it.
|
| - history_service->ScheduleAutocomplete(this, params_);
|
| - }
|
| -}
|
| -
|
| -const history::Prefix* HistoryURLProvider::BestPrefix(
|
| - const GURL& url,
|
| - const string16& prefix_suffix) const {
|
| - const Prefix* best_prefix = NULL;
|
| - const string16 text(UTF8ToUTF16(url.spec()));
|
| - for (Prefixes::const_iterator i(prefixes_.begin()); i != prefixes_.end();
|
| - ++i) {
|
| - if ((best_prefix == NULL) ||
|
| - (i->num_components > best_prefix->num_components)) {
|
| - string16 prefix_with_suffix(i->prefix + prefix_suffix);
|
| - if ((text.length() >= prefix_with_suffix.length()) &&
|
| - !text.compare(0, prefix_with_suffix.length(), prefix_with_suffix))
|
| - best_prefix = &(*i);
|
| - }
|
| - }
|
| - return best_prefix;
|
| -}
|
| -
|
| -void HistoryURLProvider::SortMatches(HistoryMatches* matches) const {
|
| +void HistoryURLProvider::SortMatches(history::HistoryMatches* matches) const {
|
| // Sort by quality, best first.
|
| - std::sort(matches->begin(), matches->end(), &history::CompareHistoryMatch);
|
| + std::sort(matches->begin(), matches->end(), &CompareHistoryMatch);
|
|
|
| // Remove duplicate matches (caused by the search string appearing in one of
|
| // the prefixes as well as after it). Consider the following scenario:
|
| @@ -733,8 +749,8 @@
|
| // we use an index instead of an iterator in the outer loop, and don't
|
| // precalculate the ending position.
|
| for (size_t i = 0; i < matches->size(); ++i) {
|
| - HistoryMatches::iterator j(matches->begin() + i + 1);
|
| - while (j != matches->end()) {
|
| + for (history::HistoryMatches::iterator j(matches->begin() + i + 1);
|
| + j != matches->end(); ) {
|
| if ((*matches)[i].url_info.url() == j->url_info.url())
|
| j = matches->erase(j);
|
| else
|
| @@ -743,9 +759,11 @@
|
| }
|
| }
|
|
|
| -void HistoryURLProvider::CullPoorMatches(HistoryMatches* matches) const {
|
| +void HistoryURLProvider::CullPoorMatches(
|
| + history::HistoryMatches* matches) const {
|
| const base::Time& threshold(history::AutocompleteAgeThreshold());
|
| - for (HistoryMatches::iterator i(matches->begin()); i != matches->end();) {
|
| + for (history::HistoryMatches::iterator i(matches->begin());
|
| + i != matches->end();) {
|
| if (RowQualifiesAsSignificant(i->url_info, threshold))
|
| ++i;
|
| else
|
| @@ -754,7 +772,7 @@
|
| }
|
|
|
| void HistoryURLProvider::CullRedirects(history::HistoryBackend* backend,
|
| - HistoryMatches* matches,
|
| + history::HistoryMatches* matches,
|
| size_t max_results) const {
|
| for (size_t source = 0;
|
| (source < matches->size()) && (source < max_results); ) {
|
| @@ -784,38 +802,37 @@
|
| }
|
|
|
| size_t HistoryURLProvider::RemoveSubsequentMatchesOf(
|
| - HistoryMatches* matches,
|
| + history::HistoryMatches* matches,
|
| size_t source_index,
|
| const std::vector<GURL>& remove) const {
|
| size_t next_index = source_index + 1; // return value = item after source
|
|
|
| // Find the first occurrence of any URL in the redirect chain. We want to
|
| // keep this one since it is rated the highest.
|
| - HistoryMatches::iterator first(std::find_first_of(
|
| + history::HistoryMatches::iterator first(std::find_first_of(
|
| matches->begin(), matches->end(), remove.begin(), remove.end()));
|
| - DCHECK(first != matches->end()) <<
|
| - "We should have always found at least the original URL.";
|
| + DCHECK(first != matches->end()) << "We should have always found at least the "
|
| + "original URL.";
|
|
|
| // Find any following occurrences of any URL in the redirect chain, these
|
| // should be deleted.
|
| - HistoryMatches::iterator next(first);
|
| - next++; // Start searching immediately after the one we found already.
|
| - while (next != matches->end() &&
|
| - (next = std::find_first_of(next, matches->end(), remove.begin(),
|
| - remove.end())) != matches->end()) {
|
| + for (history::HistoryMatches::iterator next(std::find_first_of(first + 1,
|
| + matches->end(), remove.begin(), remove.end()));
|
| + next != matches->end(); next = std::find_first_of(next, matches->end(),
|
| + remove.begin(), remove.end())) {
|
| // Remove this item. When we remove an item before the source index, we
|
| // need to shift it to the right and remember that so we can return it.
|
| next = matches->erase(next);
|
| if (static_cast<size_t>(next - matches->begin()) < next_index)
|
| - next_index--;
|
| + --next_index;
|
| }
|
| return next_index;
|
| }
|
|
|
| AutocompleteMatch HistoryURLProvider::HistoryMatchToACMatch(
|
| HistoryURLProviderParams* params,
|
| - const HistoryMatch& history_match,
|
| - const HistoryMatches& history_matches,
|
| + const history::HistoryMatch& history_match,
|
| + const history::HistoryMatches& history_matches,
|
| MatchType match_type,
|
| size_t match_number) {
|
| const history::URLRow& info = history_match.url_info;
|
|
|