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 <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/render_process_host.h"
     41 #include "content/public/browser/user_metrics.h"
     42 #include "content/public/browser/web_contents.h"
     43 #include "content/public/browser/web_contents_view.h"
     44 #include "content/public/common/page_transition_types.h"
     45 #include "content/public/common/referrer.h"
     46 #include "grit/generated_resources.h"
     47 #include "net/base/net_errors.h"
     48 #include "ui/base/l10n/l10n_util.h"
     49 #include "url/gurl.h"
     50 
     51 DEFINE_WEB_CONTENTS_USER_DATA_KEY(SearchTabHelper);
     52 
     53 namespace {
     54 
     55 // For reporting Cacheable NTP navigations.
     56 enum CacheableNTPLoad {
     57   CACHEABLE_NTP_LOAD_FAILED = 0,
     58   CACHEABLE_NTP_LOAD_SUCCEEDED = 1,
     59   CACHEABLE_NTP_LOAD_MAX = 2
     60 };
     61 
     62 void RecordCacheableNTPLoadHistogram(bool succeeded) {
     63   UMA_HISTOGRAM_ENUMERATION("InstantExtended.CacheableNTPLoad",
     64                             succeeded ? CACHEABLE_NTP_LOAD_SUCCEEDED :
     65                                 CACHEABLE_NTP_LOAD_FAILED,
     66                             CACHEABLE_NTP_LOAD_MAX);
     67 }
     68 
     69 bool IsCacheableNTP(const content::WebContents* contents) {
     70   const content::NavigationEntry* entry =
     71       contents->GetController().GetLastCommittedEntry();
     72   return chrome::ShouldUseCacheableNTP() &&
     73       chrome::NavEntryIsInstantNTP(contents, entry) &&
     74       entry->GetURL() != GURL(chrome::kChromeSearchLocalNtpUrl);
     75 }
     76 
     77 bool IsNTP(const content::WebContents* contents) {
     78   // We can't use WebContents::GetURL() because that uses the active entry,
     79   // whereas we want the visible entry.
     80   const content::NavigationEntry* entry =
     81       contents->GetController().GetVisibleEntry();
     82   if (entry && entry->GetVirtualURL() == GURL(chrome::kChromeUINewTabURL))
     83     return true;
     84 
     85   return chrome::IsInstantNTP(contents);
     86 }
     87 
     88 bool IsSearchResults(const content::WebContents* contents) {
     89   return !chrome::GetSearchTerms(contents).empty();
     90 }
     91 
     92 bool IsLocal(const content::WebContents* contents) {
     93   if (!contents)
     94     return false;
     95   const content::NavigationEntry* entry =
     96       contents->GetController().GetVisibleEntry();
     97   return entry && entry->GetURL() == GURL(chrome::kChromeSearchLocalNtpUrl);
     98 }
     99 
    100 // Returns true if |contents| are rendered inside an Instant process.
    101 bool InInstantProcess(Profile* profile,
    102                       const content::WebContents* contents) {
    103   if (!profile || !contents)
    104     return false;
    105 
    106   InstantService* instant_service =
    107       InstantServiceFactory::GetForProfile(profile);
    108   return instant_service &&
    109       instant_service->IsInstantProcess(
    110           contents->GetRenderProcessHost()->GetID());
    111 }
    112 
    113 // Updates the location bar to reflect |contents| Instant support state.
    114 void UpdateLocationBar(content::WebContents* contents) {
    115 // iOS and Android don't use the Instant framework.
    116 #if !defined(OS_IOS) && !defined(OS_ANDROID)
    117   if (!contents)
    118     return;
    119 
    120   Browser* browser = chrome::FindBrowserWithWebContents(contents);
    121   if (!browser)
    122     return;
    123   browser->OnWebContentsInstantSupportDisabled(contents);
    124 #endif
    125 }
    126 
    127 }  // namespace
    128 
    129 SearchTabHelper::SearchTabHelper(content::WebContents* web_contents)
    130     : WebContentsObserver(web_contents),
    131       is_search_enabled_(chrome::IsInstantExtendedAPIEnabled()),
    132       user_input_in_progress_(false),
    133       web_contents_(web_contents),
    134       ipc_router_(web_contents, this,
    135                   make_scoped_ptr(new SearchIPCRouterPolicyImpl(web_contents))
    136                       .PassAs<SearchIPCRouter::Policy>()),
    137       instant_service_(NULL) {
    138   if (!is_search_enabled_)
    139     return;
    140 
    141   instant_service_ =
    142       InstantServiceFactory::GetForProfile(
    143           Profile::FromBrowserContext(web_contents_->GetBrowserContext()));
    144   if (instant_service_)
    145     instant_service_->AddObserver(this);
    146 }
    147 
    148 SearchTabHelper::~SearchTabHelper() {
    149   if (instant_service_)
    150     instant_service_->RemoveObserver(this);
    151 }
    152 
    153 void SearchTabHelper::InitForPreloadedNTP() {
    154   UpdateMode(true, true);
    155 }
    156 
    157 void SearchTabHelper::OmniboxEditModelChanged(bool user_input_in_progress,
    158                                               bool cancelling) {
    159   if (!is_search_enabled_)
    160     return;
    161 
    162   user_input_in_progress_ = user_input_in_progress;
    163   if (!user_input_in_progress && !cancelling)
    164     return;
    165 
    166   UpdateMode(false, false);
    167 }
    168 
    169 void SearchTabHelper::NavigationEntryUpdated() {
    170   if (!is_search_enabled_)
    171     return;
    172 
    173   UpdateMode(false, false);
    174 }
    175 
    176 void SearchTabHelper::InstantSupportChanged(bool instant_support) {
    177   if (!is_search_enabled_)
    178     return;
    179 
    180   InstantSupportState new_state = instant_support ? INSTANT_SUPPORT_YES :
    181       INSTANT_SUPPORT_NO;
    182 
    183   model_.SetInstantSupportState(new_state);
    184 
    185   content::NavigationEntry* entry =
    186       web_contents_->GetController().GetVisibleEntry();
    187   if (entry) {
    188     chrome::SetInstantSupportStateInNavigationEntry(new_state, entry);
    189     if (!instant_support)
    190       UpdateLocationBar(web_contents_);
    191   }
    192 }
    193 
    194 bool SearchTabHelper::SupportsInstant() const {
    195   return model_.instant_support() == INSTANT_SUPPORT_YES;
    196 }
    197 
    198 void SearchTabHelper::SetSuggestionToPrefetch(
    199     const InstantSuggestion& suggestion) {
    200   ipc_router_.SetSuggestionToPrefetch(suggestion);
    201 }
    202 
    203 void SearchTabHelper::Submit(const base::string16& text) {
    204   ipc_router_.Submit(text);
    205 }
    206 
    207 void SearchTabHelper::OnTabActivated() {
    208   ipc_router_.OnTabActivated();
    209 }
    210 
    211 void SearchTabHelper::OnTabDeactivated() {
    212   ipc_router_.OnTabDeactivated();
    213 }
    214 
    215 void SearchTabHelper::ToggleVoiceSearch() {
    216   ipc_router_.ToggleVoiceSearch();
    217 }
    218 
    219 bool SearchTabHelper::IsSearchResultsPage() {
    220   return model_.mode().is_origin_search();
    221 }
    222 
    223 void SearchTabHelper::RenderViewCreated(
    224     content::RenderViewHost* render_view_host) {
    225   ipc_router_.SetPromoInformation(IsAppLauncherEnabled());
    226 }
    227 
    228 void SearchTabHelper::DidStartNavigationToPendingEntry(
    229     const GURL& url,
    230     content::NavigationController::ReloadType /* reload_type */) {
    231   if (chrome::IsNTPURL(url, profile())) {
    232     // Set the title on any pending entry corresponding to the NTP. This
    233     // prevents any flickering of the tab title.
    234     content::NavigationEntry* entry =
    235         web_contents_->GetController().GetPendingEntry();
    236     if (entry)
    237       entry->SetTitle(l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE));
    238   }
    239 }
    240 
    241 void SearchTabHelper::DidNavigateMainFrame(
    242     const content::LoadCommittedDetails& details,
    243     const content::FrameNavigateParams& params) {
    244   if (IsCacheableNTP(web_contents_)) {
    245     if (details.http_status_code == 204 || details.http_status_code >= 400) {
    246       RedirectToLocalNTP();
    247       RecordCacheableNTPLoadHistogram(false);
    248       return;
    249     }
    250     RecordCacheableNTPLoadHistogram(true);
    251   }
    252 
    253   // Always set the title on the new tab page to be the one from our UI
    254   // resources. Normally, we set the title when we begin a NTP load, but it can
    255   // get reset in several places (like when you press Reload). This check
    256   // ensures that the title is properly set to the string defined by the Chrome
    257   // UI language (rather than the server language) in all cases.
    258   //
    259   // We only override the title when it's nonempty to allow the page to set the
    260   // title if it really wants. An empty title means to use the default. There's
    261   // also a race condition between this code and the page's SetTitle call which
    262   // this rule avoids.
    263   content::NavigationEntry* entry =
    264       web_contents_->GetController().GetLastCommittedEntry();
    265   if (entry && entry->GetTitle().empty() &&
    266       (entry->GetVirtualURL() == GURL(chrome::kChromeUINewTabURL) ||
    267        chrome::NavEntryIsInstantNTP(web_contents_, entry))) {
    268     entry->SetTitle(l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE));
    269   }
    270 }
    271 
    272 void SearchTabHelper::DidFailProvisionalLoad(
    273     int64 /* frame_id */,
    274     const base::string16& /* frame_unique_name */,
    275     bool is_main_frame,
    276     const GURL& validated_url,
    277     int error_code,
    278     const base::string16& /* error_description */,
    279     content::RenderViewHost* /* render_view_host */) {
    280   // If error_code is ERR_ABORTED means that the user has canceled this
    281   // navigation so it shouldn't be redirected.
    282   if (is_main_frame &&
    283       error_code != net::ERR_ABORTED &&
    284       chrome::ShouldUseCacheableNTP() &&
    285       validated_url != GURL(chrome::kChromeSearchLocalNtpUrl) &&
    286       chrome::IsNTPURL(validated_url, profile())) {
    287     RedirectToLocalNTP();
    288     RecordCacheableNTPLoadHistogram(false);
    289   }
    290 }
    291 
    292 void SearchTabHelper::DidFinishLoad(
    293     int64 /* frame_id */,
    294     const GURL&  /* validated_url */,
    295     bool is_main_frame,
    296     content::RenderViewHost* /* render_view_host */) {
    297   if (is_main_frame)
    298     DetermineIfPageSupportsInstant();
    299 }
    300 
    301 void SearchTabHelper::NavigationEntryCommitted(
    302     const content::LoadCommittedDetails& load_details) {
    303   if (!is_search_enabled_)
    304     return;
    305 
    306   if (!load_details.is_main_frame)
    307     return;
    308 
    309   // TODO(kmadhusu): Set the page initial states (such as omnibox margin, etc)
    310   // from here. Please refer to crbug.com/247517 for more details.
    311   if (chrome::ShouldAssignURLToInstantRenderer(web_contents_->GetURL(),
    312                                                profile())) {
    313     ipc_router_.SetDisplayInstantResults();
    314   }
    315 
    316   UpdateMode(true, false);
    317 
    318   content::NavigationEntry* entry =
    319       web_contents_->GetController().GetVisibleEntry();
    320   DCHECK(entry);
    321 
    322   // Already determined the instant support state for this page, do not reset
    323   // the instant support state.
    324   //
    325   // When we get a navigation entry committed event, there seem to be two ways
    326   // to tell whether the navigation was "in-page". Ideally, when
    327   // LoadCommittedDetails::is_in_page is true, we should have
    328   // LoadCommittedDetails::type to be NAVIGATION_TYPE_IN_PAGE. Unfortunately,
    329   // they are different in some cases. To workaround this bug, we are checking
    330   // (is_in_page || type == NAVIGATION_TYPE_IN_PAGE). Please refer to
    331   // crbug.com/251330 for more details.
    332   if (load_details.is_in_page ||
    333       load_details.type == content::NAVIGATION_TYPE_IN_PAGE) {
    334     // When an "in-page" navigation happens, we will not receive a
    335     // DidFinishLoad() event. Therefore, we will not determine the Instant
    336     // support for the navigated page. So, copy over the Instant support from
    337     // the previous entry. If the page does not support Instant, update the
    338     // location bar from here to turn off search terms replacement.
    339     chrome::SetInstantSupportStateInNavigationEntry(model_.instant_support(),
    340                                                     entry);
    341     if (model_.instant_support() == INSTANT_SUPPORT_NO)
    342       UpdateLocationBar(web_contents_);
    343     return;
    344   }
    345 
    346   model_.SetInstantSupportState(INSTANT_SUPPORT_UNKNOWN);
    347   model_.SetVoiceSearchSupported(false);
    348   chrome::SetInstantSupportStateInNavigationEntry(model_.instant_support(),
    349                                                   entry);
    350 }
    351 
    352 void SearchTabHelper::OnInstantSupportDetermined(bool supports_instant) {
    353   InstantSupportChanged(supports_instant);
    354 }
    355 
    356 void SearchTabHelper::OnSetVoiceSearchSupport(bool supports_voice_search) {
    357   model_.SetVoiceSearchSupported(supports_voice_search);
    358 }
    359 
    360 void SearchTabHelper::ThemeInfoChanged(const ThemeBackgroundInfo& theme_info) {
    361   ipc_router_.SendThemeBackgroundInfo(theme_info);
    362 }
    363 
    364 void SearchTabHelper::MostVisitedItemsChanged(
    365     const std::vector<InstantMostVisitedItem>& items) {
    366   std::vector<InstantMostVisitedItem> items_copy(items);
    367   MaybeRemoveMostVisitedItems(&items_copy);
    368   ipc_router_.SendMostVisitedItems(items_copy);
    369 }
    370 
    371 void SearchTabHelper::MaybeRemoveMostVisitedItems(
    372     std::vector<InstantMostVisitedItem>* items) {
    373 // The code below uses APIs not available on Android and the experiment should
    374 // not run there.
    375 #if !defined(OS_ANDROID)
    376   if (!history::MostVisitedTilesExperiment::IsDontShowOpenURLsEnabled())
    377     return;
    378 
    379   Profile* profile =
    380       Profile::FromBrowserContext(web_contents_->GetBrowserContext());
    381   if (!profile)
    382     return;
    383 
    384   Browser* browser = chrome::FindBrowserWithProfile(profile,
    385                                                     chrome::GetActiveDesktop());
    386   if (!browser)
    387     return;
    388 
    389   TabStripModel* tab_strip_model = browser->tab_strip_model();
    390   history::TopSites* top_sites = profile->GetTopSites();
    391   if (!tab_strip_model || !top_sites) {
    392     NOTREACHED();
    393     return;
    394   }
    395 
    396   std::set<std::string> open_urls;
    397   chrome::GetOpenUrls(*tab_strip_model, *top_sites, &open_urls);
    398   history::MostVisitedTilesExperiment::RemoveItemsMatchingOpenTabs(
    399       open_urls, items);
    400 #endif
    401 }
    402 
    403 void SearchTabHelper::FocusOmnibox(OmniboxFocusState state) {
    404 // iOS and Android don't use the Instant framework.
    405 #if !defined(OS_IOS) && !defined(OS_ANDROID)
    406   Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
    407   if (!browser)
    408     return;
    409 
    410   OmniboxView* omnibox = browser->window()->GetLocationBar()->GetOmniboxView();
    411   // Do not add a default case in the switch block for the following reasons:
    412   // (1) Explicitly handle the new states. If new states are added in the
    413   // OmniboxFocusState, the compiler will warn the developer to handle the new
    414   // states.
    415   // (2) An attacker may control the renderer and sends the browser process a
    416   // malformed IPC. This function responds to the invalid |state| values by
    417   // doing nothing instead of crashing the browser process (intentional no-op).
    418   switch (state) {
    419     case OMNIBOX_FOCUS_VISIBLE:
    420       omnibox->SetFocus();
    421       omnibox->model()->SetCaretVisibility(true);
    422       break;
    423     case OMNIBOX_FOCUS_INVISIBLE:
    424       omnibox->SetFocus();
    425       omnibox->model()->SetCaretVisibility(false);
    426       // If the user clicked on the fakebox, any text already in the omnibox
    427       // should get cleared when they start typing. Selecting all the existing
    428       // text is a convenient way to accomplish this. It also gives a slight
    429       // visual cue to users who really understand selection state about what
    430       // will happen if they start typing.
    431       omnibox->SelectAll(false);
    432       break;
    433     case OMNIBOX_FOCUS_NONE:
    434       // Remove focus only if the popup is closed. This will prevent someone
    435       // from changing the omnibox value and closing the popup without user
    436       // interaction.
    437       if (!omnibox->model()->popup_model()->IsOpen())
    438         web_contents()->GetView()->Focus();
    439       break;
    440   }
    441 #endif
    442 }
    443 
    444 void SearchTabHelper::NavigateToURL(const GURL& url,
    445                                     WindowOpenDisposition disposition,
    446                                     bool is_most_visited_item_url) {
    447 // iOS and Android don't use the Instant framework.
    448 #if !defined(OS_IOS) && !defined(OS_ANDROID)
    449   // TODO(kmadhusu): Remove chrome::FindBrowser...() function call from here.
    450   // Create a SearchTabHelperDelegate interface and have the Browser object
    451   // implement that interface to provide the necessary functionality.
    452   Browser* browser = chrome::FindBrowserWithWebContents(web_contents_);
    453   Profile* profile =
    454       Profile::FromBrowserContext(web_contents_->GetBrowserContext());
    455   if (!browser || !profile)
    456     return;
    457 
    458   if (is_most_visited_item_url) {
    459     content::RecordAction(
    460         content::UserMetricsAction("InstantExtended.MostVisitedClicked"));
    461   }
    462 
    463   chrome::NavigateParams params(browser, url,
    464                                 content::PAGE_TRANSITION_AUTO_BOOKMARK);
    465   params.referrer = content::Referrer();
    466   params.source_contents = web_contents_;
    467   params.disposition = disposition;
    468   params.is_renderer_initiated = false;
    469   params.initiating_profile = profile;
    470   chrome::Navigate(&params);
    471 #endif
    472 }
    473 
    474 void SearchTabHelper::OnDeleteMostVisitedItem(const GURL& url) {
    475   DCHECK(!url.is_empty());
    476   if (instant_service_)
    477     instant_service_->DeleteMostVisitedItem(url);
    478 }
    479 
    480 void SearchTabHelper::OnUndoMostVisitedDeletion(const GURL& url) {
    481   DCHECK(!url.is_empty());
    482   if (instant_service_)
    483     instant_service_->UndoMostVisitedDeletion(url);
    484 }
    485 
    486 void SearchTabHelper::OnUndoAllMostVisitedDeletions() {
    487   if (instant_service_)
    488     instant_service_->UndoAllMostVisitedDeletions();
    489 }
    490 
    491 void SearchTabHelper::OnLogEvent(NTPLoggingEventType event) {
    492   NTPUserDataLogger::GetOrCreateFromWebContents(
    493       web_contents())->LogEvent(event);
    494 }
    495 
    496 void SearchTabHelper::OnLogImpression(int position,
    497                                       const base::string16& provider) {
    498   NTPUserDataLogger::GetOrCreateFromWebContents(
    499       web_contents())->LogImpression(position, provider);
    500 }
    501 
    502 void SearchTabHelper::PasteIntoOmnibox(const base::string16& text) {
    503 // iOS and Android don't use the Instant framework.
    504 #if !defined(OS_IOS) && !defined(OS_ANDROID)
    505   Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
    506   if (!browser)
    507     return;
    508 
    509   OmniboxView* omnibox = browser->window()->GetLocationBar()->GetOmniboxView();
    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 = text.empty() ? omnibox->GetClipboardText() :
    515       omnibox->SanitizeTextForPaste(text);
    516 
    517   if (text_to_paste.empty())
    518     return;
    519 
    520   if (!omnibox->model()->has_focus())
    521     omnibox->SetFocus();
    522 
    523   omnibox->OnBeforePossibleChange();
    524   omnibox->model()->OnPaste();
    525   omnibox->SetUserText(text_to_paste);
    526   omnibox->OnAfterPossibleChange();
    527 #endif
    528 }
    529 
    530 void SearchTabHelper::OnChromeIdentityCheck(const base::string16& identity) {
    531   SigninManagerBase* manager = SigninManagerFactory::GetForProfile(profile());
    532   if (manager) {
    533     const base::string16 username =
    534         UTF8ToUTF16(manager->GetAuthenticatedUsername());
    535     ipc_router_.SendChromeIdentityCheckResult(identity,
    536                                               identity == username);
    537   }
    538 }
    539 
    540 void SearchTabHelper::UpdateMode(bool update_origin, bool is_preloaded_ntp) {
    541   SearchMode::Type type = SearchMode::MODE_DEFAULT;
    542   SearchMode::Origin origin = SearchMode::ORIGIN_DEFAULT;
    543   if (IsNTP(web_contents_) || is_preloaded_ntp) {
    544     type = SearchMode::MODE_NTP;
    545     origin = SearchMode::ORIGIN_NTP;
    546   } else if (IsSearchResults(web_contents_)) {
    547     type = SearchMode::MODE_SEARCH_RESULTS;
    548     origin = SearchMode::ORIGIN_SEARCH;
    549   }
    550   if (!update_origin)
    551     origin = model_.mode().origin;
    552   if (user_input_in_progress_)
    553     type = SearchMode::MODE_SEARCH_SUGGESTIONS;
    554   model_.SetMode(SearchMode(type, origin));
    555 }
    556 
    557 void SearchTabHelper::DetermineIfPageSupportsInstant() {
    558   if (!InInstantProcess(profile(), web_contents_)) {
    559     // The page is not in the Instant process. This page does not support
    560     // instant. If we send an IPC message to a page that is not in the Instant
    561     // process, it will never receive it and will never respond. Therefore,
    562     // return immediately.
    563     InstantSupportChanged(false);
    564   } else if (IsLocal(web_contents_)) {
    565     // Local pages always support Instant.
    566     InstantSupportChanged(true);
    567   } else {
    568     ipc_router_.DetermineIfPageSupportsInstant();
    569   }
    570 }
    571 
    572 Profile* SearchTabHelper::profile() const {
    573   return Profile::FromBrowserContext(web_contents_->GetBrowserContext());
    574 }
    575 
    576 void SearchTabHelper::RedirectToLocalNTP() {
    577   // Extra parentheses to declare a variable.
    578   content::NavigationController::LoadURLParams load_params(
    579       (GURL(chrome::kChromeSearchLocalNtpUrl)));
    580   load_params.referrer = content::Referrer();
    581   load_params.transition_type = content::PAGE_TRANSITION_SERVER_REDIRECT;
    582   // Don't push a history entry.
    583   load_params.should_replace_current_entry = true;
    584   web_contents_->GetController().LoadURLWithParams(load_params);
    585 }
    586