Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / sync / glue / session_model_associator.cc
1 // Copyright 2012 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/glue/session_model_associator.h"
6
7 #include <algorithm>
8 #include <set>
9 #include <utility>
10
11 #include "base/bind.h"
12 #include "base/location.h"
13 #include "base/logging.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/threading/sequenced_worker_pool.h"
16 #include "chrome/browser/chrome_notification_types.h"
17 #include "chrome/browser/favicon/favicon_service_factory.h"
18 #include "chrome/browser/history/history_service.h"
19 #if !defined(OS_ANDROID)
20 #include "chrome/browser/network_time/navigation_time_helper.h"
21 #endif
22 #include "chrome/browser/prefs/pref_service_syncable.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/sessions/session_id.h"
25 #include "chrome/browser/sync/glue/device_info.h"
26 #include "chrome/browser/sync/glue/synced_device_tracker.h"
27 #include "chrome/browser/sync/glue/synced_session.h"
28 #include "chrome/browser/sync/glue/synced_tab_delegate.h"
29 #include "chrome/browser/sync/glue/synced_window_delegate.h"
30 #include "chrome/browser/sync/profile_sync_service.h"
31 #include "chrome/common/chrome_switches.h"
32 #include "chrome/common/url_constants.h"
33 #include "components/sessions/serialized_navigation_entry.h"
34 #include "components/sync_driver/sync_prefs.h"
35 #include "components/user_prefs/pref_registry_syncable.h"
36 #include "content/public/browser/browser_thread.h"
37 #include "content/public/browser/favicon_status.h"
38 #include "content/public/browser/navigation_entry.h"
39 #include "content/public/browser/notification_details.h"
40 #include "content/public/browser/notification_service.h"
41 #include "content/public/common/url_constants.h"
42 #include "sync/api/sync_error.h"
43 #include "sync/api/time.h"
44 #include "sync/internal_api/public/base/model_type.h"
45 #include "sync/internal_api/public/read_node.h"
46 #include "sync/internal_api/public/read_transaction.h"
47 #include "sync/internal_api/public/write_node.h"
48 #include "sync/internal_api/public/write_transaction.h"
49 #include "sync/protocol/session_specifics.pb.h"
50 #include "sync/syncable/directory.h"
51 #include "sync/syncable/syncable_read_transaction.h"
52 #include "sync/syncable/syncable_write_transaction.h"
53 #if defined(OS_LINUX)
54 #include "base/linux_util.h"
55 #elif defined(OS_WIN)
56 #include <windows.h>
57 #endif
58
59 using content::BrowserThread;
60 using content::NavigationEntry;
61 using sessions::SerializedNavigationEntry;
62 using syncer::SESSIONS;
63
64 namespace {
65
66 std::string SessionTagPrefix() {
67   return std::string("session_sync");
68 }
69
70 // Given a transaction, returns the GUID-based string that should be used for
71 // |current_machine_tag_|.
72 std::string GetMachineTagFromTransaction(
73     syncer::WriteTransaction* trans) {
74   syncer::syncable::Directory* dir = trans->GetWrappedWriteTrans()->directory();
75   std::string machine_tag = SessionTagPrefix();
76   machine_tag.append(dir->cache_guid());
77   return machine_tag;
78 }
79
80 // Given a session tag this function returns the client_id(cache_guid).
81 std::string GetClientIdFromSessionTag(const std::string& session_tag) {
82   if (session_tag.find_first_of(SessionTagPrefix()) == std::string::npos) {
83     LOG(ERROR) << "Session tag is malformatted";
84     return std::string();
85   }
86
87   std::string client_id = session_tag.substr(
88       SessionTagPrefix().length(),
89       session_tag.length());
90
91   return client_id;
92 }
93
94 }  // namespace
95
96 namespace browser_sync {
97
98 namespace {
99 static const char kNoSessionsFolderError[] =
100     "Server did not create the top-level sessions node. We "
101     "might be running against an out-of-date server.";
102
103 // The maximum number of navigations in each direction we care to sync.
104 static const int kMaxSyncNavigationCount = 6;
105
106 // Default number of days without activity after which a session is considered
107 // stale and becomes a candidate for garbage collection.
108 static const size_t kDefaultStaleSessionThresholdDays = 14;  // 2 weeks.
109
110 // Maximum number of favicons to sync.
111 // TODO(zea): pull this from the server.
112 static const int kMaxSyncFavicons = 200;
113
114 }  // namespace
115
116 SessionModelAssociator::SessionModelAssociator(
117     ProfileSyncService* sync_service,
118     DataTypeErrorHandler* error_handler)
119     : local_tab_pool_(sync_service),
120       local_session_syncid_(syncer::kInvalidId),
121       sync_service_(sync_service),
122       stale_session_threshold_days_(kDefaultStaleSessionThresholdDays),
123       setup_for_test_(false),
124       waiting_for_change_(false),
125       profile_(sync_service->profile()),
126       error_handler_(error_handler),
127       favicon_cache_(profile_,
128                      sync_service->current_experiments().favicon_sync_limit),
129       test_weak_factory_(this) {
130   DCHECK(CalledOnValidThread());
131   DCHECK(sync_service_);
132   DCHECK(profile_);
133 }
134
135 SessionModelAssociator::SessionModelAssociator(ProfileSyncService* sync_service,
136                                                bool setup_for_test)
137     : local_tab_pool_(sync_service),
138       local_session_syncid_(syncer::kInvalidId),
139       sync_service_(sync_service),
140       stale_session_threshold_days_(kDefaultStaleSessionThresholdDays),
141       setup_for_test_(setup_for_test),
142       waiting_for_change_(false),
143       profile_(sync_service->profile()),
144       error_handler_(NULL),
145       favicon_cache_(profile_, kMaxSyncFavicons),
146       test_weak_factory_(this) {
147   DCHECK(CalledOnValidThread());
148   DCHECK(sync_service_);
149   DCHECK(profile_);
150   DCHECK(setup_for_test);
151 }
152
153 SessionModelAssociator::~SessionModelAssociator() {
154   DCHECK(CalledOnValidThread());
155 }
156
157 bool SessionModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) {
158   DCHECK(CalledOnValidThread());
159   CHECK(has_nodes);
160   *has_nodes = false;
161   syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare());
162   syncer::ReadNode root(&trans);
163   if (root.InitByTagLookup(syncer::ModelTypeToRootTag(syncer::SESSIONS)) !=
164                            syncer::BaseNode::INIT_OK) {
165     LOG(ERROR) << kNoSessionsFolderError;
166     return false;
167   }
168   // The sync model has user created nodes iff the sessions folder has
169   // any children.
170   *has_nodes = root.HasChildren();
171   return true;
172 }
173
174 int64 SessionModelAssociator::GetSyncIdFromSessionTag(const std::string& tag) {
175   DCHECK(CalledOnValidThread());
176   syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare());
177   syncer::ReadNode node(&trans);
178   if (node.InitByClientTagLookup(SESSIONS, tag) != syncer::BaseNode::INIT_OK)
179     return syncer::kInvalidId;
180   return node.GetId();
181 }
182
183 bool SessionModelAssociator::AssociateWindows(bool reload_tabs,
184                                               syncer::SyncError* error) {
185   DCHECK(CalledOnValidThread());
186   std::string local_tag = GetCurrentMachineTag();
187   sync_pb::SessionSpecifics specifics;
188   specifics.set_session_tag(local_tag);
189   sync_pb::SessionHeader* header_s = specifics.mutable_header();
190   SyncedSession* current_session =
191       synced_session_tracker_.GetSession(local_tag);
192   current_session->modified_time = base::Time::Now();
193   header_s->set_client_name(current_session_name_);
194   header_s->set_device_type(DeviceInfo::GetLocalDeviceType());
195
196   synced_session_tracker_.ResetSessionTracking(local_tag);
197   std::set<SyncedWindowDelegate*> windows =
198       SyncedWindowDelegate::GetSyncedWindowDelegates();
199   for (std::set<SyncedWindowDelegate*>::const_iterator i =
200            windows.begin(); i != windows.end(); ++i) {
201     // Make sure the window has tabs and a viewable window. The viewable window
202     // check is necessary because, for example, when a browser is closed the
203     // destructor is not necessarily run immediately. This means its possible
204     // for us to get a handle to a browser that is about to be removed. If
205     // the tab count is 0 or the window is NULL, the browser is about to be
206     // deleted, so we ignore it.
207     if (ShouldSyncWindow(*i) && (*i)->GetTabCount() && (*i)->HasWindow()) {
208       sync_pb::SessionWindow window_s;
209       SessionID::id_type window_id = (*i)->GetSessionId();
210       DVLOG(1) << "Associating window " << window_id << " with "
211                << (*i)->GetTabCount() << " tabs.";
212       window_s.set_window_id(window_id);
213       // Note: We don't bother to set selected tab index anymore. We still
214       // consume it when receiving foreign sessions, as reading it is free, but
215       // it triggers too many sync cycles with too little value to make setting
216       // it worthwhile.
217       if ((*i)->IsTypeTabbed()) {
218         window_s.set_browser_type(
219             sync_pb::SessionWindow_BrowserType_TYPE_TABBED);
220       } else {
221         window_s.set_browser_type(
222             sync_pb::SessionWindow_BrowserType_TYPE_POPUP);
223       }
224
225       // Store the order of tabs.
226       bool found_tabs = false;
227       for (int j = 0; j < (*i)->GetTabCount(); ++j) {
228         SessionID::id_type tab_id = (*i)->GetTabIdAt(j);
229         SyncedTabDelegate* synced_tab = (*i)->GetTabAt(j);
230
231         // GetTabAt can return a null tab; in that case just skip it.
232         if (!synced_tab)
233           continue;
234
235         if (!synced_tab->HasWebContents()) {
236           // For tabs without WebContents update the |tab_id|, as it could have
237           // changed after a session restore.
238           // Note: We cannot check if a tab is valid if it has no WebContents.
239           // We assume any such tab is valid and leave the contents of
240           // corresponding sync node unchanged.
241           if (synced_tab->GetSyncId() > TabNodePool::kInvalidTabNodeID &&
242               tab_id > TabNodePool::kInvalidTabID) {
243             UpdateTabIdIfNecessary(synced_tab->GetSyncId(), tab_id);
244             found_tabs = true;
245             window_s.add_tab(tab_id);
246           }
247           continue;
248         }
249
250         if (reload_tabs) {
251           // It's possible for GetTabAt to return a tab which has no web
252           // contents. We can assume this means the tab already existed but
253           // hasn't changed, so no need to reassociate.
254           if (synced_tab->HasWebContents() &&
255               !AssociateTab(synced_tab, error)) {
256             // Association failed. Either we need to re-associate, or this is an
257             // unrecoverable error.
258             return false;
259           }
260         }
261
262         // If the tab is valid, it would have been added to the tracker either
263         // by the above AssociateTab call (at association time), or by the
264         // change processor calling AssociateTab for all modified tabs.
265         // Therefore, we can key whether this window has valid tabs based on
266         // the tab's presence in the tracker.
267         const SessionTab* tab = NULL;
268         if (synced_session_tracker_.LookupSessionTab(local_tag, tab_id, &tab)) {
269           found_tabs = true;
270           window_s.add_tab(tab_id);
271         }
272       }
273       // Only add a window if it contains valid tabs.
274       if (found_tabs) {
275         sync_pb::SessionWindow* header_window = header_s->add_window();
276         *header_window = window_s;
277
278         // Update this window's representation in the synced session tracker.
279         synced_session_tracker_.PutWindowInSession(local_tag, window_id);
280         PopulateSessionWindowFromSpecifics(
281             local_tag,
282             window_s,
283             base::Time::Now(),
284             current_session->windows[window_id],
285             &synced_session_tracker_);
286       }
287     }
288   }
289
290   local_tab_pool_.DeleteUnassociatedTabNodes();
291   // Free memory for closed windows and tabs.
292   synced_session_tracker_.CleanupSession(local_tag);
293
294   syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
295   syncer::WriteNode header_node(&trans);
296   if (header_node.InitByIdLookup(local_session_syncid_) !=
297           syncer::BaseNode::INIT_OK) {
298     if (error) {
299       *error = error_handler_->CreateAndUploadError(
300            FROM_HERE,
301            "Failed to load local session header node.",
302            model_type());
303     }
304     return false;
305   }
306   header_node.SetSessionSpecifics(specifics);
307   if (waiting_for_change_) QuitLoopForSubtleTesting();
308   return true;
309 }
310
311 // Static.
312 bool SessionModelAssociator::ShouldSyncWindow(
313     const SyncedWindowDelegate* window) {
314   if (window->IsApp())
315     return false;
316   return window->IsTypeTabbed() || window->IsTypePopup();
317 }
318
319 bool SessionModelAssociator::AssociateTabs(
320     const std::vector<SyncedTabDelegate*>& tabs,
321     syncer::SyncError* error) {
322   DCHECK(CalledOnValidThread());
323   for (std::vector<SyncedTabDelegate*>::const_iterator i = tabs.begin();
324        i != tabs.end();
325        ++i) {
326     if (!AssociateTab(*i, error))
327       return false;
328   }
329   if (waiting_for_change_) QuitLoopForSubtleTesting();
330   return true;
331 }
332
333 bool SessionModelAssociator::AssociateTab(SyncedTabDelegate* const tab,
334                                           syncer::SyncError* error) {
335   DCHECK(CalledOnValidThread());
336   DCHECK(tab->HasWebContents());
337   int tab_node_id(TabNodePool::kInvalidTabNodeID);
338   SessionID::id_type tab_id = tab->GetSessionId();
339   if (tab->IsBeingDestroyed()) {
340     // This tab is closing.
341     TabLinksMap::iterator tab_iter = local_tab_map_.find(tab_id);
342     if (tab_iter == local_tab_map_.end()) {
343       // We aren't tracking this tab (for example, sync setting page).
344       return true;
345     }
346     local_tab_pool_.FreeTabNode(tab_iter->second->tab_node_id());
347     local_tab_map_.erase(tab_iter);
348     return true;
349   }
350
351   if (!ShouldSyncTab(*tab))
352     return true;
353
354   TabLinksMap::iterator local_tab_map_iter = local_tab_map_.find(tab_id);
355   TabLink* tab_link = NULL;
356   if (local_tab_map_iter == local_tab_map_.end()) {
357     tab_node_id = tab->GetSyncId();
358     // if there is an old sync node for the tab, reuse it.
359     if (!local_tab_pool_.IsUnassociatedTabNode(tab_node_id)) {
360       // This is a new tab, get a sync node for it.
361       tab_node_id = local_tab_pool_.GetFreeTabNode();
362       if (tab_node_id == TabNodePool::kInvalidTabNodeID) {
363         if (error) {
364           *error = error_handler_->CreateAndUploadError(
365               FROM_HERE,
366               "Received invalid tab node from tab pool.",
367               model_type());
368         }
369         return false;
370       }
371       tab->SetSyncId(tab_node_id);
372     }
373     local_tab_pool_.AssociateTabNode(tab_node_id, tab_id);
374     tab_link = new TabLink(tab_node_id, tab);
375     local_tab_map_[tab_id] = make_linked_ptr<TabLink>(tab_link);
376   } else {
377     // This tab is already associated with a sync node, reuse it.
378     // Note: on some platforms the tab object may have changed, so we ensure
379     // the tab link is up to date.
380     tab_link = local_tab_map_iter->second.get();
381     local_tab_map_iter->second->set_tab(tab);
382   }
383   DCHECK(tab_link);
384   DCHECK_NE(tab_link->tab_node_id(), TabNodePool::kInvalidTabNodeID);
385
386   DVLOG(1) << "Reloading tab " << tab_id << " from window "
387            << tab->GetWindowId();
388   return WriteTabContentsToSyncModel(tab_link, error);
389 }
390
391 // static
392 GURL SessionModelAssociator::GetCurrentVirtualURL(
393     const SyncedTabDelegate& tab_delegate) {
394   const int current_index = tab_delegate.GetCurrentEntryIndex();
395   const int pending_index = tab_delegate.GetPendingEntryIndex();
396   const NavigationEntry* current_entry =
397       (current_index == pending_index) ?
398       tab_delegate.GetPendingEntry() :
399       tab_delegate.GetEntryAtIndex(current_index);
400   return current_entry->GetVirtualURL();
401 }
402
403 // static
404 GURL SessionModelAssociator::GetCurrentFaviconURL(
405     const SyncedTabDelegate& tab_delegate) {
406   const int current_index = tab_delegate.GetCurrentEntryIndex();
407   const int pending_index = tab_delegate.GetPendingEntryIndex();
408   const NavigationEntry* current_entry =
409       (current_index == pending_index) ?
410       tab_delegate.GetPendingEntry() :
411       tab_delegate.GetEntryAtIndex(current_index);
412   return (current_entry->GetFavicon().valid ?
413           current_entry->GetFavicon().url :
414           GURL());
415 }
416
417 bool SessionModelAssociator::WriteTabContentsToSyncModel(
418     TabLink* tab_link,
419     syncer::SyncError* error) {
420   DCHECK(CalledOnValidThread());
421   const SyncedTabDelegate& tab_delegate = *(tab_link->tab());
422   int tab_node_id = tab_link->tab_node_id();
423   GURL old_tab_url = tab_link->url();
424   const GURL new_url = GetCurrentVirtualURL(tab_delegate);
425   DVLOG(1) << "Local tab " << tab_delegate.GetSessionId()
426            << " now has URL " << new_url.spec();
427
428   SessionTab* session_tab = NULL;
429   {
430     syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
431     syncer::WriteNode tab_node(&trans);
432     if (tab_node.InitByClientTagLookup(
433             syncer::SESSIONS,
434             TabNodePool::TabIdToTag(current_machine_tag_, tab_node_id)) !=
435         syncer::BaseNode::INIT_OK) {
436       if (error) {
437         *error = error_handler_->CreateAndUploadError(
438             FROM_HERE,
439             "Failed to look up local tab node",
440             model_type());
441       }
442       return false;
443     }
444
445     // Load the last stored version of this tab so we can compare changes. If
446     // this is a new tab, session_tab will be a new, blank SessionTab object.
447     sync_pb::SessionSpecifics specifics = tab_node.GetSessionSpecifics();
448     const int s_tab_node_id(specifics.tab_node_id());
449     DCHECK_EQ(tab_node_id, s_tab_node_id);
450     session_tab =
451         synced_session_tracker_.GetTab(GetCurrentMachineTag(),
452                                        tab_delegate.GetSessionId(),
453                                        specifics.tab_node_id());
454     SetSessionTabFromDelegate(tab_delegate, base::Time::Now(), session_tab);
455     sync_pb::SessionTab tab_s = session_tab->ToSyncData();
456
457     if (new_url == old_tab_url) {
458       // Load the old specifics and copy over the favicon data if needed.
459       // TODO(zea): remove this once favicon sync is enabled as a separate type.
460       tab_s.set_favicon(specifics.tab().favicon());
461       tab_s.set_favicon_source(specifics.tab().favicon_source());
462       tab_s.set_favicon_type(specifics.tab().favicon_type());
463     }
464     // Retain the base SessionSpecifics data (tag, tab_node_id, etc.), and just
465     // write the new SessionTabSpecifics.
466     specifics.mutable_tab()->CopyFrom(tab_s);
467
468     // Write into the actual sync model.
469     tab_node.SetSessionSpecifics(specifics);
470   }
471
472   // Trigger the favicon load if needed. We do this outside the write
473   // transaction to avoid jank.
474   tab_link->set_url(new_url);
475   if (new_url != old_tab_url) {
476     favicon_cache_.OnFaviconVisited(new_url,
477                                     GetCurrentFaviconURL(tab_delegate));
478   }
479
480   // Update our last modified time.
481   synced_session_tracker_.GetSession(GetCurrentMachineTag())->modified_time =
482       base::Time::Now();
483
484   return true;
485 }
486
487 // static
488 void SessionModelAssociator::SetSessionTabFromDelegate(
489     const SyncedTabDelegate& tab_delegate,
490     base::Time mtime,
491     SessionTab* session_tab) {
492   DCHECK(session_tab);
493   session_tab->window_id.set_id(tab_delegate.GetWindowId());
494   session_tab->tab_id.set_id(tab_delegate.GetSessionId());
495   session_tab->tab_visual_index = 0;
496   session_tab->current_navigation_index = tab_delegate.GetCurrentEntryIndex();
497   session_tab->pinned = tab_delegate.IsPinned();
498   session_tab->extension_app_id = tab_delegate.GetExtensionAppId();
499   session_tab->user_agent_override.clear();
500   session_tab->timestamp = mtime;
501   const int current_index = tab_delegate.GetCurrentEntryIndex();
502   const int pending_index = tab_delegate.GetPendingEntryIndex();
503   const int min_index = std::max(0, current_index - kMaxSyncNavigationCount);
504   const int max_index = std::min(current_index + kMaxSyncNavigationCount,
505                                  tab_delegate.GetEntryCount());
506   bool is_managed = tab_delegate.ProfileIsManaged();
507   session_tab->navigations.clear();
508
509 #if !defined(OS_ANDROID)
510   // For getting navigation time in network time.
511   NavigationTimeHelper* nav_time_helper =
512       tab_delegate.HasWebContents() ?
513           NavigationTimeHelper::FromWebContents(tab_delegate.GetWebContents()) :
514           NULL;
515 #endif
516
517   for (int i = min_index; i < max_index; ++i) {
518     const NavigationEntry* entry = (i == pending_index) ?
519         tab_delegate.GetPendingEntry() : tab_delegate.GetEntryAtIndex(i);
520     DCHECK(entry);
521     if (!entry->GetVirtualURL().is_valid())
522       continue;
523
524     scoped_ptr<content::NavigationEntry> network_time_entry(
525         content::NavigationEntry::Create(*entry));
526 #if !defined(OS_ANDROID)
527     if (nav_time_helper) {
528       network_time_entry->SetTimestamp(
529           nav_time_helper->GetNavigationTime(entry));
530     }
531 #endif
532
533     session_tab->navigations.push_back(
534         SerializedNavigationEntry::FromNavigationEntry(i, *network_time_entry));
535     if (is_managed) {
536       session_tab->navigations.back().set_blocked_state(
537           SerializedNavigationEntry::STATE_ALLOWED);
538     }
539   }
540
541   if (is_managed) {
542     const std::vector<const NavigationEntry*>& blocked_navigations =
543         *tab_delegate.GetBlockedNavigations();
544     int offset = session_tab->navigations.size();
545     for (size_t i = 0; i < blocked_navigations.size(); ++i) {
546       session_tab->navigations.push_back(
547           SerializedNavigationEntry::FromNavigationEntry(
548               i + offset, *blocked_navigations[i]));
549       // Blocked navigations already use network navigation time.
550       session_tab->navigations.back().set_blocked_state(
551           SerializedNavigationEntry::STATE_BLOCKED);
552       // TODO(bauerb): Add categories
553     }
554   }
555   session_tab->session_storage_persistent_id.clear();
556 }
557
558 void SessionModelAssociator::FaviconsUpdated(
559     const std::set<GURL>& urls) {
560   // TODO(zea): consider a separate container for tabs with outstanding favicon
561   // loads so we don't have to iterate through all tabs comparing urls.
562   for (std::set<GURL>::const_iterator i = urls.begin(); i != urls.end(); ++i) {
563     for (TabLinksMap::iterator tab_iter = local_tab_map_.begin();
564          tab_iter != local_tab_map_.end();
565          ++tab_iter) {
566       if (tab_iter->second->url() == *i)
567         favicon_cache_.OnPageFaviconUpdated(*i);
568     }
569   }
570 }
571
572 syncer::SyncError SessionModelAssociator::AssociateModels(
573     syncer::SyncMergeResult* local_merge_result,
574     syncer::SyncMergeResult* syncer_merge_result) {
575   DCHECK(CalledOnValidThread());
576   syncer::SyncError error;
577
578   // Ensure that we disassociated properly, otherwise memory might leak.
579   DCHECK(synced_session_tracker_.Empty());
580   DCHECK_EQ(0U, local_tab_pool_.Capacity());
581
582   local_session_syncid_ = syncer::kInvalidId;
583
584   scoped_ptr<DeviceInfo> local_device_info(sync_service_->GetLocalDeviceInfo());
585
586 #if defined(OS_ANDROID)
587   std::string transaction_tag;
588 #endif
589   // Read any available foreign sessions and load any session data we may have.
590   // If we don't have any local session data in the db, create a header node.
591   {
592     syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
593
594     syncer::ReadNode root(&trans);
595     if (root.InitByTagLookup(syncer::ModelTypeToRootTag(model_type())) !=
596             syncer::BaseNode::INIT_OK) {
597       return error_handler_->CreateAndUploadError(
598           FROM_HERE,
599           kNoSessionsFolderError,
600           model_type());
601     }
602
603     // Make sure we have a machine tag.
604     if (current_machine_tag_.empty())
605       InitializeCurrentMachineTag(&trans);
606     if (local_device_info) {
607       current_session_name_ = local_device_info->client_name();
608     } else {
609       return error_handler_->CreateAndUploadError(
610           FROM_HERE,
611           "Failed to get device info.",
612           model_type());
613     }
614     synced_session_tracker_.SetLocalSessionTag(current_machine_tag_);
615     if (!UpdateAssociationsFromSyncModel(root, &trans, &error)) {
616       DCHECK(error.IsSet());
617       return error;
618     }
619
620     if (local_session_syncid_ == syncer::kInvalidId) {
621       // The sync db didn't have a header node for us, we need to create one.
622       syncer::WriteNode write_node(&trans);
623       syncer::WriteNode::InitUniqueByCreationResult result =
624           write_node.InitUniqueByCreation(SESSIONS, root, current_machine_tag_);
625       if (result != syncer::WriteNode::INIT_SUCCESS) {
626         // If we can't look it up, and we can't create it, chances are there's
627         // a pre-existing node that has encryption issues. But, since we can't
628         // load the item, we can't remove it, and error out at this point.
629         return error_handler_->CreateAndUploadError(
630             FROM_HERE,
631             "Failed to create sessions header sync node.",
632             model_type());
633       }
634
635       // Write the initial values to the specifics so that in case of a crash or
636       // error we don't persist a half-written node.
637       write_node.SetTitle(base::UTF8ToWide(current_machine_tag_));
638       sync_pb::SessionSpecifics base_specifics;
639       base_specifics.set_session_tag(current_machine_tag_);
640       sync_pb::SessionHeader* header_s = base_specifics.mutable_header();
641       header_s->set_client_name(current_session_name_);
642       header_s->set_device_type(DeviceInfo::GetLocalDeviceType());
643       write_node.SetSessionSpecifics(base_specifics);
644
645       local_session_syncid_ = write_node.GetId();
646     }
647 #if defined(OS_ANDROID)
648     transaction_tag = GetMachineTagFromTransaction(&trans);
649 #endif
650   }
651 #if defined(OS_ANDROID)
652   // We need to delete foreign sessions after giving up our
653   // syncer::WriteTransaction, since DeleteForeignSession(std::string&) uses
654   // its own syncer::WriteTransaction.
655   if (current_machine_tag_.compare(transaction_tag) != 0)
656     DeleteForeignSession(transaction_tag);
657 #endif
658
659   // Check if anything has changed on the client side.
660   if (!UpdateSyncModelDataFromClient(&error)) {
661     DCHECK(error.IsSet());
662     return error;
663   }
664
665   DVLOG(1) << "Session models associated.";
666   DCHECK(!error.IsSet());
667   return error;
668 }
669
670 syncer::SyncError SessionModelAssociator::DisassociateModels() {
671   DCHECK(CalledOnValidThread());
672   DVLOG(1) << "Disassociating local session " << GetCurrentMachineTag();
673   synced_session_tracker_.Clear();
674   local_tab_map_.clear();
675   local_tab_pool_.Clear();
676   local_session_syncid_ = syncer::kInvalidId;
677   current_machine_tag_ = "";
678   current_session_name_ = "";
679
680   // There is no local model stored with which to disassociate, just notify
681   // foreign session handlers.
682   content::NotificationService::current()->Notify(
683       chrome::NOTIFICATION_FOREIGN_SESSION_DISABLED,
684       content::Source<Profile>(sync_service_->profile()),
685       content::NotificationService::NoDetails());
686   return syncer::SyncError();
687 }
688
689 void SessionModelAssociator::InitializeCurrentMachineTag(
690     syncer::WriteTransaction* trans) {
691   DCHECK(CalledOnValidThread());
692   DCHECK(current_machine_tag_.empty());
693   std::string persisted_guid;
694   sync_driver::SyncPrefs prefs(profile_->GetPrefs());
695   persisted_guid = prefs.GetSyncSessionsGUID();
696   if (!persisted_guid.empty()) {
697     current_machine_tag_ = persisted_guid;
698     DVLOG(1) << "Restoring persisted session sync guid: "
699              << persisted_guid;
700   } else {
701     current_machine_tag_ = GetMachineTagFromTransaction(trans);
702     DVLOG(1) << "Creating session sync guid: " << current_machine_tag_;
703     prefs.SetSyncSessionsGUID(current_machine_tag_);
704   }
705
706   local_tab_pool_.SetMachineTag(current_machine_tag_);
707 }
708
709 bool SessionModelAssociator::GetSyncedFaviconForPageURL(
710     const std::string& page_url,
711     scoped_refptr<base::RefCountedMemory>* favicon_png) const {
712   return favicon_cache_.GetSyncedFaviconForPageURL(GURL(page_url), favicon_png);
713 }
714
715 scoped_ptr<browser_sync::DeviceInfo>
716     SessionModelAssociator::GetDeviceInfoForSessionTag(
717         const std::string& session_tag) {
718   std::string client_id = GetClientIdFromSessionTag(session_tag);
719   return sync_service_->GetDeviceInfo(client_id);
720 }
721
722 bool SessionModelAssociator::UpdateAssociationsFromSyncModel(
723     const syncer::ReadNode& root,
724     syncer::WriteTransaction* trans,
725     syncer::SyncError* error) {
726   DCHECK(CalledOnValidThread());
727   DCHECK(local_tab_pool_.Empty());
728   DCHECK_EQ(local_session_syncid_, syncer::kInvalidId);
729
730   // Iterate through the nodes and associate any foreign sessions.
731   int64 id = root.GetFirstChildId();
732   while (id != syncer::kInvalidId) {
733     syncer::WriteNode sync_node(trans);
734     if (sync_node.InitByIdLookup(id) != syncer::BaseNode::INIT_OK) {
735       if (error) {
736         *error = error_handler_->CreateAndUploadError(
737             FROM_HERE,
738             "Failed to load sync node",
739             model_type());
740       }
741       return false;
742     }
743     int64 next_id = sync_node.GetSuccessorId();
744
745     const sync_pb::SessionSpecifics& specifics =
746         sync_node.GetSessionSpecifics();
747     const base::Time& modification_time = sync_node.GetModificationTime();
748     if (specifics.session_tag().empty() ||
749            (specifics.has_tab() && (!specifics.has_tab_node_id() ||
750             !specifics.tab().has_tab_id()))) {
751       // This is a corrupted node. Just delete it.
752       LOG(WARNING) << "Found invalid session node, deleting.";
753       sync_node.Tombstone();
754     } else if (specifics.session_tag() != GetCurrentMachineTag()) {
755       AssociateForeignSpecifics(specifics, modification_time);
756     } else {
757       // This is previously stored local session information.
758       if (specifics.has_header() &&
759           local_session_syncid_ == syncer::kInvalidId) {
760         // This is our previous header node, reuse it.
761         local_session_syncid_ = id;
762         if (specifics.header().has_client_name()) {
763           current_session_name_ = specifics.header().client_name();
764         }
765       } else {
766         if (specifics.has_header() || !specifics.has_tab()) {
767           LOG(WARNING) << "Found invalid session node, deleting.";
768           sync_node.Tombstone();
769         } else {
770           // This is a valid old tab node, add it to the pool so it can be
771           // reused for reassociation.
772           local_tab_pool_.AddTabNode(specifics.tab_node_id());
773         }
774       }
775     }
776     id = next_id;
777   }
778
779   return true;
780 }
781
782 void SessionModelAssociator::AssociateForeignSpecifics(
783     const sync_pb::SessionSpecifics& specifics,
784     const base::Time& modification_time) {
785   DCHECK(CalledOnValidThread());
786   std::string foreign_session_tag = specifics.session_tag();
787   if (foreign_session_tag == GetCurrentMachineTag() && !setup_for_test_)
788     return;
789
790   SyncedSession* foreign_session =
791       synced_session_tracker_.GetSession(foreign_session_tag);
792   if (specifics.has_header()) {
793     // Read in the header data for this foreign session.
794     // Header data contains window information and ordered tab id's for each
795     // window.
796
797     // Load (or create) the SyncedSession object for this client.
798     const sync_pb::SessionHeader& header = specifics.header();
799     PopulateSessionHeaderFromSpecifics(header,
800                                        modification_time,
801                                        foreign_session);
802
803     // Reset the tab/window tracking for this session (must do this before
804     // we start calling PutWindowInSession and PutTabInWindow so that all
805     // unused tabs/windows get cleared by the CleanupSession(...) call).
806     synced_session_tracker_.ResetSessionTracking(foreign_session_tag);
807
808     // Process all the windows and their tab information.
809     int num_windows = header.window_size();
810     DVLOG(1) << "Associating " << foreign_session_tag << " with "
811              << num_windows << " windows.";
812     for (int i = 0; i < num_windows; ++i) {
813       const sync_pb::SessionWindow& window_s = header.window(i);
814       SessionID::id_type window_id = window_s.window_id();
815       synced_session_tracker_.PutWindowInSession(foreign_session_tag,
816                                                  window_id);
817       PopulateSessionWindowFromSpecifics(foreign_session_tag,
818                                          window_s,
819                                          modification_time,
820                                          foreign_session->windows[window_id],
821                                          &synced_session_tracker_);
822     }
823
824     // Delete any closed windows and unused tabs as necessary.
825     synced_session_tracker_.CleanupSession(foreign_session_tag);
826   } else if (specifics.has_tab()) {
827     const sync_pb::SessionTab& tab_s = specifics.tab();
828     SessionID::id_type tab_id = tab_s.tab_id();
829     SessionTab* tab =
830         synced_session_tracker_.GetTab(foreign_session_tag,
831                                        tab_id,
832                                        specifics.tab_node_id());
833
834     // Update SessionTab based on protobuf.
835     tab->SetFromSyncData(tab_s, modification_time);
836
837     // If a favicon or favicon urls are present, load them into the in-memory
838     // favicon cache.
839     LoadForeignTabFavicon(tab_s);
840
841     // Update the last modified time.
842     if (foreign_session->modified_time < modification_time)
843       foreign_session->modified_time = modification_time;
844   } else {
845     LOG(WARNING) << "Ignoring foreign session node with missing header/tab "
846                  << "fields and tag " << foreign_session_tag << ".";
847   }
848 }
849
850 bool SessionModelAssociator::DisassociateForeignSession(
851     const std::string& foreign_session_tag) {
852   DCHECK(CalledOnValidThread());
853   if (foreign_session_tag == GetCurrentMachineTag()) {
854     DVLOG(1) << "Local session deleted! Doing nothing until a navigation is "
855              << "triggered.";
856     return false;
857   }
858   DVLOG(1) << "Disassociating session " << foreign_session_tag;
859   return synced_session_tracker_.DeleteSession(foreign_session_tag);
860 }
861
862 // Static
863 void SessionModelAssociator::PopulateSessionHeaderFromSpecifics(
864     const sync_pb::SessionHeader& header_specifics,
865     base::Time mtime,
866     SyncedSession* session_header) {
867   if (header_specifics.has_client_name()) {
868     session_header->session_name = header_specifics.client_name();
869   }
870   if (header_specifics.has_device_type()) {
871     switch (header_specifics.device_type()) {
872       case sync_pb::SyncEnums_DeviceType_TYPE_WIN:
873         session_header->device_type = SyncedSession::TYPE_WIN;
874         break;
875       case sync_pb::SyncEnums_DeviceType_TYPE_MAC:
876         session_header->device_type = SyncedSession::TYPE_MACOSX;
877         break;
878       case sync_pb::SyncEnums_DeviceType_TYPE_LINUX:
879         session_header->device_type = SyncedSession::TYPE_LINUX;
880         break;
881       case sync_pb::SyncEnums_DeviceType_TYPE_CROS:
882         session_header->device_type = SyncedSession::TYPE_CHROMEOS;
883         break;
884       case sync_pb::SyncEnums_DeviceType_TYPE_PHONE:
885         session_header->device_type = SyncedSession::TYPE_PHONE;
886         break;
887       case sync_pb::SyncEnums_DeviceType_TYPE_TABLET:
888         session_header->device_type = SyncedSession::TYPE_TABLET;
889         break;
890       case sync_pb::SyncEnums_DeviceType_TYPE_OTHER:
891         // Intentionally fall-through
892       default:
893         session_header->device_type = SyncedSession::TYPE_OTHER;
894         break;
895     }
896   }
897   session_header->modified_time = mtime;
898 }
899
900 // Static
901 void SessionModelAssociator::PopulateSessionWindowFromSpecifics(
902     const std::string& session_tag,
903     const sync_pb::SessionWindow& specifics,
904     base::Time mtime,
905     SessionWindow* session_window,
906     SyncedSessionTracker* tracker) {
907   if (specifics.has_window_id())
908     session_window->window_id.set_id(specifics.window_id());
909   if (specifics.has_selected_tab_index())
910     session_window->selected_tab_index = specifics.selected_tab_index();
911   if (specifics.has_browser_type()) {
912     if (specifics.browser_type() ==
913         sync_pb::SessionWindow_BrowserType_TYPE_TABBED) {
914       session_window->type = 1;
915     } else {
916       session_window->type = 2;
917     }
918   }
919   session_window->timestamp = mtime;
920   session_window->tabs.resize(specifics.tab_size(), NULL);
921   for (int i = 0; i < specifics.tab_size(); i++) {
922     SessionID::id_type tab_id = specifics.tab(i);
923     tracker->PutTabInWindow(session_tag,
924                             session_window->window_id.id(),
925                             tab_id,
926                             i);
927   }
928 }
929
930 void SessionModelAssociator::LoadForeignTabFavicon(
931     const sync_pb::SessionTab& tab) {
932   // First go through and iterate over all the navigations, checking if any
933   // have valid favicon urls.
934   for (int i = 0; i < tab.navigation_size(); ++i) {
935     if (!tab.navigation(i).favicon_url().empty()) {
936       const std::string& page_url = tab.navigation(i).virtual_url();
937       const std::string& favicon_url = tab.navigation(i).favicon_url();
938       favicon_cache_.OnReceivedSyncFavicon(GURL(page_url),
939                                            GURL(favicon_url),
940                                            std::string(),
941                                            syncer::TimeToProtoTime(
942                                                base::Time::Now()));
943     }
944   }
945
946   // Then go through and check for any legacy favicon data.
947   if (!tab.has_favicon() || tab.favicon().empty())
948     return;
949   if (!tab.has_favicon_type() ||
950       tab.favicon_type() != sync_pb::SessionTab::TYPE_WEB_FAVICON) {
951     DVLOG(1) << "Ignoring non-web favicon.";
952     return;
953   }
954   if (tab.navigation_size() == 0)
955     return;
956   int selected_index = tab.current_navigation_index();
957   selected_index = std::max(
958       0,
959       std::min(selected_index,
960                static_cast<int>(tab.navigation_size() - 1)));
961   GURL navigation_url(tab.navigation(selected_index).virtual_url());
962   if (!navigation_url.is_valid())
963     return;
964   GURL favicon_source(tab.favicon_source());
965   if (!favicon_source.is_valid())
966     return;
967
968   const std::string& favicon = tab.favicon();
969   DVLOG(1) << "Storing synced favicon for url " << navigation_url.spec()
970            << " with size " << favicon.size() << " bytes.";
971   favicon_cache_.OnReceivedSyncFavicon(navigation_url,
972                                        favicon_source,
973                                        favicon,
974                                        syncer::TimeToProtoTime(
975                                            base::Time::Now()));
976 }
977
978 bool SessionModelAssociator::UpdateSyncModelDataFromClient(
979     syncer::SyncError* error) {
980   DCHECK(CalledOnValidThread());
981
982   // Associate all open windows and their tabs.
983   return AssociateWindows(true, error);
984 }
985
986 void SessionModelAssociator::AttemptSessionsDataRefresh() const {
987   DVLOG(1) << "Triggering sync refresh for sessions datatype.";
988   const syncer::ModelTypeSet types(syncer::SESSIONS);
989   content::NotificationService::current()->Notify(
990       chrome::NOTIFICATION_SYNC_REFRESH_LOCAL,
991       content::Source<Profile>(profile_),
992       content::Details<const syncer::ModelTypeSet>(&types));
993 }
994
995 bool SessionModelAssociator::GetLocalSession(
996     const SyncedSession* * local_session) {
997   DCHECK(CalledOnValidThread());
998   if (current_machine_tag_.empty())
999     return false;
1000   *local_session = synced_session_tracker_.GetSession(GetCurrentMachineTag());
1001   return true;
1002 }
1003
1004 bool SessionModelAssociator::GetAllForeignSessions(
1005     std::vector<const SyncedSession*>* sessions) {
1006   DCHECK(CalledOnValidThread());
1007   return synced_session_tracker_.LookupAllForeignSessions(sessions);
1008 }
1009
1010 bool SessionModelAssociator::GetForeignSession(
1011     const std::string& tag,
1012     std::vector<const SessionWindow*>* windows) {
1013   DCHECK(CalledOnValidThread());
1014   return synced_session_tracker_.LookupSessionWindows(tag, windows);
1015 }
1016
1017 bool SessionModelAssociator::GetForeignTab(
1018     const std::string& tag,
1019     const SessionID::id_type tab_id,
1020     const SessionTab** tab) {
1021   DCHECK(CalledOnValidThread());
1022   const SessionTab* synced_tab = NULL;
1023   bool success = synced_session_tracker_.LookupSessionTab(tag,
1024                                                           tab_id,
1025                                                           &synced_tab);
1026   if (success)
1027     *tab = synced_tab;
1028   return success;
1029 }
1030
1031 void SessionModelAssociator::DeleteStaleSessions() {
1032   DCHECK(CalledOnValidThread());
1033   std::vector<const SyncedSession*> sessions;
1034   if (!GetAllForeignSessions(&sessions))
1035     return;  // No foreign sessions.
1036
1037   // Iterate through all the sessions and delete any with age older than
1038   // |stale_session_threshold_days_|.
1039   for (std::vector<const SyncedSession*>::const_iterator iter =
1040            sessions.begin(); iter != sessions.end(); ++iter) {
1041     const SyncedSession* session = *iter;
1042     int session_age_in_days =
1043         (base::Time::Now() - session->modified_time).InDays();
1044     std::string session_tag = session->session_tag;
1045     if (session_age_in_days > 0 &&  // If false, local clock is not trustworty.
1046         static_cast<size_t>(session_age_in_days) >
1047             stale_session_threshold_days_) {
1048       DVLOG(1) << "Found stale session " << session_tag
1049                << " with age " << session_age_in_days << ", deleting.";
1050       DeleteForeignSession(session_tag);
1051     }
1052   }
1053 }
1054
1055 void SessionModelAssociator::SetStaleSessionThreshold(
1056     size_t stale_session_threshold_days) {
1057   DCHECK(CalledOnValidThread());
1058   if (stale_session_threshold_days_ == 0) {
1059     NOTREACHED() << "Attempted to set invalid stale session threshold.";
1060     return;
1061   }
1062   stale_session_threshold_days_ = stale_session_threshold_days;
1063   // TODO(zea): maybe make this preference-based? Might be nice to let users be
1064   // able to modify this once and forget about it. At the moment, if we want a
1065   // different threshold we will need to call this everytime we create a new
1066   // model associator and before we AssociateModels (probably from DTC).
1067 }
1068
1069 void SessionModelAssociator::DeleteForeignSession(const std::string& tag) {
1070   DCHECK(CalledOnValidThread());
1071   if (tag == GetCurrentMachineTag()) {
1072     LOG(ERROR) << "Attempting to delete local session. This is not currently "
1073                << "supported.";
1074     return;
1075   }
1076
1077   if (!DisassociateForeignSession(tag)) {
1078     // We don't have any data for this session, our work here is done!
1079     return;
1080   }
1081
1082   syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
1083   syncer::ReadNode root(&trans);
1084   if (root.InitByTagLookup(syncer::ModelTypeToRootTag(syncer::SESSIONS)) !=
1085                            syncer::BaseNode::INIT_OK) {
1086     LOG(ERROR) << kNoSessionsFolderError;
1087     return;
1088   }
1089   int64 id = root.GetFirstChildId();
1090   while (id != syncer::kInvalidId) {
1091     syncer::WriteNode sync_node(&trans);
1092     if (sync_node.InitByIdLookup(id) != syncer::BaseNode::INIT_OK) {
1093       LOG(ERROR) << "Failed to fetch sync node for id " << id;
1094       continue;
1095     }
1096     id = sync_node.GetSuccessorId();
1097     const sync_pb::SessionSpecifics& specifics =
1098         sync_node.GetSessionSpecifics();
1099     if (specifics.session_tag() == tag)
1100       sync_node.Tombstone();
1101   }
1102
1103   content::NotificationService::current()->Notify(
1104       chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED,
1105       content::Source<Profile>(sync_service_->profile()),
1106       content::NotificationService::NoDetails());
1107 }
1108
1109 bool SessionModelAssociator::IsValidTab(const SyncedTabDelegate& tab) const {
1110   if ((!sync_service_ || tab.profile() != sync_service_->profile()) &&
1111       !setup_for_test_) {
1112     return false;
1113   }
1114   const SyncedWindowDelegate* window =
1115       SyncedWindowDelegate::FindSyncedWindowDelegateWithId(
1116           tab.GetWindowId());
1117   if (!window && !setup_for_test_)
1118     return false;
1119   return true;
1120 }
1121
1122 bool SessionModelAssociator::TabHasValidEntry(
1123     const SyncedTabDelegate& tab) const {
1124   if (tab.ProfileIsManaged() && tab.GetBlockedNavigations()->size() > 0)
1125     return true;
1126
1127   int entry_count = tab.GetEntryCount();
1128   if (entry_count == 0)
1129     return false;  // This deliberately ignores a new pending entry.
1130
1131   int pending_index = tab.GetPendingEntryIndex();
1132   bool found_valid_url = false;
1133   for (int i = 0; i < entry_count; ++i) {
1134     const content::NavigationEntry* entry = (i == pending_index) ?
1135        tab.GetPendingEntry() : tab.GetEntryAtIndex(i);
1136     if (!entry)
1137       return false;
1138     const GURL& virtual_url = entry->GetVirtualURL();
1139     if (virtual_url.is_valid() &&
1140         !virtual_url.SchemeIs(content::kChromeUIScheme) &&
1141         !virtual_url.SchemeIs(chrome::kChromeNativeScheme) &&
1142         !virtual_url.SchemeIsFile()) {
1143       found_valid_url = true;
1144     }
1145   }
1146   return found_valid_url;
1147 }
1148
1149 // If this functionality changes, browser_sync::ShouldSyncSessionTab should be
1150 // modified to match.
1151 bool SessionModelAssociator::ShouldSyncTab(const SyncedTabDelegate& tab) const {
1152   DCHECK(CalledOnValidThread());
1153   if (!IsValidTab(tab))
1154     return false;
1155   return TabHasValidEntry(tab);
1156 }
1157
1158 void SessionModelAssociator::QuitLoopForSubtleTesting() {
1159   if (waiting_for_change_) {
1160     DVLOG(1) << "Quitting base::MessageLoop for test.";
1161     waiting_for_change_ = false;
1162     test_weak_factory_.InvalidateWeakPtrs();
1163     base::MessageLoop::current()->Quit();
1164   }
1165 }
1166
1167 FaviconCache* SessionModelAssociator::GetFaviconCache() {
1168   return &favicon_cache_;
1169 }
1170
1171 void SessionModelAssociator::BlockUntilLocalChangeForTest(
1172     base::TimeDelta timeout) {
1173   if (test_weak_factory_.HasWeakPtrs())
1174     return;
1175   waiting_for_change_ = true;
1176   base::MessageLoop::current()->PostDelayedTask(
1177       FROM_HERE,
1178       base::Bind(&SessionModelAssociator::QuitLoopForSubtleTesting,
1179                  test_weak_factory_.GetWeakPtr()),
1180       timeout);
1181 }
1182
1183 bool SessionModelAssociator::CryptoReadyIfNecessary() {
1184   // We only access the cryptographer while holding a transaction.
1185   syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare());
1186   const syncer::ModelTypeSet encrypted_types = trans.GetEncryptedTypes();
1187   return !encrypted_types.Has(SESSIONS) ||
1188          sync_service_->IsCryptographerReady(&trans);
1189 }
1190
1191 void SessionModelAssociator::UpdateTabIdIfNecessary(
1192     int tab_node_id,
1193     SessionID::id_type new_tab_id) {
1194   DCHECK_NE(tab_node_id, TabNodePool::kInvalidTabNodeID);
1195   SessionID::id_type old_tab_id =
1196       local_tab_pool_.GetTabIdFromTabNodeId(tab_node_id);
1197   if (old_tab_id != new_tab_id) {
1198     // Rewrite tab id if required.
1199     syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
1200     syncer::WriteNode tab_node(&trans);
1201     if (tab_node.InitByClientTagLookup(syncer::SESSIONS,
1202             TabNodePool::TabIdToTag(current_machine_tag_, tab_node_id)) ==
1203                 syncer::BaseNode::INIT_OK) {
1204       sync_pb::SessionSpecifics session_specifics =
1205           tab_node.GetSessionSpecifics();
1206       DCHECK(session_specifics.has_tab());
1207       if (session_specifics.has_tab()) {
1208         sync_pb::SessionTab* tab_s = session_specifics.mutable_tab();
1209         tab_s->set_tab_id(new_tab_id);
1210         tab_node.SetSessionSpecifics(session_specifics);
1211         // Update tab node pool with the new association.
1212         local_tab_pool_.ReassociateTabNode(tab_node_id, new_tab_id);
1213       }
1214     }
1215   }
1216 }
1217
1218 }  // namespace browser_sync