Switch global error menu icon to vectorized MD asset
[chromium-blink-merge.git] / chrome / browser / ui / omnibox / chrome_omnibox_client.cc
blob504e28fda4c96890f93c906d35e9fb3e669de614
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/prefs/pref_service.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/app/chrome_command_ids.h"
13 #include "chrome/browser/autocomplete/autocomplete_classifier_factory.h"
14 #include "chrome/browser/autocomplete/chrome_autocomplete_provider_client.h"
15 #include "chrome/browser/bitmap_fetcher/bitmap_fetcher_service_factory.h"
16 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
17 #include "chrome/browser/bookmarks/bookmark_stats.h"
18 #include "chrome/browser/command_updater.h"
19 #include "chrome/browser/extensions/api/omnibox/omnibox_api.h"
20 #include "chrome/browser/net/predictor.h"
21 #include "chrome/browser/predictors/autocomplete_action_predictor.h"
22 #include "chrome/browser/predictors/autocomplete_action_predictor_factory.h"
23 #include "chrome/browser/prerender/prerender_field_trial.h"
24 #include "chrome/browser/prerender/prerender_manager.h"
25 #include "chrome/browser/prerender/prerender_manager_factory.h"
26 #include "chrome/browser/profiles/profile.h"
27 #include "chrome/browser/search/search.h"
28 #include "chrome/browser/search_engines/template_url_service_factory.h"
29 #include "chrome/browser/sessions/session_tab_helper.h"
30 #include "chrome/browser/ui/omnibox/chrome_omnibox_edit_controller.h"
31 #include "chrome/browser/ui/omnibox/chrome_omnibox_navigation_observer.h"
32 #include "chrome/browser/ui/search/instant_search_prerenderer.h"
33 #include "chrome/browser/ui/search/search_tab_helper.h"
34 #include "chrome/common/instant_types.h"
35 #include "chrome/common/pref_names.h"
36 #include "chrome/common/url_constants.h"
37 #include "components/favicon/content/content_favicon_driver.h"
38 #include "components/omnibox/browser/autocomplete_match.h"
39 #include "components/omnibox/browser/autocomplete_result.h"
40 #include "components/omnibox/browser/search_provider.h"
41 #include "components/search/search.h"
42 #include "components/search_engines/template_url_service.h"
43 #include "content/public/browser/navigation_controller.h"
44 #include "content/public/browser/navigation_entry.h"
45 #include "content/public/browser/web_contents.h"
46 #include "extensions/common/constants.h"
47 #include "ui/base/window_open_disposition.h"
48 #include "url/gurl.h"
50 using predictors::AutocompleteActionPredictor;
52 namespace {
54 // Returns the AutocompleteMatch that the InstantController should prefetch, if
55 // any.
57 // The SearchProvider may mark some suggestions to be prefetched based on
58 // instructions from the suggest server. If such a match ranks sufficiently
59 // highly or if kAllowPrefetchNonDefaultMatch field trial is enabled, we'll
60 // return it.
62 // If the kAllowPrefetchNonDefaultMatch field trial is enabled we return the
63 // prefetch suggestion even if it is not the default match. Otherwise we only
64 // care about matches that are the default or the second entry in the dropdown
65 // (which can happen for non-default matches when a top verbatim match is
66 // shown); for other matches, we think the likelihood of the user selecting
67 // them is low enough that prefetching isn't worth doing.
68 const AutocompleteMatch* GetMatchToPrefetch(const AutocompleteResult& result) {
69 if (search::ShouldAllowPrefetchNonDefaultMatch()) {
70 const AutocompleteResult::const_iterator prefetch_match = std::find_if(
71 result.begin(), result.end(), SearchProvider::ShouldPrefetch);
72 return prefetch_match != result.end() ? &(*prefetch_match) : NULL;
75 // If the default match should be prefetched, do that.
76 const auto default_match = result.default_match();
77 if ((default_match != result.end()) &&
78 SearchProvider::ShouldPrefetch(*default_match))
79 return &(*default_match);
81 // Otherwise, if the top match is a verbatim match and the very next match
82 // is prefetchable, fetch that.
83 if (result.TopMatchIsStandaloneVerbatimMatch() && (result.size() > 1) &&
84 SearchProvider::ShouldPrefetch(result.match_at(1)))
85 return &result.match_at(1);
87 return NULL;
90 // Calls the specified callback when the requested image is downloaded. This
91 // is a separate class instead of being implemented on ChromeOmniboxClient
92 // because BitmapFetcherService currently takes ownership of this object.
93 // TODO(dschuyler): Make BitmapFetcherService use the more typical non-owning
94 // ObserverList pattern and have ChromeOmniboxClient implement the Observer
95 // call directly.
96 class AnswerImageObserver : public BitmapFetcherService::Observer {
97 public:
98 explicit AnswerImageObserver(const BitmapFetchedCallback& callback)
99 : callback_(callback) {}
101 void OnImageChanged(BitmapFetcherService::RequestId request_id,
102 const SkBitmap& image) override;
104 private:
105 const BitmapFetchedCallback callback_;
107 DISALLOW_COPY_AND_ASSIGN(AnswerImageObserver);
110 void AnswerImageObserver::OnImageChanged(
111 BitmapFetcherService::RequestId request_id,
112 const SkBitmap& image) {
113 DCHECK(!image.empty());
114 callback_.Run(image);
117 } // namespace
119 ChromeOmniboxClient::ChromeOmniboxClient(OmniboxEditController* controller,
120 Profile* profile)
121 : controller_(static_cast<ChromeOmniboxEditController*>(controller)),
122 profile_(profile),
123 scheme_classifier_(profile),
124 request_id_(BitmapFetcherService::REQUEST_ID_INVALID) {}
126 ChromeOmniboxClient::~ChromeOmniboxClient() {
127 BitmapFetcherService* image_service =
128 BitmapFetcherServiceFactory::GetForBrowserContext(profile_);
129 if (image_service)
130 image_service->CancelRequest(request_id_);
133 scoped_ptr<AutocompleteProviderClient>
134 ChromeOmniboxClient::CreateAutocompleteProviderClient() {
135 return make_scoped_ptr(new ChromeAutocompleteProviderClient(profile_));
138 scoped_ptr<OmniboxNavigationObserver>
139 ChromeOmniboxClient::CreateOmniboxNavigationObserver(
140 const base::string16& text,
141 const AutocompleteMatch& match,
142 const AutocompleteMatch& alternate_nav_match) {
143 return make_scoped_ptr(new ChromeOmniboxNavigationObserver(
144 profile_, text, match, alternate_nav_match));
147 bool ChromeOmniboxClient::CurrentPageExists() const {
148 return (controller_->GetWebContents() != NULL);
151 const GURL& ChromeOmniboxClient::GetURL() const {
152 return controller_->GetWebContents()->GetVisibleURL();
155 const base::string16& ChromeOmniboxClient::GetTitle() const {
156 return controller_->GetWebContents()->GetTitle();
159 gfx::Image ChromeOmniboxClient::GetFavicon() const {
160 return favicon::ContentFaviconDriver::FromWebContents(
161 controller_->GetWebContents())
162 ->GetFavicon();
165 bool ChromeOmniboxClient::IsInstantNTP() const {
166 return search::IsInstantNTP(controller_->GetWebContents());
169 bool ChromeOmniboxClient::IsSearchResultsPage() const {
170 Profile* profile = Profile::FromBrowserContext(
171 controller_->GetWebContents()->GetBrowserContext());
172 return TemplateURLServiceFactory::GetForProfile(profile)->
173 IsSearchResultsPageFromDefaultSearchProvider(GetURL());
176 bool ChromeOmniboxClient::IsLoading() const {
177 return controller_->GetWebContents()->IsLoading();
180 bool ChromeOmniboxClient::IsPasteAndGoEnabled() const {
181 return controller_->command_updater()->IsCommandEnabled(IDC_OPEN_CURRENT_URL);
184 bool ChromeOmniboxClient::IsNewTabPage(const std::string& url) const {
185 return url == chrome::kChromeUINewTabURL;
188 bool ChromeOmniboxClient::IsHomePage(const std::string& url) const {
189 return url == profile_->GetPrefs()->GetString(prefs::kHomePage);
192 const SessionID& ChromeOmniboxClient::GetSessionID() const {
193 return SessionTabHelper::FromWebContents(
194 controller_->GetWebContents())->session_id();
197 bookmarks::BookmarkModel* ChromeOmniboxClient::GetBookmarkModel() {
198 return BookmarkModelFactory::GetForProfile(profile_);
201 TemplateURLService* ChromeOmniboxClient::GetTemplateURLService() {
202 return TemplateURLServiceFactory::GetForProfile(profile_);
205 const AutocompleteSchemeClassifier&
206 ChromeOmniboxClient::GetSchemeClassifier() const {
207 return scheme_classifier_;
210 AutocompleteClassifier* ChromeOmniboxClient::GetAutocompleteClassifier() {
211 return AutocompleteClassifierFactory::GetForProfile(profile_);
214 gfx::Image ChromeOmniboxClient::GetIconIfExtensionMatch(
215 const AutocompleteMatch& match) const {
216 TemplateURLService* service =
217 TemplateURLServiceFactory::GetForProfile(profile_);
218 const TemplateURL* template_url = match.GetTemplateURL(service, false);
219 if (template_url &&
220 (template_url->GetType() == TemplateURL::OMNIBOX_API_EXTENSION)) {
221 return extensions::OmniboxAPI::Get(profile_)
222 ->GetOmniboxPopupIcon(template_url->GetExtensionId());
224 return gfx::Image();
227 bool ChromeOmniboxClient::ProcessExtensionKeyword(
228 TemplateURL* template_url,
229 const AutocompleteMatch& match,
230 WindowOpenDisposition disposition,
231 OmniboxNavigationObserver* observer) {
232 if (template_url->GetType() != TemplateURL::OMNIBOX_API_EXTENSION)
233 return false;
235 // Strip the keyword + leading space off the input, but don't exceed
236 // fill_into_edit. An obvious case is that the user may not have entered
237 // a leading space and is asking to launch this extension without any
238 // additional input.
239 size_t prefix_length =
240 std::min(match.keyword.length() + 1, match.fill_into_edit.length());
241 extensions::ExtensionOmniboxEventRouter::OnInputEntered(
242 controller_->GetWebContents(),
243 template_url->GetExtensionId(),
244 base::UTF16ToUTF8(match.fill_into_edit.substr(prefix_length)),
245 disposition);
247 static_cast<ChromeOmniboxNavigationObserver*>(observer)
248 ->OnSuccessfulNavigation();
249 return true;
252 void ChromeOmniboxClient::OnInputStateChanged() {
253 if (!controller_->GetWebContents())
254 return;
255 SearchTabHelper::FromWebContents(
256 controller_->GetWebContents())->OmniboxInputStateChanged();
259 void ChromeOmniboxClient::OnFocusChanged(
260 OmniboxFocusState state,
261 OmniboxFocusChangeReason reason) {
262 if (!controller_->GetWebContents())
263 return;
264 SearchTabHelper::FromWebContents(
265 controller_->GetWebContents())->OmniboxFocusChanged(state, reason);
268 void ChromeOmniboxClient::OnResultChanged(
269 const AutocompleteResult& result,
270 bool default_match_changed,
271 const base::Callback<void(const SkBitmap& bitmap)>& on_bitmap_fetched) {
272 if (search::IsInstantExtendedAPIEnabled() &&
273 ((default_match_changed && result.default_match() != result.end()) ||
274 (search::ShouldAllowPrefetchNonDefaultMatch() && !result.empty()))) {
275 InstantSuggestion prefetch_suggestion;
276 const AutocompleteMatch* match_to_prefetch = GetMatchToPrefetch(result);
277 if (match_to_prefetch) {
278 prefetch_suggestion.text = match_to_prefetch->contents;
279 prefetch_suggestion.metadata =
280 SearchProvider::GetSuggestMetadata(*match_to_prefetch);
282 // Send the prefetch suggestion unconditionally to the InstantPage. If
283 // there is no suggestion to prefetch, we need to send a blank query to
284 // clear the prefetched results.
285 SetSuggestionToPrefetch(prefetch_suggestion);
288 const auto match = std::find_if(
289 result.begin(), result.end(),
290 [](const AutocompleteMatch& current)->bool { return current.answer; });
291 if (match != result.end()) {
292 BitmapFetcherService* image_service =
293 BitmapFetcherServiceFactory::GetForBrowserContext(profile_);
294 if (image_service) {
295 image_service->CancelRequest(request_id_);
296 request_id_ = image_service->RequestImage(
297 match->answer->second_line().image_url(),
298 new AnswerImageObserver(
299 base::Bind(&ChromeOmniboxClient::OnBitmapFetched,
300 base::Unretained(this), on_bitmap_fetched)));
305 void ChromeOmniboxClient::OnCurrentMatchChanged(
306 const AutocompleteMatch& match) {
307 if (!prerender::IsOmniboxEnabled(profile_))
308 DoPreconnect(match);
311 void ChromeOmniboxClient::OnTextChanged(const AutocompleteMatch& current_match,
312 bool user_input_in_progress,
313 base::string16& user_text,
314 const AutocompleteResult& result,
315 bool is_popup_open,
316 bool has_focus) {
317 AutocompleteActionPredictor::Action recommended_action =
318 AutocompleteActionPredictor::ACTION_NONE;
319 if (user_input_in_progress) {
320 InstantSearchPrerenderer* prerenderer =
321 InstantSearchPrerenderer::GetForProfile(profile_);
322 if (prerenderer &&
323 prerenderer->IsAllowed(current_match, controller_->GetWebContents()) &&
324 is_popup_open && has_focus) {
325 recommended_action = AutocompleteActionPredictor::ACTION_PRERENDER;
326 } else {
327 AutocompleteActionPredictor* action_predictor =
328 predictors::AutocompleteActionPredictorFactory::GetForProfile(
329 profile_);
330 action_predictor->RegisterTransitionalMatches(user_text, result);
331 // Confer with the AutocompleteActionPredictor to determine what action,
332 // if any, we should take. Get the recommended action here even if we
333 // don't need it so we can get stats for anyone who is opted in to UMA,
334 // but only get it if the user has actually typed something to avoid
335 // constructing it before it's needed. Note: This event is triggered as
336 // part of startup when the initial tab transitions to the start page.
337 recommended_action =
338 action_predictor->RecommendAction(user_text, current_match);
342 UMA_HISTOGRAM_ENUMERATION("AutocompleteActionPredictor.Action",
343 recommended_action,
344 AutocompleteActionPredictor::LAST_PREDICT_ACTION);
346 switch (recommended_action) {
347 case AutocompleteActionPredictor::ACTION_PRERENDER:
348 // It's possible that there is no current page, for instance if the tab
349 // has been closed or on return from a sleep state.
350 // (http://crbug.com/105689)
351 if (!CurrentPageExists())
352 break;
353 // Ask for prerendering if the destination URL is different than the
354 // current URL.
355 if (current_match.destination_url != GetURL())
356 DoPrerender(current_match);
357 break;
358 case AutocompleteActionPredictor::ACTION_PRECONNECT:
359 DoPreconnect(current_match);
360 break;
361 case AutocompleteActionPredictor::ACTION_NONE:
362 break;
366 void ChromeOmniboxClient::OnInputAccepted(const AutocompleteMatch& match) {
367 // While the user is typing, the instant search base page may be prerendered
368 // in the background. Even though certain inputs may not be eligible for
369 // prerendering, the prerender isn't automatically cancelled as the user
370 // continues typing, in hopes the final input will end up making use of the
371 // prerenderer. Intermediate inputs that are legal for prerendering will be
372 // sent to the prerendered page to keep it up to date; then once the user
373 // commits a navigation, it will trigger code in chrome::Navigate() to swap in
374 // the prerenderer.
376 // Unfortunately, that swap code only has the navigated URL, so it doesn't
377 // actually know whether the prerenderer has been sent the relevant input
378 // already, or whether instead the user manually navigated to something that
379 // looks like a search URL (which won't have been sent to the prerenderer).
380 // In this case, we need to ensure the prerenderer is cancelled here so that
381 // code can't attempt to wrongly swap-in, or it could swap in an empty page in
382 // place of the correct navigation.
384 // This would be clearer if we could swap in the prerenderer here instead of
385 // over in chrome::Navigate(), but we have to wait until then because the
386 // final decision about whether to use the prerendered page depends on other
387 // parts of the chrome::NavigateParams struct not available until then.
388 InstantSearchPrerenderer* prerenderer =
389 InstantSearchPrerenderer::GetForProfile(profile_);
390 if (prerenderer &&
391 !prerenderer->IsAllowed(match, controller_->GetWebContents()))
392 prerenderer->Cancel();
395 void ChromeOmniboxClient::OnRevert() {
396 AutocompleteActionPredictor* action_predictor =
397 predictors::AutocompleteActionPredictorFactory::GetForProfile(profile_);
398 action_predictor->ClearTransitionalMatches();
399 action_predictor->CancelPrerender();
402 void ChromeOmniboxClient::OnURLOpenedFromOmnibox(OmniboxLog* log) {
403 predictors::AutocompleteActionPredictorFactory::GetForProfile(profile_)
404 ->OnOmniboxOpenedUrl(*log);
407 void ChromeOmniboxClient::OnBookmarkLaunched() {
408 RecordBookmarkLaunch(NULL, BOOKMARK_LAUNCH_LOCATION_OMNIBOX);
411 void ChromeOmniboxClient::DiscardNonCommittedNavigations() {
412 controller_->GetWebContents()->GetController().DiscardNonCommittedEntries();
415 void ChromeOmniboxClient::DoPrerender(
416 const AutocompleteMatch& match) {
417 content::WebContents* web_contents = controller_->GetWebContents();
418 gfx::Rect container_bounds = web_contents->GetContainerBounds();
420 InstantSearchPrerenderer* prerenderer =
421 InstantSearchPrerenderer::GetForProfile(profile_);
422 if (prerenderer && prerenderer->IsAllowed(match, web_contents)) {
423 prerenderer->Init(
424 web_contents->GetController().GetDefaultSessionStorageNamespace(),
425 container_bounds.size());
426 return;
429 predictors::AutocompleteActionPredictorFactory::GetForProfile(profile_)->
430 StartPrerendering(
431 match.destination_url,
432 web_contents->GetController().GetDefaultSessionStorageNamespace(),
433 container_bounds.size());
436 void ChromeOmniboxClient::DoPreconnect(const AutocompleteMatch& match) {
437 if (match.destination_url.SchemeIs(extensions::kExtensionScheme))
438 return;
440 // Warm up DNS Prefetch cache, or preconnect to a search service.
441 UMA_HISTOGRAM_ENUMERATION("Autocomplete.MatchType", match.type,
442 AutocompleteMatchType::NUM_TYPES);
443 if (profile_->GetNetworkPredictor()) {
444 profile_->GetNetworkPredictor()->AnticipateOmniboxUrl(
445 match.destination_url,
446 predictors::AutocompleteActionPredictor::IsPreconnectable(match));
448 // We could prefetch the alternate nav URL, if any, but because there
449 // can be many of these as a user types an initial series of characters,
450 // the OS DNS cache could suffer eviction problems for minimal gain.
453 void ChromeOmniboxClient::SetSuggestionToPrefetch(
454 const InstantSuggestion& suggestion) {
455 DCHECK(search::IsInstantExtendedAPIEnabled());
456 content::WebContents* web_contents = controller_->GetWebContents();
457 if (web_contents &&
458 SearchTabHelper::FromWebContents(web_contents)->IsSearchResultsPage()) {
459 if (search::ShouldPrefetchSearchResultsOnSRP()) {
460 SearchTabHelper::FromWebContents(web_contents)->
461 SetSuggestionToPrefetch(suggestion);
463 } else {
464 InstantSearchPrerenderer* prerenderer =
465 InstantSearchPrerenderer::GetForProfile(profile_);
466 if (prerenderer)
467 prerenderer->Prerender(suggestion);
471 void ChromeOmniboxClient::OnBitmapFetched(const BitmapFetchedCallback& callback,
472 const SkBitmap& bitmap) {
473 request_id_ = BitmapFetcherService::REQUEST_ID_INVALID;
474 callback.Run(bitmap);