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/common/instant_types.h" 23#include "extensions/common/constants.h" 24#include "ui/gfx/rect.h" 25 26namespace { 27 28// Returns the AutocompleteMatch that the InstantController should prefetch, if 29// any. 30// 31// The SearchProvider may mark some suggestions to be prefetched based on 32// instructions from the suggest server. If such a match ranks sufficiently 33// highly or if kAllowPrefetchNonDefaultMatch field trial is enabled, we'll 34// return it. 35// 36// If the kAllowPrefetchNonDefaultMatch field trial is enabled we return the 37// prefetch suggestion even if it is not the default match. Otherwise we only 38// care about matches that are the default or the very first entry in the 39// dropdown (which can happen for non-default matches only if we're hiding a top 40// verbatim match) or the second entry in the dropdown (which can happen for 41// non-default matches when a top verbatim match is shown); for other matches, 42// we think the likelihood of the user selecting them is low enough that 43// prefetching isn't worth doing. 44const AutocompleteMatch* GetMatchToPrefetch(const AutocompleteResult& result) { 45 if (chrome::ShouldAllowPrefetchNonDefaultMatch()) { 46 const AutocompleteResult::const_iterator prefetch_match = std::find_if( 47 result.begin(), result.end(), SearchProvider::ShouldPrefetch); 48 return prefetch_match != result.end() ? &(*prefetch_match) : NULL; 49 } 50 51 // If the default match should be prefetched, do that. 52 const AutocompleteResult::const_iterator default_match( 53 result.default_match()); 54 if ((default_match != result.end()) && 55 SearchProvider::ShouldPrefetch(*default_match)) 56 return &(*default_match); 57 58 // Otherwise, if the top match is a verbatim match and the very next match 59 // is prefetchable, fetch that. 60 if ((result.ShouldHideTopMatch() || 61 result.TopMatchIsStandaloneVerbatimMatch()) && 62 (result.size() > 1) && 63 SearchProvider::ShouldPrefetch(result.match_at(1))) 64 return &result.match_at(1); 65 66 return NULL; 67} 68 69} // namespace 70 71OmniboxController::OmniboxController(OmniboxEditModel* omnibox_edit_model, 72 Profile* profile) 73 : omnibox_edit_model_(omnibox_edit_model), 74 profile_(profile), 75 popup_(NULL), 76 autocomplete_controller_(new AutocompleteController(profile, this, 77 AutocompleteClassifier::kDefaultOmniboxProviders)) { 78} 79 80OmniboxController::~OmniboxController() { 81} 82 83void OmniboxController::StartAutocomplete( 84 const AutocompleteInput& input) const { 85 ClearPopupKeywordMode(); 86 popup_->SetHoveredLine(OmniboxPopupModel::kNoMatch); 87 88 // We don't explicitly clear OmniboxPopupModel::manually_selected_match, as 89 // Start ends up invoking OmniboxPopupModel::OnResultChanged which clears it. 90 autocomplete_controller_->Start(input); 91} 92 93void OmniboxController::OnResultChanged(bool default_match_changed) { 94 const bool was_open = popup_->IsOpen(); 95 if (default_match_changed) { 96 // The default match has changed, we need to let the OmniboxEditModel know 97 // about new inline autocomplete text (blue highlight). 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 } else { 105 InvalidateCurrentMatch(); 106 popup_->OnResultChanged(); 107 omnibox_edit_model_->OnPopupDataChanged(base::string16(), NULL, 108 base::string16(), false); 109 } 110 } else { 111 popup_->OnResultChanged(); 112 } 113 114 if (!popup_->IsOpen() && was_open) { 115 // Accept the temporary text as the user text, because it makes little sense 116 // to have temporary text when the popup is closed. 117 omnibox_edit_model_->AcceptTemporaryTextAsUserText(); 118 } 119 120 if (chrome::IsInstantExtendedAPIEnabled() && 121 ((default_match_changed && result().default_match() != result().end()) || 122 (chrome::ShouldAllowPrefetchNonDefaultMatch() && !result().empty()))) { 123 InstantSuggestion prefetch_suggestion; 124 const AutocompleteMatch* match_to_prefetch = GetMatchToPrefetch(result()); 125 if (match_to_prefetch) { 126 prefetch_suggestion.text = match_to_prefetch->contents; 127 prefetch_suggestion.metadata = 128 SearchProvider::GetSuggestMetadata(*match_to_prefetch); 129 } 130 // Send the prefetch suggestion unconditionally to the InstantPage. If 131 // there is no suggestion to prefetch, we need to send a blank query to 132 // clear the prefetched results. 133 omnibox_edit_model_->SetSuggestionToPrefetch(prefetch_suggestion); 134 } 135} 136 137void OmniboxController::InvalidateCurrentMatch() { 138 current_match_ = AutocompleteMatch(); 139} 140 141void OmniboxController::ClearPopupKeywordMode() const { 142 if (popup_->IsOpen() && 143 popup_->selected_line_state() == OmniboxPopupModel::KEYWORD) 144 popup_->SetSelectedLineState(OmniboxPopupModel::NORMAL); 145} 146 147void 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