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 "build/build_config.h"
14 #include "chrome/browser/history/most_visited_tiles_experiment.h"
15 #include "chrome/browser/history/top_sites.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/search/instant_service.h"
18 #include "chrome/browser/search/instant_service_factory.h"
19 #include "chrome/browser/search/search.h"
20 #include "chrome/browser/signin/signin_manager.h"
21 #include "chrome/browser/signin/signin_manager_factory.h"
22 #include "chrome/browser/ui/app_list/app_list_util.h"
23 #include "chrome/browser/ui/browser.h"
24 #include "chrome/browser/ui/browser_finder.h"
25 #include "chrome/browser/ui/browser_navigator.h"
26 #include "chrome/browser/ui/browser_window.h"
27 #include "chrome/browser/ui/omnibox/location_bar.h"
28 #include "chrome/browser/ui/omnibox/omnibox_edit_model.h"
29 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
30 #include "chrome/browser/ui/omnibox/omnibox_view.h"
31 #include "chrome/browser/ui/search/search_ipc_router_policy_impl.h"
32 #include "chrome/browser/ui/tabs/tab_strip_model.h"
33 #include "chrome/browser/ui/tabs/tab_strip_model_utils.h"
34 #include "chrome/browser/ui/webui/ntp/ntp_user_data_logger.h"
35 #include "chrome/common/url_constants.h"
36 #include "content/public/browser/navigation_controller.h"
37 #include "content/public/browser/navigation_details.h"
38 #include "content/public/browser/navigation_entry.h"
39 #include "content/public/browser/navigation_type.h"
40 #include "content/public/browser/notification_service.h"
41 #include "content/public/browser/notification_types.h"
42 #include "content/public/browser/render_process_host.h"
43 #include "content/public/browser/user_metrics.h"
44 #include "content/public/browser/web_contents.h"
45 #include "content/public/browser/web_contents_view.h"
46 #include "content/public/common/page_transition_types.h"
47 #include "content/public/common/referrer.h"
48 #include "grit/generated_resources.h"
49 #include "ui/base/l10n/l10n_util.h"
52 DEFINE_WEB_CONTENTS_USER_DATA_KEY(SearchTabHelper);
56 // For reporting Cacheable NTP navigations.
57 enum CacheableNTPLoad {
58 CACHEABLE_NTP_LOAD_FAILED = 0,
59 CACHEABLE_NTP_LOAD_SUCCEEDED = 1,
60 CACHEABLE_NTP_LOAD_MAX = 2
63 void RecordCacheableNTPLoadHistogram(bool succeeded) {
64 UMA_HISTOGRAM_ENUMERATION("InstantExtended.CacheableNTPLoad",
65 succeeded ? CACHEABLE_NTP_LOAD_SUCCEEDED :
66 CACHEABLE_NTP_LOAD_FAILED,
67 CACHEABLE_NTP_LOAD_MAX);
70 bool IsCacheableNTP(const content::WebContents* contents) {
71 const content::NavigationEntry* entry =
72 contents->GetController().GetActiveEntry();
73 return chrome::ShouldUseCacheableNTP() &&
74 chrome::NavEntryIsInstantNTP(contents, entry) &&
75 entry->GetURL() != GURL(chrome::kChromeSearchLocalNtpUrl);
78 bool IsNTP(const content::WebContents* contents) {
79 // We can't use WebContents::GetURL() because that uses the active entry,
80 // whereas we want the visible entry.
81 const content::NavigationEntry* entry =
82 contents->GetController().GetVisibleEntry();
83 if (entry && entry->GetVirtualURL() == GURL(chrome::kChromeUINewTabURL))
86 return chrome::IsInstantNTP(contents);
89 bool IsSearchResults(const content::WebContents* contents) {
90 return !chrome::GetSearchTerms(contents).empty();
93 bool IsLocal(const content::WebContents* contents) {
96 const content::NavigationEntry* entry =
97 contents->GetController().GetVisibleEntry();
98 return entry && entry->GetURL() == GURL(chrome::kChromeSearchLocalNtpUrl);
101 // Returns true if |contents| are rendered inside an Instant process.
102 bool InInstantProcess(Profile* profile,
103 const content::WebContents* contents) {
104 if (!profile || !contents)
107 InstantService* instant_service =
108 InstantServiceFactory::GetForProfile(profile);
109 return instant_service &&
110 instant_service->IsInstantProcess(
111 contents->GetRenderProcessHost()->GetID());
114 // Updates the location bar to reflect |contents| Instant support state.
115 void UpdateLocationBar(content::WebContents* contents) {
116 // iOS and Android don't use the Instant framework.
117 #if !defined(OS_IOS) && !defined(OS_ANDROID)
121 Browser* browser = chrome::FindBrowserWithWebContents(contents);
124 browser->OnWebContentsInstantSupportDisabled(contents);
130 SearchTabHelper::SearchTabHelper(content::WebContents* web_contents)
131 : WebContentsObserver(web_contents),
132 is_search_enabled_(chrome::IsInstantExtendedAPIEnabled()),
133 user_input_in_progress_(false),
134 web_contents_(web_contents),
135 ipc_router_(web_contents, this,
136 make_scoped_ptr(new SearchIPCRouterPolicyImpl(web_contents))
137 .PassAs<SearchIPCRouter::Policy>()),
138 instant_service_(NULL) {
139 if (!is_search_enabled_)
142 // Observe NOTIFICATION_NAV_ENTRY_COMMITTED events so we can reset state
143 // associated with the WebContents (such as mode, last known most visited
144 // items, instant support state etc).
147 content::NOTIFICATION_NAV_ENTRY_COMMITTED,
148 content::Source<content::NavigationController>(
149 &web_contents->GetController()));
152 InstantServiceFactory::GetForProfile(
153 Profile::FromBrowserContext(web_contents_->GetBrowserContext()));
154 if (instant_service_)
155 instant_service_->AddObserver(this);
158 SearchTabHelper::~SearchTabHelper() {
159 if (instant_service_)
160 instant_service_->RemoveObserver(this);
163 void SearchTabHelper::InitForPreloadedNTP() {
164 UpdateMode(true, true);
167 void SearchTabHelper::OmniboxEditModelChanged(bool user_input_in_progress,
169 if (!is_search_enabled_)
172 user_input_in_progress_ = user_input_in_progress;
173 if (!user_input_in_progress && !cancelling)
176 UpdateMode(false, false);
179 void SearchTabHelper::NavigationEntryUpdated() {
180 if (!is_search_enabled_)
183 UpdateMode(false, false);
186 void SearchTabHelper::InstantSupportChanged(bool instant_support) {
187 if (!is_search_enabled_)
190 InstantSupportState new_state = instant_support ? INSTANT_SUPPORT_YES :
193 model_.SetInstantSupportState(new_state);
195 content::NavigationEntry* entry =
196 web_contents_->GetController().GetVisibleEntry();
198 chrome::SetInstantSupportStateInNavigationEntry(new_state, entry);
199 if (!instant_support)
200 UpdateLocationBar(web_contents_);
204 bool SearchTabHelper::SupportsInstant() const {
205 return model_.instant_support() == INSTANT_SUPPORT_YES;
208 void SearchTabHelper::SetSuggestionToPrefetch(
209 const InstantSuggestion& suggestion) {
210 ipc_router_.SetSuggestionToPrefetch(suggestion);
213 void SearchTabHelper::Submit(const string16& text) {
214 DCHECK(!chrome::IsInstantNTP(web_contents_));
215 ipc_router_.Submit(text);
218 void SearchTabHelper::OnTabActivated() {
219 ipc_router_.OnTabActivated();
222 void SearchTabHelper::OnTabDeactivated() {
223 ipc_router_.OnTabDeactivated();
226 void SearchTabHelper::Observe(
228 const content::NotificationSource& source,
229 const content::NotificationDetails& details) {
230 DCHECK_EQ(content::NOTIFICATION_NAV_ENTRY_COMMITTED, type);
231 content::LoadCommittedDetails* load_details =
232 content::Details<content::LoadCommittedDetails>(details).ptr();
233 if (!load_details->is_main_frame)
236 // TODO(kmadhusu): Set the page initial states (such as omnibox margin, etc)
237 // from here. Please refer to crbug.com/247517 for more details.
238 if (chrome::ShouldAssignURLToInstantRenderer(web_contents_->GetURL(),
240 ipc_router_.SetDisplayInstantResults();
243 UpdateMode(true, false);
245 content::NavigationEntry* entry =
246 web_contents_->GetController().GetVisibleEntry();
249 // Already determined the instant support state for this page, do not reset
250 // the instant support state.
252 // When we get a navigation entry committed event, there seem to be two ways
253 // to tell whether the navigation was "in-page". Ideally, when
254 // LoadCommittedDetails::is_in_page is true, we should have
255 // LoadCommittedDetails::type to be NAVIGATION_TYPE_IN_PAGE. Unfortunately,
256 // they are different in some cases. To workaround this bug, we are checking
257 // (is_in_page || type == NAVIGATION_TYPE_IN_PAGE). Please refer to
258 // crbug.com/251330 for more details.
259 if (load_details->is_in_page ||
260 load_details->type == content::NAVIGATION_TYPE_IN_PAGE) {
261 // When an "in-page" navigation happens, we will not receive a
262 // DidFinishLoad() event. Therefore, we will not determine the Instant
263 // support for the navigated page. So, copy over the Instant support from
264 // the previous entry. If the page does not support Instant, update the
265 // location bar from here to turn off search terms replacement.
266 chrome::SetInstantSupportStateInNavigationEntry(model_.instant_support(),
268 if (model_.instant_support() == INSTANT_SUPPORT_NO)
269 UpdateLocationBar(web_contents_);
273 model_.SetInstantSupportState(INSTANT_SUPPORT_UNKNOWN);
274 model_.SetVoiceSearchSupported(false);
275 chrome::SetInstantSupportStateInNavigationEntry(model_.instant_support(),
279 void SearchTabHelper::RenderViewCreated(
280 content::RenderViewHost* render_view_host) {
281 ipc_router_.SetPromoInformation(IsAppLauncherEnabled());
284 void SearchTabHelper::DidNavigateMainFrame(
285 const content::LoadCommittedDetails& details,
286 const content::FrameNavigateParams& params) {
287 if (IsCacheableNTP(web_contents_)) {
288 if (details.http_status_code == 204 || details.http_status_code >= 400) {
289 RedirectToLocalNTP();
290 RecordCacheableNTPLoadHistogram(false);
293 RecordCacheableNTPLoadHistogram(true);
296 // Always set the title on the new tab page to be the one from our UI
297 // resources. Normally, we set the title when we begin a NTP load, but it can
298 // get reset in several places (like when you press Reload). This check
299 // ensures that the title is properly set to the string defined by the Chrome
300 // UI language (rather than the server language) in all cases.
302 // We only override the title when it's nonempty to allow the page to set the
303 // title if it really wants. An empty title means to use the default. There's
304 // also a race condition between this code and the page's SetTitle call which
306 content::NavigationEntry* entry =
307 web_contents_->GetController().GetActiveEntry();
308 if (entry && entry->GetTitle().empty() &&
309 (entry->GetVirtualURL() == GURL(chrome::kChromeUINewTabURL) ||
310 chrome::NavEntryIsInstantNTP(web_contents_, entry))) {
311 entry->SetTitle(l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE));
315 void SearchTabHelper::DidFailProvisionalLoad(
316 int64 /* frame_id */,
317 const string16& frame_unique_name,
319 const GURL& /* validated_url */,
320 int /* error_code */,
321 const string16& /* error_description */,
322 content::RenderViewHost* /* render_view_host */) {
323 if (is_main_frame && IsCacheableNTP(web_contents_)) {
324 RedirectToLocalNTP();
325 RecordCacheableNTPLoadHistogram(false);
329 void SearchTabHelper::DidFinishLoad(
330 int64 /* frame_id */,
331 const GURL& /* validated_url */,
333 content::RenderViewHost* /* render_view_host */) {
335 DetermineIfPageSupportsInstant();
338 void SearchTabHelper::OnInstantSupportDetermined(bool supports_instant) {
339 InstantSupportChanged(supports_instant);
342 void SearchTabHelper::OnSetVoiceSearchSupport(bool supports_voice_search) {
343 model_.SetVoiceSearchSupported(supports_voice_search);
346 void SearchTabHelper::ThemeInfoChanged(const ThemeBackgroundInfo& theme_info) {
347 ipc_router_.SendThemeBackgroundInfo(theme_info);
350 void SearchTabHelper::MostVisitedItemsChanged(
351 const std::vector<InstantMostVisitedItem>& items) {
352 std::vector<InstantMostVisitedItem> items_copy(items);
353 MaybeRemoveMostVisitedItems(&items_copy);
354 ipc_router_.SendMostVisitedItems(items_copy);
357 void SearchTabHelper::MaybeRemoveMostVisitedItems(
358 std::vector<InstantMostVisitedItem>* items) {
359 // The code below uses APIs not available on Android and the experiment should
361 #if !defined(OS_ANDROID)
362 if (!history::MostVisitedTilesExperiment::IsDontShowOpenURLsEnabled())
366 Profile::FromBrowserContext(web_contents_->GetBrowserContext());
370 Browser* browser = chrome::FindBrowserWithProfile(profile,
371 chrome::GetActiveDesktop());
375 TabStripModel* tab_strip_model = browser->tab_strip_model();
376 history::TopSites* top_sites = profile->GetTopSites();
377 if (!tab_strip_model || !top_sites) {
382 std::set<std::string> open_urls;
383 chrome::GetOpenUrls(*tab_strip_model, *top_sites, &open_urls);
384 history::MostVisitedTilesExperiment::RemoveItemsMatchingOpenTabs(
389 void SearchTabHelper::FocusOmnibox(OmniboxFocusState state) {
390 // iOS and Android don't use the Instant framework.
391 #if !defined(OS_IOS) && !defined(OS_ANDROID)
392 Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
396 OmniboxView* omnibox_view = browser->window()->GetLocationBar()->
398 // Do not add a default case in the switch block for the following reasons:
399 // (1) Explicitly handle the new states. If new states are added in the
400 // OmniboxFocusState, the compiler will warn the developer to handle the new
402 // (2) An attacker may control the renderer and sends the browser process a
403 // malformed IPC. This function responds to the invalid |state| values by
404 // doing nothing instead of crashing the browser process (intentional no-op).
406 case OMNIBOX_FOCUS_VISIBLE:
407 omnibox_view->SetFocus();
408 omnibox_view->model()->SetCaretVisibility(true);
410 case OMNIBOX_FOCUS_INVISIBLE:
411 omnibox_view->SetFocus();
412 omnibox_view->model()->SetCaretVisibility(false);
413 // If the user clicked on the fakebox, any text already in the omnibox
414 // should get cleared when they start typing. Selecting all the existing
415 // text is a convenient way to accomplish this. It also gives a slight
416 // visual cue to users who really understand selection state about what
417 // will happen if they start typing.
418 omnibox_view->SelectAll(false);
420 case OMNIBOX_FOCUS_NONE:
421 // Remove focus only if the popup is closed. This will prevent someone
422 // from changing the omnibox value and closing the popup without user
424 if (!omnibox_view->model()->popup_model()->IsOpen())
425 web_contents()->GetView()->Focus();
431 void SearchTabHelper::NavigateToURL(const GURL& url,
432 WindowOpenDisposition disposition,
433 bool is_most_visited_item_url) {
434 // iOS and Android don't use the Instant framework.
435 #if !defined(OS_IOS) && !defined(OS_ANDROID)
436 // TODO(kmadhusu): Remove chrome::FindBrowser...() function call from here.
437 // Create a SearchTabHelperDelegate interface and have the Browser object
438 // implement that interface to provide the necessary functionality.
439 Browser* browser = chrome::FindBrowserWithWebContents(web_contents_);
441 Profile::FromBrowserContext(web_contents_->GetBrowserContext());
442 if (!browser || !profile)
445 if (is_most_visited_item_url) {
446 content::RecordAction(
447 content::UserMetricsAction("InstantExtended.MostVisitedClicked"));
450 chrome::NavigateParams params(browser, url,
451 content::PAGE_TRANSITION_AUTO_BOOKMARK);
452 params.referrer = content::Referrer();
453 params.source_contents = web_contents_;
454 params.disposition = disposition;
455 params.is_renderer_initiated = false;
456 params.initiating_profile = profile;
457 chrome::Navigate(¶ms);
461 void SearchTabHelper::OnDeleteMostVisitedItem(const GURL& url) {
462 DCHECK(!url.is_empty());
463 if (instant_service_)
464 instant_service_->DeleteMostVisitedItem(url);
467 void SearchTabHelper::OnUndoMostVisitedDeletion(const GURL& url) {
468 DCHECK(!url.is_empty());
469 if (instant_service_)
470 instant_service_->UndoMostVisitedDeletion(url);
473 void SearchTabHelper::OnUndoAllMostVisitedDeletions() {
474 if (instant_service_)
475 instant_service_->UndoAllMostVisitedDeletions();
478 void SearchTabHelper::OnLogEvent(NTPLoggingEventType event) {
479 NTPUserDataLogger* data = NTPUserDataLogger::FromWebContents(web_contents());
481 data->LogEvent(event);
484 void SearchTabHelper::PasteIntoOmnibox(const string16& text) {
485 // iOS and Android don't use the Instant framework.
486 #if !defined(OS_IOS) && !defined(OS_ANDROID)
487 Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
491 OmniboxView* omnibox_view = browser->window()->GetLocationBar()->
493 // The first case is for right click to paste, where the text is retrieved
494 // from the clipboard already sanitized. The second case is needed to handle
495 // drag-and-drop value and it has to be sanitazed before setting it into the
497 string16 text_to_paste = text.empty() ? omnibox_view->GetClipboardText() :
498 omnibox_view->SanitizeTextForPaste(text);
500 if (text_to_paste.empty())
503 if (!omnibox_view->model()->has_focus())
504 omnibox_view->SetFocus();
506 omnibox_view->OnBeforePossibleChange();
507 omnibox_view->model()->on_paste();
508 omnibox_view->SetUserText(text_to_paste);
509 omnibox_view->OnAfterPossibleChange();
513 void SearchTabHelper::OnChromeIdentityCheck(const string16& identity) {
514 SigninManagerBase* manager = SigninManagerFactory::GetForProfile(profile());
516 const string16 username = UTF8ToUTF16(manager->GetAuthenticatedUsername());
517 ipc_router_.SendChromeIdentityCheckResult(identity,
518 identity == username);
522 void SearchTabHelper::UpdateMode(bool update_origin, bool is_preloaded_ntp) {
523 SearchMode::Type type = SearchMode::MODE_DEFAULT;
524 SearchMode::Origin origin = SearchMode::ORIGIN_DEFAULT;
525 if (IsNTP(web_contents_) || is_preloaded_ntp) {
526 type = SearchMode::MODE_NTP;
527 origin = SearchMode::ORIGIN_NTP;
528 } else if (IsSearchResults(web_contents_)) {
529 type = SearchMode::MODE_SEARCH_RESULTS;
530 origin = SearchMode::ORIGIN_SEARCH;
533 origin = model_.mode().origin;
534 if (user_input_in_progress_)
535 type = SearchMode::MODE_SEARCH_SUGGESTIONS;
536 model_.SetMode(SearchMode(type, origin));
539 void SearchTabHelper::DetermineIfPageSupportsInstant() {
540 if (!InInstantProcess(profile(), web_contents_)) {
541 // The page is not in the Instant process. This page does not support
542 // instant. If we send an IPC message to a page that is not in the Instant
543 // process, it will never receive it and will never respond. Therefore,
544 // return immediately.
545 InstantSupportChanged(false);
546 } else if (IsLocal(web_contents_)) {
547 // Local pages always support Instant.
548 InstantSupportChanged(true);
550 ipc_router_.DetermineIfPageSupportsInstant();
554 Profile* SearchTabHelper::profile() const {
555 return Profile::FromBrowserContext(web_contents_->GetBrowserContext());
558 void SearchTabHelper::RedirectToLocalNTP() {
559 // Extra parentheses to declare a variable.
560 content::NavigationController::LoadURLParams load_params(
561 (GURL(chrome::kChromeSearchLocalNtpUrl)));
562 load_params.referrer = content::Referrer();
563 load_params.transition_type = content::PAGE_TRANSITION_SERVER_REDIRECT;
564 // Don't push a history entry.
565 load_params.should_replace_current_entry = true;
566 web_contents_->GetController().LoadURLWithParams(load_params);