1 // Copyright (c) 2012 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.
8 #include "base/memory/scoped_ptr.h"
9 #include "base/message_loop.h"
10 #include "chrome/browser/sessions/session_types.h"
11 #include "chrome/browser/sessions/session_types_test_helper.h"
12 #include "chrome/browser/sync/glue/session_model_associator.h"
13 #include "chrome/browser/sync/glue/synced_tab_delegate.h"
14 #include "chrome/browser/sync/profile_sync_service_mock.h"
15 #include "chrome/common/chrome_notification_types.h"
16 #include "chrome/common/url_constants.h"
17 #include "chrome/test/base/profile_mock.h"
18 #include "content/public/browser/navigation_entry.h"
19 #include "content/public/browser/notification_details.h"
20 #include "content/public/browser/notification_service.h"
21 #include "content/public/common/page_transition_types.h"
22 #include "content/public/test/test_browser_thread.h"
23 #include "googleurl/src/gurl.h"
24 #include "sync/protocol/session_specifics.pb.h"
25 #include "sync/util/time.h"
26 #include "testing/gmock/include/gmock/gmock.h"
27 #include "testing/gtest/include/gtest/gtest.h"
29 using content::BrowserThread
;
30 using testing::NiceMock
;
31 using testing::Return
;
32 using testing::StrictMock
;
35 namespace browser_sync
{
37 class SyncSessionModelAssociatorTest
: public testing::Test
{
39 SyncSessionModelAssociatorTest()
40 : ui_thread_(BrowserThread::UI
, &message_loop_
),
41 sync_service_(&profile_
),
42 model_associator_(&sync_service_
, true) {}
44 void LoadTabFavicon(const sync_pb::SessionTab
& tab
) {
45 model_associator_
.LoadForeignTabFavicon(tab
);
46 message_loop_
.RunUntilIdle();
49 static GURL
GetCurrentVirtualURL(const SyncedTabDelegate
& tab_delegate
) {
50 return SessionModelAssociator::GetCurrentVirtualURL(tab_delegate
);
53 static void SetSessionTabFromDelegate(
54 const SyncedTabDelegate
& tab_delegate
,
56 SessionTab
* session_tab
) {
57 SessionModelAssociator::SetSessionTabFromDelegate(
63 bool FaviconEquals(const GURL page_url
,
64 std::string expected_bytes
) {
65 FaviconCache
* cache
= model_associator_
.GetFaviconCache();
67 scoped_refptr
<base::RefCountedMemory
> favicon
;
68 if (!cache
->GetSyncedFaviconForPageURL(gurl
, &favicon
))
69 return expected_bytes
.empty();
70 if (favicon
->size() != expected_bytes
.size())
72 for (size_t i
= 0; i
< favicon
->size(); ++i
) {
73 if (expected_bytes
[i
] != *(favicon
->front() + i
))
80 MessageLoopForUI message_loop_
;
81 content::TestBrowserThread ui_thread_
;
82 NiceMock
<ProfileMock
> profile_
;
83 NiceMock
<ProfileSyncServiceMock
> sync_service_
;
86 SessionModelAssociator model_associator_
;
91 TEST_F(SyncSessionModelAssociatorTest
, SessionWindowHasNoTabsToSync
) {
93 ASSERT_TRUE(SessionWindowHasNoTabsToSync(win
));
94 scoped_ptr
<SessionTab
> tab(new SessionTab());
95 win
.tabs
.push_back(tab
.release());
96 ASSERT_TRUE(SessionWindowHasNoTabsToSync(win
));
98 SessionTypesTestHelper::CreateNavigation("about:bubba", "title");
99 win
.tabs
[0]->navigations
.push_back(nav
);
100 ASSERT_FALSE(SessionWindowHasNoTabsToSync(win
));
103 TEST_F(SyncSessionModelAssociatorTest
, ShouldSyncSessionTab
) {
105 ASSERT_FALSE(ShouldSyncSessionTab(tab
));
107 SessionTypesTestHelper::CreateNavigation(
108 chrome::kChromeUINewTabURL
, "title");
109 tab
.navigations
.push_back(nav
);
110 // NewTab does not count as valid if it's the only navigation.
111 ASSERT_FALSE(ShouldSyncSessionTab(tab
));
113 SessionTypesTestHelper::CreateNavigation("about:bubba", "title");
114 tab
.navigations
.push_back(nav2
);
115 // Once there's another navigation, the tab is valid.
116 ASSERT_TRUE(ShouldSyncSessionTab(tab
));
119 TEST_F(SyncSessionModelAssociatorTest
,
120 ShouldSyncSessionTabIgnoresFragmentForNtp
) {
122 ASSERT_FALSE(ShouldSyncSessionTab(tab
));
124 SessionTypesTestHelper::CreateNavigation(
125 std::string(chrome::kChromeUINewTabURL
) + "#bookmarks", "title");
126 tab
.navigations
.push_back(nav
);
127 // NewTab does not count as valid if it's the only navigation.
128 ASSERT_FALSE(ShouldSyncSessionTab(tab
));
133 TEST_F(SyncSessionModelAssociatorTest
, PopulateSessionHeader
) {
134 sync_pb::SessionHeader header_s
;
135 header_s
.set_client_name("Client 1");
136 header_s
.set_device_type(sync_pb::SyncEnums_DeviceType_TYPE_WIN
);
138 SyncedSession session
;
139 base::Time time
= base::Time::Now();
140 SessionModelAssociator::PopulateSessionHeaderFromSpecifics(
141 header_s
, time
, &session
);
142 ASSERT_EQ("Client 1", session
.session_name
);
143 ASSERT_EQ(SyncedSession::TYPE_WIN
, session
.device_type
);
144 ASSERT_EQ(time
, session
.modified_time
);
147 TEST_F(SyncSessionModelAssociatorTest
, PopulateSessionWindow
) {
148 sync_pb::SessionWindow window_s
;
150 window_s
.set_browser_type(sync_pb::SessionWindow_BrowserType_TYPE_TABBED
);
151 window_s
.set_selected_tab_index(1);
153 std::string tag
= "tag";
154 SyncedSessionTracker tracker
;
155 SyncedSession
* session
= tracker
.GetSession(tag
);
156 tracker
.PutWindowInSession(tag
, 0);
157 SessionModelAssociator::PopulateSessionWindowFromSpecifics(
158 tag
, window_s
, base::Time(), session
->windows
[0], &tracker
);
159 ASSERT_EQ(1U, session
->windows
[0]->tabs
.size());
160 ASSERT_EQ(1, session
->windows
[0]->selected_tab_index
);
161 ASSERT_EQ(1, session
->windows
[0]->type
);
162 ASSERT_EQ(1U, tracker
.num_synced_sessions());
163 ASSERT_EQ(1U, tracker
.num_synced_tabs(std::string("tag")));
168 class SyncedTabDelegateMock
: public SyncedTabDelegate
{
170 SyncedTabDelegateMock() {}
171 virtual ~SyncedTabDelegateMock() {}
173 MOCK_CONST_METHOD0(GetWindowId
, SessionID::id_type());
174 MOCK_CONST_METHOD0(GetSessionId
, SessionID::id_type());
175 MOCK_CONST_METHOD0(IsBeingDestroyed
, bool());
176 MOCK_CONST_METHOD0(profile
, Profile
*());
177 MOCK_CONST_METHOD0(GetExtensionAppId
, std::string());
178 MOCK_CONST_METHOD0(GetCurrentEntryIndex
, int());
179 MOCK_CONST_METHOD0(GetEntryCount
, int());
180 MOCK_CONST_METHOD0(GetPendingEntryIndex
, int());
181 MOCK_CONST_METHOD0(GetPendingEntry
, content::NavigationEntry
*());
182 MOCK_CONST_METHOD1(GetEntryAtIndex
, content::NavigationEntry
*(int i
));
183 MOCK_CONST_METHOD0(GetActiveEntry
, content::NavigationEntry
*());
184 MOCK_CONST_METHOD0(IsPinned
, bool());
187 class SyncRefreshListener
: public content::NotificationObserver
{
189 SyncRefreshListener() : notified_of_refresh_(false) {
190 registrar_
.Add(this, chrome::NOTIFICATION_SYNC_REFRESH_LOCAL
,
191 content::NotificationService::AllSources());
194 virtual void Observe(int type
,
195 const content::NotificationSource
& source
,
196 const content::NotificationDetails
& details
) OVERRIDE
{
197 if (type
== chrome::NOTIFICATION_SYNC_REFRESH_LOCAL
) {
198 notified_of_refresh_
= true;
202 bool notified_of_refresh() const { return notified_of_refresh_
; }
205 bool notified_of_refresh_
;
206 content::NotificationRegistrar registrar_
;
209 // Test that AttemptSessionsDataRefresh() triggers the
210 // NOTIFICATION_SYNC_REFRESH_LOCAL notification.
211 TEST_F(SyncSessionModelAssociatorTest
, TriggerSessionRefresh
) {
212 SyncRefreshListener refresh_listener
;
214 EXPECT_FALSE(refresh_listener
.notified_of_refresh());
215 model_associator_
.AttemptSessionsDataRefresh();
216 EXPECT_TRUE(refresh_listener
.notified_of_refresh());
219 // Test that we exclude tabs with only chrome:// and file:// schemed navigations
220 // from ShouldSyncTab(..).
221 TEST_F(SyncSessionModelAssociatorTest
, ValidTabs
) {
222 NiceMock
<SyncedTabDelegateMock
> tab_mock
;
224 // A null entry shouldn't crash.
225 EXPECT_CALL(tab_mock
, GetCurrentEntryIndex()).WillRepeatedly(Return(0));
226 EXPECT_CALL(tab_mock
, GetEntryAtIndex(0)).WillRepeatedly(
227 Return((content::NavigationEntry
*)NULL
));
228 EXPECT_CALL(tab_mock
, GetEntryCount()).WillRepeatedly(Return(1));
229 EXPECT_CALL(tab_mock
, GetPendingEntryIndex()).WillRepeatedly(Return(-1));
230 EXPECT_FALSE(model_associator_
.ShouldSyncTab(tab_mock
));
232 // A chrome:// entry isn't valid.
233 scoped_ptr
<content::NavigationEntry
> entry(
234 content::NavigationEntry::Create());
235 entry
->SetVirtualURL(GURL("chrome://preferences/"));
236 testing::Mock::VerifyAndClearExpectations(&tab_mock
);
237 EXPECT_CALL(tab_mock
, GetCurrentEntryIndex()).WillRepeatedly(Return(0));
238 EXPECT_CALL(tab_mock
, GetEntryAtIndex(0)).WillRepeatedly(Return(entry
.get()));
239 EXPECT_CALL(tab_mock
, GetEntryCount()).WillRepeatedly(Return(1));
240 EXPECT_CALL(tab_mock
, GetPendingEntryIndex()).WillRepeatedly(Return(-1));
241 EXPECT_FALSE(model_associator_
.ShouldSyncTab(tab_mock
));
243 // A file:// entry isn't valid, even in addition to another entry.
244 scoped_ptr
<content::NavigationEntry
> entry2(
245 content::NavigationEntry::Create());
246 entry2
->SetVirtualURL(GURL("file://bla"));
247 testing::Mock::VerifyAndClearExpectations(&tab_mock
);
248 EXPECT_CALL(tab_mock
, GetCurrentEntryIndex()).WillRepeatedly(Return(0));
249 EXPECT_CALL(tab_mock
, GetEntryAtIndex(0)).WillRepeatedly(Return(entry
.get()));
250 EXPECT_CALL(tab_mock
, GetEntryAtIndex(1)).WillRepeatedly(
251 Return(entry2
.get()));
252 EXPECT_CALL(tab_mock
, GetEntryCount()).WillRepeatedly(Return(2));
253 EXPECT_CALL(tab_mock
, GetPendingEntryIndex()).WillRepeatedly(Return(-1));
254 EXPECT_FALSE(model_associator_
.ShouldSyncTab(tab_mock
));
256 // Add a valid scheme entry to tab, making the tab valid.
257 scoped_ptr
<content::NavigationEntry
> entry3(
258 content::NavigationEntry::Create());
259 entry3
->SetVirtualURL(GURL("http://www.google.com"));
260 testing::Mock::VerifyAndClearExpectations(&tab_mock
);
261 EXPECT_CALL(tab_mock
, GetCurrentEntryIndex()).WillRepeatedly(Return(0));
262 EXPECT_CALL(tab_mock
, GetEntryAtIndex(0)).WillRepeatedly(
263 Return(entry
.get()));
264 EXPECT_CALL(tab_mock
, GetEntryAtIndex(1)).WillRepeatedly(
265 Return(entry2
.get()));
266 EXPECT_CALL(tab_mock
, GetEntryAtIndex(2)).WillRepeatedly(
267 Return(entry3
.get()));
268 EXPECT_CALL(tab_mock
, GetEntryCount()).WillRepeatedly(Return(3));
269 EXPECT_CALL(tab_mock
, GetPendingEntryIndex()).WillRepeatedly(Return(-1));
270 EXPECT_TRUE(model_associator_
.ShouldSyncTab(tab_mock
));
273 // TODO(akalin): We should really use a fake for SyncedTabDelegate.
275 // Make sure GetCurrentVirtualURL() returns the virtual URL of the pending
276 // entry if the current entry is pending.
277 TEST_F(SyncSessionModelAssociatorTest
, GetCurrentVirtualURLPending
) {
278 StrictMock
<SyncedTabDelegateMock
> tab_mock
;
279 scoped_ptr
<content::NavigationEntry
> entry(
280 content::NavigationEntry::Create());
281 entry
->SetVirtualURL(GURL("http://www.google.com"));
282 EXPECT_CALL(tab_mock
, GetCurrentEntryIndex()).WillOnce(Return(0));
283 EXPECT_CALL(tab_mock
, GetPendingEntryIndex()).WillOnce(Return(0));
284 EXPECT_CALL(tab_mock
, GetPendingEntry()).WillOnce(Return(entry
.get()));
285 EXPECT_EQ(entry
->GetVirtualURL(), GetCurrentVirtualURL(tab_mock
));
288 // Make sure GetCurrentVirtualURL() returns the virtual URL of the current
289 // entry if the current entry is non-pending.
290 TEST_F(SyncSessionModelAssociatorTest
, GetCurrentVirtualURLNonPending
) {
291 StrictMock
<SyncedTabDelegateMock
> tab_mock
;
292 scoped_ptr
<content::NavigationEntry
> entry(
293 content::NavigationEntry::Create());
294 entry
->SetVirtualURL(GURL("http://www.google.com"));
295 EXPECT_CALL(tab_mock
, GetCurrentEntryIndex()).WillOnce(Return(0));
296 EXPECT_CALL(tab_mock
, GetPendingEntryIndex()).WillOnce(Return(-1));
297 EXPECT_CALL(tab_mock
, GetEntryAtIndex(0)).WillOnce(Return(entry
.get()));
298 EXPECT_EQ(entry
->GetVirtualURL(), GetCurrentVirtualURL(tab_mock
));
301 const base::Time kTime1
= base::Time::FromInternalValue(100);
302 const base::Time kTime2
= base::Time::FromInternalValue(105);
303 const base::Time kTime3
= base::Time::FromInternalValue(110);
304 const base::Time kTime4
= base::Time::FromInternalValue(120);
305 const base::Time kTime5
= base::Time::FromInternalValue(130);
307 // Populate the mock tab delegate with some data and navigation
308 // entries and make sure that setting a SessionTab from it preserves
309 // those entries (and clobbers any existing data).
310 TEST_F(SyncSessionModelAssociatorTest
, SetSessionTabFromDelegate
) {
311 // Create a tab with three valid entries.
312 NiceMock
<SyncedTabDelegateMock
> tab_mock
;
313 EXPECT_CALL(tab_mock
, GetSessionId()).WillRepeatedly(Return(0));
314 scoped_ptr
<content::NavigationEntry
> entry1(
315 content::NavigationEntry::Create());
316 entry1
->SetVirtualURL(GURL("http://www.google.com"));
317 entry1
->SetTimestamp(kTime1
);
318 scoped_ptr
<content::NavigationEntry
> entry2(
319 content::NavigationEntry::Create());
320 entry2
->SetVirtualURL(GURL("http://www.noodle.com"));
321 entry2
->SetTimestamp(kTime2
);
322 scoped_ptr
<content::NavigationEntry
> entry3(
323 content::NavigationEntry::Create());
324 entry3
->SetVirtualURL(GURL("http://www.doodle.com"));
325 entry3
->SetTimestamp(kTime3
);
326 EXPECT_CALL(tab_mock
, GetCurrentEntryIndex()).WillRepeatedly(Return(2));
327 EXPECT_CALL(tab_mock
, GetEntryAtIndex(0)).WillRepeatedly(
328 Return(entry1
.get()));
329 EXPECT_CALL(tab_mock
, GetEntryAtIndex(1)).WillRepeatedly(
330 Return(entry2
.get()));
331 EXPECT_CALL(tab_mock
, GetEntryAtIndex(2)).WillRepeatedly(
332 Return(entry3
.get()));
333 EXPECT_CALL(tab_mock
, GetEntryCount()).WillRepeatedly(Return(3));
334 EXPECT_CALL(tab_mock
, GetPendingEntryIndex()).WillRepeatedly(Return(-1));
336 SessionTab session_tab
;
337 session_tab
.window_id
.set_id(1);
338 session_tab
.tab_id
.set_id(1);
339 session_tab
.tab_visual_index
= 1;
340 session_tab
.current_navigation_index
= 1;
341 session_tab
.pinned
= true;
342 session_tab
.extension_app_id
= "app id";
343 session_tab
.user_agent_override
= "override";
344 session_tab
.timestamp
= kTime5
;
345 session_tab
.navigations
.push_back(
346 SessionTypesTestHelper::CreateNavigation(
347 "http://www.example.com", "Example"));
348 session_tab
.session_storage_persistent_id
= "persistent id";
349 SetSessionTabFromDelegate(tab_mock
, kTime4
, &session_tab
);
351 EXPECT_EQ(0, session_tab
.window_id
.id());
352 EXPECT_EQ(0, session_tab
.tab_id
.id());
353 EXPECT_EQ(0, session_tab
.tab_visual_index
);
354 EXPECT_EQ(2, session_tab
.current_navigation_index
);
355 EXPECT_FALSE(session_tab
.pinned
);
356 EXPECT_TRUE(session_tab
.extension_app_id
.empty());
357 EXPECT_TRUE(session_tab
.user_agent_override
.empty());
358 EXPECT_EQ(kTime4
, session_tab
.timestamp
);
359 ASSERT_EQ(3u, session_tab
.navigations
.size());
360 EXPECT_EQ(entry1
->GetVirtualURL(),
361 session_tab
.navigations
[0].virtual_url());
362 EXPECT_EQ(entry2
->GetVirtualURL(),
363 session_tab
.navigations
[1].virtual_url());
364 EXPECT_EQ(entry3
->GetVirtualURL(),
365 session_tab
.navigations
[2].virtual_url());
367 SessionTypesTestHelper::GetTimestamp(session_tab
.navigations
[0]));
369 SessionTypesTestHelper::GetTimestamp(session_tab
.navigations
[1]));
371 SessionTypesTestHelper::GetTimestamp(session_tab
.navigations
[2]));
372 EXPECT_TRUE(session_tab
.session_storage_persistent_id
.empty());
375 // Create tab specifics with an empty favicon. Ensure it gets ignored and not
376 // stored into the synced favicon lookups.
377 TEST_F(SyncSessionModelAssociatorTest
, LoadEmptyFavicon
) {
379 std::string favicon_url
= "http://www.faviconurl.com/favicon.ico";
380 std::string page_url
= "http://www.faviconurl.com/page.html";
381 sync_pb::SessionTab tab
;
382 tab
.set_favicon(favicon
);
383 tab
.set_favicon_source(favicon_url
);
384 tab
.set_favicon_type(sync_pb::SessionTab::TYPE_WEB_FAVICON
);
385 sync_pb::TabNavigation
* navigation
= tab
.add_navigation();
386 navigation
->set_virtual_url(page_url
);
387 tab
.set_current_navigation_index(0);
389 EXPECT_TRUE(FaviconEquals(GURL(page_url
), std::string()));
391 EXPECT_TRUE(FaviconEquals(GURL(page_url
), std::string()));
394 // Create tab specifics with a non-web favicon. Ensure it gets ignored and not
395 // stored into the synced favicon lookups.
396 TEST_F(SyncSessionModelAssociatorTest
, LoadNonWebFavicon
) {
397 std::string favicon
= "icon bytes";
398 std::string favicon_url
= "http://www.faviconurl.com/favicon.ico";
399 std::string page_url
= "http://www.faviconurl.com/page.html";
400 sync_pb::SessionTab tab
;
401 tab
.set_favicon(favicon
);
402 tab
.set_favicon_source(favicon_url
);
403 // Set favicon type to an unsupported value (1 == WEB_FAVICON).
404 tab
.mutable_unknown_fields()->AddVarint(9, 2);
405 sync_pb::TabNavigation
* navigation
= tab
.add_navigation();
406 navigation
->set_virtual_url(page_url
);
407 tab
.set_current_navigation_index(0);
409 EXPECT_TRUE(FaviconEquals(GURL(page_url
), std::string()));
411 EXPECT_TRUE(FaviconEquals(GURL(page_url
), std::string()));
414 // Create tab specifics with a valid favicon. Ensure it gets stored in the
415 // synced favicon lookups and is accessible by the page url.
416 TEST_F(SyncSessionModelAssociatorTest
, LoadValidFavicon
) {
417 std::string favicon
= "icon bytes";
418 std::string favicon_url
= "http://www.faviconurl.com/favicon.ico";
419 std::string page_url
= "http://www.faviconurl.com/page.html";
420 sync_pb::SessionTab tab
;
421 tab
.set_favicon(favicon
);
422 tab
.set_favicon_source(favicon_url
);
423 tab
.set_favicon_type(sync_pb::SessionTab::TYPE_WEB_FAVICON
);
424 sync_pb::TabNavigation
* navigation
= tab
.add_navigation();
425 navigation
->set_virtual_url(page_url
);
426 tab
.set_current_navigation_index(0);
428 EXPECT_TRUE(FaviconEquals(GURL(page_url
), std::string()));
430 EXPECT_TRUE(FaviconEquals(GURL(page_url
), favicon
));
433 // Create tab specifics with a valid favicon, load it, then load tab specifics
434 // with a new favicon for the same favicon source but different page. Ensure the
435 // old favicon remains.
436 TEST_F(SyncSessionModelAssociatorTest
, UpdateValidFavicon
) {
437 std::string favicon_url
= "http://www.faviconurl.com/favicon.ico";
439 std::string favicon
= "icon bytes";
440 std::string page_url
= "http://www.faviconurl.com/page.html";
441 sync_pb::SessionTab tab
;
442 tab
.set_favicon(favicon
);
443 tab
.set_favicon_source(favicon_url
);
444 tab
.set_favicon_type(sync_pb::SessionTab::TYPE_WEB_FAVICON
);
445 sync_pb::TabNavigation
* navigation
= tab
.add_navigation();
446 navigation
->set_virtual_url(page_url
);
447 tab
.set_current_navigation_index(0);
449 EXPECT_TRUE(FaviconEquals(GURL(page_url
), std::string()));
451 EXPECT_TRUE(FaviconEquals(GURL(page_url
), favicon
));
453 // Now have a new page with same favicon source but newer favicon data.
454 std::string favicon2
= "icon bytes 2";
455 std::string page_url2
= "http://www.faviconurl.com/page2.html";
456 sync_pb::SessionTab tab2
;
457 tab2
.set_favicon(favicon2
);
458 tab2
.set_favicon_source(favicon_url
);
459 tab2
.set_favicon_type(sync_pb::SessionTab::TYPE_WEB_FAVICON
);
460 sync_pb::TabNavigation
* navigation2
= tab2
.add_navigation();
461 navigation2
->set_virtual_url(page_url2
);
462 tab2
.set_current_navigation_index(0);
464 // The new page should be mapped to the old favicon data.
465 EXPECT_TRUE(FaviconEquals(GURL(page_url2
), std::string()));
466 LoadTabFavicon(tab2
);
467 EXPECT_TRUE(FaviconEquals(GURL(page_url
), favicon
));
468 EXPECT_TRUE(FaviconEquals(GURL(page_url2
), favicon
));
473 } // namespace browser_sync