Home | History | Annotate | Download | only in glue
      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 
     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(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(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.
   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
   1220