1// Copyright 2013 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/instant_search_prerenderer.h"
6
7#include "chrome/browser/autocomplete/autocomplete_match.h"
8#include "chrome/browser/prerender/prerender_handle.h"
9#include "chrome/browser/prerender/prerender_manager.h"
10#include "chrome/browser/prerender/prerender_manager_factory.h"
11#include "chrome/browser/profiles/profile.h"
12#include "chrome/browser/search/instant_service.h"
13#include "chrome/browser/search/instant_service_factory.h"
14#include "chrome/browser/search/search.h"
15#include "chrome/browser/ui/browser_navigator.h"
16#include "chrome/browser/ui/search/search_tab_helper.h"
17
18namespace {
19
20// Returns true if the underlying page supports Instant search.
21bool PageSupportsInstantSearch(content::WebContents* contents) {
22  // Search results page supports Instant search.
23  return SearchTabHelper::FromWebContents(contents)->IsSearchResultsPage();
24}
25
26}  // namespace
27
28InstantSearchPrerenderer::InstantSearchPrerenderer(Profile* profile,
29                                                   const GURL& url)
30    : profile_(profile),
31      prerender_url_(url) {
32}
33
34InstantSearchPrerenderer::~InstantSearchPrerenderer() {
35  if (prerender_handle_)
36    prerender_handle_->OnCancel();
37}
38
39// static
40InstantSearchPrerenderer* InstantSearchPrerenderer::GetForProfile(
41    Profile* profile) {
42  DCHECK(profile);
43  InstantService* instant_service =
44      InstantServiceFactory::GetForProfile(profile);
45  return instant_service ? instant_service->instant_search_prerenderer() : NULL;
46}
47
48void InstantSearchPrerenderer::Init(
49    const content::SessionStorageNamespaceMap& session_storage_namespace_map,
50    const gfx::Size& size) {
51  // TODO(kmadhusu): Enable Instant for Incognito profile.
52  if (profile_->IsOffTheRecord())
53    return;
54
55  // Only cancel the old prerender after starting the new one, so if the URLs
56  // are the same, the underlying prerender will be reused.
57  scoped_ptr<prerender::PrerenderHandle> old_prerender_handle(
58      prerender_handle_.release());
59  prerender::PrerenderManager* prerender_manager =
60      prerender::PrerenderManagerFactory::GetForProfile(profile_);
61  if (prerender_manager) {
62    content::SessionStorageNamespace* session_storage_namespace = NULL;
63    content::SessionStorageNamespaceMap::const_iterator it =
64        session_storage_namespace_map.find(std::string());
65    if (it != session_storage_namespace_map.end())
66      session_storage_namespace = it->second.get();
67
68    prerender_handle_.reset(prerender_manager->AddPrerenderForInstant(
69        prerender_url_, session_storage_namespace, size));
70  }
71  if (old_prerender_handle)
72    old_prerender_handle->OnCancel();
73}
74
75void InstantSearchPrerenderer::Cancel() {
76  if (!prerender_handle_)
77    return;
78
79  last_instant_suggestion_ = InstantSuggestion();
80  prerender_handle_->OnCancel();
81  prerender_handle_.reset();
82}
83
84void InstantSearchPrerenderer::Prerender(const InstantSuggestion& suggestion) {
85  if (!prerender_handle_)
86    return;
87
88  if (last_instant_suggestion_.text == suggestion.text)
89    return;
90
91  if (last_instant_suggestion_.text.empty() &&
92      !prerender_handle_->IsFinishedLoading())
93    return;
94
95  if (!prerender_contents())
96    return;
97
98  last_instant_suggestion_ = suggestion;
99  SearchTabHelper::FromWebContents(prerender_contents())->
100      SetSuggestionToPrefetch(suggestion);
101}
102
103void InstantSearchPrerenderer::Commit(const base::string16& query) {
104  DCHECK(prerender_handle_);
105  DCHECK(prerender_contents());
106  SearchTabHelper::FromWebContents(prerender_contents())->Submit(query);
107}
108
109bool InstantSearchPrerenderer::CanCommitQuery(
110    content::WebContents* source,
111    const base::string16& query) const {
112  if (!source || query.empty() || !prerender_handle_ ||
113      !prerender_handle_->IsFinishedLoading() ||
114      !prerender_contents() || !QueryMatchesPrefetch(query)) {
115    return false;
116  }
117
118  // InstantSearchPrerenderer can commit query to the prerendered page only if
119  // the underlying |source| page doesn't support Instant search.
120  return !PageSupportsInstantSearch(source);
121}
122
123bool InstantSearchPrerenderer::UsePrerenderedPage(
124    const GURL& url,
125    chrome::NavigateParams* params) {
126  base::string16 search_terms =
127      chrome::ExtractSearchTermsFromURL(profile_, url);
128  prerender::PrerenderManager* prerender_manager =
129      prerender::PrerenderManagerFactory::GetForProfile(profile_);
130  if (search_terms.empty() || !params->target_contents ||
131      !prerender_contents() || !prerender_manager ||
132      !QueryMatchesPrefetch(search_terms) ||
133      params->disposition != CURRENT_TAB) {
134    Cancel();
135    return false;
136  }
137
138  bool success = prerender_manager->MaybeUsePrerenderedPage(
139      prerender_contents()->GetURL(), params);
140  prerender_handle_.reset();
141  return success;
142}
143
144bool InstantSearchPrerenderer::IsAllowed(const AutocompleteMatch& match,
145                                         content::WebContents* source) const {
146  return source && AutocompleteMatch::IsSearchType(match.type) &&
147      !PageSupportsInstantSearch(source);
148}
149
150content::WebContents* InstantSearchPrerenderer::prerender_contents() const {
151  return (prerender_handle_ && prerender_handle_->contents()) ?
152      prerender_handle_->contents()->prerender_contents() : NULL;
153}
154
155bool InstantSearchPrerenderer::QueryMatchesPrefetch(
156    const base::string16& query) const {
157  if (chrome::ShouldReuseInstantSearchBasePage())
158    return true;
159  return last_instant_suggestion_.text == query;
160}
161