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/search_tab_helper.h"
6
7#include "base/command_line.h"
8#include "base/memory/scoped_ptr.h"
9#include "base/strings/utf_string_conversions.h"
10#include "chrome/browser/search/search.h"
11#include "chrome/browser/search_engines/template_url_service.h"
12#include "chrome/browser/search_engines/template_url_service_factory.h"
13#include "chrome/browser/signin/fake_signin_manager.h"
14#include "chrome/browser/signin/signin_manager_factory.h"
15#include "chrome/browser/sync/profile_sync_service.h"
16#include "chrome/browser/sync/profile_sync_service_factory.h"
17#include "chrome/browser/sync/profile_sync_service_mock.h"
18#include "chrome/browser/ui/search/search_ipc_router.h"
19#include "chrome/browser/ui/tabs/tab_strip_model.h"
20#include "chrome/common/chrome_switches.h"
21#include "chrome/common/ntp_logging_events.h"
22#include "chrome/common/omnibox_focus_state.h"
23#include "chrome/common/render_messages.h"
24#include "chrome/common/url_constants.h"
25#include "chrome/test/base/browser_with_test_window_test.h"
26#include "chrome/test/base/chrome_render_view_host_test_harness.h"
27#include "chrome/test/base/testing_profile.h"
28#include "chrome/test/base/ui_test_utils.h"
29#include "content/public/browser/navigation_controller.h"
30#include "content/public/browser/navigation_entry.h"
31#include "content/public/browser/web_contents.h"
32#include "content/public/test/mock_render_process_host.h"
33#include "grit/generated_resources.h"
34#include "ipc/ipc_message.h"
35#include "ipc/ipc_test_sink.h"
36#include "net/base/net_errors.h"
37#include "testing/gmock/include/gmock/gmock.h"
38#include "testing/gtest/include/gtest/gtest.h"
39#include "ui/base/l10n/l10n_util.h"
40#include "url/gurl.h"
41
42using testing::Return;
43
44namespace {
45
46class MockSearchIPCRouterDelegate : public SearchIPCRouter::Delegate {
47 public:
48  virtual ~MockSearchIPCRouterDelegate() {}
49
50  MOCK_METHOD1(OnInstantSupportDetermined, void(bool supports_instant));
51  MOCK_METHOD1(OnSetVoiceSearchSupport, void(bool supports_voice_search));
52  MOCK_METHOD1(FocusOmnibox, void(OmniboxFocusState state));
53  MOCK_METHOD3(NavigateToURL, void(const GURL&, WindowOpenDisposition, bool));
54  MOCK_METHOD1(OnDeleteMostVisitedItem, void(const GURL& url));
55  MOCK_METHOD1(OnUndoMostVisitedDeletion, void(const GURL& url));
56  MOCK_METHOD0(OnUndoAllMostVisitedDeletions, void());
57  MOCK_METHOD1(OnLogEvent, void(NTPLoggingEventType event));
58  MOCK_METHOD2(OnLogMostVisitedImpression,
59               void(int position, const base::string16& provider));
60  MOCK_METHOD2(OnLogMostVisitedNavigation,
61               void(int position, const base::string16& provider));
62  MOCK_METHOD1(PasteIntoOmnibox, void(const base::string16&));
63  MOCK_METHOD1(OnChromeIdentityCheck, void(const base::string16& identity));
64};
65
66}  // namespace
67
68class SearchTabHelperTest : public ChromeRenderViewHostTestHarness {
69 public:
70  virtual void SetUp() {
71    ChromeRenderViewHostTestHarness::SetUp();
72    SearchTabHelper::CreateForWebContents(web_contents());
73  }
74
75  virtual content::BrowserContext* CreateBrowserContext() OVERRIDE {
76    TestingProfile::Builder builder;
77    builder.AddTestingFactory(SigninManagerFactory::GetInstance(),
78                              FakeSigninManagerBase::Build);
79    builder.AddTestingFactory(
80        ProfileSyncServiceFactory::GetInstance(),
81        ProfileSyncServiceMock::BuildMockProfileSyncService);
82    return builder.Build().release();
83  }
84
85  // Creates a sign-in manager for tests.  If |username| is not empty, the
86  // testing profile of the WebContents will be connected to the given account.
87  // The account can be configured to |sync_history| or not.
88  void CreateSigninManager(const std::string& username, bool sync_history) {
89    SigninManagerBase* signin_manager = static_cast<SigninManagerBase*>(
90        SigninManagerFactory::GetForProfile(profile()));
91
92    if (!username.empty()) {
93      ASSERT_TRUE(signin_manager);
94      signin_manager->SetAuthenticatedUsername(username);
95    }
96
97    ProfileSyncServiceMock* sync_service = static_cast<ProfileSyncServiceMock*>(
98        ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile()));
99
100    EXPECT_CALL(*sync_service, sync_initialized()).WillRepeatedly(Return(true));
101    syncer::ModelTypeSet result;
102    if (sync_history) {
103      result.Put(syncer::HISTORY_DELETE_DIRECTIVES);
104    }
105    EXPECT_CALL(*sync_service, GetActiveDataTypes())
106        .WillRepeatedly(Return(result));
107  }
108
109  bool MessageWasSent(uint32 id) {
110    return process()->sink().GetFirstMessageMatching(id) != NULL;
111  }
112
113  MockSearchIPCRouterDelegate* mock_delegate() { return &delegate_; }
114
115 private:
116  MockSearchIPCRouterDelegate delegate_;
117};
118
119TEST_F(SearchTabHelperTest, DetermineIfPageSupportsInstant_Local) {
120  NavigateAndCommit(GURL(chrome::kChromeSearchLocalNtpUrl));
121  EXPECT_CALL(*mock_delegate(), OnInstantSupportDetermined(true)).Times(0);
122
123  SearchTabHelper* search_tab_helper =
124      SearchTabHelper::FromWebContents(web_contents());
125  ASSERT_NE(static_cast<SearchTabHelper*>(NULL), search_tab_helper);
126  search_tab_helper->ipc_router().set_delegate_for_testing(mock_delegate());
127  search_tab_helper->DetermineIfPageSupportsInstant();
128}
129
130TEST_F(SearchTabHelperTest, DetermineIfPageSupportsInstant_NonLocal) {
131  NavigateAndCommit(GURL("chrome-search://foo/bar"));
132  process()->sink().ClearMessages();
133  EXPECT_CALL(*mock_delegate(), OnInstantSupportDetermined(true)).Times(1);
134
135  SearchTabHelper* search_tab_helper =
136      SearchTabHelper::FromWebContents(web_contents());
137  ASSERT_NE(static_cast<SearchTabHelper*>(NULL), search_tab_helper);
138  search_tab_helper->ipc_router().set_delegate_for_testing(mock_delegate());
139  search_tab_helper->DetermineIfPageSupportsInstant();
140  ASSERT_TRUE(MessageWasSent(ChromeViewMsg_DetermineIfPageSupportsInstant::ID));
141
142  scoped_ptr<IPC::Message> response(
143      new ChromeViewHostMsg_InstantSupportDetermined(
144          web_contents()->GetRoutingID(),
145          search_tab_helper->ipc_router().page_seq_no_for_testing(),
146          true));
147  search_tab_helper->ipc_router().OnMessageReceived(*response);
148}
149
150TEST_F(SearchTabHelperTest, PageURLDoesntBelongToInstantRenderer) {
151  // Navigate to a page URL that doesn't belong to Instant renderer.
152  // SearchTabHelper::DeterminerIfPageSupportsInstant() should return
153  // immediately without dispatching any message to the renderer.
154  NavigateAndCommit(GURL("http://www.example.com"));
155  process()->sink().ClearMessages();
156  EXPECT_CALL(*mock_delegate(), OnInstantSupportDetermined(false)).Times(0);
157
158  SearchTabHelper* search_tab_helper =
159      SearchTabHelper::FromWebContents(web_contents());
160  ASSERT_NE(static_cast<SearchTabHelper*>(NULL), search_tab_helper);
161  search_tab_helper->ipc_router().set_delegate_for_testing(mock_delegate());
162  search_tab_helper->DetermineIfPageSupportsInstant();
163  ASSERT_FALSE(MessageWasSent(
164      ChromeViewMsg_DetermineIfPageSupportsInstant::ID));
165}
166
167TEST_F(SearchTabHelperTest, OnChromeIdentityCheckMatch) {
168  NavigateAndCommit(GURL(chrome::kChromeSearchLocalNtpUrl));
169  CreateSigninManager(std::string("foo@bar.com"), true);
170  SearchTabHelper* search_tab_helper =
171      SearchTabHelper::FromWebContents(web_contents());
172  ASSERT_NE(static_cast<SearchTabHelper*>(NULL), search_tab_helper);
173
174  const base::string16 test_identity = base::ASCIIToUTF16("foo@bar.com");
175  search_tab_helper->OnChromeIdentityCheck(test_identity);
176
177  const IPC::Message* message = process()->sink().GetUniqueMessageMatching(
178      ChromeViewMsg_ChromeIdentityCheckResult::ID);
179  ASSERT_TRUE(message != NULL);
180
181  ChromeViewMsg_ChromeIdentityCheckResult::Param params;
182  ChromeViewMsg_ChromeIdentityCheckResult::Read(message, &params);
183  EXPECT_EQ(test_identity, params.a);
184  ASSERT_TRUE(params.b);
185}
186
187TEST_F(SearchTabHelperTest, OnChromeIdentityCheckMismatch) {
188  NavigateAndCommit(GURL(chrome::kChromeSearchLocalNtpUrl));
189  CreateSigninManager(std::string("foo@bar.com"), true);
190  SearchTabHelper* search_tab_helper =
191      SearchTabHelper::FromWebContents(web_contents());
192  ASSERT_NE(static_cast<SearchTabHelper*>(NULL), search_tab_helper);
193
194  const base::string16 test_identity = base::ASCIIToUTF16("bar@foo.com");
195  search_tab_helper->OnChromeIdentityCheck(test_identity);
196
197  const IPC::Message* message = process()->sink().GetUniqueMessageMatching(
198      ChromeViewMsg_ChromeIdentityCheckResult::ID);
199  ASSERT_TRUE(message != NULL);
200
201  ChromeViewMsg_ChromeIdentityCheckResult::Param params;
202  ChromeViewMsg_ChromeIdentityCheckResult::Read(message, &params);
203  EXPECT_EQ(test_identity, params.a);
204  ASSERT_FALSE(params.b);
205}
206
207TEST_F(SearchTabHelperTest, OnChromeIdentityCheckSignedOutMatch) {
208  NavigateAndCommit(GURL(chrome::kChromeSearchLocalNtpUrl));
209  // This test does not sign in.
210  ProfileSyncServiceMock* sync_service = static_cast<ProfileSyncServiceMock*>(
211      ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile()));
212  EXPECT_CALL(*sync_service, sync_initialized()).WillRepeatedly(Return(false));
213  SearchTabHelper* search_tab_helper =
214      SearchTabHelper::FromWebContents(web_contents());
215  ASSERT_NE(static_cast<SearchTabHelper*>(NULL), search_tab_helper);
216
217  const base::string16 test_identity;
218  search_tab_helper->OnChromeIdentityCheck(test_identity);
219
220  const IPC::Message* message = process()->sink().GetUniqueMessageMatching(
221      ChromeViewMsg_ChromeIdentityCheckResult::ID);
222  ASSERT_TRUE(message != NULL);
223
224  ChromeViewMsg_ChromeIdentityCheckResult::Param params;
225  ChromeViewMsg_ChromeIdentityCheckResult::Read(message, &params);
226  EXPECT_EQ(test_identity, params.a);
227  ASSERT_FALSE(params.b);
228}
229
230TEST_F(SearchTabHelperTest, OnChromeIdentityCheckSignedOutMismatch) {
231  NavigateAndCommit(GURL(chrome::kChromeSearchLocalNtpUrl));
232  // This test does not sign in.
233  ProfileSyncServiceMock* sync_service = static_cast<ProfileSyncServiceMock*>(
234      ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile()));
235  EXPECT_CALL(*sync_service, sync_initialized()).WillRepeatedly(Return(false));
236  SearchTabHelper* search_tab_helper =
237      SearchTabHelper::FromWebContents(web_contents());
238  ASSERT_NE(static_cast<SearchTabHelper*>(NULL), search_tab_helper);
239
240  const base::string16 test_identity = base::ASCIIToUTF16("bar@foo.com");
241  search_tab_helper->OnChromeIdentityCheck(test_identity);
242
243  const IPC::Message* message = process()->sink().GetUniqueMessageMatching(
244      ChromeViewMsg_ChromeIdentityCheckResult::ID);
245  ASSERT_TRUE(message != NULL);
246
247  ChromeViewMsg_ChromeIdentityCheckResult::Param params;
248  ChromeViewMsg_ChromeIdentityCheckResult::Read(message, &params);
249  EXPECT_EQ(test_identity, params.a);
250  ASSERT_FALSE(params.b);
251}
252
253TEST_F(SearchTabHelperTest, OnChromeIdentityCheckMatchNotSyncing) {
254  NavigateAndCommit(GURL(chrome::kChromeSearchLocalNtpUrl));
255  CreateSigninManager(std::string("foo@bar.com"), false);
256  SearchTabHelper* search_tab_helper =
257      SearchTabHelper::FromWebContents(web_contents());
258  ASSERT_NE(static_cast<SearchTabHelper*>(NULL), search_tab_helper);
259
260  const base::string16 test_identity = base::ASCIIToUTF16("foo@bar.com");
261  search_tab_helper->OnChromeIdentityCheck(test_identity);
262
263  const IPC::Message* message = process()->sink().GetUniqueMessageMatching(
264      ChromeViewMsg_ChromeIdentityCheckResult::ID);
265  ASSERT_TRUE(message != NULL);
266
267  ChromeViewMsg_ChromeIdentityCheckResult::Param params;
268  ChromeViewMsg_ChromeIdentityCheckResult::Read(message, &params);
269  EXPECT_EQ(test_identity, params.a);
270  ASSERT_FALSE(params.b);
271}
272
273class TabTitleObserver : public content::WebContentsObserver {
274 public:
275  explicit TabTitleObserver(content::WebContents* contents)
276      : WebContentsObserver(contents) {}
277
278  base::string16 title_on_start() { return title_on_start_; }
279  base::string16 title_on_commit() { return title_on_commit_; }
280
281 private:
282  virtual void DidStartProvisionalLoadForFrame(
283      int64 /* frame_id */,
284      int64 /* parent_frame_id */,
285      bool /* is_main_frame */,
286      const GURL& /* validated_url */,
287      bool /* is_error_page */,
288      bool /* is_iframe_srcdoc */,
289      content::RenderViewHost* /* render_view_host */) OVERRIDE {
290    title_on_start_ = web_contents()->GetTitle();
291  }
292
293  virtual void DidNavigateMainFrame(
294      const content::LoadCommittedDetails& /* details */,
295      const content::FrameNavigateParams& /* params */) OVERRIDE {
296    title_on_commit_ = web_contents()->GetTitle();
297  }
298
299  base::string16 title_on_start_;
300  base::string16 title_on_commit_;
301};
302
303TEST_F(SearchTabHelperTest, TitleIsSetForNTP) {
304  TabTitleObserver title_observer(web_contents());
305  NavigateAndCommit(GURL(chrome::kChromeUINewTabURL));
306  const base::string16 title = l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE);
307  EXPECT_EQ(title, title_observer.title_on_start());
308  EXPECT_EQ(title, title_observer.title_on_commit());
309  EXPECT_EQ(title, web_contents()->GetTitle());
310}
311
312class SearchTabHelperWindowTest : public BrowserWithTestWindowTest {
313 protected:
314  virtual void SetUp() OVERRIDE {
315    BrowserWithTestWindowTest::SetUp();
316    TemplateURLServiceFactory::GetInstance()->SetTestingFactoryAndUse(
317        profile(), &TemplateURLServiceFactory::BuildInstanceFor);
318    TemplateURLService* template_url_service =
319        TemplateURLServiceFactory::GetForProfile(profile());
320    ui_test_utils::WaitForTemplateURLServiceToLoad(template_url_service);
321
322    TemplateURLData data;
323    data.SetURL("http://foo.com/url?bar={searchTerms}");
324    data.instant_url = "http://foo.com/instant?"
325        "{google:omniboxStartMarginParameter}{google:forceInstantResults}"
326        "foo=foo#foo=foo&strk";
327    data.new_tab_url = std::string("https://foo.com/newtab?strk");
328    data.alternate_urls.push_back("http://foo.com/alt#quux={searchTerms}");
329    data.search_terms_replacement_key = "strk";
330
331    TemplateURL* template_url = new TemplateURL(data);
332    template_url_service->Add(template_url);
333    template_url_service->SetUserSelectedDefaultSearchProvider(template_url);
334  }
335};
336
337TEST_F(SearchTabHelperWindowTest, OnProvisionalLoadFailRedirectNTPToLocal) {
338  AddTab(browser(), GURL(chrome::kChromeUINewTabURL));
339  content::WebContents* contents =
340        browser()->tab_strip_model()->GetWebContentsAt(0);
341  content::NavigationController* controller = &contents->GetController();
342
343  SearchTabHelper* search_tab_helper =
344      SearchTabHelper::FromWebContents(contents);
345  ASSERT_NE(static_cast<SearchTabHelper*>(NULL), search_tab_helper);
346
347  // A failed provisional load of a cacheable NTP should be redirected to local
348  // NTP.
349  const GURL cacheableNTPURL = chrome::GetNewTabPageURL(profile());
350  search_tab_helper->DidFailProvisionalLoad(1, base::string16(), true,
351      cacheableNTPURL, 1, base::string16(), NULL);
352  CommitPendingLoad(controller);
353  EXPECT_EQ(GURL(chrome::kChromeSearchLocalNtpUrl),
354                 controller->GetLastCommittedEntry()->GetURL());
355}
356
357TEST_F(SearchTabHelperWindowTest, OnProvisionalLoadFailDontRedirectIfAborted) {
358  AddTab(browser(), GURL("chrome://blank"));
359  content::WebContents* contents =
360        browser()->tab_strip_model()->GetWebContentsAt(0);
361  content::NavigationController* controller = &contents->GetController();
362
363  SearchTabHelper* search_tab_helper =
364      SearchTabHelper::FromWebContents(contents);
365  ASSERT_NE(static_cast<SearchTabHelper*>(NULL), search_tab_helper);
366
367  // A failed provisional load of a cacheable NTP should be redirected to local
368  // NTP.
369  const GURL cacheableNTPURL = chrome::GetNewTabPageURL(profile());
370  search_tab_helper->DidFailProvisionalLoad(1, base::string16(), true,
371      cacheableNTPURL, net::ERR_ABORTED, base::string16(), NULL);
372  CommitPendingLoad(controller);
373  EXPECT_EQ(GURL("chrome://blank"),
374                 controller->GetLastCommittedEntry()->GetURL());
375}
376
377TEST_F(SearchTabHelperWindowTest, OnProvisionalLoadFailDontRedirectNonNTP) {
378  AddTab(browser(), GURL(chrome::kChromeUINewTabURL));
379  content::WebContents* contents =
380        browser()->tab_strip_model()->GetWebContentsAt(0);
381  content::NavigationController* controller = &contents->GetController();
382
383  SearchTabHelper* search_tab_helper =
384      SearchTabHelper::FromWebContents(contents);
385  ASSERT_NE(static_cast<SearchTabHelper*>(NULL), search_tab_helper);
386
387  // Any other web page shouldn't be redirected when provisional load fails.
388  search_tab_helper->DidFailProvisionalLoad(1, base::string16(), true,
389      GURL("http://www.example.com"), 1, base::string16(), NULL);
390  CommitPendingLoad(controller);
391  EXPECT_NE(GURL(chrome::kChromeSearchLocalNtpUrl),
392                 controller->GetLastCommittedEntry()->GetURL());
393}
394