1 // Copyright 2012 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/search/search_tab_helper.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/metrics/histogram.h"
11 #include "base/strings/string16.h"
12 #include "base/strings/string_util.h"
13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/history/most_visited_tiles_experiment.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/search/instant_service.h"
17 #include "chrome/browser/search/instant_service_factory.h"
18 #include "chrome/browser/search/search.h"
19 #include "chrome/browser/signin/signin_manager_factory.h"
20 #include "chrome/browser/ui/app_list/app_list_util.h"
21 #include "chrome/browser/ui/browser_navigator.h"
22 #include "chrome/browser/ui/browser_window.h"
23 #include "chrome/browser/ui/omnibox/location_bar.h"
24 #include "chrome/browser/ui/omnibox/omnibox_edit_model.h"
25 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
26 #include "chrome/browser/ui/omnibox/omnibox_view.h"
27 #include "chrome/browser/ui/search/search_ipc_router_policy_impl.h"
28 #include "chrome/browser/ui/search/search_tab_helper_delegate.h"
29 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
30 #include "chrome/browser/ui/webui/ntp/ntp_user_data_logger.h"
31 #include "chrome/common/url_constants.h"
32 #include "components/signin/core/browser/signin_manager.h"
33 #include "content/public/browser/navigation_controller.h"
34 #include "content/public/browser/navigation_details.h"
35 #include "content/public/browser/navigation_entry.h"
36 #include "content/public/browser/navigation_type.h"
37 #include "content/public/browser/notification_service.h"
38 #include "content/public/browser/notification_source.h"
39 #include "content/public/browser/render_process_host.h"
40 #include "content/public/browser/user_metrics.h"
41 #include "content/public/browser/web_contents.h"
42 #include "content/public/common/page_transition_types.h"
43 #include "content/public/common/referrer.h"
44 #include "grit/generated_resources.h"
45 #include "net/base/net_errors.h"
46 #include "ui/base/l10n/l10n_util.h"
49 DEFINE_WEB_CONTENTS_USER_DATA_KEY(SearchTabHelper
);
53 // For reporting Cacheable NTP navigations.
54 enum CacheableNTPLoad
{
55 CACHEABLE_NTP_LOAD_FAILED
= 0,
56 CACHEABLE_NTP_LOAD_SUCCEEDED
= 1,
57 CACHEABLE_NTP_LOAD_MAX
= 2
60 void RecordCacheableNTPLoadHistogram(bool succeeded
) {
61 UMA_HISTOGRAM_ENUMERATION("InstantExtended.CacheableNTPLoad",
62 succeeded
? CACHEABLE_NTP_LOAD_SUCCEEDED
:
63 CACHEABLE_NTP_LOAD_FAILED
,
64 CACHEABLE_NTP_LOAD_MAX
);
67 bool IsCacheableNTP(const content::WebContents
* contents
) {
68 const content::NavigationEntry
* entry
=
69 contents
->GetController().GetLastCommittedEntry();
70 return chrome::NavEntryIsInstantNTP(contents
, entry
) &&
71 entry
->GetURL() != GURL(chrome::kChromeSearchLocalNtpUrl
);
74 bool IsNTP(const content::WebContents
* contents
) {
75 // We can't use WebContents::GetURL() because that uses the active entry,
76 // whereas we want the visible entry.
77 const content::NavigationEntry
* entry
=
78 contents
->GetController().GetVisibleEntry();
79 if (entry
&& entry
->GetVirtualURL() == GURL(chrome::kChromeUINewTabURL
))
82 return chrome::IsInstantNTP(contents
);
85 bool IsSearchResults(const content::WebContents
* contents
) {
86 return !chrome::GetSearchTerms(contents
).empty();
89 bool IsLocal(const content::WebContents
* contents
) {
92 const content::NavigationEntry
* entry
=
93 contents
->GetController().GetVisibleEntry();
94 return entry
&& entry
->GetURL() == GURL(chrome::kChromeSearchLocalNtpUrl
);
97 // Returns true if |contents| are rendered inside an Instant process.
98 bool InInstantProcess(Profile
* profile
,
99 const content::WebContents
* contents
) {
100 if (!profile
|| !contents
)
103 InstantService
* instant_service
=
104 InstantServiceFactory::GetForProfile(profile
);
105 return instant_service
&&
106 instant_service
->IsInstantProcess(
107 contents
->GetRenderProcessHost()->GetID());
110 // Called when an NTP finishes loading. If the load start time was noted,
111 // calculates and logs the total load time.
112 void RecordNewTabLoadTime(content::WebContents
* contents
) {
113 CoreTabHelper
* core_tab_helper
= CoreTabHelper::FromWebContents(contents
);
114 if (core_tab_helper
->new_tab_start_time().is_null())
117 base::TimeDelta duration
=
118 base::TimeTicks::Now() - core_tab_helper
->new_tab_start_time();
119 UMA_HISTOGRAM_TIMES("Tab.NewTabOnload", duration
);
120 core_tab_helper
->set_new_tab_start_time(base::TimeTicks());
125 SearchTabHelper::SearchTabHelper(content::WebContents
* web_contents
)
126 : WebContentsObserver(web_contents
),
127 is_search_enabled_(chrome::IsInstantExtendedAPIEnabled()),
128 web_contents_(web_contents
),
129 ipc_router_(web_contents
, this,
130 make_scoped_ptr(new SearchIPCRouterPolicyImpl(web_contents
))
131 .PassAs
<SearchIPCRouter::Policy
>()),
132 instant_service_(NULL
),
134 if (!is_search_enabled_
)
138 InstantServiceFactory::GetForProfile(
139 Profile::FromBrowserContext(web_contents_
->GetBrowserContext()));
140 if (instant_service_
)
141 instant_service_
->AddObserver(this);
144 SearchTabHelper::~SearchTabHelper() {
145 if (instant_service_
)
146 instant_service_
->RemoveObserver(this);
149 void SearchTabHelper::InitForPreloadedNTP() {
150 UpdateMode(true, true);
153 void SearchTabHelper::OmniboxInputStateChanged() {
154 if (!is_search_enabled_
)
157 UpdateMode(false, false);
160 void SearchTabHelper::OmniboxFocusChanged(OmniboxFocusState state
,
161 OmniboxFocusChangeReason reason
) {
162 content::NotificationService::current()->Notify(
163 chrome::NOTIFICATION_OMNIBOX_FOCUS_CHANGED
,
164 content::Source
<SearchTabHelper
>(this),
165 content::NotificationService::NoDetails());
167 ipc_router_
.OmniboxFocusChanged(state
, reason
);
169 // Don't send oninputstart/oninputend updates in response to focus changes
170 // if there's a navigation in progress. This prevents Chrome from sending
171 // a spurious oninputend when the user accepts a match in the omnibox.
172 if (web_contents_
->GetController().GetPendingEntry() == NULL
) {
173 ipc_router_
.SetInputInProgress(IsInputInProgress());
177 void SearchTabHelper::NavigationEntryUpdated() {
178 if (!is_search_enabled_
)
181 UpdateMode(false, false);
184 void SearchTabHelper::InstantSupportChanged(bool instant_support
) {
185 if (!is_search_enabled_
)
188 InstantSupportState new_state
= instant_support
? INSTANT_SUPPORT_YES
:
191 model_
.SetInstantSupportState(new_state
);
193 content::NavigationEntry
* entry
=
194 web_contents_
->GetController().GetLastCommittedEntry();
196 chrome::SetInstantSupportStateInNavigationEntry(new_state
, entry
);
197 if (delegate_
&& !instant_support
)
198 delegate_
->OnWebContentsInstantSupportDisabled(web_contents_
);
202 bool SearchTabHelper::SupportsInstant() const {
203 return model_
.instant_support() == INSTANT_SUPPORT_YES
;
206 void SearchTabHelper::SetSuggestionToPrefetch(
207 const InstantSuggestion
& suggestion
) {
208 ipc_router_
.SetSuggestionToPrefetch(suggestion
);
211 void SearchTabHelper::Submit(const base::string16
& text
) {
212 ipc_router_
.Submit(text
);
215 void SearchTabHelper::OnTabActivated() {
216 ipc_router_
.OnTabActivated();
219 void SearchTabHelper::OnTabDeactivated() {
220 ipc_router_
.OnTabDeactivated();
223 void SearchTabHelper::ToggleVoiceSearch() {
224 ipc_router_
.ToggleVoiceSearch();
227 bool SearchTabHelper::IsSearchResultsPage() {
228 return model_
.mode().is_origin_search();
231 void SearchTabHelper::RenderViewCreated(
232 content::RenderViewHost
* render_view_host
) {
233 ipc_router_
.SetPromoInformation(IsAppLauncherEnabled());
236 void SearchTabHelper::DidStartNavigationToPendingEntry(
238 content::NavigationController::ReloadType
/* reload_type */) {
239 if (chrome::IsNTPURL(url
, profile())) {
240 // Set the title on any pending entry corresponding to the NTP. This
241 // prevents any flickering of the tab title.
242 content::NavigationEntry
* entry
=
243 web_contents_
->GetController().GetPendingEntry();
245 entry
->SetTitle(l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE
));
249 void SearchTabHelper::DidNavigateMainFrame(
250 const content::LoadCommittedDetails
& details
,
251 const content::FrameNavigateParams
& params
) {
252 if (IsCacheableNTP(web_contents_
)) {
253 if (details
.http_status_code
== 204 || details
.http_status_code
>= 400) {
254 RedirectToLocalNTP();
255 RecordCacheableNTPLoadHistogram(false);
258 RecordCacheableNTPLoadHistogram(true);
261 // Always set the title on the new tab page to be the one from our UI
262 // resources. Normally, we set the title when we begin a NTP load, but it can
263 // get reset in several places (like when you press Reload). This check
264 // ensures that the title is properly set to the string defined by the Chrome
265 // UI language (rather than the server language) in all cases.
267 // We only override the title when it's nonempty to allow the page to set the
268 // title if it really wants. An empty title means to use the default. There's
269 // also a race condition between this code and the page's SetTitle call which
271 content::NavigationEntry
* entry
=
272 web_contents_
->GetController().GetLastCommittedEntry();
273 if (entry
&& entry
->GetTitle().empty() &&
274 (entry
->GetVirtualURL() == GURL(chrome::kChromeUINewTabURL
) ||
275 chrome::NavEntryIsInstantNTP(web_contents_
, entry
))) {
276 entry
->SetTitle(l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE
));
280 void SearchTabHelper::DidFailProvisionalLoad(
281 int64
/* frame_id */,
282 const base::string16
& /* frame_unique_name */,
284 const GURL
& validated_url
,
286 const base::string16
& /* error_description */,
287 content::RenderViewHost
* /* render_view_host */) {
288 // If error_code is ERR_ABORTED means that the user has canceled this
289 // navigation so it shouldn't be redirected.
291 error_code
!= net::ERR_ABORTED
&&
292 validated_url
!= GURL(chrome::kChromeSearchLocalNtpUrl
) &&
293 chrome::IsNTPURL(validated_url
, profile())) {
294 RedirectToLocalNTP();
295 RecordCacheableNTPLoadHistogram(false);
299 void SearchTabHelper::DidFinishLoad(
300 int64
/* frame_id */,
301 const GURL
& /* validated_url */,
303 content::RenderViewHost
* /* render_view_host */) {
305 if (chrome::IsInstantNTP(web_contents_
))
306 RecordNewTabLoadTime(web_contents_
);
308 DetermineIfPageSupportsInstant();
312 void SearchTabHelper::NavigationEntryCommitted(
313 const content::LoadCommittedDetails
& load_details
) {
314 if (!is_search_enabled_
)
317 if (!load_details
.is_main_frame
)
320 if (chrome::ShouldAssignURLToInstantRenderer(web_contents_
->GetURL(),
322 InstantService
* instant_service
=
323 InstantServiceFactory::GetForProfile(profile());
324 ipc_router_
.SetOmniboxStartMargin(instant_service
->omnibox_start_margin());
325 ipc_router_
.SetDisplayInstantResults();
328 UpdateMode(true, false);
330 content::NavigationEntry
* entry
=
331 web_contents_
->GetController().GetVisibleEntry();
334 // Already determined the instant support state for this page, do not reset
335 // the instant support state.
337 // When we get a navigation entry committed event, there seem to be two ways
338 // to tell whether the navigation was "in-page". Ideally, when
339 // LoadCommittedDetails::is_in_page is true, we should have
340 // LoadCommittedDetails::type to be NAVIGATION_TYPE_IN_PAGE. Unfortunately,
341 // they are different in some cases. To workaround this bug, we are checking
342 // (is_in_page || type == NAVIGATION_TYPE_IN_PAGE). Please refer to
343 // crbug.com/251330 for more details.
344 if (load_details
.is_in_page
||
345 load_details
.type
== content::NAVIGATION_TYPE_IN_PAGE
) {
346 // When an "in-page" navigation happens, we will not receive a
347 // DidFinishLoad() event. Therefore, we will not determine the Instant
348 // support for the navigated page. So, copy over the Instant support from
349 // the previous entry. If the page does not support Instant, update the
350 // location bar from here to turn off search terms replacement.
351 chrome::SetInstantSupportStateInNavigationEntry(model_
.instant_support(),
353 if (delegate_
&& model_
.instant_support() == INSTANT_SUPPORT_NO
)
354 delegate_
->OnWebContentsInstantSupportDisabled(web_contents_
);
358 model_
.SetInstantSupportState(INSTANT_SUPPORT_UNKNOWN
);
359 model_
.SetVoiceSearchSupported(false);
360 chrome::SetInstantSupportStateInNavigationEntry(model_
.instant_support(),
364 void SearchTabHelper::OnInstantSupportDetermined(bool supports_instant
) {
365 InstantSupportChanged(supports_instant
);
368 void SearchTabHelper::OnSetVoiceSearchSupport(bool supports_voice_search
) {
369 model_
.SetVoiceSearchSupported(supports_voice_search
);
372 void SearchTabHelper::ThemeInfoChanged(const ThemeBackgroundInfo
& theme_info
) {
373 ipc_router_
.SendThemeBackgroundInfo(theme_info
);
376 void SearchTabHelper::MostVisitedItemsChanged(
377 const std::vector
<InstantMostVisitedItem
>& items
) {
378 std::vector
<InstantMostVisitedItem
> items_copy(items
);
379 MaybeRemoveMostVisitedItems(&items_copy
);
380 ipc_router_
.SendMostVisitedItems(items_copy
);
383 void SearchTabHelper::OmniboxStartMarginChanged(int omnibox_start_margin
) {
384 ipc_router_
.SetOmniboxStartMargin(omnibox_start_margin
);
387 void SearchTabHelper::MaybeRemoveMostVisitedItems(
388 std::vector
<InstantMostVisitedItem
>* items
) {
392 if (!history::MostVisitedTilesExperiment::IsDontShowOpenURLsEnabled())
395 history::MostVisitedTilesExperiment::RemoveItemsMatchingOpenTabs(
396 delegate_
->GetOpenUrls(), items
);
399 void SearchTabHelper::FocusOmnibox(OmniboxFocusState state
) {
400 // TODO(kmadhusu): Move platform specific code from here and get rid of #ifdef.
401 #if !defined(OS_ANDROID)
402 OmniboxView
* omnibox
= GetOmniboxView();
406 // Do not add a default case in the switch block for the following reasons:
407 // (1) Explicitly handle the new states. If new states are added in the
408 // OmniboxFocusState, the compiler will warn the developer to handle the new
410 // (2) An attacker may control the renderer and sends the browser process a
411 // malformed IPC. This function responds to the invalid |state| values by
412 // doing nothing instead of crashing the browser process (intentional no-op).
414 case OMNIBOX_FOCUS_VISIBLE
:
416 omnibox
->model()->SetCaretVisibility(true);
418 case OMNIBOX_FOCUS_INVISIBLE
:
420 omnibox
->model()->SetCaretVisibility(false);
421 // If the user clicked on the fakebox, any text already in the omnibox
422 // should get cleared when they start typing. Selecting all the existing
423 // text is a convenient way to accomplish this. It also gives a slight
424 // visual cue to users who really understand selection state about what
425 // will happen if they start typing.
426 omnibox
->SelectAll(false);
427 omnibox
->ShowImeIfNeeded();
429 case OMNIBOX_FOCUS_NONE
:
430 // Remove focus only if the popup is closed. This will prevent someone
431 // from changing the omnibox value and closing the popup without user
433 if (!omnibox
->model()->popup_model()->IsOpen())
434 web_contents()->Focus();
440 void SearchTabHelper::NavigateToURL(const GURL
& url
,
441 WindowOpenDisposition disposition
,
442 bool is_most_visited_item_url
) {
443 if (is_most_visited_item_url
) {
444 content::RecordAction(
445 base::UserMetricsAction("InstantExtended.MostVisitedClicked"));
449 delegate_
->NavigateOnThumbnailClick(url
, disposition
, web_contents_
);
452 void SearchTabHelper::OnDeleteMostVisitedItem(const GURL
& url
) {
453 DCHECK(!url
.is_empty());
454 if (instant_service_
)
455 instant_service_
->DeleteMostVisitedItem(url
);
458 void SearchTabHelper::OnUndoMostVisitedDeletion(const GURL
& url
) {
459 DCHECK(!url
.is_empty());
460 if (instant_service_
)
461 instant_service_
->UndoMostVisitedDeletion(url
);
464 void SearchTabHelper::OnUndoAllMostVisitedDeletions() {
465 if (instant_service_
)
466 instant_service_
->UndoAllMostVisitedDeletions();
469 void SearchTabHelper::OnLogEvent(NTPLoggingEventType event
) {
470 // TODO(kmadhusu): Move platform specific code from here and get rid of #ifdef.
471 #if !defined(OS_ANDROID)
472 NTPUserDataLogger::GetOrCreateFromWebContents(
473 web_contents())->LogEvent(event
);
477 void SearchTabHelper::OnLogMostVisitedImpression(
478 int position
, const base::string16
& provider
) {
479 // TODO(kmadhusu): Move platform specific code from here and get rid of #ifdef.
480 #if !defined(OS_ANDROID)
481 NTPUserDataLogger::GetOrCreateFromWebContents(
482 web_contents())->LogMostVisitedImpression(position
, provider
);
486 void SearchTabHelper::OnLogMostVisitedNavigation(
487 int position
, const base::string16
& provider
) {
488 // TODO(kmadhusu): Move platform specific code from here and get rid of #ifdef.
489 #if !defined(OS_ANDROID)
490 NTPUserDataLogger::GetOrCreateFromWebContents(
491 web_contents())->LogMostVisitedNavigation(position
, provider
);
495 void SearchTabHelper::PasteIntoOmnibox(const base::string16
& text
) {
496 // TODO(kmadhusu): Move platform specific code from here and get rid of #ifdef.
497 #if !defined(OS_ANDROID)
498 OmniboxView
* omnibox
= GetOmniboxView();
501 // The first case is for right click to paste, where the text is retrieved
502 // from the clipboard already sanitized. The second case is needed to handle
503 // drag-and-drop value and it has to be sanitazed before setting it into the
505 base::string16 text_to_paste
= text
.empty() ? omnibox
->GetClipboardText() :
506 omnibox
->SanitizeTextForPaste(text
);
508 if (text_to_paste
.empty())
511 if (!omnibox
->model()->has_focus())
514 omnibox
->OnBeforePossibleChange();
515 omnibox
->model()->OnPaste();
516 omnibox
->SetUserText(text_to_paste
);
517 omnibox
->OnAfterPossibleChange();
521 void SearchTabHelper::OnChromeIdentityCheck(const base::string16
& identity
) {
522 SigninManagerBase
* manager
= SigninManagerFactory::GetForProfile(profile());
524 const base::string16 username
=
525 base::UTF8ToUTF16(manager
->GetAuthenticatedUsername());
526 ipc_router_
.SendChromeIdentityCheckResult(identity
,
527 identity
== username
);
531 void SearchTabHelper::UpdateMode(bool update_origin
, bool is_preloaded_ntp
) {
532 SearchMode::Type type
= SearchMode::MODE_DEFAULT
;
533 SearchMode::Origin origin
= SearchMode::ORIGIN_DEFAULT
;
534 if (IsNTP(web_contents_
) || is_preloaded_ntp
) {
535 type
= SearchMode::MODE_NTP
;
536 origin
= SearchMode::ORIGIN_NTP
;
537 } else if (IsSearchResults(web_contents_
)) {
538 type
= SearchMode::MODE_SEARCH_RESULTS
;
539 origin
= SearchMode::ORIGIN_SEARCH
;
542 origin
= model_
.mode().origin
;
544 OmniboxView
* omnibox
= GetOmniboxView();
545 if (omnibox
&& omnibox
->model()->user_input_in_progress())
546 type
= SearchMode::MODE_SEARCH_SUGGESTIONS
;
548 SearchMode
old_mode(model_
.mode());
549 model_
.SetMode(SearchMode(type
, origin
));
550 if (old_mode
.is_ntp() != model_
.mode().is_ntp()) {
551 ipc_router_
.SetInputInProgress(IsInputInProgress());
555 void SearchTabHelper::DetermineIfPageSupportsInstant() {
556 if (!InInstantProcess(profile(), web_contents_
)) {
557 // The page is not in the Instant process. This page does not support
558 // instant. If we send an IPC message to a page that is not in the Instant
559 // process, it will never receive it and will never respond. Therefore,
560 // return immediately.
561 InstantSupportChanged(false);
562 } else if (IsLocal(web_contents_
)) {
563 // Local pages always support Instant.
564 InstantSupportChanged(true);
566 ipc_router_
.DetermineIfPageSupportsInstant();
570 Profile
* SearchTabHelper::profile() const {
571 return Profile::FromBrowserContext(web_contents_
->GetBrowserContext());
574 void SearchTabHelper::RedirectToLocalNTP() {
575 // Extra parentheses to declare a variable.
576 content::NavigationController::LoadURLParams
load_params(
577 (GURL(chrome::kChromeSearchLocalNtpUrl
)));
578 load_params
.referrer
= content::Referrer();
579 load_params
.transition_type
= content::PAGE_TRANSITION_SERVER_REDIRECT
;
580 // Don't push a history entry.
581 load_params
.should_replace_current_entry
= true;
582 web_contents_
->GetController().LoadURLWithParams(load_params
);
585 bool SearchTabHelper::IsInputInProgress() const {
586 OmniboxView
* omnibox
= GetOmniboxView();
587 return !model_
.mode().is_ntp() && omnibox
&&
588 omnibox
->model()->focus_state() == OMNIBOX_FOCUS_VISIBLE
;
591 OmniboxView
* SearchTabHelper::GetOmniboxView() const {
592 return delegate_
? delegate_
->GetOmniboxView() : NULL
;