1 // Copyright 2014 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/sessions/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_tab_helper.h"
10 #include "chrome/browser/sessions/session_types.h"
11 #include "chrome/browser/sync/glue/session_sync_test_helper.h"
12 #include "chrome/browser/sync/glue/synced_tab_delegate.h"
13 #include "chrome/browser/sync/glue/synced_window_delegate.h"
14 #include "chrome/browser/sync/sessions/notification_service_sessions_router.h"
15 #include "chrome/browser/sync/sessions/sessions_util.h"
16 #include "chrome/browser/sync/sessions/synced_window_delegates_getter.h"
17 #include "chrome/browser/ui/sync/tab_contents_synced_tab_delegate.h"
18 #include "chrome/browser/ui/tabs/tab_strip_model.h"
19 #include "chrome/test/base/browser_with_test_window_test.h"
20 #include "components/sessions/serialized_navigation_entry_test_helper.h"
21 #include "components/sessions/session_id.h"
22 #include "components/sync_driver/device_info.h"
23 #include "components/sync_driver/local_device_info_provider_mock.h"
24 #include "content/public/browser/navigation_entry.h"
25 #include "content/public/browser/notification_details.h"
26 #include "content/public/browser/notification_service.h"
27 #include "content/public/browser/notification_source.h"
28 #include "content/public/browser/web_contents.h"
29 #include "sync/api/attachments/attachment_id.h"
30 #include "sync/api/sync_error_factory_mock.h"
31 #include "sync/internal_api/public/attachments/attachment_service_proxy_for_test.h"
32 #include "testing/gmock/include/gmock/gmock.h"
33 #include "testing/gtest/include/gtest/gtest.h"
35 using content::WebContents;
36 using sessions::SerializedNavigationEntry;
37 using sessions::SerializedNavigationEntryTestHelper;
38 using sync_driver::DeviceInfo;
39 using sync_driver::LocalDeviceInfoProvider;
40 using sync_driver::LocalDeviceInfoProviderMock;
41 using syncer::SyncChange;
42 using syncer::SyncData;
44 namespace browser_sync {
48 class SyncedWindowDelegateOverride : public SyncedWindowDelegate {
50 explicit SyncedWindowDelegateOverride(SyncedWindowDelegate* wrapped)
53 ~SyncedWindowDelegateOverride() override {}
55 bool HasWindow() const override { return wrapped_->HasWindow(); }
57 SessionID::id_type GetSessionId() const override {
58 return wrapped_->GetSessionId();
61 int GetTabCount() const override { return wrapped_->GetTabCount(); }
63 int GetActiveIndex() const override { return wrapped_->GetActiveIndex(); }
65 bool IsApp() const override { return wrapped_->IsApp(); }
67 bool IsTypeTabbed() const override { return wrapped_->IsTypeTabbed(); }
69 bool IsTypePopup() const override { return wrapped_->IsTypePopup(); }
71 bool IsTabPinned(const SyncedTabDelegate* tab) const override {
72 return wrapped_->IsTabPinned(tab);
75 SyncedTabDelegate* GetTabAt(int index) const override {
76 if (tab_overrides_.find(index) != tab_overrides_.end())
77 return tab_overrides_.find(index)->second;
79 return wrapped_->GetTabAt(index);
82 void OverrideTabAt(int index,
83 SyncedTabDelegate* delegate,
84 SessionID::id_type tab_id) {
85 tab_overrides_[index] = delegate;
86 tab_id_overrides_[index] = tab_id;
89 SessionID::id_type GetTabIdAt(int index) const override {
90 if (tab_id_overrides_.find(index) != tab_id_overrides_.end())
91 return tab_id_overrides_.find(index)->second;
92 return wrapped_->GetTabIdAt(index);
95 bool IsSessionRestoreInProgress() const override {
96 return wrapped_->IsSessionRestoreInProgress();
100 std::map<int, SyncedTabDelegate*> tab_overrides_;
101 std::map<int, SessionID::id_type> tab_id_overrides_;
102 SyncedWindowDelegate* wrapped_;
105 class TestSyncedWindowDelegatesGetter : public SyncedWindowDelegatesGetter {
107 TestSyncedWindowDelegatesGetter(
108 const std::set<SyncedWindowDelegate*>& delegates)
109 : delegates_(delegates) {}
111 const std::set<SyncedWindowDelegate*> GetSyncedWindowDelegates() override {
115 const std::set<SyncedWindowDelegate*> delegates_;
118 class TestSyncProcessorStub : public syncer::SyncChangeProcessor {
120 explicit TestSyncProcessorStub(syncer::SyncChangeList* output)
122 syncer::SyncError ProcessSyncChanges(
123 const tracked_objects::Location& from_here,
124 const syncer::SyncChangeList& change_list) override {
125 if (error_.IsSet()) {
126 syncer::SyncError error = error_;
127 error_ = syncer::SyncError();
132 output_->insert(output_->end(), change_list.begin(), change_list.end());
134 return syncer::SyncError();
137 syncer::SyncDataList GetAllSyncData(syncer::ModelType type) const override {
138 return sync_data_to_return_;
141 void FailProcessSyncChangesWith(const syncer::SyncError& error) {
145 void SetSyncDataToReturn(const syncer::SyncDataList& data) {
146 sync_data_to_return_ = data;
150 syncer::SyncError error_;
151 syncer::SyncChangeList* output_;
152 syncer::SyncDataList sync_data_to_return_;
155 syncer::SyncChange MakeRemoteChange(
157 const sync_pb::SessionSpecifics& specifics,
158 SyncChange::SyncChangeType type) {
159 sync_pb::EntitySpecifics entity;
160 entity.mutable_session()->CopyFrom(specifics);
161 return syncer::SyncChange(
164 syncer::SyncData::CreateRemoteData(
168 syncer::AttachmentIdList(),
169 syncer::AttachmentServiceProxyForTest::Create()));
172 void AddTabsToChangeList(
173 const std::vector<sync_pb::SessionSpecifics>& batch,
174 SyncChange::SyncChangeType type,
175 syncer::SyncChangeList* change_list) {
176 std::vector<sync_pb::SessionSpecifics>::const_iterator iter;
177 for (iter = batch.begin();
178 iter != batch.end(); ++iter) {
179 sync_pb::EntitySpecifics entity;
180 entity.mutable_session()->CopyFrom(*iter);
181 change_list->push_back(syncer::SyncChange(
184 syncer::SyncData::CreateRemoteData(
188 syncer::AttachmentIdList(),
189 syncer::AttachmentServiceProxyForTest::Create())));
193 void AddTabsToSyncDataList(const std::vector<sync_pb::SessionSpecifics> tabs,
194 syncer::SyncDataList* list) {
195 for (size_t i = 0; i < tabs.size(); i++) {
196 sync_pb::EntitySpecifics entity;
197 entity.mutable_session()->CopyFrom(tabs[i]);
198 list->push_back(SyncData::CreateRemoteData(
202 syncer::AttachmentIdList(),
203 syncer::AttachmentServiceProxyForTest::Create()));
207 class DummyRouter : public LocalSessionEventRouter {
209 ~DummyRouter() override {}
210 void StartRoutingTo(LocalSessionEventHandler* handler) override {}
211 void Stop() override {}
214 scoped_ptr<LocalSessionEventRouter> NewDummyRouter() {
215 return scoped_ptr<LocalSessionEventRouter>(new DummyRouter());
220 class SessionsSyncManagerTest
221 : public BrowserWithTestWindowTest {
223 SessionsSyncManagerTest()
224 : test_processor_(NULL) {
225 local_device_.reset(new LocalDeviceInfoProviderMock(
227 "Wayne Gretzky's Hacking Box",
230 sync_pb::SyncEnums_DeviceType_TYPE_LINUX,
234 void SetUp() override {
235 BrowserWithTestWindowTest::SetUp();
236 browser_sync::NotificationServiceSessionsRouter* router(
237 new browser_sync::NotificationServiceSessionsRouter(
238 profile(), syncer::SyncableService::StartSyncFlare()));
239 manager_.reset(new SessionsSyncManager(profile(), local_device_.get(),
240 scoped_ptr<LocalSessionEventRouter>(router)));
243 void TearDown() override {
244 test_processor_ = NULL;
247 BrowserWithTestWindowTest::TearDown();
250 const DeviceInfo* GetLocalDeviceInfo() {
251 return local_device_->GetLocalDeviceInfo();
254 SessionsSyncManager* manager() { return manager_.get(); }
255 SessionSyncTestHelper* helper() { return &helper_; }
256 LocalDeviceInfoProvider* local_device() { return local_device_.get(); }
258 void InitWithSyncDataTakeOutput(const syncer::SyncDataList& initial_data,
259 syncer::SyncChangeList* output) {
260 test_processor_ = new TestSyncProcessorStub(output);
261 syncer::SyncMergeResult result = manager_->MergeDataAndStartSyncing(
262 syncer::SESSIONS, initial_data,
263 scoped_ptr<syncer::SyncChangeProcessor>(test_processor_),
264 scoped_ptr<syncer::SyncErrorFactory>(
265 new syncer::SyncErrorFactoryMock()));
266 EXPECT_FALSE(result.error().IsSet());
269 void InitWithNoSyncData() {
270 InitWithSyncDataTakeOutput(syncer::SyncDataList(), NULL);
273 void TriggerProcessSyncChangesError() {
274 test_processor_->FailProcessSyncChangesWith(syncer::SyncError(
275 FROM_HERE, syncer::SyncError::DATATYPE_ERROR, "Error",
279 void SetSyncData(const syncer::SyncDataList& data) {
280 test_processor_->SetSyncDataToReturn(data);
283 syncer::SyncChangeList* FilterOutLocalHeaderChanges(
284 syncer::SyncChangeList* list) {
285 syncer::SyncChangeList::iterator it = list->begin();
287 while (it != list->end()) {
288 if (syncer::SyncDataLocal(it->sync_data()).GetTag() ==
289 manager_->current_machine_tag()) {
290 EXPECT_TRUE(SyncChange::ACTION_ADD == it->change_type() ||
291 SyncChange::ACTION_UPDATE == it->change_type());
292 it = list->erase(it);
303 scoped_ptr<SessionsSyncManager> manager_;
304 SessionSyncTestHelper helper_;
305 TestSyncProcessorStub* test_processor_;
306 scoped_ptr<LocalDeviceInfoProviderMock> local_device_;
309 // Test that the SyncSessionManager can properly fill in a SessionHeader.
310 TEST_F(SessionsSyncManagerTest, PopulateSessionHeader) {
311 sync_pb::SessionHeader header_s;
312 header_s.set_client_name("Client 1");
313 header_s.set_device_type(sync_pb::SyncEnums_DeviceType_TYPE_WIN);
315 SyncedSession session;
316 base::Time time = base::Time::Now();
317 SessionsSyncManager::PopulateSessionHeaderFromSpecifics(
318 header_s, time, &session);
319 ASSERT_EQ("Client 1", session.session_name);
320 ASSERT_EQ(SyncedSession::TYPE_WIN, session.device_type);
321 ASSERT_EQ(time, session.modified_time);
324 // Test translation between protobuf types and chrome session types.
325 TEST_F(SessionsSyncManagerTest, PopulateSessionWindow) {
326 sync_pb::SessionWindow window_s;
328 window_s.set_browser_type(sync_pb::SessionWindow_BrowserType_TYPE_TABBED);
329 window_s.set_selected_tab_index(1);
331 std::string tag = "tag";
332 SyncedSession* session = manager()->session_tracker_.GetSession(tag);
333 manager()->session_tracker_.PutWindowInSession(tag, 0);
334 manager()->BuildSyncedSessionFromSpecifics(
335 tag, window_s, base::Time(), session->windows[0]);
336 ASSERT_EQ(1U, session->windows[0]->tabs.size());
337 ASSERT_EQ(1, session->windows[0]->selected_tab_index);
338 ASSERT_EQ(SessionWindow::TYPE_TABBED, session->windows[0]->type);
339 ASSERT_EQ(1U, manager()->session_tracker_.num_synced_sessions());
341 manager()->session_tracker_.num_synced_tabs(std::string("tag")));
346 class SyncedTabDelegateFake : public SyncedTabDelegate {
348 SyncedTabDelegateFake() : current_entry_index_(0),
349 pending_entry_index_(-1),
350 is_supervised_(false),
352 blocked_navigations_(NULL) {}
353 ~SyncedTabDelegateFake() override {}
355 int GetCurrentEntryIndex() const override { return current_entry_index_; }
356 void set_current_entry_index(int i) {
357 current_entry_index_ = i;
360 content::NavigationEntry* GetEntryAtIndex(int i) const override {
361 const int size = entries_.size();
362 return (size < i + 1) ? NULL : entries_[i];
365 void AppendEntry(content::NavigationEntry* entry) {
366 entries_.push_back(entry);
369 int GetEntryCount() const override { return entries_.size(); }
371 int GetPendingEntryIndex() const override { return pending_entry_index_; }
372 void set_pending_entry_index(int i) {
373 pending_entry_index_ = i;
376 SessionID::id_type GetWindowId() const override {
377 return SessionID::id_type();
380 SessionID::id_type GetSessionId() const override {
381 return SessionID::id_type();
384 bool IsBeingDestroyed() const override { return false; }
385 Profile* profile() const override { return NULL; }
386 std::string GetExtensionAppId() const override { return std::string(); }
387 content::NavigationEntry* GetPendingEntry() const override { return NULL; }
388 content::NavigationEntry* GetActiveEntry() const override { return NULL; }
389 bool ProfileIsSupervised() const override { return is_supervised_; }
390 void set_is_supervised(bool is_supervised) { is_supervised_ = is_supervised; }
391 const std::vector<const content::NavigationEntry*>* GetBlockedNavigations()
393 return blocked_navigations_;
395 void set_blocked_navigations(
396 std::vector<const content::NavigationEntry*>* navs) {
397 blocked_navigations_ = navs;
399 bool IsPinned() const override { return false; }
400 bool HasWebContents() const override { return false; }
401 content::WebContents* GetWebContents() const override { return NULL; }
403 // Session sync related methods.
404 int GetSyncId() const override { return sync_id_; }
405 void SetSyncId(int sync_id) override { sync_id_ = sync_id; }
408 current_entry_index_ = 0;
409 pending_entry_index_ = -1;
415 int current_entry_index_;
416 int pending_entry_index_;
419 std::vector<const content::NavigationEntry*>* blocked_navigations_;
420 ScopedVector<content::NavigationEntry> entries_;
425 // Test that we exclude tabs with only chrome:// and file:// schemed navigations
426 // from ShouldSyncTab(..).
427 TEST_F(SessionsSyncManagerTest, ValidTabs) {
428 SyncedTabDelegateFake tab;
430 // A null entry shouldn't crash.
431 tab.AppendEntry(NULL);
432 EXPECT_FALSE(sessions_util::ShouldSyncTab(tab));
435 // A chrome:// entry isn't valid.
436 content::NavigationEntry* entry(content::NavigationEntry::Create());
437 entry->SetVirtualURL(GURL("chrome://preferences/"));
438 tab.AppendEntry(entry);
439 EXPECT_FALSE(sessions_util::ShouldSyncTab(tab));
442 // A file:// entry isn't valid, even in addition to another entry.
443 content::NavigationEntry* entry2(content::NavigationEntry::Create());
444 entry2->SetVirtualURL(GURL("file://bla"));
445 tab.AppendEntry(entry2);
446 EXPECT_FALSE(sessions_util::ShouldSyncTab(tab));
448 // Add a valid scheme entry to tab, making the tab valid.
449 content::NavigationEntry* entry3(content::NavigationEntry::Create());
450 entry3->SetVirtualURL(GURL("http://www.google.com"));
451 tab.AppendEntry(entry3);
452 EXPECT_FALSE(sessions_util::ShouldSyncTab(tab));
455 // Make sure GetCurrentVirtualURL() returns the virtual URL of the pending
456 // entry if the current entry is pending.
457 TEST_F(SessionsSyncManagerTest, GetCurrentVirtualURLPending) {
458 SyncedTabDelegateFake tab;
459 content::NavigationEntry* entry(content::NavigationEntry::Create());
460 entry->SetVirtualURL(GURL("http://www.google.com"));
461 tab.AppendEntry(entry);
462 EXPECT_EQ(entry->GetVirtualURL(), manager()->GetCurrentVirtualURL(tab));
465 // Make sure GetCurrentVirtualURL() returns the virtual URL of the current
466 // entry if the current entry is non-pending.
467 TEST_F(SessionsSyncManagerTest, GetCurrentVirtualURLNonPending) {
468 SyncedTabDelegateFake tab;
469 content::NavigationEntry* entry(content::NavigationEntry::Create());
470 entry->SetVirtualURL(GURL("http://www.google.com"));
471 tab.AppendEntry(entry);
472 EXPECT_EQ(entry->GetVirtualURL(), manager()->GetCurrentVirtualURL(tab));
475 static const base::Time kTime0 = base::Time::FromInternalValue(100);
476 static const base::Time kTime1 = base::Time::FromInternalValue(110);
477 static const base::Time kTime2 = base::Time::FromInternalValue(120);
478 static const base::Time kTime3 = base::Time::FromInternalValue(130);
479 static const base::Time kTime4 = base::Time::FromInternalValue(140);
480 static const base::Time kTime5 = base::Time::FromInternalValue(150);
481 static const base::Time kTime6 = base::Time::FromInternalValue(160);
482 static const base::Time kTime7 = base::Time::FromInternalValue(170);
483 static const base::Time kTime8 = base::Time::FromInternalValue(180);
484 static const base::Time kTime9 = base::Time::FromInternalValue(190);
486 // Populate the mock tab delegate with some data and navigation
487 // entries and make sure that setting a SessionTab from it preserves
488 // those entries (and clobbers any existing data).
489 TEST_F(SessionsSyncManagerTest, SetSessionTabFromDelegate) {
490 // Create a tab with three valid entries.
491 SyncedTabDelegateFake tab;
492 content::NavigationEntry* entry1(content::NavigationEntry::Create());
493 entry1->SetVirtualURL(GURL("http://www.google.com"));
494 entry1->SetTimestamp(kTime1);
495 entry1->SetHttpStatusCode(200);
496 content::NavigationEntry* entry2(content::NavigationEntry::Create());
497 entry2->SetVirtualURL(GURL("http://www.noodle.com"));
498 entry2->SetTimestamp(kTime2);
499 entry2->SetHttpStatusCode(201);
500 content::NavigationEntry* entry3(content::NavigationEntry::Create());
501 entry3->SetVirtualURL(GURL("http://www.doodle.com"));
502 entry3->SetTimestamp(kTime3);
503 entry3->SetHttpStatusCode(202);
505 tab.AppendEntry(entry1);
506 tab.AppendEntry(entry2);
507 tab.AppendEntry(entry3);
508 tab.set_current_entry_index(2);
510 SessionTab session_tab;
511 session_tab.window_id.set_id(1);
512 session_tab.tab_id.set_id(1);
513 session_tab.tab_visual_index = 1;
514 session_tab.current_navigation_index = 1;
515 session_tab.pinned = true;
516 session_tab.extension_app_id = "app id";
517 session_tab.user_agent_override = "override";
518 session_tab.timestamp = kTime5;
519 session_tab.navigations.push_back(
520 SerializedNavigationEntryTestHelper::CreateNavigation(
521 "http://www.example.com", "Example"));
522 session_tab.session_storage_persistent_id = "persistent id";
523 manager()->SetSessionTabFromDelegate(tab, kTime4, &session_tab);
525 EXPECT_EQ(0, session_tab.window_id.id());
526 EXPECT_EQ(0, session_tab.tab_id.id());
527 EXPECT_EQ(0, session_tab.tab_visual_index);
528 EXPECT_EQ(2, session_tab.current_navigation_index);
529 EXPECT_FALSE(session_tab.pinned);
530 EXPECT_TRUE(session_tab.extension_app_id.empty());
531 EXPECT_TRUE(session_tab.user_agent_override.empty());
532 EXPECT_EQ(kTime4, session_tab.timestamp);
533 ASSERT_EQ(3u, session_tab.navigations.size());
534 EXPECT_EQ(entry1->GetVirtualURL(),
535 session_tab.navigations[0].virtual_url());
536 EXPECT_EQ(entry2->GetVirtualURL(),
537 session_tab.navigations[1].virtual_url());
538 EXPECT_EQ(entry3->GetVirtualURL(),
539 session_tab.navigations[2].virtual_url());
540 EXPECT_EQ(kTime1, session_tab.navigations[0].timestamp());
541 EXPECT_EQ(kTime2, session_tab.navigations[1].timestamp());
542 EXPECT_EQ(kTime3, session_tab.navigations[2].timestamp());
543 EXPECT_EQ(200, session_tab.navigations[0].http_status_code());
544 EXPECT_EQ(201, session_tab.navigations[1].http_status_code());
545 EXPECT_EQ(202, session_tab.navigations[2].http_status_code());
546 EXPECT_EQ(SerializedNavigationEntry::STATE_INVALID,
547 session_tab.navigations[0].blocked_state());
548 EXPECT_EQ(SerializedNavigationEntry::STATE_INVALID,
549 session_tab.navigations[1].blocked_state());
550 EXPECT_EQ(SerializedNavigationEntry::STATE_INVALID,
551 session_tab.navigations[2].blocked_state());
552 EXPECT_TRUE(session_tab.session_storage_persistent_id.empty());
555 // Ensure the current_navigation_index gets set properly when the navigation
556 // stack gets trucated to +/- 6 entries.
557 TEST_F(SessionsSyncManagerTest, SetSessionTabFromDelegateNavigationIndex) {
558 SyncedTabDelegateFake tab;
559 content::NavigationEntry* entry0(content::NavigationEntry::Create());
560 entry0->SetVirtualURL(GURL("http://www.google.com"));
561 entry0->SetTimestamp(kTime0);
562 entry0->SetHttpStatusCode(200);
563 content::NavigationEntry* entry1(content::NavigationEntry::Create());
564 entry1->SetVirtualURL(GURL("http://www.zoogle.com"));
565 entry1->SetTimestamp(kTime1);
566 entry1->SetHttpStatusCode(200);
567 content::NavigationEntry* entry2(content::NavigationEntry::Create());
568 entry2->SetVirtualURL(GURL("http://www.noogle.com"));
569 entry2->SetTimestamp(kTime2);
570 entry2->SetHttpStatusCode(200);
571 content::NavigationEntry* entry3(content::NavigationEntry::Create());
572 entry3->SetVirtualURL(GURL("http://www.doogle.com"));
573 entry3->SetTimestamp(kTime3);
574 entry3->SetHttpStatusCode(200);
575 content::NavigationEntry* entry4(content::NavigationEntry::Create());
576 entry4->SetVirtualURL(GURL("http://www.yoogle.com"));
577 entry4->SetTimestamp(kTime4);
578 entry4->SetHttpStatusCode(200);
579 content::NavigationEntry* entry5(content::NavigationEntry::Create());
580 entry5->SetVirtualURL(GURL("http://www.foogle.com"));
581 entry5->SetTimestamp(kTime5);
582 entry5->SetHttpStatusCode(200);
583 content::NavigationEntry* entry6(content::NavigationEntry::Create());
584 entry6->SetVirtualURL(GURL("http://www.boogle.com"));
585 entry6->SetTimestamp(kTime6);
586 entry6->SetHttpStatusCode(200);
587 content::NavigationEntry* entry7(content::NavigationEntry::Create());
588 entry7->SetVirtualURL(GURL("http://www.moogle.com"));
589 entry7->SetTimestamp(kTime7);
590 entry7->SetHttpStatusCode(200);
591 content::NavigationEntry* entry8(content::NavigationEntry::Create());
592 entry8->SetVirtualURL(GURL("http://www.poogle.com"));
593 entry8->SetTimestamp(kTime8);
594 entry8->SetHttpStatusCode(200);
595 content::NavigationEntry* entry9(content::NavigationEntry::Create());
596 entry9->SetVirtualURL(GURL("http://www.roogle.com"));
597 entry9->SetTimestamp(kTime9);
598 entry9->SetHttpStatusCode(200);
600 tab.AppendEntry(entry0);
601 tab.AppendEntry(entry1);
602 tab.AppendEntry(entry2);
603 tab.AppendEntry(entry3);
604 tab.AppendEntry(entry4);
605 tab.AppendEntry(entry5);
606 tab.AppendEntry(entry6);
607 tab.AppendEntry(entry7);
608 tab.AppendEntry(entry8);
609 tab.AppendEntry(entry9);
610 tab.set_current_entry_index(8);
612 SessionTab session_tab;
613 manager()->SetSessionTabFromDelegate(tab, kTime9, &session_tab);
615 EXPECT_EQ(6, session_tab.current_navigation_index);
616 ASSERT_EQ(8u, session_tab.navigations.size());
617 EXPECT_EQ(entry2->GetVirtualURL(),
618 session_tab.navigations[0].virtual_url());
619 EXPECT_EQ(entry3->GetVirtualURL(),
620 session_tab.navigations[1].virtual_url());
621 EXPECT_EQ(entry4->GetVirtualURL(),
622 session_tab.navigations[2].virtual_url());
625 // Ensure the current_navigation_index gets set to the end of the navigation
626 // stack if the current navigation is invalid.
627 TEST_F(SessionsSyncManagerTest, SetSessionTabFromDelegateCurrentInvalid) {
628 SyncedTabDelegateFake tab;
629 content::NavigationEntry* entry0(content::NavigationEntry::Create());
630 entry0->SetVirtualURL(GURL("http://www.google.com"));
631 entry0->SetTimestamp(kTime0);
632 entry0->SetHttpStatusCode(200);
633 content::NavigationEntry* entry1(content::NavigationEntry::Create());
634 entry1->SetVirtualURL(GURL(""));
635 entry1->SetTimestamp(kTime1);
636 entry1->SetHttpStatusCode(200);
637 content::NavigationEntry* entry2(content::NavigationEntry::Create());
638 entry2->SetVirtualURL(GURL("http://www.noogle.com"));
639 entry2->SetTimestamp(kTime2);
640 entry2->SetHttpStatusCode(200);
641 content::NavigationEntry* entry3(content::NavigationEntry::Create());
642 entry3->SetVirtualURL(GURL("http://www.doogle.com"));
643 entry3->SetTimestamp(kTime3);
644 entry3->SetHttpStatusCode(200);
646 tab.AppendEntry(entry0);
647 tab.AppendEntry(entry1);
648 tab.AppendEntry(entry2);
649 tab.AppendEntry(entry3);
650 tab.set_current_entry_index(1);
652 SessionTab session_tab;
653 manager()->SetSessionTabFromDelegate(tab, kTime9, &session_tab);
655 EXPECT_EQ(2, session_tab.current_navigation_index);
656 ASSERT_EQ(3u, session_tab.navigations.size());
659 // Tests that for supervised users blocked navigations are recorded and marked
660 // as such, while regular navigations are marked as allowed.
661 TEST_F(SessionsSyncManagerTest, BlockedNavigations) {
662 SyncedTabDelegateFake tab;
663 content::NavigationEntry* entry1(content::NavigationEntry::Create());
664 entry1->SetVirtualURL(GURL("http://www.google.com"));
665 entry1->SetTimestamp(kTime1);
666 tab.AppendEntry(entry1);
668 content::NavigationEntry* entry2 = content::NavigationEntry::Create();
669 entry2->SetVirtualURL(GURL("http://blocked.com/foo"));
670 entry2->SetTimestamp(kTime2);
671 content::NavigationEntry* entry3 = content::NavigationEntry::Create();
672 entry3->SetVirtualURL(GURL("http://evil.com"));
673 entry3->SetTimestamp(kTime3);
674 ScopedVector<const content::NavigationEntry> blocked_navigations;
675 blocked_navigations.push_back(entry2);
676 blocked_navigations.push_back(entry3);
678 tab.set_is_supervised(true);
679 tab.set_blocked_navigations(&blocked_navigations.get());
681 SessionTab session_tab;
682 session_tab.window_id.set_id(1);
683 session_tab.tab_id.set_id(1);
684 session_tab.tab_visual_index = 1;
685 session_tab.current_navigation_index = 1;
686 session_tab.pinned = true;
687 session_tab.extension_app_id = "app id";
688 session_tab.user_agent_override = "override";
689 session_tab.timestamp = kTime5;
690 session_tab.navigations.push_back(
691 SerializedNavigationEntryTestHelper::CreateNavigation(
692 "http://www.example.com", "Example"));
693 session_tab.session_storage_persistent_id = "persistent id";
694 manager()->SetSessionTabFromDelegate(tab, kTime4, &session_tab);
696 EXPECT_EQ(0, session_tab.window_id.id());
697 EXPECT_EQ(0, session_tab.tab_id.id());
698 EXPECT_EQ(0, session_tab.tab_visual_index);
699 EXPECT_EQ(0, session_tab.current_navigation_index);
700 EXPECT_FALSE(session_tab.pinned);
701 EXPECT_TRUE(session_tab.extension_app_id.empty());
702 EXPECT_TRUE(session_tab.user_agent_override.empty());
703 EXPECT_EQ(kTime4, session_tab.timestamp);
704 ASSERT_EQ(3u, session_tab.navigations.size());
705 EXPECT_EQ(entry1->GetVirtualURL(),
706 session_tab.navigations[0].virtual_url());
707 EXPECT_EQ(entry2->GetVirtualURL(),
708 session_tab.navigations[1].virtual_url());
709 EXPECT_EQ(entry3->GetVirtualURL(),
710 session_tab.navigations[2].virtual_url());
711 EXPECT_EQ(kTime1, session_tab.navigations[0].timestamp());
712 EXPECT_EQ(kTime2, session_tab.navigations[1].timestamp());
713 EXPECT_EQ(kTime3, session_tab.navigations[2].timestamp());
714 EXPECT_EQ(SerializedNavigationEntry::STATE_ALLOWED,
715 session_tab.navigations[0].blocked_state());
716 EXPECT_EQ(SerializedNavigationEntry::STATE_BLOCKED,
717 session_tab.navigations[1].blocked_state());
718 EXPECT_EQ(SerializedNavigationEntry::STATE_BLOCKED,
719 session_tab.navigations[2].blocked_state());
720 EXPECT_TRUE(session_tab.session_storage_persistent_id.empty());
723 // Tests that the local session header objects is created properly in
724 // presence of no other session activity, once and only once.
725 TEST_F(SessionsSyncManagerTest, MergeLocalSessionNoTabs) {
726 syncer::SyncChangeList out;
727 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out);
728 EXPECT_FALSE(manager()->current_machine_tag().empty());
730 EXPECT_EQ(2U, out.size());
731 EXPECT_TRUE(out[0].IsValid());
732 EXPECT_EQ(SyncChange::ACTION_ADD, out[0].change_type());
733 const SyncData data(out[0].sync_data());
734 EXPECT_EQ(manager()->current_machine_tag(),
735 syncer::SyncDataLocal(data).GetTag());
736 const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session());
737 EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag());
738 EXPECT_TRUE(specifics.has_header());
739 const sync_pb::SessionHeader& header_s = specifics.header();
740 EXPECT_TRUE(header_s.has_device_type());
741 EXPECT_EQ(GetLocalDeviceInfo()->client_name(), header_s.client_name());
742 EXPECT_EQ(0, header_s.window_size());
744 EXPECT_TRUE(out[1].IsValid());
745 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[1].change_type());
746 const SyncData data_2(out[1].sync_data());
747 EXPECT_EQ(manager()->current_machine_tag(),
748 syncer::SyncDataLocal(data_2).GetTag());
749 const sync_pb::SessionSpecifics& specifics2(data_2.GetSpecifics().session());
750 EXPECT_EQ(manager()->current_machine_tag(), specifics2.session_tag());
751 EXPECT_TRUE(specifics2.has_header());
752 const sync_pb::SessionHeader& header_s2 = specifics2.header();
753 EXPECT_EQ(0, header_s2.window_size());
755 // Now take that header node and feed it in as input.
756 SyncData d(SyncData::CreateRemoteData(
760 syncer::AttachmentIdList(),
761 syncer::AttachmentServiceProxyForTest::Create()));
762 syncer::SyncDataList in(&d, &d + 1);
764 SessionsSyncManager manager2(profile(), local_device(), NewDummyRouter());
765 syncer::SyncMergeResult result = manager2.MergeDataAndStartSyncing(
766 syncer::SESSIONS, in,
767 scoped_ptr<syncer::SyncChangeProcessor>(
768 new TestSyncProcessorStub(&out)),
769 scoped_ptr<syncer::SyncErrorFactory>(
770 new syncer::SyncErrorFactoryMock()));
771 ASSERT_FALSE(result.error().IsSet());
773 EXPECT_EQ(1U, out.size());
774 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[0].change_type());
775 EXPECT_TRUE(out[0].sync_data().GetSpecifics().session().has_header());
778 // Ensure model association associates the pre-existing tabs.
779 TEST_F(SessionsSyncManagerTest, SwappedOutOnRestore) {
780 AddTab(browser(), GURL("http://foo1"));
781 NavigateAndCommitActiveTab(GURL("http://foo2"));
782 AddTab(browser(), GURL("http://bar1"));
783 NavigateAndCommitActiveTab(GURL("http://bar2"));
784 AddTab(browser(), GURL("http://baz1"));
785 NavigateAndCommitActiveTab(GURL("http://baz2"));
786 const int kRestoredTabId = 1337;
787 const int kNewTabId = 2468;
789 syncer::SyncDataList in;
790 syncer::SyncChangeList out;
791 InitWithSyncDataTakeOutput(in, &out);
793 // Should be one header add, 3 tab add/update pairs, one header update.
794 ASSERT_EQ(8U, out.size());
796 // For input, we set up:
797 // * one "normal" fully loaded tab
798 // * one "frozen" tab with no WebContents and a tab_id change
799 // * one "frozen" tab with no WebContents and no tab_id change
800 SyncData t0(SyncData::CreateRemoteData(
802 out[2].sync_data().GetSpecifics(),
804 syncer::AttachmentIdList(),
805 syncer::AttachmentServiceProxyForTest::Create()));
806 sync_pb::EntitySpecifics entity(out[4].sync_data().GetSpecifics());
807 entity.mutable_session()->mutable_tab()->set_tab_id(kRestoredTabId);
808 SyncData t1(SyncData::CreateRemoteData(
812 syncer::AttachmentIdList(),
813 syncer::AttachmentServiceProxyForTest::Create()));
814 SyncData t2(SyncData::CreateRemoteData(
816 out[6].sync_data().GetSpecifics(),
818 syncer::AttachmentIdList(),
819 syncer::AttachmentServiceProxyForTest::Create()));
824 manager()->StopSyncing(syncer::SESSIONS);
826 const std::set<SyncedWindowDelegate*> windows(
827 SyncedWindowDelegate::GetSyncedWindowDelegates());
828 ASSERT_EQ(1U, windows.size());
829 SyncedTabDelegateFake t1_override, t2_override;
830 t1_override.SetSyncId(1); // No WebContents by default.
831 t2_override.SetSyncId(2); // No WebContents by default.
832 SyncedWindowDelegateOverride window_override(*windows.begin());
833 window_override.OverrideTabAt(1, &t1_override, kNewTabId);
834 window_override.OverrideTabAt(2, &t2_override,
835 t2.GetSpecifics().session().tab().tab_id());
836 std::set<SyncedWindowDelegate*> delegates;
837 delegates.insert(&window_override);
838 scoped_ptr<TestSyncedWindowDelegatesGetter> getter(
839 new TestSyncedWindowDelegatesGetter(delegates));
840 manager()->synced_window_getter_.reset(getter.release());
842 syncer::SyncMergeResult result = manager()->MergeDataAndStartSyncing(
843 syncer::SESSIONS, in,
844 scoped_ptr<syncer::SyncChangeProcessor>(
845 new TestSyncProcessorStub(&out)),
846 scoped_ptr<syncer::SyncErrorFactory>(
847 new syncer::SyncErrorFactoryMock()));
849 // There should be two changes, one for the fully associated tab, and
850 // one for the tab_id update to t1. t2 shouldn't need to be updated.
851 ASSERT_EQ(2U, FilterOutLocalHeaderChanges(&out)->size());
852 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[0].change_type());
853 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[1].change_type());
855 out[1].sync_data().GetSpecifics().session().tab().tab_id());
858 SessionsSyncManager::TabLinksMap tab_map = manager()->local_tab_map_;
859 ASSERT_EQ(3U, tab_map.size());
860 int t2_tab_id = t2.GetSpecifics().session().tab().tab_id();
861 EXPECT_EQ(2, tab_map.find(t2_tab_id)->second->tab_node_id());
862 EXPECT_EQ(1, tab_map.find(kNewTabId)->second->tab_node_id());
863 int t0_tab_id = out[0].sync_data().GetSpecifics().session().tab().tab_id();
864 EXPECT_EQ(0, tab_map.find(t0_tab_id)->second->tab_node_id());
865 // TODO(tim): Once bug 337057 is fixed, we can issue an OnLocalTabModified
866 // from here (using an override similar to above) to return a new tab id
867 // and verify that we don't see any node creations in the SyncChangeProcessor
868 // (similar to how SessionsSyncManagerTest.OnLocalTabModified works.)
871 // Tests MergeDataAndStartSyncing with sync data but no local data.
872 TEST_F(SessionsSyncManagerTest, MergeWithInitialForeignSession) {
873 std::string tag = "tag1";
875 SessionID::id_type n1[] = {5, 10, 13, 17};
876 std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1));
877 std::vector<sync_pb::SessionSpecifics> tabs1;
878 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
879 tag, tab_list1, &tabs1));
880 // Add a second window.
881 SessionID::id_type n2[] = {7, 15, 18, 20};
882 std::vector<SessionID::id_type> tab_list2(n2, n2 + arraysize(n2));
883 helper()->AddWindowSpecifics(1, tab_list2, &meta);
885 // Set up initial data.
886 syncer::SyncDataList initial_data;
887 sync_pb::EntitySpecifics entity;
888 entity.mutable_session()->CopyFrom(meta);
889 initial_data.push_back(SyncData::CreateRemoteData(
893 syncer::AttachmentIdList(),
894 syncer::AttachmentServiceProxyForTest::Create()));
895 AddTabsToSyncDataList(tabs1, &initial_data);
897 for (size_t i = 0; i < tab_list2.size(); ++i) {
898 sync_pb::EntitySpecifics entity;
899 helper()->BuildTabSpecifics(tag, 0, tab_list2[i],
900 entity.mutable_session());
901 initial_data.push_back(SyncData::CreateRemoteData(
905 syncer::AttachmentIdList(),
906 syncer::AttachmentServiceProxyForTest::Create()));
909 syncer::SyncChangeList output;
910 InitWithSyncDataTakeOutput(initial_data, &output);
911 EXPECT_TRUE(FilterOutLocalHeaderChanges(&output)->empty());
913 std::vector<const SyncedSession*> foreign_sessions;
914 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
915 ASSERT_EQ(1U, foreign_sessions.size());
916 std::vector<std::vector<SessionID::id_type> > session_reference;
917 session_reference.push_back(tab_list1);
918 session_reference.push_back(tab_list2);
919 helper()->VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));
922 // This is a combination of MergeWithInitialForeignSession and
923 // MergeLocalSessionExistingTabs. We repeat some checks performed in each of
924 // those tests to ensure the common mixed scenario works.
925 TEST_F(SessionsSyncManagerTest, MergeWithLocalAndForeignTabs) {
927 AddTab(browser(), GURL("http://foo1"));
928 NavigateAndCommitActiveTab(GURL("http://foo2"));
931 std::string tag = "tag1";
932 SessionID::id_type n1[] = {5, 10, 13, 17};
933 std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1));
934 std::vector<sync_pb::SessionSpecifics> tabs1;
935 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
936 tag, tab_list1, &tabs1));
937 syncer::SyncDataList foreign_data;
938 sync_pb::EntitySpecifics entity;
939 entity.mutable_session()->CopyFrom(meta);
940 foreign_data.push_back(SyncData::CreateRemoteData(
944 syncer::AttachmentIdList(),
945 syncer::AttachmentServiceProxyForTest::Create()));
946 AddTabsToSyncDataList(tabs1, &foreign_data);
948 syncer::SyncChangeList output;
949 InitWithSyncDataTakeOutput(foreign_data, &output);
950 ASSERT_EQ(4U, output.size());
952 // Verify the local header.
953 EXPECT_TRUE(output[0].IsValid());
954 EXPECT_EQ(SyncChange::ACTION_ADD, output[0].change_type());
955 const SyncData data(output[0].sync_data());
956 EXPECT_EQ(manager()->current_machine_tag(),
957 syncer::SyncDataLocal(data).GetTag());
958 const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session());
959 EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag());
960 EXPECT_TRUE(specifics.has_header());
961 const sync_pb::SessionHeader& header_s = specifics.header();
962 EXPECT_TRUE(header_s.has_device_type());
963 EXPECT_EQ(GetLocalDeviceInfo()->client_name(), header_s.client_name());
964 EXPECT_EQ(0, header_s.window_size());
966 // Verify the tab node creations and updates with content.
967 for (int i = 1; i < 3; i++) {
968 EXPECT_TRUE(output[i].IsValid());
969 const SyncData data(output[i].sync_data());
970 EXPECT_TRUE(StartsWithASCII(syncer::SyncDataLocal(data).GetTag(),
971 manager()->current_machine_tag(), true));
972 const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session());
973 EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag());
975 EXPECT_EQ(SyncChange::ACTION_ADD, output[1].change_type());
976 EXPECT_EQ(SyncChange::ACTION_UPDATE, output[2].change_type());
977 EXPECT_TRUE(output[2].sync_data().GetSpecifics().session().has_tab());
979 // Verify the header was updated to reflect window state.
980 EXPECT_TRUE(output[3].IsValid());
981 EXPECT_EQ(SyncChange::ACTION_UPDATE, output[3].change_type());
982 const SyncData data_2(output[3].sync_data());
983 EXPECT_EQ(manager()->current_machine_tag(),
984 syncer::SyncDataLocal(data_2).GetTag());
985 const sync_pb::SessionSpecifics& specifics2(data_2.GetSpecifics().session());
986 EXPECT_EQ(manager()->current_machine_tag(), specifics2.session_tag());
987 EXPECT_TRUE(specifics2.has_header());
988 const sync_pb::SessionHeader& header_s2 = specifics2.header();
989 EXPECT_EQ(1, header_s2.window_size());
991 // Verify foreign data.
992 std::vector<const SyncedSession*> foreign_sessions;
993 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
994 std::vector<std::vector<SessionID::id_type> > session_reference;
995 session_reference.push_back(tab_list1);
996 helper()->VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));
997 // There should be one and only one foreign session. If VerifySyncedSession
998 // was successful above this EXPECT call ensures the local session didn't
999 // get mistakenly added to foreign tracking (Similar to ExistingTabs test).
1000 EXPECT_EQ(1U, foreign_sessions.size());
1003 // Tests the common scenario. Merge with both local and foreign session data
1004 // followed by updates flowing from sync and local.
1005 TEST_F(SessionsSyncManagerTest, UpdatesAfterMixedMerge) {
1006 // Add local and foreign data.
1007 AddTab(browser(), GURL("http://foo1"));
1008 NavigateAndCommitActiveTab(GURL("http://foo2"));
1010 std::string tag1 = "tag1";
1011 syncer::SyncDataList foreign_data1;
1012 std::vector<std::vector<SessionID::id_type> > meta1_reference;
1013 sync_pb::SessionSpecifics meta1;
1015 SessionID::id_type n1[] = {5, 10, 13, 17};
1016 std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1));
1017 meta1_reference.push_back(tab_list1);
1018 std::vector<sync_pb::SessionSpecifics> tabs1;
1019 meta1 = helper()->BuildForeignSession(tag1, tab_list1, &tabs1);
1020 sync_pb::EntitySpecifics entity;
1021 entity.mutable_session()->CopyFrom(meta1);
1022 foreign_data1.push_back(SyncData::CreateRemoteData(
1026 syncer::AttachmentIdList(),
1027 syncer::AttachmentServiceProxyForTest::Create()));
1028 AddTabsToSyncDataList(tabs1, &foreign_data1);
1030 syncer::SyncChangeList output1;
1031 InitWithSyncDataTakeOutput(foreign_data1, &output1);
1032 ASSERT_EQ(4U, output1.size());
1034 // Add a second window to the foreign session.
1035 // TODO(tim): Bug 98892. Add local window too when observers are hooked up.
1036 SessionID::id_type tab_nums2[] = {7, 15, 18, 20};
1037 std::vector<SessionID::id_type> tab_list2(
1038 tab_nums2, tab_nums2 + arraysize(tab_nums2));
1039 meta1_reference.push_back(tab_list2);
1040 helper()->AddWindowSpecifics(1, tab_list2, &meta1);
1041 std::vector<sync_pb::SessionSpecifics> tabs2;
1042 tabs2.resize(tab_list2.size());
1043 for (size_t i = 0; i < tab_list2.size(); ++i) {
1044 helper()->BuildTabSpecifics(tag1, 0, tab_list2[i], &tabs2[i]);
1047 syncer::SyncChangeList changes;
1048 changes.push_back(MakeRemoteChange(1, meta1, SyncChange::ACTION_UPDATE));
1049 AddTabsToChangeList(tabs2, SyncChange::ACTION_ADD, &changes);
1050 manager()->ProcessSyncChanges(FROM_HERE, changes);
1053 // Check that the foreign session was associated and retrieve the data.
1054 std::vector<const SyncedSession*> foreign_sessions;
1055 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1056 ASSERT_EQ(1U, foreign_sessions.size());
1057 ASSERT_EQ(4U, foreign_sessions[0]->windows.find(0)->second->tabs.size());
1058 ASSERT_EQ(4U, foreign_sessions[0]->windows.find(1)->second->tabs.size());
1059 helper()->VerifySyncedSession(tag1, meta1_reference, *(foreign_sessions[0]));
1061 // Add a new foreign session.
1062 std::string tag2 = "tag2";
1063 SessionID::id_type n2[] = {107, 115};
1064 std::vector<SessionID::id_type> tag2_tab_list(n2, n2 + arraysize(n2));
1065 std::vector<sync_pb::SessionSpecifics> tag2_tabs;
1066 sync_pb::SessionSpecifics meta2(helper()->BuildForeignSession(
1067 tag2, tag2_tab_list, &tag2_tabs));
1068 changes.push_back(MakeRemoteChange(100, meta2, SyncChange::ACTION_ADD));
1069 AddTabsToChangeList(tag2_tabs, SyncChange::ACTION_ADD, &changes);
1071 manager()->ProcessSyncChanges(FROM_HERE, changes);
1074 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1075 std::vector<std::vector<SessionID::id_type> > meta2_reference;
1076 meta2_reference.push_back(tag2_tab_list);
1077 ASSERT_EQ(2U, foreign_sessions.size());
1078 ASSERT_EQ(2U, foreign_sessions[1]->windows.find(0)->second->tabs.size());
1079 helper()->VerifySyncedSession(tag2, meta2_reference, *(foreign_sessions[1]));
1080 foreign_sessions.clear();
1082 // Remove a tab from a window.
1083 meta1_reference[0].pop_back();
1084 tab_list1.pop_back();
1085 sync_pb::SessionWindow* win = meta1.mutable_header()->mutable_window(0);
1087 for (std::vector<int>::const_iterator iter = tab_list1.begin();
1088 iter != tab_list1.end(); ++iter) {
1089 win->add_tab(*iter);
1091 syncer::SyncChangeList removal;
1092 removal.push_back(MakeRemoteChange(1, meta1, SyncChange::ACTION_UPDATE));
1093 AddTabsToChangeList(tabs1, SyncChange::ACTION_UPDATE, &removal);
1094 manager()->ProcessSyncChanges(FROM_HERE, removal);
1096 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1097 ASSERT_EQ(2U, foreign_sessions.size());
1098 ASSERT_EQ(3U, foreign_sessions[0]->windows.find(0)->second->tabs.size());
1099 helper()->VerifySyncedSession(tag1, meta1_reference, *(foreign_sessions[0]));
1102 // Tests that this SyncSessionManager knows how to delete foreign sessions
1104 TEST_F(SessionsSyncManagerTest, DeleteForeignSession) {
1105 InitWithNoSyncData();
1106 std::string tag = "tag1";
1107 syncer::SyncChangeList changes;
1109 std::vector<const SyncedSession*> foreign_sessions;
1110 ASSERT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions));
1111 manager()->DeleteForeignSessionInternal(tag, &changes);
1112 ASSERT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions));
1113 EXPECT_TRUE(changes.empty());
1115 // Fill an instance of session specifics with a foreign session's data.
1116 std::vector<sync_pb::SessionSpecifics> tabs;
1117 SessionID::id_type n1[] = {5, 10, 13, 17};
1118 std::vector<SessionID::id_type> tab_nums1(n1, n1 + arraysize(n1));
1119 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
1120 tag, tab_nums1, &tabs));
1122 // Update associator with the session's meta node, window, and tabs.
1123 manager()->UpdateTrackerWithForeignSession(meta, base::Time());
1124 for (std::vector<sync_pb::SessionSpecifics>::iterator iter = tabs.begin();
1125 iter != tabs.end(); ++iter) {
1126 manager()->UpdateTrackerWithForeignSession(*iter, base::Time());
1128 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1129 ASSERT_EQ(1U, foreign_sessions.size());
1131 // Now delete the foreign session.
1132 manager()->DeleteForeignSessionInternal(tag, &changes);
1133 EXPECT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions));
1135 EXPECT_EQ(5U, changes.size());
1136 std::set<std::string> expected_tags(&tag, &tag + 1);
1137 for (int i = 0; i < 5; i++)
1138 expected_tags.insert(TabNodePool::TabIdToTag(tag, i));
1140 for (int i = 0; i < 5; i++) {
1141 SCOPED_TRACE(changes[i].ToString());
1142 EXPECT_TRUE(changes[i].IsValid());
1143 EXPECT_EQ(SyncChange::ACTION_DELETE, changes[i].change_type());
1144 EXPECT_TRUE(changes[i].sync_data().IsValid());
1146 expected_tags.erase(
1147 syncer::SyncDataLocal(changes[i].sync_data()).GetTag()));
1151 // Write a foreign session to a node, with the tabs arriving first, and then
1153 TEST_F(SessionsSyncManagerTest, WriteForeignSessionToNodeTabsFirst) {
1154 InitWithNoSyncData();
1156 // Fill an instance of session specifics with a foreign session's data.
1157 std::string tag = "tag1";
1158 SessionID::id_type nums1[] = {5, 10, 13, 17};
1159 std::vector<sync_pb::SessionSpecifics> tabs1;
1160 std::vector<SessionID::id_type> tab_list1 (nums1, nums1 + arraysize(nums1));
1161 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
1162 tag, tab_list1, &tabs1));
1164 syncer::SyncChangeList adds;
1165 // Add tabs for first window, then the meta node.
1166 AddTabsToChangeList(tabs1, SyncChange::ACTION_ADD, &adds);
1167 adds.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_ADD));
1168 manager()->ProcessSyncChanges(FROM_HERE, adds);
1170 // Check that the foreign session was associated and retrieve the data.
1171 std::vector<const SyncedSession*> foreign_sessions;
1172 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1173 ASSERT_EQ(1U, foreign_sessions.size());
1174 std::vector<std::vector<SessionID::id_type> > session_reference;
1175 session_reference.push_back(tab_list1);
1176 helper()->VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));
1179 // Write a foreign session to a node with some tabs that never arrive.
1180 TEST_F(SessionsSyncManagerTest, WriteForeignSessionToNodeMissingTabs) {
1181 InitWithNoSyncData();
1183 // Fill an instance of session specifics with a foreign session's data.
1184 std::string tag = "tag1";
1185 SessionID::id_type nums1[] = {5, 10, 13, 17};
1186 std::vector<sync_pb::SessionSpecifics> tabs1;
1187 std::vector<SessionID::id_type> tab_list1 (nums1, nums1 + arraysize(nums1));
1188 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
1189 tag, tab_list1, &tabs1));
1190 // Add a second window, but this time only create two tab nodes, despite the
1191 // window expecting four tabs.
1192 SessionID::id_type tab_nums2[] = {7, 15, 18, 20};
1193 std::vector<SessionID::id_type> tab_list2(
1194 tab_nums2, tab_nums2 + arraysize(tab_nums2));
1195 helper()->AddWindowSpecifics(1, tab_list2, &meta);
1196 std::vector<sync_pb::SessionSpecifics> tabs2;
1198 for (size_t i = 0; i < 2; ++i) {
1199 helper()->BuildTabSpecifics(tag, 0, tab_list2[i], &tabs2[i]);
1202 syncer::SyncChangeList changes;
1203 changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_ADD));
1204 AddTabsToChangeList(tabs1, SyncChange::ACTION_ADD, &changes);
1205 AddTabsToChangeList(tabs2, SyncChange::ACTION_ADD, &changes);
1206 manager()->ProcessSyncChanges(FROM_HERE, changes);
1209 // Check that the foreign session was associated and retrieve the data.
1210 std::vector<const SyncedSession*> foreign_sessions;
1211 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1212 ASSERT_EQ(1U, foreign_sessions.size());
1213 ASSERT_EQ(2U, foreign_sessions[0]->windows.size());
1214 ASSERT_EQ(4U, foreign_sessions[0]->windows.find(0)->second->tabs.size());
1215 ASSERT_EQ(4U, foreign_sessions[0]->windows.find(1)->second->tabs.size());
1217 // Close the second window.
1218 meta.mutable_header()->clear_window();
1219 helper()->AddWindowSpecifics(0, tab_list1, &meta);
1220 changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_UPDATE));
1221 // Update associator with the session's meta node containing one window.
1222 manager()->ProcessSyncChanges(FROM_HERE, changes);
1224 // Check that the foreign session was associated and retrieve the data.
1225 foreign_sessions.clear();
1226 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1227 ASSERT_EQ(1U, foreign_sessions.size());
1228 ASSERT_EQ(1U, foreign_sessions[0]->windows.size());
1229 std::vector<std::vector<SessionID::id_type> > session_reference;
1230 session_reference.push_back(tab_list1);
1231 helper()->VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));
1234 // Tests that the SessionsSyncManager can handle a remote client deleting
1235 // sync nodes that belong to this local session.
1236 TEST_F(SessionsSyncManagerTest, ProcessRemoteDeleteOfLocalSession) {
1237 syncer::SyncChangeList out;
1238 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out);
1239 ASSERT_EQ(2U, out.size());
1240 sync_pb::EntitySpecifics entity(out[0].sync_data().GetSpecifics());
1241 SyncData d(SyncData::CreateRemoteData(
1245 syncer::AttachmentIdList(),
1246 syncer::AttachmentServiceProxyForTest::Create()));
1247 SetSyncData(syncer::SyncDataList(&d, &d + 1));
1250 syncer::SyncChangeList changes;
1252 MakeRemoteChange(1, entity.session(), SyncChange::ACTION_DELETE));
1253 manager()->ProcessSyncChanges(FROM_HERE, changes);
1254 EXPECT_TRUE(manager()->local_tab_pool_out_of_sync_);
1255 EXPECT_TRUE(out.empty()); // ChangeProcessor shouldn't see any activity.
1257 // This should trigger repair of the TabNodePool.
1258 const GURL foo1("http://foo/1");
1259 AddTab(browser(), foo1);
1260 EXPECT_FALSE(manager()->local_tab_pool_out_of_sync_);
1262 // AddTab triggers two notifications, one for the tab insertion and one for
1263 // committing the NavigationEntry. The first notification results in a tab
1264 // we don't associate although we do update the header node. The second
1265 // notification triggers association of the tab, and the subsequent window
1266 // update. So we should see 4 changes at the SyncChangeProcessor.
1267 ASSERT_EQ(4U, out.size());
1269 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[0].change_type());
1270 ASSERT_TRUE(out[0].sync_data().GetSpecifics().session().has_header());
1271 EXPECT_EQ(SyncChange::ACTION_ADD, out[1].change_type());
1272 int tab_node_id = out[1].sync_data().GetSpecifics().session().tab_node_id();
1273 EXPECT_EQ(TabNodePool::TabIdToTag(
1274 manager()->current_machine_tag(), tab_node_id),
1275 syncer::SyncDataLocal(out[1].sync_data()).GetTag());
1276 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[2].change_type());
1277 ASSERT_TRUE(out[2].sync_data().GetSpecifics().session().has_tab());
1278 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[3].change_type());
1279 ASSERT_TRUE(out[3].sync_data().GetSpecifics().session().has_header());
1281 // Verify the actual content.
1282 const sync_pb::SessionHeader& session_header =
1283 out[3].sync_data().GetSpecifics().session().header();
1284 ASSERT_EQ(1, session_header.window_size());
1285 EXPECT_EQ(1, session_header.window(0).tab_size());
1286 const sync_pb::SessionTab& tab1 =
1287 out[2].sync_data().GetSpecifics().session().tab();
1288 ASSERT_EQ(1, tab1.navigation_size());
1289 EXPECT_EQ(foo1.spec(), tab1.navigation(0).virtual_url());
1291 // Verify TabNodePool integrity.
1292 EXPECT_EQ(1U, manager()->local_tab_pool_.Capacity());
1293 EXPECT_TRUE(manager()->local_tab_pool_.Empty());
1296 SessionsSyncManager::TabLinksMap tab_map = manager()->local_tab_map_;
1297 ASSERT_EQ(1U, tab_map.size());
1298 int tab_id = out[2].sync_data().GetSpecifics().session().tab().tab_id();
1299 EXPECT_EQ(tab_node_id, tab_map.find(tab_id)->second->tab_node_id());
1302 // Test that receiving a session delete from sync removes the session
1304 TEST_F(SessionsSyncManagerTest, ProcessForeignDelete) {
1305 InitWithNoSyncData();
1306 SessionID::id_type n[] = {5};
1307 std::vector<sync_pb::SessionSpecifics> tabs1;
1308 std::vector<SessionID::id_type> tab_list(n, n + arraysize(n));
1309 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
1310 "tag1", tab_list, &tabs1));
1312 syncer::SyncChangeList changes;
1313 changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_ADD));
1314 AddTabsToChangeList(tabs1, SyncChange::ACTION_ADD, &changes);
1315 manager()->ProcessSyncChanges(FROM_HERE, changes);
1317 std::vector<const SyncedSession*> foreign_sessions;
1318 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1319 ASSERT_EQ(1U, foreign_sessions.size());
1322 foreign_sessions.clear();
1323 changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_DELETE));
1324 manager()->ProcessSyncChanges(FROM_HERE, changes);
1326 EXPECT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions));
1329 // TODO(shashishekhar): "Move this to TabNodePool unittests."
1330 TEST_F(SessionsSyncManagerTest, SaveUnassociatedNodesForReassociation) {
1331 syncer::SyncChangeList changes;
1332 InitWithNoSyncData();
1334 std::string local_tag = manager()->current_machine_tag();
1335 // Create a free node and then dissassociate sessions so that it ends up
1337 manager()->local_tab_pool_.GetFreeTabNode(&changes);
1339 // Update the tab_id of the node, so that it is considered a valid
1340 // unassociated node otherwise it will be mistaken for a corrupted node and
1341 // will be deleted before being added to the tab node pool.
1342 sync_pb::EntitySpecifics entity(changes[0].sync_data().GetSpecifics());
1343 entity.mutable_session()->mutable_tab()->set_tab_id(1);
1344 SyncData d(SyncData::CreateRemoteData(
1348 syncer::AttachmentIdList(),
1349 syncer::AttachmentServiceProxyForTest::Create()));
1350 syncer::SyncDataList in(&d, &d + 1);
1352 SessionsSyncManager manager2(profile(), local_device(), NewDummyRouter());
1353 syncer::SyncMergeResult result = manager2.MergeDataAndStartSyncing(
1354 syncer::SESSIONS, in,
1355 scoped_ptr<syncer::SyncChangeProcessor>(
1356 new TestSyncProcessorStub(&changes)),
1357 scoped_ptr<syncer::SyncErrorFactory>(
1358 new syncer::SyncErrorFactoryMock()));
1359 ASSERT_FALSE(result.error().IsSet());
1360 EXPECT_TRUE(FilterOutLocalHeaderChanges(&changes)->empty());
1363 TEST_F(SessionsSyncManagerTest, MergeDeletesCorruptNode) {
1364 syncer::SyncChangeList changes;
1365 InitWithNoSyncData();
1367 std::string local_tag = manager()->current_machine_tag();
1368 int tab_node_id = manager()->local_tab_pool_.GetFreeTabNode(&changes);
1369 SyncData d(SyncData::CreateRemoteData(
1371 changes[0].sync_data().GetSpecifics(),
1373 syncer::AttachmentIdList(),
1374 syncer::AttachmentServiceProxyForTest::Create()));
1375 syncer::SyncDataList in(&d, &d + 1);
1379 InitWithSyncDataTakeOutput(in, &changes);
1380 EXPECT_EQ(1U, FilterOutLocalHeaderChanges(&changes)->size());
1381 EXPECT_EQ(SyncChange::ACTION_DELETE, changes[0].change_type());
1382 EXPECT_EQ(TabNodePool::TabIdToTag(local_tag, tab_node_id),
1383 syncer::SyncDataLocal(changes[0].sync_data()).GetTag());
1386 // Test that things work if a tab is initially ignored.
1387 TEST_F(SessionsSyncManagerTest, AssociateWindowsDontReloadTabs) {
1388 syncer::SyncChangeList out;
1389 // Go to a URL that is ignored by session syncing.
1390 AddTab(browser(), GURL("chrome://preferences/"));
1391 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out);
1392 ASSERT_EQ(2U, out.size()); // Header add and update.
1395 out[1].sync_data().GetSpecifics().session().header().window_size());
1398 // Go to a sync-interesting URL.
1399 NavigateAndCommitActiveTab(GURL("http://foo2"));
1401 EXPECT_EQ(3U, out.size()); // Tab add, update, and header update.
1404 StartsWithASCII(syncer::SyncDataLocal(out[0].sync_data()).GetTag(),
1405 manager()->current_machine_tag(),
1407 EXPECT_EQ(manager()->current_machine_tag(),
1408 out[0].sync_data().GetSpecifics().session().session_tag());
1409 EXPECT_EQ(SyncChange::ACTION_ADD, out[0].change_type());
1412 StartsWithASCII(syncer::SyncDataLocal(out[1].sync_data()).GetTag(),
1413 manager()->current_machine_tag(),
1415 EXPECT_EQ(manager()->current_machine_tag(),
1416 out[1].sync_data().GetSpecifics().session().session_tag());
1417 EXPECT_TRUE(out[1].sync_data().GetSpecifics().session().has_tab());
1418 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[1].change_type());
1420 EXPECT_TRUE(out[2].IsValid());
1421 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[2].change_type());
1422 const SyncData data(out[2].sync_data());
1423 EXPECT_EQ(manager()->current_machine_tag(),
1424 syncer::SyncDataLocal(data).GetTag());
1425 const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session());
1426 EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag());
1427 EXPECT_TRUE(specifics.has_header());
1428 const sync_pb::SessionHeader& header_s = specifics.header();
1429 EXPECT_EQ(1, header_s.window_size());
1430 EXPECT_EQ(1, header_s.window(0).tab_size());
1433 // Tests that the SyncSessionManager responds to local tab events properly.
1434 TEST_F(SessionsSyncManagerTest, OnLocalTabModified) {
1435 syncer::SyncChangeList out;
1436 // Init with no local data, relies on MergeLocalSessionNoTabs.
1437 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out);
1438 ASSERT_FALSE(manager()->current_machine_tag().empty());
1439 ASSERT_EQ(2U, out.size());
1441 // Copy the original header.
1442 sync_pb::EntitySpecifics header(out[0].sync_data().GetSpecifics());
1445 const GURL foo1("http://foo/1");
1446 const GURL foo2("http://foo/2");
1447 const GURL bar1("http://bar/1");
1448 const GURL bar2("http://bar/2");
1449 AddTab(browser(), foo1);
1450 NavigateAndCommitActiveTab(foo2);
1451 AddTab(browser(), bar1);
1452 NavigateAndCommitActiveTab(bar2);
1454 // One add, one update for each AddTab.
1455 // One update for each NavigateAndCommit.
1456 // = 6 total tab updates.
1457 // One header update corresponding to each of those.
1458 // = 6 total header updates.
1459 // 12 total updates.
1460 ASSERT_EQ(12U, out.size());
1462 // Verify the tab node creations and updates to ensure the SyncProcessor
1463 // sees the right operations.
1464 for (int i = 0; i < 12; i++) {
1466 EXPECT_TRUE(out[i].IsValid());
1467 const SyncData data(out[i].sync_data());
1468 EXPECT_TRUE(StartsWithASCII(syncer::SyncDataLocal(data).GetTag(),
1469 manager()->current_machine_tag(), true));
1470 const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session());
1471 EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag());
1473 // First thing on an AddTab is a no-op header update for parented tab.
1474 EXPECT_EQ(header.SerializeAsString(),
1475 data.GetSpecifics().SerializeAsString());
1476 EXPECT_EQ(manager()->current_machine_tag(),
1477 syncer::SyncDataLocal(data).GetTag());
1478 } else if (i % 6 == 1) {
1479 // Next, the TabNodePool should create the tab node.
1480 EXPECT_EQ(SyncChange::ACTION_ADD, out[i].change_type());
1481 EXPECT_EQ(TabNodePool::TabIdToTag(
1482 manager()->current_machine_tag(),
1483 data.GetSpecifics().session().tab_node_id()),
1484 syncer::SyncDataLocal(data).GetTag());
1485 } else if (i % 6 == 2) {
1486 // Then we see the tab update to the URL.
1487 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type());
1488 EXPECT_EQ(TabNodePool::TabIdToTag(
1489 manager()->current_machine_tag(),
1490 data.GetSpecifics().session().tab_node_id()),
1491 syncer::SyncDataLocal(data).GetTag());
1492 ASSERT_TRUE(specifics.has_tab());
1493 } else if (i % 6 == 3) {
1494 // The header needs to be updated to reflect the new window state.
1495 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type());
1496 EXPECT_TRUE(specifics.has_header());
1497 } else if (i % 6 == 4) {
1498 // Now we move on to NavigateAndCommit. Update the tab.
1499 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type());
1500 EXPECT_EQ(TabNodePool::TabIdToTag(
1501 manager()->current_machine_tag(),
1502 data.GetSpecifics().session().tab_node_id()),
1503 syncer::SyncDataLocal(data).GetTag());
1504 ASSERT_TRUE(specifics.has_tab());
1505 } else if (i % 6 == 5) {
1506 // The header needs to be updated to reflect the new window state.
1507 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type());
1508 ASSERT_TRUE(specifics.has_header());
1509 header = data.GetSpecifics();
1513 // Verify the actual content to ensure sync sees the right data.
1514 // When it's all said and done, the header should reflect two tabs.
1515 const sync_pb::SessionHeader& session_header = header.session().header();
1516 ASSERT_EQ(1, session_header.window_size());
1517 EXPECT_EQ(2, session_header.window(0).tab_size());
1519 // ASSERT_TRUEs above allow us to dive in freely here.
1520 // Verify first tab.
1521 const sync_pb::SessionTab& tab1_1 =
1522 out[2].sync_data().GetSpecifics().session().tab();
1523 ASSERT_EQ(1, tab1_1.navigation_size());
1524 EXPECT_EQ(foo1.spec(), tab1_1.navigation(0).virtual_url());
1525 const sync_pb::SessionTab& tab1_2 =
1526 out[4].sync_data().GetSpecifics().session().tab();
1527 ASSERT_EQ(2, tab1_2.navigation_size());
1528 EXPECT_EQ(foo1.spec(), tab1_2.navigation(0).virtual_url());
1529 EXPECT_EQ(foo2.spec(), tab1_2.navigation(1).virtual_url());
1531 // Verify second tab.
1532 const sync_pb::SessionTab& tab2_1 =
1533 out[8].sync_data().GetSpecifics().session().tab();
1534 ASSERT_EQ(1, tab2_1.navigation_size());
1535 EXPECT_EQ(bar1.spec(), tab2_1.navigation(0).virtual_url());
1536 const sync_pb::SessionTab& tab2_2 =
1537 out[10].sync_data().GetSpecifics().session().tab();
1538 ASSERT_EQ(2, tab2_2.navigation_size());
1539 EXPECT_EQ(bar1.spec(), tab2_2.navigation(0).virtual_url());
1540 EXPECT_EQ(bar2.spec(), tab2_2.navigation(1).virtual_url());
1543 // Ensure model association associates the pre-existing tabs.
1544 TEST_F(SessionsSyncManagerTest, MergeLocalSessionExistingTabs) {
1545 AddTab(browser(), GURL("http://foo1"));
1546 NavigateAndCommitActiveTab(GURL("http://foo2")); // Adds back entry.
1547 AddTab(browser(), GURL("http://bar1"));
1548 NavigateAndCommitActiveTab(GURL("http://bar2")); // Adds back entry.
1550 syncer::SyncChangeList out;
1551 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out);
1552 ASSERT_EQ(6U, out.size());
1554 // Check that this machine's data is not included in the foreign windows.
1555 std::vector<const SyncedSession*> foreign_sessions;
1556 ASSERT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions));
1558 // Verify the header.
1559 EXPECT_TRUE(out[0].IsValid());
1560 EXPECT_EQ(SyncChange::ACTION_ADD, out[0].change_type());
1561 const SyncData data(out[0].sync_data());
1562 EXPECT_EQ(manager()->current_machine_tag(),
1563 syncer::SyncDataLocal(data).GetTag());
1564 const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session());
1565 EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag());
1566 EXPECT_TRUE(specifics.has_header());
1567 const sync_pb::SessionHeader& header_s = specifics.header();
1568 EXPECT_TRUE(header_s.has_device_type());
1569 EXPECT_EQ(GetLocalDeviceInfo()->client_name(), header_s.client_name());
1570 EXPECT_EQ(0, header_s.window_size());
1572 // Verify the tab node creations and updates with content.
1573 for (int i = 1; i < 5; i++) {
1574 EXPECT_TRUE(out[i].IsValid());
1575 const SyncData data(out[i].sync_data());
1576 EXPECT_TRUE(StartsWithASCII(syncer::SyncDataLocal(data).GetTag(),
1577 manager()->current_machine_tag(), true));
1578 const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session());
1579 EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag());
1581 EXPECT_EQ(SyncChange::ACTION_ADD, out[i].change_type());
1583 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type());
1584 EXPECT_TRUE(specifics.has_tab());
1588 // Verify the header was updated to reflect new window state.
1589 EXPECT_TRUE(out[5].IsValid());
1590 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[5].change_type());
1591 const SyncData data_2(out[5].sync_data());
1592 EXPECT_EQ(manager()->current_machine_tag(),
1593 syncer::SyncDataLocal(data_2).GetTag());
1594 const sync_pb::SessionSpecifics& specifics2(data_2.GetSpecifics().session());
1595 EXPECT_EQ(manager()->current_machine_tag(), specifics2.session_tag());
1596 EXPECT_TRUE(specifics2.has_header());
1597 const sync_pb::SessionHeader& header_s2 = specifics2.header();
1598 EXPECT_EQ(1, header_s2.window_size());
1601 SessionsSyncManager::TabLinksMap tab_map = manager()->local_tab_map_;
1602 ASSERT_EQ(2U, tab_map.size());
1603 // Tabs are ordered by sessionid in tab_map, so should be able to traverse
1604 // the tree based on order of tabs created
1605 SessionsSyncManager::TabLinksMap::iterator iter = tab_map.begin();
1606 ASSERT_EQ(2, iter->second->tab()->GetEntryCount());
1607 EXPECT_EQ(GURL("http://foo1"), iter->second->tab()->
1608 GetEntryAtIndex(0)->GetVirtualURL());
1609 EXPECT_EQ(GURL("http://foo2"), iter->second->tab()->
1610 GetEntryAtIndex(1)->GetVirtualURL());
1612 ASSERT_EQ(2, iter->second->tab()->GetEntryCount());
1613 EXPECT_EQ(GURL("http://bar1"), iter->second->tab()->
1614 GetEntryAtIndex(0)->GetVirtualURL());
1615 EXPECT_EQ(GURL("http://bar2"), iter->second->tab()->
1616 GetEntryAtIndex(1)->GetVirtualURL());
1619 // Test garbage collection of stale foreign sessions.
1620 TEST_F(SessionsSyncManagerTest, DoGarbageCollection) {
1621 // Fill two instances of session specifics with a foreign session's data.
1622 std::string tag1 = "tag1";
1623 SessionID::id_type n1[] = {5, 10, 13, 17};
1624 std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1));
1625 std::vector<sync_pb::SessionSpecifics> tabs1;
1626 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
1627 tag1, tab_list1, &tabs1));
1628 std::string tag2 = "tag2";
1629 SessionID::id_type n2[] = {8, 15, 18, 20};
1630 std::vector<SessionID::id_type> tab_list2(n2, n2 + arraysize(n2));
1631 std::vector<sync_pb::SessionSpecifics> tabs2;
1632 sync_pb::SessionSpecifics meta2(helper()->BuildForeignSession(
1633 tag2, tab_list2, &tabs2));
1634 // Set the modification time for tag1 to be 21 days ago, tag2 to 5 days ago.
1635 base::Time tag1_time = base::Time::Now() - base::TimeDelta::FromDays(21);
1636 base::Time tag2_time = base::Time::Now() - base::TimeDelta::FromDays(5);
1638 syncer::SyncDataList foreign_data;
1639 sync_pb::EntitySpecifics entity1, entity2;
1640 entity1.mutable_session()->CopyFrom(meta);
1641 entity2.mutable_session()->CopyFrom(meta2);
1642 foreign_data.push_back(SyncData::CreateRemoteData(
1646 syncer::AttachmentIdList(),
1647 syncer::AttachmentServiceProxyForTest::Create()));
1648 foreign_data.push_back(SyncData::CreateRemoteData(
1652 syncer::AttachmentIdList(),
1653 syncer::AttachmentServiceProxyForTest::Create()));
1654 AddTabsToSyncDataList(tabs1, &foreign_data);
1655 AddTabsToSyncDataList(tabs2, &foreign_data);
1657 syncer::SyncChangeList output;
1658 InitWithSyncDataTakeOutput(foreign_data, &output);
1659 ASSERT_EQ(2U, output.size());
1662 // Check that the foreign session was associated and retrieve the data.
1663 std::vector<const SyncedSession*> foreign_sessions;
1664 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1665 ASSERT_EQ(2U, foreign_sessions.size());
1666 foreign_sessions.clear();
1668 // Now garbage collect and verify the non-stale session is still there.
1669 manager()->DoGarbageCollection();
1670 ASSERT_EQ(5U, output.size());
1671 EXPECT_EQ(SyncChange::ACTION_DELETE, output[0].change_type());
1672 const SyncData data(output[0].sync_data());
1673 EXPECT_EQ(tag1, syncer::SyncDataLocal(data).GetTag());
1674 for (int i = 1; i < 5; i++) {
1675 EXPECT_EQ(SyncChange::ACTION_DELETE, output[i].change_type());
1676 const SyncData data(output[i].sync_data());
1677 EXPECT_EQ(TabNodePool::TabIdToTag(tag1, i),
1678 syncer::SyncDataLocal(data).GetTag());
1681 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1682 ASSERT_EQ(1U, foreign_sessions.size());
1683 std::vector<std::vector<SessionID::id_type> > session_reference;
1684 session_reference.push_back(tab_list2);
1685 helper()->VerifySyncedSession(tag2, session_reference,
1686 *(foreign_sessions[0]));
1689 // Test that an update to a previously considered "stale" session,
1690 // prior to garbage collection, will save the session from deletion.
1691 TEST_F(SessionsSyncManagerTest, GarbageCollectionHonoursUpdate) {
1692 std::string tag1 = "tag1";
1693 SessionID::id_type n1[] = {5, 10, 13, 17};
1694 std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1));
1695 std::vector<sync_pb::SessionSpecifics> tabs1;
1696 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
1697 tag1, tab_list1, &tabs1));
1698 syncer::SyncDataList foreign_data;
1699 sync_pb::EntitySpecifics entity1;
1700 base::Time tag1_time = base::Time::Now() - base::TimeDelta::FromDays(21);
1701 entity1.mutable_session()->CopyFrom(meta);
1702 foreign_data.push_back(SyncData::CreateRemoteData(
1706 syncer::AttachmentIdList(),
1707 syncer::AttachmentServiceProxyForTest::Create()));
1708 AddTabsToSyncDataList(tabs1, &foreign_data);
1709 syncer::SyncChangeList output;
1710 InitWithSyncDataTakeOutput(foreign_data, &output);
1711 ASSERT_EQ(2U, output.size());
1713 // Update to a non-stale time.
1714 sync_pb::EntitySpecifics update_entity;
1715 update_entity.mutable_session()->CopyFrom(tabs1[0]);
1716 syncer::SyncChangeList changes;
1718 syncer::SyncChange(FROM_HERE,
1719 SyncChange::ACTION_UPDATE,
1720 syncer::SyncData::CreateRemoteData(
1724 syncer::AttachmentIdList(),
1725 syncer::AttachmentServiceProxyForTest::Create())));
1726 manager()->ProcessSyncChanges(FROM_HERE, changes);
1728 // Check that the foreign session was associated and retrieve the data.
1729 std::vector<const SyncedSession*> foreign_sessions;
1730 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1731 ASSERT_EQ(1U, foreign_sessions.size());
1732 foreign_sessions.clear();
1734 // Verify the now non-stale session does not get deleted.
1735 manager()->DoGarbageCollection();
1736 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1737 ASSERT_EQ(1U, foreign_sessions.size());
1738 std::vector<std::vector<SessionID::id_type> > session_reference;
1739 session_reference.push_back(tab_list1);
1740 helper()->VerifySyncedSession(
1741 tag1, session_reference, *(foreign_sessions[0]));
1744 // Test that swapping WebContents for a tab is properly observed and handled
1745 // by the SessionsSyncManager.
1746 TEST_F(SessionsSyncManagerTest, CheckPrerenderedWebContentsSwap) {
1747 AddTab(browser(), GURL("http://foo1"));
1748 NavigateAndCommitActiveTab(GURL("http://foo2"));
1750 syncer::SyncChangeList out;
1751 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out);
1752 ASSERT_EQ(4U, out.size()); // Header, tab ADD, tab UPDATE, header UPDATE.
1754 // To simulate WebContents swap during prerendering, create new WebContents
1755 // and swap with old WebContents.
1756 scoped_ptr<content::WebContents> old_web_contents;
1757 old_web_contents.reset(browser()->tab_strip_model()->GetActiveWebContents());
1759 // Create new WebContents, with the required tab helpers.
1760 WebContents* new_web_contents = WebContents::CreateWithSessionStorage(
1761 WebContents::CreateParams(profile()),
1762 old_web_contents->GetController().GetSessionStorageNamespaceMap());
1763 SessionTabHelper::CreateForWebContents(new_web_contents);
1764 TabContentsSyncedTabDelegate::CreateForWebContents(new_web_contents);
1765 new_web_contents->GetController()
1766 .CopyStateFrom(old_web_contents->GetController());
1768 // Swap the WebContents.
1769 int index = browser()->tab_strip_model()->GetIndexOfWebContents(
1770 old_web_contents.get());
1771 browser()->tab_strip_model()->ReplaceWebContentsAt(index, new_web_contents);
1773 ASSERT_EQ(9U, out.size());
1774 EXPECT_EQ(SyncChange::ACTION_ADD, out[4].change_type());
1775 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[5].change_type());
1778 NavigateAndCommitActiveTab(GURL("http://bar2"));
1780 // Delete old WebContents. This should not crash.
1781 old_web_contents.reset();
1783 // Try more navigations and verify output size. This can also reveal
1784 // bugs (leaks) on memcheck bots if the SessionSyncManager
1785 // didn't properly clean up the tab pool or session tracker.
1786 NavigateAndCommitActiveTab(GURL("http://bar3"));
1788 AddTab(browser(), GURL("http://bar4"));
1789 NavigateAndCommitActiveTab(GURL("http://bar5"));
1790 ASSERT_EQ(19U, out.size());
1794 class SessionNotificationObserver : public content::NotificationObserver {
1796 SessionNotificationObserver() : notified_of_update_(false),
1797 notified_of_refresh_(false) {
1798 registrar_.Add(this, chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED,
1799 content::NotificationService::AllSources());
1800 registrar_.Add(this, chrome::NOTIFICATION_SYNC_REFRESH_LOCAL,
1801 content::NotificationService::AllSources());
1803 void Observe(int type,
1804 const content::NotificationSource& source,
1805 const content::NotificationDetails& details) override {
1807 case chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED:
1808 notified_of_update_ = true;
1810 case chrome::NOTIFICATION_SYNC_REFRESH_LOCAL:
1811 notified_of_refresh_ = true;
1818 bool notified_of_update() const { return notified_of_update_; }
1819 bool notified_of_refresh() const { return notified_of_refresh_; }
1821 notified_of_update_ = false;
1822 notified_of_refresh_ = false;
1825 content::NotificationRegistrar registrar_;
1826 bool notified_of_update_;
1827 bool notified_of_refresh_;
1831 // Test that NOTIFICATION_FOREIGN_SESSION_UPDATED is sent when processing
1833 TEST_F(SessionsSyncManagerTest, NotifiedOfUpdates) {
1834 SessionNotificationObserver observer;
1835 ASSERT_FALSE(observer.notified_of_update());
1836 InitWithNoSyncData();
1838 SessionID::id_type n[] = {5};
1839 std::vector<sync_pb::SessionSpecifics> tabs1;
1840 std::vector<SessionID::id_type> tab_list(n, n + arraysize(n));
1841 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
1842 "tag1", tab_list, &tabs1));
1844 syncer::SyncChangeList changes;
1845 changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_ADD));
1846 manager()->ProcessSyncChanges(FROM_HERE, changes);
1847 EXPECT_TRUE(observer.notified_of_update());
1851 AddTabsToChangeList(tabs1, SyncChange::ACTION_ADD, &changes);
1852 manager()->ProcessSyncChanges(FROM_HERE, changes);
1853 EXPECT_TRUE(observer.notified_of_update());
1857 changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_DELETE));
1858 manager()->ProcessSyncChanges(FROM_HERE, changes);
1859 EXPECT_TRUE(observer.notified_of_update());
1862 // Test that NOTIFICATION_FOREIGN_SESSION_UPDATED is sent when handling
1863 // local hide/removal of foreign session.
1864 TEST_F(SessionsSyncManagerTest, NotifiedOfLocalRemovalOfForeignSession) {
1865 InitWithNoSyncData();
1866 const std::string tag("tag1");
1867 SessionID::id_type n[] = {5};
1868 std::vector<sync_pb::SessionSpecifics> tabs1;
1869 std::vector<SessionID::id_type> tab_list(n, n + arraysize(n));
1870 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
1871 tag, tab_list, &tabs1));
1873 syncer::SyncChangeList changes;
1874 changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_ADD));
1875 manager()->ProcessSyncChanges(FROM_HERE, changes);
1877 SessionNotificationObserver observer;
1878 ASSERT_FALSE(observer.notified_of_update());
1879 manager()->DeleteForeignSession(tag);
1880 ASSERT_TRUE(observer.notified_of_update());
1883 #if defined(OS_ANDROID) || defined(OS_IOS)
1884 // Tests that opening the other devices page triggers a session sync refresh.
1885 // This page only exists on mobile platforms today; desktop has a
1886 // search-enhanced NTP without other devices.
1887 TEST_F(SessionsSyncManagerTest, NotifiedOfRefresh) {
1888 SessionNotificationObserver observer;
1889 ASSERT_FALSE(observer.notified_of_refresh());
1890 InitWithNoSyncData();
1891 AddTab(browser(), GURL("http://foo1"));
1892 EXPECT_FALSE(observer.notified_of_refresh());
1893 NavigateAndCommitActiveTab(GURL("chrome://newtab/#open_tabs"));
1894 EXPECT_TRUE(observer.notified_of_refresh());
1896 #endif // defined(OS_ANDROID) || defined(OS_IOS)
1898 // Tests receipt of duplicate tab IDs in the same window. This should never
1899 // happen, but we want to make sure the client won't do anything bad if it does
1900 // receive such garbage input data.
1901 TEST_F(SessionsSyncManagerTest, ReceiveDuplicateTabInSameWindow) {
1902 std::string tag = "tag1";
1904 // Reuse tab ID 10 in an attempt to trigger bad behavior.
1905 SessionID::id_type n1[] = {5, 10, 10, 17};
1906 std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1));
1907 std::vector<sync_pb::SessionSpecifics> tabs1;
1908 sync_pb::SessionSpecifics meta(
1909 helper()->BuildForeignSession(tag, tab_list1, &tabs1));
1911 // Set up initial data.
1912 syncer::SyncDataList initial_data;
1913 sync_pb::EntitySpecifics entity;
1914 entity.mutable_session()->CopyFrom(meta);
1915 initial_data.push_back(SyncData::CreateRemoteData(
1919 syncer::AttachmentIdList(),
1920 syncer::AttachmentServiceProxyForTest::Create()));
1921 AddTabsToSyncDataList(tabs1, &initial_data);
1923 syncer::SyncChangeList output;
1924 InitWithSyncDataTakeOutput(initial_data, &output);
1927 // Tests receipt of duplicate tab IDs for the same session. The duplicate tab
1928 // ID is present in two different windows. A client can't be expected to do
1929 // anything reasonable with this input, but we can expect that it doesn't
1931 TEST_F(SessionsSyncManagerTest, ReceiveDuplicateTabInOtherWindow) {
1932 std::string tag = "tag1";
1934 SessionID::id_type n1[] = {5, 10, 17};
1935 std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1));
1936 std::vector<sync_pb::SessionSpecifics> tabs1;
1937 sync_pb::SessionSpecifics meta(
1938 helper()->BuildForeignSession(tag, tab_list1, &tabs1));
1940 // Add a second window. Tab ID 10 is a duplicate.
1941 SessionID::id_type n2[] = {10, 18, 20};
1942 std::vector<SessionID::id_type> tab_list2(n2, n2 + arraysize(n2));
1943 helper()->AddWindowSpecifics(1, tab_list2, &meta);
1945 // Set up initial data.
1946 syncer::SyncDataList initial_data;
1947 sync_pb::EntitySpecifics entity;
1948 entity.mutable_session()->CopyFrom(meta);
1949 initial_data.push_back(SyncData::CreateRemoteData(
1953 syncer::AttachmentIdList(),
1954 syncer::AttachmentServiceProxyForTest::Create()));
1955 AddTabsToSyncDataList(tabs1, &initial_data);
1957 for (size_t i = 0; i < tab_list2.size(); ++i) {
1958 sync_pb::EntitySpecifics entity;
1959 helper()->BuildTabSpecifics(tag, 0, tab_list2[i], entity.mutable_session());
1960 initial_data.push_back(SyncData::CreateRemoteData(
1964 syncer::AttachmentIdList(),
1965 syncer::AttachmentServiceProxyForTest::Create()));
1968 syncer::SyncChangeList output;
1969 InitWithSyncDataTakeOutput(initial_data, &output);
1972 // Tests receipt of multiple unassociated tabs and makes sure that
1973 // the ones with later timestamp win
1974 TEST_F(SessionsSyncManagerTest, ReceiveDuplicateUnassociatedTabs) {
1975 std::string tag = "tag1";
1977 SessionID::id_type n1[] = {5, 10, 17};
1978 std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1));
1979 std::vector<sync_pb::SessionSpecifics> tabs1;
1980 sync_pb::SessionSpecifics meta(
1981 helper()->BuildForeignSession(tag, tab_list1, &tabs1));
1983 // Set up initial data.
1984 syncer::SyncDataList initial_data;
1985 sync_pb::EntitySpecifics entity;
1986 entity.mutable_session()->CopyFrom(meta);
1987 initial_data.push_back(SyncData::CreateRemoteData(
1991 syncer::AttachmentIdList(),
1992 syncer::AttachmentServiceProxyForTest::Create()));
1996 for (size_t i = 0; i < tabs1.size(); i++) {
1997 entity.mutable_session()->CopyFrom(tabs1[i]);
1998 initial_data.push_back(SyncData::CreateRemoteData(
2001 base::Time::FromDoubleT(2000),
2002 syncer::AttachmentIdList(),
2003 syncer::AttachmentServiceProxyForTest::Create()));
2006 // Add two more tabs with duplicating IDs but with different modification
2007 // times, one before and one after the tabs above.
2008 // These two tabs get a different visual indices to distinguish them from the
2009 // tabs above that get visual index 1 by default.
2010 sync_pb::SessionSpecifics duplicating_tab1;
2011 helper()->BuildTabSpecifics(tag, 0, 10, &duplicating_tab1);
2012 duplicating_tab1.mutable_tab()->set_tab_visual_index(2);
2013 entity.mutable_session()->CopyFrom(duplicating_tab1);
2014 initial_data.push_back(SyncData::CreateRemoteData(
2017 base::Time::FromDoubleT(1000),
2018 syncer::AttachmentIdList(),
2019 syncer::AttachmentServiceProxyForTest::Create()));
2021 sync_pb::SessionSpecifics duplicating_tab2;
2022 helper()->BuildTabSpecifics(tag, 0, 17, &duplicating_tab2);
2023 duplicating_tab2.mutable_tab()->set_tab_visual_index(3);
2024 entity.mutable_session()->CopyFrom(duplicating_tab2);
2025 initial_data.push_back(SyncData::CreateRemoteData(
2028 base::Time::FromDoubleT(3000),
2029 syncer::AttachmentIdList(),
2030 syncer::AttachmentServiceProxyForTest::Create()));
2032 syncer::SyncChangeList output;
2033 InitWithSyncDataTakeOutput(initial_data, &output);
2035 std::vector<const SyncedSession*> foreign_sessions;
2036 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
2038 const std::vector<SessionTab*>& window_tabs =
2039 foreign_sessions[0]->windows.find(0)->second->tabs;
2040 ASSERT_EQ(3U, window_tabs.size());
2041 // The first one is from the original set of tabs.
2042 ASSERT_EQ(1, window_tabs[0]->tab_visual_index);
2043 // The one from the original set of tabs wins over duplicating_tab1.
2044 ASSERT_EQ(1, window_tabs[1]->tab_visual_index);
2045 // duplicating_tab2 wins due to the later timestamp.
2046 ASSERT_EQ(3, window_tabs[2]->tab_visual_index);
2049 } // namespace browser_sync