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