// 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; }