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. 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 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 69 OmniboxController::~OmniboxController() { 70 } 71 72 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 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 137 void OmniboxController::InvalidateCurrentMatch() { 138 current_match_ = AutocompleteMatch(); 139 } 140 141 void OmniboxController::ClearPopupKeywordMode() const { 142 if (popup_->IsOpen() && 143 popup_->selected_line_state() == OmniboxPopupModel::KEYWORD) 144 popup_->SetSelectedLineState(OmniboxPopupModel::NORMAL); 145 } 146 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