Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "components/sync_sessions/sessions_sync_manager.h" | 5 #include "components/sync_sessions/sessions_sync_manager.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <utility> | 8 #include <utility> |
| 9 | 9 |
| 10 #include "base/format_macros.h" | 10 #include "base/format_macros.h" |
| (...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 103 } | 103 } |
| 104 } | 104 } |
| 105 | 105 |
| 106 // Ensure that the tab id is not invalid. | 106 // Ensure that the tab id is not invalid. |
| 107 bool ShouldSyncTabId(SessionID::id_type tab_id) { | 107 bool ShouldSyncTabId(SessionID::id_type tab_id) { |
| 108 if (tab_id == kInvalidTabID) | 108 if (tab_id == kInvalidTabID) |
| 109 return false; | 109 return false; |
| 110 return true; | 110 return true; |
| 111 } | 111 } |
| 112 | 112 |
| 113 SyncedSession::DeviceType ProtoDeviceTypeToSyncedSessionDeviceType( | |
| 114 sync_pb::SyncEnums::DeviceType proto_device_type) { | |
| 115 switch (proto_device_type) { | |
| 116 case sync_pb::SyncEnums_DeviceType_TYPE_WIN: | |
| 117 return SyncedSession::TYPE_WIN; | |
| 118 case sync_pb::SyncEnums_DeviceType_TYPE_MAC: | |
| 119 return SyncedSession::TYPE_MACOSX; | |
| 120 case sync_pb::SyncEnums_DeviceType_TYPE_LINUX: | |
| 121 return SyncedSession::TYPE_LINUX; | |
| 122 case sync_pb::SyncEnums_DeviceType_TYPE_CROS: | |
| 123 return SyncedSession::TYPE_CHROMEOS; | |
| 124 case sync_pb::SyncEnums_DeviceType_TYPE_PHONE: | |
| 125 return SyncedSession::TYPE_PHONE; | |
| 126 case sync_pb::SyncEnums_DeviceType_TYPE_TABLET: | |
| 127 return SyncedSession::TYPE_TABLET; | |
| 128 case sync_pb::SyncEnums_DeviceType_TYPE_OTHER: | |
| 129 // Intentionally fall-through | |
|
skym
2017/04/17 20:37:02
Why allow a fall through? Why not have a compile e
Nicolas Zea
2017/04/17 21:24:54
Good call, done.
| |
| 130 default: | |
| 131 return SyncedSession::TYPE_OTHER; | |
| 132 } | |
| 133 } | |
| 134 | |
| 113 } // namespace | 135 } // namespace |
| 114 | 136 |
| 115 // |local_device| is owned by ProfileSyncService, its lifetime exceeds | 137 // |local_device| is owned by ProfileSyncService, its lifetime exceeds |
| 116 // lifetime of SessionSyncManager. | 138 // lifetime of SessionSyncManager. |
| 117 SessionsSyncManager::SessionsSyncManager( | 139 SessionsSyncManager::SessionsSyncManager( |
| 118 sync_sessions::SyncSessionsClient* sessions_client, | 140 sync_sessions::SyncSessionsClient* sessions_client, |
| 119 syncer::SyncPrefs* sync_prefs, | 141 syncer::SyncPrefs* sync_prefs, |
| 120 LocalDeviceInfoProvider* local_device, | 142 LocalDeviceInfoProvider* local_device, |
| 121 LocalSessionEventRouter* router, | 143 LocalSessionEventRouter* router, |
| 122 const base::Closure& sessions_updated_callback, | 144 const base::Closure& sessions_updated_callback, |
| (...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 222 merge_result.set_error( | 244 merge_result.set_error( |
| 223 sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes)); | 245 sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes)); |
| 224 | 246 |
| 225 local_event_router_->StartRoutingTo(this); | 247 local_event_router_->StartRoutingTo(this); |
| 226 return merge_result; | 248 return merge_result; |
| 227 } | 249 } |
| 228 | 250 |
| 229 void SessionsSyncManager::AssociateWindows( | 251 void SessionsSyncManager::AssociateWindows( |
| 230 ReloadTabsOption option, | 252 ReloadTabsOption option, |
| 231 syncer::SyncChangeList* change_output) { | 253 syncer::SyncChangeList* change_output) { |
| 232 const std::string local_tag = current_machine_tag(); | 254 // Note that |current_session| is a pointer owned by |session_tracker_|. |
| 233 sync_pb::SessionSpecifics specifics; | 255 // |session_tracker_| will continue to update |current_session| under |
| 234 specifics.set_session_tag(local_tag); | 256 // the hood, e.g. during ResetSessionTracking(..), so care must be taken |
|
skym
2017/04/17 20:37:02
I don't understand what this means and how you're
Nicolas Zea
2017/04/17 21:24:54
Expanded the comment to clarify. ResetSessionTrack
| |
| 235 sync_pb::SessionHeader* header_s = specifics.mutable_header(); | 257 // accessing it. |
| 236 SyncedSession* current_session = session_tracker_.GetSession(local_tag); | 258 SyncedSession* current_session = |
| 237 current_session->modified_time = base::Time::Now(); | 259 session_tracker_.GetSession(current_machine_tag()); |
| 238 header_s->set_client_name(current_session_name_); | 260 current_session->session_name = current_session_name_; |
| 239 header_s->set_device_type(current_device_type_); | 261 current_session->device_type = |
| 262 ProtoDeviceTypeToSyncedSessionDeviceType(current_device_type_); | |
| 263 current_session->session_tag = current_machine_tag(); | |
| 240 | 264 |
| 241 session_tracker_.ResetSessionTracking(local_tag); | |
| 242 SyncedWindowDelegatesGetter::SyncedWindowDelegateMap windows = | 265 SyncedWindowDelegatesGetter::SyncedWindowDelegateMap windows = |
| 243 synced_window_delegates_getter()->GetSyncedWindowDelegates(); | 266 synced_window_delegates_getter()->GetSyncedWindowDelegates(); |
| 244 | 267 |
| 245 if (option == RELOAD_TABS) { | 268 // On Android, it's possible to not have any tabbed windows if this is a cold |
| 246 UMA_HISTOGRAM_COUNTS("Sync.SessionWindows", windows.size()); | 269 // start triggered for a custom tab. In that case, the previous session must |
| 270 // be restored, otherwise it will be lost. On the other hand, if there is at | |
| 271 // least one tabbed window open, it's safe to overwrite the previous session | |
| 272 // entirely. See crbug.com/639009 for more info. | |
| 273 bool found_tabbed_window = false; | |
| 274 for (auto& window_iter_pair : windows) { | |
| 275 if (window_iter_pair.second->IsTypeTabbed()) | |
| 276 found_tabbed_window = true; | |
| 247 } | 277 } |
| 248 if (windows.size() == 0) { | 278 |
| 249 // Assume that the window hasn't loaded. Attempting to associate now would | 279 if (found_tabbed_window) { |
| 250 // clobber any old windows, so just return. | 280 // Just reset the session tracking. No need to worry about the previous |
| 251 LOG(ERROR) << "No windows present, see crbug.com/639009"; | 281 // session; the current tabbed windows are now the source of truth. |
| 252 return; | 282 session_tracker_.ResetSessionTracking(current_machine_tag()); |
| 283 current_session->modified_time = base::Time::Now(); | |
| 284 } else { | |
| 285 DVLOG(1) << "Found no tabbed windows. Reloading " | |
| 286 << current_session->windows.size() | |
| 287 << " windows from previous session."; | |
| 288 | |
| 289 // A copy of the specifics must be made because |current_session| will be | |
| 290 // updated in place and therefore can't be relied on as the source of truth. | |
| 291 sync_pb::SessionHeader header_specifics; | |
| 292 header_specifics.CopyFrom(current_session->ToSessionHeaderProto()); | |
| 293 session_tracker_.ResetSessionTracking(current_machine_tag()); | |
| 294 PopulateSyncedSessionFromSpecifics(current_machine_tag(), header_specifics, | |
| 295 base::Time::Now(), current_session); | |
| 296 | |
| 297 // The tab entities stored in sync have outdated SessionId values. Go | |
| 298 // through and update them to the new SessionIds. | |
| 299 for (auto& win_iter : current_session->windows) { | |
| 300 for (auto& tab : win_iter.second->wrapped_window.tabs) { | |
| 301 int sync_id = TabNodePool::kInvalidTabNodeID; | |
| 302 if (!session_tracker_.GetTabNodeFromLocalTabId(tab->tab_id.id(), | |
| 303 &sync_id) || | |
| 304 sync_id == TabNodePool::kInvalidTabNodeID) { | |
| 305 continue; | |
| 306 } | |
| 307 DVLOG(1) << "Rewriting tab node " << sync_id << " with tab id " | |
| 308 << tab->tab_id.id(); | |
| 309 UpdateTabSpecifics(sync_id, *tab, change_output); | |
| 310 } | |
| 311 } | |
| 253 } | 312 } |
| 254 for (auto window_iter_pair : windows) { | 313 |
| 314 for (auto& window_iter_pair : windows) { | |
| 255 const SyncedWindowDelegate* window_delegate = window_iter_pair.second; | 315 const SyncedWindowDelegate* window_delegate = window_iter_pair.second; |
| 256 if (option == RELOAD_TABS) { | 316 if (option == RELOAD_TABS) { |
| 257 UMA_HISTOGRAM_COUNTS("Sync.SessionTabs", window_delegate->GetTabCount()); | 317 UMA_HISTOGRAM_COUNTS("Sync.SessionTabs", window_delegate->GetTabCount()); |
| 258 } | 318 } |
| 259 | 319 |
| 260 // Make sure the window has tabs and a viewable window. The viewable window | 320 // Make sure the window has tabs and a viewable window. The viewable |
| 261 // check is necessary because, for example, when a browser is closed the | 321 // window check is necessary because, for example, when a browser is |
| 262 // destructor is not necessarily run immediately. This means its possible | 322 // closed the destructor is not necessarily run immediately. This means |
| 263 // for us to get a handle to a browser that is about to be removed. If | 323 // its possible for us to get a handle to a browser that is about to be |
| 264 // the tab count is 0 or the window is null, the browser is about to be | 324 // removed. If the tab count is 0 or the window is null, the browser is |
| 265 // deleted, so we ignore it. | 325 // about to be deleted, so we ignore it. |
| 266 if (window_delegate->ShouldSync() && window_delegate->GetTabCount() && | 326 if (window_delegate->ShouldSync() && window_delegate->GetTabCount() && |
| 267 window_delegate->HasWindow()) { | 327 window_delegate->HasWindow()) { |
| 268 sync_pb::SessionWindow window_s; | 328 sync_pb::SessionWindow window_s; |
| 269 SessionID::id_type window_id = window_delegate->GetSessionId(); | 329 SessionID::id_type window_id = window_delegate->GetSessionId(); |
| 270 DVLOG(1) << "Associating window " << window_id << " with " | 330 DVLOG(1) << "Associating window " << window_id << " with " |
| 271 << window_delegate->GetTabCount() << " tabs."; | 331 << window_delegate->GetTabCount() << " tabs."; |
| 272 window_s.set_window_id(window_id); | |
| 273 // Note: We don't bother to set selected tab index anymore. We still | |
| 274 // consume it when receiving foreign sessions, as reading it is free, but | |
| 275 // it triggers too many sync cycles with too little value to make setting | |
| 276 // it worthwhile. | |
| 277 if (window_delegate->IsTypeTabbed()) { | |
| 278 window_s.set_browser_type( | |
| 279 sync_pb::SessionWindow_BrowserType_TYPE_TABBED); | |
| 280 } else if (window_delegate->IsTypePopup()) { | |
| 281 window_s.set_browser_type( | |
| 282 sync_pb::SessionWindow_BrowserType_TYPE_POPUP); | |
| 283 } else { | |
| 284 // This is a custom tab within an app. These will not be restored on | |
| 285 // startup if not present. | |
| 286 window_s.set_browser_type( | |
| 287 sync_pb::SessionWindow_BrowserType_TYPE_CUSTOM_TAB); | |
| 288 } | |
| 289 | 332 |
| 290 bool found_tabs = false; | 333 bool found_tabs = false; |
| 291 for (int j = 0; j < window_delegate->GetTabCount(); ++j) { | 334 for (int j = 0; j < window_delegate->GetTabCount(); ++j) { |
| 292 SessionID::id_type tab_id = window_delegate->GetTabIdAt(j); | 335 SessionID::id_type tab_id = window_delegate->GetTabIdAt(j); |
| 293 SyncedTabDelegate* synced_tab = window_delegate->GetTabAt(j); | 336 SyncedTabDelegate* synced_tab = window_delegate->GetTabAt(j); |
| 294 | 337 |
| 295 // GetTabAt can return a null tab; in that case just skip it. | 338 // GetTabAt can return a null tab; in that case just skip it. Similarly, |
| 296 if (!synced_tab) | 339 // if for some reason the tab id is invalid, skip it. |
| 340 if (!synced_tab || !ShouldSyncTabId(tab_id)) | |
| 297 continue; | 341 continue; |
| 298 | 342 |
| 299 if (!ShouldSyncTabId(tab_id)) { | |
| 300 LOG(ERROR) << "Not syncing invalid tab with id " << tab_id; | |
| 301 continue; | |
| 302 } | |
| 303 | |
| 304 // Placeholder tabs are those without WebContents, either because they | 343 // Placeholder tabs are those without WebContents, either because they |
| 305 // were never loaded into memory or they were evicted from memory | 344 // were never loaded into memory or they were evicted from memory |
| 306 // (typically only on Android devices). They only have a tab id, window | 345 // (typically only on Android devices). They only have a tab id, |
| 307 // id, and a saved synced id (corresponding to the tab node id). Note | 346 // window id, and a saved synced id (corresponding to the tab node |
| 308 // that only placeholders have this sync id, as it's necessary to | 347 // id). Note that only placeholders have this sync id, as it's |
| 309 // properly reassociate the tab with the entity that was backing it. | 348 // necessary to properly reassociate the tab with the entity that was |
| 349 // backing it. | |
| 310 if (synced_tab->IsPlaceholderTab()) { | 350 if (synced_tab->IsPlaceholderTab()) { |
| 311 // For tabs without WebContents update the |tab_id| and |window_id|, | 351 // For tabs without WebContents update the |tab_id| and |window_id|, |
| 312 // as it could have changed after a session restore. | 352 // as it could have changed after a session restore. |
| 313 if (synced_tab->GetSyncId() > TabNodePool::kInvalidTabNodeID) { | 353 if (synced_tab->GetSyncId() > TabNodePool::kInvalidTabNodeID) { |
| 314 AssociateRestoredPlaceholderTab(*synced_tab, tab_id, window_id, | 354 AssociateRestoredPlaceholderTab(*synced_tab, tab_id, window_id, |
| 315 change_output); | 355 change_output); |
| 356 } else { | |
| 357 DVLOG(1) << "Placeholder tab " << tab_id << " has no sync id."; | |
| 316 } | 358 } |
| 317 } else if (RELOAD_TABS == option) { | 359 } else if (RELOAD_TABS == option) { |
| 318 AssociateTab(synced_tab, change_output); | 360 AssociateTab(synced_tab, change_output); |
| 319 } | 361 } |
| 320 | 362 |
| 321 // If the tab was syncable, it would have been added to the tracker | 363 // If the tab was syncable, it would have been added to the tracker |
| 322 // either by the above Associate[RestoredPlaceholder]Tab call or by the | 364 // either by the above Associate[RestoredPlaceholder]Tab call or by |
| 323 // OnLocalTabModified method invoking AssociateTab directly. Therefore, | 365 // the OnLocalTabModified method invoking AssociateTab directly. |
| 324 // we can key whether this window has valid tabs based on the tab's | 366 // Therefore, we can key whether this window has valid tabs based on |
| 325 // presence in the tracker. | 367 // the tab's presence in the tracker. |
| 326 const sessions::SessionTab* tab = nullptr; | 368 const sessions::SessionTab* tab = nullptr; |
| 327 if (session_tracker_.LookupSessionTab(local_tag, tab_id, &tab)) { | 369 if (session_tracker_.LookupSessionTab(current_machine_tag(), tab_id, |
| 370 &tab)) { | |
| 328 found_tabs = true; | 371 found_tabs = true; |
| 329 window_s.add_tab(tab_id); | 372 |
| 373 // Update this window's representation in the synced session tracker. | |
| 374 // This is a no-op if called multiple times. | |
| 375 session_tracker_.PutWindowInSession(current_machine_tag(), window_id); | |
| 376 | |
| 377 // Put the tab in the window (must happen after the window is added | |
| 378 // to the session). | |
| 379 session_tracker_.PutTabInWindow(current_machine_tag(), window_id, | |
| 380 tab_id); | |
| 330 } | 381 } |
| 331 } | 382 } |
| 332 if (found_tabs) { | 383 if (found_tabs) { |
| 333 sync_pb::SessionWindow* header_window = header_s->add_window(); | 384 SyncedSessionWindow* synced_session_window = |
| 334 *header_window = window_s; | 385 current_session->windows[window_id].get(); |
| 335 | 386 // Note: We don't bother to set selected tab index anymore. We still |
|
skym
2017/04/17 20:37:02
Seems like we should ideally be able to stop commi
Nicolas Zea
2017/04/17 21:24:54
This comment is out of date actually (was copied o
| |
| 336 // Update this window's representation in the synced session tracker. | 387 // consume it when receiving foreign sessions, as reading it is free, |
| 337 session_tracker_.PutWindowInSession(local_tag, window_id); | 388 // but it triggers too many sync cycles with too little value to make |
| 338 BuildSyncedSessionFromSpecifics( | 389 // setting it worthwhile. |
| 339 local_tag, window_s, current_session->modified_time, | 390 if (window_delegate->IsTypeTabbed()) { |
| 340 current_session->windows[window_id].get()); | 391 synced_session_window->window_type = |
| 392 sync_pb::SessionWindow_BrowserType_TYPE_TABBED; | |
| 393 } else if (window_delegate->IsTypePopup()) { | |
| 394 synced_session_window->window_type = | |
| 395 sync_pb::SessionWindow_BrowserType_TYPE_POPUP; | |
| 396 } else { | |
| 397 // This is a custom tab within an app. These will not be restored on | |
| 398 // startup if not present. | |
| 399 synced_session_window->window_type = | |
| 400 sync_pb::SessionWindow_BrowserType_TYPE_CUSTOM_TAB; | |
| 401 } | |
| 341 } | 402 } |
| 342 } | 403 } |
| 343 } | 404 } |
| 344 std::set<int> deleted_tab_node_ids; | 405 std::set<int> deleted_tab_node_ids; |
| 345 session_tracker_.CleanupLocalTabs(&deleted_tab_node_ids); | 406 session_tracker_.CleanupLocalTabs(&deleted_tab_node_ids); |
| 346 AppendDeletionsForTabNodes(deleted_tab_node_ids, current_machine_tag(), | 407 AppendDeletionsForTabNodes(deleted_tab_node_ids, current_machine_tag(), |
| 347 change_output); | 408 change_output); |
| 348 | 409 |
| 349 // Always update the header. Sync takes care of dropping this update | 410 // Always update the header. Sync takes care of dropping this update |
| 350 // if the entity specifics are identical (i.e windows, client name did | 411 // if the entity specifics are identical (i.e windows, client name did |
| 351 // not change). | 412 // not change). |
| 352 sync_pb::EntitySpecifics entity; | 413 sync_pb::EntitySpecifics entity; |
| 353 entity.mutable_session()->CopyFrom(specifics); | 414 entity.mutable_session()->set_session_tag(current_machine_tag()); |
| 415 entity.mutable_session()->mutable_header()->CopyFrom( | |
| 416 current_session->ToSessionHeaderProto()); | |
| 354 syncer::SyncData data = syncer::SyncData::CreateLocalData( | 417 syncer::SyncData data = syncer::SyncData::CreateLocalData( |
| 355 current_machine_tag(), current_session_name_, entity); | 418 current_machine_tag(), current_session_name_, entity); |
| 356 change_output->push_back( | 419 change_output->push_back( |
| 357 syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_UPDATE, data)); | 420 syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_UPDATE, data)); |
| 358 } | 421 } |
| 359 | 422 |
| 360 void SessionsSyncManager::AssociateTab(SyncedTabDelegate* const tab_delegate, | 423 void SessionsSyncManager::AssociateTab(SyncedTabDelegate* const tab_delegate, |
| 361 syncer::SyncChangeList* change_output) { | 424 syncer::SyncChangeList* change_output) { |
| 362 DCHECK(!tab_delegate->IsPlaceholderTab()); | 425 DCHECK(!tab_delegate->IsPlaceholderTab()); |
| 363 | 426 |
| 364 if (tab_delegate->IsBeingDestroyed()) { | 427 if (tab_delegate->IsBeingDestroyed()) { |
| 365 // Do nothing. By not proactively adding the tab to the session, it will be | 428 // Do nothing. By not proactively adding the tab to the session, it will be |
| 366 // removed if necessary during subsequent cleanup. | 429 // removed if necessary during subsequent cleanup. |
| 367 return; | 430 return; |
| 368 } | 431 } |
| 369 | 432 |
| 370 if (!tab_delegate->ShouldSync(sessions_client_)) | 433 if (!tab_delegate->ShouldSync(sessions_client_)) |
| 371 return; | 434 return; |
| 372 | 435 |
| 373 SessionID::id_type tab_id = tab_delegate->GetSessionId(); | 436 SessionID::id_type tab_id = tab_delegate->GetSessionId(); |
| 374 DVLOG(1) << "Syncing tab " << tab_id << " from window " | 437 DVLOG(1) << "Syncing tab " << tab_id << " from window " |
| 375 << tab_delegate->GetWindowId(); | 438 << tab_delegate->GetWindowId(); |
| 376 | 439 |
| 377 int tab_node_id = TabNodePool::kInvalidTabNodeID; | 440 int tab_node_id = TabNodePool::kInvalidTabNodeID; |
| 378 bool existing_tab_node = | 441 bool existing_tab_node = true; |
| 379 session_tracker_.GetTabNodeFromLocalTabId(tab_id, &tab_node_id); | 442 if (session_tracker_.IsLocalTabNodeAssociated(tab_delegate->GetSyncId())) { |
| 380 CHECK_NE(TabNodePool::kInvalidTabNodeID, tab_node_id) << "crbug.com/673618"; | 443 tab_node_id = tab_delegate->GetSyncId(); |
| 381 tab_delegate->SetSyncId(tab_node_id); | 444 session_tracker_.ReassociateLocalTab(tab_node_id, tab_id); |
| 445 } else { | |
| 446 existing_tab_node = | |
| 447 session_tracker_.GetTabNodeFromLocalTabId(tab_id, &tab_node_id); | |
| 448 CHECK_NE(TabNodePool::kInvalidTabNodeID, tab_node_id) << "crbug.com/673618"; | |
| 449 tab_delegate->SetSyncId(tab_node_id); | |
| 450 } | |
| 451 | |
| 382 sessions::SessionTab* session_tab = | 452 sessions::SessionTab* session_tab = |
| 383 session_tracker_.GetTab(current_machine_tag(), tab_id); | 453 session_tracker_.GetTab(current_machine_tag(), tab_id); |
| 384 | 454 |
| 385 // Get the previously synced url. | 455 // Get the previously synced url. |
| 386 int old_index = session_tab->normalized_navigation_index(); | 456 int old_index = session_tab->normalized_navigation_index(); |
| 387 GURL old_url; | 457 GURL old_url; |
| 388 if (session_tab->navigations.size() > static_cast<size_t>(old_index)) | 458 if (session_tab->navigations.size() > static_cast<size_t>(old_index)) |
| 389 old_url = session_tab->navigations[old_index].virtual_url(); | 459 old_url = session_tab->navigations[old_index].virtual_url(); |
| 390 | 460 |
| 391 // Update the tracker's session representation. | 461 // Update the tracker's session representation. |
| (...skipping 234 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 626 if (!session_tracker_.LookupAllForeignSessions( | 696 if (!session_tracker_.LookupAllForeignSessions( |
| 627 sessions, SyncedSessionTracker::PRESENTABLE)) | 697 sessions, SyncedSessionTracker::PRESENTABLE)) |
| 628 return false; | 698 return false; |
| 629 std::sort(sessions->begin(), sessions->end(), SessionsRecencyComparator); | 699 std::sort(sessions->begin(), sessions->end(), SessionsRecencyComparator); |
| 630 return true; | 700 return true; |
| 631 } | 701 } |
| 632 | 702 |
| 633 bool SessionsSyncManager::InitFromSyncModel( | 703 bool SessionsSyncManager::InitFromSyncModel( |
| 634 const syncer::SyncDataList& sync_data, | 704 const syncer::SyncDataList& sync_data, |
| 635 syncer::SyncChangeList* new_changes) { | 705 syncer::SyncChangeList* new_changes) { |
| 706 // Map of all rewritten local ids. Because ids are reset on each restart, | |
| 707 // and id generation happens outside of Sync, all ids from a previous local | |
| 708 // session must be rewritten in order to be valid. | |
| 709 // Key: previous session id. Value: new session id. | |
| 710 std::map<SessionID::id_type, SessionID::id_type> session_id_map; | |
| 711 | |
| 636 bool found_current_header = false; | 712 bool found_current_header = false; |
| 637 int bad_foreign_hash_count = 0; | 713 int bad_foreign_hash_count = 0; |
| 638 for (syncer::SyncDataList::const_iterator it = sync_data.begin(); | 714 for (syncer::SyncDataList::const_iterator it = sync_data.begin(); |
| 639 it != sync_data.end(); ++it) { | 715 it != sync_data.end(); ++it) { |
| 640 const syncer::SyncData& data = *it; | 716 const syncer::SyncData& data = *it; |
| 641 DCHECK(data.GetSpecifics().has_session()); | 717 DCHECK(data.GetSpecifics().has_session()); |
| 642 syncer::SyncDataRemote remote(data); | 718 syncer::SyncDataRemote remote(data); |
| 643 const sync_pb::SessionSpecifics& specifics = data.GetSpecifics().session(); | 719 const sync_pb::SessionSpecifics& specifics = data.GetSpecifics().session(); |
| 644 if (specifics.session_tag().empty() || | 720 if (specifics.session_tag().empty() || |
| 645 (specifics.has_tab() && | 721 (specifics.has_tab() && |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 662 syncer::SyncChange(FROM_HERE, SyncChange::ACTION_DELETE, remote)); | 738 syncer::SyncChange(FROM_HERE, SyncChange::ACTION_DELETE, remote)); |
| 663 } | 739 } |
| 664 } else { | 740 } else { |
| 665 // This is previously stored local session information. | 741 // This is previously stored local session information. |
| 666 if (specifics.has_header() && !found_current_header) { | 742 if (specifics.has_header() && !found_current_header) { |
| 667 // This is our previous header node, reuse it. | 743 // This is our previous header node, reuse it. |
| 668 found_current_header = true; | 744 found_current_header = true; |
| 669 if (specifics.header().has_client_name()) | 745 if (specifics.header().has_client_name()) |
| 670 current_session_name_ = specifics.header().client_name(); | 746 current_session_name_ = specifics.header().client_name(); |
| 671 | 747 |
| 672 // TODO(zea): crbug.com/639009 update the tracker with the specifics | 748 // The specifics from the SyncData are immutable. Create a mutable copy |
| 673 // from the header node as well. This will be necessary to preserve | 749 // to hold the rewritten ids. |
| 674 // the set of open tabs when a custom tab is opened. | 750 sync_pb::SessionSpecifics rewritten_specifics(specifics); |
| 751 | |
| 752 // Go through and generate new tab and window ids as necessary, updating | |
| 753 // the specifics in place. | |
| 754 for (auto& window : | |
| 755 *rewritten_specifics.mutable_header()->mutable_window()) { | |
| 756 session_id_map[window.window_id()] = SessionID().id(); | |
| 757 window.set_window_id(session_id_map[window.window_id()]); | |
| 758 | |
| 759 google::protobuf::RepeatedField<int>* tab_ids = window.mutable_tab(); | |
| 760 for (int i = 0; i < tab_ids->size(); i++) { | |
| 761 auto tab_iter = session_id_map.find(tab_ids->Get(i)); | |
| 762 if (tab_iter == session_id_map.end()) { | |
| 763 session_id_map[tab_ids->Get(i)] = SessionID().id(); | |
|
skym
2017/04/17 20:37:02
I was really confused by this SessionID call until
Nicolas Zea
2017/04/17 21:24:54
Yeah, I know what you mean. Added a comment to mak
| |
| 764 } | |
| 765 *(tab_ids->Mutable(i)) = session_id_map[tab_ids->Get(i)]; | |
| 766 // Note: the tab id of the SessionTab will be updated when the tab | |
| 767 // node itself is processed. | |
| 768 } | |
| 769 } | |
| 770 | |
| 771 UpdateTrackerWithSpecifics(rewritten_specifics, | |
| 772 remote.GetModifiedTime()); | |
| 773 | |
| 774 DVLOG(1) << "Loaded local header and rewrote " << session_id_map.size() | |
| 775 << " ids."; | |
| 776 | |
| 675 } else { | 777 } else { |
| 676 if (specifics.has_header() || !specifics.has_tab()) { | 778 if (specifics.has_header() || !specifics.has_tab()) { |
| 677 LOG(WARNING) << "Found more than one session header node with local " | 779 LOG(WARNING) << "Found more than one session header node with local " |
| 678 << "tag."; | 780 << "tag."; |
| 679 syncer::SyncChange tombstone(TombstoneTab(specifics)); | 781 syncer::SyncChange tombstone(TombstoneTab(specifics)); |
| 680 if (tombstone.IsValid()) | 782 if (tombstone.IsValid()) |
| 681 new_changes->push_back(tombstone); | 783 new_changes->push_back(tombstone); |
| 682 } else if (specifics.tab().tab_id() == kInvalidTabID) { | 784 } else if (specifics.tab().tab_id() == kInvalidTabID) { |
| 683 LOG(WARNING) << "Found tab node with invalid tab id."; | 785 LOG(WARNING) << "Found tab node with invalid tab id."; |
| 684 syncer::SyncChange tombstone(TombstoneTab(specifics)); | 786 syncer::SyncChange tombstone(TombstoneTab(specifics)); |
| 685 if (tombstone.IsValid()) | 787 if (tombstone.IsValid()) |
| 686 new_changes->push_back(tombstone); | 788 new_changes->push_back(tombstone); |
| 687 } else { | 789 } else { |
| 688 // This is a valid old tab node, add it to the tracker and associate | 790 // This is a valid old tab node, add it to the tracker and associate |
| 689 // it. | 791 // it (using the new tab id). |
| 690 DVLOG(1) << "Associating local tab " << specifics.tab().tab_id() | 792 DVLOG(1) << "Associating local tab " << specifics.tab().tab_id() |
| 691 << " with node " << specifics.tab_node_id(); | 793 << " with node " << specifics.tab_node_id(); |
| 692 session_tracker_.ReassociateLocalTab(specifics.tab_node_id(), | 794 |
| 693 specifics.tab().tab_id()); | 795 // Now file the tab under the new tab id. |
| 694 UpdateTrackerWithSpecifics(specifics, remote.GetModifiedTime()); | 796 SessionID::id_type new_tab_id = kInvalidTabID; |
| 797 auto iter = session_id_map.find(specifics.tab().tab_id()); | |
| 798 if (iter != session_id_map.end()) { | |
| 799 new_tab_id = iter->second; | |
| 800 } else { | |
| 801 session_id_map[specifics.tab().tab_id()] = SessionID().id(); | |
| 802 new_tab_id = session_id_map[specifics.tab().tab_id()]; | |
| 803 } | |
| 804 DVLOG(1) << "Remapping tab " << specifics.tab().tab_id() << " to " | |
| 805 << new_tab_id; | |
| 806 | |
| 807 // The specifics from the SyncData are immutable. Create a mutable | |
| 808 // copy to hold the rewritten ids. | |
| 809 sync_pb::SessionSpecifics rewritten_specifics(specifics); | |
| 810 rewritten_specifics.mutable_tab()->set_tab_id(new_tab_id); | |
| 811 session_tracker_.ReassociateLocalTab( | |
| 812 rewritten_specifics.tab_node_id(), new_tab_id); | |
| 813 UpdateTrackerWithSpecifics(rewritten_specifics, | |
| 814 remote.GetModifiedTime()); | |
| 815 | |
| 816 session_tracker_.ReassociateLocalTab( | |
| 817 rewritten_specifics.tab_node_id(), new_tab_id); | |
| 695 } | 818 } |
| 696 } | 819 } |
| 697 } | 820 } |
| 698 } | 821 } |
| 699 | 822 |
| 700 // Cleanup all foreign sessions, since orphaned tabs may have been added after | 823 // Cleanup all foreign sessions, since orphaned tabs may have been added after |
| 701 // the header. | 824 // the header. |
| 702 std::vector<const SyncedSession*> sessions; | 825 std::vector<const SyncedSession*> sessions; |
| 703 session_tracker_.LookupAllForeignSessions(&sessions, | 826 session_tracker_.LookupAllForeignSessions(&sessions, |
| 704 SyncedSessionTracker::RAW); | 827 SyncedSessionTracker::RAW); |
| 705 for (const auto* session : sessions) { | 828 for (const auto* session : sessions) { |
| 706 session_tracker_.CleanupForeignSession(session->session_tag); | 829 session_tracker_.CleanupSession(session->session_tag); |
| 707 } | 830 } |
| 708 | 831 |
| 709 UMA_HISTOGRAM_COUNTS_100("Sync.SessionsBadForeignHashOnMergeCount", | 832 UMA_HISTOGRAM_COUNTS_100("Sync.SessionsBadForeignHashOnMergeCount", |
| 710 bad_foreign_hash_count); | 833 bad_foreign_hash_count); |
| 711 | 834 |
| 712 return found_current_header; | 835 return found_current_header; |
| 713 } | 836 } |
| 714 | 837 |
| 715 void SessionsSyncManager::UpdateTrackerWithSpecifics( | 838 void SessionsSyncManager::UpdateTrackerWithSpecifics( |
| 716 const sync_pb::SessionSpecifics& specifics, | 839 const sync_pb::SessionSpecifics& specifics, |
| 717 const base::Time& modification_time) { | 840 const base::Time& modification_time) { |
| 718 std::string session_tag = specifics.session_tag(); | 841 std::string session_tag = specifics.session_tag(); |
| 719 SyncedSession* session = session_tracker_.GetSession(session_tag); | 842 SyncedSession* session = session_tracker_.GetSession(session_tag); |
| 720 if (specifics.has_header()) { | 843 if (specifics.has_header()) { |
| 721 // Read in the header data for this session. Header data is | 844 // Read in the header data for this session. Header data is |
| 722 // essentially a collection of windows, each of which has an ordered id list | 845 // essentially a collection of windows, each of which has an ordered id list |
| 723 // for their tabs. | 846 // for their tabs. |
| 724 | 847 |
| 725 if (!IsValidSessionHeader(specifics.header())) { | 848 if (!IsValidSessionHeader(specifics.header())) { |
| 726 LOG(WARNING) << "Ignoring session node with invalid header " | 849 LOG(WARNING) << "Ignoring session node with invalid header " |
| 727 << "and tag " << session_tag << "."; | 850 << "and tag " << session_tag << "."; |
| 728 return; | 851 return; |
| 729 } | 852 } |
| 730 | 853 |
| 731 // Load (or create) the SyncedSession object for this client. | 854 // Load (or create) the SyncedSession object for this client. |
| 732 const sync_pb::SessionHeader& header = specifics.header(); | 855 const sync_pb::SessionHeader& header = specifics.header(); |
| 733 PopulateSessionHeaderFromSpecifics(header, modification_time, session); | |
| 734 | 856 |
| 735 // Reset the tab/window tracking for this session (must do this before | 857 // Reset the tab/window tracking for this session (must do this before |
| 736 // we start calling PutWindowInSession and PutTabInWindow so that all | 858 // we start calling PutWindowInSession and PutTabInWindow so that all |
| 737 // unused tabs/windows get cleared by the CleanupSession(...) call). | 859 // unused tabs/windows get cleared by the CleanupSession(...) call). |
| 738 session_tracker_.ResetSessionTracking(session_tag); | 860 session_tracker_.ResetSessionTracking(session_tag); |
| 739 | 861 |
| 740 // Process all the windows and their tab information. | 862 PopulateSyncedSessionFromSpecifics(session_tag, header, modification_time, |
| 741 int num_windows = header.window_size(); | 863 session); |
| 742 DVLOG(1) << "Populating " << session_tag << " with " << num_windows | |
| 743 << " windows."; | |
| 744 | 864 |
| 745 for (int i = 0; i < num_windows; ++i) { | |
| 746 const sync_pb::SessionWindow& window_s = header.window(i); | |
| 747 SessionID::id_type window_id = window_s.window_id(); | |
| 748 session_tracker_.PutWindowInSession(session_tag, window_id); | |
| 749 BuildSyncedSessionFromSpecifics(session_tag, window_s, modification_time, | |
| 750 session->windows[window_id].get()); | |
| 751 } | |
| 752 // Delete any closed windows and unused tabs as necessary. | 865 // Delete any closed windows and unused tabs as necessary. |
| 753 session_tracker_.CleanupForeignSession(session_tag); | 866 session_tracker_.CleanupSession(session_tag); |
| 754 } else if (specifics.has_tab()) { | 867 } else if (specifics.has_tab()) { |
| 755 const sync_pb::SessionTab& tab_s = specifics.tab(); | 868 const sync_pb::SessionTab& tab_s = specifics.tab(); |
| 756 SessionID::id_type tab_id = tab_s.tab_id(); | 869 SessionID::id_type tab_id = tab_s.tab_id(); |
| 757 DVLOG(1) << "Populating " << session_tag << "'s tab id " << tab_id | 870 DVLOG(1) << "Populating " << session_tag << "'s tab id " << tab_id |
| 758 << " from node " << specifics.tab_node_id(); | 871 << " from node " << specifics.tab_node_id(); |
| 759 | 872 |
| 760 // Ensure the tracker is aware of the tab node id. Deleting foreign sessions | 873 // Ensure the tracker is aware of the tab node id. Deleting foreign sessions |
| 761 // requires deleting all relevant tab nodes, and it's easier to track the | 874 // requires deleting all relevant tab nodes, and it's easier to track the |
| 762 // tab node ids themselves separately from the tab ids. | 875 // tab node ids themselves separately from the tab ids. |
| 763 // | 876 // |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 815 current_machine_tag_ = persisted_guid; | 928 current_machine_tag_ = persisted_guid; |
| 816 DVLOG(1) << "Restoring persisted session sync guid: " << persisted_guid; | 929 DVLOG(1) << "Restoring persisted session sync guid: " << persisted_guid; |
| 817 } else { | 930 } else { |
| 818 DCHECK(!cache_guid.empty()); | 931 DCHECK(!cache_guid.empty()); |
| 819 current_machine_tag_ = BuildMachineTag(cache_guid); | 932 current_machine_tag_ = BuildMachineTag(cache_guid); |
| 820 DVLOG(1) << "Creating session sync guid: " << current_machine_tag_; | 933 DVLOG(1) << "Creating session sync guid: " << current_machine_tag_; |
| 821 sync_prefs_->SetSyncSessionsGUID(current_machine_tag_); | 934 sync_prefs_->SetSyncSessionsGUID(current_machine_tag_); |
| 822 } | 935 } |
| 823 } | 936 } |
| 824 | 937 |
| 938 void SessionsSyncManager::PopulateSyncedSessionFromSpecifics( | |
| 939 const std::string& session_tag, | |
| 940 const sync_pb::SessionHeader& header_specifics, | |
| 941 base::Time mtime, | |
| 942 SyncedSession* synced_session) { | |
| 943 if (header_specifics.has_client_name()) | |
| 944 synced_session->session_name = header_specifics.client_name(); | |
| 945 if (header_specifics.has_device_type()) { | |
| 946 synced_session->device_type = ProtoDeviceTypeToSyncedSessionDeviceType( | |
| 947 header_specifics.device_type()); | |
| 948 } | |
| 949 synced_session->modified_time = | |
| 950 std::max(mtime, synced_session->modified_time); | |
| 951 | |
| 952 // Process all the windows and their tab information. | |
| 953 int num_windows = header_specifics.window_size(); | |
| 954 DVLOG(1) << "Populating " << session_tag << " with " << num_windows | |
| 955 << " windows."; | |
| 956 | |
| 957 for (int i = 0; i < num_windows; ++i) { | |
| 958 const sync_pb::SessionWindow& window_s = header_specifics.window(i); | |
| 959 SessionID::id_type window_id = window_s.window_id(); | |
| 960 session_tracker_.PutWindowInSession(session_tag, window_id); | |
| 961 PopulateSyncedSessionWindowFromSpecifics( | |
| 962 session_tag, window_s, synced_session->modified_time, | |
| 963 synced_session->windows[window_id].get()); | |
| 964 } | |
| 965 } | |
| 966 | |
| 825 // static | 967 // static |
| 826 void SessionsSyncManager::PopulateSessionHeaderFromSpecifics( | 968 void SessionsSyncManager::PopulateSyncedSessionWindowFromSpecifics( |
| 827 const sync_pb::SessionHeader& header_specifics, | |
| 828 base::Time mtime, | |
| 829 SyncedSession* session_header) { | |
| 830 if (header_specifics.has_client_name()) | |
| 831 session_header->session_name = header_specifics.client_name(); | |
| 832 if (header_specifics.has_device_type()) { | |
| 833 switch (header_specifics.device_type()) { | |
| 834 case sync_pb::SyncEnums_DeviceType_TYPE_WIN: | |
| 835 session_header->device_type = SyncedSession::TYPE_WIN; | |
| 836 break; | |
| 837 case sync_pb::SyncEnums_DeviceType_TYPE_MAC: | |
| 838 session_header->device_type = SyncedSession::TYPE_MACOSX; | |
| 839 break; | |
| 840 case sync_pb::SyncEnums_DeviceType_TYPE_LINUX: | |
| 841 session_header->device_type = SyncedSession::TYPE_LINUX; | |
| 842 break; | |
| 843 case sync_pb::SyncEnums_DeviceType_TYPE_CROS: | |
| 844 session_header->device_type = SyncedSession::TYPE_CHROMEOS; | |
| 845 break; | |
| 846 case sync_pb::SyncEnums_DeviceType_TYPE_PHONE: | |
| 847 session_header->device_type = SyncedSession::TYPE_PHONE; | |
| 848 break; | |
| 849 case sync_pb::SyncEnums_DeviceType_TYPE_TABLET: | |
| 850 session_header->device_type = SyncedSession::TYPE_TABLET; | |
| 851 break; | |
| 852 case sync_pb::SyncEnums_DeviceType_TYPE_OTHER: | |
| 853 // Intentionally fall-through | |
| 854 default: | |
| 855 session_header->device_type = SyncedSession::TYPE_OTHER; | |
| 856 break; | |
| 857 } | |
| 858 } | |
| 859 session_header->modified_time = | |
| 860 std::max(mtime, session_header->modified_time); | |
| 861 } | |
| 862 | |
| 863 // static | |
| 864 void SessionsSyncManager::BuildSyncedSessionFromSpecifics( | |
| 865 const std::string& session_tag, | 969 const std::string& session_tag, |
| 866 const sync_pb::SessionWindow& specifics, | 970 const sync_pb::SessionWindow& specifics, |
| 867 base::Time mtime, | 971 base::Time mtime, |
| 868 SyncedSessionWindow* synced_session_window) { | 972 SyncedSessionWindow* synced_session_window) { |
| 869 sessions::SessionWindow* session_window = | 973 sessions::SessionWindow* session_window = |
| 870 &synced_session_window->wrapped_window; | 974 &synced_session_window->wrapped_window; |
| 871 if (specifics.has_window_id()) | 975 if (specifics.has_window_id()) |
| 872 session_window->window_id.set_id(specifics.window_id()); | 976 session_window->window_id.set_id(specifics.window_id()); |
| 873 if (specifics.has_selected_tab_index()) | 977 if (specifics.has_selected_tab_index()) |
| 874 session_window->selected_tab_index = specifics.selected_tab_index(); | 978 session_window->selected_tab_index = specifics.selected_tab_index(); |
| (...skipping 135 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1010 | 1114 |
| 1011 // Update tracker with the new association (and inform it of the tab node | 1115 // Update tracker with the new association (and inform it of the tab node |
| 1012 // in the process). | 1116 // in the process). |
| 1013 session_tracker_.ReassociateLocalTab(tab_delegate.GetSyncId(), new_tab_id); | 1117 session_tracker_.ReassociateLocalTab(tab_delegate.GetSyncId(), new_tab_id); |
| 1014 | 1118 |
| 1015 // Update the window id on the SessionTab itself. | 1119 // Update the window id on the SessionTab itself. |
| 1016 sessions::SessionTab* local_tab = | 1120 sessions::SessionTab* local_tab = |
| 1017 session_tracker_.GetTab(current_machine_tag(), new_tab_id); | 1121 session_tracker_.GetTab(current_machine_tag(), new_tab_id); |
| 1018 local_tab->window_id.set_id(new_window_id); | 1122 local_tab->window_id.set_id(new_window_id); |
| 1019 | 1123 |
| 1124 UpdateTabSpecifics(tab_delegate.GetSyncId(), *local_tab, change_output); | |
| 1125 } | |
| 1126 | |
| 1127 void SessionsSyncManager::UpdateTabSpecifics( | |
| 1128 int sync_id, | |
| 1129 const sessions::SessionTab& tab, | |
| 1130 syncer::SyncChangeList* change_output) { | |
| 1020 // Rewrite the specifics based on the reassociated SessionTab to preserve | 1131 // Rewrite the specifics based on the reassociated SessionTab to preserve |
| 1021 // the new tab and window ids. | 1132 // the new tab and window ids. |
| 1022 sync_pb::EntitySpecifics entity; | 1133 sync_pb::EntitySpecifics entity; |
| 1023 entity.mutable_session()->CopyFrom(SessionTabToSpecifics( | 1134 entity.mutable_session()->CopyFrom( |
| 1024 *local_tab, current_machine_tag(), tab_delegate.GetSyncId())); | 1135 SessionTabToSpecifics(tab, current_machine_tag(), sync_id)); |
| 1025 syncer::SyncData data = syncer::SyncData::CreateLocalData( | 1136 syncer::SyncData data = syncer::SyncData::CreateLocalData( |
| 1026 TabNodeIdToTag(current_machine_tag(), tab_delegate.GetSyncId()), | 1137 TabNodeIdToTag(current_machine_tag(), sync_id), current_session_name_, |
| 1027 current_session_name_, entity); | 1138 entity); |
| 1028 change_output->push_back( | 1139 change_output->push_back( |
| 1029 syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_UPDATE, data)); | 1140 syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_UPDATE, data)); |
| 1030 } | 1141 } |
| 1031 | 1142 |
| 1032 // static | 1143 // static |
| 1033 void SessionsSyncManager::SetSessionTabFromDelegate( | 1144 void SessionsSyncManager::SetSessionTabFromDelegate( |
| 1034 const SyncedTabDelegate& tab_delegate, | 1145 const SyncedTabDelegate& tab_delegate, |
| 1035 base::Time mtime, | 1146 base::Time mtime, |
| 1036 sessions::SessionTab* session_tab) { | 1147 sessions::SessionTab* session_tab) { |
| 1037 DCHECK(session_tab); | 1148 DCHECK(session_tab); |
| (...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1126 } | 1237 } |
| 1127 | 1238 |
| 1128 // static | 1239 // static |
| 1129 std::string SessionsSyncManager::TagHashFromSpecifics( | 1240 std::string SessionsSyncManager::TagHashFromSpecifics( |
| 1130 const sync_pb::SessionSpecifics& specifics) { | 1241 const sync_pb::SessionSpecifics& specifics) { |
| 1131 return syncer::GenerateSyncableHash(syncer::SESSIONS, | 1242 return syncer::GenerateSyncableHash(syncer::SESSIONS, |
| 1132 TagFromSpecifics(specifics)); | 1243 TagFromSpecifics(specifics)); |
| 1133 } | 1244 } |
| 1134 | 1245 |
| 1135 }; // namespace sync_sessions | 1246 }; // namespace sync_sessions |
| OLD | NEW |