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