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.
5 #include "chrome/browser/ui/search/instant_search_prerenderer.h"
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"
36 using base::ASCIIToUTF16;
40 using content::Referrer;
41 using prerender::Origin;
42 using prerender::PrerenderContents;
43 using prerender::PrerenderHandle;
44 using prerender::PrerenderManager;
45 using prerender::PrerenderManagerFactory;
46 using prerender::PrerenderTabHelper;
48 class DummyPrerenderContents : public PrerenderContents {
50 DummyPrerenderContents(
51 PrerenderManager* prerender_manager,
54 const Referrer& referrer,
56 bool call_did_finish_load,
57 const content::SessionStorageNamespaceMap& session_storage_namespace_map);
59 virtual void StartPrerendering(
60 int ALLOW_UNUSED creator_child_id,
61 const gfx::Size& ALLOW_UNUSED size,
62 content::SessionStorageNamespace* session_storage_namespace) OVERRIDE;
63 virtual bool GetChildId(int* child_id) const OVERRIDE;
64 virtual bool GetRouteId(int* route_id) const OVERRIDE;
69 bool call_did_finish_load_;
70 content::SessionStorageNamespaceMap session_storage_namespace_map_;
72 DISALLOW_COPY_AND_ASSIGN(DummyPrerenderContents);
75 class DummyPrerenderContentsFactory : public PrerenderContents::Factory {
77 DummyPrerenderContentsFactory(
78 bool call_did_finish_load,
79 const content::SessionStorageNamespaceMap& session_storage_namespace_map)
80 : call_did_finish_load_(call_did_finish_load),
81 session_storage_namespace_map_(session_storage_namespace_map) {
84 virtual PrerenderContents* CreatePrerenderContents(
85 PrerenderManager* prerender_manager,
88 const Referrer& referrer,
90 uint8 experiment_id) OVERRIDE;
93 bool call_did_finish_load_;
94 content::SessionStorageNamespaceMap session_storage_namespace_map_;
96 DISALLOW_COPY_AND_ASSIGN(DummyPrerenderContentsFactory);
99 DummyPrerenderContents::DummyPrerenderContents(
100 PrerenderManager* prerender_manager,
103 const Referrer& referrer,
105 bool call_did_finish_load,
106 const content::SessionStorageNamespaceMap& session_storage_namespace_map)
107 : PrerenderContents(prerender_manager, profile, url, referrer, origin,
108 PrerenderManager::kNoExperiment),
111 call_did_finish_load_(call_did_finish_load),
112 session_storage_namespace_map_(session_storage_namespace_map) {
115 void DummyPrerenderContents::StartPrerendering(
116 int ALLOW_UNUSED creator_child_id,
117 const gfx::Size& ALLOW_UNUSED size,
118 content::SessionStorageNamespace* session_storage_namespace) {
119 prerender_contents_.reset(content::WebContents::CreateWithSessionStorage(
120 content::WebContents::CreateParams(profile_),
121 session_storage_namespace_map_));
122 PrerenderTabHelper::CreateForWebContentsWithPasswordManager(
123 prerender_contents_.get(), NULL);
124 content::NavigationController::LoadURLParams params(url_);
125 prerender_contents_->GetController().LoadURLWithParams(params);
126 SearchTabHelper::CreateForWebContents(prerender_contents_.get());
128 prerendering_has_started_ = true;
129 DCHECK(session_storage_namespace);
130 session_storage_namespace_id_ = session_storage_namespace->id();
131 NotifyPrerenderStart();
133 if (call_did_finish_load_)
134 DidFinishLoad(1, url_, true, NULL);
137 bool DummyPrerenderContents::GetChildId(int* child_id) const {
142 bool DummyPrerenderContents::GetRouteId(int* route_id) const {
147 PrerenderContents* DummyPrerenderContentsFactory::CreatePrerenderContents(
148 PrerenderManager* prerender_manager,
151 const Referrer& referrer,
153 uint8 experiment_id) {
154 return new DummyPrerenderContents(prerender_manager, profile, url, referrer,
155 origin, call_did_finish_load_,
156 session_storage_namespace_map_);
161 class InstantSearchPrerendererTest : public InstantUnitTestBase {
163 InstantSearchPrerendererTest() {}
166 virtual void SetUp() OVERRIDE {
167 ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
168 "EmbeddedSearch", "Group1 strk:20 prefetch_results:1"));
169 InstantUnitTestBase::SetUp();
172 void Init(bool prerender_search_results_base_page,
173 bool call_did_finish_load) {
174 AddTab(browser(), GURL(content::kAboutBlankURL));
176 content::SessionStorageNamespaceMap session_storage_namespace_map;
177 session_storage_namespace_map[std::string()] =
178 GetActiveWebContents()->GetController().
179 GetDefaultSessionStorageNamespace();
180 PrerenderManagerFactory::GetForProfile(browser()->profile())->
181 SetPrerenderContentsFactory(
182 new DummyPrerenderContentsFactory(call_did_finish_load,
183 session_storage_namespace_map));
185 if (prerender_search_results_base_page) {
186 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
187 prerenderer->Init(session_storage_namespace_map, gfx::Size(640, 480));
188 EXPECT_NE(static_cast<PrerenderHandle*>(NULL), prerender_handle());
192 InstantSearchPrerenderer* GetInstantSearchPrerenderer() {
193 return instant_service_->instant_search_prerenderer();
196 const GURL& GetPrerenderURL() {
197 return GetInstantSearchPrerenderer()->prerender_url_;
200 void SetLastQuery(const base::string16& query) {
201 GetInstantSearchPrerenderer()->last_instant_suggestion_ =
202 InstantSuggestion(query, std::string());
205 content::WebContents* prerender_contents() {
206 return GetInstantSearchPrerenderer()->prerender_contents();
209 bool MessageWasSent(uint32 id) {
210 content::MockRenderProcessHost* process =
211 static_cast<content::MockRenderProcessHost*>(
212 prerender_contents()->GetRenderViewHost()->GetProcess());
213 return process->sink().GetFirstMessageMatching(id) != NULL;
216 content::WebContents* GetActiveWebContents() const {
217 return browser()->tab_strip_model()->GetWebContentsAt(0);
220 PrerenderHandle* prerender_handle() {
221 return GetInstantSearchPrerenderer()->prerender_handle_.get();
224 void PrerenderSearchQuery(const base::string16& query) {
226 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
227 prerenderer->Prerender(InstantSuggestion(query, std::string()));
228 CommitPendingLoad(&prerender_contents()->GetController());
229 EXPECT_TRUE(prerenderer->CanCommitQuery(GetActiveWebContents(), query));
230 EXPECT_NE(static_cast<PrerenderHandle*>(NULL), prerender_handle());
234 TEST_F(InstantSearchPrerendererTest, GetSearchTermsFromPrerenderedPage) {
236 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
237 GURL url(GetPrerenderURL());
238 EXPECT_EQ(GURL("https://www.google.com/instant?ion=1&foo=foo#foo=foo&strk"),
240 EXPECT_EQ(UTF16ToASCII(prerenderer->get_last_query()),
241 UTF16ToASCII(chrome::ExtractSearchTermsFromURL(profile(), url)));
243 // Assume the prerendered page prefetched search results for the query
245 SetLastQuery(ASCIIToUTF16("flowers"));
246 EXPECT_EQ("flowers", UTF16ToASCII(prerenderer->get_last_query()));
247 EXPECT_EQ(UTF16ToASCII(prerenderer->get_last_query()),
248 UTF16ToASCII(chrome::ExtractSearchTermsFromURL(profile(), url)));
251 TEST_F(InstantSearchPrerendererTest, PrefetchSearchResults) {
253 EXPECT_TRUE(prerender_handle()->IsFinishedLoading());
254 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
255 prerenderer->Prerender(
256 InstantSuggestion(ASCIIToUTF16("flowers"), std::string()));
257 EXPECT_EQ("flowers", UTF16ToASCII(prerenderer->get_last_query()));
258 EXPECT_TRUE(MessageWasSent(
259 ChromeViewMsg_SearchBoxSetSuggestionToPrefetch::ID));
262 TEST_F(InstantSearchPrerendererTest, DoNotPrefetchSearchResults) {
264 // Page hasn't finished loading yet.
265 EXPECT_FALSE(prerender_handle()->IsFinishedLoading());
266 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
267 prerenderer->Prerender(
268 InstantSuggestion(ASCIIToUTF16("flowers"), std::string()));
269 EXPECT_EQ("", UTF16ToASCII(prerenderer->get_last_query()));
270 EXPECT_FALSE(MessageWasSent(
271 ChromeViewMsg_SearchBoxSetSuggestionToPrefetch::ID));
274 TEST_F(InstantSearchPrerendererTest, CanCommitQuery) {
276 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
277 base::string16 query = ASCIIToUTF16("flowers");
278 prerenderer->Prerender(InstantSuggestion(query, std::string()));
279 EXPECT_TRUE(prerenderer->CanCommitQuery(GetActiveWebContents(), query));
281 // Make sure InstantSearchPrerenderer::CanCommitQuery() returns false for
282 // invalid search queries.
283 EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(),
284 ASCIIToUTF16("joy")));
285 EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(),
289 TEST_F(InstantSearchPrerendererTest, CommitQuery) {
290 base::string16 query = ASCIIToUTF16("flowers");
291 PrerenderSearchQuery(query);
292 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
293 prerenderer->Commit(query);
294 EXPECT_TRUE(MessageWasSent(ChromeViewMsg_SearchBoxSubmit::ID));
297 TEST_F(InstantSearchPrerendererTest, CancelPrerenderRequestOnTabChangeEvent) {
299 EXPECT_NE(static_cast<PrerenderHandle*>(NULL), prerender_handle());
301 // Add a new tab to deactivate the current tab.
302 AddTab(browser(), GURL(content::kAboutBlankURL));
303 EXPECT_EQ(2, browser()->tab_strip_model()->count());
305 // Make sure the pending prerender request is cancelled.
306 EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
309 TEST_F(InstantSearchPrerendererTest, CancelPendingPrerenderRequest) {
311 EXPECT_NE(static_cast<PrerenderHandle*>(NULL), prerender_handle());
313 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
314 prerenderer->Cancel();
315 EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
318 TEST_F(InstantSearchPrerendererTest, PrerenderingAllowed) {
320 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
321 content::WebContents* active_tab = GetActiveWebContents();
322 EXPECT_EQ(GURL(content::kAboutBlankURL), active_tab->GetURL());
324 // Allow prerendering only for search type AutocompleteMatch suggestions.
325 AutocompleteMatch search_type_match(NULL, 1100, false,
326 AutocompleteMatchType::SEARCH_SUGGEST);
327 EXPECT_TRUE(AutocompleteMatch::IsSearchType(search_type_match.type));
328 EXPECT_TRUE(prerenderer->IsAllowed(search_type_match, active_tab));
330 AutocompleteMatch url_type_match(NULL, 1100, true,
331 AutocompleteMatchType::URL_WHAT_YOU_TYPED);
332 EXPECT_FALSE(AutocompleteMatch::IsSearchType(url_type_match.type));
333 EXPECT_FALSE(prerenderer->IsAllowed(url_type_match, active_tab));
335 // Search results page supports Instant search. InstantSearchPrerenderer is
336 // used only when the underlying page doesn't support Instant.
337 NavigateAndCommitActiveTab(GURL("https://www.google.com/alt#quux=foo&strk"));
338 active_tab = GetActiveWebContents();
339 EXPECT_FALSE(chrome::ExtractSearchTermsFromURL(profile(),
340 active_tab->GetURL()).empty());
341 EXPECT_FALSE(chrome::ShouldPrefetchSearchResultsOnSRP());
342 EXPECT_FALSE(prerenderer->IsAllowed(search_type_match, active_tab));
345 TEST_F(InstantSearchPrerendererTest, UsePrerenderPage) {
346 PrerenderSearchQuery(ASCIIToUTF16("foo"));
348 // Open a search results page. A prerendered page exists for |url|. Make sure
349 // the browser swaps the current tab contents with the prerendered contents.
350 GURL url("https://www.google.com/alt#quux=foo&strk");
351 browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
352 content::PAGE_TRANSITION_TYPED,
354 EXPECT_EQ(GetPrerenderURL(), GetActiveWebContents()->GetURL());
355 EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
358 TEST_F(InstantSearchPrerendererTest, PrerenderRequestCancelled) {
359 PrerenderSearchQuery(ASCIIToUTF16("foo"));
361 // Cancel the prerender request.
362 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
363 prerenderer->Cancel();
364 EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
366 // Open a search results page. Prerendered page does not exists for |url|.
367 // Make sure the browser navigates the current tab to this |url|.
368 GURL url("https://www.google.com/alt#quux=foo&strk");
369 browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
370 content::PAGE_TRANSITION_TYPED,
372 EXPECT_NE(GetPrerenderURL(), GetActiveWebContents()->GetURL());
373 EXPECT_EQ(url, GetActiveWebContents()->GetURL());
376 TEST_F(InstantSearchPrerendererTest,
377 CancelPrerenderRequest_SearchQueryMistmatch) {
378 PrerenderSearchQuery(ASCIIToUTF16("foo"));
380 // Open a search results page. Committed query("pen") doesn't match with the
381 // prerendered search query("foo"). Make sure the InstantSearchPrerenderer
382 // cancels the active prerender request and the browser navigates the active
383 // tab to this |url|.
384 GURL url("https://www.google.com/alt#quux=pen&strk");
385 browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
386 content::PAGE_TRANSITION_TYPED,
388 EXPECT_NE(GetPrerenderURL(), GetActiveWebContents()->GetURL());
389 EXPECT_EQ(url, GetActiveWebContents()->GetURL());
390 EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
393 TEST_F(InstantSearchPrerendererTest,
394 CancelPrerenderRequest_EmptySearchQueryCommitted) {
395 PrerenderSearchQuery(ASCIIToUTF16("foo"));
397 // Open a search results page. Make sure the InstantSearchPrerenderer cancels
398 // the active prerender request upon the receipt of empty search query.
399 GURL url("https://www.google.com/alt#quux=&strk");
400 browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
401 content::PAGE_TRANSITION_TYPED,
403 EXPECT_NE(GetPrerenderURL(), GetActiveWebContents()->GetURL());
404 EXPECT_EQ(url, GetActiveWebContents()->GetURL());
405 EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
408 class ReuseInstantSearchBasePageTest : public InstantSearchPrerendererTest {
410 ReuseInstantSearchBasePageTest() {}
413 virtual void SetUp() OVERRIDE {
414 ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
416 "Group1 strk:20 prefetch_results:1 reuse_instant_search_base_page:1"));
417 InstantUnitTestBase::SetUp();
421 TEST_F(ReuseInstantSearchBasePageTest, CanCommitQuery) {
423 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
424 base::string16 query = ASCIIToUTF16("flowers");
425 prerenderer->Prerender(InstantSuggestion(query, std::string()));
426 EXPECT_TRUE(prerenderer->CanCommitQuery(GetActiveWebContents(), query));
428 // When the Instant search base page has finished loading,
429 // InstantSearchPrerenderer can commit any search query to the prerendered
430 // page (even if it doesn't match the last known suggestion query).
431 EXPECT_TRUE(prerenderer->CanCommitQuery(GetActiveWebContents(),
432 ASCIIToUTF16("joy")));
433 // Invalid search query committed.
434 EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(),
438 TEST_F(ReuseInstantSearchBasePageTest,
439 CanCommitQuery_InstantSearchBasePageLoadInProgress) {
441 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
442 base::string16 query = ASCIIToUTF16("flowers");
443 prerenderer->Prerender(InstantSuggestion(query, std::string()));
445 // When the Instant search base page hasn't finished loading,
446 // InstantSearchPrerenderer cannot commit any search query to the base page.
447 EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(), query));
448 EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(),
449 ASCIIToUTF16("joy")));
452 #if !defined(OS_IOS) && !defined(OS_ANDROID)
453 class TestUsePrerenderPage : public InstantSearchPrerendererTest {
455 virtual void SetUp() OVERRIDE {
456 // Disable query extraction flag in field trials.
457 ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
459 "Group1 strk:20 query_extraction:0 prefetch_results:1"));
460 InstantUnitTestBase::SetUpWithoutQueryExtraction();
464 TEST_F(TestUsePrerenderPage, ExtractSearchTermsAndUsePrerenderPage) {
465 PrerenderSearchQuery(ASCIIToUTF16("foo"));
467 // Open a search results page. Query extraction flag is disabled in field
468 // trials. Search results page URL does not contain search terms replacement
469 // key. Make sure UsePrerenderedPage() extracts the search terms from the URL
470 // and uses the prerendered page contents.
471 GURL url("https://www.google.com/alt#quux=foo");
472 browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
473 content::PAGE_TRANSITION_TYPED,
475 EXPECT_EQ(GetPrerenderURL(), GetActiveWebContents()->GetURL());
476 EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());