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/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/browser_instant_controller.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/instant_types.h"
28 #include "chrome/common/render_messages.h"
29 #include "components/omnibox/autocomplete_match.h"
30 #include "content/public/browser/navigation_controller.h"
31 #include "content/public/browser/web_contents.h"
32 #include "content/public/common/url_constants.h"
33 #include "content/public/test/mock_render_process_host.h"
34 #include "ipc/ipc_message.h"
35 #include "ipc/ipc_test_sink.h"
36 #include "ui/gfx/size.h"
38 using base::ASCIIToUTF16;
42 using content::Referrer;
43 using prerender::Origin;
44 using prerender::PrerenderContents;
45 using prerender::PrerenderHandle;
46 using prerender::PrerenderManager;
47 using prerender::PrerenderManagerFactory;
48 using prerender::PrerenderTabHelper;
50 class DummyPrerenderContents : public PrerenderContents {
52 DummyPrerenderContents(
53 PrerenderManager* prerender_manager,
56 const Referrer& referrer,
58 bool call_did_finish_load,
59 const content::SessionStorageNamespaceMap& session_storage_namespace_map);
61 void StartPrerendering(
63 const gfx::Size& size,
64 content::SessionStorageNamespace* session_storage_namespace,
65 net::URLRequestContextGetter* request_context) override;
66 bool GetChildId(int* child_id) const override;
67 bool GetRouteId(int* route_id) const override;
72 bool call_did_finish_load_;
73 content::SessionStorageNamespaceMap session_storage_namespace_map_;
75 DISALLOW_COPY_AND_ASSIGN(DummyPrerenderContents);
78 class DummyPrerenderContentsFactory : public PrerenderContents::Factory {
80 DummyPrerenderContentsFactory(
81 bool call_did_finish_load,
82 const content::SessionStorageNamespaceMap& session_storage_namespace_map)
83 : call_did_finish_load_(call_did_finish_load),
84 session_storage_namespace_map_(session_storage_namespace_map) {
87 PrerenderContents* CreatePrerenderContents(
88 PrerenderManager* prerender_manager,
91 const Referrer& referrer,
93 uint8 experiment_id) override;
96 bool call_did_finish_load_;
97 content::SessionStorageNamespaceMap session_storage_namespace_map_;
99 DISALLOW_COPY_AND_ASSIGN(DummyPrerenderContentsFactory);
102 DummyPrerenderContents::DummyPrerenderContents(
103 PrerenderManager* prerender_manager,
106 const Referrer& referrer,
108 bool call_did_finish_load,
109 const content::SessionStorageNamespaceMap& session_storage_namespace_map)
110 : PrerenderContents(prerender_manager, profile, url, referrer, origin,
111 PrerenderManager::kNoExperiment),
114 call_did_finish_load_(call_did_finish_load),
115 session_storage_namespace_map_(session_storage_namespace_map) {
118 void DummyPrerenderContents::StartPrerendering(
119 int creator_child_id,
120 const gfx::Size& size,
121 content::SessionStorageNamespace* session_storage_namespace,
122 net::URLRequestContextGetter* request_context) {
123 prerender_contents_.reset(content::WebContents::CreateWithSessionStorage(
124 content::WebContents::CreateParams(profile_),
125 session_storage_namespace_map_));
126 PrerenderTabHelper::CreateForWebContentsWithPasswordManager(
127 prerender_contents_.get(), NULL);
128 content::NavigationController::LoadURLParams params(url_);
129 prerender_contents_->GetController().LoadURLWithParams(params);
130 SearchTabHelper::CreateForWebContents(prerender_contents_.get());
132 prerendering_has_started_ = true;
133 DCHECK(session_storage_namespace);
134 session_storage_namespace_id_ = session_storage_namespace->id();
135 NotifyPrerenderStart();
137 if (call_did_finish_load_)
138 DidFinishLoad(prerender_contents_->GetMainFrame(), url_);
141 bool DummyPrerenderContents::GetChildId(int* child_id) const {
146 bool DummyPrerenderContents::GetRouteId(int* route_id) const {
151 PrerenderContents* DummyPrerenderContentsFactory::CreatePrerenderContents(
152 PrerenderManager* prerender_manager,
155 const Referrer& referrer,
157 uint8 experiment_id) {
158 return new DummyPrerenderContents(prerender_manager, profile, url, referrer,
159 origin, call_did_finish_load_,
160 session_storage_namespace_map_);
165 class InstantSearchPrerendererTest : public InstantUnitTestBase {
167 InstantSearchPrerendererTest() {}
170 void SetUp() override {
171 ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial("EmbeddedSearch",
173 InstantUnitTestBase::SetUp();
176 void Init(bool prerender_search_results_base_page,
177 bool call_did_finish_load) {
178 AddTab(browser(), GURL(url::kAboutBlankURL));
180 content::SessionStorageNamespaceMap session_storage_namespace_map;
181 session_storage_namespace_map[std::string()] =
182 GetActiveWebContents()->GetController().
183 GetDefaultSessionStorageNamespace();
184 PrerenderManagerFactory::GetForProfile(browser()->profile())->
185 SetPrerenderContentsFactory(
186 new DummyPrerenderContentsFactory(call_did_finish_load,
187 session_storage_namespace_map));
188 PrerenderManagerFactory::GetForProfile(browser()->profile())->
189 OnCookieStoreLoaded();
190 if (prerender_search_results_base_page) {
191 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
192 prerenderer->Init(session_storage_namespace_map, gfx::Size(640, 480));
193 EXPECT_NE(static_cast<PrerenderHandle*>(NULL), prerender_handle());
197 InstantSearchPrerenderer* GetInstantSearchPrerenderer() {
198 return instant_service_->instant_search_prerenderer();
201 const GURL& GetPrerenderURL() {
202 return GetInstantSearchPrerenderer()->prerender_url_;
205 void SetLastQuery(const base::string16& query) {
206 GetInstantSearchPrerenderer()->last_instant_suggestion_ =
207 InstantSuggestion(query, std::string());
210 content::WebContents* prerender_contents() {
211 return GetInstantSearchPrerenderer()->prerender_contents();
214 bool MessageWasSent(uint32 id) {
215 content::MockRenderProcessHost* process =
216 static_cast<content::MockRenderProcessHost*>(
217 prerender_contents()->GetRenderViewHost()->GetProcess());
218 return process->sink().GetFirstMessageMatching(id) != NULL;
221 content::WebContents* GetActiveWebContents() const {
222 return browser()->tab_strip_model()->GetWebContentsAt(0);
225 PrerenderHandle* prerender_handle() {
226 return GetInstantSearchPrerenderer()->prerender_handle_.get();
229 void PrerenderSearchQuery(const base::string16& query) {
231 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
232 prerenderer->Prerender(InstantSuggestion(query, std::string()));
233 CommitPendingLoad(&prerender_contents()->GetController());
234 EXPECT_TRUE(prerenderer->CanCommitQuery(GetActiveWebContents(), query));
235 EXPECT_NE(static_cast<PrerenderHandle*>(NULL), prerender_handle());
239 TEST_F(InstantSearchPrerendererTest, GetSearchTermsFromPrerenderedPage) {
241 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
242 GURL url(GetPrerenderURL());
243 EXPECT_EQ(GURL("https://www.google.com/instant?ion=1&foo=foo#foo=foo&strk"),
245 EXPECT_EQ(base::UTF16ToASCII(prerenderer->get_last_query()),
247 chrome::ExtractSearchTermsFromURL(profile(), url)));
249 // Assume the prerendered page prefetched search results for the query
251 SetLastQuery(ASCIIToUTF16("flowers"));
252 EXPECT_EQ("flowers", base::UTF16ToASCII(prerenderer->get_last_query()));
253 EXPECT_EQ(base::UTF16ToASCII(prerenderer->get_last_query()),
255 chrome::ExtractSearchTermsFromURL(profile(), url)));
258 TEST_F(InstantSearchPrerendererTest, PrefetchSearchResults) {
260 EXPECT_TRUE(prerender_handle()->IsFinishedLoading());
261 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
262 prerenderer->Prerender(
263 InstantSuggestion(ASCIIToUTF16("flowers"), std::string()));
264 EXPECT_EQ("flowers", base::UTF16ToASCII(prerenderer->get_last_query()));
265 EXPECT_TRUE(MessageWasSent(
266 ChromeViewMsg_SearchBoxSetSuggestionToPrefetch::ID));
269 TEST_F(InstantSearchPrerendererTest, DoNotPrefetchSearchResults) {
271 // Page hasn't finished loading yet.
272 EXPECT_FALSE(prerender_handle()->IsFinishedLoading());
273 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
274 prerenderer->Prerender(
275 InstantSuggestion(ASCIIToUTF16("flowers"), std::string()));
276 EXPECT_EQ("", base::UTF16ToASCII(prerenderer->get_last_query()));
277 EXPECT_FALSE(MessageWasSent(
278 ChromeViewMsg_SearchBoxSetSuggestionToPrefetch::ID));
281 TEST_F(InstantSearchPrerendererTest, CanCommitQuery) {
283 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
284 base::string16 query = ASCIIToUTF16("flowers");
285 prerenderer->Prerender(InstantSuggestion(query, std::string()));
286 EXPECT_TRUE(prerenderer->CanCommitQuery(GetActiveWebContents(), query));
288 // Make sure InstantSearchPrerenderer::CanCommitQuery() returns false for
289 // invalid search queries.
290 EXPECT_TRUE(prerenderer->CanCommitQuery(GetActiveWebContents(),
291 ASCIIToUTF16("joy")));
292 EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(),
296 TEST_F(InstantSearchPrerendererTest, CommitQuery) {
297 base::string16 query = ASCIIToUTF16("flowers");
298 PrerenderSearchQuery(query);
299 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
300 prerenderer->Commit(query, EmbeddedSearchRequestParams());
301 EXPECT_TRUE(MessageWasSent(ChromeViewMsg_SearchBoxSubmit::ID));
304 TEST_F(InstantSearchPrerendererTest, CancelPrerenderRequestOnTabChangeEvent) {
306 EXPECT_NE(static_cast<PrerenderHandle*>(NULL), prerender_handle());
308 // Add a new tab to deactivate the current tab.
309 AddTab(browser(), GURL(url::kAboutBlankURL));
310 EXPECT_EQ(2, browser()->tab_strip_model()->count());
312 // Make sure the pending prerender request is cancelled.
313 EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
316 TEST_F(InstantSearchPrerendererTest, CancelPendingPrerenderRequest) {
318 EXPECT_NE(static_cast<PrerenderHandle*>(NULL), prerender_handle());
320 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
321 prerenderer->Cancel();
322 EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
325 TEST_F(InstantSearchPrerendererTest, PrerenderingAllowed) {
327 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
328 content::WebContents* active_tab = GetActiveWebContents();
329 EXPECT_EQ(GURL(url::kAboutBlankURL), active_tab->GetURL());
331 // Allow prerendering only for search type AutocompleteMatch suggestions.
332 AutocompleteMatch search_type_match(NULL, 1100, false,
333 AutocompleteMatchType::SEARCH_SUGGEST);
334 EXPECT_TRUE(AutocompleteMatch::IsSearchType(search_type_match.type));
335 EXPECT_TRUE(prerenderer->IsAllowed(search_type_match, active_tab));
337 AutocompleteMatch url_type_match(NULL, 1100, true,
338 AutocompleteMatchType::URL_WHAT_YOU_TYPED);
339 EXPECT_FALSE(AutocompleteMatch::IsSearchType(url_type_match.type));
340 EXPECT_FALSE(prerenderer->IsAllowed(url_type_match, active_tab));
342 // Search results page supports Instant search. InstantSearchPrerenderer is
343 // used only when the underlying page doesn't support Instant.
344 NavigateAndCommitActiveTab(GURL("https://www.google.com/alt#quux=foo&strk"));
345 active_tab = GetActiveWebContents();
346 EXPECT_FALSE(chrome::ExtractSearchTermsFromURL(profile(),
347 active_tab->GetURL()).empty());
348 EXPECT_FALSE(chrome::ShouldPrefetchSearchResultsOnSRP());
349 EXPECT_FALSE(prerenderer->IsAllowed(search_type_match, active_tab));
352 TEST_F(InstantSearchPrerendererTest, UsePrerenderPage) {
353 PrerenderSearchQuery(ASCIIToUTF16("foo"));
355 // Open a search results page. A prerendered page exists for |url|. Make sure
356 // the browser swaps the current tab contents with the prerendered contents.
357 GURL url("https://www.google.com/alt#quux=foo&strk");
358 browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
359 ui::PAGE_TRANSITION_TYPED,
361 EXPECT_EQ(GetPrerenderURL(), GetActiveWebContents()->GetURL());
362 EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
365 TEST_F(InstantSearchPrerendererTest, PrerenderRequestCancelled) {
366 PrerenderSearchQuery(ASCIIToUTF16("foo"));
368 // Cancel the prerender request.
369 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
370 prerenderer->Cancel();
371 EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
373 // Open a search results page. Prerendered page does not exists for |url|.
374 // Make sure the browser navigates the current tab to this |url|.
375 GURL url("https://www.google.com/alt#quux=foo&strk");
376 browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
377 ui::PAGE_TRANSITION_TYPED,
379 EXPECT_NE(GetPrerenderURL(), GetActiveWebContents()->GetURL());
380 EXPECT_EQ(url, GetActiveWebContents()->GetURL());
383 TEST_F(InstantSearchPrerendererTest,
384 UsePrerenderedPage_SearchQueryMistmatch) {
385 PrerenderSearchQuery(ASCIIToUTF16("foo"));
387 // Open a search results page. Committed query("pen") doesn't match with the
388 // prerendered search query("foo"). Make sure the browser swaps the current
389 // tab contents with the prerendered contents.
390 GURL url("https://www.google.com/alt#quux=pen&strk");
391 browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
392 ui::PAGE_TRANSITION_TYPED,
394 EXPECT_EQ(GetPrerenderURL(), GetActiveWebContents()->GetURL());
395 EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
398 TEST_F(InstantSearchPrerendererTest,
399 CancelPrerenderRequest_EmptySearchQueryCommitted) {
400 PrerenderSearchQuery(ASCIIToUTF16("foo"));
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 ui::PAGE_TRANSITION_TYPED,
408 EXPECT_NE(GetPrerenderURL(), GetActiveWebContents()->GetURL());
409 EXPECT_EQ(url, GetActiveWebContents()->GetURL());
410 EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
413 TEST_F(InstantSearchPrerendererTest,
414 CancelPrerenderRequest_UnsupportedDispositions) {
415 PrerenderSearchQuery(ASCIIToUTF16("pen"));
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 ui::PAGE_TRANSITION_TYPED,
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());
430 class ReuseInstantSearchBasePageTest : public InstantSearchPrerendererTest {
432 ReuseInstantSearchBasePageTest() {}
435 void SetUp() override {
436 ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial("EmbeddedSearch",
438 InstantUnitTestBase::SetUp();
442 TEST_F(ReuseInstantSearchBasePageTest, CanCommitQuery) {
444 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
445 base::string16 query = ASCIIToUTF16("flowers");
446 prerenderer->Prerender(InstantSuggestion(query, std::string()));
447 EXPECT_TRUE(prerenderer->CanCommitQuery(GetActiveWebContents(), query));
449 // When the Instant search base page has finished loading,
450 // InstantSearchPrerenderer can commit any search query to the prerendered
451 // page (even if it doesn't match the last known suggestion query).
452 EXPECT_TRUE(prerenderer->CanCommitQuery(GetActiveWebContents(),
453 ASCIIToUTF16("joy")));
454 // Invalid search query committed.
455 EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(),
459 TEST_F(ReuseInstantSearchBasePageTest,
460 CanCommitQuery_InstantSearchBasePageLoadInProgress) {
462 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
463 base::string16 query = ASCIIToUTF16("flowers");
464 prerenderer->Prerender(InstantSuggestion(query, std::string()));
466 // When the Instant search base page hasn't finished loading,
467 // InstantSearchPrerenderer cannot commit any search query to the base page.
468 EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(), query));
469 EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(),
470 ASCIIToUTF16("joy")));
473 #if !defined(OS_IOS) && !defined(OS_ANDROID)
474 class TestUsePrerenderPage : public InstantSearchPrerendererTest {
476 void SetUp() override {
477 // Disable query extraction flag in field trials.
478 ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
479 "EmbeddedSearch", "Group1 strk:20 query_extraction:0"));
480 InstantUnitTestBase::SetUpWithoutQueryExtraction();
484 TEST_F(TestUsePrerenderPage, ExtractSearchTermsAndUsePrerenderPage) {
485 PrerenderSearchQuery(ASCIIToUTF16("foo"));
487 // Open a search results page. Query extraction flag is disabled in field
488 // trials. Search results page URL does not contain search terms replacement
489 // key. Make sure UsePrerenderedPage() extracts the search terms from the URL
490 // and uses the prerendered page contents.
491 GURL url("https://www.google.com/alt#quux=foo");
492 browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
493 ui::PAGE_TRANSITION_TYPED,
495 EXPECT_EQ(GetPrerenderURL(), GetActiveWebContents()->GetURL());
496 EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
499 TEST_F(TestUsePrerenderPage, SetEmbeddedSearchRequestParams) {
500 PrerenderSearchQuery(ASCIIToUTF16("foo"));
501 EXPECT_TRUE(browser()->instant_controller());
503 // Open a search results page. Query extraction flag is disabled in field
504 // trials. Search results page URL does not contain search terms replacement
506 GURL url("https://www.google.com/url?bar=foo&aqs=chrome...0&ie=utf-8&oq=f");
507 browser()->instant_controller()->OpenInstant(CURRENT_TAB, url);
508 content::MockRenderProcessHost* process =
509 static_cast<content::MockRenderProcessHost*>(
510 prerender_contents()->GetRenderViewHost()->GetProcess());
511 const IPC::Message* message = process->sink().GetFirstMessageMatching(
512 ChromeViewMsg_SearchBoxSubmit::ID);
513 ASSERT_TRUE(message);
515 // Verify the IPC message params.
516 Tuple2<base::string16, EmbeddedSearchRequestParams> params;
517 ChromeViewMsg_SearchBoxSubmit::Read(message, ¶ms);
518 EXPECT_EQ("foo", base::UTF16ToASCII(params.a));
519 EXPECT_EQ("f", base::UTF16ToASCII(params.b.original_query));
520 EXPECT_EQ("utf-8", base::UTF16ToASCII(params.b.input_encoding));
521 EXPECT_EQ("", base::UTF16ToASCII(params.b.rlz_parameter_value));
522 EXPECT_EQ("chrome...0", base::UTF16ToASCII(params.b.assisted_query_stats));