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(base::UTF16ToASCII(prerenderer
->get_last_query()),
242 chrome::ExtractSearchTermsFromURL(profile(), url
)));
244 // Assume the prerendered page prefetched search results for the query
246 SetLastQuery(ASCIIToUTF16("flowers"));
247 EXPECT_EQ("flowers", base::UTF16ToASCII(prerenderer
->get_last_query()));
248 EXPECT_EQ(base::UTF16ToASCII(prerenderer
->get_last_query()),
250 chrome::ExtractSearchTermsFromURL(profile(), url
)));
253 TEST_F(InstantSearchPrerendererTest
, PrefetchSearchResults
) {
255 EXPECT_TRUE(prerender_handle()->IsFinishedLoading());
256 InstantSearchPrerenderer
* prerenderer
= GetInstantSearchPrerenderer();
257 prerenderer
->Prerender(
258 InstantSuggestion(ASCIIToUTF16("flowers"), std::string()));
259 EXPECT_EQ("flowers", base::UTF16ToASCII(prerenderer
->get_last_query()));
260 EXPECT_TRUE(MessageWasSent(
261 ChromeViewMsg_SearchBoxSetSuggestionToPrefetch::ID
));
264 TEST_F(InstantSearchPrerendererTest
, DoNotPrefetchSearchResults
) {
266 // Page hasn't finished loading yet.
267 EXPECT_FALSE(prerender_handle()->IsFinishedLoading());
268 InstantSearchPrerenderer
* prerenderer
= GetInstantSearchPrerenderer();
269 prerenderer
->Prerender(
270 InstantSuggestion(ASCIIToUTF16("flowers"), std::string()));
271 EXPECT_EQ("", base::UTF16ToASCII(prerenderer
->get_last_query()));
272 EXPECT_FALSE(MessageWasSent(
273 ChromeViewMsg_SearchBoxSetSuggestionToPrefetch::ID
));
276 TEST_F(InstantSearchPrerendererTest
, CanCommitQuery
) {
278 InstantSearchPrerenderer
* prerenderer
= GetInstantSearchPrerenderer();
279 base::string16 query
= ASCIIToUTF16("flowers");
280 prerenderer
->Prerender(InstantSuggestion(query
, std::string()));
281 EXPECT_TRUE(prerenderer
->CanCommitQuery(GetActiveWebContents(), query
));
283 // Make sure InstantSearchPrerenderer::CanCommitQuery() returns false for
284 // invalid search queries.
285 EXPECT_FALSE(prerenderer
->CanCommitQuery(GetActiveWebContents(),
286 ASCIIToUTF16("joy")));
287 EXPECT_FALSE(prerenderer
->CanCommitQuery(GetActiveWebContents(),
291 TEST_F(InstantSearchPrerendererTest
, CommitQuery
) {
292 base::string16 query
= ASCIIToUTF16("flowers");
293 PrerenderSearchQuery(query
);
294 InstantSearchPrerenderer
* prerenderer
= GetInstantSearchPrerenderer();
295 prerenderer
->Commit(query
);
296 EXPECT_TRUE(MessageWasSent(ChromeViewMsg_SearchBoxSubmit::ID
));
299 TEST_F(InstantSearchPrerendererTest
, CancelPrerenderRequestOnTabChangeEvent
) {
301 EXPECT_NE(static_cast<PrerenderHandle
*>(NULL
), prerender_handle());
303 // Add a new tab to deactivate the current tab.
304 AddTab(browser(), GURL(content::kAboutBlankURL
));
305 EXPECT_EQ(2, browser()->tab_strip_model()->count());
307 // Make sure the pending prerender request is cancelled.
308 EXPECT_EQ(static_cast<PrerenderHandle
*>(NULL
), prerender_handle());
311 TEST_F(InstantSearchPrerendererTest
, CancelPendingPrerenderRequest
) {
313 EXPECT_NE(static_cast<PrerenderHandle
*>(NULL
), prerender_handle());
315 InstantSearchPrerenderer
* prerenderer
= GetInstantSearchPrerenderer();
316 prerenderer
->Cancel();
317 EXPECT_EQ(static_cast<PrerenderHandle
*>(NULL
), prerender_handle());
320 TEST_F(InstantSearchPrerendererTest
, PrerenderingAllowed
) {
322 InstantSearchPrerenderer
* prerenderer
= GetInstantSearchPrerenderer();
323 content::WebContents
* active_tab
= GetActiveWebContents();
324 EXPECT_EQ(GURL(content::kAboutBlankURL
), active_tab
->GetURL());
326 // Allow prerendering only for search type AutocompleteMatch suggestions.
327 AutocompleteMatch
search_type_match(NULL
, 1100, false,
328 AutocompleteMatchType::SEARCH_SUGGEST
);
329 EXPECT_TRUE(AutocompleteMatch::IsSearchType(search_type_match
.type
));
330 EXPECT_TRUE(prerenderer
->IsAllowed(search_type_match
, active_tab
));
332 AutocompleteMatch
url_type_match(NULL
, 1100, true,
333 AutocompleteMatchType::URL_WHAT_YOU_TYPED
);
334 EXPECT_FALSE(AutocompleteMatch::IsSearchType(url_type_match
.type
));
335 EXPECT_FALSE(prerenderer
->IsAllowed(url_type_match
, active_tab
));
337 // Search results page supports Instant search. InstantSearchPrerenderer is
338 // used only when the underlying page doesn't support Instant.
339 NavigateAndCommitActiveTab(GURL("https://www.google.com/alt#quux=foo&strk"));
340 active_tab
= GetActiveWebContents();
341 EXPECT_FALSE(chrome::ExtractSearchTermsFromURL(profile(),
342 active_tab
->GetURL()).empty());
343 EXPECT_FALSE(chrome::ShouldPrefetchSearchResultsOnSRP());
344 EXPECT_FALSE(prerenderer
->IsAllowed(search_type_match
, active_tab
));
347 TEST_F(InstantSearchPrerendererTest
, UsePrerenderPage
) {
348 PrerenderSearchQuery(ASCIIToUTF16("foo"));
350 // Open a search results page. A prerendered page exists for |url|. Make sure
351 // the browser swaps the current tab contents with the prerendered contents.
352 GURL
url("https://www.google.com/alt#quux=foo&strk");
353 browser()->OpenURL(content::OpenURLParams(url
, Referrer(), CURRENT_TAB
,
354 content::PAGE_TRANSITION_TYPED
,
356 EXPECT_EQ(GetPrerenderURL(), GetActiveWebContents()->GetURL());
357 EXPECT_EQ(static_cast<PrerenderHandle
*>(NULL
), prerender_handle());
360 TEST_F(InstantSearchPrerendererTest
, PrerenderRequestCancelled
) {
361 PrerenderSearchQuery(ASCIIToUTF16("foo"));
363 // Cancel the prerender request.
364 InstantSearchPrerenderer
* prerenderer
= GetInstantSearchPrerenderer();
365 prerenderer
->Cancel();
366 EXPECT_EQ(static_cast<PrerenderHandle
*>(NULL
), prerender_handle());
368 // Open a search results page. Prerendered page does not exists for |url|.
369 // Make sure the browser navigates the current tab to this |url|.
370 GURL
url("https://www.google.com/alt#quux=foo&strk");
371 browser()->OpenURL(content::OpenURLParams(url
, Referrer(), CURRENT_TAB
,
372 content::PAGE_TRANSITION_TYPED
,
374 EXPECT_NE(GetPrerenderURL(), GetActiveWebContents()->GetURL());
375 EXPECT_EQ(url
, GetActiveWebContents()->GetURL());
378 TEST_F(InstantSearchPrerendererTest
,
379 CancelPrerenderRequest_SearchQueryMistmatch
) {
380 PrerenderSearchQuery(ASCIIToUTF16("foo"));
382 // Open a search results page. Committed query("pen") doesn't match with the
383 // prerendered search query("foo"). Make sure the InstantSearchPrerenderer
384 // cancels the active prerender request and the browser navigates the active
385 // tab to this |url|.
386 GURL
url("https://www.google.com/alt#quux=pen&strk");
387 browser()->OpenURL(content::OpenURLParams(url
, Referrer(), CURRENT_TAB
,
388 content::PAGE_TRANSITION_TYPED
,
390 EXPECT_NE(GetPrerenderURL(), GetActiveWebContents()->GetURL());
391 EXPECT_EQ(url
, GetActiveWebContents()->GetURL());
392 EXPECT_EQ(static_cast<PrerenderHandle
*>(NULL
), prerender_handle());
395 TEST_F(InstantSearchPrerendererTest
,
396 CancelPrerenderRequest_EmptySearchQueryCommitted
) {
397 PrerenderSearchQuery(ASCIIToUTF16("foo"));
399 // Open a search results page. Make sure the InstantSearchPrerenderer cancels
400 // the active prerender request upon the receipt of empty search query.
401 GURL
url("https://www.google.com/alt#quux=&strk");
402 browser()->OpenURL(content::OpenURLParams(url
, Referrer(), CURRENT_TAB
,
403 content::PAGE_TRANSITION_TYPED
,
405 EXPECT_NE(GetPrerenderURL(), GetActiveWebContents()->GetURL());
406 EXPECT_EQ(url
, GetActiveWebContents()->GetURL());
407 EXPECT_EQ(static_cast<PrerenderHandle
*>(NULL
), prerender_handle());
410 class ReuseInstantSearchBasePageTest
: public InstantSearchPrerendererTest
{
412 ReuseInstantSearchBasePageTest() {}
415 virtual void SetUp() OVERRIDE
{
416 ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
418 "Group1 strk:20 prefetch_results:1 reuse_instant_search_base_page:1"));
419 InstantUnitTestBase::SetUp();
423 TEST_F(ReuseInstantSearchBasePageTest
, CanCommitQuery
) {
425 InstantSearchPrerenderer
* prerenderer
= GetInstantSearchPrerenderer();
426 base::string16 query
= ASCIIToUTF16("flowers");
427 prerenderer
->Prerender(InstantSuggestion(query
, std::string()));
428 EXPECT_TRUE(prerenderer
->CanCommitQuery(GetActiveWebContents(), query
));
430 // When the Instant search base page has finished loading,
431 // InstantSearchPrerenderer can commit any search query to the prerendered
432 // page (even if it doesn't match the last known suggestion query).
433 EXPECT_TRUE(prerenderer
->CanCommitQuery(GetActiveWebContents(),
434 ASCIIToUTF16("joy")));
435 // Invalid search query committed.
436 EXPECT_FALSE(prerenderer
->CanCommitQuery(GetActiveWebContents(),
440 TEST_F(ReuseInstantSearchBasePageTest
,
441 CanCommitQuery_InstantSearchBasePageLoadInProgress
) {
443 InstantSearchPrerenderer
* prerenderer
= GetInstantSearchPrerenderer();
444 base::string16 query
= ASCIIToUTF16("flowers");
445 prerenderer
->Prerender(InstantSuggestion(query
, std::string()));
447 // When the Instant search base page hasn't finished loading,
448 // InstantSearchPrerenderer cannot commit any search query to the base page.
449 EXPECT_FALSE(prerenderer
->CanCommitQuery(GetActiveWebContents(), query
));
450 EXPECT_FALSE(prerenderer
->CanCommitQuery(GetActiveWebContents(),
451 ASCIIToUTF16("joy")));
454 #if !defined(OS_IOS) && !defined(OS_ANDROID)
455 class TestUsePrerenderPage
: public InstantSearchPrerendererTest
{
457 virtual void SetUp() OVERRIDE
{
458 // Disable query extraction flag in field trials.
459 ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
461 "Group1 strk:20 query_extraction:0 prefetch_results:1"));
462 InstantUnitTestBase::SetUpWithoutQueryExtraction();
466 TEST_F(TestUsePrerenderPage
, ExtractSearchTermsAndUsePrerenderPage
) {
467 PrerenderSearchQuery(ASCIIToUTF16("foo"));
469 // Open a search results page. Query extraction flag is disabled in field
470 // trials. Search results page URL does not contain search terms replacement
471 // key. Make sure UsePrerenderedPage() extracts the search terms from the URL
472 // and uses the prerendered page contents.
473 GURL
url("https://www.google.com/alt#quux=foo");
474 browser()->OpenURL(content::OpenURLParams(url
, Referrer(), CURRENT_TAB
,
475 content::PAGE_TRANSITION_TYPED
,
477 EXPECT_EQ(GetPrerenderURL(), GetActiveWebContents()->GetURL());
478 EXPECT_EQ(static_cast<PrerenderHandle
*>(NULL
), prerender_handle());