Home | History | Annotate | Download | only in search
      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/instant_controller.h"
      6 
      7 #include "base/metrics/histogram.h"
      8 #include "base/prefs/pref_service.h"
      9 #include "base/strings/stringprintf.h"
     10 #include "chrome/browser/chrome_notification_types.h"
     11 #include "chrome/browser/content_settings/content_settings_provider.h"
     12 #include "chrome/browser/content_settings/host_content_settings_map.h"
     13 #include "chrome/browser/platform_util.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/search_engines/search_terms_data.h"
     19 #include "chrome/browser/search_engines/template_url_service.h"
     20 #include "chrome/browser/search_engines/template_url_service_factory.h"
     21 #include "chrome/browser/ui/browser_instant_controller.h"
     22 #include "chrome/browser/ui/search/instant_tab.h"
     23 #include "chrome/browser/ui/search/search_tab_helper.h"
     24 #include "chrome/common/chrome_switches.h"
     25 #include "chrome/common/content_settings_types.h"
     26 #include "chrome/common/pref_names.h"
     27 #include "chrome/common/url_constants.h"
     28 #include "components/sessions/serialized_navigation_entry.h"
     29 #include "content/public/browser/navigation_entry.h"
     30 #include "content/public/browser/notification_service.h"
     31 #include "content/public/browser/render_process_host.h"
     32 #include "content/public/browser/render_widget_host_view.h"
     33 #include "content/public/browser/user_metrics.h"
     34 #include "content/public/browser/web_contents.h"
     35 #include "content/public/browser/web_contents_view.h"
     36 #include "net/base/escape.h"
     37 #include "net/base/network_change_notifier.h"
     38 
     39 #if defined(TOOLKIT_VIEWS)
     40 #include "ui/views/widget/widget.h"
     41 #endif
     42 
     43 namespace {
     44 
     45 // For reporting Instant navigations.
     46 enum InstantNavigation {
     47   INSTANT_NAVIGATION_LOCAL_CLICK = 0,
     48   INSTANT_NAVIGATION_LOCAL_SUBMIT = 1,
     49   INSTANT_NAVIGATION_ONLINE_CLICK = 2,
     50   INSTANT_NAVIGATION_ONLINE_SUBMIT = 3,
     51   INSTANT_NAVIGATION_NONEXTENDED = 4,
     52   INSTANT_NAVIGATION_MAX = 5
     53 };
     54 
     55 void RecordNavigationHistogram(bool is_local, bool is_click, bool is_extended) {
     56   InstantNavigation navigation;
     57   if (!is_extended) {
     58     navigation = INSTANT_NAVIGATION_NONEXTENDED;
     59   } else if (is_local) {
     60     navigation = is_click ? INSTANT_NAVIGATION_LOCAL_CLICK :
     61                             INSTANT_NAVIGATION_LOCAL_SUBMIT;
     62   } else {
     63     navigation = is_click ? INSTANT_NAVIGATION_ONLINE_CLICK :
     64                             INSTANT_NAVIGATION_ONLINE_SUBMIT;
     65   }
     66   UMA_HISTOGRAM_ENUMERATION("InstantExtended.InstantNavigation",
     67                             navigation,
     68                             INSTANT_NAVIGATION_MAX);
     69 }
     70 
     71 bool IsContentsFrom(const InstantPage* page,
     72                     const content::WebContents* contents) {
     73   return page && (page->contents() == contents);
     74 }
     75 
     76 // Adds a transient NavigationEntry to the supplied |contents|'s
     77 // NavigationController if the page's URL has not already been updated with the
     78 // supplied |search_terms|. Sets the |search_terms| on the transient entry for
     79 // search terms extraction to work correctly.
     80 void EnsureSearchTermsAreSet(content::WebContents* contents,
     81                              const string16& search_terms) {
     82   content::NavigationController* controller = &contents->GetController();
     83 
     84   // If search terms are already correct or there is already a transient entry
     85   // (there shouldn't be), bail out early.
     86   if (chrome::GetSearchTerms(contents) == search_terms ||
     87       controller->GetTransientEntry())
     88     return;
     89 
     90   const content::NavigationEntry* active_entry = controller->GetActiveEntry();
     91   content::NavigationEntry* transient = controller->CreateNavigationEntry(
     92       active_entry->GetURL(),
     93       active_entry->GetReferrer(),
     94       active_entry->GetTransitionType(),
     95       false,
     96       std::string(),
     97       contents->GetBrowserContext());
     98   transient->SetExtraData(sessions::kSearchTermsKey, search_terms);
     99   controller->SetTransientEntry(transient);
    100 
    101   SearchTabHelper::FromWebContents(contents)->NavigationEntryUpdated();
    102 }
    103 
    104 }  // namespace
    105 
    106 InstantController::InstantController(BrowserInstantController* browser)
    107     : browser_(browser),
    108       omnibox_focus_state_(OMNIBOX_FOCUS_NONE),
    109       omnibox_focus_change_reason_(OMNIBOX_FOCUS_CHANGE_EXPLICIT),
    110       omnibox_bounds_(-1, -1, 0, 0) {
    111 }
    112 
    113 InstantController::~InstantController() {
    114 }
    115 
    116 void InstantController::SetOmniboxBounds(const gfx::Rect& bounds) {
    117   if (omnibox_bounds_ == bounds)
    118     return;
    119 
    120   omnibox_bounds_ = bounds;
    121   if (instant_tab_)
    122     instant_tab_->sender()->SetOmniboxBounds(omnibox_bounds_);
    123 }
    124 
    125 void InstantController::ToggleVoiceSearch() {
    126   if (instant_tab_)
    127     instant_tab_->sender()->ToggleVoiceSearch();
    128 }
    129 
    130 void InstantController::InstantPageLoadFailed(content::WebContents* contents) {
    131   if (!chrome::ShouldPreferRemoteNTPOnStartup()) {
    132     // We only need to fall back on errors if we're showing the online page
    133     // at startup, as otherwise we fall back correctly when trying to show
    134     // a page that hasn't yet indicated that it supports the InstantExtended
    135     // API.
    136     return;
    137   }
    138 
    139   DCHECK(IsContentsFrom(instant_tab(), contents));
    140 
    141   // Verify we're not already on a local page and that the URL precisely
    142   // equals the instant_url (minus the query params, as those will be filled
    143   // in by template values).  This check is necessary to make sure we don't
    144   // inadvertently redirect to the local NTP if someone, say, reloads a SRP
    145   // while offline, as a committed results page still counts as an instant
    146   // url.  We also check to make sure there's no forward history, as if
    147   // someone hits the back button a lot when offline and returns to a NTP
    148   // we don't want to redirect and nuke their forward history stack.
    149   const GURL& current_url = contents->GetURL();
    150   GURL instant_url = chrome::GetInstantURL(profile(),
    151                                            chrome::kDisableStartMargin);
    152   if (instant_tab_->IsLocal() ||
    153       !chrome::MatchesOriginAndPath(instant_url, current_url) ||
    154       !current_url.ref().empty() ||
    155       contents->GetController().CanGoForward())
    156     return;
    157   LOG_INSTANT_DEBUG_EVENT(this, "InstantPageLoadFailed: instant_tab");
    158   RedirectToLocalNTP(contents);
    159 }
    160 
    161 bool InstantController::SubmitQuery(const string16& search_terms) {
    162   if (instant_tab_ && instant_tab_->supports_instant() &&
    163       search_mode_.is_origin_search()) {
    164     // Use |instant_tab_| to run the query if we're already on a search results
    165     // page. (NOTE: in particular, we do not send the query to NTPs.)
    166     instant_tab_->sender()->Submit(search_terms);
    167     instant_tab_->contents()->GetView()->Focus();
    168     EnsureSearchTermsAreSet(instant_tab_->contents(), search_terms);
    169     return true;
    170   }
    171   return false;
    172 }
    173 
    174 void InstantController::OmniboxFocusChanged(
    175     OmniboxFocusState state,
    176     OmniboxFocusChangeReason reason,
    177     gfx::NativeView view_gaining_focus) {
    178   LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf(
    179       "OmniboxFocusChanged: %d to %d for reason %d", omnibox_focus_state_,
    180       state, reason));
    181 
    182   omnibox_focus_state_ = state;
    183   if (!instant_tab_)
    184     return;
    185 
    186   content::NotificationService::current()->Notify(
    187       chrome::NOTIFICATION_OMNIBOX_FOCUS_CHANGED,
    188       content::Source<InstantController>(this),
    189       content::NotificationService::NoDetails());
    190 
    191   instant_tab_->sender()->FocusChanged(omnibox_focus_state_, reason);
    192   // Don't send oninputstart/oninputend updates in response to focus changes
    193   // if there's a navigation in progress. This prevents Chrome from sending
    194   // a spurious oninputend when the user accepts a match in the omnibox.
    195   if (instant_tab_->contents()->GetController().GetPendingEntry() == NULL)
    196     instant_tab_->sender()->SetInputInProgress(IsInputInProgress());
    197 }
    198 
    199 void InstantController::SearchModeChanged(const SearchMode& old_mode,
    200                                           const SearchMode& new_mode) {
    201   LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf(
    202       "SearchModeChanged: [origin:mode] %d:%d to %d:%d", old_mode.origin,
    203       old_mode.mode, new_mode.origin, new_mode.mode));
    204 
    205   search_mode_ = new_mode;
    206   ResetInstantTab();
    207 
    208   if (instant_tab_ && old_mode.is_ntp() != new_mode.is_ntp())
    209     instant_tab_->sender()->SetInputInProgress(IsInputInProgress());
    210 }
    211 
    212 void InstantController::ActiveTabChanged() {
    213   LOG_INSTANT_DEBUG_EVENT(this, "ActiveTabChanged");
    214   ResetInstantTab();
    215 }
    216 
    217 void InstantController::TabDeactivated(content::WebContents* contents) {
    218   // If user is deactivating an NTP tab, log the number of mouseovers for this
    219   // NTP session.
    220   if (chrome::IsInstantNTP(contents))
    221     InstantTab::EmitMouseoverCount(contents);
    222 }
    223 
    224 void InstantController::LogDebugEvent(const std::string& info) const {
    225   DVLOG(1) << info;
    226 
    227   debug_events_.push_front(std::make_pair(
    228       base::Time::Now().ToInternalValue(), info));
    229   static const size_t kMaxDebugEventSize = 2000;
    230   if (debug_events_.size() > kMaxDebugEventSize)
    231     debug_events_.pop_back();
    232 }
    233 
    234 void InstantController::ClearDebugEvents() {
    235   debug_events_.clear();
    236 }
    237 
    238 void InstantController::DeleteMostVisitedItem(const GURL& url) {
    239   DCHECK(!url.is_empty());
    240   InstantService* instant_service = GetInstantService();
    241   if (!instant_service)
    242     return;
    243 
    244   instant_service->DeleteMostVisitedItem(url);
    245 }
    246 
    247 void InstantController::UndoMostVisitedDeletion(const GURL& url) {
    248   DCHECK(!url.is_empty());
    249   InstantService* instant_service = GetInstantService();
    250   if (!instant_service)
    251     return;
    252 
    253   instant_service->UndoMostVisitedDeletion(url);
    254 }
    255 
    256 void InstantController::UndoAllMostVisitedDeletions() {
    257   InstantService* instant_service = GetInstantService();
    258   if (!instant_service)
    259     return;
    260 
    261   instant_service->UndoAllMostVisitedDeletions();
    262 }
    263 
    264 Profile* InstantController::profile() const {
    265   return browser_->profile();
    266 }
    267 
    268 InstantTab* InstantController::instant_tab() const {
    269   return instant_tab_.get();
    270 }
    271 
    272 void InstantController::InstantSupportChanged(
    273     InstantSupportState instant_support) {
    274   // Handle INSTANT_SUPPORT_YES here because InstantPage is not hooked up to the
    275   // active tab. Search model changed listener in InstantPage will handle other
    276   // cases.
    277   if (instant_support != INSTANT_SUPPORT_YES)
    278     return;
    279 
    280   ResetInstantTab();
    281 }
    282 
    283 void InstantController::InstantSupportDetermined(
    284     const content::WebContents* contents,
    285     bool supports_instant) {
    286   DCHECK(IsContentsFrom(instant_tab(), contents));
    287 
    288   if (!supports_instant)
    289     base::MessageLoop::current()->DeleteSoon(FROM_HERE, instant_tab_.release());
    290 
    291   content::NotificationService::current()->Notify(
    292       chrome::NOTIFICATION_INSTANT_TAB_SUPPORT_DETERMINED,
    293       content::Source<InstantController>(this),
    294       content::NotificationService::NoDetails());
    295 }
    296 
    297 void InstantController::InstantPageAboutToNavigateMainFrame(
    298     const content::WebContents* contents,
    299     const GURL& url) {
    300   DCHECK(IsContentsFrom(instant_tab(), contents));
    301 
    302   // The Instant tab navigated.  Send it the data it needs to display
    303   // properly.
    304   UpdateInfoForInstantTab();
    305 }
    306 
    307 void InstantController::FocusOmnibox(const content::WebContents* contents,
    308                                      OmniboxFocusState state) {
    309   DCHECK(IsContentsFrom(instant_tab(), contents));
    310   browser_->FocusOmnibox(state);
    311 }
    312 
    313 void InstantController::NavigateToURL(const content::WebContents* contents,
    314                                       const GURL& url,
    315                                       content::PageTransition transition,
    316                                       WindowOpenDisposition disposition,
    317                                       bool is_search_type) {
    318   LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf(
    319       "NavigateToURL: url='%s'", url.spec().c_str()));
    320 
    321   // TODO(samarth): handle case where contents are no longer "active" (e.g. user
    322   // has switched tabs).
    323   if (transition == content::PAGE_TRANSITION_AUTO_BOOKMARK) {
    324     content::RecordAction(
    325         content::UserMetricsAction("InstantExtended.MostVisitedClicked"));
    326   } else {
    327     // Exclude navigation by Most Visited click and searches.
    328     if (!is_search_type)
    329       RecordNavigationHistogram(UsingLocalPage(), true, true);
    330   }
    331   browser_->OpenURL(url, transition, disposition);
    332 }
    333 
    334 void InstantController::PasteIntoOmnibox(const content::WebContents* contents,
    335       const string16& text) {
    336   if (search_mode_.is_origin_default())
    337     return;
    338 
    339   DCHECK(IsContentsFrom(instant_tab(), contents));
    340 
    341   browser_->PasteIntoOmnibox(text);
    342 }
    343 
    344 void InstantController::ResetInstantTab() {
    345   if (!search_mode_.is_origin_default()) {
    346     content::WebContents* active_tab = browser_->GetActiveWebContents();
    347     if (!instant_tab_ || active_tab != instant_tab_->contents()) {
    348       instant_tab_.reset(new InstantTab(this, browser_->profile()));
    349       instant_tab_->Init(active_tab);
    350       UpdateInfoForInstantTab();
    351     }
    352   } else {
    353     instant_tab_.reset();
    354   }
    355 }
    356 
    357 void InstantController::UpdateInfoForInstantTab() {
    358   if (instant_tab_) {
    359     instant_tab_->sender()->SetOmniboxBounds(omnibox_bounds_);
    360 
    361     // Update theme details.
    362     InstantService* instant_service = GetInstantService();
    363     if (instant_service) {
    364       instant_service->UpdateThemeInfo();
    365       instant_service->UpdateMostVisitedItemsInfo();
    366     }
    367 
    368     instant_tab_->InitializeFonts();
    369     instant_tab_->InitializePromos();
    370     instant_tab_->sender()->FocusChanged(omnibox_focus_state_,
    371                                          omnibox_focus_change_reason_);
    372     instant_tab_->sender()->SetInputInProgress(IsInputInProgress());
    373   }
    374 }
    375 
    376 bool InstantController::IsInputInProgress() const {
    377   return !search_mode_.is_ntp() &&
    378       omnibox_focus_state_ == OMNIBOX_FOCUS_VISIBLE;
    379 }
    380 
    381 bool InstantController::UsingLocalPage() const {
    382   return instant_tab_ && instant_tab_->IsLocal();
    383 }
    384 
    385 void InstantController::RedirectToLocalNTP(content::WebContents* contents) {
    386   contents->GetController().LoadURL(
    387       GURL(chrome::kChromeSearchLocalNtpUrl),
    388       content::Referrer(),
    389       content::PAGE_TRANSITION_SERVER_REDIRECT,
    390       std::string());  // No extra headers.
    391   // TODO(dcblack): Remove extraneous history entry caused by 404s.
    392   // Note that the base case of a 204 being returned doesn't push a history
    393   // entry.
    394 }
    395 
    396 InstantService* InstantController::GetInstantService() const {
    397   return InstantServiceFactory::GetForProfile(profile());
    398 }
    399