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"
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"
46 using predictors::AutocompleteActionPredictor
;
50 // Returns the AutocompleteMatch that the InstantController should prefetch, if
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
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);
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
92 class AnswerImageObserver
: public BitmapFetcherService::Observer
{
94 explicit AnswerImageObserver(const BitmapFetchedCallback
& callback
)
95 : callback_(callback
) {}
97 void OnImageChanged(BitmapFetcherService::RequestId request_id
,
98 const SkBitmap
& image
) override
;
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
);
115 ChromeOmniboxClient::ChromeOmniboxClient(OmniboxEditController
* controller
,
117 : controller_(controller
),
119 request_id_(BitmapFetcherService::REQUEST_ID_INVALID
) {}
121 ChromeOmniboxClient::~ChromeOmniboxClient() {
122 BitmapFetcherService
* image_service
=
123 BitmapFetcherServiceFactory::GetForBrowserContext(profile_
);
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())
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);
189 (template_url
->GetType() == TemplateURL::OMNIBOX_API_EXTENSION
)) {
190 return extensions::OmniboxAPI::Get(profile_
)
191 ->GetOmniboxPopupIcon(template_url
->GetExtensionId());
196 bool ChromeOmniboxClient::ProcessExtensionKeyword(
197 TemplateURL
* template_url
,
198 const AutocompleteMatch
& match
,
199 WindowOpenDisposition disposition
) {
200 if (template_url
->GetType() != TemplateURL::OMNIBOX_API_EXTENSION
)
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
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
)),
218 void ChromeOmniboxClient::OnInputStateChanged() {
219 if (!controller_
->GetWebContents())
221 SearchTabHelper::FromWebContents(
222 controller_
->GetWebContents())->OmniboxInputStateChanged();
225 void ChromeOmniboxClient::OnFocusChanged(
226 OmniboxFocusState state
,
227 OmniboxFocusChangeReason reason
) {
228 if (!controller_
->GetWebContents())
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_
);
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_
))
277 void ChromeOmniboxClient::OnTextChanged(const AutocompleteMatch
& current_match
,
278 bool user_input_in_progress
,
279 base::string16
& user_text
,
280 const AutocompleteResult
& result
,
283 AutocompleteActionPredictor::Action recommended_action
=
284 AutocompleteActionPredictor::ACTION_NONE
;
285 if (user_input_in_progress
) {
286 InstantSearchPrerenderer
* prerenderer
=
287 InstantSearchPrerenderer::GetForProfile(profile_
);
289 prerenderer
->IsAllowed(current_match
, controller_
->GetWebContents()) &&
290 is_popup_open
&& has_focus
) {
291 recommended_action
= AutocompleteActionPredictor::ACTION_PRERENDER
;
293 AutocompleteActionPredictor
* action_predictor
=
294 predictors::AutocompleteActionPredictorFactory::GetForProfile(
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.
304 action_predictor
->RecommendAction(user_text
, current_match
);
308 UMA_HISTOGRAM_ENUMERATION("AutocompleteActionPredictor.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())
319 // Ask for prerendering if the destination URL is different than the
321 if (current_match
.destination_url
!= GetURL())
322 DoPrerender(current_match
);
324 case AutocompleteActionPredictor::ACTION_PRECONNECT
:
325 DoPreconnect(current_match
);
327 case AutocompleteActionPredictor::ACTION_NONE
:
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
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_
);
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
)) {
388 web_contents
->GetController().GetDefaultSessionStorageNamespace(),
389 container_bounds
.size());
393 predictors::AutocompleteActionPredictorFactory::GetForProfile(profile_
)->
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
))
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();
422 SearchTabHelper::FromWebContents(web_contents
)->IsSearchResultsPage()) {
423 if (search::ShouldPrefetchSearchResultsOnSRP()) {
424 SearchTabHelper::FromWebContents(web_contents
)->
425 SetSuggestionToPrefetch(suggestion
);
428 InstantSearchPrerenderer
* prerenderer
=
429 InstantSearchPrerenderer::GetForProfile(profile_
);
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
);