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