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. 4 5 #include "chrome/browser/sync/sessions2/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_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" 28 29 using content::WebContents; 30 using sessions::SerializedNavigationEntry; 31 using sessions::SerializedNavigationEntryTestHelper; 32 using syncer::SyncChange; 33 using syncer::SyncData; 34 35 namespace browser_sync { 36 37 namespace { 38 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; 50 } 51 52 if (output_) 53 output_->insert(output_->end(), change_list.begin(), change_list.end()); 54 55 return syncer::SyncError(); 56 } 57 58 virtual syncer::SyncDataList GetAllSyncData(syncer::ModelType type) 59 const OVERRIDE { 60 return syncer::SyncDataList(); 61 } 62 63 void FailProcessSyncChangesWith(const syncer::SyncError& error) { 64 error_ = error; 65 } 66 67 private: 68 syncer::SyncError error_; 69 syncer::SyncChangeList* output_; 70 }; 71 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())); 81 } 82 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()))); 96 } 97 } 98 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())); 106 } 107 } 108 109 class DummyRouter : public LocalSessionEventRouter { 110 public: 111 virtual ~DummyRouter() {} 112 virtual void StartRoutingTo(LocalSessionEventHandler* handler) OVERRIDE {} 113 virtual void Stop() OVERRIDE {} 114 }; 115 116 scoped_ptr<LocalSessionEventRouter> NewDummyRouter() { 117 return scoped_ptr<LocalSessionEventRouter>(new DummyRouter()); 118 } 119 120 } // namespace 121 122 class SessionsSyncManagerTest 123 : public BrowserWithTestWindowTest, 124 public SessionsSyncManager::SyncInternalApiDelegate { 125 public: 126 SessionsSyncManagerTest() : test_processor_(NULL) {} 127 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))); 135 } 136 137 virtual void TearDown() OVERRIDE { 138 test_processor_ = NULL; 139 helper()->Reset(); 140 manager_.reset(); 141 BrowserWithTestWindowTest::TearDown(); 142 } 143 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)); 151 } 152 153 virtual std::string GetLocalSyncCacheGUID() const OVERRIDE { 154 return "cache_guid"; 155 } 156 157 SessionsSyncManager* manager() { return manager_.get(); } 158 SessionSyncTestHelper* helper() { return &helper_; } 159 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()); 169 } 170 171 void InitWithNoSyncData() { 172 InitWithSyncDataTakeOutput(syncer::SyncDataList(), NULL); 173 } 174 175 void TriggerProcessSyncChangesError() { 176 test_processor_->FailProcessSyncChangesWith(syncer::SyncError( 177 FROM_HERE, syncer::SyncError::DATATYPE_ERROR, "Error", 178 syncer::SESSIONS)); 179 } 180 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; 193 } 194 } 195 EXPECT_TRUE(found); 196 return list; 197 } 198 199 private: 200 scoped_ptr<SessionsSyncManager> manager_; 201 SessionSyncTestHelper helper_; 202 TestSyncProcessorStub* test_processor_; 203 }; 204 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); 210 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); 218 } 219 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); 226 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"))); 238 } 239 240 namespace { 241 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() {} 249 250 virtual int GetCurrentEntryIndex() const OVERRIDE { 251 return current_entry_index_; 252 } 253 void set_current_entry_index(int i) { 254 current_entry_index_ = i; 255 } 256 257 virtual content::NavigationEntry* GetEntryAtIndex(int i) const OVERRIDE { 258 const int size = entries_.size(); 259 return (size < i + 1) ? NULL : entries_[i]; 260 } 261 262 void AppendEntry(content::NavigationEntry* entry) { 263 entries_.push_back(entry); 264 } 265 266 virtual int GetEntryCount() const OVERRIDE { 267 return entries_.size(); 268 } 269 270 virtual int GetPendingEntryIndex() const OVERRIDE { 271 return pending_entry_index_; 272 } 273 void set_pending_entry_index(int i) { 274 pending_entry_index_ = i; 275 } 276 277 virtual SessionID::id_type GetWindowId() const OVERRIDE { 278 return SessionID::id_type(); 279 } 280 281 virtual SessionID::id_type GetSessionId() const OVERRIDE { 282 return SessionID::id_type(); 283 } 284 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(); 289 } 290 virtual content::NavigationEntry* GetPendingEntry() const OVERRIDE { 291 return NULL; 292 } 293 virtual content::NavigationEntry* GetActiveEntry() const OVERRIDE { 294 return NULL; 295 } 296 virtual bool ProfileIsManaged() const OVERRIDE { 297 return is_managed_; 298 } 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_; 303 } 304 void set_blocked_navigations( 305 std::vector<const content::NavigationEntry*>* navs) { 306 blocked_navigations_ = navs; 307 } 308 virtual bool IsPinned() const OVERRIDE { 309 return false; 310 } 311 virtual bool HasWebContents() const OVERRIDE { 312 return false; 313 } 314 virtual content::WebContents* GetWebContents() const OVERRIDE { 315 return NULL; 316 } 317 318 // Session sync related methods. 319 virtual int GetSyncId() const OVERRIDE { 320 return -1; 321 } 322 virtual void SetSyncId(int sync_id) OVERRIDE {} 323 324 void reset() { 325 current_entry_index_ = 0; 326 pending_entry_index_ = -1; 327 entries_.clear(); 328 } 329 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_; 336 }; 337 338 } // namespace 339 340 // Test that we exclude tabs with only chrome:// and file:// schemed navigations 341 // from ShouldSyncTab(..). 342 TEST_F(SessionsSyncManagerTest, ValidTabs) { 343 SyncedTabDelegateFake tab; 344 345 // A null entry shouldn't crash. 346 tab.AppendEntry(NULL); 347 EXPECT_FALSE(manager()->ShouldSyncTab(tab)); 348 tab.reset(); 349 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)); 355 356 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)); 362 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)); 368 } 369 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)); 378 } 379 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)); 388 } 389 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); 395 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); 414 415 tab.AppendEntry(entry1); 416 tab.AppendEntry(entry2); 417 tab.AppendEntry(entry3); 418 tab.set_current_entry_index(2); 419 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); 434 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()); 463 } 464 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); 473 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); 483 484 tab.set_is_managed(true); 485 tab.set_blocked_navigations(&blocked_navigations.get()); 486 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); 501 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()); 527 } 528 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()); 535 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()); 548 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()); 558 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()); 571 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()); 575 } 576 577 // Tests MergeDataAndStartSyncing with sync data but no local data. 578 TEST_F(SessionsSyncManagerTest, MergeWithInitialForeignSession) { 579 std::string tag = "tag1"; 580 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); 590 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); 597 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())); 604 } 605 606 syncer::SyncChangeList output; 607 InitWithSyncDataTakeOutput(initial_data, &output); 608 EXPECT_TRUE(FilterOutLocalHeaderChanges(&output)->empty()); 609 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])); 617 } 618 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")); 626 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); 639 640 syncer::SyncChangeList output; 641 InitWithSyncDataTakeOutput(foreign_data, &output); 642 ASSERT_EQ(4U, output.size()); 643 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()); 656 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()); 665 } 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()); 669 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()); 680 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()); 691 } 692 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")); 699 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; 704 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); 715 716 syncer::SyncChangeList output1; 717 InitWithSyncDataTakeOutput(foreign_data1, &output1); 718 ASSERT_EQ(4U, output1.size()); 719 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]); 731 } 732 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(); 738 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])); 746 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); 756 757 manager()->ProcessSyncChanges(FROM_HERE, changes); 758 changes.clear(); 759 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(); 767 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); 776 } 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); 781 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])); 786 } 787 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; 794 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()); 800 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)); 807 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()); 813 } 814 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions)); 815 ASSERT_EQ(1U, foreign_sessions.size()); 816 817 // Now delete the foreign session. 818 manager()->DeleteForeignSessionInternal(tag, &changes); 819 EXPECT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions)); 820 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)); 825 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())); 832 } 833 } 834 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(); 839 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)); 847 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); 853 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])); 861 } 862 863 // Write a foreign session to a node with some tabs that never arrive. 864 TEST_F(SessionsSyncManagerTest, WriteForeignSessionToNodeMissingTabs) { 865 InitWithNoSyncData(); 866 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]); 884 } 885 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(); 892 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()); 900 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); 907 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])); 916 } 917 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)); 927 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); 932 933 std::vector<const SyncedSession*> foreign_sessions; 934 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions)); 935 ASSERT_EQ(1U, foreign_sessions.size()); 936 937 changes.clear(); 938 foreign_sessions.clear(); 939 changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_DELETE)); 940 manager()->ProcessSyncChanges(FROM_HERE, changes); 941 942 EXPECT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions)); 943 } 944 945 // TODO(shashishekhar): "Move this to TabNodePool unittests." 946 TEST_F(SessionsSyncManagerTest, SaveUnassociatedNodesForReassociation) { 947 syncer::SyncChangeList changes; 948 InitWithNoSyncData(); 949 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); 954 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()); 972 } 973 974 TEST_F(SessionsSyncManagerTest, MergeDeletesCorruptNode) { 975 syncer::SyncChangeList changes; 976 InitWithNoSyncData(); 977 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()); 991 } 992 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( 1001 0, 1002 out[1].sync_data().GetSpecifics().session().header().window_size()); 1003 out.clear(); 1004 1005 // Go to a sync-interesting URL. 1006 NavigateAndCommitActiveTab(GURL("http://foo2")); 1007 1008 EXPECT_EQ(3U, out.size()); // Tab add, update, and header update. 1009 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()); 1015 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()); 1022 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()); 1033 } 1034 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()); 1042 1043 // Copy the original header. 1044 sync_pb::EntitySpecifics header(out[0].sync_data().GetSpecifics()); 1045 out.clear(); 1046 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); 1055 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()); 1063 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 } else if (i % 6 == 1) { 1079 // Next, the TabNodePool should create the tab node. 1080 EXPECT_EQ(SyncChange::ACTION_ADD, out[i].change_type()); 1081 } else if (i % 6 == 2) { 1082 // Then we see the tab update to the URL. 1083 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type()); 1084 ASSERT_TRUE(specifics.has_tab()); 1085 } else if (i % 6 == 3) { 1086 // The header needs to be updated to reflect the new window state. 1087 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type()); 1088 EXPECT_TRUE(specifics.has_header()); 1089 } else if (i % 6 == 4) { 1090 // Now we move on to NavigateAndCommit. Update the tab. 1091 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type()); 1092 ASSERT_TRUE(specifics.has_tab()); 1093 } else if (i % 6 == 5) { 1094 // The header needs to be updated to reflect the new window state. 1095 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type()); 1096 ASSERT_TRUE(specifics.has_header()); 1097 header = data.GetSpecifics(); 1098 } 1099 } 1100 1101 // Verify the actual content to ensure sync sees the right data. 1102 // When it's all said and done, the header should reflect two tabs. 1103 const sync_pb::SessionHeader& session_header = header.session().header(); 1104 ASSERT_EQ(1, session_header.window_size()); 1105 EXPECT_EQ(2, session_header.window(0).tab_size()); 1106 1107 // ASSERT_TRUEs above allow us to dive in freely here. 1108 // Verify first tab. 1109 const sync_pb::SessionTab& tab1_1 = 1110 out[2].sync_data().GetSpecifics().session().tab(); 1111 ASSERT_EQ(1, tab1_1.navigation_size()); 1112 EXPECT_EQ(foo1.spec(), tab1_1.navigation(0).virtual_url()); 1113 const sync_pb::SessionTab& tab1_2 = 1114 out[4].sync_data().GetSpecifics().session().tab(); 1115 ASSERT_EQ(2, tab1_2.navigation_size()); 1116 EXPECT_EQ(foo1.spec(), tab1_2.navigation(0).virtual_url()); 1117 EXPECT_EQ(foo2.spec(), tab1_2.navigation(1).virtual_url()); 1118 1119 // Verify second tab. 1120 const sync_pb::SessionTab& tab2_1 = 1121 out[8].sync_data().GetSpecifics().session().tab(); 1122 ASSERT_EQ(1, tab2_1.navigation_size()); 1123 EXPECT_EQ(bar1.spec(), tab2_1.navigation(0).virtual_url()); 1124 const sync_pb::SessionTab& tab2_2 = 1125 out[10].sync_data().GetSpecifics().session().tab(); 1126 ASSERT_EQ(2, tab2_2.navigation_size()); 1127 EXPECT_EQ(bar1.spec(), tab2_2.navigation(0).virtual_url()); 1128 EXPECT_EQ(bar2.spec(), tab2_2.navigation(1).virtual_url()); 1129 } 1130 1131 // Ensure model association associates the pre-existing tabs. 1132 TEST_F(SessionsSyncManagerTest, MergeLocalSessionExistingTabs) { 1133 AddTab(browser(), GURL("http://foo1")); 1134 NavigateAndCommitActiveTab(GURL("http://foo2")); // Adds back entry. 1135 AddTab(browser(), GURL("http://bar1")); 1136 NavigateAndCommitActiveTab(GURL("http://bar2")); // Adds back entry. 1137 1138 syncer::SyncChangeList out; 1139 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); 1140 ASSERT_EQ(6U, out.size()); 1141 1142 // Check that this machine's data is not included in the foreign windows. 1143 std::vector<const SyncedSession*> foreign_sessions; 1144 ASSERT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions)); 1145 1146 // Verify the header. 1147 EXPECT_TRUE(out[0].IsValid()); 1148 EXPECT_EQ(SyncChange::ACTION_ADD, out[0].change_type()); 1149 const SyncData data(out[0].sync_data()); 1150 EXPECT_EQ(manager()->current_machine_tag(), data.GetTag()); 1151 const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session()); 1152 EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag()); 1153 EXPECT_TRUE(specifics.has_header()); 1154 const sync_pb::SessionHeader& header_s = specifics.header(); 1155 EXPECT_TRUE(header_s.has_device_type()); 1156 EXPECT_EQ(GetLocalDeviceInfo()->client_name(), header_s.client_name()); 1157 EXPECT_EQ(0, header_s.window_size()); 1158 1159 // Verify the tab node creations and updates with content. 1160 for (int i = 1; i < 5; i++) { 1161 EXPECT_TRUE(out[i].IsValid()); 1162 const SyncData data(out[i].sync_data()); 1163 EXPECT_TRUE(StartsWithASCII(data.GetTag(), 1164 manager()->current_machine_tag(), true)); 1165 const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session()); 1166 EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag()); 1167 if (i % 2 == 1) { 1168 EXPECT_EQ(SyncChange::ACTION_ADD, out[i].change_type()); 1169 } else { 1170 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type()); 1171 EXPECT_TRUE(specifics.has_tab()); 1172 } 1173 } 1174 1175 // Verify the header was updated to reflect new window state. 1176 EXPECT_TRUE(out[5].IsValid()); 1177 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[5].change_type()); 1178 const SyncData data_2(out[5].sync_data()); 1179 EXPECT_EQ(manager()->current_machine_tag(), data_2.GetTag()); 1180 const sync_pb::SessionSpecifics& specifics2(data_2.GetSpecifics().session()); 1181 EXPECT_EQ(manager()->current_machine_tag(), specifics2.session_tag()); 1182 EXPECT_TRUE(specifics2.has_header()); 1183 const sync_pb::SessionHeader& header_s2 = specifics2.header(); 1184 EXPECT_EQ(1, header_s2.window_size()); 1185 1186 // Verify TabLinks. 1187 SessionsSyncManager::TabLinksMap tab_map = manager()->local_tab_map_; 1188 ASSERT_EQ(2U, tab_map.size()); 1189 // Tabs are ordered by sessionid in tab_map, so should be able to traverse 1190 // the tree based on order of tabs created 1191 SessionsSyncManager::TabLinksMap::iterator iter = tab_map.begin(); 1192 ASSERT_EQ(2, iter->second->tab()->GetEntryCount()); 1193 EXPECT_EQ(GURL("http://foo1"), iter->second->tab()-> 1194 GetEntryAtIndex(0)->GetVirtualURL()); 1195 EXPECT_EQ(GURL("http://foo2"), iter->second->tab()-> 1196 GetEntryAtIndex(1)->GetVirtualURL()); 1197 iter++; 1198 ASSERT_EQ(2, iter->second->tab()->GetEntryCount()); 1199 EXPECT_EQ(GURL("http://bar1"), iter->second->tab()-> 1200 GetEntryAtIndex(0)->GetVirtualURL()); 1201 EXPECT_EQ(GURL("http://bar2"), iter->second->tab()-> 1202 GetEntryAtIndex(1)->GetVirtualURL()); 1203 } 1204 1205 // Test garbage collection of stale foreign sessions. 1206 TEST_F(SessionsSyncManagerTest, DoGarbageCollection) { 1207 // Fill two instances of session specifics with a foreign session's data. 1208 std::string tag1 = "tag1"; 1209 SessionID::id_type n1[] = {5, 10, 13, 17}; 1210 std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1)); 1211 std::vector<sync_pb::SessionSpecifics> tabs1; 1212 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession( 1213 tag1, tab_list1, &tabs1)); 1214 std::string tag2 = "tag2"; 1215 SessionID::id_type n2[] = {8, 15, 18, 20}; 1216 std::vector<SessionID::id_type> tab_list2(n2, n2 + arraysize(n2)); 1217 std::vector<sync_pb::SessionSpecifics> tabs2; 1218 sync_pb::SessionSpecifics meta2(helper()->BuildForeignSession( 1219 tag2, tab_list2, &tabs2)); 1220 // Set the modification time for tag1 to be 21 days ago, tag2 to 5 days ago. 1221 base::Time tag1_time = base::Time::Now() - base::TimeDelta::FromDays(21); 1222 base::Time tag2_time = base::Time::Now() - base::TimeDelta::FromDays(5); 1223 1224 syncer::SyncDataList foreign_data; 1225 sync_pb::EntitySpecifics entity1, entity2; 1226 entity1.mutable_session()->CopyFrom(meta); 1227 entity2.mutable_session()->CopyFrom(meta2); 1228 foreign_data.push_back(SyncData::CreateRemoteData(1, entity1, tag1_time)); 1229 foreign_data.push_back(SyncData::CreateRemoteData(1, entity2, tag2_time)); 1230 AddTabsToSyncDataList(tabs1, &foreign_data); 1231 AddTabsToSyncDataList(tabs2, &foreign_data); 1232 1233 syncer::SyncChangeList output; 1234 InitWithSyncDataTakeOutput(foreign_data, &output); 1235 ASSERT_EQ(2U, output.size()); 1236 output.clear(); 1237 1238 // Check that the foreign session was associated and retrieve the data. 1239 std::vector<const SyncedSession*> foreign_sessions; 1240 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions)); 1241 ASSERT_EQ(2U, foreign_sessions.size()); 1242 foreign_sessions.clear(); 1243 1244 // Now garbage collect and verify the non-stale session is still there. 1245 manager()->DoGarbageCollection(); 1246 ASSERT_EQ(5U, output.size()); 1247 EXPECT_EQ(SyncChange::ACTION_DELETE, output[0].change_type()); 1248 const SyncData data(output[0].sync_data()); 1249 EXPECT_EQ(tag1, data.GetTag()); 1250 for (int i = 1; i < 5; i++) { 1251 EXPECT_EQ(SyncChange::ACTION_DELETE, output[i].change_type()); 1252 const SyncData data(output[i].sync_data()); 1253 EXPECT_EQ(TabNodePool2::TabIdToTag(tag1, i), data.GetTag()); 1254 } 1255 1256 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions)); 1257 ASSERT_EQ(1U, foreign_sessions.size()); 1258 std::vector<std::vector<SessionID::id_type> > session_reference; 1259 session_reference.push_back(tab_list2); 1260 helper()->VerifySyncedSession(tag2, session_reference, 1261 *(foreign_sessions[0])); 1262 } 1263 1264 // Test that an update to a previously considered "stale" session, 1265 // prior to garbage collection, will save the session from deletion. 1266 TEST_F(SessionsSyncManagerTest, GarbageCollectionHonoursUpdate) { 1267 std::string tag1 = "tag1"; 1268 SessionID::id_type n1[] = {5, 10, 13, 17}; 1269 std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1)); 1270 std::vector<sync_pb::SessionSpecifics> tabs1; 1271 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession( 1272 tag1, tab_list1, &tabs1)); 1273 syncer::SyncDataList foreign_data; 1274 sync_pb::EntitySpecifics entity1; 1275 base::Time tag1_time = base::Time::Now() - base::TimeDelta::FromDays(21); 1276 entity1.mutable_session()->CopyFrom(meta); 1277 foreign_data.push_back(SyncData::CreateRemoteData(1, entity1, tag1_time)); 1278 AddTabsToSyncDataList(tabs1, &foreign_data); 1279 syncer::SyncChangeList output; 1280 InitWithSyncDataTakeOutput(foreign_data, &output); 1281 ASSERT_EQ(2U, output.size()); 1282 1283 // Update to a non-stale time. 1284 sync_pb::EntitySpecifics update_entity; 1285 update_entity.mutable_session()->CopyFrom(tabs1[0]); 1286 syncer::SyncChangeList changes; 1287 changes.push_back(syncer::SyncChange( 1288 FROM_HERE, 1289 SyncChange::ACTION_UPDATE, 1290 syncer::SyncData::CreateRemoteData(1, update_entity, 1291 base::Time::Now()))); 1292 manager()->ProcessSyncChanges(FROM_HERE, changes); 1293 1294 // Check that the foreign session was associated and retrieve the data. 1295 std::vector<const SyncedSession*> foreign_sessions; 1296 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions)); 1297 ASSERT_EQ(1U, foreign_sessions.size()); 1298 foreign_sessions.clear(); 1299 1300 // Verify the now non-stale session does not get deleted. 1301 manager()->DoGarbageCollection(); 1302 ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions)); 1303 ASSERT_EQ(1U, foreign_sessions.size()); 1304 std::vector<std::vector<SessionID::id_type> > session_reference; 1305 session_reference.push_back(tab_list1); 1306 helper()->VerifySyncedSession( 1307 tag1, session_reference, *(foreign_sessions[0])); 1308 } 1309 1310 // Test that swapping WebContents for a tab is properly observed and handled 1311 // by the SessionsSyncManager. 1312 TEST_F(SessionsSyncManagerTest, CheckPrerenderedWebContentsSwap) { 1313 AddTab(browser(), GURL("http://foo1")); 1314 NavigateAndCommitActiveTab(GURL("http://foo2")); 1315 1316 syncer::SyncChangeList out; 1317 InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); 1318 ASSERT_EQ(4U, out.size()); // Header, tab ADD, tab UPDATE, header UPDATE. 1319 1320 // To simulate WebContents swap during prerendering, create new WebContents 1321 // and swap with old WebContents. 1322 scoped_ptr<content::WebContents> old_web_contents; 1323 old_web_contents.reset(browser()->tab_strip_model()->GetActiveWebContents()); 1324 1325 // Create new WebContents, with the required tab helpers. 1326 WebContents* new_web_contents = WebContents::CreateWithSessionStorage( 1327 WebContents::CreateParams(profile()), 1328 old_web_contents->GetController().GetSessionStorageNamespaceMap()); 1329 SessionTabHelper::CreateForWebContents(new_web_contents); 1330 TabContentsSyncedTabDelegate::CreateForWebContents(new_web_contents); 1331 new_web_contents->GetController() 1332 .CopyStateFrom(old_web_contents->GetController()); 1333 1334 // Swap the WebContents. 1335 int index = browser()->tab_strip_model()->GetIndexOfWebContents( 1336 old_web_contents.get()); 1337 browser()->tab_strip_model()->ReplaceWebContentsAt(index, new_web_contents); 1338 1339 ASSERT_EQ(9U, out.size()); 1340 EXPECT_EQ(SyncChange::ACTION_ADD, out[4].change_type()); 1341 EXPECT_EQ(SyncChange::ACTION_UPDATE, out[5].change_type()); 1342 1343 // Navigate away. 1344 NavigateAndCommitActiveTab(GURL("http://bar2")); 1345 1346 // Delete old WebContents. This should not crash. 1347 old_web_contents.reset(); 1348 1349 // Try more navigations and verify output size. This can also reveal 1350 // bugs (leaks) on memcheck bots if the SessionSyncManager 1351 // didn't properly clean up the tab pool or session tracker. 1352 NavigateAndCommitActiveTab(GURL("http://bar3")); 1353 1354 AddTab(browser(), GURL("http://bar4")); 1355 NavigateAndCommitActiveTab(GURL("http://bar5")); 1356 ASSERT_EQ(19U, out.size()); 1357 } 1358 1359 namespace { 1360 class SessionNotificationObserver : public content::NotificationObserver { 1361 public: 1362 SessionNotificationObserver() : notified_of_update_(false), 1363 notified_of_refresh_(false) { 1364 registrar_.Add(this, chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED, 1365 content::NotificationService::AllSources()); 1366 registrar_.Add(this, chrome::NOTIFICATION_SYNC_REFRESH_LOCAL, 1367 content::NotificationService::AllSources()); 1368 } 1369 virtual void Observe(int type, 1370 const content::NotificationSource& source, 1371 const content::NotificationDetails& details) OVERRIDE { 1372 switch (type) { 1373 case chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED: 1374 notified_of_update_ = true; 1375 break; 1376 case chrome::NOTIFICATION_SYNC_REFRESH_LOCAL: 1377 notified_of_refresh_ = true; 1378 break; 1379 default: 1380 NOTREACHED(); 1381 break; 1382 } 1383 } 1384 bool notified_of_update() const { return notified_of_update_; } 1385 bool notified_of_refresh() const { return notified_of_refresh_; } 1386 void Reset() { 1387 notified_of_update_ = false; 1388 notified_of_refresh_ = false; 1389 } 1390 private: 1391 content::NotificationRegistrar registrar_; 1392 bool notified_of_update_; 1393 bool notified_of_refresh_; 1394 }; 1395 } // namespace 1396 1397 // Test that NOTIFICATION_FOREIGN_SESSION_UPDATED is sent. 1398 TEST_F(SessionsSyncManagerTest, NotifiedOfUpdates) { 1399 SessionNotificationObserver observer; 1400 ASSERT_FALSE(observer.notified_of_update()); 1401 InitWithNoSyncData(); 1402 1403 SessionID::id_type n[] = {5}; 1404 std::vector<sync_pb::SessionSpecifics> tabs1; 1405 std::vector<SessionID::id_type> tab_list(n, n + arraysize(n)); 1406 sync_pb::SessionSpecifics meta(helper()->BuildForeignSession( 1407 "tag1", tab_list, &tabs1)); 1408 1409 syncer::SyncChangeList changes; 1410 changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_ADD)); 1411 manager()->ProcessSyncChanges(FROM_HERE, changes); 1412 EXPECT_TRUE(observer.notified_of_update()); 1413 1414 changes.clear(); 1415 observer.Reset(); 1416 AddTabsToChangeList(tabs1, SyncChange::ACTION_ADD, &changes); 1417 manager()->ProcessSyncChanges(FROM_HERE, changes); 1418 EXPECT_TRUE(observer.notified_of_update()); 1419 1420 changes.clear(); 1421 observer.Reset(); 1422 changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_DELETE)); 1423 manager()->ProcessSyncChanges(FROM_HERE, changes); 1424 EXPECT_TRUE(observer.notified_of_update()); 1425 } 1426 1427 #if defined(OS_ANDROID) || defined(OS_IOS) 1428 // Tests that opening the other devices page triggers a session sync refresh. 1429 // This page only exists on mobile platforms today; desktop has a 1430 // search-enhanced NTP without other devices. 1431 TEST_F(SessionsSyncManagerTest, NotifiedOfRefresh) { 1432 SessionNotificationObserver observer; 1433 ASSERT_FALSE(observer.notified_of_refresh()); 1434 InitWithNoSyncData(); 1435 AddTab(browser(), GURL("http://foo1")); 1436 EXPECT_FALSE(observer.notified_of_refresh()); 1437 NavigateAndCommitActiveTab(GURL("chrome://newtab/#open_tabs")); 1438 EXPECT_TRUE(observer.notified_of_refresh()); 1439 } 1440 #endif // defined(OS_ANDROID) || defined(OS_IOS) 1441 1442 } // namespace browser_sync 1443