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