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 "base/basictypes.h"
8#include "base/compiler_specific.h"
9#include "base/memory/scoped_ptr.h"
10#include "base/metrics/field_trial.h"
11#include "base/strings/string16.h"
12#include "base/strings/utf_string_conversions.h"
13#include "chrome/browser/autocomplete/autocomplete_match.h"
14#include "chrome/browser/prerender/prerender_contents.h"
15#include "chrome/browser/prerender/prerender_handle.h"
16#include "chrome/browser/prerender/prerender_manager.h"
17#include "chrome/browser/prerender/prerender_manager_factory.h"
18#include "chrome/browser/prerender/prerender_origin.h"
19#include "chrome/browser/prerender/prerender_tracker.h"
20#include "chrome/browser/profiles/profile.h"
21#include "chrome/browser/search/instant_service.h"
22#include "chrome/browser/search/instant_unittest_base.h"
23#include "chrome/browser/search/search.h"
24#include "chrome/browser/ui/search/search_tab_helper.h"
25#include "chrome/browser/ui/tabs/tab_strip_model.h"
26#include "chrome/common/render_messages.h"
27#include "content/public/browser/navigation_controller.h"
28#include "content/public/browser/web_contents.h"
29#include "content/public/common/url_constants.h"
30#include "content/public/test/mock_render_process_host.h"
31#include "ipc/ipc_message.h"
32#include "ipc/ipc_test_sink.h"
33#include "ui/gfx/size.h"
34
35namespace {
36
37using content::Referrer;
38using prerender::Origin;
39using prerender::PrerenderContents;
40using prerender::PrerenderHandle;
41using prerender::PrerenderManager;
42using prerender::PrerenderManagerFactory;
43
44class DummyPrerenderContents : public PrerenderContents {
45 public:
46  DummyPrerenderContents(
47      PrerenderManager* prerender_manager,
48      Profile* profile,
49      const GURL& url,
50      const Referrer& referrer,
51      Origin origin,
52      bool call_did_finish_load,
53      const content::SessionStorageNamespaceMap& session_storage_namespace_map);
54
55  virtual void StartPrerendering(
56      int ALLOW_UNUSED creator_child_id,
57      const gfx::Size& ALLOW_UNUSED size,
58      content::SessionStorageNamespace* session_storage_namespace) OVERRIDE;
59  virtual bool GetChildId(int* child_id) const OVERRIDE;
60  virtual bool GetRouteId(int* route_id) const OVERRIDE;
61
62 private:
63  Profile* profile_;
64  const GURL url_;
65  bool call_did_finish_load_;
66  content::SessionStorageNamespaceMap session_storage_namespace_map_;
67
68  DISALLOW_COPY_AND_ASSIGN(DummyPrerenderContents);
69};
70
71class DummyPrerenderContentsFactory : public PrerenderContents::Factory {
72 public:
73  DummyPrerenderContentsFactory(
74      bool call_did_finish_load,
75      const content::SessionStorageNamespaceMap& session_storage_namespace_map)
76      : call_did_finish_load_(call_did_finish_load),
77        session_storage_namespace_map_(session_storage_namespace_map) {
78  }
79
80  virtual PrerenderContents* CreatePrerenderContents(
81      PrerenderManager* prerender_manager,
82      Profile* profile,
83      const GURL& url,
84      const Referrer& referrer,
85      Origin origin,
86      uint8 experiment_id) OVERRIDE;
87
88 private:
89  bool call_did_finish_load_;
90  content::SessionStorageNamespaceMap session_storage_namespace_map_;
91
92  DISALLOW_COPY_AND_ASSIGN(DummyPrerenderContentsFactory);
93};
94
95DummyPrerenderContents::DummyPrerenderContents(
96    PrerenderManager* prerender_manager,
97    Profile* profile,
98    const GURL& url,
99    const Referrer& referrer,
100    Origin origin,
101    bool call_did_finish_load,
102    const content::SessionStorageNamespaceMap& session_storage_namespace_map)
103    : PrerenderContents(prerender_manager, profile, url, referrer, origin,
104                        PrerenderManager::kNoExperiment),
105      profile_(profile),
106      url_(url),
107      call_did_finish_load_(call_did_finish_load),
108      session_storage_namespace_map_(session_storage_namespace_map) {
109}
110
111void DummyPrerenderContents::StartPrerendering(
112    int ALLOW_UNUSED creator_child_id,
113    const gfx::Size& ALLOW_UNUSED size,
114    content::SessionStorageNamespace* session_storage_namespace) {
115  prerender_contents_.reset(content::WebContents::CreateWithSessionStorage(
116      content::WebContents::CreateParams(profile_),
117      session_storage_namespace_map_));
118  content::NavigationController::LoadURLParams params(url_);
119  prerender_contents_->GetController().LoadURLWithParams(params);
120  SearchTabHelper::CreateForWebContents(prerender_contents_.get());
121
122  AddObserver(prerender_manager()->prerender_tracker());
123  prerendering_has_started_ = true;
124  DCHECK(session_storage_namespace);
125  session_storage_namespace_id_ = session_storage_namespace->id();
126  NotifyPrerenderStart();
127
128  if (call_did_finish_load_)
129    DidFinishLoad(1, url_, true, NULL);
130}
131
132bool DummyPrerenderContents::GetChildId(int* child_id) const {
133  *child_id = 1;
134  return true;
135}
136
137bool DummyPrerenderContents::GetRouteId(int* route_id) const {
138  *route_id = 1;
139  return true;
140}
141
142PrerenderContents* DummyPrerenderContentsFactory::CreatePrerenderContents(
143    PrerenderManager* prerender_manager,
144    Profile* profile,
145    const GURL& url,
146    const Referrer& referrer,
147    Origin origin,
148    uint8 experiment_id) {
149  return new DummyPrerenderContents(prerender_manager, profile, url, referrer,
150                                    origin, call_did_finish_load_,
151                                    session_storage_namespace_map_);
152}
153
154}  // namespace
155
156class InstantSearchPrerendererTest : public InstantUnitTestBase {
157 public:
158  InstantSearchPrerendererTest() {}
159
160 protected:
161  virtual void SetUp() OVERRIDE {
162    ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
163        "EmbeddedSearch",
164        "Group1 strk:20 use_cacheable_ntp:1 prefetch_results:1"));
165    InstantUnitTestBase::SetUp();
166  }
167
168  void Init(bool prerender_search_results_base_page,
169            bool call_did_finish_load) {
170    AddTab(browser(), GURL(content::kAboutBlankURL));
171
172    content::SessionStorageNamespaceMap session_storage_namespace_map;
173    session_storage_namespace_map[std::string()] =
174        GetActiveWebContents()->GetController().
175            GetDefaultSessionStorageNamespace();
176    PrerenderManagerFactory::GetForProfile(browser()->profile())->
177        SetPrerenderContentsFactory(
178            new DummyPrerenderContentsFactory(call_did_finish_load,
179                                              session_storage_namespace_map));
180
181    if (prerender_search_results_base_page) {
182      InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
183      prerenderer->Init(session_storage_namespace_map, gfx::Size(640, 480));
184      EXPECT_NE(static_cast<PrerenderHandle*>(NULL), prerender_handle());
185    }
186  }
187
188  InstantSearchPrerenderer* GetInstantSearchPrerenderer() {
189    return instant_service_->instant_search_prerenderer();
190  }
191
192  const GURL& GetPrerenderURL() {
193    return GetInstantSearchPrerenderer()->prerender_url_;
194  }
195
196  void SetLastQuery(const string16& query) {
197    GetInstantSearchPrerenderer()->last_instant_suggestion_ =
198        InstantSuggestion(query, std::string());
199  }
200
201  content::WebContents* prerender_contents() {
202    return GetInstantSearchPrerenderer()->prerender_contents();
203  }
204
205  bool MessageWasSent(uint32 id) {
206    content::MockRenderProcessHost* process =
207        static_cast<content::MockRenderProcessHost*>(
208            prerender_contents()->GetRenderViewHost()->GetProcess());
209    return process->sink().GetFirstMessageMatching(id) != NULL;
210  }
211
212  content::WebContents* GetActiveWebContents() const {
213    return browser()->tab_strip_model()->GetWebContentsAt(0);
214  }
215
216  PrerenderHandle* prerender_handle() {
217    return GetInstantSearchPrerenderer()->prerender_handle_.get();
218  }
219
220  void PrerenderSearchQuery(const string16& query) {
221    Init(true, true);
222    InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
223    prerenderer->Prerender(InstantSuggestion(query, std::string()));
224    CommitPendingLoad(&prerender_contents()->GetController());
225    EXPECT_TRUE(prerenderer->CanCommitQuery(GetActiveWebContents(), query));
226    EXPECT_NE(static_cast<PrerenderHandle*>(NULL), prerender_handle());
227  }
228};
229
230TEST_F(InstantSearchPrerendererTest, GetSearchTermsFromPrerenderedPage) {
231  Init(false, false);
232  InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
233  GURL url(GetPrerenderURL());
234  EXPECT_EQ(GURL("https://www.google.com/instant?ion=1&foo=foo#foo=foo&strk"),
235            url);
236  EXPECT_EQ(UTF16ToASCII(prerenderer->get_last_query()),
237            UTF16ToASCII(chrome::GetSearchTermsFromURL(profile(), url)));
238
239  // Assume the prerendered page prefetched search results for the query
240  // "flowers".
241  SetLastQuery(ASCIIToUTF16("flowers"));
242  EXPECT_EQ("flowers", UTF16ToASCII(prerenderer->get_last_query()));
243  EXPECT_EQ(UTF16ToASCII(prerenderer->get_last_query()),
244            UTF16ToASCII(chrome::GetSearchTermsFromURL(profile(), url)));
245}
246
247TEST_F(InstantSearchPrerendererTest, PrefetchSearchResults) {
248  Init(true, true);
249  EXPECT_TRUE(prerender_handle()->IsFinishedLoading());
250  InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
251  prerenderer->Prerender(
252      InstantSuggestion(ASCIIToUTF16("flowers"), std::string()));
253  EXPECT_EQ("flowers", UTF16ToASCII(prerenderer->get_last_query()));
254  EXPECT_TRUE(MessageWasSent(
255      ChromeViewMsg_SearchBoxSetSuggestionToPrefetch::ID));
256}
257
258TEST_F(InstantSearchPrerendererTest, DoNotPrefetchSearchResults) {
259  Init(true, false);
260  // Page hasn't finished loading yet.
261  EXPECT_FALSE(prerender_handle()->IsFinishedLoading());
262  InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
263  prerenderer->Prerender(
264      InstantSuggestion(ASCIIToUTF16("flowers"), std::string()));
265  EXPECT_EQ("", UTF16ToASCII(prerenderer->get_last_query()));
266  EXPECT_FALSE(MessageWasSent(
267      ChromeViewMsg_SearchBoxSetSuggestionToPrefetch::ID));
268}
269
270TEST_F(InstantSearchPrerendererTest, CanCommitQuery) {
271  Init(true, true);
272  InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
273  string16 query = ASCIIToUTF16("flowers");
274  prerenderer->Prerender(InstantSuggestion(query, std::string()));
275  EXPECT_TRUE(prerenderer->CanCommitQuery(GetActiveWebContents(), query));
276
277  // Make sure InstantSearchPrerenderer::CanCommitQuery() returns false for
278  // invalid search queries.
279  EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(),
280                                           ASCIIToUTF16("joy")));
281  EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(), string16()));
282}
283
284TEST_F(InstantSearchPrerendererTest, CommitQuery) {
285  string16 query = ASCIIToUTF16("flowers");
286  PrerenderSearchQuery(query);
287  InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
288  prerenderer->Commit(query);
289  EXPECT_TRUE(MessageWasSent(ChromeViewMsg_SearchBoxSubmit::ID));
290}
291
292TEST_F(InstantSearchPrerendererTest, CancelPrerenderRequestOnTabChangeEvent) {
293  Init(true, true);
294  EXPECT_NE(static_cast<PrerenderHandle*>(NULL), prerender_handle());
295
296  // Add a new tab to deactivate the current tab.
297  AddTab(browser(), GURL(content::kAboutBlankURL));
298  EXPECT_EQ(2, browser()->tab_strip_model()->count());
299
300  // Make sure the pending prerender request is cancelled.
301  EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
302}
303
304TEST_F(InstantSearchPrerendererTest, CancelPendingPrerenderRequest) {
305  Init(true, true);
306  EXPECT_NE(static_cast<PrerenderHandle*>(NULL), prerender_handle());
307
308  InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
309  prerenderer->Cancel();
310  EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
311}
312
313TEST_F(InstantSearchPrerendererTest, PrerenderingAllowed) {
314  Init(true, true);
315  InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
316  content::WebContents* active_tab = GetActiveWebContents();
317  EXPECT_EQ(GURL(content::kAboutBlankURL), active_tab->GetURL());
318
319  // Allow prerendering only for search type AutocompleteMatch suggestions.
320  AutocompleteMatch search_type_match(NULL, 1100, false,
321                                      AutocompleteMatchType::SEARCH_SUGGEST);
322  EXPECT_TRUE(AutocompleteMatch::IsSearchType(search_type_match.type));
323  EXPECT_TRUE(prerenderer->IsAllowed(search_type_match, active_tab));
324
325  AutocompleteMatch url_type_match(NULL, 1100, true,
326                                   AutocompleteMatchType::URL_WHAT_YOU_TYPED);
327  EXPECT_FALSE(AutocompleteMatch::IsSearchType(url_type_match.type));
328  EXPECT_FALSE(prerenderer->IsAllowed(url_type_match, active_tab));
329
330  // Search results page supports Instant search. InstantSearchPrerenderer is
331  // used only when the underlying page doesn't support Instant.
332  NavigateAndCommitActiveTab(GURL("https://www.google.com/alt#quux=foo&strk"));
333  active_tab = GetActiveWebContents();
334  EXPECT_FALSE(chrome::GetSearchTermsFromURL(profile(), active_tab->GetURL())
335      .empty());
336  EXPECT_FALSE(chrome::ShouldPrefetchSearchResultsOnSRP());
337  EXPECT_FALSE(prerenderer->IsAllowed(search_type_match, active_tab));
338}
339
340TEST_F(InstantSearchPrerendererTest, UsePrerenderPage) {
341  PrerenderSearchQuery(ASCIIToUTF16("foo"));
342
343  // Open a search results page. A prerendered page exists for |url|. Make sure
344  // the browser swaps the current tab contents with the prerendered contents.
345  GURL url("https://www.google.com/alt#quux=foo&strk");
346  browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
347                                            content::PAGE_TRANSITION_TYPED,
348                                            false));
349  EXPECT_EQ(GetPrerenderURL(), GetActiveWebContents()->GetURL());
350  EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
351}
352
353TEST_F(InstantSearchPrerendererTest, PrerenderRequestCancelled) {
354  PrerenderSearchQuery(ASCIIToUTF16("foo"));
355
356  // Cancel the prerender request.
357  InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
358  prerenderer->Cancel();
359  EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
360
361  // Open a search results page. Prerendered page does not exists for |url|.
362  // Make sure the browser navigates the current tab to this |url|.
363  GURL url("https://www.google.com/alt#quux=foo&strk");
364  browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
365                                            content::PAGE_TRANSITION_TYPED,
366                                            false));
367  EXPECT_NE(GetPrerenderURL(), GetActiveWebContents()->GetURL());
368  EXPECT_EQ(url, GetActiveWebContents()->GetURL());
369}
370
371TEST_F(InstantSearchPrerendererTest,
372       CancelPrerenderRequest_SearchQueryMistmatch) {
373  PrerenderSearchQuery(ASCIIToUTF16("foo"));
374
375  // Open a search results page. Committed query("pen") doesn't match with the
376  // prerendered search query("foo"). Make sure the InstantSearchPrerenderer
377  // cancels the active prerender request and the browser navigates the active
378  // tab to this |url|.
379  GURL url("https://www.google.com/alt#quux=pen&strk");
380  browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
381                                            content::PAGE_TRANSITION_TYPED,
382                                            false));
383  EXPECT_NE(GetPrerenderURL(), GetActiveWebContents()->GetURL());
384  EXPECT_EQ(url, GetActiveWebContents()->GetURL());
385  EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
386}
387
388TEST_F(InstantSearchPrerendererTest,
389       CancelPrerenderRequest_EmptySearchQueryCommitted) {
390  PrerenderSearchQuery(ASCIIToUTF16("foo"));
391
392  // Open a search results page. Make sure the InstantSearchPrerenderer cancels
393  // the active prerender request upon the receipt of empty search query.
394  GURL url("https://www.google.com/alt#quux=&strk");
395  browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
396                                            content::PAGE_TRANSITION_TYPED,
397                                            false));
398  EXPECT_NE(GetPrerenderURL(), GetActiveWebContents()->GetURL());
399  EXPECT_EQ(url, GetActiveWebContents()->GetURL());
400  EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
401}
402