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/prerender/prerender_contents.h"
14#include "chrome/browser/prerender/prerender_handle.h"
15#include "chrome/browser/prerender/prerender_manager.h"
16#include "chrome/browser/prerender/prerender_manager_factory.h"
17#include "chrome/browser/prerender/prerender_origin.h"
18#include "chrome/browser/prerender/prerender_tab_helper.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 "components/omnibox/autocomplete_match.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(prerender_contents_->GetMainFrame(), url_);
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("EmbeddedSearch",
170                                                       "Group1 strk:20"));
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_TRUE(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                                            ui::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                                            ui::PAGE_TRANSITION_TYPED,
376                                            false));
377  EXPECT_NE(GetPrerenderURL(), GetActiveWebContents()->GetURL());
378  EXPECT_EQ(url, GetActiveWebContents()->GetURL());
379}
380
381TEST_F(InstantSearchPrerendererTest,
382       UsePrerenderedPage_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 browser swaps the current
387  // tab contents with the prerendered contents.
388  GURL url("https://www.google.com/alt#quux=pen&strk");
389  browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
390                                            ui::PAGE_TRANSITION_TYPED,
391                                            false));
392  EXPECT_EQ(GetPrerenderURL(), GetActiveWebContents()->GetURL());
393  EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
394}
395
396TEST_F(InstantSearchPrerendererTest,
397       CancelPrerenderRequest_EmptySearchQueryCommitted) {
398  PrerenderSearchQuery(ASCIIToUTF16("foo"));
399
400  // Open a search results page. Make sure the InstantSearchPrerenderer cancels
401  // the active prerender request upon the receipt of empty search query.
402  GURL url("https://www.google.com/alt#quux=&strk");
403  browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
404                                            ui::PAGE_TRANSITION_TYPED,
405                                            false));
406  EXPECT_NE(GetPrerenderURL(), GetActiveWebContents()->GetURL());
407  EXPECT_EQ(url, GetActiveWebContents()->GetURL());
408  EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
409}
410
411TEST_F(InstantSearchPrerendererTest,
412       CancelPrerenderRequest_UnsupportedDispositions) {
413  PrerenderSearchQuery(ASCIIToUTF16("pen"));
414
415  // Open a search results page. Make sure the InstantSearchPrerenderer cancels
416  // the active prerender request for unsupported window dispositions.
417  GURL url("https://www.google.com/alt#quux=pen&strk");
418  browser()->OpenURL(content::OpenURLParams(url, Referrer(), NEW_FOREGROUND_TAB,
419                                            ui::PAGE_TRANSITION_TYPED,
420                                            false));
421  content::WebContents* new_tab =
422      browser()->tab_strip_model()->GetWebContentsAt(1);
423  EXPECT_NE(GetPrerenderURL(), new_tab->GetURL());
424  EXPECT_EQ(url, new_tab->GetURL());
425  EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
426}
427
428class ReuseInstantSearchBasePageTest : public InstantSearchPrerendererTest {
429  public:
430   ReuseInstantSearchBasePageTest() {}
431
432  protected:
433   virtual void SetUp() OVERRIDE {
434    ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial("EmbeddedSearch",
435                                                       "Group1 strk:20"));
436    InstantUnitTestBase::SetUp();
437   }
438};
439
440TEST_F(ReuseInstantSearchBasePageTest, CanCommitQuery) {
441  Init(true, true);
442  InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
443  base::string16 query = ASCIIToUTF16("flowers");
444  prerenderer->Prerender(InstantSuggestion(query, std::string()));
445  EXPECT_TRUE(prerenderer->CanCommitQuery(GetActiveWebContents(), query));
446
447  // When the Instant search base page has finished loading,
448  // InstantSearchPrerenderer can commit any search query to the prerendered
449  // page (even if it doesn't match the last known suggestion query).
450  EXPECT_TRUE(prerenderer->CanCommitQuery(GetActiveWebContents(),
451                                           ASCIIToUTF16("joy")));
452  // Invalid search query committed.
453  EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(),
454                                           base::string16()));
455}
456
457TEST_F(ReuseInstantSearchBasePageTest,
458       CanCommitQuery_InstantSearchBasePageLoadInProgress) {
459  Init(true, false);
460  InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
461  base::string16 query = ASCIIToUTF16("flowers");
462  prerenderer->Prerender(InstantSuggestion(query, std::string()));
463
464  // When the Instant search base page hasn't finished loading,
465  // InstantSearchPrerenderer cannot commit any search query to the base page.
466  EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(), query));
467  EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(),
468                                           ASCIIToUTF16("joy")));
469}
470
471#if !defined(OS_IOS) && !defined(OS_ANDROID)
472class TestUsePrerenderPage : public InstantSearchPrerendererTest {
473 protected:
474  virtual void SetUp() OVERRIDE {
475    // Disable query extraction flag in field trials.
476    ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
477        "EmbeddedSearch", "Group1 strk:20 query_extraction:0"));
478    InstantUnitTestBase::SetUpWithoutQueryExtraction();
479  }
480};
481
482TEST_F(TestUsePrerenderPage, ExtractSearchTermsAndUsePrerenderPage) {
483  PrerenderSearchQuery(ASCIIToUTF16("foo"));
484
485  // Open a search results page. Query extraction flag is disabled in field
486  // trials. Search results page URL does not contain search terms replacement
487  // key. Make sure UsePrerenderedPage() extracts the search terms from the URL
488  // and uses the prerendered page contents.
489  GURL url("https://www.google.com/alt#quux=foo");
490  browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
491                                            ui::PAGE_TRANSITION_TYPED,
492                                            false));
493  EXPECT_EQ(GetPrerenderURL(), GetActiveWebContents()->GetURL());
494  EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
495}
496#endif
497