Clean OmniboxEditModel of remaining //content dependencies
[chromium-blink-merge.git] / chrome / browser / ui / omnibox / chrome_omnibox_client.cc
blobc46c3c51a6f19475bf1834d72a0d133f2b1ebb2d
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.
5 #include "chrome/browser/ui/omnibox/chrome_omnibox_client.h"
7 #include "base/bind.h"
8 #include "base/callback.h"
9 #include "base/metrics/histogram.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/app/chrome_command_ids.h"
12 #include "chrome/browser/autocomplete/chrome_autocomplete_provider_client.h"
13 #include "chrome/browser/bitmap_fetcher/bitmap_fetcher_service_factory.h"
14 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
15 #include "chrome/browser/chrome_notification_types.h"
16 #include "chrome/browser/command_updater.h"
17 #include "chrome/browser/extensions/api/omnibox/omnibox_api.h"
18 #include "chrome/browser/net/predictor.h"
19 #include "chrome/browser/predictors/autocomplete_action_predictor.h"
20 #include "chrome/browser/predictors/autocomplete_action_predictor_factory.h"
21 #include "chrome/browser/prerender/prerender_field_trial.h"
22 #include "chrome/browser/prerender/prerender_manager.h"
23 #include "chrome/browser/prerender/prerender_manager_factory.h"
24 #include "chrome/browser/profiles/profile.h"
25 #include "chrome/browser/search/search.h"
26 #include "chrome/browser/search_engines/template_url_service_factory.h"
27 #include "chrome/browser/sessions/session_tab_helper.h"
28 #include "chrome/browser/ui/omnibox/omnibox_edit_controller.h"
29 #include "chrome/browser/ui/search/instant_search_prerenderer.h"
30 #include "chrome/browser/ui/search/search_tab_helper.h"
31 #include "chrome/common/instant_types.h"
32 #include "components/favicon/content/content_favicon_driver.h"
33 #include "components/omnibox/browser/autocomplete_match.h"
34 #include "components/omnibox/browser/autocomplete_result.h"
35 #include "components/omnibox/browser/search_provider.h"
36 #include "components/search/search.h"
37 #include "components/search_engines/template_url_service.h"
38 #include "content/public/browser/navigation_controller.h"
39 #include "content/public/browser/navigation_entry.h"
40 #include "content/public/browser/notification_service.h"
41 #include "content/public/browser/web_contents.h"
42 #include "extensions/common/constants.h"
43 #include "ui/base/window_open_disposition.h"
44 #include "url/gurl.h"
46 using predictors::AutocompleteActionPredictor;
48 namespace {
50 // Returns the AutocompleteMatch that the InstantController should prefetch, if
51 // any.
53 // The SearchProvider may mark some suggestions to be prefetched based on
54 // instructions from the suggest server. If such a match ranks sufficiently
55 // highly or if kAllowPrefetchNonDefaultMatch field trial is enabled, we'll
56 // return it.
58 // If the kAllowPrefetchNonDefaultMatch field trial is enabled we return the
59 // prefetch suggestion even if it is not the default match. Otherwise we only
60 // care about matches that are the default or the second entry in the dropdown
61 // (which can happen for non-default matches when a top verbatim match is
62 // shown); for other matches, we think the likelihood of the user selecting
63 // them is low enough that prefetching isn't worth doing.
64 const AutocompleteMatch* GetMatchToPrefetch(const AutocompleteResult& result) {
65 if (search::ShouldAllowPrefetchNonDefaultMatch()) {
66 const AutocompleteResult::const_iterator prefetch_match = std::find_if(
67 result.begin(), result.end(), SearchProvider::ShouldPrefetch);
68 return prefetch_match != result.end() ? &(*prefetch_match) : NULL;
71 // If the default match should be prefetched, do that.
72 const auto default_match = result.default_match();
73 if ((default_match != result.end()) &&
74 SearchProvider::ShouldPrefetch(*default_match))
75 return &(*default_match);
77 // Otherwise, if the top match is a verbatim match and the very next match
78 // is prefetchable, fetch that.
79 if (result.TopMatchIsStandaloneVerbatimMatch() && (result.size() > 1) &&
80 SearchProvider::ShouldPrefetch(result.match_at(1)))
81 return &result.match_at(1);
83 return NULL;
86 // Calls the specified callback when the requested image is downloaded. This
87 // is a separate class instead of being implemented on ChromeOmniboxClient
88 // because BitmapFetcherService currently takes ownership of this object.
89 // TODO(dschuyler): Make BitmapFetcherService use the more typical non-owning
90 // ObserverList pattern and have ChromeOmniboxClient implement the Observer
91 // call directly.
92 class AnswerImageObserver : public BitmapFetcherService::Observer {
93 public:
94 explicit AnswerImageObserver(const BitmapFetchedCallback& callback)
95 : callback_(callback) {}
97 void OnImageChanged(BitmapFetcherService::RequestId request_id,
98 const SkBitmap& image) override;
100 private:
101 const BitmapFetchedCallback callback_;
103 DISALLOW_COPY_AND_ASSIGN(AnswerImageObserver);
106 void AnswerImageObserver::OnImageChanged(
107 BitmapFetcherService::RequestId request_id,
108 const SkBitmap& image) {
109 DCHECK(!image.empty());
110 callback_.Run(image);
113 } // namespace
115 ChromeOmniboxClient::ChromeOmniboxClient(OmniboxEditController* controller,
116 Profile* profile)
117 : controller_(controller),
118 profile_(profile),
119 request_id_(BitmapFetcherService::REQUEST_ID_INVALID) {}
121 ChromeOmniboxClient::~ChromeOmniboxClient() {
122 BitmapFetcherService* image_service =
123 BitmapFetcherServiceFactory::GetForBrowserContext(profile_);
124 if (image_service)
125 image_service->CancelRequest(request_id_);
128 scoped_ptr<AutocompleteProviderClient>
129 ChromeOmniboxClient::CreateAutocompleteProviderClient() {
130 return make_scoped_ptr(new ChromeAutocompleteProviderClient(profile_));
133 bool ChromeOmniboxClient::CurrentPageExists() const {
134 return (controller_->GetWebContents() != NULL);
137 const GURL& ChromeOmniboxClient::GetURL() const {
138 return controller_->GetWebContents()->GetVisibleURL();
141 const base::string16& ChromeOmniboxClient::GetTitle() const {
142 return controller_->GetWebContents()->GetTitle();
145 gfx::Image ChromeOmniboxClient::GetFavicon() const {
146 return favicon::ContentFaviconDriver::FromWebContents(
147 controller_->GetWebContents())
148 ->GetFavicon();
151 bool ChromeOmniboxClient::IsInstantNTP() const {
152 return search::IsInstantNTP(controller_->GetWebContents());
155 bool ChromeOmniboxClient::IsSearchResultsPage() const {
156 Profile* profile = Profile::FromBrowserContext(
157 controller_->GetWebContents()->GetBrowserContext());
158 return TemplateURLServiceFactory::GetForProfile(profile)->
159 IsSearchResultsPageFromDefaultSearchProvider(GetURL());
162 bool ChromeOmniboxClient::IsLoading() const {
163 return controller_->GetWebContents()->IsLoading();
166 bool ChromeOmniboxClient::IsPasteAndGoEnabled() const {
167 return controller_->command_updater()->IsCommandEnabled(IDC_OPEN_CURRENT_URL);
170 const SessionID& ChromeOmniboxClient::GetSessionID() const {
171 return SessionTabHelper::FromWebContents(
172 controller_->GetWebContents())->session_id();
175 bookmarks::BookmarkModel* ChromeOmniboxClient::GetBookmarkModel() {
176 return BookmarkModelFactory::GetForProfile(profile_);
179 TemplateURLService* ChromeOmniboxClient::GetTemplateURLService() {
180 return TemplateURLServiceFactory::GetForProfile(profile_);
183 gfx::Image ChromeOmniboxClient::GetIconIfExtensionMatch(
184 const AutocompleteMatch& match) const {
185 TemplateURLService* service =
186 TemplateURLServiceFactory::GetForProfile(profile_);
187 const TemplateURL* template_url = match.GetTemplateURL(service, false);
188 if (template_url &&
189 (template_url->GetType() == TemplateURL::OMNIBOX_API_EXTENSION)) {
190 return extensions::OmniboxAPI::Get(profile_)
191 ->GetOmniboxPopupIcon(template_url->GetExtensionId());
193 return gfx::Image();
196 bool ChromeOmniboxClient::ProcessExtensionKeyword(
197 TemplateURL* template_url,
198 const AutocompleteMatch& match,
199 WindowOpenDisposition disposition) {
200 if (template_url->GetType() != TemplateURL::OMNIBOX_API_EXTENSION)
201 return false;
203 // Strip the keyword + leading space off the input, but don't exceed
204 // fill_into_edit. An obvious case is that the user may not have entered
205 // a leading space and is asking to launch this extension without any
206 // additional input.
207 size_t prefix_length =
208 std::min(match.keyword.length() + 1, match.fill_into_edit.length());
209 extensions::ExtensionOmniboxEventRouter::OnInputEntered(
210 controller_->GetWebContents(),
211 template_url->GetExtensionId(),
212 base::UTF16ToUTF8(match.fill_into_edit.substr(prefix_length)),
213 disposition);
215 return true;
218 void ChromeOmniboxClient::OnInputStateChanged() {
219 if (!controller_->GetWebContents())
220 return;
221 SearchTabHelper::FromWebContents(
222 controller_->GetWebContents())->OmniboxInputStateChanged();
225 void ChromeOmniboxClient::OnFocusChanged(
226 OmniboxFocusState state,
227 OmniboxFocusChangeReason reason) {
228 if (!controller_->GetWebContents())
229 return;
230 SearchTabHelper::FromWebContents(
231 controller_->GetWebContents())->OmniboxFocusChanged(state, reason);
234 void ChromeOmniboxClient::OnResultChanged(
235 const AutocompleteResult& result,
236 bool default_match_changed,
237 const base::Callback<void(const SkBitmap& bitmap)>& on_bitmap_fetched) {
238 if (search::IsInstantExtendedAPIEnabled() &&
239 ((default_match_changed && result.default_match() != result.end()) ||
240 (search::ShouldAllowPrefetchNonDefaultMatch() && !result.empty()))) {
241 InstantSuggestion prefetch_suggestion;
242 const AutocompleteMatch* match_to_prefetch = GetMatchToPrefetch(result);
243 if (match_to_prefetch) {
244 prefetch_suggestion.text = match_to_prefetch->contents;
245 prefetch_suggestion.metadata =
246 SearchProvider::GetSuggestMetadata(*match_to_prefetch);
248 // Send the prefetch suggestion unconditionally to the InstantPage. If
249 // there is no suggestion to prefetch, we need to send a blank query to
250 // clear the prefetched results.
251 SetSuggestionToPrefetch(prefetch_suggestion);
254 const auto match = std::find_if(
255 result.begin(), result.end(),
256 [](const AutocompleteMatch& current)->bool { return current.answer; });
257 if (match != result.end()) {
258 BitmapFetcherService* image_service =
259 BitmapFetcherServiceFactory::GetForBrowserContext(profile_);
260 if (image_service) {
261 image_service->CancelRequest(request_id_);
262 request_id_ = image_service->RequestImage(
263 match->answer->second_line().image_url(),
264 new AnswerImageObserver(
265 base::Bind(&ChromeOmniboxClient::OnBitmapFetched,
266 base::Unretained(this), on_bitmap_fetched)));
271 void ChromeOmniboxClient::OnCurrentMatchChanged(
272 const AutocompleteMatch& match) {
273 if (!prerender::IsOmniboxEnabled(profile_))
274 DoPreconnect(match);
277 void ChromeOmniboxClient::OnTextChanged(const AutocompleteMatch& current_match,
278 bool user_input_in_progress,
279 base::string16& user_text,
280 const AutocompleteResult& result,
281 bool is_popup_open,
282 bool has_focus) {
283 AutocompleteActionPredictor::Action recommended_action =
284 AutocompleteActionPredictor::ACTION_NONE;
285 if (user_input_in_progress) {
286 InstantSearchPrerenderer* prerenderer =
287 InstantSearchPrerenderer::GetForProfile(profile_);
288 if (prerenderer &&
289 prerenderer->IsAllowed(current_match, controller_->GetWebContents()) &&
290 is_popup_open && has_focus) {
291 recommended_action = AutocompleteActionPredictor::ACTION_PRERENDER;
292 } else {
293 AutocompleteActionPredictor* action_predictor =
294 predictors::AutocompleteActionPredictorFactory::GetForProfile(
295 profile_);
296 action_predictor->RegisterTransitionalMatches(user_text, result);
297 // Confer with the AutocompleteActionPredictor to determine what action,
298 // if any, we should take. Get the recommended action here even if we
299 // don't need it so we can get stats for anyone who is opted in to UMA,
300 // but only get it if the user has actually typed something to avoid
301 // constructing it before it's needed. Note: This event is triggered as
302 // part of startup when the initial tab transitions to the start page.
303 recommended_action =
304 action_predictor->RecommendAction(user_text, current_match);
308 UMA_HISTOGRAM_ENUMERATION("AutocompleteActionPredictor.Action",
309 recommended_action,
310 AutocompleteActionPredictor::LAST_PREDICT_ACTION);
312 switch (recommended_action) {
313 case AutocompleteActionPredictor::ACTION_PRERENDER:
314 // It's possible that there is no current page, for instance if the tab
315 // has been closed or on return from a sleep state.
316 // (http://crbug.com/105689)
317 if (!CurrentPageExists())
318 break;
319 // Ask for prerendering if the destination URL is different than the
320 // current URL.
321 if (current_match.destination_url != GetURL())
322 DoPrerender(current_match);
323 break;
324 case AutocompleteActionPredictor::ACTION_PRECONNECT:
325 DoPreconnect(current_match);
326 break;
327 case AutocompleteActionPredictor::ACTION_NONE:
328 break;
332 void ChromeOmniboxClient::OnInputAccepted(const AutocompleteMatch& match) {
333 // While the user is typing, the instant search base page may be prerendered
334 // in the background. Even though certain inputs may not be eligible for
335 // prerendering, the prerender isn't automatically cancelled as the user
336 // continues typing, in hopes the final input will end up making use of the
337 // prerenderer. Intermediate inputs that are legal for prerendering will be
338 // sent to the prerendered page to keep it up to date; then once the user
339 // commits a navigation, it will trigger code in chrome::Navigate() to swap in
340 // the prerenderer.
342 // Unfortunately, that swap code only has the navigated URL, so it doesn't
343 // actually know whether the prerenderer has been sent the relevant input
344 // already, or whether instead the user manually navigated to something that
345 // looks like a search URL (which won't have been sent to the prerenderer).
346 // In this case, we need to ensure the prerenderer is cancelled here so that
347 // code can't attempt to wrongly swap-in, or it could swap in an empty page in
348 // place of the correct navigation.
350 // This would be clearer if we could swap in the prerenderer here instead of
351 // over in chrome::Navigate(), but we have to wait until then because the
352 // final decision about whether to use the prerendered page depends on other
353 // parts of the chrome::NavigateParams struct not available until then.
354 InstantSearchPrerenderer* prerenderer =
355 InstantSearchPrerenderer::GetForProfile(profile_);
356 if (prerenderer &&
357 !prerenderer->IsAllowed(match, controller_->GetWebContents()))
358 prerenderer->Cancel();
361 void ChromeOmniboxClient::OnRevert() {
362 AutocompleteActionPredictor* action_predictor =
363 predictors::AutocompleteActionPredictorFactory::GetForProfile(profile_);
364 action_predictor->ClearTransitionalMatches();
365 action_predictor->CancelPrerender();
368 void ChromeOmniboxClient::OnURLOpenedFromOmnibox(OmniboxLog* log) {
369 content::NotificationService::current()->Notify(
370 chrome::NOTIFICATION_OMNIBOX_OPENED_URL,
371 content::Source<Profile>(profile_),
372 content::Details<OmniboxLog>(log));
375 void ChromeOmniboxClient::DiscardNonCommittedNavigations() {
376 controller_->GetWebContents()->GetController().DiscardNonCommittedEntries();
379 void ChromeOmniboxClient::DoPrerender(
380 const AutocompleteMatch& match) {
381 content::WebContents* web_contents = controller_->GetWebContents();
382 gfx::Rect container_bounds = web_contents->GetContainerBounds();
384 InstantSearchPrerenderer* prerenderer =
385 InstantSearchPrerenderer::GetForProfile(profile_);
386 if (prerenderer && prerenderer->IsAllowed(match, web_contents)) {
387 prerenderer->Init(
388 web_contents->GetController().GetDefaultSessionStorageNamespace(),
389 container_bounds.size());
390 return;
393 predictors::AutocompleteActionPredictorFactory::GetForProfile(profile_)->
394 StartPrerendering(
395 match.destination_url,
396 web_contents->GetController().GetDefaultSessionStorageNamespace(),
397 container_bounds.size());
400 void ChromeOmniboxClient::DoPreconnect(const AutocompleteMatch& match) {
401 if (match.destination_url.SchemeIs(extensions::kExtensionScheme))
402 return;
404 // Warm up DNS Prefetch cache, or preconnect to a search service.
405 UMA_HISTOGRAM_ENUMERATION("Autocomplete.MatchType", match.type,
406 AutocompleteMatchType::NUM_TYPES);
407 if (profile_->GetNetworkPredictor()) {
408 profile_->GetNetworkPredictor()->AnticipateOmniboxUrl(
409 match.destination_url,
410 predictors::AutocompleteActionPredictor::IsPreconnectable(match));
412 // We could prefetch the alternate nav URL, if any, but because there
413 // can be many of these as a user types an initial series of characters,
414 // the OS DNS cache could suffer eviction problems for minimal gain.
417 void ChromeOmniboxClient::SetSuggestionToPrefetch(
418 const InstantSuggestion& suggestion) {
419 DCHECK(search::IsInstantExtendedAPIEnabled());
420 content::WebContents* web_contents = controller_->GetWebContents();
421 if (web_contents &&
422 SearchTabHelper::FromWebContents(web_contents)->IsSearchResultsPage()) {
423 if (search::ShouldPrefetchSearchResultsOnSRP()) {
424 SearchTabHelper::FromWebContents(web_contents)->
425 SetSuggestionToPrefetch(suggestion);
427 } else {
428 InstantSearchPrerenderer* prerenderer =
429 InstantSearchPrerenderer::GetForProfile(profile_);
430 if (prerenderer)
431 prerenderer->Prerender(suggestion);
435 void ChromeOmniboxClient::OnBitmapFetched(const BitmapFetchedCallback& callback,
436 const SkBitmap& bitmap) {
437 request_id_ = BitmapFetcherService::REQUEST_ID_INVALID;
438 callback.Run(bitmap);