Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / ui / search / search_tab_helper.cc
blob04cef26e7797f504dbc77f308b806965c4d5cde8
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"
7 #include <set>
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/profiles/profile.h"
15 #include "chrome/browser/search/instant_service.h"
16 #include "chrome/browser/search/instant_service_factory.h"
17 #include "chrome/browser/search/search.h"
18 #include "chrome/browser/signin/signin_manager_factory.h"
19 #include "chrome/browser/sync/profile_sync_service.h"
20 #include "chrome/browser/sync/profile_sync_service_factory.h"
21 #include "chrome/browser/ui/app_list/app_list_util.h"
22 #include "chrome/browser/ui/browser_navigator.h"
23 #include "chrome/browser/ui/browser_window.h"
24 #include "chrome/browser/ui/location_bar/location_bar.h"
25 #include "chrome/browser/ui/omnibox/omnibox_edit_model.h"
26 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
27 #include "chrome/browser/ui/omnibox/omnibox_view.h"
28 #include "chrome/browser/ui/search/instant_search_prerenderer.h"
29 #include "chrome/browser/ui/search/instant_tab.h"
30 #include "chrome/browser/ui/search/search_ipc_router_policy_impl.h"
31 #include "chrome/browser/ui/search/search_tab_helper_delegate.h"
32 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
33 #include "chrome/browser/ui/webui/ntp/ntp_user_data_logger.h"
34 #include "chrome/common/url_constants.h"
35 #include "chrome/grit/generated_resources.h"
36 #include "components/google/core/browser/google_util.h"
37 #include "components/search/search.h"
38 #include "components/signin/core/browser/signin_manager.h"
39 #include "content/public/browser/navigation_controller.h"
40 #include "content/public/browser/navigation_details.h"
41 #include "content/public/browser/navigation_entry.h"
42 #include "content/public/browser/navigation_type.h"
43 #include "content/public/browser/notification_service.h"
44 #include "content/public/browser/notification_source.h"
45 #include "content/public/browser/render_frame_host.h"
46 #include "content/public/browser/render_process_host.h"
47 #include "content/public/browser/user_metrics.h"
48 #include "content/public/browser/web_contents.h"
49 #include "content/public/common/referrer.h"
50 #include "google_apis/gaia/gaia_auth_util.h"
51 #include "net/base/net_errors.h"
52 #include "ui/base/l10n/l10n_util.h"
53 #include "ui/base/page_transition_types.h"
54 #include "url/gurl.h"
56 DEFINE_WEB_CONTENTS_USER_DATA_KEY(SearchTabHelper);
58 namespace {
60 bool IsCacheableNTP(const content::WebContents* contents) {
61 const content::NavigationEntry* entry =
62 contents->GetController().GetLastCommittedEntry();
63 return chrome::NavEntryIsInstantNTP(contents, entry) &&
64 entry->GetURL() != GURL(chrome::kChromeSearchLocalNtpUrl);
67 bool IsNTP(const content::WebContents* contents) {
68 // We can't use WebContents::GetURL() because that uses the active entry,
69 // whereas we want the visible entry.
70 const content::NavigationEntry* entry =
71 contents->GetController().GetVisibleEntry();
72 if (entry && entry->GetVirtualURL() == GURL(chrome::kChromeUINewTabURL))
73 return true;
75 return chrome::IsInstantNTP(contents);
78 bool IsSearchResults(const content::WebContents* contents) {
79 return !chrome::GetSearchTerms(contents).empty();
82 bool IsLocal(const content::WebContents* contents) {
83 if (!contents)
84 return false;
85 const content::NavigationEntry* entry =
86 contents->GetController().GetVisibleEntry();
87 return entry && entry->GetURL() == GURL(chrome::kChromeSearchLocalNtpUrl);
90 // Returns true if |contents| are rendered inside an Instant process.
91 bool InInstantProcess(Profile* profile,
92 const content::WebContents* contents) {
93 if (!profile || !contents)
94 return false;
96 InstantService* instant_service =
97 InstantServiceFactory::GetForProfile(profile);
98 return instant_service &&
99 instant_service->IsInstantProcess(
100 contents->GetRenderProcessHost()->GetID());
103 // Called when an NTP finishes loading. If the load start time was noted,
104 // calculates and logs the total load time.
105 void RecordNewTabLoadTime(content::WebContents* contents) {
106 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents);
107 if (core_tab_helper->new_tab_start_time().is_null())
108 return;
110 base::TimeDelta duration =
111 base::TimeTicks::Now() - core_tab_helper->new_tab_start_time();
112 if (IsCacheableNTP(contents)) {
113 if (google_util::IsGoogleDomainUrl(
114 contents->GetController().GetLastCommittedEntry()->GetURL(),
115 google_util::ALLOW_SUBDOMAIN,
116 google_util::DISALLOW_NON_STANDARD_PORTS)) {
117 UMA_HISTOGRAM_TIMES("Tab.NewTabOnload.Google", duration);
118 } else {
119 UMA_HISTOGRAM_TIMES("Tab.NewTabOnload.Other", duration);
121 } else {
122 UMA_HISTOGRAM_TIMES("Tab.NewTabOnload.Local", duration);
124 core_tab_helper->set_new_tab_start_time(base::TimeTicks());
127 // Returns true if the user wants to sync history. This function returning true
128 // is not a guarantee that history is being synced, but it can be used to
129 // disable a feature that should not be shown to users who prefer not to sync
130 // their history.
131 bool IsHistorySyncEnabled(Profile* profile) {
132 ProfileSyncService* sync =
133 ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile);
134 return sync &&
135 sync->GetPreferredDataTypes().Has(syncer::HISTORY_DELETE_DIRECTIVES);
138 bool OmniboxHasFocus(OmniboxView* omnibox) {
139 return omnibox && omnibox->model()->has_focus();
142 } // namespace
144 SearchTabHelper::SearchTabHelper(content::WebContents* web_contents)
145 : WebContentsObserver(web_contents),
146 is_search_enabled_(chrome::IsInstantExtendedAPIEnabled()),
147 web_contents_(web_contents),
148 ipc_router_(web_contents,
149 this,
150 make_scoped_ptr(new SearchIPCRouterPolicyImpl(web_contents))),
151 instant_service_(NULL),
152 delegate_(NULL),
153 omnibox_has_focus_fn_(&OmniboxHasFocus) {
154 if (!is_search_enabled_)
155 return;
157 instant_service_ =
158 InstantServiceFactory::GetForProfile(
159 Profile::FromBrowserContext(web_contents_->GetBrowserContext()));
160 if (instant_service_)
161 instant_service_->AddObserver(this);
164 SearchTabHelper::~SearchTabHelper() {
165 if (instant_service_)
166 instant_service_->RemoveObserver(this);
169 void SearchTabHelper::InitForPreloadedNTP() {
170 UpdateMode(true, true);
173 void SearchTabHelper::OmniboxInputStateChanged() {
174 if (!is_search_enabled_)
175 return;
177 UpdateMode(false, false);
180 void SearchTabHelper::OmniboxFocusChanged(OmniboxFocusState state,
181 OmniboxFocusChangeReason reason) {
182 content::NotificationService::current()->Notify(
183 chrome::NOTIFICATION_OMNIBOX_FOCUS_CHANGED,
184 content::Source<SearchTabHelper>(this),
185 content::NotificationService::NoDetails());
187 ipc_router_.OmniboxFocusChanged(state, reason);
189 // Don't send oninputstart/oninputend updates in response to focus changes
190 // if there's a navigation in progress. This prevents Chrome from sending
191 // a spurious oninputend when the user accepts a match in the omnibox.
192 if (web_contents_->GetController().GetPendingEntry() == NULL) {
193 ipc_router_.SetInputInProgress(IsInputInProgress());
195 InstantSearchPrerenderer* prerenderer =
196 InstantSearchPrerenderer::GetForProfile(profile());
197 if (!prerenderer || !chrome::ShouldPrerenderInstantUrlOnOmniboxFocus())
198 return;
200 if (state == OMNIBOX_FOCUS_NONE) {
201 prerenderer->Cancel();
202 return;
205 if (!IsSearchResultsPage()) {
206 prerenderer->Init(
207 web_contents_->GetController().GetDefaultSessionStorageNamespace(),
208 web_contents_->GetContainerBounds().size());
213 void SearchTabHelper::NavigationEntryUpdated() {
214 if (!is_search_enabled_)
215 return;
217 UpdateMode(false, false);
220 void SearchTabHelper::InstantSupportChanged(bool instant_support) {
221 if (!is_search_enabled_)
222 return;
224 InstantSupportState new_state = instant_support ? INSTANT_SUPPORT_YES :
225 INSTANT_SUPPORT_NO;
227 model_.SetInstantSupportState(new_state);
229 content::NavigationEntry* entry =
230 web_contents_->GetController().GetLastCommittedEntry();
231 if (entry) {
232 chrome::SetInstantSupportStateInNavigationEntry(new_state, entry);
233 if (delegate_ && !instant_support)
234 delegate_->OnWebContentsInstantSupportDisabled(web_contents_);
238 bool SearchTabHelper::SupportsInstant() const {
239 return model_.instant_support() == INSTANT_SUPPORT_YES;
242 void SearchTabHelper::SetSuggestionToPrefetch(
243 const InstantSuggestion& suggestion) {
244 ipc_router_.SetSuggestionToPrefetch(suggestion);
247 void SearchTabHelper::Submit(const base::string16& text,
248 const EmbeddedSearchRequestParams& params) {
249 ipc_router_.Submit(text, params);
252 void SearchTabHelper::OnTabActivated() {
253 ipc_router_.OnTabActivated();
255 OmniboxView* omnibox_view = GetOmniboxView();
256 if (chrome::ShouldPrerenderInstantUrlOnOmniboxFocus() &&
257 omnibox_has_focus_fn_(omnibox_view)) {
258 InstantSearchPrerenderer* prerenderer =
259 InstantSearchPrerenderer::GetForProfile(profile());
260 if (prerenderer && !IsSearchResultsPage()) {
261 prerenderer->Init(
262 web_contents_->GetController().GetDefaultSessionStorageNamespace(),
263 web_contents_->GetContainerBounds().size());
268 void SearchTabHelper::OnTabDeactivated() {
269 ipc_router_.OnTabDeactivated();
272 void SearchTabHelper::ToggleVoiceSearch() {
273 ipc_router_.ToggleVoiceSearch();
276 bool SearchTabHelper::IsSearchResultsPage() {
277 return model_.mode().is_origin_search();
280 void SearchTabHelper::RenderViewCreated(
281 content::RenderViewHost* render_view_host) {
282 ipc_router_.SetPromoInformation(IsAppLauncherEnabled());
285 void SearchTabHelper::DidStartNavigationToPendingEntry(
286 const GURL& url,
287 content::NavigationController::ReloadType /* reload_type */) {
288 if (chrome::IsNTPURL(url, profile())) {
289 // Set the title on any pending entry corresponding to the NTP. This
290 // prevents any flickering of the tab title.
291 content::NavigationEntry* entry =
292 web_contents_->GetController().GetPendingEntry();
293 if (entry)
294 entry->SetTitle(l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE));
298 void SearchTabHelper::DidNavigateMainFrame(
299 const content::LoadCommittedDetails& details,
300 const content::FrameNavigateParams& params) {
301 if (IsCacheableNTP(web_contents_)) {
302 UMA_HISTOGRAM_ENUMERATION("InstantExtended.CacheableNTPLoad",
303 chrome::CACHEABLE_NTP_LOAD_SUCCEEDED,
304 chrome::CACHEABLE_NTP_LOAD_MAX);
307 // Always set the title on the new tab page to be the one from our UI
308 // resources. Normally, we set the title when we begin a NTP load, but it can
309 // get reset in several places (like when you press Reload). This check
310 // ensures that the title is properly set to the string defined by the Chrome
311 // UI language (rather than the server language) in all cases.
313 // We only override the title when it's nonempty to allow the page to set the
314 // title if it really wants. An empty title means to use the default. There's
315 // also a race condition between this code and the page's SetTitle call which
316 // this rule avoids.
317 content::NavigationEntry* entry =
318 web_contents_->GetController().GetLastCommittedEntry();
319 if (entry && entry->GetTitle().empty() &&
320 (entry->GetVirtualURL() == GURL(chrome::kChromeUINewTabURL) ||
321 chrome::NavEntryIsInstantNTP(web_contents_, entry))) {
322 entry->SetTitle(l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE));
326 void SearchTabHelper::DidFinishLoad(content::RenderFrameHost* render_frame_host,
327 const GURL& /* validated_url */) {
328 if (!render_frame_host->GetParent()) {
329 if (chrome::IsInstantNTP(web_contents_))
330 RecordNewTabLoadTime(web_contents_);
332 DetermineIfPageSupportsInstant();
336 void SearchTabHelper::NavigationEntryCommitted(
337 const content::LoadCommittedDetails& load_details) {
338 if (!is_search_enabled_)
339 return;
341 if (!load_details.is_main_frame)
342 return;
344 if (chrome::ShouldAssignURLToInstantRenderer(web_contents_->GetURL(),
345 profile())) {
346 InstantService* instant_service =
347 InstantServiceFactory::GetForProfile(profile());
348 ipc_router_.SetOmniboxStartMargin(instant_service->omnibox_start_margin());
349 ipc_router_.SetDisplayInstantResults();
352 UpdateMode(true, false);
354 content::NavigationEntry* entry =
355 web_contents_->GetController().GetVisibleEntry();
356 DCHECK(entry);
358 // Already determined the instant support state for this page, do not reset
359 // the instant support state.
361 // When we get a navigation entry committed event, there seem to be two ways
362 // to tell whether the navigation was "in-page". Ideally, when
363 // LoadCommittedDetails::is_in_page is true, we should have
364 // LoadCommittedDetails::type to be NAVIGATION_TYPE_IN_PAGE. Unfortunately,
365 // they are different in some cases. To workaround this bug, we are checking
366 // (is_in_page || type == NAVIGATION_TYPE_IN_PAGE). Please refer to
367 // crbug.com/251330 for more details.
368 if (load_details.is_in_page ||
369 load_details.type == content::NAVIGATION_TYPE_IN_PAGE) {
370 // When an "in-page" navigation happens, we will not receive a
371 // DidFinishLoad() event. Therefore, we will not determine the Instant
372 // support for the navigated page. So, copy over the Instant support from
373 // the previous entry. If the page does not support Instant, update the
374 // location bar from here to turn off search terms replacement.
375 chrome::SetInstantSupportStateInNavigationEntry(model_.instant_support(),
376 entry);
377 if (delegate_ && model_.instant_support() == INSTANT_SUPPORT_NO)
378 delegate_->OnWebContentsInstantSupportDisabled(web_contents_);
379 return;
382 model_.SetInstantSupportState(INSTANT_SUPPORT_UNKNOWN);
383 model_.SetVoiceSearchSupported(false);
384 chrome::SetInstantSupportStateInNavigationEntry(model_.instant_support(),
385 entry);
387 if (InInstantProcess(profile(), web_contents_))
388 ipc_router_.OnNavigationEntryCommitted();
391 void SearchTabHelper::OnInstantSupportDetermined(bool supports_instant) {
392 InstantSupportChanged(supports_instant);
395 void SearchTabHelper::OnSetVoiceSearchSupport(bool supports_voice_search) {
396 model_.SetVoiceSearchSupported(supports_voice_search);
399 void SearchTabHelper::ThemeInfoChanged(const ThemeBackgroundInfo& theme_info) {
400 ipc_router_.SendThemeBackgroundInfo(theme_info);
403 void SearchTabHelper::MostVisitedItemsChanged(
404 const std::vector<InstantMostVisitedItem>& items) {
405 // When most visited change, the NTP usually reloads the tiles. This means
406 // our metrics get inconsistent. So we'd rather emit stats now.
407 InstantTab::EmitNtpStatistics(web_contents_);
408 ipc_router_.SendMostVisitedItems(items);
411 void SearchTabHelper::OmniboxStartMarginChanged(int omnibox_start_margin) {
412 ipc_router_.SetOmniboxStartMargin(omnibox_start_margin);
415 void SearchTabHelper::FocusOmnibox(OmniboxFocusState state) {
416 // TODO(kmadhusu): Move platform specific code from here and get rid of #ifdef.
417 #if !defined(OS_ANDROID)
418 OmniboxView* omnibox = GetOmniboxView();
419 if (!omnibox)
420 return;
422 // Do not add a default case in the switch block for the following reasons:
423 // (1) Explicitly handle the new states. If new states are added in the
424 // OmniboxFocusState, the compiler will warn the developer to handle the new
425 // states.
426 // (2) An attacker may control the renderer and sends the browser process a
427 // malformed IPC. This function responds to the invalid |state| values by
428 // doing nothing instead of crashing the browser process (intentional no-op).
429 switch (state) {
430 case OMNIBOX_FOCUS_VISIBLE:
431 omnibox->SetFocus();
432 omnibox->model()->SetCaretVisibility(true);
433 break;
434 case OMNIBOX_FOCUS_INVISIBLE:
435 omnibox->SetFocus();
436 omnibox->model()->SetCaretVisibility(false);
437 // If the user clicked on the fakebox, any text already in the omnibox
438 // should get cleared when they start typing. Selecting all the existing
439 // text is a convenient way to accomplish this. It also gives a slight
440 // visual cue to users who really understand selection state about what
441 // will happen if they start typing.
442 omnibox->SelectAll(false);
443 omnibox->ShowImeIfNeeded();
444 break;
445 case OMNIBOX_FOCUS_NONE:
446 // Remove focus only if the popup is closed. This will prevent someone
447 // from changing the omnibox value and closing the popup without user
448 // interaction.
449 if (!omnibox->model()->popup_model()->IsOpen())
450 web_contents()->Focus();
451 break;
453 #endif
456 void SearchTabHelper::NavigateToURL(const GURL& url,
457 WindowOpenDisposition disposition,
458 bool is_most_visited_item_url) {
459 if (is_most_visited_item_url) {
460 content::RecordAction(
461 base::UserMetricsAction("InstantExtended.MostVisitedClicked"));
464 if (delegate_)
465 delegate_->NavigateOnThumbnailClick(url, disposition, web_contents_);
468 void SearchTabHelper::OnDeleteMostVisitedItem(const GURL& url) {
469 DCHECK(!url.is_empty());
470 if (instant_service_)
471 instant_service_->DeleteMostVisitedItem(url);
474 void SearchTabHelper::OnUndoMostVisitedDeletion(const GURL& url) {
475 DCHECK(!url.is_empty());
476 if (instant_service_)
477 instant_service_->UndoMostVisitedDeletion(url);
480 void SearchTabHelper::OnUndoAllMostVisitedDeletions() {
481 if (instant_service_)
482 instant_service_->UndoAllMostVisitedDeletions();
485 void SearchTabHelper::OnLogEvent(NTPLoggingEventType event,
486 base::TimeDelta time) {
487 // TODO(kmadhusu): Move platform specific code from here and get rid of #ifdef.
488 #if !defined(OS_ANDROID)
489 NTPUserDataLogger::GetOrCreateFromWebContents(web_contents())
490 ->LogEvent(event, time);
491 #endif
494 void SearchTabHelper::OnLogMostVisitedImpression(
495 int position, const base::string16& provider) {
496 // TODO(kmadhusu): Move platform specific code from here and get rid of #ifdef.
497 #if !defined(OS_ANDROID)
498 NTPUserDataLogger::GetOrCreateFromWebContents(
499 web_contents())->LogMostVisitedImpression(position, provider);
500 #endif
503 void SearchTabHelper::OnLogMostVisitedNavigation(
504 int position, const base::string16& provider) {
505 // TODO(kmadhusu): Move platform specific code from here and get rid of #ifdef.
506 #if !defined(OS_ANDROID)
507 NTPUserDataLogger::GetOrCreateFromWebContents(
508 web_contents())->LogMostVisitedNavigation(position, provider);
509 #endif
512 void SearchTabHelper::PasteIntoOmnibox(const base::string16& text) {
513 // TODO(kmadhusu): Move platform specific code from here and get rid of #ifdef.
514 #if !defined(OS_ANDROID)
515 OmniboxView* omnibox = GetOmniboxView();
516 if (!omnibox)
517 return;
518 // The first case is for right click to paste, where the text is retrieved
519 // from the clipboard already sanitized. The second case is needed to handle
520 // drag-and-drop value and it has to be sanitazed before setting it into the
521 // omnibox.
522 base::string16 text_to_paste = text.empty() ? omnibox->GetClipboardText() :
523 omnibox->SanitizeTextForPaste(text);
525 if (text_to_paste.empty())
526 return;
528 if (!omnibox->model()->has_focus())
529 omnibox->SetFocus();
531 omnibox->OnBeforePossibleChange();
532 omnibox->model()->OnPaste();
533 omnibox->SetUserText(text_to_paste);
534 omnibox->OnAfterPossibleChange();
535 #endif
538 void SearchTabHelper::OnChromeIdentityCheck(const base::string16& identity) {
539 SigninManagerBase* manager = SigninManagerFactory::GetForProfile(profile());
540 if (manager) {
541 ipc_router_.SendChromeIdentityCheckResult(
542 identity, gaia::AreEmailsSame(base::UTF16ToUTF8(identity),
543 manager->GetAuthenticatedUsername()));
544 } else {
545 ipc_router_.SendChromeIdentityCheckResult(identity, false);
549 void SearchTabHelper::OnHistorySyncCheck() {
550 ipc_router_.SendHistorySyncCheckResult(IsHistorySyncEnabled(profile()));
553 void SearchTabHelper::UpdateMode(bool update_origin, bool is_preloaded_ntp) {
554 SearchMode::Type type = SearchMode::MODE_DEFAULT;
555 SearchMode::Origin origin = SearchMode::ORIGIN_DEFAULT;
556 if (IsNTP(web_contents_) || is_preloaded_ntp) {
557 type = SearchMode::MODE_NTP;
558 origin = SearchMode::ORIGIN_NTP;
559 } else if (IsSearchResults(web_contents_)) {
560 type = SearchMode::MODE_SEARCH_RESULTS;
561 origin = SearchMode::ORIGIN_SEARCH;
563 if (!update_origin)
564 origin = model_.mode().origin;
566 OmniboxView* omnibox = GetOmniboxView();
567 if (omnibox && omnibox->model()->user_input_in_progress())
568 type = SearchMode::MODE_SEARCH_SUGGESTIONS;
570 SearchMode old_mode(model_.mode());
571 model_.SetMode(SearchMode(type, origin));
572 if (old_mode.is_ntp() != model_.mode().is_ntp()) {
573 ipc_router_.SetInputInProgress(IsInputInProgress());
577 void SearchTabHelper::DetermineIfPageSupportsInstant() {
578 if (!InInstantProcess(profile(), web_contents_)) {
579 // The page is not in the Instant process. This page does not support
580 // instant. If we send an IPC message to a page that is not in the Instant
581 // process, it will never receive it and will never respond. Therefore,
582 // return immediately.
583 InstantSupportChanged(false);
584 } else if (IsLocal(web_contents_)) {
585 // Local pages always support Instant.
586 InstantSupportChanged(true);
587 } else {
588 ipc_router_.DetermineIfPageSupportsInstant();
592 Profile* SearchTabHelper::profile() const {
593 return Profile::FromBrowserContext(web_contents_->GetBrowserContext());
596 bool SearchTabHelper::IsInputInProgress() const {
597 OmniboxView* omnibox = GetOmniboxView();
598 return !model_.mode().is_ntp() && omnibox &&
599 omnibox->model()->focus_state() == OMNIBOX_FOCUS_VISIBLE;
602 OmniboxView* SearchTabHelper::GetOmniboxView() const {
603 return delegate_ ? delegate_->GetOmniboxView() : NULL;