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/search_tab_helper.h"
      6 
      7 #include "build/build_config.h"
      8 #include "chrome/browser/profiles/profile.h"
      9 #include "chrome/browser/search/instant_service.h"
     10 #include "chrome/browser/search/instant_service_factory.h"
     11 #include "chrome/browser/search/search.h"
     12 #include "chrome/browser/ui/browser.h"
     13 #include "chrome/browser/ui/browser_finder.h"
     14 #include "chrome/common/render_messages.h"
     15 #include "chrome/common/url_constants.h"
     16 #include "content/public/browser/navigation_controller.h"
     17 #include "content/public/browser/navigation_details.h"
     18 #include "content/public/browser/navigation_entry.h"
     19 #include "content/public/browser/navigation_type.h"
     20 #include "content/public/browser/notification_service.h"
     21 #include "content/public/browser/notification_types.h"
     22 #include "content/public/browser/render_process_host.h"
     23 #include "content/public/browser/web_contents.h"
     24 #include "grit/generated_resources.h"
     25 #include "ui/base/l10n/l10n_util.h"
     26 
     27 DEFINE_WEB_CONTENTS_USER_DATA_KEY(SearchTabHelper);
     28 
     29 namespace {
     30 
     31 bool IsNTP(const content::WebContents* contents) {
     32   // We can't use WebContents::GetURL() because that uses the active entry,
     33   // whereas we want the visible entry.
     34   const content::NavigationEntry* entry =
     35       contents->GetController().GetVisibleEntry();
     36   if (entry && entry->GetVirtualURL() == GURL(chrome::kChromeUINewTabURL))
     37     return true;
     38 
     39   return chrome::IsInstantNTP(contents);
     40 }
     41 
     42 bool IsSearchResults(const content::WebContents* contents) {
     43   return !chrome::GetSearchTerms(contents).empty();
     44 }
     45 
     46 // TODO(kmadhusu): Move this helper from anonymous namespace to chrome
     47 // namespace and remove InstantPage::IsLocal().
     48 bool IsLocal(const content::WebContents* contents) {
     49   return contents &&
     50       contents->GetURL() == GURL(chrome::kChromeSearchLocalNtpUrl);
     51 }
     52 
     53 // Returns true if |contents| are rendered inside an Instant process.
     54 bool InInstantProcess(Profile* profile,
     55                       const content::WebContents* contents) {
     56   InstantService* instant_service =
     57       InstantServiceFactory::GetForProfile(profile);
     58   return instant_service &&
     59       instant_service->IsInstantProcess(
     60           contents->GetRenderProcessHost()->GetID());
     61 }
     62 
     63 // Updates the location bar to reflect |contents| Instant support state.
     64 void UpdateLocationBar(content::WebContents* contents) {
     65 // iOS and Android doesn't use the Instant framework.
     66 #if !defined(OS_IOS) && !defined(OS_ANDROID)
     67   if (!contents)
     68     return;
     69 
     70   Browser* browser = chrome::FindBrowserWithWebContents(contents);
     71   if (!browser)
     72     return;
     73   browser->OnWebContentsInstantSupportDisabled(contents);
     74 #endif
     75 }
     76 
     77 }  // namespace
     78 
     79 SearchTabHelper::SearchTabHelper(content::WebContents* web_contents)
     80     : WebContentsObserver(web_contents),
     81       is_search_enabled_(chrome::IsInstantExtendedAPIEnabled()),
     82       user_input_in_progress_(false),
     83       web_contents_(web_contents) {
     84   if (!is_search_enabled_)
     85     return;
     86 
     87   // Observe NOTIFICATION_NAV_ENTRY_COMMITTED events so we can reset state
     88   // associated with the WebContents  (such as mode, last known most visited
     89   // items, instant support state etc).
     90   registrar_.Add(
     91       this,
     92       content::NOTIFICATION_NAV_ENTRY_COMMITTED,
     93       content::Source<content::NavigationController>(
     94           &web_contents->GetController()));
     95 }
     96 
     97 SearchTabHelper::~SearchTabHelper() {
     98 }
     99 
    100 void SearchTabHelper::InitForPreloadedNTP() {
    101   UpdateMode(true, true);
    102 }
    103 
    104 void SearchTabHelper::OmniboxEditModelChanged(bool user_input_in_progress,
    105                                               bool cancelling) {
    106   if (!is_search_enabled_)
    107     return;
    108 
    109   user_input_in_progress_ = user_input_in_progress;
    110   if (!user_input_in_progress && !cancelling)
    111     return;
    112 
    113   UpdateMode(false, false);
    114 }
    115 
    116 void SearchTabHelper::NavigationEntryUpdated() {
    117   if (!is_search_enabled_)
    118     return;
    119 
    120   UpdateMode(false, false);
    121 }
    122 
    123 void SearchTabHelper::InstantSupportChanged(bool instant_support) {
    124   if (!is_search_enabled_)
    125     return;
    126 
    127   InstantSupportState new_state = instant_support ? INSTANT_SUPPORT_YES :
    128       INSTANT_SUPPORT_NO;
    129 
    130   model_.SetInstantSupportState(new_state);
    131 
    132   content::NavigationEntry* entry =
    133       web_contents_->GetController().GetVisibleEntry();
    134   if (entry) {
    135     chrome::SetInstantSupportStateInNavigationEntry(new_state, entry);
    136     if (!instant_support)
    137       UpdateLocationBar(web_contents_);
    138   }
    139 }
    140 
    141 bool SearchTabHelper::SupportsInstant() const {
    142   return model_.instant_support() == INSTANT_SUPPORT_YES;
    143 }
    144 
    145 void SearchTabHelper::Observe(
    146     int type,
    147     const content::NotificationSource& source,
    148     const content::NotificationDetails& details) {
    149   DCHECK_EQ(content::NOTIFICATION_NAV_ENTRY_COMMITTED, type);
    150   content::LoadCommittedDetails* load_details =
    151       content::Details<content::LoadCommittedDetails>(details).ptr();
    152   if (!load_details->is_main_frame)
    153     return;
    154 
    155   UpdateMode(true, false);
    156 
    157   content::NavigationEntry* entry =
    158       web_contents_->GetController().GetVisibleEntry();
    159   DCHECK(entry);
    160 
    161   // Already determined the instant support state for this page, do not reset
    162   // the instant support state.
    163   //
    164   // When we get a navigation entry committed event, there seem to be two ways
    165   // to tell whether the navigation was "in-page". Ideally, when
    166   // LoadCommittedDetails::is_in_page is true, we should have
    167   // LoadCommittedDetails::type to be NAVIGATION_TYPE_IN_PAGE. Unfortunately,
    168   // they are different in some cases. To workaround this bug, we are checking
    169   // (is_in_page || type == NAVIGATION_TYPE_IN_PAGE). Please refer to
    170   // crbug.com/251330 for more details.
    171   if (load_details->is_in_page ||
    172       load_details->type == content::NAVIGATION_TYPE_IN_PAGE) {
    173     // When an "in-page" navigation happens, we will not receive a
    174     // DidFinishLoad() event. Therefore, we will not determine the Instant
    175     // support for the navigated page. So, copy over the Instant support from
    176     // the previous entry. If the page does not support Instant, update the
    177     // location bar from here to turn off search terms replacement.
    178     chrome::SetInstantSupportStateInNavigationEntry(model_.instant_support(),
    179                                                     entry);
    180     if (model_.instant_support() == INSTANT_SUPPORT_NO)
    181       UpdateLocationBar(web_contents_);
    182     return;
    183   }
    184 
    185   model_.SetInstantSupportState(INSTANT_SUPPORT_UNKNOWN);
    186   model_.SetVoiceSearchSupported(false);
    187   chrome::SetInstantSupportStateInNavigationEntry(model_.instant_support(),
    188                                                   entry);
    189 }
    190 
    191 void SearchTabHelper::DidNavigateMainFrame(
    192     const content::LoadCommittedDetails& details,
    193     const content::FrameNavigateParams& params) {
    194   // Always set the title on the new tab page to be the one from our UI
    195   // resources.  Normally, we set the title when we begin a NTP load, but it
    196   // can get reset in several places (like when you press Reload). This check
    197   // ensures that the title is properly set to the string defined by the Chrome
    198   // UI language (rather than the server language) in all cases.
    199   //
    200   // We only override the title when it's nonempty to allow the page to set the
    201   // title if it really wants. An empty title means to use the default. There's
    202   // also a race condition between this code and the page's SetTitle call which
    203   // this rule avoids.
    204   content::NavigationEntry* entry =
    205       web_contents_->GetController().GetActiveEntry();
    206   if (entry && entry->GetTitle().empty() &&
    207       (entry->GetVirtualURL() == GURL(chrome::kChromeUINewTabURL) ||
    208        chrome::NavEntryIsInstantNTP(web_contents_, entry))) {
    209     entry->SetTitle(l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE));
    210   }
    211 }
    212 
    213 bool SearchTabHelper::OnMessageReceived(const IPC::Message& message) {
    214   bool handled = true;
    215   IPC_BEGIN_MESSAGE_MAP(SearchTabHelper, message)
    216     IPC_MESSAGE_HANDLER(ChromeViewHostMsg_InstantSupportDetermined,
    217                         OnInstantSupportDetermined)
    218     IPC_MESSAGE_HANDLER(ChromeViewHostMsg_SetVoiceSearchSupported,
    219                         OnSetVoiceSearchSupported)
    220     IPC_MESSAGE_UNHANDLED(handled = false)
    221   IPC_END_MESSAGE_MAP()
    222   return handled;
    223 }
    224 
    225 void SearchTabHelper::DidFinishLoad(
    226     int64 /* frame_id */,
    227     const GURL&  /* validated_url */,
    228     bool is_main_frame,
    229     content::RenderViewHost* /* render_view_host */) {
    230   if (is_main_frame)
    231     DetermineIfPageSupportsInstant();
    232 }
    233 
    234 void SearchTabHelper::UpdateMode(bool update_origin, bool is_preloaded_ntp) {
    235   SearchMode::Type type = SearchMode::MODE_DEFAULT;
    236   SearchMode::Origin origin = SearchMode::ORIGIN_DEFAULT;
    237   if (IsNTP(web_contents_) || is_preloaded_ntp) {
    238     type = SearchMode::MODE_NTP;
    239     origin = SearchMode::ORIGIN_NTP;
    240   } else if (IsSearchResults(web_contents_)) {
    241     type = SearchMode::MODE_SEARCH_RESULTS;
    242     origin = SearchMode::ORIGIN_SEARCH;
    243   }
    244   if (!update_origin)
    245     origin = model_.mode().origin;
    246   if (user_input_in_progress_)
    247     type = SearchMode::MODE_SEARCH_SUGGESTIONS;
    248   model_.SetMode(SearchMode(type, origin));
    249 }
    250 
    251 void SearchTabHelper::DetermineIfPageSupportsInstant() {
    252   Profile* profile =
    253       Profile::FromBrowserContext(web_contents_->GetBrowserContext());
    254   if (!InInstantProcess(profile, web_contents_)) {
    255     // The page is not in the Instant process. This page does not support
    256     // instant. If we send an IPC message to a page that is not in the Instant
    257     // process, it will never receive it and will never respond. Therefore,
    258     // return immediately.
    259     InstantSupportChanged(false);
    260   } else if (IsLocal(web_contents_)) {
    261     // Local pages always support Instant.
    262     InstantSupportChanged(true);
    263   } else {
    264     Send(new ChromeViewMsg_DetermineIfPageSupportsInstant(routing_id()));
    265   }
    266 }
    267 
    268 void SearchTabHelper::OnInstantSupportDetermined(int page_id,
    269                                                  bool instant_support) {
    270   if (!web_contents()->IsActiveEntry(page_id))
    271     return;
    272 
    273   InstantSupportChanged(instant_support);
    274 }
    275 
    276 void SearchTabHelper::OnSetVoiceSearchSupported(int page_id, bool supported) {
    277   if (web_contents()->IsActiveEntry(page_id))
    278     model_.SetVoiceSearchSupported(supported);
    279 }
    280