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/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_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 "content/public/browser/navigation_controller.h" 28 #include "content/public/browser/web_contents.h" 29 #include "content/public/common/url_constants.h" 30 #include "content/public/test/mock_render_process_host.h" 31 #include "ipc/ipc_message.h" 32 #include "ipc/ipc_test_sink.h" 33 #include "ui/gfx/size.h" 34 35 namespace { 36 37 using content::Referrer; 38 using prerender::Origin; 39 using prerender::PrerenderContents; 40 using prerender::PrerenderHandle; 41 using prerender::PrerenderManager; 42 using prerender::PrerenderManagerFactory; 43 44 class DummyPrerenderContents : public PrerenderContents { 45 public: 46 DummyPrerenderContents( 47 PrerenderManager* prerender_manager, 48 Profile* profile, 49 const GURL& url, 50 const Referrer& referrer, 51 Origin origin, 52 bool call_did_finish_load, 53 const content::SessionStorageNamespaceMap& session_storage_namespace_map); 54 55 virtual void StartPrerendering( 56 int ALLOW_UNUSED creator_child_id, 57 const gfx::Size& ALLOW_UNUSED size, 58 content::SessionStorageNamespace* session_storage_namespace) OVERRIDE; 59 virtual bool GetChildId(int* child_id) const OVERRIDE; 60 virtual bool GetRouteId(int* route_id) const OVERRIDE; 61 62 private: 63 Profile* profile_; 64 const GURL url_; 65 bool call_did_finish_load_; 66 content::SessionStorageNamespaceMap session_storage_namespace_map_; 67 68 DISALLOW_COPY_AND_ASSIGN(DummyPrerenderContents); 69 }; 70 71 class DummyPrerenderContentsFactory : public PrerenderContents::Factory { 72 public: 73 DummyPrerenderContentsFactory( 74 bool call_did_finish_load, 75 const content::SessionStorageNamespaceMap& session_storage_namespace_map) 76 : call_did_finish_load_(call_did_finish_load), 77 session_storage_namespace_map_(session_storage_namespace_map) { 78 } 79 80 virtual PrerenderContents* CreatePrerenderContents( 81 PrerenderManager* prerender_manager, 82 Profile* profile, 83 const GURL& url, 84 const Referrer& referrer, 85 Origin origin, 86 uint8 experiment_id) OVERRIDE; 87 88 private: 89 bool call_did_finish_load_; 90 content::SessionStorageNamespaceMap session_storage_namespace_map_; 91 92 DISALLOW_COPY_AND_ASSIGN(DummyPrerenderContentsFactory); 93 }; 94 95 DummyPrerenderContents::DummyPrerenderContents( 96 PrerenderManager* prerender_manager, 97 Profile* profile, 98 const GURL& url, 99 const Referrer& referrer, 100 Origin origin, 101 bool call_did_finish_load, 102 const content::SessionStorageNamespaceMap& session_storage_namespace_map) 103 : PrerenderContents(prerender_manager, profile, url, referrer, origin, 104 PrerenderManager::kNoExperiment), 105 profile_(profile), 106 url_(url), 107 call_did_finish_load_(call_did_finish_load), 108 session_storage_namespace_map_(session_storage_namespace_map) { 109 } 110 111 void DummyPrerenderContents::StartPrerendering( 112 int ALLOW_UNUSED creator_child_id, 113 const gfx::Size& ALLOW_UNUSED size, 114 content::SessionStorageNamespace* session_storage_namespace) { 115 prerender_contents_.reset(content::WebContents::CreateWithSessionStorage( 116 content::WebContents::CreateParams(profile_), 117 session_storage_namespace_map_)); 118 content::NavigationController::LoadURLParams params(url_); 119 prerender_contents_->GetController().LoadURLWithParams(params); 120 SearchTabHelper::CreateForWebContents(prerender_contents_.get()); 121 122 AddObserver(prerender_manager()->prerender_tracker()); 123 prerendering_has_started_ = true; 124 DCHECK(session_storage_namespace); 125 session_storage_namespace_id_ = session_storage_namespace->id(); 126 NotifyPrerenderStart(); 127 128 if (call_did_finish_load_) 129 DidFinishLoad(1, url_, true, NULL); 130 } 131 132 bool DummyPrerenderContents::GetChildId(int* child_id) const { 133 *child_id = 1; 134 return true; 135 } 136 137 bool DummyPrerenderContents::GetRouteId(int* route_id) const { 138 *route_id = 1; 139 return true; 140 } 141 142 PrerenderContents* DummyPrerenderContentsFactory::CreatePrerenderContents( 143 PrerenderManager* prerender_manager, 144 Profile* profile, 145 const GURL& url, 146 const Referrer& referrer, 147 Origin origin, 148 uint8 experiment_id) { 149 return new DummyPrerenderContents(prerender_manager, profile, url, referrer, 150 origin, call_did_finish_load_, 151 session_storage_namespace_map_); 152 } 153 154 } // namespace 155 156 class InstantSearchPrerendererTest : public InstantUnitTestBase { 157 public: 158 InstantSearchPrerendererTest() {} 159 160 protected: 161 virtual void SetUp() OVERRIDE { 162 ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial( 163 "EmbeddedSearch", 164 "Group1 strk:20 use_cacheable_ntp:1 prefetch_results:1")); 165 InstantUnitTestBase::SetUp(); 166 } 167 168 void Init(bool prerender_search_results_base_page, 169 bool call_did_finish_load) { 170 AddTab(browser(), GURL(content::kAboutBlankURL)); 171 172 content::SessionStorageNamespaceMap session_storage_namespace_map; 173 session_storage_namespace_map[std::string()] = 174 GetActiveWebContents()->GetController(). 175 GetDefaultSessionStorageNamespace(); 176 PrerenderManagerFactory::GetForProfile(browser()->profile())-> 177 SetPrerenderContentsFactory( 178 new DummyPrerenderContentsFactory(call_did_finish_load, 179 session_storage_namespace_map)); 180 181 if (prerender_search_results_base_page) { 182 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer(); 183 prerenderer->Init(session_storage_namespace_map, gfx::Size(640, 480)); 184 EXPECT_NE(static_cast<PrerenderHandle*>(NULL), prerender_handle()); 185 } 186 } 187 188 InstantSearchPrerenderer* GetInstantSearchPrerenderer() { 189 return instant_service_->instant_search_prerenderer(); 190 } 191 192 const GURL& GetPrerenderURL() { 193 return GetInstantSearchPrerenderer()->prerender_url_; 194 } 195 196 void SetLastQuery(const string16& query) { 197 GetInstantSearchPrerenderer()->last_instant_suggestion_ = 198 InstantSuggestion(query, std::string()); 199 } 200 201 content::WebContents* prerender_contents() { 202 return GetInstantSearchPrerenderer()->prerender_contents(); 203 } 204 205 bool MessageWasSent(uint32 id) { 206 content::MockRenderProcessHost* process = 207 static_cast<content::MockRenderProcessHost*>( 208 prerender_contents()->GetRenderViewHost()->GetProcess()); 209 return process->sink().GetFirstMessageMatching(id) != NULL; 210 } 211 212 content::WebContents* GetActiveWebContents() const { 213 return browser()->tab_strip_model()->GetWebContentsAt(0); 214 } 215 216 PrerenderHandle* prerender_handle() { 217 return GetInstantSearchPrerenderer()->prerender_handle_.get(); 218 } 219 220 void PrerenderSearchQuery(const string16& query) { 221 Init(true, true); 222 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer(); 223 prerenderer->Prerender(InstantSuggestion(query, std::string())); 224 CommitPendingLoad(&prerender_contents()->GetController()); 225 EXPECT_TRUE(prerenderer->CanCommitQuery(GetActiveWebContents(), query)); 226 EXPECT_NE(static_cast<PrerenderHandle*>(NULL), prerender_handle()); 227 } 228 }; 229 230 TEST_F(InstantSearchPrerendererTest, GetSearchTermsFromPrerenderedPage) { 231 Init(false, false); 232 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer(); 233 GURL url(GetPrerenderURL()); 234 EXPECT_EQ(GURL("https://www.google.com/instant?ion=1&foo=foo#foo=foo&strk"), 235 url); 236 EXPECT_EQ(UTF16ToASCII(prerenderer->get_last_query()), 237 UTF16ToASCII(chrome::GetSearchTermsFromURL(profile(), url))); 238 239 // Assume the prerendered page prefetched search results for the query 240 // "flowers". 241 SetLastQuery(ASCIIToUTF16("flowers")); 242 EXPECT_EQ("flowers", UTF16ToASCII(prerenderer->get_last_query())); 243 EXPECT_EQ(UTF16ToASCII(prerenderer->get_last_query()), 244 UTF16ToASCII(chrome::GetSearchTermsFromURL(profile(), url))); 245 } 246 247 TEST_F(InstantSearchPrerendererTest, PrefetchSearchResults) { 248 Init(true, true); 249 EXPECT_TRUE(prerender_handle()->IsFinishedLoading()); 250 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer(); 251 prerenderer->Prerender( 252 InstantSuggestion(ASCIIToUTF16("flowers"), std::string())); 253 EXPECT_EQ("flowers", UTF16ToASCII(prerenderer->get_last_query())); 254 EXPECT_TRUE(MessageWasSent( 255 ChromeViewMsg_SearchBoxSetSuggestionToPrefetch::ID)); 256 } 257 258 TEST_F(InstantSearchPrerendererTest, DoNotPrefetchSearchResults) { 259 Init(true, false); 260 // Page hasn't finished loading yet. 261 EXPECT_FALSE(prerender_handle()->IsFinishedLoading()); 262 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer(); 263 prerenderer->Prerender( 264 InstantSuggestion(ASCIIToUTF16("flowers"), std::string())); 265 EXPECT_EQ("", UTF16ToASCII(prerenderer->get_last_query())); 266 EXPECT_FALSE(MessageWasSent( 267 ChromeViewMsg_SearchBoxSetSuggestionToPrefetch::ID)); 268 } 269 270 TEST_F(InstantSearchPrerendererTest, CanCommitQuery) { 271 Init(true, true); 272 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer(); 273 string16 query = ASCIIToUTF16("flowers"); 274 prerenderer->Prerender(InstantSuggestion(query, std::string())); 275 EXPECT_TRUE(prerenderer->CanCommitQuery(GetActiveWebContents(), query)); 276 277 // Make sure InstantSearchPrerenderer::CanCommitQuery() returns false for 278 // invalid search queries. 279 EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(), 280 ASCIIToUTF16("joy"))); 281 EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(), string16())); 282 } 283 284 TEST_F(InstantSearchPrerendererTest, CommitQuery) { 285 string16 query = ASCIIToUTF16("flowers"); 286 PrerenderSearchQuery(query); 287 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer(); 288 prerenderer->Commit(query); 289 EXPECT_TRUE(MessageWasSent(ChromeViewMsg_SearchBoxSubmit::ID)); 290 } 291 292 TEST_F(InstantSearchPrerendererTest, CancelPrerenderRequestOnTabChangeEvent) { 293 Init(true, true); 294 EXPECT_NE(static_cast<PrerenderHandle*>(NULL), prerender_handle()); 295 296 // Add a new tab to deactivate the current tab. 297 AddTab(browser(), GURL(content::kAboutBlankURL)); 298 EXPECT_EQ(2, browser()->tab_strip_model()->count()); 299 300 // Make sure the pending prerender request is cancelled. 301 EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle()); 302 } 303 304 TEST_F(InstantSearchPrerendererTest, CancelPendingPrerenderRequest) { 305 Init(true, true); 306 EXPECT_NE(static_cast<PrerenderHandle*>(NULL), prerender_handle()); 307 308 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer(); 309 prerenderer->Cancel(); 310 EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle()); 311 } 312 313 TEST_F(InstantSearchPrerendererTest, PrerenderingAllowed) { 314 Init(true, true); 315 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer(); 316 content::WebContents* active_tab = GetActiveWebContents(); 317 EXPECT_EQ(GURL(content::kAboutBlankURL), active_tab->GetURL()); 318 319 // Allow prerendering only for search type AutocompleteMatch suggestions. 320 AutocompleteMatch search_type_match(NULL, 1100, false, 321 AutocompleteMatchType::SEARCH_SUGGEST); 322 EXPECT_TRUE(AutocompleteMatch::IsSearchType(search_type_match.type)); 323 EXPECT_TRUE(prerenderer->IsAllowed(search_type_match, active_tab)); 324 325 AutocompleteMatch url_type_match(NULL, 1100, true, 326 AutocompleteMatchType::URL_WHAT_YOU_TYPED); 327 EXPECT_FALSE(AutocompleteMatch::IsSearchType(url_type_match.type)); 328 EXPECT_FALSE(prerenderer->IsAllowed(url_type_match, active_tab)); 329 330 // Search results page supports Instant search. InstantSearchPrerenderer is 331 // used only when the underlying page doesn't support Instant. 332 NavigateAndCommitActiveTab(GURL("https://www.google.com/alt#quux=foo&strk")); 333 active_tab = GetActiveWebContents(); 334 EXPECT_FALSE(chrome::GetSearchTermsFromURL(profile(), active_tab->GetURL()) 335 .empty()); 336 EXPECT_FALSE(chrome::ShouldPrefetchSearchResultsOnSRP()); 337 EXPECT_FALSE(prerenderer->IsAllowed(search_type_match, active_tab)); 338 } 339 340 TEST_F(InstantSearchPrerendererTest, UsePrerenderPage) { 341 PrerenderSearchQuery(ASCIIToUTF16("foo")); 342 343 // Open a search results page. A prerendered page exists for |url|. Make sure 344 // the browser swaps the current tab contents with the prerendered contents. 345 GURL url("https://www.google.com/alt#quux=foo&strk"); 346 browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB, 347 content::PAGE_TRANSITION_TYPED, 348 false)); 349 EXPECT_EQ(GetPrerenderURL(), GetActiveWebContents()->GetURL()); 350 EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle()); 351 } 352 353 TEST_F(InstantSearchPrerendererTest, PrerenderRequestCancelled) { 354 PrerenderSearchQuery(ASCIIToUTF16("foo")); 355 356 // Cancel the prerender request. 357 InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer(); 358 prerenderer->Cancel(); 359 EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle()); 360 361 // Open a search results page. Prerendered page does not exists for |url|. 362 // Make sure the browser navigates the current tab to this |url|. 363 GURL url("https://www.google.com/alt#quux=foo&strk"); 364 browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB, 365 content::PAGE_TRANSITION_TYPED, 366 false)); 367 EXPECT_NE(GetPrerenderURL(), GetActiveWebContents()->GetURL()); 368 EXPECT_EQ(url, GetActiveWebContents()->GetURL()); 369 } 370 371 TEST_F(InstantSearchPrerendererTest, 372 CancelPrerenderRequest_SearchQueryMistmatch) { 373 PrerenderSearchQuery(ASCIIToUTF16("foo")); 374 375 // Open a search results page. Committed query("pen") doesn't match with the 376 // prerendered search query("foo"). Make sure the InstantSearchPrerenderer 377 // cancels the active prerender request and the browser navigates the active 378 // tab to this |url|. 379 GURL url("https://www.google.com/alt#quux=pen&strk"); 380 browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB, 381 content::PAGE_TRANSITION_TYPED, 382 false)); 383 EXPECT_NE(GetPrerenderURL(), GetActiveWebContents()->GetURL()); 384 EXPECT_EQ(url, GetActiveWebContents()->GetURL()); 385 EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle()); 386 } 387 388 TEST_F(InstantSearchPrerendererTest, 389 CancelPrerenderRequest_EmptySearchQueryCommitted) { 390 PrerenderSearchQuery(ASCIIToUTF16("foo")); 391 392 // Open a search results page. Make sure the InstantSearchPrerenderer cancels 393 // the active prerender request upon the receipt of empty search query. 394 GURL url("https://www.google.com/alt#quux=&strk"); 395 browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB, 396 content::PAGE_TRANSITION_TYPED, 397 false)); 398 EXPECT_NE(GetPrerenderURL(), GetActiveWebContents()->GetURL()); 399 EXPECT_EQ(url, GetActiveWebContents()->GetURL()); 400 EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle()); 401 } 402