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_tab_helper.h"
20#include "chrome/browser/prerender/prerender_tracker.h"
21#include "chrome/browser/profiles/profile.h"
22#include "chrome/browser/search/instant_service.h"
23#include "chrome/browser/search/instant_unittest_base.h"
24#include "chrome/browser/search/search.h"
25#include "chrome/browser/ui/search/search_tab_helper.h"
26#include "chrome/browser/ui/tabs/tab_strip_model.h"
27#include "chrome/common/render_messages.h"
28#include "content/public/browser/navigation_controller.h"
29#include "content/public/browser/web_contents.h"
30#include "content/public/common/url_constants.h"
31#include "content/public/test/mock_render_process_host.h"
32#include "ipc/ipc_message.h"
33#include "ipc/ipc_test_sink.h"
34#include "ui/gfx/size.h"
35
36using base::ASCIIToUTF16;
37
38namespace {
39
40using content::Referrer;
41using prerender::Origin;
42using prerender::PrerenderContents;
43using prerender::PrerenderHandle;
44using prerender::PrerenderManager;
45using prerender::PrerenderManagerFactory;
46using prerender::PrerenderTabHelper;
47
48class DummyPrerenderContents : public PrerenderContents {
49 public:
50  DummyPrerenderContents(
51      PrerenderManager* prerender_manager,
52      Profile* profile,
53      const GURL& url,
54      const Referrer& referrer,
55      Origin origin,
56      bool call_did_finish_load,
57      const content::SessionStorageNamespaceMap& session_storage_namespace_map);
58
59  virtual void StartPrerendering(
60      int ALLOW_UNUSED creator_child_id,
61      const gfx::Size& ALLOW_UNUSED size,
62      content::SessionStorageNamespace* session_storage_namespace,
63      net::URLRequestContextGetter* request_context) OVERRIDE;
64  virtual bool GetChildId(int* child_id) const OVERRIDE;
65  virtual bool GetRouteId(int* route_id) const OVERRIDE;
66
67 private:
68  Profile* profile_;
69  const GURL url_;
70  bool call_did_finish_load_;
71  content::SessionStorageNamespaceMap session_storage_namespace_map_;
72
73  DISALLOW_COPY_AND_ASSIGN(DummyPrerenderContents);
74};
75
76class DummyPrerenderContentsFactory : public PrerenderContents::Factory {
77 public:
78  DummyPrerenderContentsFactory(
79      bool call_did_finish_load,
80      const content::SessionStorageNamespaceMap& session_storage_namespace_map)
81      : call_did_finish_load_(call_did_finish_load),
82        session_storage_namespace_map_(session_storage_namespace_map) {
83  }
84
85  virtual PrerenderContents* CreatePrerenderContents(
86      PrerenderManager* prerender_manager,
87      Profile* profile,
88      const GURL& url,
89      const Referrer& referrer,
90      Origin origin,
91      uint8 experiment_id) OVERRIDE;
92
93 private:
94  bool call_did_finish_load_;
95  content::SessionStorageNamespaceMap session_storage_namespace_map_;
96
97  DISALLOW_COPY_AND_ASSIGN(DummyPrerenderContentsFactory);
98};
99
100DummyPrerenderContents::DummyPrerenderContents(
101    PrerenderManager* prerender_manager,
102    Profile* profile,
103    const GURL& url,
104    const Referrer& referrer,
105    Origin origin,
106    bool call_did_finish_load,
107    const content::SessionStorageNamespaceMap& session_storage_namespace_map)
108    : PrerenderContents(prerender_manager, profile, url, referrer, origin,
109                        PrerenderManager::kNoExperiment),
110      profile_(profile),
111      url_(url),
112      call_did_finish_load_(call_did_finish_load),
113      session_storage_namespace_map_(session_storage_namespace_map) {
114}
115
116void DummyPrerenderContents::StartPrerendering(
117    int ALLOW_UNUSED creator_child_id,
118    const gfx::Size& ALLOW_UNUSED size,
119    content::SessionStorageNamespace* session_storage_namespace,
120    net::URLRequestContextGetter* request_context) {
121  prerender_contents_.reset(content::WebContents::CreateWithSessionStorage(
122      content::WebContents::CreateParams(profile_),
123      session_storage_namespace_map_));
124  PrerenderTabHelper::CreateForWebContentsWithPasswordManager(
125      prerender_contents_.get(), NULL);
126  content::NavigationController::LoadURLParams params(url_);
127  prerender_contents_->GetController().LoadURLWithParams(params);
128  SearchTabHelper::CreateForWebContents(prerender_contents_.get());
129
130  prerendering_has_started_ = true;
131  DCHECK(session_storage_namespace);
132  session_storage_namespace_id_ = session_storage_namespace->id();
133  NotifyPrerenderStart();
134
135  if (call_did_finish_load_)
136    DidFinishLoad(1, url_, true, NULL);
137}
138
139bool DummyPrerenderContents::GetChildId(int* child_id) const {
140  *child_id = 1;
141  return true;
142}
143
144bool DummyPrerenderContents::GetRouteId(int* route_id) const {
145  *route_id = 1;
146  return true;
147}
148
149PrerenderContents* DummyPrerenderContentsFactory::CreatePrerenderContents(
150    PrerenderManager* prerender_manager,
151    Profile* profile,
152    const GURL& url,
153    const Referrer& referrer,
154    Origin origin,
155    uint8 experiment_id) {
156  return new DummyPrerenderContents(prerender_manager, profile, url, referrer,
157                                    origin, call_did_finish_load_,
158                                    session_storage_namespace_map_);
159}
160
161}  // namespace
162
163class InstantSearchPrerendererTest : public InstantUnitTestBase {
164 public:
165  InstantSearchPrerendererTest() {}
166
167 protected:
168  virtual void SetUp() OVERRIDE {
169    ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
170        "EmbeddedSearch", "Group1 strk:20 prefetch_results:1"));
171    InstantUnitTestBase::SetUp();
172  }
173
174  void Init(bool prerender_search_results_base_page,
175            bool call_did_finish_load) {
176    AddTab(browser(), GURL(url::kAboutBlankURL));
177
178    content::SessionStorageNamespaceMap session_storage_namespace_map;
179    session_storage_namespace_map[std::string()] =
180        GetActiveWebContents()->GetController().
181            GetDefaultSessionStorageNamespace();
182    PrerenderManagerFactory::GetForProfile(browser()->profile())->
183        SetPrerenderContentsFactory(
184            new DummyPrerenderContentsFactory(call_did_finish_load,
185                                              session_storage_namespace_map));
186    PrerenderManagerFactory::GetForProfile(browser()->profile())->
187        OnCookieStoreLoaded();
188    if (prerender_search_results_base_page) {
189      InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
190      prerenderer->Init(session_storage_namespace_map, gfx::Size(640, 480));
191      EXPECT_NE(static_cast<PrerenderHandle*>(NULL), prerender_handle());
192    }
193  }
194
195  InstantSearchPrerenderer* GetInstantSearchPrerenderer() {
196    return instant_service_->instant_search_prerenderer();
197  }
198
199  const GURL& GetPrerenderURL() {
200    return GetInstantSearchPrerenderer()->prerender_url_;
201  }
202
203  void SetLastQuery(const base::string16& query) {
204    GetInstantSearchPrerenderer()->last_instant_suggestion_ =
205        InstantSuggestion(query, std::string());
206  }
207
208  content::WebContents* prerender_contents() {
209    return GetInstantSearchPrerenderer()->prerender_contents();
210  }
211
212  bool MessageWasSent(uint32 id) {
213    content::MockRenderProcessHost* process =
214        static_cast<content::MockRenderProcessHost*>(
215            prerender_contents()->GetRenderViewHost()->GetProcess());
216    return process->sink().GetFirstMessageMatching(id) != NULL;
217  }
218
219  content::WebContents* GetActiveWebContents() const {
220    return browser()->tab_strip_model()->GetWebContentsAt(0);
221  }
222
223  PrerenderHandle* prerender_handle() {
224    return GetInstantSearchPrerenderer()->prerender_handle_.get();
225  }
226
227  void PrerenderSearchQuery(const base::string16& query) {
228    Init(true, true);
229    InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
230    prerenderer->Prerender(InstantSuggestion(query, std::string()));
231    CommitPendingLoad(&prerender_contents()->GetController());
232    EXPECT_TRUE(prerenderer->CanCommitQuery(GetActiveWebContents(), query));
233    EXPECT_NE(static_cast<PrerenderHandle*>(NULL), prerender_handle());
234  }
235};
236
237TEST_F(InstantSearchPrerendererTest, GetSearchTermsFromPrerenderedPage) {
238  Init(false, false);
239  InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
240  GURL url(GetPrerenderURL());
241  EXPECT_EQ(GURL("https://www.google.com/instant?ion=1&foo=foo#foo=foo&strk"),
242            url);
243  EXPECT_EQ(base::UTF16ToASCII(prerenderer->get_last_query()),
244            base::UTF16ToASCII(
245                chrome::ExtractSearchTermsFromURL(profile(), url)));
246
247  // Assume the prerendered page prefetched search results for the query
248  // "flowers".
249  SetLastQuery(ASCIIToUTF16("flowers"));
250  EXPECT_EQ("flowers", base::UTF16ToASCII(prerenderer->get_last_query()));
251  EXPECT_EQ(base::UTF16ToASCII(prerenderer->get_last_query()),
252            base::UTF16ToASCII(
253                chrome::ExtractSearchTermsFromURL(profile(), url)));
254}
255
256TEST_F(InstantSearchPrerendererTest, PrefetchSearchResults) {
257  Init(true, true);
258  EXPECT_TRUE(prerender_handle()->IsFinishedLoading());
259  InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
260  prerenderer->Prerender(
261      InstantSuggestion(ASCIIToUTF16("flowers"), std::string()));
262  EXPECT_EQ("flowers", base::UTF16ToASCII(prerenderer->get_last_query()));
263  EXPECT_TRUE(MessageWasSent(
264      ChromeViewMsg_SearchBoxSetSuggestionToPrefetch::ID));
265}
266
267TEST_F(InstantSearchPrerendererTest, DoNotPrefetchSearchResults) {
268  Init(true, false);
269  // Page hasn't finished loading yet.
270  EXPECT_FALSE(prerender_handle()->IsFinishedLoading());
271  InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
272  prerenderer->Prerender(
273      InstantSuggestion(ASCIIToUTF16("flowers"), std::string()));
274  EXPECT_EQ("", base::UTF16ToASCII(prerenderer->get_last_query()));
275  EXPECT_FALSE(MessageWasSent(
276      ChromeViewMsg_SearchBoxSetSuggestionToPrefetch::ID));
277}
278
279TEST_F(InstantSearchPrerendererTest, CanCommitQuery) {
280  Init(true, true);
281  InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
282  base::string16 query = ASCIIToUTF16("flowers");
283  prerenderer->Prerender(InstantSuggestion(query, std::string()));
284  EXPECT_TRUE(prerenderer->CanCommitQuery(GetActiveWebContents(), query));
285
286  // Make sure InstantSearchPrerenderer::CanCommitQuery() returns false for
287  // invalid search queries.
288  EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(),
289                                           ASCIIToUTF16("joy")));
290  EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(),
291                                           base::string16()));
292}
293
294TEST_F(InstantSearchPrerendererTest, CommitQuery) {
295  base::string16 query = ASCIIToUTF16("flowers");
296  PrerenderSearchQuery(query);
297  InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
298  prerenderer->Commit(query);
299  EXPECT_TRUE(MessageWasSent(ChromeViewMsg_SearchBoxSubmit::ID));
300}
301
302TEST_F(InstantSearchPrerendererTest, CancelPrerenderRequestOnTabChangeEvent) {
303  Init(true, true);
304  EXPECT_NE(static_cast<PrerenderHandle*>(NULL), prerender_handle());
305
306  // Add a new tab to deactivate the current tab.
307  AddTab(browser(), GURL(url::kAboutBlankURL));
308  EXPECT_EQ(2, browser()->tab_strip_model()->count());
309
310  // Make sure the pending prerender request is cancelled.
311  EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
312}
313
314TEST_F(InstantSearchPrerendererTest, CancelPendingPrerenderRequest) {
315  Init(true, true);
316  EXPECT_NE(static_cast<PrerenderHandle*>(NULL), prerender_handle());
317
318  InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
319  prerenderer->Cancel();
320  EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
321}
322
323TEST_F(InstantSearchPrerendererTest, PrerenderingAllowed) {
324  Init(true, true);
325  InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
326  content::WebContents* active_tab = GetActiveWebContents();
327  EXPECT_EQ(GURL(url::kAboutBlankURL), active_tab->GetURL());
328
329  // Allow prerendering only for search type AutocompleteMatch suggestions.
330  AutocompleteMatch search_type_match(NULL, 1100, false,
331                                      AutocompleteMatchType::SEARCH_SUGGEST);
332  EXPECT_TRUE(AutocompleteMatch::IsSearchType(search_type_match.type));
333  EXPECT_TRUE(prerenderer->IsAllowed(search_type_match, active_tab));
334
335  AutocompleteMatch url_type_match(NULL, 1100, true,
336                                   AutocompleteMatchType::URL_WHAT_YOU_TYPED);
337  EXPECT_FALSE(AutocompleteMatch::IsSearchType(url_type_match.type));
338  EXPECT_FALSE(prerenderer->IsAllowed(url_type_match, active_tab));
339
340  // Search results page supports Instant search. InstantSearchPrerenderer is
341  // used only when the underlying page doesn't support Instant.
342  NavigateAndCommitActiveTab(GURL("https://www.google.com/alt#quux=foo&strk"));
343  active_tab = GetActiveWebContents();
344  EXPECT_FALSE(chrome::ExtractSearchTermsFromURL(profile(),
345                                                 active_tab->GetURL()).empty());
346  EXPECT_FALSE(chrome::ShouldPrefetchSearchResultsOnSRP());
347  EXPECT_FALSE(prerenderer->IsAllowed(search_type_match, active_tab));
348}
349
350TEST_F(InstantSearchPrerendererTest, UsePrerenderPage) {
351  PrerenderSearchQuery(ASCIIToUTF16("foo"));
352
353  // Open a search results page. A prerendered page exists for |url|. Make sure
354  // the browser swaps the current tab contents with the prerendered contents.
355  GURL url("https://www.google.com/alt#quux=foo&strk");
356  browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
357                                            content::PAGE_TRANSITION_TYPED,
358                                            false));
359  EXPECT_EQ(GetPrerenderURL(), GetActiveWebContents()->GetURL());
360  EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
361}
362
363TEST_F(InstantSearchPrerendererTest, PrerenderRequestCancelled) {
364  PrerenderSearchQuery(ASCIIToUTF16("foo"));
365
366  // Cancel the prerender request.
367  InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
368  prerenderer->Cancel();
369  EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
370
371  // Open a search results page. Prerendered page does not exists for |url|.
372  // Make sure the browser navigates the current tab to this |url|.
373  GURL url("https://www.google.com/alt#quux=foo&strk");
374  browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
375                                            content::PAGE_TRANSITION_TYPED,
376                                            false));
377  EXPECT_NE(GetPrerenderURL(), GetActiveWebContents()->GetURL());
378  EXPECT_EQ(url, GetActiveWebContents()->GetURL());
379}
380
381TEST_F(InstantSearchPrerendererTest,
382       CancelPrerenderRequest_SearchQueryMistmatch) {
383  PrerenderSearchQuery(ASCIIToUTF16("foo"));
384
385  // Open a search results page. Committed query("pen") doesn't match with the
386  // prerendered search query("foo"). Make sure the InstantSearchPrerenderer
387  // cancels the active prerender request and the browser navigates the active
388  // tab to this |url|.
389  GURL url("https://www.google.com/alt#quux=pen&strk");
390  browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
391                                            content::PAGE_TRANSITION_TYPED,
392                                            false));
393  EXPECT_NE(GetPrerenderURL(), GetActiveWebContents()->GetURL());
394  EXPECT_EQ(url, GetActiveWebContents()->GetURL());
395  EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
396}
397
398TEST_F(InstantSearchPrerendererTest,
399       CancelPrerenderRequest_EmptySearchQueryCommitted) {
400  PrerenderSearchQuery(ASCIIToUTF16("foo"));
401
402  // Open a search results page. Make sure the InstantSearchPrerenderer cancels
403  // the active prerender request upon the receipt of empty search query.
404  GURL url("https://www.google.com/alt#quux=&strk");
405  browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
406                                            content::PAGE_TRANSITION_TYPED,
407                                            false));
408  EXPECT_NE(GetPrerenderURL(), GetActiveWebContents()->GetURL());
409  EXPECT_EQ(url, GetActiveWebContents()->GetURL());
410  EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
411}
412
413TEST_F(InstantSearchPrerendererTest,
414       CancelPrerenderRequest_UnsupportedDispositions) {
415  PrerenderSearchQuery(ASCIIToUTF16("pen"));
416
417  // Open a search results page. Make sure the InstantSearchPrerenderer cancels
418  // the active prerender request for unsupported window dispositions.
419  GURL url("https://www.google.com/alt#quux=pen&strk");
420  browser()->OpenURL(content::OpenURLParams(url, Referrer(), NEW_FOREGROUND_TAB,
421                                            content::PAGE_TRANSITION_TYPED,
422                                            false));
423  content::WebContents* new_tab =
424      browser()->tab_strip_model()->GetWebContentsAt(1);
425  EXPECT_NE(GetPrerenderURL(), new_tab->GetURL());
426  EXPECT_EQ(url, new_tab->GetURL());
427  EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
428}
429
430class ReuseInstantSearchBasePageTest : public InstantSearchPrerendererTest {
431  public:
432   ReuseInstantSearchBasePageTest() {}
433
434  protected:
435   virtual void SetUp() OVERRIDE {
436    ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
437        "EmbeddedSearch",
438        "Group1 strk:20 prefetch_results:1 reuse_instant_search_base_page:1"));
439    InstantUnitTestBase::SetUp();
440   }
441};
442
443TEST_F(ReuseInstantSearchBasePageTest, CanCommitQuery) {
444  Init(true, true);
445  InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
446  base::string16 query = ASCIIToUTF16("flowers");
447  prerenderer->Prerender(InstantSuggestion(query, std::string()));
448  EXPECT_TRUE(prerenderer->CanCommitQuery(GetActiveWebContents(), query));
449
450  // When the Instant search base page has finished loading,
451  // InstantSearchPrerenderer can commit any search query to the prerendered
452  // page (even if it doesn't match the last known suggestion query).
453  EXPECT_TRUE(prerenderer->CanCommitQuery(GetActiveWebContents(),
454                                           ASCIIToUTF16("joy")));
455  // Invalid search query committed.
456  EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(),
457                                           base::string16()));
458}
459
460TEST_F(ReuseInstantSearchBasePageTest,
461       CanCommitQuery_InstantSearchBasePageLoadInProgress) {
462  Init(true, false);
463  InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
464  base::string16 query = ASCIIToUTF16("flowers");
465  prerenderer->Prerender(InstantSuggestion(query, std::string()));
466
467  // When the Instant search base page hasn't finished loading,
468  // InstantSearchPrerenderer cannot commit any search query to the base page.
469  EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(), query));
470  EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(),
471                                           ASCIIToUTF16("joy")));
472}
473
474#if !defined(OS_IOS) && !defined(OS_ANDROID)
475class TestUsePrerenderPage : public InstantSearchPrerendererTest {
476 protected:
477  virtual void SetUp() OVERRIDE {
478    // Disable query extraction flag in field trials.
479    ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
480        "EmbeddedSearch",
481        "Group1 strk:20 query_extraction:0 prefetch_results:1"));
482    InstantUnitTestBase::SetUpWithoutQueryExtraction();
483  }
484};
485
486TEST_F(TestUsePrerenderPage, ExtractSearchTermsAndUsePrerenderPage) {
487  PrerenderSearchQuery(ASCIIToUTF16("foo"));
488
489  // Open a search results page. Query extraction flag is disabled in field
490  // trials. Search results page URL does not contain search terms replacement
491  // key. Make sure UsePrerenderedPage() extracts the search terms from the URL
492  // and uses the prerendered page contents.
493  GURL url("https://www.google.com/alt#quux=foo");
494  browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
495                                            content::PAGE_TRANSITION_TYPED,
496                                            false));
497  EXPECT_EQ(GetPrerenderURL(), GetActiveWebContents()->GetURL());
498  EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
499}
500#endif
501