blob: 2bbe5d08110b5c2b49fcf68cae36bf99ffc2f62a [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/search/instant_search_prerenderer.h"
#include "chrome/browser/prerender/prerender_handle.h"
#include "chrome/browser/prerender/prerender_manager.h"
#include "chrome/browser/prerender/prerender_manager_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search/instant_service.h"
#include "chrome/browser/search/instant_service_factory.h"
#include "chrome/browser/search/search.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/browser/ui/search/search_tab_helper.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/search/search.h"
#include "components/search_engines/template_url_service.h"
namespace {
// Returns true if the underlying page supports Instant search.
bool PageSupportsInstantSearch(content::WebContents* contents) {
// Search results page supports Instant search.
return SearchTabHelper::FromWebContents(contents)->IsSearchResultsPage();
}
// Returns true if |match| is associated with the default search provider.
bool MatchIsFromDefaultSearchProvider(const AutocompleteMatch& match,
Profile* profile) {
DCHECK(profile);
TemplateURLService* template_url_service =
TemplateURLServiceFactory::GetForProfile(profile);
return match.GetTemplateURL(template_url_service, false) ==
template_url_service->GetDefaultSearchProvider();
}
} // namespace
InstantSearchPrerenderer::InstantSearchPrerenderer(Profile* profile,
const GURL& url)
: profile_(profile),
prerender_url_(url) {
}
InstantSearchPrerenderer::~InstantSearchPrerenderer() {
if (prerender_handle_)
prerender_handle_->OnCancel();
}
// static
InstantSearchPrerenderer* InstantSearchPrerenderer::GetForProfile(
Profile* profile) {
DCHECK(profile);
InstantService* instant_service =
InstantServiceFactory::GetForProfile(profile);
return instant_service ? instant_service->instant_search_prerenderer() : NULL;
}
void InstantSearchPrerenderer::Init(
content::SessionStorageNamespace* session_storage_namespace,
const gfx::Size& size) {
// TODO(kmadhusu): Enable Instant for Incognito profile.
if (profile_->IsOffTheRecord())
return;
// Only cancel the old prerender after starting the new one, so if the URLs
// are the same, the underlying prerender will be reused.
scoped_ptr<prerender::PrerenderHandle> old_prerender_handle(
prerender_handle_.release());
prerender::PrerenderManager* prerender_manager =
prerender::PrerenderManagerFactory::GetForProfile(profile_);
if (prerender_manager) {
prerender_handle_.reset(prerender_manager->AddPrerenderForInstant(
prerender_url_, session_storage_namespace, size));
}
if (old_prerender_handle)
old_prerender_handle->OnCancel();
}
void InstantSearchPrerenderer::Cancel() {
if (!prerender_handle_)
return;
last_instant_suggestion_ = InstantSuggestion();
prerender_handle_->OnCancel();
prerender_handle_.reset();
}
void InstantSearchPrerenderer::Prerender(const InstantSuggestion& suggestion) {
if (!prerender_handle_)
return;
if (last_instant_suggestion_.text == suggestion.text)
return;
if (last_instant_suggestion_.text.empty() &&
!prerender_handle_->IsFinishedLoading())
return;
if (!prerender_contents())
return;
last_instant_suggestion_ = suggestion;
SearchTabHelper::FromWebContents(prerender_contents())->
SetSuggestionToPrefetch(suggestion);
}
void InstantSearchPrerenderer::Commit(
const base::string16& query,
const EmbeddedSearchRequestParams& params) {
DCHECK(prerender_handle_);
DCHECK(prerender_contents());
SearchTabHelper::FromWebContents(prerender_contents())->Submit(query, params);
}
bool InstantSearchPrerenderer::CanCommitQuery(
content::WebContents* source,
const base::string16& query) const {
if (!source || query.empty() || !prerender_handle_ ||
!prerender_handle_->IsFinishedLoading() ||
!prerender_contents() || !QueryMatchesPrefetch(query)) {
return false;
}
// InstantSearchPrerenderer can commit query to the prerendered page only if
// the underlying |source| page doesn't support Instant search.
return !PageSupportsInstantSearch(source);
}
bool InstantSearchPrerenderer::UsePrerenderedPage(
const GURL& url,
chrome::NavigateParams* params) {
base::string16 search_terms =
search::ExtractSearchTermsFromURL(profile_, url);
prerender::PrerenderManager* prerender_manager =
prerender::PrerenderManagerFactory::GetForProfile(profile_);
if (search_terms.empty() || !params->target_contents ||
!prerender_contents() || !prerender_manager ||
!QueryMatchesPrefetch(search_terms) ||
params->disposition != CURRENT_TAB) {
Cancel();
return false;
}
// Do not use prerendered page for renderer initiated search requests.
if (params->is_renderer_initiated &&
params->transition == ui::PAGE_TRANSITION_LINK) {
Cancel();
return false;
}
bool success = prerender_manager->MaybeUsePrerenderedPage(
prerender_contents()->GetURL(), params);
prerender_handle_.reset();
return success;
}
bool InstantSearchPrerenderer::IsAllowed(const AutocompleteMatch& match,
content::WebContents* source) const {
// We block prerendering for anything but search-type matches associated with
// the default search provider.
//
// This is more restrictive than necessary. All that's really needed to be
// able to successfully prerender is that the |destination_url| of |match| be
// from the same origin and path as the default search engine, and the params
// to be sent to the server be a subset of the params we can pass to the
// prerenderer. So for example, if we normally prerender search URLs like
// https://google.com/search?q=foo&x=bar, then any match with a URL like that,
// potentially with the q and/or x params omitted, is prerenderable.
//
// However, if the URL has other params _not_ normally in the prerendered URL,
// there's no way to pass them to the prerendered page, and worse, if the URL
// does something like specifying params in both the query and ref sections of
// the URL (as Google URLs often do), it can quickly become impossible to
// figure out how to correctly tease out the right param names and values to
// send. Rather than try and write parsing code to deal with all these kinds
// of cases, for various different search engines, including accommodating
// changing behavior over time, we do the simple restriction described above.
// This handles the by-far-the-most-common cases while still being simple and
// maintainable.
return source && AutocompleteMatch::IsSearchType(match.type) &&
MatchIsFromDefaultSearchProvider(match, profile_) &&
!PageSupportsInstantSearch(source);
}
content::WebContents* InstantSearchPrerenderer::prerender_contents() const {
return (prerender_handle_ && prerender_handle_->contents()) ?
prerender_handle_->contents()->prerender_contents() : NULL;
}
bool InstantSearchPrerenderer::QueryMatchesPrefetch(
const base::string16& query) const {
if (search::ShouldReuseInstantSearchBasePage())
return true;
return last_instant_suggestion_.text == query;
}