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/safe_numerics.h"
15#include "base/strings/utf_string_conversions.h"
16#include "base/threading/sequenced_worker_pool.h"
17#include "chrome/browser/chrome_notification_types.h"
18#include "chrome/browser/favicon/favicon_service_factory.h"
19#include "chrome/browser/history/history_service.h"
20#if !defined(OS_ANDROID)
21#include "chrome/browser/network_time/navigation_time_helper.h"
22#endif
23#include "chrome/browser/prefs/pref_service_syncable.h"
24#include "chrome/browser/profiles/profile.h"
25#include "chrome/browser/sessions/session_id.h"
26#include "chrome/browser/sync/glue/device_info.h"
27#include "chrome/browser/sync/glue/synced_device_tracker.h"
28#include "chrome/browser/sync/glue/synced_session.h"
29#include "chrome/browser/sync/glue/synced_tab_delegate.h"
30#include "chrome/browser/sync/glue/synced_window_delegate.h"
31#include "chrome/browser/sync/profile_sync_service.h"
32#include "chrome/common/chrome_switches.h"
33#include "chrome/common/pref_names.h"
34#include "chrome/common/url_constants.h"
35#include "components/sessions/serialized_navigation_entry.h"
36#include "components/user_prefs/pref_registry_syncable.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
59using content::BrowserThread;
60using content::NavigationEntry;
61using prefs::kSyncSessionsGUID;
62using sessions::SerializedNavigationEntry;
63using syncer::SESSIONS;
64
65namespace {
66
67std::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_|.
73std::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).
82std::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
97namespace browser_sync {
98
99namespace {
100static 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.
105static 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.
109static const size_t kDefaultStaleSessionThresholdDays = 14;  // 2 weeks.
110
111// Maximum number of favicons to sync.
112// TODO(zea): pull this from the server.
113static const int kMaxSyncFavicons = 200;
114
115}  // namespace
116
117SessionModelAssociator::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
136SessionModelAssociator::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
154SessionModelAssociator::~SessionModelAssociator() {
155  DCHECK(CalledOnValidThread());
156}
157
158bool 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
175int64 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
184bool 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.
313bool SessionModelAssociator::ShouldSyncWindow(
314    const SyncedWindowDelegate* window) {
315  if (window->IsApp())
316    return false;
317  return window->IsTypeTabbed() || window->IsTypePopup();
318}
319
320bool 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
334bool 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
393GURL 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
405GURL 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
418bool 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
489void 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
559void 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
573syncer::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(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
671syncer::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
690void 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
710bool 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
716scoped_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
723bool 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
783void 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
851bool 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
864void 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
902void 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
931void 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
979bool 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
987void 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
996bool 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
1005bool SessionModelAssociator::GetAllForeignSessions(
1006    std::vector<const SyncedSession*>* sessions) {
1007  DCHECK(CalledOnValidThread());
1008  return synced_session_tracker_.LookupAllForeignSessions(sessions);
1009}
1010
1011bool 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
1018bool 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
1032void 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
1056void 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
1070void 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
1110bool 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
1123bool 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(chrome::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.
1152bool SessionModelAssociator::ShouldSyncTab(const SyncedTabDelegate& tab) const {
1153  DCHECK(CalledOnValidThread());
1154  if (!IsValidTab(tab))
1155    return false;
1156  return TabHasValidEntry(tab);
1157}
1158
1159void 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
1168FaviconCache* SessionModelAssociator::GetFaviconCache() {
1169  return &favicon_cache_;
1170}
1171
1172void 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
1184bool 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
1192void 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
1220