1 // Copyright 2013 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/ui/omnibox/omnibox_controller.h"
6
7 #include "base/metrics/histogram.h"
8 #include "chrome/browser/autocomplete/autocomplete_classifier.h"
9 #include "chrome/browser/autocomplete/autocomplete_match.h"
10 #include "chrome/browser/autocomplete/search_provider.h"
11 #include "chrome/browser/net/predictor.h"
12 #include "chrome/browser/predictors/autocomplete_action_predictor.h"
13 #include "chrome/browser/prerender/prerender_field_trial.h"
14 #include "chrome/browser/prerender/prerender_manager.h"
15 #include "chrome/browser/prerender/prerender_manager_factory.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/search/search.h"
18 #include "chrome/browser/ui/omnibox/omnibox_edit_controller.h"
19 #include "chrome/browser/ui/omnibox/omnibox_edit_model.h"
20 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
21 #include "chrome/browser/ui/omnibox/omnibox_popup_view.h"
22 #include "chrome/browser/ui/search/instant_controller.h"
23 #include "chrome/common/instant_types.h"
24 #include "extensions/common/constants.h"
25 #include "ui/gfx/rect.h"
26
27 namespace {
28
29 // Returns the AutocompleteMatch that the InstantController should prefetch, if
30 // any.
31 //
32 // The SearchProvider may mark some suggestions to be prefetched based on
33 // instructions from the suggest server. If such a match ranks sufficiently
34 // highly, we'll return it.
35 //
36 // We only care about matches that are the default or the very first entry in
37 // the dropdown (which can happen for non-default matches only if we're hiding
38 // a top verbatim match) or the second entry in the dropdown (which can happen
39 // for non-default matches when a top verbatim match is shown); for other
40 // matches, we think the likelihood of the user selecting them is low enough
41 // that prefetching isn't worth doing.
GetMatchToPrefetch(const AutocompleteResult & result)42 const AutocompleteMatch* GetMatchToPrefetch(const AutocompleteResult& result) {
43 const AutocompleteResult::const_iterator default_match(
44 result.default_match());
45 if (default_match == result.end())
46 return NULL;
47
48 if (SearchProvider::ShouldPrefetch(*default_match))
49 return &(*default_match);
50
51 return ((result.ShouldHideTopMatch() ||
52 result.TopMatchIsVerbatimAndHasNoConsecutiveVerbatimMatches()) &&
53 (result.size() > 1) &&
54 SearchProvider::ShouldPrefetch(result.match_at(1))) ?
55 &result.match_at(1) : NULL;
56 }
57
58 } // namespace
59
OmniboxController(OmniboxEditModel * omnibox_edit_model,Profile * profile)60 OmniboxController::OmniboxController(OmniboxEditModel* omnibox_edit_model,
61 Profile* profile)
62 : omnibox_edit_model_(omnibox_edit_model),
63 profile_(profile),
64 popup_(NULL),
65 autocomplete_controller_(new AutocompleteController(profile, this,
66 AutocompleteClassifier::kDefaultOmniboxProviders)) {
67 }
68
~OmniboxController()69 OmniboxController::~OmniboxController() {
70 }
71
StartAutocomplete(base::string16 user_text,size_t cursor_position,const GURL & current_url,AutocompleteInput::PageClassification current_page_classification,bool prevent_inline_autocomplete,bool prefer_keyword,bool allow_exact_keyword_match) const72 void OmniboxController::StartAutocomplete(
73 base::string16 user_text,
74 size_t cursor_position,
75 const GURL& current_url,
76 AutocompleteInput::PageClassification current_page_classification,
77 bool prevent_inline_autocomplete,
78 bool prefer_keyword,
79 bool allow_exact_keyword_match) const {
80 ClearPopupKeywordMode();
81 popup_->SetHoveredLine(OmniboxPopupModel::kNoMatch);
82
83 // We don't explicitly clear OmniboxPopupModel::manually_selected_match, as
84 // Start ends up invoking OmniboxPopupModel::OnResultChanged which clears it.
85 autocomplete_controller_->Start(AutocompleteInput(
86 user_text, cursor_position, base::string16(), current_url,
87 current_page_classification, prevent_inline_autocomplete,
88 prefer_keyword, allow_exact_keyword_match,
89 AutocompleteInput::ALL_MATCHES));
90 }
91
OnResultChanged(bool default_match_changed)92 void OmniboxController::OnResultChanged(bool default_match_changed) {
93 const bool was_open = popup_->IsOpen();
94 if (default_match_changed) {
95 // The default match has changed, we need to let the OmniboxEditModel know
96 // about new inline autocomplete text (blue highlight).
97 const AutocompleteResult& result = this->result();
98 const AutocompleteResult::const_iterator match(result.default_match());
99 if (match != result.end()) {
100 current_match_ = *match;
101 if (!prerender::IsOmniboxEnabled(profile_))
102 DoPreconnect(*match);
103 omnibox_edit_model_->OnCurrentMatchChanged();
104
105 if (chrome::IsInstantExtendedAPIEnabled() &&
106 omnibox_edit_model_->GetInstantController()) {
107 InstantSuggestion prefetch_suggestion;
108 const AutocompleteMatch* match_to_prefetch = GetMatchToPrefetch(result);
109 if (match_to_prefetch) {
110 prefetch_suggestion.text = match_to_prefetch->contents;
111 prefetch_suggestion.metadata =
112 SearchProvider::GetSuggestMetadata(*match_to_prefetch);
113 }
114 // Send the prefetch suggestion unconditionally to the InstantPage. If
115 // there is no suggestion to prefetch, we need to send a blank query to
116 // clear the prefetched results.
117 omnibox_edit_model_->GetInstantController()->SetSuggestionToPrefetch(
118 prefetch_suggestion);
119 }
120 } else {
121 InvalidateCurrentMatch();
122 popup_->OnResultChanged();
123 omnibox_edit_model_->OnPopupDataChanged(base::string16(), NULL,
124 base::string16(), false);
125 }
126 } else {
127 popup_->OnResultChanged();
128 }
129
130 if (!popup_->IsOpen() && was_open) {
131 // Accept the temporary text as the user text, because it makes little sense
132 // to have temporary text when the popup is closed.
133 omnibox_edit_model_->AcceptTemporaryTextAsUserText();
134 }
135 }
136
InvalidateCurrentMatch()137 void OmniboxController::InvalidateCurrentMatch() {
138 current_match_ = AutocompleteMatch();
139 }
140
ClearPopupKeywordMode() const141 void OmniboxController::ClearPopupKeywordMode() const {
142 if (popup_->IsOpen() &&
143 popup_->selected_line_state() == OmniboxPopupModel::KEYWORD)
144 popup_->SetSelectedLineState(OmniboxPopupModel::NORMAL);
145 }
146
DoPreconnect(const AutocompleteMatch & match)147 void OmniboxController::DoPreconnect(const AutocompleteMatch& match) {
148 if (!match.destination_url.SchemeIs(extensions::kExtensionScheme)) {
149 // Warm up DNS Prefetch cache, or preconnect to a search service.
150 UMA_HISTOGRAM_ENUMERATION("Autocomplete.MatchType", match.type,
151 AutocompleteMatchType::NUM_TYPES);
152 if (profile_->GetNetworkPredictor()) {
153 profile_->GetNetworkPredictor()->AnticipateOmniboxUrl(
154 match.destination_url,
155 predictors::AutocompleteActionPredictor::IsPreconnectable(match));
156 }
157 // We could prefetch the alternate nav URL, if any, but because there
158 // can be many of these as a user types an initial series of characters,
159 // the OS DNS cache could suffer eviction problems for minimal gain.
160 }
161 }
162