blob: 4d857acaf4fdf3004177a410e5b64f7df53bf136 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/search/instant_search_prerenderer.h"
#include <stdint.h>
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
#include "base/metrics/field_trial.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/prerender/prerender_contents.h"
#include "chrome/browser/prerender/prerender_handle.h"
#include "chrome/browser/prerender/prerender_manager.h"
#include "chrome/browser/prerender/prerender_manager_factory.h"
#include "chrome/browser/prerender/prerender_origin.h"
#include "chrome/browser/prerender/prerender_tab_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search/instant_service.h"
#include "chrome/browser/search/instant_unittest_base.h"
#include "chrome/browser/search/search.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/ui/browser_instant_controller.h"
#include "chrome/browser/ui/search/search_tab_helper.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/instant_types.h"
#include "chrome/common/render_messages.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/mock_render_process_host.h"
#include "ipc/ipc_message.h"
#include "ipc/ipc_test_sink.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/gfx/geometry/size.h"
using base::ASCIIToUTF16;
namespace {
using content::Referrer;
using prerender::Origin;
using prerender::PrerenderContents;
using prerender::PrerenderHandle;
using prerender::PrerenderManager;
using prerender::PrerenderManagerFactory;
using prerender::PrerenderTabHelper;
class DummyPrerenderContents : public PrerenderContents {
public:
DummyPrerenderContents(
PrerenderManager* prerender_manager,
Profile* profile,
const GURL& url,
const Referrer& referrer,
Origin origin,
bool call_did_finish_load);
void StartPrerendering(
const gfx::Size& size,
content::SessionStorageNamespace* session_storage_namespace) override;
bool GetChildId(int* child_id) const override;
bool GetRouteId(int* route_id) const override;
private:
Profile* profile_;
const GURL url_;
bool call_did_finish_load_;
DISALLOW_COPY_AND_ASSIGN(DummyPrerenderContents);
};
class DummyPrerenderContentsFactory : public PrerenderContents::Factory {
public:
explicit DummyPrerenderContentsFactory(bool call_did_finish_load)
: call_did_finish_load_(call_did_finish_load) {
}
PrerenderContents* CreatePrerenderContents(
PrerenderManager* prerender_manager,
Profile* profile,
const GURL& url,
const Referrer& referrer,
Origin origin) override;
private:
bool call_did_finish_load_;
DISALLOW_COPY_AND_ASSIGN(DummyPrerenderContentsFactory);
};
DummyPrerenderContents::DummyPrerenderContents(
PrerenderManager* prerender_manager,
Profile* profile,
const GURL& url,
const Referrer& referrer,
Origin origin,
bool call_did_finish_load)
: PrerenderContents(prerender_manager, profile, url, referrer, origin),
profile_(profile),
url_(url),
call_did_finish_load_(call_did_finish_load) {
}
void DummyPrerenderContents::StartPrerendering(
const gfx::Size& size,
content::SessionStorageNamespace* session_storage_namespace) {
content::SessionStorageNamespaceMap session_storage_namespace_map;
session_storage_namespace_map[std::string()] = session_storage_namespace;
prerender_contents_.reset(content::WebContents::CreateWithSessionStorage(
content::WebContents::CreateParams(profile_),
session_storage_namespace_map));
PrerenderTabHelper::CreateForWebContents(prerender_contents_.get());
content::NavigationController::LoadURLParams params(url_);
prerender_contents_->GetController().LoadURLWithParams(params);
SearchTabHelper::CreateForWebContents(prerender_contents_.get());
prerendering_has_started_ = true;
DCHECK(session_storage_namespace);
session_storage_namespace_id_ = session_storage_namespace->id();
NotifyPrerenderStart();
if (call_did_finish_load_)
DidFinishLoad(prerender_contents_->GetMainFrame(), url_);
}
bool DummyPrerenderContents::GetChildId(int* child_id) const {
*child_id = 1;
return true;
}
bool DummyPrerenderContents::GetRouteId(int* route_id) const {
*route_id = 1;
return true;
}
PrerenderContents* DummyPrerenderContentsFactory::CreatePrerenderContents(
PrerenderManager* prerender_manager,
Profile* profile,
const GURL& url,
const Referrer& referrer,
Origin origin) {
return new DummyPrerenderContents(prerender_manager, profile, url, referrer,
origin, call_did_finish_load_);
}
} // namespace
class InstantSearchPrerendererTest : public InstantUnitTestBase {
public:
InstantSearchPrerendererTest() {}
protected:
void SetUp() override {
ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial("EmbeddedSearch",
"Group1 strk:20"));
InstantUnitTestBase::SetUp();
}
void Init(bool prerender_search_results_base_page,
bool call_did_finish_load) {
AddTab(browser(), GURL(url::kAboutBlankURL));
PrerenderManagerFactory::GetForProfile(browser()->profile())->
SetPrerenderContentsFactory(
new DummyPrerenderContentsFactory(call_did_finish_load));
if (prerender_search_results_base_page) {
content::SessionStorageNamespace* session_storage_namespace =
GetActiveWebContents()->GetController().
GetDefaultSessionStorageNamespace();
InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
prerenderer->Init(session_storage_namespace, gfx::Size(640, 480));
EXPECT_NE(static_cast<PrerenderHandle*>(NULL), prerender_handle());
}
}
InstantSearchPrerenderer* GetInstantSearchPrerenderer() {
return instant_service_->instant_search_prerenderer();
}
const GURL& GetPrerenderURL() {
return GetInstantSearchPrerenderer()->prerender_url_;
}
void SetLastQuery(const base::string16& query) {
GetInstantSearchPrerenderer()->last_instant_suggestion_ =
InstantSuggestion(query, std::string());
}
content::WebContents* prerender_contents() {
return GetInstantSearchPrerenderer()->prerender_contents();
}
bool MessageWasSent(uint32_t id) {
content::MockRenderProcessHost* process =
static_cast<content::MockRenderProcessHost*>(
prerender_contents()->GetRenderViewHost()->GetProcess());
return process->sink().GetFirstMessageMatching(id) != NULL;
}
content::WebContents* GetActiveWebContents() const {
return browser()->tab_strip_model()->GetWebContentsAt(0);
}
PrerenderHandle* prerender_handle() {
return GetInstantSearchPrerenderer()->prerender_handle_.get();
}
void PrerenderSearchQuery(const base::string16& query) {
Init(true, true);
InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
prerenderer->Prerender(InstantSuggestion(query, std::string()));
CommitPendingLoad(&prerender_contents()->GetController());
EXPECT_TRUE(prerenderer->CanCommitQuery(GetActiveWebContents(), query));
EXPECT_NE(static_cast<PrerenderHandle*>(NULL), prerender_handle());
}
};
TEST_F(InstantSearchPrerendererTest, GetSearchTermsFromPrerenderedPage) {
Init(false, false);
InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
GURL url(GetPrerenderURL());
EXPECT_EQ(GURL("https://www.google.com/instant?ion=1&foo=foo#foo=foo&strk"),
url);
EXPECT_EQ(
base::UTF16ToASCII(prerenderer->get_last_query()),
base::UTF16ToASCII(search::ExtractSearchTermsFromURL(profile(), url)));
// Assume the prerendered page prefetched search results for the query
// "flowers".
SetLastQuery(ASCIIToUTF16("flowers"));
EXPECT_EQ("flowers", base::UTF16ToASCII(prerenderer->get_last_query()));
EXPECT_EQ(
base::UTF16ToASCII(prerenderer->get_last_query()),
base::UTF16ToASCII(search::ExtractSearchTermsFromURL(profile(), url)));
}
TEST_F(InstantSearchPrerendererTest, PrefetchSearchResults) {
Init(true, true);
EXPECT_TRUE(prerender_handle()->IsFinishedLoading());
InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
prerenderer->Prerender(
InstantSuggestion(ASCIIToUTF16("flowers"), std::string()));
EXPECT_EQ("flowers", base::UTF16ToASCII(prerenderer->get_last_query()));
EXPECT_TRUE(MessageWasSent(
ChromeViewMsg_SearchBoxSetSuggestionToPrefetch::ID));
}
TEST_F(InstantSearchPrerendererTest, DoNotPrefetchSearchResults) {
Init(true, false);
// Page hasn't finished loading yet.
EXPECT_FALSE(prerender_handle()->IsFinishedLoading());
InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
prerenderer->Prerender(
InstantSuggestion(ASCIIToUTF16("flowers"), std::string()));
EXPECT_EQ("", base::UTF16ToASCII(prerenderer->get_last_query()));
EXPECT_FALSE(MessageWasSent(
ChromeViewMsg_SearchBoxSetSuggestionToPrefetch::ID));
}
TEST_F(InstantSearchPrerendererTest, CanCommitQuery) {
Init(true, true);
InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
base::string16 query = ASCIIToUTF16("flowers");
prerenderer->Prerender(InstantSuggestion(query, std::string()));
EXPECT_TRUE(prerenderer->CanCommitQuery(GetActiveWebContents(), query));
// Make sure InstantSearchPrerenderer::CanCommitQuery() returns false for
// invalid search queries.
EXPECT_TRUE(prerenderer->CanCommitQuery(GetActiveWebContents(),
ASCIIToUTF16("joy")));
EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(),
base::string16()));
}
TEST_F(InstantSearchPrerendererTest, CommitQuery) {
base::string16 query = ASCIIToUTF16("flowers");
PrerenderSearchQuery(query);
InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
prerenderer->Commit(query, EmbeddedSearchRequestParams());
EXPECT_TRUE(MessageWasSent(ChromeViewMsg_SearchBoxSubmit::ID));
}
TEST_F(InstantSearchPrerendererTest, CancelPrerenderRequestOnTabChangeEvent) {
Init(true, true);
EXPECT_NE(static_cast<PrerenderHandle*>(NULL), prerender_handle());
// Add a new tab to deactivate the current tab.
AddTab(browser(), GURL(url::kAboutBlankURL));
EXPECT_EQ(2, browser()->tab_strip_model()->count());
// Make sure the pending prerender request is cancelled.
EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
}
TEST_F(InstantSearchPrerendererTest, CancelPendingPrerenderRequest) {
Init(true, true);
EXPECT_NE(static_cast<PrerenderHandle*>(NULL), prerender_handle());
InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
prerenderer->Cancel();
EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
}
TEST_F(InstantSearchPrerendererTest, PrerenderingAllowed) {
Init(true, true);
InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
content::WebContents* active_tab = GetActiveWebContents();
EXPECT_EQ(GURL(url::kAboutBlankURL), active_tab->GetURL());
// Allow prerendering only for search type AutocompleteMatch suggestions.
AutocompleteMatch search_type_match(NULL, 1100, false,
AutocompleteMatchType::SEARCH_SUGGEST);
search_type_match.keyword = ASCIIToUTF16("{google:baseurl}");
EXPECT_TRUE(AutocompleteMatch::IsSearchType(search_type_match.type));
EXPECT_TRUE(prerenderer->IsAllowed(search_type_match, active_tab));
// Do not allow prerendering for custom search provider requests.
TemplateURLData data;
data.SetURL("https://www.dummyurl.com/search?q=%s&img=1");
data.SetShortName(ASCIIToUTF16("t"));
data.SetKeyword(ASCIIToUTF16("k"));
TemplateURL* t_url = new TemplateURL(data);
TemplateURLService* service =
TemplateURLServiceFactory::GetForProfile(profile());
service->Add(t_url);
service->Load();
AutocompleteMatch custom_search_type_match(
NULL, 1100, false, AutocompleteMatchType::SEARCH_SUGGEST);
custom_search_type_match.keyword = ASCIIToUTF16("k");
custom_search_type_match.destination_url =
GURL("https://www.dummyurl.com/search?q=fan&img=1");
TemplateURL* template_url =
custom_search_type_match.GetTemplateURL(service, false);
EXPECT_TRUE(template_url);
EXPECT_TRUE(AutocompleteMatch::IsSearchType(custom_search_type_match.type));
EXPECT_FALSE(prerenderer->IsAllowed(custom_search_type_match, active_tab));
AutocompleteMatch url_type_match(NULL, 1100, true,
AutocompleteMatchType::URL_WHAT_YOU_TYPED);
EXPECT_FALSE(AutocompleteMatch::IsSearchType(url_type_match.type));
EXPECT_FALSE(prerenderer->IsAllowed(url_type_match, active_tab));
// Search results page supports Instant search. InstantSearchPrerenderer is
// used only when the underlying page doesn't support Instant.
NavigateAndCommitActiveTab(GURL("https://www.google.com/alt#quux=foo&strk"));
active_tab = GetActiveWebContents();
EXPECT_FALSE(
search::ExtractSearchTermsFromURL(profile(), active_tab->GetURL())
.empty());
EXPECT_FALSE(search::ShouldPrefetchSearchResultsOnSRP());
EXPECT_FALSE(prerenderer->IsAllowed(search_type_match, active_tab));
}
TEST_F(InstantSearchPrerendererTest, UsePrerenderPage) {
PrerenderSearchQuery(ASCIIToUTF16("foo"));
// Open a search results page. A prerendered page exists for |url|. Make sure
// the browser swaps the current tab contents with the prerendered contents.
GURL url("https://www.google.com/alt#quux=foo&strk");
browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
ui::PAGE_TRANSITION_TYPED,
false));
EXPECT_EQ(GetPrerenderURL(), GetActiveWebContents()->GetURL());
EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
}
TEST_F(InstantSearchPrerendererTest, PrerenderRequestCancelled) {
PrerenderSearchQuery(ASCIIToUTF16("foo"));
// Cancel the prerender request.
InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
prerenderer->Cancel();
EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
// Open a search results page. Prerendered page does not exists for |url|.
// Make sure the browser navigates the current tab to this |url|.
GURL url("https://www.google.com/alt#quux=foo&strk");
browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
ui::PAGE_TRANSITION_TYPED,
false));
EXPECT_NE(GetPrerenderURL(), GetActiveWebContents()->GetURL());
EXPECT_EQ(url, GetActiveWebContents()->GetURL());
}
TEST_F(InstantSearchPrerendererTest,
UsePrerenderedPage_SearchQueryMistmatch) {
PrerenderSearchQuery(ASCIIToUTF16("foo"));
// Open a search results page. Committed query("pen") doesn't match with the
// prerendered search query("foo"). Make sure the browser swaps the current
// tab contents with the prerendered contents.
GURL url("https://www.google.com/alt#quux=pen&strk");
browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
ui::PAGE_TRANSITION_TYPED,
false));
EXPECT_EQ(GetPrerenderURL(), GetActiveWebContents()->GetURL());
EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
}
TEST_F(InstantSearchPrerendererTest,
CancelPrerenderRequest_EmptySearchQueryCommitted) {
PrerenderSearchQuery(ASCIIToUTF16("foo"));
// Open a search results page. Make sure the InstantSearchPrerenderer cancels
// the active prerender request upon the receipt of empty search query.
GURL url("https://www.google.com/alt#quux=&strk");
browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
ui::PAGE_TRANSITION_TYPED,
false));
EXPECT_NE(GetPrerenderURL(), GetActiveWebContents()->GetURL());
EXPECT_EQ(url, GetActiveWebContents()->GetURL());
EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
}
TEST_F(InstantSearchPrerendererTest,
CancelPrerenderRequest_UnsupportedDispositions) {
PrerenderSearchQuery(ASCIIToUTF16("pen"));
// Open a search results page. Make sure the InstantSearchPrerenderer cancels
// the active prerender request for unsupported window dispositions.
GURL url("https://www.google.com/alt#quux=pen&strk");
browser()->OpenURL(content::OpenURLParams(url, Referrer(), NEW_FOREGROUND_TAB,
ui::PAGE_TRANSITION_TYPED,
false));
content::WebContents* new_tab =
browser()->tab_strip_model()->GetWebContentsAt(1);
EXPECT_NE(GetPrerenderURL(), new_tab->GetURL());
EXPECT_EQ(url, new_tab->GetURL());
EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
}
class ReuseInstantSearchBasePageTest : public InstantSearchPrerendererTest {
public:
ReuseInstantSearchBasePageTest() {}
protected:
void SetUp() override {
ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial("EmbeddedSearch",
"Group1 strk:20"));
InstantUnitTestBase::SetUp();
}
};
TEST_F(ReuseInstantSearchBasePageTest, CanCommitQuery) {
Init(true, true);
InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
base::string16 query = ASCIIToUTF16("flowers");
prerenderer->Prerender(InstantSuggestion(query, std::string()));
EXPECT_TRUE(prerenderer->CanCommitQuery(GetActiveWebContents(), query));
// When the Instant search base page has finished loading,
// InstantSearchPrerenderer can commit any search query to the prerendered
// page (even if it doesn't match the last known suggestion query).
EXPECT_TRUE(prerenderer->CanCommitQuery(GetActiveWebContents(),
ASCIIToUTF16("joy")));
// Invalid search query committed.
EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(),
base::string16()));
}
TEST_F(ReuseInstantSearchBasePageTest,
CanCommitQuery_InstantSearchBasePageLoadInProgress) {
Init(true, false);
InstantSearchPrerenderer* prerenderer = GetInstantSearchPrerenderer();
base::string16 query = ASCIIToUTF16("flowers");
prerenderer->Prerender(InstantSuggestion(query, std::string()));
// When the Instant search base page hasn't finished loading,
// InstantSearchPrerenderer cannot commit any search query to the base page.
EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(), query));
EXPECT_FALSE(prerenderer->CanCommitQuery(GetActiveWebContents(),
ASCIIToUTF16("joy")));
}
#if !defined(OS_IOS) && !defined(OS_ANDROID)
class TestUsePrerenderPage : public InstantSearchPrerendererTest {
protected:
void SetUp() override {
// Disable query extraction flag in field trials.
ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
"EmbeddedSearch", "Group1 strk:20 query_extraction:0"));
InstantUnitTestBase::SetUpWithoutQueryExtraction();
}
};
TEST_F(TestUsePrerenderPage, ExtractSearchTermsAndUsePrerenderPage) {
PrerenderSearchQuery(ASCIIToUTF16("foo"));
// Open a search results page. Query extraction flag is disabled in field
// trials. Search results page URL does not contain search terms replacement
// key. Make sure UsePrerenderedPage() extracts the search terms from the URL
// and uses the prerendered page contents.
GURL url("https://www.google.com/alt#quux=foo");
browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
ui::PAGE_TRANSITION_TYPED,
false));
EXPECT_EQ(GetPrerenderURL(), GetActiveWebContents()->GetURL());
EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
}
TEST_F(TestUsePrerenderPage, DoNotUsePrerenderPage) {
PrerenderSearchQuery(ASCIIToUTF16("foo"));
// Do not use prerendered page for renderer initiated search request.
GURL url("https://www.google.com/alt#quux=foo");
browser()->OpenURL(content::OpenURLParams(url, Referrer(), CURRENT_TAB,
ui::PAGE_TRANSITION_LINK,
true /* is_renderer_initiated */));
EXPECT_NE(GetPrerenderURL(), GetActiveWebContents()->GetURL());
EXPECT_EQ(static_cast<PrerenderHandle*>(NULL), prerender_handle());
}
TEST_F(TestUsePrerenderPage, SetEmbeddedSearchRequestParams) {
PrerenderSearchQuery(ASCIIToUTF16("foo"));
EXPECT_TRUE(browser()->instant_controller());
// Open a search results page. Query extraction flag is disabled in field
// trials. Search results page URL does not contain search terms replacement
// key.
GURL url("https://www.google.com/url?bar=foo&aqs=chrome...0&ie=utf-8&oq=f");
browser()->instant_controller()->OpenInstant(CURRENT_TAB, url);
content::MockRenderProcessHost* process =
static_cast<content::MockRenderProcessHost*>(
prerender_contents()->GetRenderViewHost()->GetProcess());
const IPC::Message* message = process->sink().GetFirstMessageMatching(
ChromeViewMsg_SearchBoxSubmit::ID);
ASSERT_TRUE(message);
// Verify the IPC message params.
base::Tuple<base::string16, EmbeddedSearchRequestParams> params;
ChromeViewMsg_SearchBoxSubmit::Read(message, &params);
EXPECT_EQ("foo", base::UTF16ToASCII(base::get<0>(params)));
EXPECT_EQ("f", base::UTF16ToASCII(base::get<1>(params).original_query));
EXPECT_EQ("utf-8", base::UTF16ToASCII(base::get<1>(params).input_encoding));
EXPECT_EQ("", base::UTF16ToASCII(base::get<1>(params).rlz_parameter_value));
EXPECT_EQ("chrome...0",
base::UTF16ToASCII(base::get<1>(params).assisted_query_stats));
}
#endif