- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / search / search_tab_helper.cc
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.
4
5 #include "chrome/browser/ui/search/search_tab_helper.h"
6
7 #include <set>
8
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"
50 #include "url/gurl.h"
51
52 DEFINE_WEB_CONTENTS_USER_DATA_KEY(SearchTabHelper);
53
54 namespace {
55
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
61 };
62
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);
68 }
69
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);
76 }
77
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))
84     return true;
85
86   return chrome::IsInstantNTP(contents);
87 }
88
89 bool IsSearchResults(const content::WebContents* contents) {
90   return !chrome::GetSearchTerms(contents).empty();
91 }
92
93 bool IsLocal(const content::WebContents* contents) {
94   if (!contents)
95     return false;
96   const content::NavigationEntry* entry =
97       contents->GetController().GetVisibleEntry();
98   return entry && entry->GetURL() == GURL(chrome::kChromeSearchLocalNtpUrl);
99 }
100
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)
105     return false;
106
107   InstantService* instant_service =
108       InstantServiceFactory::GetForProfile(profile);
109   return instant_service &&
110       instant_service->IsInstantProcess(
111           contents->GetRenderProcessHost()->GetID());
112 }
113
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)
118   if (!contents)
119     return;
120
121   Browser* browser = chrome::FindBrowserWithWebContents(contents);
122   if (!browser)
123     return;
124   browser->OnWebContentsInstantSupportDisabled(contents);
125 #endif
126 }
127
128 }  // namespace
129
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_)
140     return;
141
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).
145   registrar_.Add(
146       this,
147       content::NOTIFICATION_NAV_ENTRY_COMMITTED,
148       content::Source<content::NavigationController>(
149           &web_contents->GetController()));
150
151   instant_service_ =
152       InstantServiceFactory::GetForProfile(
153           Profile::FromBrowserContext(web_contents_->GetBrowserContext()));
154   if (instant_service_)
155     instant_service_->AddObserver(this);
156 }
157
158 SearchTabHelper::~SearchTabHelper() {
159   if (instant_service_)
160     instant_service_->RemoveObserver(this);
161 }
162
163 void SearchTabHelper::InitForPreloadedNTP() {
164   UpdateMode(true, true);
165 }
166
167 void SearchTabHelper::OmniboxEditModelChanged(bool user_input_in_progress,
168                                               bool cancelling) {
169   if (!is_search_enabled_)
170     return;
171
172   user_input_in_progress_ = user_input_in_progress;
173   if (!user_input_in_progress && !cancelling)
174     return;
175
176   UpdateMode(false, false);
177 }
178
179 void SearchTabHelper::NavigationEntryUpdated() {
180   if (!is_search_enabled_)
181     return;
182
183   UpdateMode(false, false);
184 }
185
186 void SearchTabHelper::InstantSupportChanged(bool instant_support) {
187   if (!is_search_enabled_)
188     return;
189
190   InstantSupportState new_state = instant_support ? INSTANT_SUPPORT_YES :
191       INSTANT_SUPPORT_NO;
192
193   model_.SetInstantSupportState(new_state);
194
195   content::NavigationEntry* entry =
196       web_contents_->GetController().GetVisibleEntry();
197   if (entry) {
198     chrome::SetInstantSupportStateInNavigationEntry(new_state, entry);
199     if (!instant_support)
200       UpdateLocationBar(web_contents_);
201   }
202 }
203
204 bool SearchTabHelper::SupportsInstant() const {
205   return model_.instant_support() == INSTANT_SUPPORT_YES;
206 }
207
208 void SearchTabHelper::SetSuggestionToPrefetch(
209     const InstantSuggestion& suggestion) {
210   ipc_router_.SetSuggestionToPrefetch(suggestion);
211 }
212
213 void SearchTabHelper::Submit(const string16& text) {
214   DCHECK(!chrome::IsInstantNTP(web_contents_));
215   ipc_router_.Submit(text);
216 }
217
218 void SearchTabHelper::OnTabActivated() {
219   ipc_router_.OnTabActivated();
220 }
221
222 void SearchTabHelper::OnTabDeactivated() {
223   ipc_router_.OnTabDeactivated();
224 }
225
226 void SearchTabHelper::Observe(
227     int type,
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)
234     return;
235
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(),
239                                                profile())) {
240     ipc_router_.SetDisplayInstantResults();
241   }
242
243   UpdateMode(true, false);
244
245   content::NavigationEntry* entry =
246       web_contents_->GetController().GetVisibleEntry();
247   DCHECK(entry);
248
249   // Already determined the instant support state for this page, do not reset
250   // the instant support state.
251   //
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(),
267                                                     entry);
268     if (model_.instant_support() == INSTANT_SUPPORT_NO)
269       UpdateLocationBar(web_contents_);
270     return;
271   }
272
273   model_.SetInstantSupportState(INSTANT_SUPPORT_UNKNOWN);
274   model_.SetVoiceSearchSupported(false);
275   chrome::SetInstantSupportStateInNavigationEntry(model_.instant_support(),
276                                                   entry);
277 }
278
279 void SearchTabHelper::RenderViewCreated(
280     content::RenderViewHost* render_view_host) {
281   ipc_router_.SetPromoInformation(IsAppLauncherEnabled());
282 }
283
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);
291       return;
292     }
293     RecordCacheableNTPLoadHistogram(true);
294   }
295
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.
301   //
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
305   // this rule avoids.
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));
312   }
313 }
314
315 void SearchTabHelper::DidFailProvisionalLoad(
316     int64 /* frame_id */,
317     const string16& frame_unique_name,
318     bool is_main_frame,
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);
326   }
327 }
328
329 void SearchTabHelper::DidFinishLoad(
330     int64 /* frame_id */,
331     const GURL&  /* validated_url */,
332     bool is_main_frame,
333     content::RenderViewHost* /* render_view_host */) {
334   if (is_main_frame)
335     DetermineIfPageSupportsInstant();
336 }
337
338 void SearchTabHelper::OnInstantSupportDetermined(bool supports_instant) {
339   InstantSupportChanged(supports_instant);
340 }
341
342 void SearchTabHelper::OnSetVoiceSearchSupport(bool supports_voice_search) {
343   model_.SetVoiceSearchSupported(supports_voice_search);
344 }
345
346 void SearchTabHelper::ThemeInfoChanged(const ThemeBackgroundInfo& theme_info) {
347   ipc_router_.SendThemeBackgroundInfo(theme_info);
348 }
349
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);
355 }
356
357 void SearchTabHelper::MaybeRemoveMostVisitedItems(
358     std::vector<InstantMostVisitedItem>* items) {
359 // The code below uses APIs not available on Android and the experiment should
360 // not run there.
361 #if !defined(OS_ANDROID)
362   if (!history::MostVisitedTilesExperiment::IsDontShowOpenURLsEnabled())
363     return;
364
365   Profile* profile =
366       Profile::FromBrowserContext(web_contents_->GetBrowserContext());
367   if (!profile)
368     return;
369
370   Browser* browser = chrome::FindBrowserWithProfile(profile,
371                                                     chrome::GetActiveDesktop());
372   if (!browser)
373     return;
374
375   TabStripModel* tab_strip_model = browser->tab_strip_model();
376   history::TopSites* top_sites = profile->GetTopSites();
377   if (!tab_strip_model || !top_sites) {
378     NOTREACHED();
379     return;
380   }
381
382   std::set<std::string> open_urls;
383   chrome::GetOpenUrls(*tab_strip_model, *top_sites, &open_urls);
384   history::MostVisitedTilesExperiment::RemoveItemsMatchingOpenTabs(
385       open_urls, items);
386 #endif
387 }
388
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());
393   if (!browser)
394     return;
395
396   OmniboxView* omnibox_view = browser->window()->GetLocationBar()->
397       GetLocationEntry();
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
401   // states.
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).
405   switch (state) {
406     case OMNIBOX_FOCUS_VISIBLE:
407       omnibox_view->SetFocus();
408       omnibox_view->model()->SetCaretVisibility(true);
409       break;
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);
419       break;
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
423       // interaction.
424       if (!omnibox_view->model()->popup_model()->IsOpen())
425         web_contents()->GetView()->Focus();
426       break;
427   }
428 #endif
429 }
430
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_);
440   Profile* profile =
441       Profile::FromBrowserContext(web_contents_->GetBrowserContext());
442   if (!browser || !profile)
443     return;
444
445   if (is_most_visited_item_url) {
446     content::RecordAction(
447         content::UserMetricsAction("InstantExtended.MostVisitedClicked"));
448   }
449
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(&params);
458 #endif
459 }
460
461 void SearchTabHelper::OnDeleteMostVisitedItem(const GURL& url) {
462   DCHECK(!url.is_empty());
463   if (instant_service_)
464     instant_service_->DeleteMostVisitedItem(url);
465 }
466
467 void SearchTabHelper::OnUndoMostVisitedDeletion(const GURL& url) {
468   DCHECK(!url.is_empty());
469   if (instant_service_)
470     instant_service_->UndoMostVisitedDeletion(url);
471 }
472
473 void SearchTabHelper::OnUndoAllMostVisitedDeletions() {
474   if (instant_service_)
475     instant_service_->UndoAllMostVisitedDeletions();
476 }
477
478 void SearchTabHelper::OnLogEvent(NTPLoggingEventType event) {
479   NTPUserDataLogger* data = NTPUserDataLogger::FromWebContents(web_contents());
480   if (data)
481     data->LogEvent(event);
482 }
483
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());
488   if (!browser)
489     return;
490
491   OmniboxView* omnibox_view = browser->window()->GetLocationBar()->
492       GetLocationEntry();
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
496   // omnibox.
497   string16 text_to_paste = text.empty() ? omnibox_view->GetClipboardText() :
498       omnibox_view->SanitizeTextForPaste(text);
499
500   if (text_to_paste.empty())
501     return;
502
503   if (!omnibox_view->model()->has_focus())
504     omnibox_view->SetFocus();
505
506   omnibox_view->OnBeforePossibleChange();
507   omnibox_view->model()->on_paste();
508   omnibox_view->SetUserText(text_to_paste);
509   omnibox_view->OnAfterPossibleChange();
510 #endif
511 }
512
513 void SearchTabHelper::OnChromeIdentityCheck(const string16& identity) {
514   SigninManagerBase* manager = SigninManagerFactory::GetForProfile(profile());
515   if (manager) {
516     const string16 username = UTF8ToUTF16(manager->GetAuthenticatedUsername());
517     ipc_router_.SendChromeIdentityCheckResult(identity,
518                                               identity == username);
519   }
520 }
521
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;
531   }
532   if (!update_origin)
533     origin = model_.mode().origin;
534   if (user_input_in_progress_)
535     type = SearchMode::MODE_SEARCH_SUGGESTIONS;
536   model_.SetMode(SearchMode(type, origin));
537 }
538
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);
549   } else {
550     ipc_router_.DetermineIfPageSupportsInstant();
551   }
552 }
553
554 Profile* SearchTabHelper::profile() const {
555   return Profile::FromBrowserContext(web_contents_->GetBrowserContext());
556 }
557
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);
567 }