Switch global error menu icon to vectorized MD asset
[chromium-blink-merge.git] / chrome / browser / ui / search / search_tab_helper.cc
blob2464b71b88b3e923bdb686ad53760254fc03b601
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/clipboard_utils.h"
26 #include "chrome/browser/ui/search/instant_search_prerenderer.h"
27 #include "chrome/browser/ui/search/instant_tab.h"
28 #include "chrome/browser/ui/search/search_ipc_router_policy_impl.h"
29 #include "chrome/browser/ui/search/search_tab_helper_delegate.h"
30 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
31 #include "chrome/browser/ui/webui/ntp/ntp_user_data_logger.h"
32 #include "chrome/common/url_constants.h"
33 #include "chrome/grit/generated_resources.h"
34 #include "components/google/core/browser/google_util.h"
35 #include "components/omnibox/browser/omnibox_edit_model.h"
36 #include "components/omnibox/browser/omnibox_popup_model.h"
37 #include "components/omnibox/browser/omnibox_view.h"
38 #include "components/search/search.h"
39 #include "components/signin/core/browser/signin_manager.h"
40 #include "content/public/browser/navigation_controller.h"
41 #include "content/public/browser/navigation_details.h"
42 #include "content/public/browser/navigation_entry.h"
43 #include "content/public/browser/navigation_type.h"
44 #include "content/public/browser/notification_service.h"
45 #include "content/public/browser/notification_source.h"
46 #include "content/public/browser/render_frame_host.h"
47 #include "content/public/browser/render_process_host.h"
48 #include "content/public/browser/user_metrics.h"
49 #include "content/public/browser/web_contents.h"
50 #include "content/public/common/referrer.h"
51 #include "google_apis/gaia/gaia_auth_util.h"
52 #include "net/base/net_errors.h"
53 #include "ui/base/l10n/l10n_util.h"
54 #include "ui/base/page_transition_types.h"
55 #include "url/gurl.h"
57 DEFINE_WEB_CONTENTS_USER_DATA_KEY(SearchTabHelper);
59 namespace {
61 bool IsCacheableNTP(const content::WebContents* contents) {
62 const content::NavigationEntry* entry =
63 contents->GetController().GetLastCommittedEntry();
64 return search::NavEntryIsInstantNTP(contents, entry) &&
65 entry->GetURL() != GURL(chrome::kChromeSearchLocalNtpUrl);
68 bool IsNTP(const content::WebContents* contents) {
69 // We can't use WebContents::GetURL() because that uses the active entry,
70 // whereas we want the visible entry.
71 const content::NavigationEntry* entry =
72 contents->GetController().GetVisibleEntry();
73 if (entry && entry->GetVirtualURL() == GURL(chrome::kChromeUINewTabURL))
74 return true;
76 return search::IsInstantNTP(contents);
79 bool IsSearchResults(const content::WebContents* contents) {
80 return !search::GetSearchTerms(contents).empty();
83 bool IsLocal(const content::WebContents* contents) {
84 if (!contents)
85 return false;
86 const content::NavigationEntry* entry =
87 contents->GetController().GetVisibleEntry();
88 return entry && entry->GetURL() == GURL(chrome::kChromeSearchLocalNtpUrl);
91 // Returns true if |contents| are rendered inside an Instant process.
92 bool InInstantProcess(Profile* profile,
93 const content::WebContents* contents) {
94 if (!profile || !contents)
95 return false;
97 InstantService* instant_service =
98 InstantServiceFactory::GetForProfile(profile);
99 return instant_service &&
100 instant_service->IsInstantProcess(
101 contents->GetRenderProcessHost()->GetID());
104 // Called when an NTP finishes loading. If the load start time was noted,
105 // calculates and logs the total load time.
106 void RecordNewTabLoadTime(content::WebContents* contents) {
107 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents);
108 if (core_tab_helper->new_tab_start_time().is_null())
109 return;
111 base::TimeDelta duration =
112 base::TimeTicks::Now() - core_tab_helper->new_tab_start_time();
113 if (IsCacheableNTP(contents)) {
114 if (google_util::IsGoogleDomainUrl(
115 contents->GetController().GetLastCommittedEntry()->GetURL(),
116 google_util::ALLOW_SUBDOMAIN,
117 google_util::DISALLOW_NON_STANDARD_PORTS)) {
118 UMA_HISTOGRAM_TIMES("Tab.NewTabOnload.Google", duration);
119 } else {
120 UMA_HISTOGRAM_TIMES("Tab.NewTabOnload.Other", duration);
122 } else {
123 UMA_HISTOGRAM_TIMES("Tab.NewTabOnload.Local", duration);
125 core_tab_helper->set_new_tab_start_time(base::TimeTicks());
128 // Returns true if the user wants to sync history. This function returning true
129 // is not a guarantee that history is being synced, but it can be used to
130 // disable a feature that should not be shown to users who prefer not to sync
131 // their history.
132 bool IsHistorySyncEnabled(Profile* profile) {
133 ProfileSyncService* sync =
134 ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile);
135 return sync &&
136 sync->GetPreferredDataTypes().Has(syncer::HISTORY_DELETE_DIRECTIVES);
139 bool OmniboxHasFocus(OmniboxView* omnibox) {
140 return omnibox && omnibox->model()->has_focus();
143 } // namespace
145 SearchTabHelper::SearchTabHelper(content::WebContents* web_contents)
146 : WebContentsObserver(web_contents),
147 is_search_enabled_(search::IsInstantExtendedAPIEnabled()),
148 web_contents_(web_contents),
149 ipc_router_(web_contents,
150 this,
151 make_scoped_ptr(new SearchIPCRouterPolicyImpl(web_contents))),
152 instant_service_(NULL),
153 delegate_(NULL),
154 omnibox_has_focus_fn_(&OmniboxHasFocus) {
155 if (!is_search_enabled_)
156 return;
158 instant_service_ =
159 InstantServiceFactory::GetForProfile(
160 Profile::FromBrowserContext(web_contents_->GetBrowserContext()));
161 if (instant_service_)
162 instant_service_->AddObserver(this);
165 SearchTabHelper::~SearchTabHelper() {
166 if (instant_service_)
167 instant_service_->RemoveObserver(this);
170 void SearchTabHelper::InitForPreloadedNTP() {
171 UpdateMode(true, true);
174 void SearchTabHelper::OmniboxInputStateChanged() {
175 if (!is_search_enabled_)
176 return;
178 UpdateMode(false, false);
181 void SearchTabHelper::OmniboxFocusChanged(OmniboxFocusState state,
182 OmniboxFocusChangeReason reason) {
183 content::NotificationService::current()->Notify(
184 chrome::NOTIFICATION_OMNIBOX_FOCUS_CHANGED,
185 content::Source<SearchTabHelper>(this),
186 content::NotificationService::NoDetails());
188 ipc_router_.OmniboxFocusChanged(state, reason);
190 // Don't send oninputstart/oninputend updates in response to focus changes
191 // if there's a navigation in progress. This prevents Chrome from sending
192 // a spurious oninputend when the user accepts a match in the omnibox.
193 if (web_contents_->GetController().GetPendingEntry() == NULL) {
194 ipc_router_.SetInputInProgress(IsInputInProgress());
196 InstantSearchPrerenderer* prerenderer =
197 InstantSearchPrerenderer::GetForProfile(profile());
198 if (!prerenderer || !search::ShouldPrerenderInstantUrlOnOmniboxFocus())
199 return;
201 if (state == OMNIBOX_FOCUS_NONE) {
202 prerenderer->Cancel();
203 return;
206 if (!IsSearchResultsPage()) {
207 prerenderer->Init(
208 web_contents_->GetController().GetDefaultSessionStorageNamespace(),
209 web_contents_->GetContainerBounds().size());
214 void SearchTabHelper::NavigationEntryUpdated() {
215 if (!is_search_enabled_)
216 return;
218 UpdateMode(false, false);
221 void SearchTabHelper::InstantSupportChanged(bool instant_support) {
222 if (!is_search_enabled_)
223 return;
225 InstantSupportState new_state = instant_support ? INSTANT_SUPPORT_YES :
226 INSTANT_SUPPORT_NO;
228 model_.SetInstantSupportState(new_state);
230 content::NavigationEntry* entry =
231 web_contents_->GetController().GetLastCommittedEntry();
232 if (entry) {
233 search::SetInstantSupportStateInNavigationEntry(new_state, entry);
234 if (delegate_ && !instant_support)
235 delegate_->OnWebContentsInstantSupportDisabled(web_contents_);
239 bool SearchTabHelper::SupportsInstant() const {
240 return model_.instant_support() == INSTANT_SUPPORT_YES;
243 void SearchTabHelper::SetSuggestionToPrefetch(
244 const InstantSuggestion& suggestion) {
245 ipc_router_.SetSuggestionToPrefetch(suggestion);
248 void SearchTabHelper::Submit(const base::string16& text,
249 const EmbeddedSearchRequestParams& params) {
250 ipc_router_.Submit(text, params);
253 void SearchTabHelper::OnTabActivated() {
254 ipc_router_.OnTabActivated();
256 OmniboxView* omnibox_view = GetOmniboxView();
257 if (search::ShouldPrerenderInstantUrlOnOmniboxFocus() &&
258 omnibox_has_focus_fn_(omnibox_view)) {
259 InstantSearchPrerenderer* prerenderer =
260 InstantSearchPrerenderer::GetForProfile(profile());
261 if (prerenderer && !IsSearchResultsPage()) {
262 prerenderer->Init(
263 web_contents_->GetController().GetDefaultSessionStorageNamespace(),
264 web_contents_->GetContainerBounds().size());
269 void SearchTabHelper::OnTabDeactivated() {
270 ipc_router_.OnTabDeactivated();
273 void SearchTabHelper::ToggleVoiceSearch() {
274 ipc_router_.ToggleVoiceSearch();
277 bool SearchTabHelper::IsSearchResultsPage() {
278 return model_.mode().is_origin_search();
281 void SearchTabHelper::RenderViewCreated(
282 content::RenderViewHost* render_view_host) {
283 ipc_router_.SetPromoInformation(IsAppLauncherEnabled());
286 void SearchTabHelper::DidStartNavigationToPendingEntry(
287 const GURL& url,
288 content::NavigationController::ReloadType /* reload_type */) {
289 if (search::IsNTPURL(url, profile())) {
290 // Set the title on any pending entry corresponding to the NTP. This
291 // prevents any flickering of the tab title.
292 content::NavigationEntry* entry =
293 web_contents_->GetController().GetPendingEntry();
294 if (entry)
295 entry->SetTitle(l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE));
299 void SearchTabHelper::DidNavigateMainFrame(
300 const content::LoadCommittedDetails& details,
301 const content::FrameNavigateParams& params) {
302 if (IsCacheableNTP(web_contents_)) {
303 UMA_HISTOGRAM_ENUMERATION("InstantExtended.CacheableNTPLoad",
304 search::CACHEABLE_NTP_LOAD_SUCCEEDED,
305 search::CACHEABLE_NTP_LOAD_MAX);
308 // Always set the title on the new tab page to be the one from our UI
309 // resources. Normally, we set the title when we begin a NTP load, but it can
310 // get reset in several places (like when you press Reload). This check
311 // ensures that the title is properly set to the string defined by the Chrome
312 // UI language (rather than the server language) in all cases.
314 // We only override the title when it's nonempty to allow the page to set the
315 // title if it really wants. An empty title means to use the default. There's
316 // also a race condition between this code and the page's SetTitle call which
317 // this rule avoids.
318 content::NavigationEntry* entry =
319 web_contents_->GetController().GetLastCommittedEntry();
320 if (entry && entry->GetTitle().empty() &&
321 (entry->GetVirtualURL() == GURL(chrome::kChromeUINewTabURL) ||
322 search::NavEntryIsInstantNTP(web_contents_, entry))) {
323 entry->SetTitle(l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE));
327 void SearchTabHelper::DidFinishLoad(content::RenderFrameHost* render_frame_host,
328 const GURL& /* validated_url */) {
329 if (!render_frame_host->GetParent()) {
330 if (search::IsInstantNTP(web_contents_))
331 RecordNewTabLoadTime(web_contents_);
333 DetermineIfPageSupportsInstant();
337 void SearchTabHelper::NavigationEntryCommitted(
338 const content::LoadCommittedDetails& load_details) {
339 if (!is_search_enabled_)
340 return;
342 if (!load_details.is_main_frame)
343 return;
345 if (search::ShouldAssignURLToInstantRenderer(web_contents_->GetURL(),
346 profile())) {
347 InstantService* instant_service =
348 InstantServiceFactory::GetForProfile(profile());
349 ipc_router_.SetOmniboxStartMargin(instant_service->omnibox_start_margin());
350 ipc_router_.SetDisplayInstantResults();
353 UpdateMode(true, false);
355 content::NavigationEntry* entry =
356 web_contents_->GetController().GetVisibleEntry();
357 DCHECK(entry);
359 // Already determined the instant support state for this page, do not reset
360 // the instant support state.
361 if (load_details.is_in_page) {
362 // When an "in-page" navigation happens, we will not receive a
363 // DidFinishLoad() event. Therefore, we will not determine the Instant
364 // support for the navigated page. So, copy over the Instant support from
365 // the previous entry. If the page does not support Instant, update the
366 // location bar from here to turn off search terms replacement.
367 search::SetInstantSupportStateInNavigationEntry(model_.instant_support(),
368 entry);
369 if (delegate_ && model_.instant_support() == INSTANT_SUPPORT_NO)
370 delegate_->OnWebContentsInstantSupportDisabled(web_contents_);
371 return;
374 model_.SetInstantSupportState(INSTANT_SUPPORT_UNKNOWN);
375 model_.SetVoiceSearchSupported(false);
376 search::SetInstantSupportStateInNavigationEntry(model_.instant_support(),
377 entry);
379 if (InInstantProcess(profile(), web_contents_))
380 ipc_router_.OnNavigationEntryCommitted();
383 void SearchTabHelper::OnInstantSupportDetermined(bool supports_instant) {
384 InstantSupportChanged(supports_instant);
387 void SearchTabHelper::OnSetVoiceSearchSupport(bool supports_voice_search) {
388 model_.SetVoiceSearchSupported(supports_voice_search);
391 void SearchTabHelper::ThemeInfoChanged(const ThemeBackgroundInfo& theme_info) {
392 ipc_router_.SendThemeBackgroundInfo(theme_info);
395 void SearchTabHelper::MostVisitedItemsChanged(
396 const std::vector<InstantMostVisitedItem>& items) {
397 // When most visited change, the NTP usually reloads the tiles. This means
398 // our metrics get inconsistent. So we'd rather emit stats now.
399 InstantTab::EmitNtpStatistics(web_contents_);
400 ipc_router_.SendMostVisitedItems(items);
403 void SearchTabHelper::OmniboxStartMarginChanged(int omnibox_start_margin) {
404 ipc_router_.SetOmniboxStartMargin(omnibox_start_margin);
407 void SearchTabHelper::FocusOmnibox(OmniboxFocusState state) {
408 // TODO(kmadhusu): Move platform specific code from here and get rid of #ifdef.
409 #if !defined(OS_ANDROID)
410 OmniboxView* omnibox = GetOmniboxView();
411 if (!omnibox)
412 return;
414 // Do not add a default case in the switch block for the following reasons:
415 // (1) Explicitly handle the new states. If new states are added in the
416 // OmniboxFocusState, the compiler will warn the developer to handle the new
417 // states.
418 // (2) An attacker may control the renderer and sends the browser process a
419 // malformed IPC. This function responds to the invalid |state| values by
420 // doing nothing instead of crashing the browser process (intentional no-op).
421 switch (state) {
422 case OMNIBOX_FOCUS_VISIBLE:
423 omnibox->SetFocus();
424 omnibox->model()->SetCaretVisibility(true);
425 break;
426 case OMNIBOX_FOCUS_INVISIBLE:
427 omnibox->SetFocus();
428 omnibox->model()->SetCaretVisibility(false);
429 // If the user clicked on the fakebox, any text already in the omnibox
430 // should get cleared when they start typing. Selecting all the existing
431 // text is a convenient way to accomplish this. It also gives a slight
432 // visual cue to users who really understand selection state about what
433 // will happen if they start typing.
434 omnibox->SelectAll(false);
435 omnibox->ShowImeIfNeeded();
436 break;
437 case OMNIBOX_FOCUS_NONE:
438 // Remove focus only if the popup is closed. This will prevent someone
439 // from changing the omnibox value and closing the popup without user
440 // interaction.
441 if (!omnibox->model()->popup_model()->IsOpen())
442 web_contents()->Focus();
443 break;
445 #endif
448 void SearchTabHelper::NavigateToURL(const GURL& url,
449 WindowOpenDisposition disposition,
450 bool is_most_visited_item_url) {
451 if (is_most_visited_item_url) {
452 content::RecordAction(
453 base::UserMetricsAction("InstantExtended.MostVisitedClicked"));
456 if (delegate_)
457 delegate_->NavigateOnThumbnailClick(url, disposition, web_contents_);
460 void SearchTabHelper::OnDeleteMostVisitedItem(const GURL& url) {
461 DCHECK(!url.is_empty());
462 if (instant_service_)
463 instant_service_->DeleteMostVisitedItem(url);
466 void SearchTabHelper::OnUndoMostVisitedDeletion(const GURL& url) {
467 DCHECK(!url.is_empty());
468 if (instant_service_)
469 instant_service_->UndoMostVisitedDeletion(url);
472 void SearchTabHelper::OnUndoAllMostVisitedDeletions() {
473 if (instant_service_)
474 instant_service_->UndoAllMostVisitedDeletions();
477 void SearchTabHelper::OnLogEvent(NTPLoggingEventType event,
478 base::TimeDelta time) {
479 // TODO(kmadhusu): Move platform specific code from here and get rid of #ifdef.
480 #if !defined(OS_ANDROID)
481 NTPUserDataLogger::GetOrCreateFromWebContents(web_contents())
482 ->LogEvent(event, time);
483 #endif
486 void SearchTabHelper::OnLogMostVisitedImpression(
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())->LogMostVisitedImpression(position, provider);
492 #endif
495 void SearchTabHelper::OnLogMostVisitedNavigation(
496 int position, const base::string16& provider) {
497 // TODO(kmadhusu): Move platform specific code from here and get rid of #ifdef.
498 #if !defined(OS_ANDROID)
499 NTPUserDataLogger::GetOrCreateFromWebContents(
500 web_contents())->LogMostVisitedNavigation(position, provider);
501 #endif
504 void SearchTabHelper::PasteIntoOmnibox(const base::string16& text) {
505 // TODO(kmadhusu): Move platform specific code from here and get rid of #ifdef.
506 #if !defined(OS_ANDROID)
507 OmniboxView* omnibox = GetOmniboxView();
508 if (!omnibox)
509 return;
510 // The first case is for right click to paste, where the text is retrieved
511 // from the clipboard already sanitized. The second case is needed to handle
512 // drag-and-drop value and it has to be sanitazed before setting it into the
513 // omnibox.
514 base::string16 text_to_paste =
515 text.empty() ? GetClipboardText() : omnibox->SanitizeTextForPaste(text);
517 if (text_to_paste.empty())
518 return;
520 if (!omnibox->model()->has_focus())
521 omnibox->SetFocus();
523 omnibox->OnBeforePossibleChange();
524 omnibox->model()->OnPaste();
525 omnibox->SetUserText(text_to_paste);
526 omnibox->OnAfterPossibleChange();
527 #endif
530 void SearchTabHelper::OnChromeIdentityCheck(const base::string16& identity) {
531 SigninManagerBase* manager = SigninManagerFactory::GetForProfile(profile());
532 if (manager) {
533 ipc_router_.SendChromeIdentityCheckResult(
534 identity,
535 gaia::AreEmailsSame(base::UTF16ToUTF8(identity),
536 manager->GetAuthenticatedAccountInfo().email));
537 } else {
538 ipc_router_.SendChromeIdentityCheckResult(identity, false);
542 void SearchTabHelper::OnHistorySyncCheck() {
543 ipc_router_.SendHistorySyncCheckResult(IsHistorySyncEnabled(profile()));
546 void SearchTabHelper::UpdateMode(bool update_origin, bool is_preloaded_ntp) {
547 SearchMode::Type type = SearchMode::MODE_DEFAULT;
548 SearchMode::Origin origin = SearchMode::ORIGIN_DEFAULT;
549 if (IsNTP(web_contents_) || is_preloaded_ntp) {
550 type = SearchMode::MODE_NTP;
551 origin = SearchMode::ORIGIN_NTP;
552 } else if (IsSearchResults(web_contents_)) {
553 type = SearchMode::MODE_SEARCH_RESULTS;
554 origin = SearchMode::ORIGIN_SEARCH;
556 if (!update_origin)
557 origin = model_.mode().origin;
559 OmniboxView* omnibox = GetOmniboxView();
560 if (omnibox && omnibox->model()->user_input_in_progress())
561 type = SearchMode::MODE_SEARCH_SUGGESTIONS;
563 SearchMode old_mode(model_.mode());
564 model_.SetMode(SearchMode(type, origin));
565 if (old_mode.is_ntp() != model_.mode().is_ntp()) {
566 ipc_router_.SetInputInProgress(IsInputInProgress());
570 void SearchTabHelper::DetermineIfPageSupportsInstant() {
571 if (!InInstantProcess(profile(), web_contents_)) {
572 // The page is not in the Instant process. This page does not support
573 // instant. If we send an IPC message to a page that is not in the Instant
574 // process, it will never receive it and will never respond. Therefore,
575 // return immediately.
576 InstantSupportChanged(false);
577 } else if (IsLocal(web_contents_)) {
578 // Local pages always support Instant.
579 InstantSupportChanged(true);
580 } else {
581 ipc_router_.DetermineIfPageSupportsInstant();
585 Profile* SearchTabHelper::profile() const {
586 return Profile::FromBrowserContext(web_contents_->GetBrowserContext());
589 bool SearchTabHelper::IsInputInProgress() const {
590 OmniboxView* omnibox = GetOmniboxView();
591 return !model_.mode().is_ntp() && omnibox &&
592 omnibox->model()->focus_state() == OMNIBOX_FOCUS_VISIBLE;
595 OmniboxView* SearchTabHelper::GetOmniboxView() const {
596 return delegate_ ? delegate_->GetOmniboxView() : NULL;