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/glue/synced_window_delegate.h"
16 #include "chrome/browser/sync/sessions2/notification_service_sessions_router.h"
17 #include "chrome/browser/sync/sessions2/sessions_util.h"
18 #include "chrome/browser/sync/sessions2/synced_window_delegates_getter.h"
19 #include "chrome/browser/ui/sync/tab_contents_synced_tab_delegate.h"
20 #include "chrome/browser/ui/tabs/tab_strip_model.h"
21 #include "chrome/test/base/browser_with_test_window_test.h"
22 #include "components/sessions/serialized_navigation_entry_test_helper.h"
23 #include "content/public/browser/navigation_entry.h"
24 #include "content/public/browser/notification_details.h"
25 #include "content/public/browser/notification_service.h"
26 #include "content/public/browser/notification_source.h"
27 #include "content/public/browser/web_contents.h"
28 #include "sync/api/sync_error_factory_mock.h"
29 #include "testing/gmock/include/gmock/gmock.h"
30 #include "testing/gtest/include/gtest/gtest.h"
32 using content::WebContents;
33 using sessions::SerializedNavigationEntry;
34 using sessions::SerializedNavigationEntryTestHelper;
35 using syncer::SyncChange;
36 using syncer::SyncData;
38 namespace browser_sync {
42 class SyncedWindowDelegateOverride : public SyncedWindowDelegate {
44 explicit SyncedWindowDelegateOverride(SyncedWindowDelegate* wrapped)
47 virtual ~SyncedWindowDelegateOverride() {}
49 virtual bool HasWindow() const OVERRIDE {
50 return wrapped_->HasWindow();
53 virtual SessionID::id_type GetSessionId() const OVERRIDE {
54 return wrapped_->GetSessionId();
57 virtual int GetTabCount() const OVERRIDE {
58 return wrapped_->GetTabCount();
61 virtual int GetActiveIndex() const OVERRIDE {
62 return wrapped_->GetActiveIndex();
65 virtual bool IsApp() const OVERRIDE {
66 return wrapped_->IsApp();
69 virtual bool IsTypeTabbed() const OVERRIDE {
70 return wrapped_->IsTypeTabbed();
73 virtual bool IsTypePopup() const OVERRIDE {
74 return wrapped_->IsTypePopup();
77 virtual bool IsTabPinned(const SyncedTabDelegate* tab) const OVERRIDE {
78 return wrapped_->IsTabPinned(tab);
81 virtual SyncedTabDelegate* GetTabAt(int index) const OVERRIDE {
82 if (tab_overrides_.find(index) != tab_overrides_.end())
83 return tab_overrides_.find(index)->second;
85 return wrapped_->GetTabAt(index);
88 void OverrideTabAt(int index,
89 SyncedTabDelegate* delegate,
90 SessionID::id_type tab_id) {
91 tab_overrides_[index] = delegate;
92 tab_id_overrides_[index] = tab_id;
95 virtual SessionID::id_type GetTabIdAt(int index) const OVERRIDE {
96 if (tab_id_overrides_.find(index) != tab_id_overrides_.end())
97 return tab_id_overrides_.find(index)->second;
98 return wrapped_->GetTabIdAt(index);
101 virtual bool IsSessionRestoreInProgress() const OVERRIDE {
102 return wrapped_->IsSessionRestoreInProgress();
106 std::map<int, SyncedTabDelegate*> tab_overrides_;
107 std::map<int, SessionID::id_type> tab_id_overrides_;
108 SyncedWindowDelegate* wrapped_;
111 class TestSyncedWindowDelegatesGetter : public SyncedWindowDelegatesGetter {
113 TestSyncedWindowDelegatesGetter(
114 const std::set<SyncedWindowDelegate*>& delegates)
115 : delegates_(delegates) {}
117 virtual const std::set<SyncedWindowDelegate*> GetSyncedWindowDelegates()
122 const std::set<SyncedWindowDelegate*> delegates_;
125 class TestSyncProcessorStub : public syncer::SyncChangeProcessor {
127 explicit TestSyncProcessorStub(syncer::SyncChangeList* output)
129 virtual syncer::SyncError ProcessSyncChanges(
130 const tracked_objects::Location& from_here,
131 const syncer::SyncChangeList& change_list) OVERRIDE {
132 if (error_.IsSet()) {
133 syncer::SyncError error = error_;
134 error_ = syncer::SyncError();
139 output_->insert(output_->end(), change_list.begin(), change_list.end());
141 return syncer::SyncError();
144 virtual syncer::SyncDataList GetAllSyncData(syncer::ModelType type)
146 return sync_data_to_return_;
149 void FailProcessSyncChangesWith(const syncer::SyncError& error) {
153 void SetSyncDataToReturn(const syncer::SyncDataList& data) {
154 sync_data_to_return_ = data;
158 syncer::SyncError error_;
159 syncer::SyncChangeList* output_;
160 syncer::SyncDataList sync_data_to_return_;
163 syncer::SyncChange MakeRemoteChange(
165 const sync_pb::SessionSpecifics& specifics,
166 SyncChange::SyncChangeType type) {
167 sync_pb::EntitySpecifics entity;
168 entity.mutable_session()->CopyFrom(specifics);
169 return syncer::SyncChange(
171 syncer::SyncData::CreateRemoteData(id, entity, base::Time()));
174 void AddTabsToChangeList(
175 const std::vector<sync_pb::SessionSpecifics>& batch,
176 SyncChange::SyncChangeType type,
177 syncer::SyncChangeList* change_list) {
178 std::vector<sync_pb::SessionSpecifics>::const_iterator iter;
179 for (iter = batch.begin();
180 iter != batch.end(); ++iter) {
181 sync_pb::EntitySpecifics entity;
182 entity.mutable_session()->CopyFrom(*iter);
183 change_list->push_back(syncer::SyncChange(
185 syncer::SyncData::CreateRemoteData(iter->tab_node_id(),
186 entity, base::Time())));
190 void AddTabsToSyncDataList(const std::vector<sync_pb::SessionSpecifics> tabs,
191 syncer::SyncDataList* list) {
192 for (size_t i = 0; i < tabs.size(); i++) {
193 sync_pb::EntitySpecifics entity;
194 entity.mutable_session()->CopyFrom(tabs[i]);
195 list->push_back(SyncData::CreateRemoteData(
196 i + 2, entity, base::Time()));
200 class DummyRouter : public LocalSessionEventRouter {
202 virtual ~DummyRouter() {}
203 virtual void StartRoutingTo(LocalSessionEventHandler* handler) OVERRIDE {}
204 virtual void Stop() OVERRIDE {}
207 scoped_ptr<LocalSessionEventRouter> NewDummyRouter() {
208 return scoped_ptr<LocalSessionEventRouter>(new DummyRouter());
213 class SessionsSyncManagerTest
214 : public BrowserWithTestWindowTest,
215 public SessionsSyncManager::SyncInternalApiDelegate {
217 SessionsSyncManagerTest() : test_processor_(NULL) {}
219 virtual void SetUp() OVERRIDE {
220 BrowserWithTestWindowTest::SetUp();
221 browser_sync::NotificationServiceSessionsRouter* router(
222 new browser_sync::NotificationServiceSessionsRouter(
223 profile(), syncer::SyncableService::StartSyncFlare()));
224 manager_.reset(new SessionsSyncManager(profile(), this,
225 scoped_ptr<LocalSessionEventRouter>(router)));
228 virtual void TearDown() OVERRIDE {
229 test_processor_ = NULL;
232 BrowserWithTestWindowTest::TearDown();
235 virtual scoped_ptr<DeviceInfo> GetLocalDeviceInfo() const OVERRIDE {
236 return scoped_ptr<DeviceInfo>(
237 new DeviceInfo(GetLocalSyncCacheGUID(),
238 "Wayne Gretzky's Hacking Box",
241 sync_pb::SyncEnums_DeviceType_TYPE_LINUX));
244 virtual std::string GetLocalSyncCacheGUID() const OVERRIDE {
248 SessionsSyncManager* manager() { return manager_.get(); }
249 SessionSyncTestHelper* helper() { return &helper_; }
251 void InitWithSyncDataTakeOutput(const syncer::SyncDataList& initial_data,
252 syncer::SyncChangeList* output) {
253 test_processor_ = new TestSyncProcessorStub(output);
254 syncer::SyncMergeResult result = manager_->MergeDataAndStartSyncing(
255 syncer::SESSIONS, initial_data,
256 scoped_ptr<syncer::SyncChangeProcessor>(test_processor_),
257 scoped_ptr<syncer::SyncErrorFactory>(
258 new syncer::SyncErrorFactoryMock()));
259 EXPECT_FALSE(result.error().IsSet());
262 void InitWithNoSyncData() {
263 InitWithSyncDataTakeOutput(syncer::SyncDataList(), NULL);
266 void TriggerProcessSyncChangesError() {
267 test_processor_->FailProcessSyncChangesWith(syncer::SyncError(
268 FROM_HERE, syncer::SyncError::DATATYPE_ERROR, "Error",
272 void SetSyncData(const syncer::SyncDataList& data) {
273 test_processor_->SetSyncDataToReturn(data);
276 syncer::SyncChangeList* FilterOutLocalHeaderChanges(
277 syncer::SyncChangeList* list) {
278 syncer::SyncChangeList::iterator it = list->begin();
280 while (it != list->end()) {
281 if (it->sync_data().GetTag() == manager_->current_machine_tag()) {
282 EXPECT_TRUE(SyncChange::ACTION_ADD == it->change_type() ||
283 SyncChange::ACTION_UPDATE == it->change_type());
284 it = list->erase(it);
295 scoped_ptr<SessionsSyncManager> manager_;
296 SessionSyncTestHelper helper_;
297 TestSyncProcessorStub* test_processor_;
300 // Test that the SyncSessionManager can properly fill in a SessionHeader.
301 TEST_F(SessionsSyncManagerTest, PopulateSessionHeader) {
302 sync_pb::SessionHeader header_s;
303 header_s.set_client_name("Client 1");
304 header_s.set_device_type(sync_pb::SyncEnums_DeviceType_TYPE_WIN);
306 SyncedSession session;
307 base::Time time = base::Time::Now();
308 SessionsSyncManager::PopulateSessionHeaderFromSpecifics(
309 header_s, time, &session);
310 ASSERT_EQ("Client 1", session.session_name);
311 ASSERT_EQ(SyncedSession::TYPE_WIN, session.device_type);
312 ASSERT_EQ(time, session.modified_time);
315 // Test translation between protobuf types and chrome session types.
316 TEST_F(SessionsSyncManagerTest, PopulateSessionWindow) {
317 sync_pb::SessionWindow window_s;
319 window_s.set_browser_type(sync_pb::SessionWindow_BrowserType_TYPE_TABBED);
320 window_s.set_selected_tab_index(1);
322 std::string tag = "tag";
323 SyncedSession* session = manager()->session_tracker_.GetSession(tag);
324 manager()->session_tracker_.PutWindowInSession(tag, 0);
325 manager()->BuildSyncedSessionFromSpecifics(
326 tag, window_s, base::Time(), session->windows[0]);
327 ASSERT_EQ(1U, session->windows[0]->tabs.size());
328 ASSERT_EQ(1, session->windows[0]->selected_tab_index);
329 ASSERT_EQ(1, session->windows[0]->type);
330 ASSERT_EQ(1U, manager()->session_tracker_.num_synced_sessions());
332 manager()->session_tracker_.num_synced_tabs(std::string("tag")));
337 class SyncedTabDelegateFake : public SyncedTabDelegate {
339 SyncedTabDelegateFake() : current_entry_index_(0),
340 pending_entry_index_(-1),
343 blocked_navigations_(NULL) {}
344 virtual ~SyncedTabDelegateFake() {}
346 virtual int GetCurrentEntryIndex() const OVERRIDE {
347 return current_entry_index_;
349 void set_current_entry_index(int i) {
350 current_entry_index_ = i;
353 virtual content::NavigationEntry* GetEntryAtIndex(int i) const OVERRIDE {
354 const int size = entries_.size();
355 return (size < i + 1) ? NULL : entries_[i];
358 void AppendEntry(content::NavigationEntry* entry) {
359 entries_.push_back(entry);
362 virtual int GetEntryCount() const OVERRIDE {
363 return entries_.size();
366 virtual int GetPendingEntryIndex() const OVERRIDE {
367 return pending_entry_index_;
369 void set_pending_entry_index(int i) {
370 pending_entry_index_ = i;
373 virtual SessionID::id_type GetWindowId() const OVERRIDE {
374 return SessionID::id_type();
377 virtual SessionID::id_type GetSessionId() const OVERRIDE {
378 return SessionID::id_type();
381 virtual bool IsBeingDestroyed() const OVERRIDE { return false; }
382 virtual Profile* profile() const OVERRIDE { return NULL; }
383 virtual std::string GetExtensionAppId() const OVERRIDE {
384 return std::string();
386 virtual content::NavigationEntry* GetPendingEntry() const OVERRIDE {
389 virtual content::NavigationEntry* GetActiveEntry() const OVERRIDE {
392 virtual bool ProfileIsManaged() const OVERRIDE {
395 void set_is_managed(bool is_managed) { is_managed_ = is_managed; }
396 virtual const std::vector<const content::NavigationEntry*>*
397 GetBlockedNavigations() const OVERRIDE {
398 return blocked_navigations_;
400 void set_blocked_navigations(
401 std::vector<const content::NavigationEntry*>* navs) {
402 blocked_navigations_ = navs;
404 virtual bool IsPinned() const OVERRIDE {
407 virtual bool HasWebContents() const OVERRIDE {
410 virtual content::WebContents* GetWebContents() const OVERRIDE {
414 // Session sync related methods.
415 virtual int GetSyncId() const OVERRIDE {
418 virtual void SetSyncId(int sync_id) OVERRIDE {
423 current_entry_index_ = 0;
424 pending_entry_index_ = -1;
430 int current_entry_index_;
431 int pending_entry_index_;
434 std::vector<const content::NavigationEntry*>* blocked_navigations_;
435 ScopedVector<content::NavigationEntry> entries_;
440 // Test that we exclude tabs with only chrome:// and file:// schemed navigations
441 // from ShouldSyncTab(..).
442 TEST_F(SessionsSyncManagerTest, ValidTabs) {
443 SyncedTabDelegateFake tab;
445 // A null entry shouldn't crash.
446 tab.AppendEntry(NULL);
447 EXPECT_FALSE(sessions_util::ShouldSyncTab(tab));
450 // A chrome:// entry isn't valid.
451 content::NavigationEntry* entry(content::NavigationEntry::Create());
452 entry->SetVirtualURL(GURL("chrome://preferences/"));
453 tab.AppendEntry(entry);
454 EXPECT_FALSE(sessions_util::ShouldSyncTab(tab));
457 // A file:// entry isn't valid, even in addition to another entry.
458 content::NavigationEntry* entry2(content::NavigationEntry::Create());
459 entry2->SetVirtualURL(GURL("file://bla"));
460 tab.AppendEntry(entry2);
461 EXPECT_FALSE(sessions_util::ShouldSyncTab(tab));
463 // Add a valid scheme entry to tab, making the tab valid.
464 content::NavigationEntry* entry3(content::NavigationEntry::Create());
465 entry3->SetVirtualURL(GURL("http://www.google.com"));
466 tab.AppendEntry(entry3);
467 EXPECT_FALSE(sessions_util::ShouldSyncTab(tab));
470 // Make sure GetCurrentVirtualURL() returns the virtual URL of the pending
471 // entry if the current entry is pending.
472 TEST_F(SessionsSyncManagerTest, GetCurrentVirtualURLPending) {
473 SyncedTabDelegateFake tab;
474 content::NavigationEntry* entry(content::NavigationEntry::Create());
475 entry->SetVirtualURL(GURL("http://www.google.com"));
476 tab.AppendEntry(entry);
477 EXPECT_EQ(entry->GetVirtualURL(), manager()->GetCurrentVirtualURL(tab));
480 // Make sure GetCurrentVirtualURL() returns the virtual URL of the current
481 // entry if the current entry is non-pending.
482 TEST_F(SessionsSyncManagerTest, GetCurrentVirtualURLNonPending) {
483 SyncedTabDelegateFake tab;
484 content::NavigationEntry* entry(content::NavigationEntry::Create());
485 entry->SetVirtualURL(GURL("http://www.google.com"));
486 tab.AppendEntry(entry);
487 EXPECT_EQ(entry->GetVirtualURL(), manager()->GetCurrentVirtualURL(tab));
490 static const base::Time kTime1 = base::Time::FromInternalValue(100);
491 static const base::Time kTime2 = base::Time::FromInternalValue(105);
492 static const base::Time kTime3 = base::Time::FromInternalValue(110);
493 static const base::Time kTime4 = base::Time::FromInternalValue(120);
494 static const base::Time kTime5 = base::Time::FromInternalValue(130);
496 // Populate the mock tab delegate with some data and navigation
497 // entries and make sure that setting a SessionTab from it preserves
498 // those entries (and clobbers any existing data).
499 TEST_F(SessionsSyncManagerTest, SetSessionTabFromDelegate) {
500 // Create a tab with three valid entries.
501 SyncedTabDelegateFake tab;
502 content::NavigationEntry* entry1(content::NavigationEntry::Create());
503 entry1->SetVirtualURL(GURL("http://www.google.com"));
504 entry1->SetTimestamp(kTime1);
505 entry1->SetHttpStatusCode(200);
506 content::NavigationEntry* entry2(content::NavigationEntry::Create());
507 entry2->SetVirtualURL(GURL("http://www.noodle.com"));
508 entry2->SetTimestamp(kTime2);
509 entry2->SetHttpStatusCode(201);
510 content::NavigationEntry* entry3(content::NavigationEntry::Create());
511 entry3->SetVirtualURL(GURL("http://www.doodle.com"));
512 entry3->SetTimestamp(kTime3);
513 entry3->SetHttpStatusCode(202);
515 tab.AppendEntry(entry1);
516 tab.AppendEntry(entry2);
517 tab.AppendEntry(entry3);
518 tab.set_current_entry_index(2);
520 SessionTab session_tab;
521 session_tab.window_id.set_id(1);
522 session_tab.tab_id.set_id(1);
523 session_tab.tab_visual_index = 1;
524 session_tab.current_navigation_index = 1;
525 session_tab.pinned = true;
526 session_tab.extension_app_id = "app id";
527 session_tab.user_agent_override = "override";
528 session_tab.timestamp = kTime5;
529 session_tab.navigations.push_back(
530 SerializedNavigationEntryTestHelper::CreateNavigation(
531 "http://www.example.com", "Example"));
532 session_tab.session_storage_persistent_id = "persistent id";
533 manager()->SetSessionTabFromDelegate(tab, kTime4, &session_tab);
535 EXPECT_EQ(0, session_tab.window_id.id());
536 EXPECT_EQ(0, session_tab.tab_id.id());
537 EXPECT_EQ(0, session_tab.tab_visual_index);
538 EXPECT_EQ(2, session_tab.current_navigation_index);
539 EXPECT_FALSE(session_tab.pinned);
540 EXPECT_TRUE(session_tab.extension_app_id.empty());
541 EXPECT_TRUE(session_tab.user_agent_override.empty());
542 EXPECT_EQ(kTime4, session_tab.timestamp);
543 ASSERT_EQ(3u, session_tab.navigations.size());
544 EXPECT_EQ(entry1->GetVirtualURL(),
545 session_tab.navigations[0].virtual_url());
546 EXPECT_EQ(entry2->GetVirtualURL(),
547 session_tab.navigations[1].virtual_url());
548 EXPECT_EQ(entry3->GetVirtualURL(),
549 session_tab.navigations[2].virtual_url());
550 EXPECT_EQ(kTime1, session_tab.navigations[0].timestamp());
551 EXPECT_EQ(kTime2, session_tab.navigations[1].timestamp());
552 EXPECT_EQ(kTime3, session_tab.navigations[2].timestamp());
553 EXPECT_EQ(200, session_tab.navigations[0].http_status_code());
554 EXPECT_EQ(201, session_tab.navigations[1].http_status_code());
555 EXPECT_EQ(202, session_tab.navigations[2].http_status_code());
556 EXPECT_EQ(SerializedNavigationEntry::STATE_INVALID,
557 session_tab.navigations[0].blocked_state());
558 EXPECT_EQ(SerializedNavigationEntry::STATE_INVALID,
559 session_tab.navigations[1].blocked_state());
560 EXPECT_EQ(SerializedNavigationEntry::STATE_INVALID,
561 session_tab.navigations[2].blocked_state());
562 EXPECT_TRUE(session_tab.session_storage_persistent_id.empty());
565 // Tests that for managed users blocked navigations are recorded and marked as
566 // such, while regular navigations are marked as allowed.
567 TEST_F(SessionsSyncManagerTest, BlockedNavigations) {
568 SyncedTabDelegateFake tab;
569 content::NavigationEntry* entry1(content::NavigationEntry::Create());
570 entry1->SetVirtualURL(GURL("http://www.google.com"));
571 entry1->SetTimestamp(kTime1);
572 tab.AppendEntry(entry1);
574 content::NavigationEntry* entry2 = content::NavigationEntry::Create();
575 entry2->SetVirtualURL(GURL("http://blocked.com/foo"));
576 entry2->SetTimestamp(kTime2);
577 content::NavigationEntry* entry3 = content::NavigationEntry::Create();
578 entry3->SetVirtualURL(GURL("http://evil.com"));
579 entry3->SetTimestamp(kTime3);
580 ScopedVector<const content::NavigationEntry> blocked_navigations;
581 blocked_navigations.push_back(entry2);
582 blocked_navigations.push_back(entry3);
584 tab.set_is_managed(true);
585 tab.set_blocked_navigations(&blocked_navigations.get());
587 SessionTab session_tab;
588 session_tab.window_id.set_id(1);
589 session_tab.tab_id.set_id(1);
590 session_tab.tab_visual_index = 1;
591 session_tab.current_navigation_index = 1;
592 session_tab.pinned = true;
593 session_tab.extension_app_id = "app id";
594 session_tab.user_agent_override = "override";
595 session_tab.timestamp = kTime5;
596 session_tab.navigations.push_back(
597 SerializedNavigationEntryTestHelper::CreateNavigation(
598 "http://www.example.com", "Example"));
599 session_tab.session_storage_persistent_id = "persistent id";
600 manager()->SetSessionTabFromDelegate(tab, kTime4, &session_tab);
602 EXPECT_EQ(0, session_tab.window_id.id());
603 EXPECT_EQ(0, session_tab.tab_id.id());
604 EXPECT_EQ(0, session_tab.tab_visual_index);
605 EXPECT_EQ(0, session_tab.current_navigation_index);
606 EXPECT_FALSE(session_tab.pinned);
607 EXPECT_TRUE(session_tab.extension_app_id.empty());
608 EXPECT_TRUE(session_tab.user_agent_override.empty());
609 EXPECT_EQ(kTime4, session_tab.timestamp);
610 ASSERT_EQ(3u, session_tab.navigations.size());
611 EXPECT_EQ(entry1->GetVirtualURL(),
612 session_tab.navigations[0].virtual_url());
613 EXPECT_EQ(entry2->GetVirtualURL(),
614 session_tab.navigations[1].virtual_url());
615 EXPECT_EQ(entry3->GetVirtualURL(),
616 session_tab.navigations[2].virtual_url());
617 EXPECT_EQ(kTime1, session_tab.navigations[0].timestamp());
618 EXPECT_EQ(kTime2, session_tab.navigations[1].timestamp());
619 EXPECT_EQ(kTime3, session_tab.navigations[2].timestamp());
620 EXPECT_EQ(SerializedNavigationEntry::STATE_ALLOWED,
621 session_tab.navigations[0].blocked_state());
622 EXPECT_EQ(SerializedNavigationEntry::STATE_BLOCKED,
623 session_tab.navigations[1].blocked_state());
624 EXPECT_EQ(SerializedNavigationEntry::STATE_BLOCKED,
625 session_tab.navigations[2].blocked_state());
626 EXPECT_TRUE(session_tab.session_storage_persistent_id.empty());
629 // Tests that the local session header objects is created properly in
630 // presence of no other session activity, once and only once.
631 TEST_F(SessionsSyncManagerTest, MergeLocalSessionNoTabs) {
632 syncer::SyncChangeList out;
633 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out);
634 EXPECT_FALSE(manager()->current_machine_tag().empty());
636 EXPECT_EQ(2U, out.size());
637 EXPECT_TRUE(out[0].IsValid());
638 EXPECT_EQ(SyncChange::ACTION_ADD, out[0].change_type());
639 const SyncData data(out[0].sync_data());
640 EXPECT_EQ(manager()->current_machine_tag(), data.GetTag());
641 const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session());
642 EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag());
643 EXPECT_TRUE(specifics.has_header());
644 const sync_pb::SessionHeader& header_s = specifics.header();
645 EXPECT_TRUE(header_s.has_device_type());
646 EXPECT_EQ(GetLocalDeviceInfo()->client_name(), header_s.client_name());
647 EXPECT_EQ(0, header_s.window_size());
649 EXPECT_TRUE(out[1].IsValid());
650 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[1].change_type());
651 const SyncData data_2(out[1].sync_data());
652 EXPECT_EQ(manager()->current_machine_tag(), data_2.GetTag());
653 const sync_pb::SessionSpecifics& specifics2(data_2.GetSpecifics().session());
654 EXPECT_EQ(manager()->current_machine_tag(), specifics2.session_tag());
655 EXPECT_TRUE(specifics2.has_header());
656 const sync_pb::SessionHeader& header_s2 = specifics2.header();
657 EXPECT_EQ(0, header_s2.window_size());
659 // Now take that header node and feed it in as input.
660 SyncData d(SyncData::CreateRemoteData(1, data.GetSpecifics(), base::Time()));
661 syncer::SyncDataList in(&d, &d + 1);
663 SessionsSyncManager manager2(profile(), this, NewDummyRouter());
664 syncer::SyncMergeResult result = manager2.MergeDataAndStartSyncing(
665 syncer::SESSIONS, in,
666 scoped_ptr<syncer::SyncChangeProcessor>(
667 new TestSyncProcessorStub(&out)),
668 scoped_ptr<syncer::SyncErrorFactory>(
669 new syncer::SyncErrorFactoryMock()));
670 ASSERT_FALSE(result.error().IsSet());
672 EXPECT_EQ(1U, out.size());
673 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[0].change_type());
674 EXPECT_TRUE(out[0].sync_data().GetSpecifics().session().has_header());
677 // Ensure model association associates the pre-existing tabs.
678 TEST_F(SessionsSyncManagerTest, SwappedOutOnRestore) {
679 AddTab(browser(), GURL("http://foo1"));
680 NavigateAndCommitActiveTab(GURL("http://foo2"));
681 AddTab(browser(), GURL("http://bar1"));
682 NavigateAndCommitActiveTab(GURL("http://bar2"));
683 AddTab(browser(), GURL("http://baz1"));
684 NavigateAndCommitActiveTab(GURL("http://baz2"));
685 const int kRestoredTabId = 1337;
686 const int kNewTabId = 2468;
688 syncer::SyncDataList in;
689 syncer::SyncChangeList out;
690 InitWithSyncDataTakeOutput(in, &out);
692 // Should be one header add, 3 tab add/update pairs, one header update.
693 ASSERT_EQ(8U, out.size());
695 // For input, we set up:
696 // * one "normal" fully loaded tab
697 // * one "frozen" tab with no WebContents and a tab_id change
698 // * one "frozen" tab with no WebContents and no tab_id change
699 SyncData t0(SyncData::CreateRemoteData(1, out[2].sync_data().GetSpecifics(),
701 sync_pb::EntitySpecifics entity(out[4].sync_data().GetSpecifics());
702 entity.mutable_session()->mutable_tab()->set_tab_id(kRestoredTabId);
703 SyncData t1(SyncData::CreateRemoteData(2, entity, base::Time()));
704 SyncData t2(SyncData::CreateRemoteData(3, out[6].sync_data().GetSpecifics(),
710 manager()->StopSyncing(syncer::SESSIONS);
712 const std::set<SyncedWindowDelegate*> windows(
713 SyncedWindowDelegate::GetSyncedWindowDelegates());
714 ASSERT_EQ(1U, windows.size());
715 SyncedTabDelegateFake t1_override, t2_override;
716 t1_override.SetSyncId(1); // No WebContents by default.
717 t2_override.SetSyncId(2); // No WebContents by default.
718 SyncedWindowDelegateOverride window_override(*windows.begin());
719 window_override.OverrideTabAt(1, &t1_override, kNewTabId);
720 window_override.OverrideTabAt(2, &t2_override,
721 t2.GetSpecifics().session().tab().tab_id());
722 std::set<SyncedWindowDelegate*> delegates;
723 delegates.insert(&window_override);
724 scoped_ptr<TestSyncedWindowDelegatesGetter> getter(
725 new TestSyncedWindowDelegatesGetter(delegates));
726 manager()->synced_window_getter_.reset(getter.release());
728 syncer::SyncMergeResult result = manager()->MergeDataAndStartSyncing(
729 syncer::SESSIONS, in,
730 scoped_ptr<syncer::SyncChangeProcessor>(
731 new TestSyncProcessorStub(&out)),
732 scoped_ptr<syncer::SyncErrorFactory>(
733 new syncer::SyncErrorFactoryMock()));
735 // There should be two changes, one for the fully associated tab, and
736 // one for the tab_id update to t1. t2 shouldn't need to be updated.
737 ASSERT_EQ(2U, FilterOutLocalHeaderChanges(&out)->size());
738 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[0].change_type());
739 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[1].change_type());
741 out[1].sync_data().GetSpecifics().session().tab().tab_id());
744 SessionsSyncManager::TabLinksMap tab_map = manager()->local_tab_map_;
745 ASSERT_EQ(3U, tab_map.size());
746 int t2_tab_id = t2.GetSpecifics().session().tab().tab_id();
747 EXPECT_EQ(2, tab_map.find(t2_tab_id)->second->tab_node_id());
748 EXPECT_EQ(1, tab_map.find(kNewTabId)->second->tab_node_id());
749 int t0_tab_id = out[0].sync_data().GetSpecifics().session().tab().tab_id();
750 EXPECT_EQ(0, tab_map.find(t0_tab_id)->second->tab_node_id());
751 // TODO(tim): Once bug 337057 is fixed, we can issue an OnLocalTabModified
752 // from here (using an override similar to above) to return a new tab id
753 // and verify that we don't see any node creations in the SyncChangeProcessor
754 // (similar to how SessionsSyncManagerTest.OnLocalTabModified works.)
757 // Tests MergeDataAndStartSyncing with sync data but no local data.
758 TEST_F(SessionsSyncManagerTest, MergeWithInitialForeignSession) {
759 std::string tag = "tag1";
761 SessionID::id_type n1[] = {5, 10, 13, 17};
762 std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1));
763 std::vector<sync_pb::SessionSpecifics> tabs1;
764 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
765 tag, tab_list1, &tabs1));
766 // Add a second window.
767 SessionID::id_type n2[] = {7, 15, 18, 20};
768 std::vector<SessionID::id_type> tab_list2(n2, n2 + arraysize(n2));
769 helper()->AddWindowSpecifics(1, tab_list2, &meta);
771 // Set up initial data.
772 syncer::SyncDataList initial_data;
773 sync_pb::EntitySpecifics entity;
774 entity.mutable_session()->CopyFrom(meta);
775 initial_data.push_back(SyncData::CreateRemoteData(1, entity, base::Time()));
776 AddTabsToSyncDataList(tabs1, &initial_data);
778 for (size_t i = 0; i < tab_list2.size(); ++i) {
779 sync_pb::EntitySpecifics entity;
780 helper()->BuildTabSpecifics(tag, 0, tab_list2[i],
781 entity.mutable_session());
782 initial_data.push_back(
783 SyncData::CreateRemoteData(i + 10, entity, base::Time()));
786 syncer::SyncChangeList output;
787 InitWithSyncDataTakeOutput(initial_data, &output);
788 EXPECT_TRUE(FilterOutLocalHeaderChanges(&output)->empty());
790 std::vector<const SyncedSession*> foreign_sessions;
791 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
792 ASSERT_EQ(1U, foreign_sessions.size());
793 std::vector<std::vector<SessionID::id_type> > session_reference;
794 session_reference.push_back(tab_list1);
795 session_reference.push_back(tab_list2);
796 helper()->VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));
799 // This is a combination of MergeWithInitialForeignSession and
800 // MergeLocalSessionExistingTabs. We repeat some checks performed in each of
801 // those tests to ensure the common mixed scenario works.
802 TEST_F(SessionsSyncManagerTest, MergeWithLocalAndForeignTabs) {
804 AddTab(browser(), GURL("http://foo1"));
805 NavigateAndCommitActiveTab(GURL("http://foo2"));
808 std::string tag = "tag1";
809 SessionID::id_type n1[] = {5, 10, 13, 17};
810 std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1));
811 std::vector<sync_pb::SessionSpecifics> tabs1;
812 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
813 tag, tab_list1, &tabs1));
814 syncer::SyncDataList foreign_data;
815 sync_pb::EntitySpecifics entity;
816 entity.mutable_session()->CopyFrom(meta);
817 foreign_data.push_back(SyncData::CreateRemoteData(1, entity, base::Time()));
818 AddTabsToSyncDataList(tabs1, &foreign_data);
820 syncer::SyncChangeList output;
821 InitWithSyncDataTakeOutput(foreign_data, &output);
822 ASSERT_EQ(4U, output.size());
824 // Verify the local header.
825 EXPECT_TRUE(output[0].IsValid());
826 EXPECT_EQ(SyncChange::ACTION_ADD, output[0].change_type());
827 const SyncData data(output[0].sync_data());
828 EXPECT_EQ(manager()->current_machine_tag(), data.GetTag());
829 const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session());
830 EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag());
831 EXPECT_TRUE(specifics.has_header());
832 const sync_pb::SessionHeader& header_s = specifics.header();
833 EXPECT_TRUE(header_s.has_device_type());
834 EXPECT_EQ(GetLocalDeviceInfo()->client_name(), header_s.client_name());
835 EXPECT_EQ(0, header_s.window_size());
837 // Verify the tab node creations and updates with content.
838 for (int i = 1; i < 3; i++) {
839 EXPECT_TRUE(output[i].IsValid());
840 const SyncData data(output[i].sync_data());
841 EXPECT_TRUE(StartsWithASCII(data.GetTag(),
842 manager()->current_machine_tag(), true));
843 const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session());
844 EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag());
846 EXPECT_EQ(SyncChange::ACTION_ADD, output[1].change_type());
847 EXPECT_EQ(SyncChange::ACTION_UPDATE, output[2].change_type());
848 EXPECT_TRUE(output[2].sync_data().GetSpecifics().session().has_tab());
850 // Verify the header was updated to reflect window state.
851 EXPECT_TRUE(output[3].IsValid());
852 EXPECT_EQ(SyncChange::ACTION_UPDATE, output[3].change_type());
853 const SyncData data_2(output[3].sync_data());
854 EXPECT_EQ(manager()->current_machine_tag(), data_2.GetTag());
855 const sync_pb::SessionSpecifics& specifics2(data_2.GetSpecifics().session());
856 EXPECT_EQ(manager()->current_machine_tag(), specifics2.session_tag());
857 EXPECT_TRUE(specifics2.has_header());
858 const sync_pb::SessionHeader& header_s2 = specifics2.header();
859 EXPECT_EQ(1, header_s2.window_size());
861 // Verify foreign data.
862 std::vector<const SyncedSession*> foreign_sessions;
863 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
864 std::vector<std::vector<SessionID::id_type> > session_reference;
865 session_reference.push_back(tab_list1);
866 helper()->VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));
867 // There should be one and only one foreign session. If VerifySyncedSession
868 // was successful above this EXPECT call ensures the local session didn't
869 // get mistakenly added to foreign tracking (Similar to ExistingTabs test).
870 EXPECT_EQ(1U, foreign_sessions.size());
873 // Tests the common scenario. Merge with both local and foreign session data
874 // followed by updates flowing from sync and local.
875 TEST_F(SessionsSyncManagerTest, UpdatesAfterMixedMerge) {
876 // Add local and foreign data.
877 AddTab(browser(), GURL("http://foo1"));
878 NavigateAndCommitActiveTab(GURL("http://foo2"));
880 std::string tag1 = "tag1";
881 syncer::SyncDataList foreign_data1;
882 std::vector<std::vector<SessionID::id_type> > meta1_reference;
883 sync_pb::SessionSpecifics meta1;
885 SessionID::id_type n1[] = {5, 10, 13, 17};
886 std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1));
887 meta1_reference.push_back(tab_list1);
888 std::vector<sync_pb::SessionSpecifics> tabs1;
889 meta1 = helper()->BuildForeignSession(tag1, tab_list1, &tabs1);
890 sync_pb::EntitySpecifics entity;
891 entity.mutable_session()->CopyFrom(meta1);
892 foreign_data1.push_back(SyncData::CreateRemoteData(
893 1, entity, base::Time()));
894 AddTabsToSyncDataList(tabs1, &foreign_data1);
896 syncer::SyncChangeList output1;
897 InitWithSyncDataTakeOutput(foreign_data1, &output1);
898 ASSERT_EQ(4U, output1.size());
900 // Add a second window to the foreign session.
901 // TODO(tim): Bug 98892. Add local window too when observers are hooked up.
902 SessionID::id_type tab_nums2[] = {7, 15, 18, 20};
903 std::vector<SessionID::id_type> tab_list2(
904 tab_nums2, tab_nums2 + arraysize(tab_nums2));
905 meta1_reference.push_back(tab_list2);
906 helper()->AddWindowSpecifics(1, tab_list2, &meta1);
907 std::vector<sync_pb::SessionSpecifics> tabs2;
908 tabs2.resize(tab_list2.size());
909 for (size_t i = 0; i < tab_list2.size(); ++i) {
910 helper()->BuildTabSpecifics(tag1, 0, tab_list2[i], &tabs2[i]);
913 syncer::SyncChangeList changes;
914 changes.push_back(MakeRemoteChange(1, meta1, SyncChange::ACTION_UPDATE));
915 AddTabsToChangeList(tabs2, SyncChange::ACTION_ADD, &changes);
916 manager()->ProcessSyncChanges(FROM_HERE, changes);
919 // Check that the foreign session was associated and retrieve the data.
920 std::vector<const SyncedSession*> foreign_sessions;
921 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
922 ASSERT_EQ(1U, foreign_sessions.size());
923 ASSERT_EQ(4U, foreign_sessions[0]->windows.find(0)->second->tabs.size());
924 ASSERT_EQ(4U, foreign_sessions[0]->windows.find(1)->second->tabs.size());
925 helper()->VerifySyncedSession(tag1, meta1_reference, *(foreign_sessions[0]));
927 // Add a new foreign session.
928 std::string tag2 = "tag2";
929 SessionID::id_type n2[] = {107, 115};
930 std::vector<SessionID::id_type> tag2_tab_list(n2, n2 + arraysize(n2));
931 std::vector<sync_pb::SessionSpecifics> tag2_tabs;
932 sync_pb::SessionSpecifics meta2(helper()->BuildForeignSession(
933 tag2, tag2_tab_list, &tag2_tabs));
934 changes.push_back(MakeRemoteChange(100, meta2, SyncChange::ACTION_ADD));
935 AddTabsToChangeList(tag2_tabs, SyncChange::ACTION_ADD, &changes);
937 manager()->ProcessSyncChanges(FROM_HERE, changes);
940 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
941 std::vector<std::vector<SessionID::id_type> > meta2_reference;
942 meta2_reference.push_back(tag2_tab_list);
943 ASSERT_EQ(2U, foreign_sessions.size());
944 ASSERT_EQ(2U, foreign_sessions[1]->windows.find(0)->second->tabs.size());
945 helper()->VerifySyncedSession(tag2, meta2_reference, *(foreign_sessions[1]));
946 foreign_sessions.clear();
948 // Remove a tab from a window.
949 meta1_reference[0].pop_back();
950 tab_list1.pop_back();
951 sync_pb::SessionWindow* win = meta1.mutable_header()->mutable_window(0);
953 for (std::vector<int>::const_iterator iter = tab_list1.begin();
954 iter != tab_list1.end(); ++iter) {
957 syncer::SyncChangeList removal;
958 removal.push_back(MakeRemoteChange(1, meta1, SyncChange::ACTION_UPDATE));
959 AddTabsToChangeList(tabs1, SyncChange::ACTION_UPDATE, &removal);
960 manager()->ProcessSyncChanges(FROM_HERE, removal);
962 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
963 ASSERT_EQ(2U, foreign_sessions.size());
964 ASSERT_EQ(3U, foreign_sessions[0]->windows.find(0)->second->tabs.size());
965 helper()->VerifySyncedSession(tag1, meta1_reference, *(foreign_sessions[0]));
968 // Tests that this SyncSessionManager knows how to delete foreign sessions
970 TEST_F(SessionsSyncManagerTest, DeleteForeignSession) {
971 InitWithNoSyncData();
972 std::string tag = "tag1";
973 syncer::SyncChangeList changes;
975 std::vector<const SyncedSession*> foreign_sessions;
976 ASSERT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions));
977 manager()->DeleteForeignSessionInternal(tag, &changes);
978 ASSERT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions));
979 EXPECT_TRUE(changes.empty());
981 // Fill an instance of session specifics with a foreign session's data.
982 std::vector<sync_pb::SessionSpecifics> tabs;
983 SessionID::id_type n1[] = {5, 10, 13, 17};
984 std::vector<SessionID::id_type> tab_nums1(n1, n1 + arraysize(n1));
985 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
986 tag, tab_nums1, &tabs));
988 // Update associator with the session's meta node, window, and tabs.
989 manager()->UpdateTrackerWithForeignSession(meta, base::Time());
990 for (std::vector<sync_pb::SessionSpecifics>::iterator iter = tabs.begin();
991 iter != tabs.end(); ++iter) {
992 manager()->UpdateTrackerWithForeignSession(*iter, base::Time());
994 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
995 ASSERT_EQ(1U, foreign_sessions.size());
997 // Now delete the foreign session.
998 manager()->DeleteForeignSessionInternal(tag, &changes);
999 EXPECT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions));
1001 EXPECT_EQ(5U, changes.size());
1002 std::set<std::string> expected_tags(&tag, &tag + 1);
1003 for (int i = 0; i < 5; i++)
1004 expected_tags.insert(TabNodePool2::TabIdToTag(tag, i));
1006 for (int i = 0; i < 5; i++) {
1007 SCOPED_TRACE(changes[i].ToString());
1008 EXPECT_TRUE(changes[i].IsValid());
1009 EXPECT_EQ(SyncChange::ACTION_DELETE, changes[i].change_type());
1010 EXPECT_TRUE(changes[i].sync_data().IsValid());
1011 EXPECT_EQ(1U, expected_tags.erase(changes[i].sync_data().GetTag()));
1015 // Write a foreign session to a node, with the tabs arriving first, and then
1017 TEST_F(SessionsSyncManagerTest, WriteForeignSessionToNodeTabsFirst) {
1018 InitWithNoSyncData();
1020 // Fill an instance of session specifics with a foreign session's data.
1021 std::string tag = "tag1";
1022 SessionID::id_type nums1[] = {5, 10, 13, 17};
1023 std::vector<sync_pb::SessionSpecifics> tabs1;
1024 std::vector<SessionID::id_type> tab_list1 (nums1, nums1 + arraysize(nums1));
1025 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
1026 tag, tab_list1, &tabs1));
1028 syncer::SyncChangeList adds;
1029 // Add tabs for first window, then the meta node.
1030 AddTabsToChangeList(tabs1, SyncChange::ACTION_ADD, &adds);
1031 adds.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_ADD));
1032 manager()->ProcessSyncChanges(FROM_HERE, adds);
1034 // Check that the foreign session was associated and retrieve the data.
1035 std::vector<const SyncedSession*> foreign_sessions;
1036 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1037 ASSERT_EQ(1U, foreign_sessions.size());
1038 std::vector<std::vector<SessionID::id_type> > session_reference;
1039 session_reference.push_back(tab_list1);
1040 helper()->VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));
1043 // Write a foreign session to a node with some tabs that never arrive.
1044 TEST_F(SessionsSyncManagerTest, WriteForeignSessionToNodeMissingTabs) {
1045 InitWithNoSyncData();
1047 // Fill an instance of session specifics with a foreign session's data.
1048 std::string tag = "tag1";
1049 SessionID::id_type nums1[] = {5, 10, 13, 17};
1050 std::vector<sync_pb::SessionSpecifics> tabs1;
1051 std::vector<SessionID::id_type> tab_list1 (nums1, nums1 + arraysize(nums1));
1052 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
1053 tag, tab_list1, &tabs1));
1054 // Add a second window, but this time only create two tab nodes, despite the
1055 // window expecting four tabs.
1056 SessionID::id_type tab_nums2[] = {7, 15, 18, 20};
1057 std::vector<SessionID::id_type> tab_list2(
1058 tab_nums2, tab_nums2 + arraysize(tab_nums2));
1059 helper()->AddWindowSpecifics(1, tab_list2, &meta);
1060 std::vector<sync_pb::SessionSpecifics> tabs2;
1062 for (size_t i = 0; i < 2; ++i) {
1063 helper()->BuildTabSpecifics(tag, 0, tab_list2[i], &tabs2[i]);
1066 syncer::SyncChangeList changes;
1067 changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_ADD));
1068 AddTabsToChangeList(tabs1, SyncChange::ACTION_ADD, &changes);
1069 AddTabsToChangeList(tabs2, SyncChange::ACTION_ADD, &changes);
1070 manager()->ProcessSyncChanges(FROM_HERE, changes);
1073 // Check that the foreign session was associated and retrieve the data.
1074 std::vector<const SyncedSession*> foreign_sessions;
1075 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1076 ASSERT_EQ(1U, foreign_sessions.size());
1077 ASSERT_EQ(2U, foreign_sessions[0]->windows.size());
1078 ASSERT_EQ(4U, foreign_sessions[0]->windows.find(0)->second->tabs.size());
1079 ASSERT_EQ(4U, foreign_sessions[0]->windows.find(1)->second->tabs.size());
1081 // Close the second window.
1082 meta.mutable_header()->clear_window();
1083 helper()->AddWindowSpecifics(0, tab_list1, &meta);
1084 changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_UPDATE));
1085 // Update associator with the session's meta node containing one window.
1086 manager()->ProcessSyncChanges(FROM_HERE, changes);
1088 // Check that the foreign session was associated and retrieve the data.
1089 foreign_sessions.clear();
1090 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1091 ASSERT_EQ(1U, foreign_sessions.size());
1092 ASSERT_EQ(1U, foreign_sessions[0]->windows.size());
1093 std::vector<std::vector<SessionID::id_type> > session_reference;
1094 session_reference.push_back(tab_list1);
1095 helper()->VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));
1098 // Tests that the SessionsSyncManager can handle a remote client deleting
1099 // sync nodes that belong to this local session.
1100 TEST_F(SessionsSyncManagerTest, ProcessRemoteDeleteOfLocalSession) {
1101 syncer::SyncChangeList out;
1102 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out);
1103 ASSERT_EQ(2U, out.size());
1104 sync_pb::EntitySpecifics entity(out[0].sync_data().GetSpecifics());
1105 SyncData d(SyncData::CreateRemoteData(1, entity, base::Time()));
1106 SetSyncData(syncer::SyncDataList(&d, &d + 1));
1109 syncer::SyncChangeList changes;
1111 MakeRemoteChange(1, entity.session(), SyncChange::ACTION_DELETE));
1112 manager()->ProcessSyncChanges(FROM_HERE, changes);
1113 EXPECT_TRUE(manager()->local_tab_pool_out_of_sync_);
1114 EXPECT_TRUE(out.empty()); // ChangeProcessor shouldn't see any activity.
1116 // This should trigger repair of the TabNodePool.
1117 const GURL foo1("http://foo/1");
1118 AddTab(browser(), foo1);
1119 EXPECT_FALSE(manager()->local_tab_pool_out_of_sync_);
1121 // AddTab triggers two notifications, one for the tab insertion and one for
1122 // committing the NavigationEntry. The first notification results in a tab
1123 // we don't associate although we do update the header node. The second
1124 // notification triggers association of the tab, and the subsequent window
1125 // update. So we should see 4 changes at the SyncChangeProcessor.
1126 ASSERT_EQ(4U, out.size());
1128 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[0].change_type());
1129 ASSERT_TRUE(out[0].sync_data().GetSpecifics().session().has_header());
1130 EXPECT_EQ(SyncChange::ACTION_ADD, out[1].change_type());
1131 int tab_node_id = out[1].sync_data().GetSpecifics().session().tab_node_id();
1132 EXPECT_EQ(TabNodePool2::TabIdToTag(
1133 manager()->current_machine_tag(), tab_node_id),
1134 out[1].sync_data().GetTag());
1135 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[2].change_type());
1136 ASSERT_TRUE(out[2].sync_data().GetSpecifics().session().has_tab());
1137 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[3].change_type());
1138 ASSERT_TRUE(out[3].sync_data().GetSpecifics().session().has_header());
1140 // Verify the actual content.
1141 const sync_pb::SessionHeader& session_header =
1142 out[3].sync_data().GetSpecifics().session().header();
1143 ASSERT_EQ(1, session_header.window_size());
1144 EXPECT_EQ(1, session_header.window(0).tab_size());
1145 const sync_pb::SessionTab& tab1 =
1146 out[2].sync_data().GetSpecifics().session().tab();
1147 ASSERT_EQ(1, tab1.navigation_size());
1148 EXPECT_EQ(foo1.spec(), tab1.navigation(0).virtual_url());
1150 // Verify TabNodePool integrity.
1151 EXPECT_EQ(1U, manager()->local_tab_pool_.Capacity());
1152 EXPECT_TRUE(manager()->local_tab_pool_.Empty());
1155 SessionsSyncManager::TabLinksMap tab_map = manager()->local_tab_map_;
1156 ASSERT_EQ(1U, tab_map.size());
1157 int tab_id = out[2].sync_data().GetSpecifics().session().tab().tab_id();
1158 EXPECT_EQ(tab_node_id, tab_map.find(tab_id)->second->tab_node_id());
1161 // Test that receiving a session delete from sync removes the session
1163 TEST_F(SessionsSyncManagerTest, ProcessForeignDelete) {
1164 InitWithNoSyncData();
1165 SessionID::id_type n[] = {5};
1166 std::vector<sync_pb::SessionSpecifics> tabs1;
1167 std::vector<SessionID::id_type> tab_list(n, n + arraysize(n));
1168 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
1169 "tag1", tab_list, &tabs1));
1171 syncer::SyncChangeList changes;
1172 changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_ADD));
1173 AddTabsToChangeList(tabs1, SyncChange::ACTION_ADD, &changes);
1174 manager()->ProcessSyncChanges(FROM_HERE, changes);
1176 std::vector<const SyncedSession*> foreign_sessions;
1177 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1178 ASSERT_EQ(1U, foreign_sessions.size());
1181 foreign_sessions.clear();
1182 changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_DELETE));
1183 manager()->ProcessSyncChanges(FROM_HERE, changes);
1185 EXPECT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions));
1188 // TODO(shashishekhar): "Move this to TabNodePool unittests."
1189 TEST_F(SessionsSyncManagerTest, SaveUnassociatedNodesForReassociation) {
1190 syncer::SyncChangeList changes;
1191 InitWithNoSyncData();
1193 std::string local_tag = manager()->current_machine_tag();
1194 // Create a free node and then dissassociate sessions so that it ends up
1196 manager()->local_tab_pool_.GetFreeTabNode(&changes);
1198 // Update the tab_id of the node, so that it is considered a valid
1199 // unassociated node otherwise it will be mistaken for a corrupted node and
1200 // will be deleted before being added to the tab node pool.
1201 sync_pb::EntitySpecifics entity(changes[0].sync_data().GetSpecifics());
1202 entity.mutable_session()->mutable_tab()->set_tab_id(1);
1203 SyncData d(SyncData::CreateRemoteData(1, entity, base::Time()));
1204 syncer::SyncDataList in(&d, &d + 1);
1206 SessionsSyncManager manager2(profile(), this, NewDummyRouter());
1207 syncer::SyncMergeResult result = manager2.MergeDataAndStartSyncing(
1208 syncer::SESSIONS, in,
1209 scoped_ptr<syncer::SyncChangeProcessor>(
1210 new TestSyncProcessorStub(&changes)),
1211 scoped_ptr<syncer::SyncErrorFactory>(
1212 new syncer::SyncErrorFactoryMock()));
1213 ASSERT_FALSE(result.error().IsSet());
1214 EXPECT_TRUE(FilterOutLocalHeaderChanges(&changes)->empty());
1217 TEST_F(SessionsSyncManagerTest, MergeDeletesCorruptNode) {
1218 syncer::SyncChangeList changes;
1219 InitWithNoSyncData();
1221 std::string local_tag = manager()->current_machine_tag();
1222 int tab_node_id = manager()->local_tab_pool_.GetFreeTabNode(&changes);
1223 SyncData d(SyncData::CreateRemoteData(
1224 1, changes[0].sync_data().GetSpecifics(), base::Time()));
1225 syncer::SyncDataList in(&d, &d + 1);
1229 InitWithSyncDataTakeOutput(in, &changes);
1230 EXPECT_EQ(1U, FilterOutLocalHeaderChanges(&changes)->size());
1231 EXPECT_EQ(SyncChange::ACTION_DELETE, changes[0].change_type());
1232 EXPECT_EQ(TabNodePool2::TabIdToTag(local_tag, tab_node_id),
1233 changes[0].sync_data().GetTag());
1236 // Test that things work if a tab is initially ignored.
1237 TEST_F(SessionsSyncManagerTest, AssociateWindowsDontReloadTabs) {
1238 syncer::SyncChangeList out;
1239 // Go to a URL that is ignored by session syncing.
1240 AddTab(browser(), GURL("chrome://preferences/"));
1241 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out);
1242 ASSERT_EQ(2U, out.size()); // Header add and update.
1245 out[1].sync_data().GetSpecifics().session().header().window_size());
1248 // Go to a sync-interesting URL.
1249 NavigateAndCommitActiveTab(GURL("http://foo2"));
1251 EXPECT_EQ(3U, out.size()); // Tab add, update, and header update.
1253 EXPECT_TRUE(StartsWithASCII(out[0].sync_data().GetTag(),
1254 manager()->current_machine_tag(), true));
1255 EXPECT_EQ(manager()->current_machine_tag(),
1256 out[0].sync_data().GetSpecifics().session().session_tag());
1257 EXPECT_EQ(SyncChange::ACTION_ADD, out[0].change_type());
1259 EXPECT_TRUE(StartsWithASCII(out[1].sync_data().GetTag(),
1260 manager()->current_machine_tag(), true));
1261 EXPECT_EQ(manager()->current_machine_tag(),
1262 out[1].sync_data().GetSpecifics().session().session_tag());
1263 EXPECT_TRUE(out[1].sync_data().GetSpecifics().session().has_tab());
1264 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[1].change_type());
1266 EXPECT_TRUE(out[2].IsValid());
1267 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[2].change_type());
1268 const SyncData data(out[2].sync_data());
1269 EXPECT_EQ(manager()->current_machine_tag(), data.GetTag());
1270 const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session());
1271 EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag());
1272 EXPECT_TRUE(specifics.has_header());
1273 const sync_pb::SessionHeader& header_s = specifics.header();
1274 EXPECT_EQ(1, header_s.window_size());
1275 EXPECT_EQ(1, header_s.window(0).tab_size());
1278 // Tests that the SyncSessionManager responds to local tab events properly.
1279 TEST_F(SessionsSyncManagerTest, OnLocalTabModified) {
1280 syncer::SyncChangeList out;
1281 // Init with no local data, relies on MergeLocalSessionNoTabs.
1282 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out);
1283 ASSERT_FALSE(manager()->current_machine_tag().empty());
1284 ASSERT_EQ(2U, out.size());
1286 // Copy the original header.
1287 sync_pb::EntitySpecifics header(out[0].sync_data().GetSpecifics());
1290 const GURL foo1("http://foo/1");
1291 const GURL foo2("http://foo/2");
1292 const GURL bar1("http://bar/1");
1293 const GURL bar2("http://bar/2");
1294 AddTab(browser(), foo1);
1295 NavigateAndCommitActiveTab(foo2);
1296 AddTab(browser(), bar1);
1297 NavigateAndCommitActiveTab(bar2);
1299 // One add, one update for each AddTab.
1300 // One update for each NavigateAndCommit.
1301 // = 6 total tab updates.
1302 // One header update corresponding to each of those.
1303 // = 6 total header updates.
1304 // 12 total updates.
1305 ASSERT_EQ(12U, out.size());
1307 // Verify the tab node creations and updates to ensure the SyncProcessor
1308 // sees the right operations.
1309 for (int i = 0; i < 12; i++) {
1311 EXPECT_TRUE(out[i].IsValid());
1312 const SyncData data(out[i].sync_data());
1313 EXPECT_TRUE(StartsWithASCII(data.GetTag(),
1314 manager()->current_machine_tag(), true));
1315 const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session());
1316 EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag());
1318 // First thing on an AddTab is a no-op header update for parented tab.
1319 EXPECT_EQ(header.SerializeAsString(),
1320 data.GetSpecifics().SerializeAsString());
1321 EXPECT_EQ(manager()->current_machine_tag(), data.GetTag());
1322 } else if (i % 6 == 1) {
1323 // Next, the TabNodePool should create the tab node.
1324 EXPECT_EQ(SyncChange::ACTION_ADD, out[i].change_type());
1325 EXPECT_EQ(TabNodePool2::TabIdToTag(
1326 manager()->current_machine_tag(),
1327 data.GetSpecifics().session().tab_node_id()),
1329 } else if (i % 6 == 2) {
1330 // Then we see the tab update to the URL.
1331 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type());
1332 EXPECT_EQ(TabNodePool2::TabIdToTag(
1333 manager()->current_machine_tag(),
1334 data.GetSpecifics().session().tab_node_id()),
1336 ASSERT_TRUE(specifics.has_tab());
1337 } else if (i % 6 == 3) {
1338 // The header needs to be updated to reflect the new window state.
1339 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type());
1340 EXPECT_TRUE(specifics.has_header());
1341 } else if (i % 6 == 4) {
1342 // Now we move on to NavigateAndCommit. Update the tab.
1343 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type());
1344 EXPECT_EQ(TabNodePool2::TabIdToTag(
1345 manager()->current_machine_tag(),
1346 data.GetSpecifics().session().tab_node_id()),
1348 ASSERT_TRUE(specifics.has_tab());
1349 } else if (i % 6 == 5) {
1350 // The header needs to be updated to reflect the new window state.
1351 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type());
1352 ASSERT_TRUE(specifics.has_header());
1353 header = data.GetSpecifics();
1357 // Verify the actual content to ensure sync sees the right data.
1358 // When it's all said and done, the header should reflect two tabs.
1359 const sync_pb::SessionHeader& session_header = header.session().header();
1360 ASSERT_EQ(1, session_header.window_size());
1361 EXPECT_EQ(2, session_header.window(0).tab_size());
1363 // ASSERT_TRUEs above allow us to dive in freely here.
1364 // Verify first tab.
1365 const sync_pb::SessionTab& tab1_1 =
1366 out[2].sync_data().GetSpecifics().session().tab();
1367 ASSERT_EQ(1, tab1_1.navigation_size());
1368 EXPECT_EQ(foo1.spec(), tab1_1.navigation(0).virtual_url());
1369 const sync_pb::SessionTab& tab1_2 =
1370 out[4].sync_data().GetSpecifics().session().tab();
1371 ASSERT_EQ(2, tab1_2.navigation_size());
1372 EXPECT_EQ(foo1.spec(), tab1_2.navigation(0).virtual_url());
1373 EXPECT_EQ(foo2.spec(), tab1_2.navigation(1).virtual_url());
1375 // Verify second tab.
1376 const sync_pb::SessionTab& tab2_1 =
1377 out[8].sync_data().GetSpecifics().session().tab();
1378 ASSERT_EQ(1, tab2_1.navigation_size());
1379 EXPECT_EQ(bar1.spec(), tab2_1.navigation(0).virtual_url());
1380 const sync_pb::SessionTab& tab2_2 =
1381 out[10].sync_data().GetSpecifics().session().tab();
1382 ASSERT_EQ(2, tab2_2.navigation_size());
1383 EXPECT_EQ(bar1.spec(), tab2_2.navigation(0).virtual_url());
1384 EXPECT_EQ(bar2.spec(), tab2_2.navigation(1).virtual_url());
1387 // Ensure model association associates the pre-existing tabs.
1388 TEST_F(SessionsSyncManagerTest, MergeLocalSessionExistingTabs) {
1389 AddTab(browser(), GURL("http://foo1"));
1390 NavigateAndCommitActiveTab(GURL("http://foo2")); // Adds back entry.
1391 AddTab(browser(), GURL("http://bar1"));
1392 NavigateAndCommitActiveTab(GURL("http://bar2")); // Adds back entry.
1394 syncer::SyncChangeList out;
1395 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out);
1396 ASSERT_EQ(6U, out.size());
1398 // Check that this machine's data is not included in the foreign windows.
1399 std::vector<const SyncedSession*> foreign_sessions;
1400 ASSERT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions));
1402 // Verify the header.
1403 EXPECT_TRUE(out[0].IsValid());
1404 EXPECT_EQ(SyncChange::ACTION_ADD, out[0].change_type());
1405 const SyncData data(out[0].sync_data());
1406 EXPECT_EQ(manager()->current_machine_tag(), data.GetTag());
1407 const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session());
1408 EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag());
1409 EXPECT_TRUE(specifics.has_header());
1410 const sync_pb::SessionHeader& header_s = specifics.header();
1411 EXPECT_TRUE(header_s.has_device_type());
1412 EXPECT_EQ(GetLocalDeviceInfo()->client_name(), header_s.client_name());
1413 EXPECT_EQ(0, header_s.window_size());
1415 // Verify the tab node creations and updates with content.
1416 for (int i = 1; i < 5; i++) {
1417 EXPECT_TRUE(out[i].IsValid());
1418 const SyncData data(out[i].sync_data());
1419 EXPECT_TRUE(StartsWithASCII(data.GetTag(),
1420 manager()->current_machine_tag(), true));
1421 const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session());
1422 EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag());
1424 EXPECT_EQ(SyncChange::ACTION_ADD, out[i].change_type());
1426 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type());
1427 EXPECT_TRUE(specifics.has_tab());
1431 // Verify the header was updated to reflect new window state.
1432 EXPECT_TRUE(out[5].IsValid());
1433 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[5].change_type());
1434 const SyncData data_2(out[5].sync_data());
1435 EXPECT_EQ(manager()->current_machine_tag(), data_2.GetTag());
1436 const sync_pb::SessionSpecifics& specifics2(data_2.GetSpecifics().session());
1437 EXPECT_EQ(manager()->current_machine_tag(), specifics2.session_tag());
1438 EXPECT_TRUE(specifics2.has_header());
1439 const sync_pb::SessionHeader& header_s2 = specifics2.header();
1440 EXPECT_EQ(1, header_s2.window_size());
1443 SessionsSyncManager::TabLinksMap tab_map = manager()->local_tab_map_;
1444 ASSERT_EQ(2U, tab_map.size());
1445 // Tabs are ordered by sessionid in tab_map, so should be able to traverse
1446 // the tree based on order of tabs created
1447 SessionsSyncManager::TabLinksMap::iterator iter = tab_map.begin();
1448 ASSERT_EQ(2, iter->second->tab()->GetEntryCount());
1449 EXPECT_EQ(GURL("http://foo1"), iter->second->tab()->
1450 GetEntryAtIndex(0)->GetVirtualURL());
1451 EXPECT_EQ(GURL("http://foo2"), iter->second->tab()->
1452 GetEntryAtIndex(1)->GetVirtualURL());
1454 ASSERT_EQ(2, iter->second->tab()->GetEntryCount());
1455 EXPECT_EQ(GURL("http://bar1"), iter->second->tab()->
1456 GetEntryAtIndex(0)->GetVirtualURL());
1457 EXPECT_EQ(GURL("http://bar2"), iter->second->tab()->
1458 GetEntryAtIndex(1)->GetVirtualURL());
1461 // Test garbage collection of stale foreign sessions.
1462 TEST_F(SessionsSyncManagerTest, DoGarbageCollection) {
1463 // Fill two instances of session specifics with a foreign session's data.
1464 std::string tag1 = "tag1";
1465 SessionID::id_type n1[] = {5, 10, 13, 17};
1466 std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1));
1467 std::vector<sync_pb::SessionSpecifics> tabs1;
1468 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
1469 tag1, tab_list1, &tabs1));
1470 std::string tag2 = "tag2";
1471 SessionID::id_type n2[] = {8, 15, 18, 20};
1472 std::vector<SessionID::id_type> tab_list2(n2, n2 + arraysize(n2));
1473 std::vector<sync_pb::SessionSpecifics> tabs2;
1474 sync_pb::SessionSpecifics meta2(helper()->BuildForeignSession(
1475 tag2, tab_list2, &tabs2));
1476 // Set the modification time for tag1 to be 21 days ago, tag2 to 5 days ago.
1477 base::Time tag1_time = base::Time::Now() - base::TimeDelta::FromDays(21);
1478 base::Time tag2_time = base::Time::Now() - base::TimeDelta::FromDays(5);
1480 syncer::SyncDataList foreign_data;
1481 sync_pb::EntitySpecifics entity1, entity2;
1482 entity1.mutable_session()->CopyFrom(meta);
1483 entity2.mutable_session()->CopyFrom(meta2);
1484 foreign_data.push_back(SyncData::CreateRemoteData(1, entity1, tag1_time));
1485 foreign_data.push_back(SyncData::CreateRemoteData(1, entity2, tag2_time));
1486 AddTabsToSyncDataList(tabs1, &foreign_data);
1487 AddTabsToSyncDataList(tabs2, &foreign_data);
1489 syncer::SyncChangeList output;
1490 InitWithSyncDataTakeOutput(foreign_data, &output);
1491 ASSERT_EQ(2U, output.size());
1494 // Check that the foreign session was associated and retrieve the data.
1495 std::vector<const SyncedSession*> foreign_sessions;
1496 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1497 ASSERT_EQ(2U, foreign_sessions.size());
1498 foreign_sessions.clear();
1500 // Now garbage collect and verify the non-stale session is still there.
1501 manager()->DoGarbageCollection();
1502 ASSERT_EQ(5U, output.size());
1503 EXPECT_EQ(SyncChange::ACTION_DELETE, output[0].change_type());
1504 const SyncData data(output[0].sync_data());
1505 EXPECT_EQ(tag1, data.GetTag());
1506 for (int i = 1; i < 5; i++) {
1507 EXPECT_EQ(SyncChange::ACTION_DELETE, output[i].change_type());
1508 const SyncData data(output[i].sync_data());
1509 EXPECT_EQ(TabNodePool2::TabIdToTag(tag1, i), data.GetTag());
1512 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1513 ASSERT_EQ(1U, foreign_sessions.size());
1514 std::vector<std::vector<SessionID::id_type> > session_reference;
1515 session_reference.push_back(tab_list2);
1516 helper()->VerifySyncedSession(tag2, session_reference,
1517 *(foreign_sessions[0]));
1520 // Test that an update to a previously considered "stale" session,
1521 // prior to garbage collection, will save the session from deletion.
1522 TEST_F(SessionsSyncManagerTest, GarbageCollectionHonoursUpdate) {
1523 std::string tag1 = "tag1";
1524 SessionID::id_type n1[] = {5, 10, 13, 17};
1525 std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1));
1526 std::vector<sync_pb::SessionSpecifics> tabs1;
1527 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
1528 tag1, tab_list1, &tabs1));
1529 syncer::SyncDataList foreign_data;
1530 sync_pb::EntitySpecifics entity1;
1531 base::Time tag1_time = base::Time::Now() - base::TimeDelta::FromDays(21);
1532 entity1.mutable_session()->CopyFrom(meta);
1533 foreign_data.push_back(SyncData::CreateRemoteData(1, entity1, tag1_time));
1534 AddTabsToSyncDataList(tabs1, &foreign_data);
1535 syncer::SyncChangeList output;
1536 InitWithSyncDataTakeOutput(foreign_data, &output);
1537 ASSERT_EQ(2U, output.size());
1539 // Update to a non-stale time.
1540 sync_pb::EntitySpecifics update_entity;
1541 update_entity.mutable_session()->CopyFrom(tabs1[0]);
1542 syncer::SyncChangeList changes;
1543 changes.push_back(syncer::SyncChange(
1545 SyncChange::ACTION_UPDATE,
1546 syncer::SyncData::CreateRemoteData(1, update_entity,
1547 base::Time::Now())));
1548 manager()->ProcessSyncChanges(FROM_HERE, changes);
1550 // Check that the foreign session was associated and retrieve the data.
1551 std::vector<const SyncedSession*> foreign_sessions;
1552 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1553 ASSERT_EQ(1U, foreign_sessions.size());
1554 foreign_sessions.clear();
1556 // Verify the now non-stale session does not get deleted.
1557 manager()->DoGarbageCollection();
1558 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1559 ASSERT_EQ(1U, foreign_sessions.size());
1560 std::vector<std::vector<SessionID::id_type> > session_reference;
1561 session_reference.push_back(tab_list1);
1562 helper()->VerifySyncedSession(
1563 tag1, session_reference, *(foreign_sessions[0]));
1566 // Test that swapping WebContents for a tab is properly observed and handled
1567 // by the SessionsSyncManager.
1568 TEST_F(SessionsSyncManagerTest, CheckPrerenderedWebContentsSwap) {
1569 AddTab(browser(), GURL("http://foo1"));
1570 NavigateAndCommitActiveTab(GURL("http://foo2"));
1572 syncer::SyncChangeList out;
1573 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out);
1574 ASSERT_EQ(4U, out.size()); // Header, tab ADD, tab UPDATE, header UPDATE.
1576 // To simulate WebContents swap during prerendering, create new WebContents
1577 // and swap with old WebContents.
1578 scoped_ptr<content::WebContents> old_web_contents;
1579 old_web_contents.reset(browser()->tab_strip_model()->GetActiveWebContents());
1581 // Create new WebContents, with the required tab helpers.
1582 WebContents* new_web_contents = WebContents::CreateWithSessionStorage(
1583 WebContents::CreateParams(profile()),
1584 old_web_contents->GetController().GetSessionStorageNamespaceMap());
1585 SessionTabHelper::CreateForWebContents(new_web_contents);
1586 TabContentsSyncedTabDelegate::CreateForWebContents(new_web_contents);
1587 new_web_contents->GetController()
1588 .CopyStateFrom(old_web_contents->GetController());
1590 // Swap the WebContents.
1591 int index = browser()->tab_strip_model()->GetIndexOfWebContents(
1592 old_web_contents.get());
1593 browser()->tab_strip_model()->ReplaceWebContentsAt(index, new_web_contents);
1595 ASSERT_EQ(9U, out.size());
1596 EXPECT_EQ(SyncChange::ACTION_ADD, out[4].change_type());
1597 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[5].change_type());
1600 NavigateAndCommitActiveTab(GURL("http://bar2"));
1602 // Delete old WebContents. This should not crash.
1603 old_web_contents.reset();
1605 // Try more navigations and verify output size. This can also reveal
1606 // bugs (leaks) on memcheck bots if the SessionSyncManager
1607 // didn't properly clean up the tab pool or session tracker.
1608 NavigateAndCommitActiveTab(GURL("http://bar3"));
1610 AddTab(browser(), GURL("http://bar4"));
1611 NavigateAndCommitActiveTab(GURL("http://bar5"));
1612 ASSERT_EQ(19U, out.size());
1616 class SessionNotificationObserver : public content::NotificationObserver {
1618 SessionNotificationObserver() : notified_of_update_(false),
1619 notified_of_refresh_(false) {
1620 registrar_.Add(this, chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED,
1621 content::NotificationService::AllSources());
1622 registrar_.Add(this, chrome::NOTIFICATION_SYNC_REFRESH_LOCAL,
1623 content::NotificationService::AllSources());
1625 virtual void Observe(int type,
1626 const content::NotificationSource& source,
1627 const content::NotificationDetails& details) OVERRIDE {
1629 case chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED:
1630 notified_of_update_ = true;
1632 case chrome::NOTIFICATION_SYNC_REFRESH_LOCAL:
1633 notified_of_refresh_ = true;
1640 bool notified_of_update() const { return notified_of_update_; }
1641 bool notified_of_refresh() const { return notified_of_refresh_; }
1643 notified_of_update_ = false;
1644 notified_of_refresh_ = false;
1647 content::NotificationRegistrar registrar_;
1648 bool notified_of_update_;
1649 bool notified_of_refresh_;
1653 // Test that NOTIFICATION_FOREIGN_SESSION_UPDATED is sent when processing
1655 TEST_F(SessionsSyncManagerTest, NotifiedOfUpdates) {
1656 SessionNotificationObserver observer;
1657 ASSERT_FALSE(observer.notified_of_update());
1658 InitWithNoSyncData();
1660 SessionID::id_type n[] = {5};
1661 std::vector<sync_pb::SessionSpecifics> tabs1;
1662 std::vector<SessionID::id_type> tab_list(n, n + arraysize(n));
1663 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
1664 "tag1", tab_list, &tabs1));
1666 syncer::SyncChangeList changes;
1667 changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_ADD));
1668 manager()->ProcessSyncChanges(FROM_HERE, changes);
1669 EXPECT_TRUE(observer.notified_of_update());
1673 AddTabsToChangeList(tabs1, SyncChange::ACTION_ADD, &changes);
1674 manager()->ProcessSyncChanges(FROM_HERE, changes);
1675 EXPECT_TRUE(observer.notified_of_update());
1679 changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_DELETE));
1680 manager()->ProcessSyncChanges(FROM_HERE, changes);
1681 EXPECT_TRUE(observer.notified_of_update());
1684 // Test that NOTIFICATION_FOREIGN_SESSION_UPDATED is sent when handling
1685 // local hide/removal of foreign session.
1686 TEST_F(SessionsSyncManagerTest, NotifiedOfLocalRemovalOfForeignSession) {
1687 InitWithNoSyncData();
1688 const std::string tag("tag1");
1689 SessionID::id_type n[] = {5};
1690 std::vector<sync_pb::SessionSpecifics> tabs1;
1691 std::vector<SessionID::id_type> tab_list(n, n + arraysize(n));
1692 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
1693 tag, tab_list, &tabs1));
1695 syncer::SyncChangeList changes;
1696 changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_ADD));
1697 manager()->ProcessSyncChanges(FROM_HERE, changes);
1699 SessionNotificationObserver observer;
1700 ASSERT_FALSE(observer.notified_of_update());
1701 manager()->DeleteForeignSession(tag);
1702 ASSERT_TRUE(observer.notified_of_update());
1705 #if defined(OS_ANDROID) || defined(OS_IOS)
1706 // Tests that opening the other devices page triggers a session sync refresh.
1707 // This page only exists on mobile platforms today; desktop has a
1708 // search-enhanced NTP without other devices.
1709 TEST_F(SessionsSyncManagerTest, NotifiedOfRefresh) {
1710 SessionNotificationObserver observer;
1711 ASSERT_FALSE(observer.notified_of_refresh());
1712 InitWithNoSyncData();
1713 AddTab(browser(), GURL("http://foo1"));
1714 EXPECT_FALSE(observer.notified_of_refresh());
1715 NavigateAndCommitActiveTab(GURL("chrome://newtab/#open_tabs"));
1716 EXPECT_TRUE(observer.notified_of_refresh());
1718 #endif // defined(OS_ANDROID) || defined(OS_IOS)
1720 } // namespace browser_sync