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