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.
5 #include "chrome/browser/sync/sessions2/sessions_sync_manager.h"
7 #include "base/strings/string_util.h"
8 #include "chrome/browser/chrome_notification_types.h"
9 #include "chrome/browser/sessions/session_id.h"
10 #include "chrome/browser/sessions/session_tab_helper.h"
11 #include "chrome/browser/sessions/session_types.h"
12 #include "chrome/browser/sync/glue/device_info.h"
13 #include "chrome/browser/sync/glue/session_sync_test_helper.h"
14 #include "chrome/browser/sync/glue/synced_tab_delegate.h"
15 #include "chrome/browser/sync/sessions2/notification_service_sessions_router.h"
16 #include "chrome/browser/ui/sync/tab_contents_synced_tab_delegate.h"
17 #include "chrome/browser/ui/tabs/tab_strip_model.h"
18 #include "chrome/test/base/browser_with_test_window_test.h"
19 #include "components/sessions/serialized_navigation_entry_test_helper.h"
20 #include "content/public/browser/navigation_entry.h"
21 #include "content/public/browser/notification_details.h"
22 #include "content/public/browser/notification_service.h"
23 #include "content/public/browser/notification_source.h"
24 #include "content/public/browser/web_contents.h"
25 #include "sync/api/sync_error_factory_mock.h"
26 #include "testing/gmock/include/gmock/gmock.h"
27 #include "testing/gtest/include/gtest/gtest.h"
29 using content::WebContents
;
30 using sessions::SerializedNavigationEntry
;
31 using sessions::SerializedNavigationEntryTestHelper
;
32 using syncer::SyncChange
;
33 using syncer::SyncData
;
35 namespace browser_sync
{
39 class TestSyncProcessorStub
: public syncer::SyncChangeProcessor
{
41 explicit TestSyncProcessorStub(syncer::SyncChangeList
* output
)
43 virtual syncer::SyncError
ProcessSyncChanges(
44 const tracked_objects::Location
& from_here
,
45 const syncer::SyncChangeList
& change_list
) OVERRIDE
{
47 syncer::SyncError error
= error_
;
48 error_
= syncer::SyncError();
53 output_
->insert(output_
->end(), change_list
.begin(), change_list
.end());
55 return syncer::SyncError();
58 virtual syncer::SyncDataList
GetAllSyncData(syncer::ModelType type
)
60 return syncer::SyncDataList();
63 void FailProcessSyncChangesWith(const syncer::SyncError
& error
) {
68 syncer::SyncError error_
;
69 syncer::SyncChangeList
* output_
;
72 syncer::SyncChange
MakeRemoteChange(
74 const sync_pb::SessionSpecifics
& specifics
,
75 SyncChange::SyncChangeType type
) {
76 sync_pb::EntitySpecifics entity
;
77 entity
.mutable_session()->CopyFrom(specifics
);
78 return syncer::SyncChange(
80 syncer::SyncData::CreateRemoteData(id
, entity
, base::Time()));
83 void AddTabsToChangeList(
84 const std::vector
<sync_pb::SessionSpecifics
>& batch
,
85 SyncChange::SyncChangeType type
,
86 syncer::SyncChangeList
* change_list
) {
87 std::vector
<sync_pb::SessionSpecifics
>::const_iterator iter
;
88 for (iter
= batch
.begin();
89 iter
!= batch
.end(); ++iter
) {
90 sync_pb::EntitySpecifics entity
;
91 entity
.mutable_session()->CopyFrom(*iter
);
92 change_list
->push_back(syncer::SyncChange(
94 syncer::SyncData::CreateRemoteData(iter
->tab_node_id(),
95 entity
, base::Time())));
99 void AddTabsToSyncDataList(const std::vector
<sync_pb::SessionSpecifics
> tabs
,
100 syncer::SyncDataList
* list
) {
101 for (size_t i
= 0; i
< tabs
.size(); i
++) {
102 sync_pb::EntitySpecifics entity
;
103 entity
.mutable_session()->CopyFrom(tabs
[i
]);
104 list
->push_back(SyncData::CreateRemoteData(
105 i
+ 2, entity
, base::Time()));
109 class DummyRouter
: public LocalSessionEventRouter
{
111 virtual ~DummyRouter() {}
112 virtual void StartRoutingTo(LocalSessionEventHandler
* handler
) OVERRIDE
{}
113 virtual void Stop() OVERRIDE
{}
116 scoped_ptr
<LocalSessionEventRouter
> NewDummyRouter() {
117 return scoped_ptr
<LocalSessionEventRouter
>(new DummyRouter());
122 class SessionsSyncManagerTest
123 : public BrowserWithTestWindowTest
,
124 public SessionsSyncManager::SyncInternalApiDelegate
{
126 SessionsSyncManagerTest() : test_processor_(NULL
) {}
128 virtual void SetUp() OVERRIDE
{
129 BrowserWithTestWindowTest::SetUp();
130 browser_sync::NotificationServiceSessionsRouter
* router(
131 new browser_sync::NotificationServiceSessionsRouter(
132 profile(), syncer::SyncableService::StartSyncFlare()));
133 manager_
.reset(new SessionsSyncManager(profile(), this,
134 scoped_ptr
<LocalSessionEventRouter
>(router
)));
137 virtual void TearDown() OVERRIDE
{
138 test_processor_
= NULL
;
141 BrowserWithTestWindowTest::TearDown();
144 virtual scoped_ptr
<DeviceInfo
> GetLocalDeviceInfo() const OVERRIDE
{
145 return scoped_ptr
<DeviceInfo
>(
146 new DeviceInfo(GetLocalSyncCacheGUID(),
147 "Wayne Gretzky's Hacking Box",
150 sync_pb::SyncEnums_DeviceType_TYPE_LINUX
));
153 virtual std::string
GetLocalSyncCacheGUID() const OVERRIDE
{
157 SessionsSyncManager
* manager() { return manager_
.get(); }
158 SessionSyncTestHelper
* helper() { return &helper_
; }
160 void InitWithSyncDataTakeOutput(const syncer::SyncDataList
& initial_data
,
161 syncer::SyncChangeList
* output
) {
162 test_processor_
= new TestSyncProcessorStub(output
);
163 syncer::SyncMergeResult result
= manager_
->MergeDataAndStartSyncing(
164 syncer::SESSIONS
, initial_data
,
165 scoped_ptr
<syncer::SyncChangeProcessor
>(test_processor_
),
166 scoped_ptr
<syncer::SyncErrorFactory
>(
167 new syncer::SyncErrorFactoryMock()));
168 EXPECT_FALSE(result
.error().IsSet());
171 void InitWithNoSyncData() {
172 InitWithSyncDataTakeOutput(syncer::SyncDataList(), NULL
);
175 void TriggerProcessSyncChangesError() {
176 test_processor_
->FailProcessSyncChangesWith(syncer::SyncError(
177 FROM_HERE
, syncer::SyncError::DATATYPE_ERROR
, "Error",
181 syncer::SyncChangeList
* FilterOutLocalHeaderChanges(
182 syncer::SyncChangeList
* list
) {
183 syncer::SyncChangeList::iterator it
= list
->begin();
185 while (it
!= list
->end()) {
186 if (it
->sync_data().GetTag() == manager_
->current_machine_tag()) {
187 EXPECT_TRUE(SyncChange::ACTION_ADD
== it
->change_type() ||
188 SyncChange::ACTION_UPDATE
== it
->change_type());
189 it
= list
->erase(it
);
200 scoped_ptr
<SessionsSyncManager
> manager_
;
201 SessionSyncTestHelper helper_
;
202 TestSyncProcessorStub
* test_processor_
;
205 // Test that the SyncSessionManager can properly fill in a SessionHeader.
206 TEST_F(SessionsSyncManagerTest
, PopulateSessionHeader
) {
207 sync_pb::SessionHeader header_s
;
208 header_s
.set_client_name("Client 1");
209 header_s
.set_device_type(sync_pb::SyncEnums_DeviceType_TYPE_WIN
);
211 SyncedSession session
;
212 base::Time time
= base::Time::Now();
213 SessionsSyncManager::PopulateSessionHeaderFromSpecifics(
214 header_s
, time
, &session
);
215 ASSERT_EQ("Client 1", session
.session_name
);
216 ASSERT_EQ(SyncedSession::TYPE_WIN
, session
.device_type
);
217 ASSERT_EQ(time
, session
.modified_time
);
220 // Test translation between protobuf types and chrome session types.
221 TEST_F(SessionsSyncManagerTest
, PopulateSessionWindow
) {
222 sync_pb::SessionWindow window_s
;
224 window_s
.set_browser_type(sync_pb::SessionWindow_BrowserType_TYPE_TABBED
);
225 window_s
.set_selected_tab_index(1);
227 std::string tag
= "tag";
228 SyncedSession
* session
= manager()->session_tracker_
.GetSession(tag
);
229 manager()->session_tracker_
.PutWindowInSession(tag
, 0);
230 manager()->BuildSyncedSessionFromSpecifics(
231 tag
, window_s
, base::Time(), session
->windows
[0]);
232 ASSERT_EQ(1U, session
->windows
[0]->tabs
.size());
233 ASSERT_EQ(1, session
->windows
[0]->selected_tab_index
);
234 ASSERT_EQ(1, session
->windows
[0]->type
);
235 ASSERT_EQ(1U, manager()->session_tracker_
.num_synced_sessions());
237 manager()->session_tracker_
.num_synced_tabs(std::string("tag")));
242 class SyncedTabDelegateFake
: public SyncedTabDelegate
{
244 SyncedTabDelegateFake() : current_entry_index_(0),
245 pending_entry_index_(-1),
247 blocked_navigations_(NULL
) {}
248 virtual ~SyncedTabDelegateFake() {}
250 virtual int GetCurrentEntryIndex() const OVERRIDE
{
251 return current_entry_index_
;
253 void set_current_entry_index(int i
) {
254 current_entry_index_
= i
;
257 virtual content::NavigationEntry
* GetEntryAtIndex(int i
) const OVERRIDE
{
258 const int size
= entries_
.size();
259 return (size
< i
+ 1) ? NULL
: entries_
[i
];
262 void AppendEntry(content::NavigationEntry
* entry
) {
263 entries_
.push_back(entry
);
266 virtual int GetEntryCount() const OVERRIDE
{
267 return entries_
.size();
270 virtual int GetPendingEntryIndex() const OVERRIDE
{
271 return pending_entry_index_
;
273 void set_pending_entry_index(int i
) {
274 pending_entry_index_
= i
;
277 virtual SessionID::id_type
GetWindowId() const OVERRIDE
{
278 return SessionID::id_type();
281 virtual SessionID::id_type
GetSessionId() const OVERRIDE
{
282 return SessionID::id_type();
285 virtual bool IsBeingDestroyed() const OVERRIDE
{ return false; }
286 virtual Profile
* profile() const OVERRIDE
{ return NULL
; }
287 virtual std::string
GetExtensionAppId() const OVERRIDE
{
288 return std::string();
290 virtual content::NavigationEntry
* GetPendingEntry() const OVERRIDE
{
293 virtual content::NavigationEntry
* GetActiveEntry() const OVERRIDE
{
296 virtual bool ProfileIsManaged() const OVERRIDE
{
299 void set_is_managed(bool is_managed
) { is_managed_
= is_managed
; }
300 virtual const std::vector
<const content::NavigationEntry
*>*
301 GetBlockedNavigations() const OVERRIDE
{
302 return blocked_navigations_
;
304 void set_blocked_navigations(
305 std::vector
<const content::NavigationEntry
*>* navs
) {
306 blocked_navigations_
= navs
;
308 virtual bool IsPinned() const OVERRIDE
{
311 virtual bool HasWebContents() const OVERRIDE
{
314 virtual content::WebContents
* GetWebContents() const OVERRIDE
{
318 // Session sync related methods.
319 virtual int GetSyncId() const OVERRIDE
{
322 virtual void SetSyncId(int sync_id
) OVERRIDE
{}
325 current_entry_index_
= 0;
326 pending_entry_index_
= -1;
331 int current_entry_index_
;
332 int pending_entry_index_
;
334 std::vector
<const content::NavigationEntry
*>* blocked_navigations_
;
335 ScopedVector
<content::NavigationEntry
> entries_
;
340 // Test that we exclude tabs with only chrome:// and file:// schemed navigations
341 // from ShouldSyncTab(..).
342 TEST_F(SessionsSyncManagerTest
, ValidTabs
) {
343 SyncedTabDelegateFake tab
;
345 // A null entry shouldn't crash.
346 tab
.AppendEntry(NULL
);
347 EXPECT_FALSE(manager()->ShouldSyncTab(tab
));
350 // A chrome:// entry isn't valid.
351 content::NavigationEntry
* entry(content::NavigationEntry::Create());
352 entry
->SetVirtualURL(GURL("chrome://preferences/"));
353 tab
.AppendEntry(entry
);
354 EXPECT_FALSE(manager()->ShouldSyncTab(tab
));
357 // A file:// entry isn't valid, even in addition to another entry.
358 content::NavigationEntry
* entry2(content::NavigationEntry::Create());
359 entry2
->SetVirtualURL(GURL("file://bla"));
360 tab
.AppendEntry(entry2
);
361 EXPECT_FALSE(manager()->ShouldSyncTab(tab
));
363 // Add a valid scheme entry to tab, making the tab valid.
364 content::NavigationEntry
* entry3(content::NavigationEntry::Create());
365 entry3
->SetVirtualURL(GURL("http://www.google.com"));
366 tab
.AppendEntry(entry3
);
367 EXPECT_FALSE(manager()->ShouldSyncTab(tab
));
370 // Make sure GetCurrentVirtualURL() returns the virtual URL of the pending
371 // entry if the current entry is pending.
372 TEST_F(SessionsSyncManagerTest
, GetCurrentVirtualURLPending
) {
373 SyncedTabDelegateFake tab
;
374 content::NavigationEntry
* entry(content::NavigationEntry::Create());
375 entry
->SetVirtualURL(GURL("http://www.google.com"));
376 tab
.AppendEntry(entry
);
377 EXPECT_EQ(entry
->GetVirtualURL(), manager()->GetCurrentVirtualURL(tab
));
380 // Make sure GetCurrentVirtualURL() returns the virtual URL of the current
381 // entry if the current entry is non-pending.
382 TEST_F(SessionsSyncManagerTest
, GetCurrentVirtualURLNonPending
) {
383 SyncedTabDelegateFake tab
;
384 content::NavigationEntry
* entry(content::NavigationEntry::Create());
385 entry
->SetVirtualURL(GURL("http://www.google.com"));
386 tab
.AppendEntry(entry
);
387 EXPECT_EQ(entry
->GetVirtualURL(), manager()->GetCurrentVirtualURL(tab
));
390 static const base::Time kTime1
= base::Time::FromInternalValue(100);
391 static const base::Time kTime2
= base::Time::FromInternalValue(105);
392 static const base::Time kTime3
= base::Time::FromInternalValue(110);
393 static const base::Time kTime4
= base::Time::FromInternalValue(120);
394 static const base::Time kTime5
= base::Time::FromInternalValue(130);
396 // Populate the mock tab delegate with some data and navigation
397 // entries and make sure that setting a SessionTab from it preserves
398 // those entries (and clobbers any existing data).
399 TEST_F(SessionsSyncManagerTest
, SetSessionTabFromDelegate
) {
400 // Create a tab with three valid entries.
401 SyncedTabDelegateFake tab
;
402 content::NavigationEntry
* entry1(content::NavigationEntry::Create());
403 entry1
->SetVirtualURL(GURL("http://www.google.com"));
404 entry1
->SetTimestamp(kTime1
);
405 entry1
->SetHttpStatusCode(200);
406 content::NavigationEntry
* entry2(content::NavigationEntry::Create());
407 entry2
->SetVirtualURL(GURL("http://www.noodle.com"));
408 entry2
->SetTimestamp(kTime2
);
409 entry2
->SetHttpStatusCode(201);
410 content::NavigationEntry
* entry3(content::NavigationEntry::Create());
411 entry3
->SetVirtualURL(GURL("http://www.doodle.com"));
412 entry3
->SetTimestamp(kTime3
);
413 entry3
->SetHttpStatusCode(202);
415 tab
.AppendEntry(entry1
);
416 tab
.AppendEntry(entry2
);
417 tab
.AppendEntry(entry3
);
418 tab
.set_current_entry_index(2);
420 SessionTab session_tab
;
421 session_tab
.window_id
.set_id(1);
422 session_tab
.tab_id
.set_id(1);
423 session_tab
.tab_visual_index
= 1;
424 session_tab
.current_navigation_index
= 1;
425 session_tab
.pinned
= true;
426 session_tab
.extension_app_id
= "app id";
427 session_tab
.user_agent_override
= "override";
428 session_tab
.timestamp
= kTime5
;
429 session_tab
.navigations
.push_back(
430 SerializedNavigationEntryTestHelper::CreateNavigation(
431 "http://www.example.com", "Example"));
432 session_tab
.session_storage_persistent_id
= "persistent id";
433 manager()->SetSessionTabFromDelegate(tab
, kTime4
, &session_tab
);
435 EXPECT_EQ(0, session_tab
.window_id
.id());
436 EXPECT_EQ(0, session_tab
.tab_id
.id());
437 EXPECT_EQ(0, session_tab
.tab_visual_index
);
438 EXPECT_EQ(2, session_tab
.current_navigation_index
);
439 EXPECT_FALSE(session_tab
.pinned
);
440 EXPECT_TRUE(session_tab
.extension_app_id
.empty());
441 EXPECT_TRUE(session_tab
.user_agent_override
.empty());
442 EXPECT_EQ(kTime4
, session_tab
.timestamp
);
443 ASSERT_EQ(3u, session_tab
.navigations
.size());
444 EXPECT_EQ(entry1
->GetVirtualURL(),
445 session_tab
.navigations
[0].virtual_url());
446 EXPECT_EQ(entry2
->GetVirtualURL(),
447 session_tab
.navigations
[1].virtual_url());
448 EXPECT_EQ(entry3
->GetVirtualURL(),
449 session_tab
.navigations
[2].virtual_url());
450 EXPECT_EQ(kTime1
, session_tab
.navigations
[0].timestamp());
451 EXPECT_EQ(kTime2
, session_tab
.navigations
[1].timestamp());
452 EXPECT_EQ(kTime3
, session_tab
.navigations
[2].timestamp());
453 EXPECT_EQ(200, session_tab
.navigations
[0].http_status_code());
454 EXPECT_EQ(201, session_tab
.navigations
[1].http_status_code());
455 EXPECT_EQ(202, session_tab
.navigations
[2].http_status_code());
456 EXPECT_EQ(SerializedNavigationEntry::STATE_INVALID
,
457 session_tab
.navigations
[0].blocked_state());
458 EXPECT_EQ(SerializedNavigationEntry::STATE_INVALID
,
459 session_tab
.navigations
[1].blocked_state());
460 EXPECT_EQ(SerializedNavigationEntry::STATE_INVALID
,
461 session_tab
.navigations
[2].blocked_state());
462 EXPECT_TRUE(session_tab
.session_storage_persistent_id
.empty());
465 // Tests that for managed users blocked navigations are recorded and marked as
466 // such, while regular navigations are marked as allowed.
467 TEST_F(SessionsSyncManagerTest
, BlockedNavigations
) {
468 SyncedTabDelegateFake tab
;
469 content::NavigationEntry
* entry1(content::NavigationEntry::Create());
470 entry1
->SetVirtualURL(GURL("http://www.google.com"));
471 entry1
->SetTimestamp(kTime1
);
472 tab
.AppendEntry(entry1
);
474 content::NavigationEntry
* entry2
= content::NavigationEntry::Create();
475 entry2
->SetVirtualURL(GURL("http://blocked.com/foo"));
476 entry2
->SetTimestamp(kTime2
);
477 content::NavigationEntry
* entry3
= content::NavigationEntry::Create();
478 entry3
->SetVirtualURL(GURL("http://evil.com"));
479 entry3
->SetTimestamp(kTime3
);
480 ScopedVector
<const content::NavigationEntry
> blocked_navigations
;
481 blocked_navigations
.push_back(entry2
);
482 blocked_navigations
.push_back(entry3
);
484 tab
.set_is_managed(true);
485 tab
.set_blocked_navigations(&blocked_navigations
.get());
487 SessionTab session_tab
;
488 session_tab
.window_id
.set_id(1);
489 session_tab
.tab_id
.set_id(1);
490 session_tab
.tab_visual_index
= 1;
491 session_tab
.current_navigation_index
= 1;
492 session_tab
.pinned
= true;
493 session_tab
.extension_app_id
= "app id";
494 session_tab
.user_agent_override
= "override";
495 session_tab
.timestamp
= kTime5
;
496 session_tab
.navigations
.push_back(
497 SerializedNavigationEntryTestHelper::CreateNavigation(
498 "http://www.example.com", "Example"));
499 session_tab
.session_storage_persistent_id
= "persistent id";
500 manager()->SetSessionTabFromDelegate(tab
, kTime4
, &session_tab
);
502 EXPECT_EQ(0, session_tab
.window_id
.id());
503 EXPECT_EQ(0, session_tab
.tab_id
.id());
504 EXPECT_EQ(0, session_tab
.tab_visual_index
);
505 EXPECT_EQ(0, session_tab
.current_navigation_index
);
506 EXPECT_FALSE(session_tab
.pinned
);
507 EXPECT_TRUE(session_tab
.extension_app_id
.empty());
508 EXPECT_TRUE(session_tab
.user_agent_override
.empty());
509 EXPECT_EQ(kTime4
, session_tab
.timestamp
);
510 ASSERT_EQ(3u, session_tab
.navigations
.size());
511 EXPECT_EQ(entry1
->GetVirtualURL(),
512 session_tab
.navigations
[0].virtual_url());
513 EXPECT_EQ(entry2
->GetVirtualURL(),
514 session_tab
.navigations
[1].virtual_url());
515 EXPECT_EQ(entry3
->GetVirtualURL(),
516 session_tab
.navigations
[2].virtual_url());
517 EXPECT_EQ(kTime1
, session_tab
.navigations
[0].timestamp());
518 EXPECT_EQ(kTime2
, session_tab
.navigations
[1].timestamp());
519 EXPECT_EQ(kTime3
, session_tab
.navigations
[2].timestamp());
520 EXPECT_EQ(SerializedNavigationEntry::STATE_ALLOWED
,
521 session_tab
.navigations
[0].blocked_state());
522 EXPECT_EQ(SerializedNavigationEntry::STATE_BLOCKED
,
523 session_tab
.navigations
[1].blocked_state());
524 EXPECT_EQ(SerializedNavigationEntry::STATE_BLOCKED
,
525 session_tab
.navigations
[2].blocked_state());
526 EXPECT_TRUE(session_tab
.session_storage_persistent_id
.empty());
529 // Tests that the local session header objects is created properly in
530 // presence of no other session activity, once and only once.
531 TEST_F(SessionsSyncManagerTest
, MergeLocalSessionNoTabs
) {
532 syncer::SyncChangeList out
;
533 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out
);
534 EXPECT_FALSE(manager()->current_machine_tag().empty());
536 EXPECT_EQ(2U, out
.size());
537 EXPECT_TRUE(out
[0].IsValid());
538 EXPECT_EQ(SyncChange::ACTION_ADD
, out
[0].change_type());
539 const SyncData
data(out
[0].sync_data());
540 EXPECT_EQ(manager()->current_machine_tag(), data
.GetTag());
541 const sync_pb::SessionSpecifics
& specifics(data
.GetSpecifics().session());
542 EXPECT_EQ(manager()->current_machine_tag(), specifics
.session_tag());
543 EXPECT_TRUE(specifics
.has_header());
544 const sync_pb::SessionHeader
& header_s
= specifics
.header();
545 EXPECT_TRUE(header_s
.has_device_type());
546 EXPECT_EQ(GetLocalDeviceInfo()->client_name(), header_s
.client_name());
547 EXPECT_EQ(0, header_s
.window_size());
549 EXPECT_TRUE(out
[1].IsValid());
550 EXPECT_EQ(SyncChange::ACTION_UPDATE
, out
[1].change_type());
551 const SyncData
data_2(out
[1].sync_data());
552 EXPECT_EQ(manager()->current_machine_tag(), data_2
.GetTag());
553 const sync_pb::SessionSpecifics
& specifics2(data_2
.GetSpecifics().session());
554 EXPECT_EQ(manager()->current_machine_tag(), specifics2
.session_tag());
555 EXPECT_TRUE(specifics2
.has_header());
556 const sync_pb::SessionHeader
& header_s2
= specifics2
.header();
557 EXPECT_EQ(0, header_s2
.window_size());
559 // Now take that header node and feed it in as input.
560 SyncData
d(SyncData::CreateRemoteData(1, data
.GetSpecifics(), base::Time()));
561 syncer::SyncDataList
in(&d
, &d
+ 1);
563 SessionsSyncManager
manager2(profile(), this, NewDummyRouter());
564 syncer::SyncMergeResult result
= manager2
.MergeDataAndStartSyncing(
565 syncer::SESSIONS
, in
,
566 scoped_ptr
<syncer::SyncChangeProcessor
>(
567 new TestSyncProcessorStub(&out
)),
568 scoped_ptr
<syncer::SyncErrorFactory
>(
569 new syncer::SyncErrorFactoryMock()));
570 ASSERT_FALSE(result
.error().IsSet());
572 EXPECT_EQ(1U, out
.size());
573 EXPECT_EQ(SyncChange::ACTION_UPDATE
, out
[0].change_type());
574 EXPECT_TRUE(out
[0].sync_data().GetSpecifics().session().has_header());
577 // Tests MergeDataAndStartSyncing with sync data but no local data.
578 TEST_F(SessionsSyncManagerTest
, MergeWithInitialForeignSession
) {
579 std::string tag
= "tag1";
581 SessionID::id_type n1
[] = {5, 10, 13, 17};
582 std::vector
<SessionID::id_type
> tab_list1(n1
, n1
+ arraysize(n1
));
583 std::vector
<sync_pb::SessionSpecifics
> tabs1
;
584 sync_pb::SessionSpecifics
meta(helper()->BuildForeignSession(
585 tag
, tab_list1
, &tabs1
));
586 // Add a second window.
587 SessionID::id_type n2
[] = {7, 15, 18, 20};
588 std::vector
<SessionID::id_type
> tab_list2(n2
, n2
+ arraysize(n2
));
589 helper()->AddWindowSpecifics(1, tab_list2
, &meta
);
591 // Set up initial data.
592 syncer::SyncDataList initial_data
;
593 sync_pb::EntitySpecifics entity
;
594 entity
.mutable_session()->CopyFrom(meta
);
595 initial_data
.push_back(SyncData::CreateRemoteData(1, entity
, base::Time()));
596 AddTabsToSyncDataList(tabs1
, &initial_data
);
598 for (size_t i
= 0; i
< tab_list2
.size(); ++i
) {
599 sync_pb::EntitySpecifics entity
;
600 helper()->BuildTabSpecifics(tag
, 0, tab_list2
[i
],
601 entity
.mutable_session());
602 initial_data
.push_back(
603 SyncData::CreateRemoteData(i
+ 10, entity
, base::Time()));
606 syncer::SyncChangeList output
;
607 InitWithSyncDataTakeOutput(initial_data
, &output
);
608 EXPECT_TRUE(FilterOutLocalHeaderChanges(&output
)->empty());
610 std::vector
<const SyncedSession
*> foreign_sessions
;
611 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions
));
612 ASSERT_EQ(1U, foreign_sessions
.size());
613 std::vector
<std::vector
<SessionID::id_type
> > session_reference
;
614 session_reference
.push_back(tab_list1
);
615 session_reference
.push_back(tab_list2
);
616 helper()->VerifySyncedSession(tag
, session_reference
, *(foreign_sessions
[0]));
619 // This is a combination of MergeWithInitialForeignSession and
620 // MergeLocalSessionExistingTabs. We repeat some checks performed in each of
621 // those tests to ensure the common mixed scenario works.
622 TEST_F(SessionsSyncManagerTest
, MergeWithLocalAndForeignTabs
) {
624 AddTab(browser(), GURL("http://foo1"));
625 NavigateAndCommitActiveTab(GURL("http://foo2"));
628 std::string tag
= "tag1";
629 SessionID::id_type n1
[] = {5, 10, 13, 17};
630 std::vector
<SessionID::id_type
> tab_list1(n1
, n1
+ arraysize(n1
));
631 std::vector
<sync_pb::SessionSpecifics
> tabs1
;
632 sync_pb::SessionSpecifics
meta(helper()->BuildForeignSession(
633 tag
, tab_list1
, &tabs1
));
634 syncer::SyncDataList foreign_data
;
635 sync_pb::EntitySpecifics entity
;
636 entity
.mutable_session()->CopyFrom(meta
);
637 foreign_data
.push_back(SyncData::CreateRemoteData(1, entity
, base::Time()));
638 AddTabsToSyncDataList(tabs1
, &foreign_data
);
640 syncer::SyncChangeList output
;
641 InitWithSyncDataTakeOutput(foreign_data
, &output
);
642 ASSERT_EQ(4U, output
.size());
644 // Verify the local header.
645 EXPECT_TRUE(output
[0].IsValid());
646 EXPECT_EQ(SyncChange::ACTION_ADD
, output
[0].change_type());
647 const SyncData
data(output
[0].sync_data());
648 EXPECT_EQ(manager()->current_machine_tag(), data
.GetTag());
649 const sync_pb::SessionSpecifics
& specifics(data
.GetSpecifics().session());
650 EXPECT_EQ(manager()->current_machine_tag(), specifics
.session_tag());
651 EXPECT_TRUE(specifics
.has_header());
652 const sync_pb::SessionHeader
& header_s
= specifics
.header();
653 EXPECT_TRUE(header_s
.has_device_type());
654 EXPECT_EQ(GetLocalDeviceInfo()->client_name(), header_s
.client_name());
655 EXPECT_EQ(0, header_s
.window_size());
657 // Verify the tab node creations and updates with content.
658 for (int i
= 1; i
< 3; i
++) {
659 EXPECT_TRUE(output
[i
].IsValid());
660 const SyncData
data(output
[i
].sync_data());
661 EXPECT_TRUE(StartsWithASCII(data
.GetTag(),
662 manager()->current_machine_tag(), true));
663 const sync_pb::SessionSpecifics
& specifics(data
.GetSpecifics().session());
664 EXPECT_EQ(manager()->current_machine_tag(), specifics
.session_tag());
666 EXPECT_EQ(SyncChange::ACTION_ADD
, output
[1].change_type());
667 EXPECT_EQ(SyncChange::ACTION_UPDATE
, output
[2].change_type());
668 EXPECT_TRUE(output
[2].sync_data().GetSpecifics().session().has_tab());
670 // Verify the header was updated to reflect window state.
671 EXPECT_TRUE(output
[3].IsValid());
672 EXPECT_EQ(SyncChange::ACTION_UPDATE
, output
[3].change_type());
673 const SyncData
data_2(output
[3].sync_data());
674 EXPECT_EQ(manager()->current_machine_tag(), data_2
.GetTag());
675 const sync_pb::SessionSpecifics
& specifics2(data_2
.GetSpecifics().session());
676 EXPECT_EQ(manager()->current_machine_tag(), specifics2
.session_tag());
677 EXPECT_TRUE(specifics2
.has_header());
678 const sync_pb::SessionHeader
& header_s2
= specifics2
.header();
679 EXPECT_EQ(1, header_s2
.window_size());
681 // Verify foreign data.
682 std::vector
<const SyncedSession
*> foreign_sessions
;
683 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions
));
684 std::vector
<std::vector
<SessionID::id_type
> > session_reference
;
685 session_reference
.push_back(tab_list1
);
686 helper()->VerifySyncedSession(tag
, session_reference
, *(foreign_sessions
[0]));
687 // There should be one and only one foreign session. If VerifySyncedSession
688 // was successful above this EXPECT call ensures the local session didn't
689 // get mistakenly added to foreign tracking (Similar to ExistingTabs test).
690 EXPECT_EQ(1U, foreign_sessions
.size());
693 // Tests the common scenario. Merge with both local and foreign session data
694 // followed by updates flowing from sync and local.
695 TEST_F(SessionsSyncManagerTest
, UpdatesAfterMixedMerge
) {
696 // Add local and foreign data.
697 AddTab(browser(), GURL("http://foo1"));
698 NavigateAndCommitActiveTab(GURL("http://foo2"));
700 std::string tag1
= "tag1";
701 syncer::SyncDataList foreign_data1
;
702 std::vector
<std::vector
<SessionID::id_type
> > meta1_reference
;
703 sync_pb::SessionSpecifics meta1
;
705 SessionID::id_type n1
[] = {5, 10, 13, 17};
706 std::vector
<SessionID::id_type
> tab_list1(n1
, n1
+ arraysize(n1
));
707 meta1_reference
.push_back(tab_list1
);
708 std::vector
<sync_pb::SessionSpecifics
> tabs1
;
709 meta1
= helper()->BuildForeignSession(tag1
, tab_list1
, &tabs1
);
710 sync_pb::EntitySpecifics entity
;
711 entity
.mutable_session()->CopyFrom(meta1
);
712 foreign_data1
.push_back(SyncData::CreateRemoteData(
713 1, entity
, base::Time()));
714 AddTabsToSyncDataList(tabs1
, &foreign_data1
);
716 syncer::SyncChangeList output1
;
717 InitWithSyncDataTakeOutput(foreign_data1
, &output1
);
718 ASSERT_EQ(4U, output1
.size());
720 // Add a second window to the foreign session.
721 // TODO(tim): Bug 98892. Add local window too when observers are hooked up.
722 SessionID::id_type tab_nums2
[] = {7, 15, 18, 20};
723 std::vector
<SessionID::id_type
> tab_list2(
724 tab_nums2
, tab_nums2
+ arraysize(tab_nums2
));
725 meta1_reference
.push_back(tab_list2
);
726 helper()->AddWindowSpecifics(1, tab_list2
, &meta1
);
727 std::vector
<sync_pb::SessionSpecifics
> tabs2
;
728 tabs2
.resize(tab_list2
.size());
729 for (size_t i
= 0; i
< tab_list2
.size(); ++i
) {
730 helper()->BuildTabSpecifics(tag1
, 0, tab_list2
[i
], &tabs2
[i
]);
733 syncer::SyncChangeList changes
;
734 changes
.push_back(MakeRemoteChange(1, meta1
, SyncChange::ACTION_UPDATE
));
735 AddTabsToChangeList(tabs2
, SyncChange::ACTION_ADD
, &changes
);
736 manager()->ProcessSyncChanges(FROM_HERE
, changes
);
739 // Check that the foreign session was associated and retrieve the data.
740 std::vector
<const SyncedSession
*> foreign_sessions
;
741 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions
));
742 ASSERT_EQ(1U, foreign_sessions
.size());
743 ASSERT_EQ(4U, foreign_sessions
[0]->windows
.find(0)->second
->tabs
.size());
744 ASSERT_EQ(4U, foreign_sessions
[0]->windows
.find(1)->second
->tabs
.size());
745 helper()->VerifySyncedSession(tag1
, meta1_reference
, *(foreign_sessions
[0]));
747 // Add a new foreign session.
748 std::string tag2
= "tag2";
749 SessionID::id_type n2
[] = {107, 115};
750 std::vector
<SessionID::id_type
> tag2_tab_list(n2
, n2
+ arraysize(n2
));
751 std::vector
<sync_pb::SessionSpecifics
> tag2_tabs
;
752 sync_pb::SessionSpecifics
meta2(helper()->BuildForeignSession(
753 tag2
, tag2_tab_list
, &tag2_tabs
));
754 changes
.push_back(MakeRemoteChange(100, meta2
, SyncChange::ACTION_ADD
));
755 AddTabsToChangeList(tag2_tabs
, SyncChange::ACTION_ADD
, &changes
);
757 manager()->ProcessSyncChanges(FROM_HERE
, changes
);
760 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions
));
761 std::vector
<std::vector
<SessionID::id_type
> > meta2_reference
;
762 meta2_reference
.push_back(tag2_tab_list
);
763 ASSERT_EQ(2U, foreign_sessions
.size());
764 ASSERT_EQ(2U, foreign_sessions
[1]->windows
.find(0)->second
->tabs
.size());
765 helper()->VerifySyncedSession(tag2
, meta2_reference
, *(foreign_sessions
[1]));
766 foreign_sessions
.clear();
768 // Remove a tab from a window.
769 meta1_reference
[0].pop_back();
770 tab_list1
.pop_back();
771 sync_pb::SessionWindow
* win
= meta1
.mutable_header()->mutable_window(0);
773 for (std::vector
<int>::const_iterator iter
= tab_list1
.begin();
774 iter
!= tab_list1
.end(); ++iter
) {
777 syncer::SyncChangeList removal
;
778 removal
.push_back(MakeRemoteChange(1, meta1
, SyncChange::ACTION_UPDATE
));
779 AddTabsToChangeList(tabs1
, SyncChange::ACTION_UPDATE
, &removal
);
780 manager()->ProcessSyncChanges(FROM_HERE
, removal
);
782 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions
));
783 ASSERT_EQ(2U, foreign_sessions
.size());
784 ASSERT_EQ(3U, foreign_sessions
[0]->windows
.find(0)->second
->tabs
.size());
785 helper()->VerifySyncedSession(tag1
, meta1_reference
, *(foreign_sessions
[0]));
788 // Tests that this SyncSessionManager knows how to delete foreign sessions
790 TEST_F(SessionsSyncManagerTest
, DeleteForeignSession
) {
791 InitWithNoSyncData();
792 std::string tag
= "tag1";
793 syncer::SyncChangeList changes
;
795 std::vector
<const SyncedSession
*> foreign_sessions
;
796 ASSERT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions
));
797 manager()->DeleteForeignSessionInternal(tag
, &changes
);
798 ASSERT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions
));
799 EXPECT_TRUE(changes
.empty());
801 // Fill an instance of session specifics with a foreign session's data.
802 std::vector
<sync_pb::SessionSpecifics
> tabs
;
803 SessionID::id_type n1
[] = {5, 10, 13, 17};
804 std::vector
<SessionID::id_type
> tab_nums1(n1
, n1
+ arraysize(n1
));
805 sync_pb::SessionSpecifics
meta(helper()->BuildForeignSession(
806 tag
, tab_nums1
, &tabs
));
808 // Update associator with the session's meta node, window, and tabs.
809 manager()->UpdateTrackerWithForeignSession(meta
, base::Time());
810 for (std::vector
<sync_pb::SessionSpecifics
>::iterator iter
= tabs
.begin();
811 iter
!= tabs
.end(); ++iter
) {
812 manager()->UpdateTrackerWithForeignSession(*iter
, base::Time());
814 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions
));
815 ASSERT_EQ(1U, foreign_sessions
.size());
817 // Now delete the foreign session.
818 manager()->DeleteForeignSessionInternal(tag
, &changes
);
819 EXPECT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions
));
821 EXPECT_EQ(5U, changes
.size());
822 std::set
<std::string
> expected_tags(&tag
, &tag
+ 1);
823 for (int i
= 0; i
< 5; i
++)
824 expected_tags
.insert(TabNodePool2::TabIdToTag(tag
, i
));
826 for (int i
= 0; i
< 5; i
++) {
827 SCOPED_TRACE(changes
[i
].ToString());
828 EXPECT_TRUE(changes
[i
].IsValid());
829 EXPECT_EQ(SyncChange::ACTION_DELETE
, changes
[i
].change_type());
830 EXPECT_TRUE(changes
[i
].sync_data().IsValid());
831 EXPECT_EQ(1U, expected_tags
.erase(changes
[i
].sync_data().GetTag()));
835 // Write a foreign session to a node, with the tabs arriving first, and then
837 TEST_F(SessionsSyncManagerTest
, WriteForeignSessionToNodeTabsFirst
) {
838 InitWithNoSyncData();
840 // Fill an instance of session specifics with a foreign session's data.
841 std::string tag
= "tag1";
842 SessionID::id_type nums1
[] = {5, 10, 13, 17};
843 std::vector
<sync_pb::SessionSpecifics
> tabs1
;
844 std::vector
<SessionID::id_type
> tab_list1 (nums1
, nums1
+ arraysize(nums1
));
845 sync_pb::SessionSpecifics
meta(helper()->BuildForeignSession(
846 tag
, tab_list1
, &tabs1
));
848 syncer::SyncChangeList adds
;
849 // Add tabs for first window, then the meta node.
850 AddTabsToChangeList(tabs1
, SyncChange::ACTION_ADD
, &adds
);
851 adds
.push_back(MakeRemoteChange(1, meta
, SyncChange::ACTION_ADD
));
852 manager()->ProcessSyncChanges(FROM_HERE
, adds
);
854 // Check that the foreign session was associated and retrieve the data.
855 std::vector
<const SyncedSession
*> foreign_sessions
;
856 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions
));
857 ASSERT_EQ(1U, foreign_sessions
.size());
858 std::vector
<std::vector
<SessionID::id_type
> > session_reference
;
859 session_reference
.push_back(tab_list1
);
860 helper()->VerifySyncedSession(tag
, session_reference
, *(foreign_sessions
[0]));
863 // Write a foreign session to a node with some tabs that never arrive.
864 TEST_F(SessionsSyncManagerTest
, WriteForeignSessionToNodeMissingTabs
) {
865 InitWithNoSyncData();
867 // Fill an instance of session specifics with a foreign session's data.
868 std::string tag
= "tag1";
869 SessionID::id_type nums1
[] = {5, 10, 13, 17};
870 std::vector
<sync_pb::SessionSpecifics
> tabs1
;
871 std::vector
<SessionID::id_type
> tab_list1 (nums1
, nums1
+ arraysize(nums1
));
872 sync_pb::SessionSpecifics
meta(helper()->BuildForeignSession(
873 tag
, tab_list1
, &tabs1
));
874 // Add a second window, but this time only create two tab nodes, despite the
875 // window expecting four tabs.
876 SessionID::id_type tab_nums2
[] = {7, 15, 18, 20};
877 std::vector
<SessionID::id_type
> tab_list2(
878 tab_nums2
, tab_nums2
+ arraysize(tab_nums2
));
879 helper()->AddWindowSpecifics(1, tab_list2
, &meta
);
880 std::vector
<sync_pb::SessionSpecifics
> tabs2
;
882 for (size_t i
= 0; i
< 2; ++i
) {
883 helper()->BuildTabSpecifics(tag
, 0, tab_list2
[i
], &tabs2
[i
]);
886 syncer::SyncChangeList changes
;
887 changes
.push_back(MakeRemoteChange(1, meta
, SyncChange::ACTION_ADD
));
888 AddTabsToChangeList(tabs1
, SyncChange::ACTION_ADD
, &changes
);
889 AddTabsToChangeList(tabs2
, SyncChange::ACTION_ADD
, &changes
);
890 manager()->ProcessSyncChanges(FROM_HERE
, changes
);
893 // Check that the foreign session was associated and retrieve the data.
894 std::vector
<const SyncedSession
*> foreign_sessions
;
895 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions
));
896 ASSERT_EQ(1U, foreign_sessions
.size());
897 ASSERT_EQ(2U, foreign_sessions
[0]->windows
.size());
898 ASSERT_EQ(4U, foreign_sessions
[0]->windows
.find(0)->second
->tabs
.size());
899 ASSERT_EQ(4U, foreign_sessions
[0]->windows
.find(1)->second
->tabs
.size());
901 // Close the second window.
902 meta
.mutable_header()->clear_window();
903 helper()->AddWindowSpecifics(0, tab_list1
, &meta
);
904 changes
.push_back(MakeRemoteChange(1, meta
, SyncChange::ACTION_UPDATE
));
905 // Update associator with the session's meta node containing one window.
906 manager()->ProcessSyncChanges(FROM_HERE
, changes
);
908 // Check that the foreign session was associated and retrieve the data.
909 foreign_sessions
.clear();
910 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions
));
911 ASSERT_EQ(1U, foreign_sessions
.size());
912 ASSERT_EQ(1U, foreign_sessions
[0]->windows
.size());
913 std::vector
<std::vector
<SessionID::id_type
> > session_reference
;
914 session_reference
.push_back(tab_list1
);
915 helper()->VerifySyncedSession(tag
, session_reference
, *(foreign_sessions
[0]));
918 // Test that receiving a session delete from sync removes the session
920 TEST_F(SessionsSyncManagerTest
, ProcessForeignDelete
) {
921 InitWithNoSyncData();
922 SessionID::id_type n
[] = {5};
923 std::vector
<sync_pb::SessionSpecifics
> tabs1
;
924 std::vector
<SessionID::id_type
> tab_list(n
, n
+ arraysize(n
));
925 sync_pb::SessionSpecifics
meta(helper()->BuildForeignSession(
926 "tag1", tab_list
, &tabs1
));
928 syncer::SyncChangeList changes
;
929 changes
.push_back(MakeRemoteChange(1, meta
, SyncChange::ACTION_ADD
));
930 AddTabsToChangeList(tabs1
, SyncChange::ACTION_ADD
, &changes
);
931 manager()->ProcessSyncChanges(FROM_HERE
, changes
);
933 std::vector
<const SyncedSession
*> foreign_sessions
;
934 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions
));
935 ASSERT_EQ(1U, foreign_sessions
.size());
938 foreign_sessions
.clear();
939 changes
.push_back(MakeRemoteChange(1, meta
, SyncChange::ACTION_DELETE
));
940 manager()->ProcessSyncChanges(FROM_HERE
, changes
);
942 EXPECT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions
));
945 // TODO(shashishekhar): "Move this to TabNodePool unittests."
946 TEST_F(SessionsSyncManagerTest
, SaveUnassociatedNodesForReassociation
) {
947 syncer::SyncChangeList changes
;
948 InitWithNoSyncData();
950 std::string local_tag
= manager()->current_machine_tag();
951 // Create a free node and then dissassociate sessions so that it ends up
953 manager()->local_tab_pool_
.GetFreeTabNode(&changes
);
955 // Update the tab_id of the node, so that it is considered a valid
956 // unassociated node otherwise it will be mistaken for a corrupted node and
957 // will be deleted before being added to the tab node pool.
958 sync_pb::EntitySpecifics
entity(changes
[0].sync_data().GetSpecifics());
959 entity
.mutable_session()->mutable_tab()->set_tab_id(1);
960 SyncData
d(SyncData::CreateRemoteData(1, entity
, base::Time()));
961 syncer::SyncDataList
in(&d
, &d
+ 1);
963 SessionsSyncManager
manager2(profile(), this, NewDummyRouter());
964 syncer::SyncMergeResult result
= manager2
.MergeDataAndStartSyncing(
965 syncer::SESSIONS
, in
,
966 scoped_ptr
<syncer::SyncChangeProcessor
>(
967 new TestSyncProcessorStub(&changes
)),
968 scoped_ptr
<syncer::SyncErrorFactory
>(
969 new syncer::SyncErrorFactoryMock()));
970 ASSERT_FALSE(result
.error().IsSet());
971 EXPECT_TRUE(FilterOutLocalHeaderChanges(&changes
)->empty());
974 TEST_F(SessionsSyncManagerTest
, MergeDeletesCorruptNode
) {
975 syncer::SyncChangeList changes
;
976 InitWithNoSyncData();
978 std::string local_tag
= manager()->current_machine_tag();
979 int tab_node_id
= manager()->local_tab_pool_
.GetFreeTabNode(&changes
);
980 SyncData
d(SyncData::CreateRemoteData(
981 1, changes
[0].sync_data().GetSpecifics(), base::Time()));
982 syncer::SyncDataList
in(&d
, &d
+ 1);
986 InitWithSyncDataTakeOutput(in
, &changes
);
987 EXPECT_EQ(1U, FilterOutLocalHeaderChanges(&changes
)->size());
988 EXPECT_EQ(SyncChange::ACTION_DELETE
, changes
[0].change_type());
989 EXPECT_EQ(TabNodePool2::TabIdToTag(local_tag
, tab_node_id
),
990 changes
[0].sync_data().GetTag());
993 // Test that things work if a tab is initially ignored.
994 TEST_F(SessionsSyncManagerTest
, AssociateWindowsDontReloadTabs
) {
995 syncer::SyncChangeList out
;
996 // Go to a URL that is ignored by session syncing.
997 AddTab(browser(), GURL("chrome://preferences/"));
998 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out
);
999 ASSERT_EQ(2U, out
.size()); // Header add and update.
1002 out
[1].sync_data().GetSpecifics().session().header().window_size());
1005 // Go to a sync-interesting URL.
1006 NavigateAndCommitActiveTab(GURL("http://foo2"));
1008 EXPECT_EQ(3U, out
.size()); // Tab add, update, and header update.
1010 EXPECT_TRUE(StartsWithASCII(out
[0].sync_data().GetTag(),
1011 manager()->current_machine_tag(), true));
1012 EXPECT_EQ(manager()->current_machine_tag(),
1013 out
[0].sync_data().GetSpecifics().session().session_tag());
1014 EXPECT_EQ(SyncChange::ACTION_ADD
, out
[0].change_type());
1016 EXPECT_TRUE(StartsWithASCII(out
[1].sync_data().GetTag(),
1017 manager()->current_machine_tag(), true));
1018 EXPECT_EQ(manager()->current_machine_tag(),
1019 out
[1].sync_data().GetSpecifics().session().session_tag());
1020 EXPECT_TRUE(out
[1].sync_data().GetSpecifics().session().has_tab());
1021 EXPECT_EQ(SyncChange::ACTION_UPDATE
, out
[1].change_type());
1023 EXPECT_TRUE(out
[2].IsValid());
1024 EXPECT_EQ(SyncChange::ACTION_UPDATE
, out
[2].change_type());
1025 const SyncData
data(out
[2].sync_data());
1026 EXPECT_EQ(manager()->current_machine_tag(), data
.GetTag());
1027 const sync_pb::SessionSpecifics
& specifics(data
.GetSpecifics().session());
1028 EXPECT_EQ(manager()->current_machine_tag(), specifics
.session_tag());
1029 EXPECT_TRUE(specifics
.has_header());
1030 const sync_pb::SessionHeader
& header_s
= specifics
.header();
1031 EXPECT_EQ(1, header_s
.window_size());
1032 EXPECT_EQ(1, header_s
.window(0).tab_size());
1035 // Tests that the SyncSessionManager responds to local tab events properly.
1036 TEST_F(SessionsSyncManagerTest
, OnLocalTabModified
) {
1037 syncer::SyncChangeList out
;
1038 // Init with no local data, relies on MergeLocalSessionNoTabs.
1039 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out
);
1040 ASSERT_FALSE(manager()->current_machine_tag().empty());
1041 ASSERT_EQ(2U, out
.size());
1043 // Copy the original header.
1044 sync_pb::EntitySpecifics
header(out
[0].sync_data().GetSpecifics());
1047 const GURL
foo1("http://foo/1");
1048 const GURL
foo2("http://foo/2");
1049 const GURL
bar1("http://bar/1");
1050 const GURL
bar2("http://bar/2");
1051 AddTab(browser(), foo1
);
1052 NavigateAndCommitActiveTab(foo2
);
1053 AddTab(browser(), bar1
);
1054 NavigateAndCommitActiveTab(bar2
);
1056 // One add, one update for each AddTab.
1057 // One update for each NavigateAndCommit.
1058 // = 6 total tab updates.
1059 // One header update corresponding to each of those.
1060 // = 6 total header updates.
1061 // 12 total updates.
1062 ASSERT_EQ(12U, out
.size());
1064 // Verify the tab node creations and updates to ensure the SyncProcessor
1065 // sees the right operations.
1066 for (int i
= 0; i
< 12; i
++) {
1068 EXPECT_TRUE(out
[i
].IsValid());
1069 const SyncData
data(out
[i
].sync_data());
1070 EXPECT_TRUE(StartsWithASCII(data
.GetTag(),
1071 manager()->current_machine_tag(), true));
1072 const sync_pb::SessionSpecifics
& specifics(data
.GetSpecifics().session());
1073 EXPECT_EQ(manager()->current_machine_tag(), specifics
.session_tag());
1075 // First thing on an AddTab is a no-op header update for parented tab.
1076 EXPECT_EQ(header
.SerializeAsString(),
1077 data
.GetSpecifics().SerializeAsString());
1078 EXPECT_EQ(manager()->current_machine_tag(), data
.GetTag());
1079 } else if (i
% 6 == 1) {
1080 // Next, the TabNodePool should create the tab node.
1081 EXPECT_EQ(SyncChange::ACTION_ADD
, out
[i
].change_type());
1082 EXPECT_EQ(TabNodePool2::TabIdToTag(
1083 manager()->current_machine_tag(),
1084 data
.GetSpecifics().session().tab_node_id()),
1086 } else if (i
% 6 == 2) {
1087 // Then we see the tab update to the URL.
1088 EXPECT_EQ(SyncChange::ACTION_UPDATE
, out
[i
].change_type());
1089 EXPECT_EQ(TabNodePool2::TabIdToTag(
1090 manager()->current_machine_tag(),
1091 data
.GetSpecifics().session().tab_node_id()),
1093 ASSERT_TRUE(specifics
.has_tab());
1094 } else if (i
% 6 == 3) {
1095 // The header needs to be updated to reflect the new window state.
1096 EXPECT_EQ(SyncChange::ACTION_UPDATE
, out
[i
].change_type());
1097 EXPECT_TRUE(specifics
.has_header());
1098 } else if (i
% 6 == 4) {
1099 // Now we move on to NavigateAndCommit. Update the tab.
1100 EXPECT_EQ(SyncChange::ACTION_UPDATE
, out
[i
].change_type());
1101 EXPECT_EQ(TabNodePool2::TabIdToTag(
1102 manager()->current_machine_tag(),
1103 data
.GetSpecifics().session().tab_node_id()),
1105 ASSERT_TRUE(specifics
.has_tab());
1106 } else if (i
% 6 == 5) {
1107 // The header needs to be updated to reflect the new window state.
1108 EXPECT_EQ(SyncChange::ACTION_UPDATE
, out
[i
].change_type());
1109 ASSERT_TRUE(specifics
.has_header());
1110 header
= data
.GetSpecifics();
1114 // Verify the actual content to ensure sync sees the right data.
1115 // When it's all said and done, the header should reflect two tabs.
1116 const sync_pb::SessionHeader
& session_header
= header
.session().header();
1117 ASSERT_EQ(1, session_header
.window_size());
1118 EXPECT_EQ(2, session_header
.window(0).tab_size());
1120 // ASSERT_TRUEs above allow us to dive in freely here.
1121 // Verify first tab.
1122 const sync_pb::SessionTab
& tab1_1
=
1123 out
[2].sync_data().GetSpecifics().session().tab();
1124 ASSERT_EQ(1, tab1_1
.navigation_size());
1125 EXPECT_EQ(foo1
.spec(), tab1_1
.navigation(0).virtual_url());
1126 const sync_pb::SessionTab
& tab1_2
=
1127 out
[4].sync_data().GetSpecifics().session().tab();
1128 ASSERT_EQ(2, tab1_2
.navigation_size());
1129 EXPECT_EQ(foo1
.spec(), tab1_2
.navigation(0).virtual_url());
1130 EXPECT_EQ(foo2
.spec(), tab1_2
.navigation(1).virtual_url());
1132 // Verify second tab.
1133 const sync_pb::SessionTab
& tab2_1
=
1134 out
[8].sync_data().GetSpecifics().session().tab();
1135 ASSERT_EQ(1, tab2_1
.navigation_size());
1136 EXPECT_EQ(bar1
.spec(), tab2_1
.navigation(0).virtual_url());
1137 const sync_pb::SessionTab
& tab2_2
=
1138 out
[10].sync_data().GetSpecifics().session().tab();
1139 ASSERT_EQ(2, tab2_2
.navigation_size());
1140 EXPECT_EQ(bar1
.spec(), tab2_2
.navigation(0).virtual_url());
1141 EXPECT_EQ(bar2
.spec(), tab2_2
.navigation(1).virtual_url());
1144 // Ensure model association associates the pre-existing tabs.
1145 TEST_F(SessionsSyncManagerTest
, MergeLocalSessionExistingTabs
) {
1146 AddTab(browser(), GURL("http://foo1"));
1147 NavigateAndCommitActiveTab(GURL("http://foo2")); // Adds back entry.
1148 AddTab(browser(), GURL("http://bar1"));
1149 NavigateAndCommitActiveTab(GURL("http://bar2")); // Adds back entry.
1151 syncer::SyncChangeList out
;
1152 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out
);
1153 ASSERT_EQ(6U, out
.size());
1155 // Check that this machine's data is not included in the foreign windows.
1156 std::vector
<const SyncedSession
*> foreign_sessions
;
1157 ASSERT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions
));
1159 // Verify the header.
1160 EXPECT_TRUE(out
[0].IsValid());
1161 EXPECT_EQ(SyncChange::ACTION_ADD
, out
[0].change_type());
1162 const SyncData
data(out
[0].sync_data());
1163 EXPECT_EQ(manager()->current_machine_tag(), data
.GetTag());
1164 const sync_pb::SessionSpecifics
& specifics(data
.GetSpecifics().session());
1165 EXPECT_EQ(manager()->current_machine_tag(), specifics
.session_tag());
1166 EXPECT_TRUE(specifics
.has_header());
1167 const sync_pb::SessionHeader
& header_s
= specifics
.header();
1168 EXPECT_TRUE(header_s
.has_device_type());
1169 EXPECT_EQ(GetLocalDeviceInfo()->client_name(), header_s
.client_name());
1170 EXPECT_EQ(0, header_s
.window_size());
1172 // Verify the tab node creations and updates with content.
1173 for (int i
= 1; i
< 5; i
++) {
1174 EXPECT_TRUE(out
[i
].IsValid());
1175 const SyncData
data(out
[i
].sync_data());
1176 EXPECT_TRUE(StartsWithASCII(data
.GetTag(),
1177 manager()->current_machine_tag(), true));
1178 const sync_pb::SessionSpecifics
& specifics(data
.GetSpecifics().session());
1179 EXPECT_EQ(manager()->current_machine_tag(), specifics
.session_tag());
1181 EXPECT_EQ(SyncChange::ACTION_ADD
, out
[i
].change_type());
1183 EXPECT_EQ(SyncChange::ACTION_UPDATE
, out
[i
].change_type());
1184 EXPECT_TRUE(specifics
.has_tab());
1188 // Verify the header was updated to reflect new window state.
1189 EXPECT_TRUE(out
[5].IsValid());
1190 EXPECT_EQ(SyncChange::ACTION_UPDATE
, out
[5].change_type());
1191 const SyncData
data_2(out
[5].sync_data());
1192 EXPECT_EQ(manager()->current_machine_tag(), data_2
.GetTag());
1193 const sync_pb::SessionSpecifics
& specifics2(data_2
.GetSpecifics().session());
1194 EXPECT_EQ(manager()->current_machine_tag(), specifics2
.session_tag());
1195 EXPECT_TRUE(specifics2
.has_header());
1196 const sync_pb::SessionHeader
& header_s2
= specifics2
.header();
1197 EXPECT_EQ(1, header_s2
.window_size());
1200 SessionsSyncManager::TabLinksMap tab_map
= manager()->local_tab_map_
;
1201 ASSERT_EQ(2U, tab_map
.size());
1202 // Tabs are ordered by sessionid in tab_map, so should be able to traverse
1203 // the tree based on order of tabs created
1204 SessionsSyncManager::TabLinksMap::iterator iter
= tab_map
.begin();
1205 ASSERT_EQ(2, iter
->second
->tab()->GetEntryCount());
1206 EXPECT_EQ(GURL("http://foo1"), iter
->second
->tab()->
1207 GetEntryAtIndex(0)->GetVirtualURL());
1208 EXPECT_EQ(GURL("http://foo2"), iter
->second
->tab()->
1209 GetEntryAtIndex(1)->GetVirtualURL());
1211 ASSERT_EQ(2, iter
->second
->tab()->GetEntryCount());
1212 EXPECT_EQ(GURL("http://bar1"), iter
->second
->tab()->
1213 GetEntryAtIndex(0)->GetVirtualURL());
1214 EXPECT_EQ(GURL("http://bar2"), iter
->second
->tab()->
1215 GetEntryAtIndex(1)->GetVirtualURL());
1218 // Test garbage collection of stale foreign sessions.
1219 TEST_F(SessionsSyncManagerTest
, DoGarbageCollection
) {
1220 // Fill two instances of session specifics with a foreign session's data.
1221 std::string tag1
= "tag1";
1222 SessionID::id_type n1
[] = {5, 10, 13, 17};
1223 std::vector
<SessionID::id_type
> tab_list1(n1
, n1
+ arraysize(n1
));
1224 std::vector
<sync_pb::SessionSpecifics
> tabs1
;
1225 sync_pb::SessionSpecifics
meta(helper()->BuildForeignSession(
1226 tag1
, tab_list1
, &tabs1
));
1227 std::string tag2
= "tag2";
1228 SessionID::id_type n2
[] = {8, 15, 18, 20};
1229 std::vector
<SessionID::id_type
> tab_list2(n2
, n2
+ arraysize(n2
));
1230 std::vector
<sync_pb::SessionSpecifics
> tabs2
;
1231 sync_pb::SessionSpecifics
meta2(helper()->BuildForeignSession(
1232 tag2
, tab_list2
, &tabs2
));
1233 // Set the modification time for tag1 to be 21 days ago, tag2 to 5 days ago.
1234 base::Time tag1_time
= base::Time::Now() - base::TimeDelta::FromDays(21);
1235 base::Time tag2_time
= base::Time::Now() - base::TimeDelta::FromDays(5);
1237 syncer::SyncDataList foreign_data
;
1238 sync_pb::EntitySpecifics entity1
, entity2
;
1239 entity1
.mutable_session()->CopyFrom(meta
);
1240 entity2
.mutable_session()->CopyFrom(meta2
);
1241 foreign_data
.push_back(SyncData::CreateRemoteData(1, entity1
, tag1_time
));
1242 foreign_data
.push_back(SyncData::CreateRemoteData(1, entity2
, tag2_time
));
1243 AddTabsToSyncDataList(tabs1
, &foreign_data
);
1244 AddTabsToSyncDataList(tabs2
, &foreign_data
);
1246 syncer::SyncChangeList output
;
1247 InitWithSyncDataTakeOutput(foreign_data
, &output
);
1248 ASSERT_EQ(2U, output
.size());
1251 // Check that the foreign session was associated and retrieve the data.
1252 std::vector
<const SyncedSession
*> foreign_sessions
;
1253 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions
));
1254 ASSERT_EQ(2U, foreign_sessions
.size());
1255 foreign_sessions
.clear();
1257 // Now garbage collect and verify the non-stale session is still there.
1258 manager()->DoGarbageCollection();
1259 ASSERT_EQ(5U, output
.size());
1260 EXPECT_EQ(SyncChange::ACTION_DELETE
, output
[0].change_type());
1261 const SyncData
data(output
[0].sync_data());
1262 EXPECT_EQ(tag1
, data
.GetTag());
1263 for (int i
= 1; i
< 5; i
++) {
1264 EXPECT_EQ(SyncChange::ACTION_DELETE
, output
[i
].change_type());
1265 const SyncData
data(output
[i
].sync_data());
1266 EXPECT_EQ(TabNodePool2::TabIdToTag(tag1
, i
), data
.GetTag());
1269 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions
));
1270 ASSERT_EQ(1U, foreign_sessions
.size());
1271 std::vector
<std::vector
<SessionID::id_type
> > session_reference
;
1272 session_reference
.push_back(tab_list2
);
1273 helper()->VerifySyncedSession(tag2
, session_reference
,
1274 *(foreign_sessions
[0]));
1277 // Test that an update to a previously considered "stale" session,
1278 // prior to garbage collection, will save the session from deletion.
1279 TEST_F(SessionsSyncManagerTest
, GarbageCollectionHonoursUpdate
) {
1280 std::string tag1
= "tag1";
1281 SessionID::id_type n1
[] = {5, 10, 13, 17};
1282 std::vector
<SessionID::id_type
> tab_list1(n1
, n1
+ arraysize(n1
));
1283 std::vector
<sync_pb::SessionSpecifics
> tabs1
;
1284 sync_pb::SessionSpecifics
meta(helper()->BuildForeignSession(
1285 tag1
, tab_list1
, &tabs1
));
1286 syncer::SyncDataList foreign_data
;
1287 sync_pb::EntitySpecifics entity1
;
1288 base::Time tag1_time
= base::Time::Now() - base::TimeDelta::FromDays(21);
1289 entity1
.mutable_session()->CopyFrom(meta
);
1290 foreign_data
.push_back(SyncData::CreateRemoteData(1, entity1
, tag1_time
));
1291 AddTabsToSyncDataList(tabs1
, &foreign_data
);
1292 syncer::SyncChangeList output
;
1293 InitWithSyncDataTakeOutput(foreign_data
, &output
);
1294 ASSERT_EQ(2U, output
.size());
1296 // Update to a non-stale time.
1297 sync_pb::EntitySpecifics update_entity
;
1298 update_entity
.mutable_session()->CopyFrom(tabs1
[0]);
1299 syncer::SyncChangeList changes
;
1300 changes
.push_back(syncer::SyncChange(
1302 SyncChange::ACTION_UPDATE
,
1303 syncer::SyncData::CreateRemoteData(1, update_entity
,
1304 base::Time::Now())));
1305 manager()->ProcessSyncChanges(FROM_HERE
, changes
);
1307 // Check that the foreign session was associated and retrieve the data.
1308 std::vector
<const SyncedSession
*> foreign_sessions
;
1309 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions
));
1310 ASSERT_EQ(1U, foreign_sessions
.size());
1311 foreign_sessions
.clear();
1313 // Verify the now non-stale session does not get deleted.
1314 manager()->DoGarbageCollection();
1315 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions
));
1316 ASSERT_EQ(1U, foreign_sessions
.size());
1317 std::vector
<std::vector
<SessionID::id_type
> > session_reference
;
1318 session_reference
.push_back(tab_list1
);
1319 helper()->VerifySyncedSession(
1320 tag1
, session_reference
, *(foreign_sessions
[0]));
1323 // Test that swapping WebContents for a tab is properly observed and handled
1324 // by the SessionsSyncManager.
1325 TEST_F(SessionsSyncManagerTest
, CheckPrerenderedWebContentsSwap
) {
1326 AddTab(browser(), GURL("http://foo1"));
1327 NavigateAndCommitActiveTab(GURL("http://foo2"));
1329 syncer::SyncChangeList out
;
1330 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out
);
1331 ASSERT_EQ(4U, out
.size()); // Header, tab ADD, tab UPDATE, header UPDATE.
1333 // To simulate WebContents swap during prerendering, create new WebContents
1334 // and swap with old WebContents.
1335 scoped_ptr
<content::WebContents
> old_web_contents
;
1336 old_web_contents
.reset(browser()->tab_strip_model()->GetActiveWebContents());
1338 // Create new WebContents, with the required tab helpers.
1339 WebContents
* new_web_contents
= WebContents::CreateWithSessionStorage(
1340 WebContents::CreateParams(profile()),
1341 old_web_contents
->GetController().GetSessionStorageNamespaceMap());
1342 SessionTabHelper::CreateForWebContents(new_web_contents
);
1343 TabContentsSyncedTabDelegate::CreateForWebContents(new_web_contents
);
1344 new_web_contents
->GetController()
1345 .CopyStateFrom(old_web_contents
->GetController());
1347 // Swap the WebContents.
1348 int index
= browser()->tab_strip_model()->GetIndexOfWebContents(
1349 old_web_contents
.get());
1350 browser()->tab_strip_model()->ReplaceWebContentsAt(index
, new_web_contents
);
1352 ASSERT_EQ(9U, out
.size());
1353 EXPECT_EQ(SyncChange::ACTION_ADD
, out
[4].change_type());
1354 EXPECT_EQ(SyncChange::ACTION_UPDATE
, out
[5].change_type());
1357 NavigateAndCommitActiveTab(GURL("http://bar2"));
1359 // Delete old WebContents. This should not crash.
1360 old_web_contents
.reset();
1362 // Try more navigations and verify output size. This can also reveal
1363 // bugs (leaks) on memcheck bots if the SessionSyncManager
1364 // didn't properly clean up the tab pool or session tracker.
1365 NavigateAndCommitActiveTab(GURL("http://bar3"));
1367 AddTab(browser(), GURL("http://bar4"));
1368 NavigateAndCommitActiveTab(GURL("http://bar5"));
1369 ASSERT_EQ(19U, out
.size());
1373 class SessionNotificationObserver
: public content::NotificationObserver
{
1375 SessionNotificationObserver() : notified_of_update_(false),
1376 notified_of_refresh_(false) {
1377 registrar_
.Add(this, chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED
,
1378 content::NotificationService::AllSources());
1379 registrar_
.Add(this, chrome::NOTIFICATION_SYNC_REFRESH_LOCAL
,
1380 content::NotificationService::AllSources());
1382 virtual void Observe(int type
,
1383 const content::NotificationSource
& source
,
1384 const content::NotificationDetails
& details
) OVERRIDE
{
1386 case chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED
:
1387 notified_of_update_
= true;
1389 case chrome::NOTIFICATION_SYNC_REFRESH_LOCAL
:
1390 notified_of_refresh_
= true;
1397 bool notified_of_update() const { return notified_of_update_
; }
1398 bool notified_of_refresh() const { return notified_of_refresh_
; }
1400 notified_of_update_
= false;
1401 notified_of_refresh_
= false;
1404 content::NotificationRegistrar registrar_
;
1405 bool notified_of_update_
;
1406 bool notified_of_refresh_
;
1410 // Test that NOTIFICATION_FOREIGN_SESSION_UPDATED is sent.
1411 TEST_F(SessionsSyncManagerTest
, NotifiedOfUpdates
) {
1412 SessionNotificationObserver observer
;
1413 ASSERT_FALSE(observer
.notified_of_update());
1414 InitWithNoSyncData();
1416 SessionID::id_type n
[] = {5};
1417 std::vector
<sync_pb::SessionSpecifics
> tabs1
;
1418 std::vector
<SessionID::id_type
> tab_list(n
, n
+ arraysize(n
));
1419 sync_pb::SessionSpecifics
meta(helper()->BuildForeignSession(
1420 "tag1", tab_list
, &tabs1
));
1422 syncer::SyncChangeList changes
;
1423 changes
.push_back(MakeRemoteChange(1, meta
, SyncChange::ACTION_ADD
));
1424 manager()->ProcessSyncChanges(FROM_HERE
, changes
);
1425 EXPECT_TRUE(observer
.notified_of_update());
1429 AddTabsToChangeList(tabs1
, SyncChange::ACTION_ADD
, &changes
);
1430 manager()->ProcessSyncChanges(FROM_HERE
, changes
);
1431 EXPECT_TRUE(observer
.notified_of_update());
1435 changes
.push_back(MakeRemoteChange(1, meta
, SyncChange::ACTION_DELETE
));
1436 manager()->ProcessSyncChanges(FROM_HERE
, changes
);
1437 EXPECT_TRUE(observer
.notified_of_update());
1440 #if defined(OS_ANDROID) || defined(OS_IOS)
1441 // Tests that opening the other devices page triggers a session sync refresh.
1442 // This page only exists on mobile platforms today; desktop has a
1443 // search-enhanced NTP without other devices.
1444 TEST_F(SessionsSyncManagerTest
, NotifiedOfRefresh
) {
1445 SessionNotificationObserver observer
;
1446 ASSERT_FALSE(observer
.notified_of_refresh());
1447 InitWithNoSyncData();
1448 AddTab(browser(), GURL("http://foo1"));
1449 EXPECT_FALSE(observer
.notified_of_refresh());
1450 NavigateAndCommitActiveTab(GURL("chrome://newtab/#open_tabs"));
1451 EXPECT_TRUE(observer
.notified_of_refresh());
1453 #endif // defined(OS_ANDROID) || defined(OS_IOS)
1455 } // namespace browser_sync