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