Home | History | Annotate | Download | only in glue
      1 // Copyright (c) 2011 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 <utility>
      9 
     10 #include "base/logging.h"
     11 #include "chrome/browser/extensions/extension_tab_helper.h"
     12 #include "chrome/browser/profiles/profile.h"
     13 #include "chrome/browser/sync/profile_sync_service.h"
     14 #include "chrome/browser/sync/syncable/syncable.h"
     15 #include "chrome/browser/tabs/tab_strip_model.h"
     16 #include "chrome/browser/ui/browser_list.h"
     17 #include "chrome/browser/ui/browser_window.h"
     18 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
     19 #include "chrome/common/extensions/extension.h"
     20 #include "chrome/common/url_constants.h"
     21 #include "content/browser/tab_contents/navigation_controller.h"
     22 #include "content/browser/tab_contents/navigation_entry.h"
     23 #include "content/common/notification_details.h"
     24 #include "content/common/notification_service.h"
     25 
     26 namespace browser_sync {
     27 
     28 namespace {
     29 static const char kNoSessionsFolderError[] =
     30     "Server did not create the top-level sessions node. We "
     31     "might be running against an out-of-date server.";
     32 
     33 // The maximum number of navigations in each direction we care to sync.
     34 static const int max_sync_navigation_count = 6;
     35 }  // namespace
     36 
     37 SessionModelAssociator::SessionModelAssociator(ProfileSyncService* sync_service)
     38     : tab_pool_(sync_service),
     39       local_session_syncid_(sync_api::kInvalidId),
     40       sync_service_(sync_service),
     41       setup_for_test_(false) {
     42   DCHECK(CalledOnValidThread());
     43   DCHECK(sync_service_);
     44 }
     45 
     46 SessionModelAssociator::SessionModelAssociator(ProfileSyncService* sync_service,
     47                                                bool setup_for_test)
     48     : tab_pool_(sync_service),
     49       local_session_syncid_(sync_api::kInvalidId),
     50       sync_service_(sync_service),
     51       setup_for_test_(setup_for_test) {
     52   DCHECK(CalledOnValidThread());
     53   DCHECK(sync_service_);
     54 }
     55 
     56 SessionModelAssociator::~SessionModelAssociator() {
     57   DCHECK(CalledOnValidThread());
     58 }
     59 
     60 bool SessionModelAssociator::InitSyncNodeFromChromeId(
     61     const std::string& id,
     62     sync_api::BaseNode* sync_node) {
     63   NOTREACHED();
     64   return false;
     65 }
     66 
     67 bool SessionModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) {
     68   DCHECK(CalledOnValidThread());
     69   CHECK(has_nodes);
     70   *has_nodes = false;
     71   sync_api::ReadTransaction trans(sync_service_->GetUserShare());
     72   sync_api::ReadNode root(&trans);
     73   if (!root.InitByTagLookup(kSessionsTag)) {
     74     LOG(ERROR) << kNoSessionsFolderError;
     75     return false;
     76   }
     77   // The sync model has user created nodes iff the sessions folder has
     78   // any children.
     79   *has_nodes = root.GetFirstChildId() != sync_api::kInvalidId;
     80   return true;
     81 }
     82 
     83 int64 SessionModelAssociator::GetSyncIdFromChromeId(const size_t& id) {
     84   DCHECK(CalledOnValidThread());
     85   return GetSyncIdFromSessionTag(TabIdToTag(GetCurrentMachineTag(), id));
     86 }
     87 
     88 int64 SessionModelAssociator::GetSyncIdFromSessionTag(const std::string& tag) {
     89   DCHECK(CalledOnValidThread());
     90   sync_api::ReadTransaction trans(sync_service_->GetUserShare());
     91   sync_api::ReadNode node(&trans);
     92   if (!node.InitByClientTagLookup(syncable::SESSIONS, tag))
     93     return sync_api::kInvalidId;
     94   return node.GetId();
     95 }
     96 
     97 const TabContents*
     98 SessionModelAssociator::GetChromeNodeFromSyncId(int64 sync_id) {
     99   NOTREACHED();
    100   return NULL;
    101 }
    102 
    103 bool SessionModelAssociator::InitSyncNodeFromChromeId(
    104     const size_t& id,
    105     sync_api::BaseNode* sync_node) {
    106   NOTREACHED();
    107   return false;
    108 }
    109 
    110 void SessionModelAssociator::ReassociateWindows(bool reload_tabs) {
    111   DCHECK(CalledOnValidThread());
    112   sync_pb::SessionSpecifics specifics;
    113   specifics.set_session_tag(GetCurrentMachineTag());
    114   sync_pb::SessionHeader* header_s = specifics.mutable_header();
    115 
    116   for (BrowserList::const_iterator i = BrowserList::begin();
    117        i != BrowserList::end(); ++i) {
    118     // Make sure the browser has tabs and a window. Browsers destructor
    119     // removes itself from the BrowserList. When a browser is closed the
    120     // destructor is not necessarily run immediately. This means its possible
    121     // for us to get a handle to a browser that is about to be removed. If
    122     // the tab count is 0 or the window is NULL, the browser is about to be
    123     // deleted, so we ignore it.
    124     if (ShouldSyncWindowType((*i)->type()) && (*i)->tab_count() &&
    125         (*i)->window()) {
    126       sync_pb::SessionWindow window_s;
    127       SessionID::id_type window_id = (*i)->session_id().id();
    128       VLOG(1) << "Reassociating window " << window_id << " with " <<
    129           (*i)->tab_count() << " tabs.";
    130       window_s.set_window_id(window_id);
    131       window_s.set_selected_tab_index((*i)->active_index());
    132       if ((*i)->type() ==
    133           Browser::TYPE_NORMAL) {
    134         window_s.set_browser_type(
    135             sync_pb::SessionWindow_BrowserType_TYPE_NORMAL);
    136       } else {
    137         window_s.set_browser_type(
    138             sync_pb::SessionWindow_BrowserType_TYPE_POPUP);
    139       }
    140 
    141       // Store the order of tabs.
    142       bool found_tabs = false;
    143       for (int j = 0; j < (*i)->tab_count(); ++j) {
    144         TabContents* tab = (*i)->GetTabContentsAt(j);
    145         DCHECK(tab);
    146         if (IsValidTab(*tab)) {
    147           found_tabs = true;
    148           window_s.add_tab(tab->controller().session_id().id());
    149           if (reload_tabs) {
    150             ReassociateTab(*tab);
    151           }
    152         }
    153       }
    154       // Only add a window if it contains valid tabs.
    155       if (found_tabs) {
    156         sync_pb::SessionWindow* header_window = header_s->add_window();
    157         *header_window = window_s;
    158       }
    159     }
    160   }
    161 
    162   sync_api::WriteTransaction trans(sync_service_->GetUserShare());
    163   sync_api::WriteNode header_node(&trans);
    164   if (!header_node.InitByIdLookup(local_session_syncid_)) {
    165     LOG(ERROR) << "Failed to load local session header node.";
    166     return;
    167   }
    168   header_node.SetSessionSpecifics(specifics);
    169 }
    170 
    171 // Static.
    172 bool SessionModelAssociator::ShouldSyncWindowType(const Browser::Type& type) {
    173   switch (type) {
    174     case Browser::TYPE_POPUP:
    175       return true;
    176     case Browser::TYPE_APP:
    177       return false;
    178     case Browser::TYPE_APP_POPUP:
    179       return false;
    180     case Browser::TYPE_DEVTOOLS:
    181       return false;
    182     case Browser::TYPE_APP_PANEL:
    183       return false;
    184     case Browser::TYPE_NORMAL:
    185     default:
    186       return true;
    187   }
    188 }
    189 
    190 void SessionModelAssociator::ReassociateTabs(
    191     const std::vector<TabContents*>& tabs) {
    192   DCHECK(CalledOnValidThread());
    193   for (std::vector<TabContents*>::const_iterator i = tabs.begin();
    194        i != tabs.end();
    195        ++i) {
    196     ReassociateTab(**i);
    197   }
    198 }
    199 
    200 void SessionModelAssociator::ReassociateTab(const TabContents& tab) {
    201   DCHECK(CalledOnValidThread());
    202   if (!IsValidTab(tab))
    203     return;
    204 
    205   int64 sync_id;
    206   SessionID::id_type id = tab.controller().session_id().id();
    207   if (tab.is_being_destroyed()) {
    208     // This tab is closing.
    209     TabLinksMap::iterator tab_iter = tab_map_.find(id);
    210     if (tab_iter == tab_map_.end()) {
    211       // We aren't tracking this tab (for example, sync setting page).
    212       return;
    213     }
    214     tab_pool_.FreeTabNode(tab_iter->second.sync_id());
    215     tab_map_.erase(tab_iter);
    216     return;
    217   }
    218 
    219   TabLinksMap::const_iterator tablink = tab_map_.find(id);
    220   if (tablink == tab_map_.end()) {
    221     // This is a new tab, get a sync node for it.
    222     sync_id = tab_pool_.GetFreeTabNode();
    223   } else {
    224     // This tab is already associated with a sync node, reuse it.
    225     sync_id = tablink->second.sync_id();
    226   }
    227   Associate(&tab, sync_id);
    228 }
    229 
    230 void SessionModelAssociator::Associate(const TabContents* tab, int64 sync_id) {
    231   DCHECK(CalledOnValidThread());
    232   SessionID::id_type session_id = tab->controller().session_id().id();
    233   Browser* browser = BrowserList::FindBrowserWithID(
    234       tab->controller().window_id().id());
    235   if (!browser)  // Can happen for weird things like developer console.
    236     return;
    237 
    238   TabLinks t(sync_id, tab);
    239   tab_map_[session_id] = t;
    240 
    241   sync_api::WriteTransaction trans(sync_service_->GetUserShare());
    242   WriteTabContentsToSyncModel(*browser, *tab, sync_id, &trans);
    243 }
    244 
    245 bool SessionModelAssociator::WriteTabContentsToSyncModel(
    246     const Browser& browser,
    247     const TabContents& tab,
    248     int64 sync_id,
    249     sync_api::WriteTransaction* trans) {
    250   DCHECK(CalledOnValidThread());
    251   sync_api::WriteNode tab_node(trans);
    252   if (!tab_node.InitByIdLookup(sync_id)) {
    253     LOG(ERROR) << "Failed to look up tab node " << sync_id;
    254     return false;
    255   }
    256 
    257   sync_pb::SessionSpecifics session_s;
    258   session_s.set_session_tag(GetCurrentMachineTag());
    259   sync_pb::SessionTab* tab_s = session_s.mutable_tab();
    260 
    261   SessionID::id_type tab_id = tab.controller().session_id().id();
    262   tab_s->set_tab_id(tab_id);
    263   tab_s->set_window_id(tab.controller().window_id().id());
    264   const int current_index = tab.controller().GetCurrentEntryIndex();
    265   const int min_index = std::max(0,
    266                                  current_index - max_sync_navigation_count);
    267   const int max_index = std::min(current_index + max_sync_navigation_count,
    268                                  tab.controller().entry_count());
    269   const int pending_index = tab.controller().pending_entry_index();
    270   int index_in_window = browser.tabstrip_model()->GetWrapperIndex(&tab);
    271   DCHECK(index_in_window != TabStripModel::kNoTab);
    272   tab_s->set_pinned(browser.tabstrip_model()->IsTabPinned(index_in_window));
    273   TabContentsWrapper* wrapper =
    274       TabContentsWrapper::GetCurrentWrapperForContents(
    275           const_cast<TabContents*>(&tab));
    276   if (wrapper->extension_tab_helper()->extension_app()) {
    277     tab_s->set_extension_app_id(
    278         wrapper->extension_tab_helper()->extension_app()->id());
    279   }
    280   for (int i = min_index; i < max_index; ++i) {
    281     const NavigationEntry* entry = (i == pending_index) ?
    282        tab.controller().pending_entry() : tab.controller().GetEntryAtIndex(i);
    283     DCHECK(entry);
    284     if (entry->virtual_url().is_valid()) {
    285       if (i == max_index - 1) {
    286         VLOG(1) << "Associating tab " << tab_id << " with sync id " << sync_id
    287             << " and url " << entry->virtual_url().possibly_invalid_spec();
    288       }
    289       TabNavigation tab_nav;
    290       tab_nav.SetFromNavigationEntry(*entry);
    291       sync_pb::TabNavigation* nav_s = tab_s->add_navigation();
    292       PopulateSessionSpecificsNavigation(&tab_nav, nav_s);
    293     }
    294   }
    295   tab_s->set_current_navigation_index(current_index);
    296 
    297   tab_node.SetSessionSpecifics(session_s);
    298   return true;
    299 }
    300 
    301 // Static
    302 // TODO(zea): perhaps sync state (scroll position, form entries, etc.) as well?
    303 // See http://crbug.com/67068.
    304 void SessionModelAssociator::PopulateSessionSpecificsNavigation(
    305     const TabNavigation* navigation,
    306     sync_pb::TabNavigation* tab_navigation) {
    307   tab_navigation->set_index(navigation->index());
    308   tab_navigation->set_virtual_url(navigation->virtual_url().spec());
    309   tab_navigation->set_referrer(navigation->referrer().spec());
    310   tab_navigation->set_title(UTF16ToUTF8(navigation->title()));
    311   switch (navigation->transition()) {
    312     case PageTransition::LINK:
    313       tab_navigation->set_page_transition(
    314         sync_pb::TabNavigation_PageTransition_LINK);
    315       break;
    316     case PageTransition::TYPED:
    317       tab_navigation->set_page_transition(
    318         sync_pb::TabNavigation_PageTransition_TYPED);
    319       break;
    320     case PageTransition::AUTO_BOOKMARK:
    321       tab_navigation->set_page_transition(
    322         sync_pb::TabNavigation_PageTransition_AUTO_BOOKMARK);
    323       break;
    324     case PageTransition::AUTO_SUBFRAME:
    325       tab_navigation->set_page_transition(
    326         sync_pb::TabNavigation_PageTransition_AUTO_SUBFRAME);
    327       break;
    328     case PageTransition::MANUAL_SUBFRAME:
    329       tab_navigation->set_page_transition(
    330         sync_pb::TabNavigation_PageTransition_MANUAL_SUBFRAME);
    331       break;
    332     case PageTransition::GENERATED:
    333       tab_navigation->set_page_transition(
    334         sync_pb::TabNavigation_PageTransition_GENERATED);
    335       break;
    336     case PageTransition::START_PAGE:
    337       tab_navigation->set_page_transition(
    338         sync_pb::TabNavigation_PageTransition_START_PAGE);
    339       break;
    340     case PageTransition::FORM_SUBMIT:
    341       tab_navigation->set_page_transition(
    342         sync_pb::TabNavigation_PageTransition_FORM_SUBMIT);
    343       break;
    344     case PageTransition::RELOAD:
    345       tab_navigation->set_page_transition(
    346         sync_pb::TabNavigation_PageTransition_RELOAD);
    347       break;
    348     case PageTransition::KEYWORD:
    349       tab_navigation->set_page_transition(
    350         sync_pb::TabNavigation_PageTransition_KEYWORD);
    351       break;
    352     case PageTransition::KEYWORD_GENERATED:
    353       tab_navigation->set_page_transition(
    354         sync_pb::TabNavigation_PageTransition_KEYWORD_GENERATED);
    355       break;
    356     case PageTransition::CHAIN_START:
    357       tab_navigation->set_page_transition(
    358         sync_pb::TabNavigation_PageTransition_CHAIN_START);
    359       break;
    360     case PageTransition::CHAIN_END:
    361       tab_navigation->set_page_transition(
    362         sync_pb::TabNavigation_PageTransition_CHAIN_END);
    363       break;
    364     case PageTransition::CLIENT_REDIRECT:
    365       tab_navigation->set_navigation_qualifier(
    366         sync_pb::TabNavigation_PageTransitionQualifier_CLIENT_REDIRECT);
    367       break;
    368     case PageTransition::SERVER_REDIRECT:
    369       tab_navigation->set_navigation_qualifier(
    370         sync_pb::TabNavigation_PageTransitionQualifier_SERVER_REDIRECT);
    371       break;
    372     default:
    373       tab_navigation->set_page_transition(
    374         sync_pb::TabNavigation_PageTransition_TYPED);
    375   }
    376 }
    377 
    378 void SessionModelAssociator::Disassociate(int64 sync_id) {
    379   DCHECK(CalledOnValidThread());
    380   NOTIMPLEMENTED();
    381   // TODO(zea): we will need this once we support deleting foreign sessions.
    382 }
    383 
    384 bool SessionModelAssociator::AssociateModels() {
    385   DCHECK(CalledOnValidThread());
    386 
    387   // Ensure that we disassociated properly, otherwise memory might leak.
    388   DCHECK(foreign_session_tracker_.empty());
    389   DCHECK_EQ(0U, tab_pool_.capacity());
    390 
    391   local_session_syncid_ = sync_api::kInvalidId;
    392 
    393   // Read any available foreign sessions and load any session data we may have.
    394   // If we don't have any local session data in the db, create a header node.
    395   {
    396     sync_api::WriteTransaction trans(sync_service_->GetUserShare());
    397 
    398     sync_api::ReadNode root(&trans);
    399     if (!root.InitByTagLookup(kSessionsTag)) {
    400       LOG(ERROR) << kNoSessionsFolderError;
    401       return false;
    402     }
    403 
    404     // Make sure we have a machine tag.
    405     if (current_machine_tag_.empty())
    406       InitializeCurrentMachineTag(&trans);
    407 
    408     UpdateAssociationsFromSyncModel(root, &trans);
    409 
    410     if (local_session_syncid_ == sync_api::kInvalidId) {
    411       // The sync db didn't have a header node for us, we need to create one.
    412       sync_api::WriteNode write_node(&trans);
    413       if (!write_node.InitUniqueByCreation(syncable::SESSIONS, root,
    414           current_machine_tag_)) {
    415         LOG(ERROR) << "Failed to create sessions header sync node.";
    416         return false;
    417       }
    418       write_node.SetTitle(UTF8ToWide(current_machine_tag_));
    419       local_session_syncid_ = write_node.GetId();
    420     }
    421   }
    422 
    423   // Check if anything has changed on the client side.
    424   UpdateSyncModelDataFromClient();
    425 
    426   VLOG(1) << "Session models associated.";
    427 
    428   return true;
    429 }
    430 
    431 bool SessionModelAssociator::DisassociateModels() {
    432   DCHECK(CalledOnValidThread());
    433   foreign_session_tracker_.clear();
    434   tab_map_.clear();
    435   tab_pool_.clear();
    436   local_session_syncid_ = sync_api::kInvalidId;
    437 
    438   // There is no local model stored with which to disassociate, just notify
    439   // foreign session handlers.
    440   NotificationService::current()->Notify(
    441       NotificationType::FOREIGN_SESSION_DISABLED,
    442       NotificationService::AllSources(),
    443       NotificationService::NoDetails());
    444   return true;
    445 }
    446 
    447 void SessionModelAssociator::InitializeCurrentMachineTag(
    448     sync_api::WriteTransaction* trans) {
    449   DCHECK(CalledOnValidThread());
    450   syncable::Directory* dir = trans->GetWrappedWriteTrans()->directory();
    451 
    452   // TODO(zea): We need a better way of creating a machine tag. The directory
    453   // kernel's cache_guid changes every time syncing is turned on and off. This
    454   // will result in session's associated with stale machine tags persisting on
    455   // the server since that tag will not be reused. Eventually this should
    456   // become some string identifiable to the user. (Home, Work, Laptop, etc.)
    457   // See issue at http://crbug.com/59672
    458   current_machine_tag_ = "session_sync";
    459   current_machine_tag_.append(dir->cache_guid());
    460   VLOG(1) << "Creating machine tag: " << current_machine_tag_;
    461   tab_pool_.set_machine_tag(current_machine_tag_);
    462 }
    463 
    464 bool SessionModelAssociator::UpdateAssociationsFromSyncModel(
    465     const sync_api::ReadNode& root,
    466     const sync_api::BaseTransaction* trans) {
    467   DCHECK(CalledOnValidThread());
    468 
    469   // Iterate through the nodes and associate any foreign sessions.
    470   int64 id = root.GetFirstChildId();
    471   while (id != sync_api::kInvalidId) {
    472     sync_api::ReadNode sync_node(trans);
    473     if (!sync_node.InitByIdLookup(id)) {
    474       LOG(ERROR) << "Failed to fetch sync node for id " << id;
    475       return false;
    476     }
    477 
    478     const sync_pb::SessionSpecifics& specifics =
    479         sync_node.GetSessionSpecifics();
    480     const int64 modification_time = sync_node.GetModificationTime();
    481     if (specifics.session_tag() != GetCurrentMachineTag()) {
    482       if (!AssociateForeignSpecifics(specifics, modification_time)) {
    483         return false;
    484       }
    485     } else if (id != local_session_syncid_) {
    486       // This is previously stored local session information.
    487       if (specifics.has_header()) {
    488         DCHECK_EQ(sync_api::kInvalidId, local_session_syncid_);
    489 
    490         // This is our previous header node, reuse it.
    491         local_session_syncid_ = id;
    492       } else {
    493         DCHECK(specifics.has_tab());
    494 
    495         // This is a tab node. We want to track these to reuse them in our free
    496         // tab node pool. They will be overwritten eventually, so need to do
    497         // anything else.
    498         tab_pool_.AddTabNode(id);
    499       }
    500     }
    501 
    502     id = sync_node.GetSuccessorId();
    503   }
    504 
    505   // After updating from sync model all tabid's should be free.
    506   DCHECK(tab_pool_.full());
    507 
    508   return true;
    509 }
    510 
    511 bool SessionModelAssociator::AssociateForeignSpecifics(
    512     const sync_pb::SessionSpecifics& specifics,
    513     const int64 modification_time) {
    514   DCHECK(CalledOnValidThread());
    515   std::string foreign_session_tag = specifics.session_tag();
    516   DCHECK(foreign_session_tag != GetCurrentMachineTag() || setup_for_test_);
    517 
    518   if (specifics.has_header()) {
    519     // Read in the header data for this foreign session.
    520     // Header data contains window information and ordered tab id's for each
    521     // window.
    522 
    523     // Load (or create) the ForeignSession object for this client.
    524     ForeignSession* foreign_session =
    525         foreign_session_tracker_.GetForeignSession(foreign_session_tag);
    526 
    527     const sync_pb::SessionHeader& header = specifics.header();
    528     foreign_session->windows.reserve(header.window_size());
    529     VLOG(1) << "Associating " << foreign_session_tag << " with " <<
    530         header.window_size() << " windows.";
    531     size_t i;
    532     for (i = 0; i < static_cast<size_t>(header.window_size()); ++i) {
    533       if (i >= foreign_session->windows.size()) {
    534         // This a new window, create it.
    535         foreign_session->windows.push_back(new SessionWindow());
    536       }
    537       const sync_pb::SessionWindow& window_s = header.window(i);
    538       PopulateSessionWindowFromSpecifics(foreign_session_tag,
    539                                          window_s,
    540                                          modification_time,
    541                                          foreign_session->windows[i],
    542                                          &foreign_session_tracker_);
    543     }
    544     // Remove any remaining windows (in case windows were closed)
    545     for (; i < foreign_session->windows.size(); ++i) {
    546       delete foreign_session->windows[i];
    547     }
    548     foreign_session->windows.resize(header.window_size());
    549   } else if (specifics.has_tab()) {
    550     const sync_pb::SessionTab& tab_s = specifics.tab();
    551     SessionID::id_type tab_id = tab_s.tab_id();
    552     SessionTab* tab =
    553         foreign_session_tracker_.GetSessionTab(foreign_session_tag,
    554                                                tab_id,
    555                                                false);
    556     PopulateSessionTabFromSpecifics(tab_s, modification_time, tab);
    557   } else {
    558     NOTREACHED();
    559     return false;
    560   }
    561 
    562   return true;
    563 }
    564 
    565 void SessionModelAssociator::DisassociateForeignSession(
    566     const std::string& foreign_session_tag) {
    567   DCHECK(CalledOnValidThread());
    568   foreign_session_tracker_.DeleteForeignSession(foreign_session_tag);
    569 }
    570 
    571 // Static
    572 void SessionModelAssociator::PopulateSessionWindowFromSpecifics(
    573     const std::string& foreign_session_tag,
    574     const sync_pb::SessionWindow& specifics,
    575     int64 mtime,
    576     SessionWindow* session_window,
    577     ForeignSessionTracker* tracker) {
    578   if (specifics.has_window_id())
    579     session_window->window_id.set_id(specifics.window_id());
    580   if (specifics.has_selected_tab_index())
    581     session_window->selected_tab_index = specifics.selected_tab_index();
    582   if (specifics.has_browser_type()) {
    583     if (specifics.browser_type() ==
    584         sync_pb::SessionWindow_BrowserType_TYPE_NORMAL) {
    585       session_window->type = 1;
    586     } else {
    587       session_window->type = 2;
    588     }
    589   }
    590   session_window->timestamp = base::Time::FromInternalValue(mtime);
    591   session_window->tabs.resize(specifics.tab_size());
    592   for (int i = 0; i < specifics.tab_size(); i++) {
    593     SessionID::id_type tab_id = specifics.tab(i);
    594     session_window->tabs[i] =
    595         tracker->GetSessionTab(foreign_session_tag, tab_id, true);
    596   }
    597 }
    598 
    599 // Static
    600 void SessionModelAssociator::PopulateSessionTabFromSpecifics(
    601     const sync_pb::SessionTab& specifics,
    602     const int64 mtime,
    603     SessionTab* tab) {
    604   if (specifics.has_tab_id())
    605     tab->tab_id.set_id(specifics.tab_id());
    606   if (specifics.has_window_id())
    607     tab->window_id.set_id(specifics.window_id());
    608   if (specifics.has_tab_visual_index())
    609     tab->tab_visual_index = specifics.tab_visual_index();
    610   if (specifics.has_current_navigation_index())
    611     tab->current_navigation_index = specifics.current_navigation_index();
    612   if (specifics.has_pinned())
    613     tab->pinned = specifics.pinned();
    614   if (specifics.has_extension_app_id())
    615     tab->extension_app_id = specifics.extension_app_id();
    616   tab->timestamp = base::Time::FromInternalValue(mtime);
    617   tab->navigations.clear();  // In case we are reusing a previous SessionTab.
    618   for (int i = 0; i < specifics.navigation_size(); i++) {
    619     AppendSessionTabNavigation(specifics.navigation(i), &tab->navigations);
    620   }
    621 }
    622 
    623 // Static
    624 void SessionModelAssociator::AppendSessionTabNavigation(
    625     const sync_pb::TabNavigation& specifics,
    626     std::vector<TabNavigation>* navigations) {
    627   int index = 0;
    628   GURL virtual_url;
    629   GURL referrer;
    630   string16 title;
    631   std::string state;
    632   PageTransition::Type transition(PageTransition::LINK);
    633   if (specifics.has_index())
    634     index = specifics.index();
    635   if (specifics.has_virtual_url()) {
    636     GURL gurl(specifics.virtual_url());
    637     virtual_url = gurl;
    638   }
    639   if (specifics.has_referrer()) {
    640     GURL gurl(specifics.referrer());
    641     referrer = gurl;
    642   }
    643   if (specifics.has_title())
    644     title = UTF8ToUTF16(specifics.title());
    645   if (specifics.has_state())
    646     state = specifics.state();
    647   if (specifics.has_page_transition() ||
    648       specifics.has_navigation_qualifier()) {
    649     switch (specifics.page_transition()) {
    650       case sync_pb::TabNavigation_PageTransition_LINK:
    651         transition = PageTransition::LINK;
    652         break;
    653       case sync_pb::TabNavigation_PageTransition_TYPED:
    654         transition = PageTransition::TYPED;
    655         break;
    656       case sync_pb::TabNavigation_PageTransition_AUTO_BOOKMARK:
    657         transition = PageTransition::AUTO_BOOKMARK;
    658         break;
    659       case sync_pb::TabNavigation_PageTransition_AUTO_SUBFRAME:
    660         transition = PageTransition::AUTO_SUBFRAME;
    661         break;
    662       case sync_pb::TabNavigation_PageTransition_MANUAL_SUBFRAME:
    663         transition = PageTransition::MANUAL_SUBFRAME;
    664         break;
    665       case sync_pb::TabNavigation_PageTransition_GENERATED:
    666         transition = PageTransition::GENERATED;
    667         break;
    668       case sync_pb::TabNavigation_PageTransition_START_PAGE:
    669         transition = PageTransition::START_PAGE;
    670         break;
    671       case sync_pb::TabNavigation_PageTransition_FORM_SUBMIT:
    672         transition = PageTransition::FORM_SUBMIT;
    673         break;
    674       case sync_pb::TabNavigation_PageTransition_RELOAD:
    675         transition = PageTransition::RELOAD;
    676         break;
    677       case sync_pb::TabNavigation_PageTransition_KEYWORD:
    678         transition = PageTransition::KEYWORD;
    679         break;
    680       case sync_pb::TabNavigation_PageTransition_KEYWORD_GENERATED:
    681         transition = PageTransition::KEYWORD_GENERATED;
    682         break;
    683       case sync_pb::TabNavigation_PageTransition_CHAIN_START:
    684         transition = sync_pb::TabNavigation_PageTransition_CHAIN_START;
    685         break;
    686       case sync_pb::TabNavigation_PageTransition_CHAIN_END:
    687         transition = PageTransition::CHAIN_END;
    688         break;
    689       default:
    690         switch (specifics.navigation_qualifier()) {
    691           case sync_pb::
    692               TabNavigation_PageTransitionQualifier_CLIENT_REDIRECT:
    693             transition = PageTransition::CLIENT_REDIRECT;
    694             break;
    695             case sync_pb::
    696                 TabNavigation_PageTransitionQualifier_SERVER_REDIRECT:
    697             transition = PageTransition::SERVER_REDIRECT;
    698               break;
    699             default:
    700             transition = PageTransition::TYPED;
    701         }
    702     }
    703   }
    704   TabNavigation tab_navigation(index, virtual_url, referrer, title, state,
    705                                transition);
    706   navigations->insert(navigations->end(), tab_navigation);
    707 }
    708 
    709 void SessionModelAssociator::UpdateSyncModelDataFromClient() {
    710   DCHECK(CalledOnValidThread());
    711   // TODO(zea): the logic for determining if we want to sync and the loading of
    712   // the previous session should go here. We can probably reuse the code for
    713   // loading the current session from the old session implementation.
    714   // SessionService::SessionCallback* callback =
    715   //     NewCallback(this, &SessionModelAssociator::OnGotSession);
    716   // GetSessionService()->GetCurrentSession(&consumer_, callback);
    717 
    718   // Associate all open windows and their tabs.
    719   ReassociateWindows(true);
    720 }
    721 
    722 SessionModelAssociator::TabNodePool::TabNodePool(
    723     ProfileSyncService* sync_service)
    724     : tab_pool_fp_(-1),
    725       sync_service_(sync_service) {
    726 }
    727 
    728 SessionModelAssociator::TabNodePool::~TabNodePool() {}
    729 
    730 void SessionModelAssociator::TabNodePool::AddTabNode(int64 sync_id) {
    731   tab_syncid_pool_.resize(tab_syncid_pool_.size() + 1);
    732   tab_syncid_pool_[static_cast<size_t>(++tab_pool_fp_)] = sync_id;
    733 }
    734 
    735 int64 SessionModelAssociator::TabNodePool::GetFreeTabNode() {
    736   DCHECK_GT(machine_tag_.length(), 0U);
    737   if (tab_pool_fp_ == -1) {
    738     // Tab pool has no free nodes, allocate new one.
    739     sync_api::WriteTransaction trans(sync_service_->GetUserShare());
    740     sync_api::ReadNode root(&trans);
    741     if (!root.InitByTagLookup(kSessionsTag)) {
    742       LOG(ERROR) << kNoSessionsFolderError;
    743       return 0;
    744     }
    745     size_t tab_node_id = tab_syncid_pool_.size();
    746     std::string tab_node_tag = TabIdToTag(machine_tag_, tab_node_id);
    747     sync_api::WriteNode tab_node(&trans);
    748     if (!tab_node.InitUniqueByCreation(syncable::SESSIONS, root,
    749                                        tab_node_tag)) {
    750       LOG(ERROR) << "Could not create new node!";
    751       return -1;
    752     }
    753     tab_node.SetTitle(UTF8ToWide(tab_node_tag));
    754 
    755     // Grow the pool by 1 since we created a new node. We don't actually need
    756     // to put the node's id in the pool now, since the pool is still empty.
    757     // The id will be added when that tab is closed and the node is freed.
    758     tab_syncid_pool_.resize(tab_node_id + 1);
    759     VLOG(1) << "Adding sync node " << tab_node.GetId() << " to tab syncid pool";
    760     return tab_node.GetId();
    761   } else {
    762     // There are nodes available, grab next free and decrement free pointer.
    763     return tab_syncid_pool_[static_cast<size_t>(tab_pool_fp_--)];
    764   }
    765 }
    766 
    767 void SessionModelAssociator::TabNodePool::FreeTabNode(int64 sync_id) {
    768   // Pool size should always match # of free tab nodes.
    769   DCHECK_LT(tab_pool_fp_, static_cast<int64>(tab_syncid_pool_.size()));
    770   tab_syncid_pool_[static_cast<size_t>(++tab_pool_fp_)] = sync_id;
    771 }
    772 
    773 bool SessionModelAssociator::GetAllForeignSessions(
    774     std::vector<const ForeignSession*>* sessions) {
    775   DCHECK(CalledOnValidThread());
    776   return foreign_session_tracker_.LookupAllForeignSessions(sessions);
    777 }
    778 
    779 bool SessionModelAssociator::GetForeignSession(
    780     const std::string& tag,
    781     std::vector<SessionWindow*>* windows) {
    782   DCHECK(CalledOnValidThread());
    783   return foreign_session_tracker_.LookupSessionWindows(tag, windows);
    784 }
    785 
    786 bool SessionModelAssociator::GetForeignTab(
    787     const std::string& tag,
    788     const SessionID::id_type tab_id,
    789     const SessionTab** tab) {
    790   DCHECK(CalledOnValidThread());
    791   return foreign_session_tracker_.LookupSessionTab(tag, tab_id, tab);
    792 }
    793 
    794 // Static
    795 bool SessionModelAssociator::SessionWindowHasNoTabsToSync(
    796     const SessionWindow& window) {
    797   int num_populated = 0;
    798   for (std::vector<SessionTab*>::const_iterator i = window.tabs.begin();
    799       i != window.tabs.end(); ++i) {
    800     const SessionTab* tab = *i;
    801     if (IsValidSessionTab(*tab))
    802       num_populated++;
    803   }
    804   if (num_populated == 0)
    805     return true;
    806   return false;
    807 }
    808 
    809 // Valid local tab?
    810 bool SessionModelAssociator::IsValidTab(const TabContents& tab) {
    811   DCHECK(CalledOnValidThread());
    812   if ((tab.profile() == sync_service_->profile() ||
    813        sync_service_->profile() == NULL)) {
    814     const NavigationEntry* entry = tab.controller().GetActiveEntry();
    815     if (!entry)
    816       return false;
    817     if (entry->virtual_url().is_valid() &&
    818         (entry->virtual_url() != GURL(chrome::kChromeUINewTabURL) ||
    819          tab.controller().entry_count() > 1)) {
    820       return true;
    821     }
    822   }
    823   return false;
    824 }
    825 
    826 // Static
    827 bool SessionModelAssociator::IsValidSessionTab(const SessionTab& tab) {
    828   if (tab.navigations.empty())
    829     return false;
    830   int selected_index = tab.current_navigation_index;
    831   selected_index = std::max(
    832       0,
    833       std::min(selected_index,
    834           static_cast<int>(tab.navigations.size() - 1)));
    835   if (selected_index == 0 &&
    836       tab.navigations.size() == 1 &&
    837       tab.navigations.at(selected_index).virtual_url() ==
    838           GURL(chrome::kChromeUINewTabURL)) {
    839     // This is a new tab with no further history, skip.
    840     return false;
    841   }
    842   return true;
    843 }
    844 
    845 // ==========================================================================
    846 // The following methods are not currently used but will likely become useful
    847 // if we choose to sync the previous browser session.
    848 
    849 SessionService* SessionModelAssociator::GetSessionService() {
    850   DCHECK(CalledOnValidThread());
    851   DCHECK(sync_service_);
    852   Profile* profile = sync_service_->profile();
    853   DCHECK(profile);
    854   SessionService* sessions_service = profile->GetSessionService();
    855   DCHECK(sessions_service);
    856   return sessions_service;
    857 }
    858 
    859 void SessionModelAssociator::OnGotSession(
    860     int handle,
    861     std::vector<SessionWindow*>* windows) {
    862   DCHECK(CalledOnValidThread());
    863   DCHECK(local_session_syncid_);
    864 
    865   sync_pb::SessionSpecifics specifics;
    866   specifics.set_session_tag(GetCurrentMachineTag());
    867   sync_pb::SessionHeader* header_s = specifics.mutable_header();
    868   PopulateSessionSpecificsHeader(*windows, header_s);
    869 
    870   sync_api::WriteTransaction trans(sync_service_->GetUserShare());
    871   sync_api::ReadNode root(&trans);
    872   if (!root.InitByTagLookup(kSessionsTag)) {
    873     LOG(ERROR) << kNoSessionsFolderError;
    874     return;
    875   }
    876 
    877   sync_api::WriteNode header_node(&trans);
    878   if (!header_node.InitByIdLookup(local_session_syncid_)) {
    879     LOG(ERROR) << "Failed to load local session header node.";
    880     return;
    881   }
    882 
    883   header_node.SetSessionSpecifics(specifics);
    884 }
    885 
    886 void SessionModelAssociator::PopulateSessionSpecificsHeader(
    887     const std::vector<SessionWindow*>& windows,
    888     sync_pb::SessionHeader* header_s) {
    889   DCHECK(CalledOnValidThread());
    890 
    891   // Iterate through the vector of windows, extracting the window data, along
    892   // with the tab data to populate the session specifics.
    893   for (size_t i = 0; i < windows.size(); ++i) {
    894     if (SessionWindowHasNoTabsToSync(*(windows[i])))
    895       continue;
    896     sync_pb::SessionWindow* window_s = header_s->add_window();
    897     PopulateSessionSpecificsWindow(*(windows[i]), window_s);
    898     if (!SyncLocalWindowToSyncModel(*(windows[i])))
    899       return;
    900   }
    901 }
    902 
    903 // Called when populating session specifics to send to the sync model, called
    904 // when associating models, or updating the sync model.
    905 void SessionModelAssociator::PopulateSessionSpecificsWindow(
    906     const SessionWindow& window,
    907     sync_pb::SessionWindow* session_window) {
    908   DCHECK(CalledOnValidThread());
    909   session_window->set_window_id(window.window_id.id());
    910   session_window->set_selected_tab_index(window.selected_tab_index);
    911   if (window.type == Browser::TYPE_NORMAL) {
    912     session_window->set_browser_type(
    913       sync_pb::SessionWindow_BrowserType_TYPE_NORMAL);
    914   } else if (window.type == Browser::TYPE_POPUP) {
    915     session_window->set_browser_type(
    916       sync_pb::SessionWindow_BrowserType_TYPE_POPUP);
    917   } else {
    918     // ignore
    919     LOG(WARNING) << "Session Sync unable to handle windows of type" <<
    920         window.type;
    921     return;
    922   }
    923   for (std::vector<SessionTab*>::const_iterator i = window.tabs.begin();
    924       i != window.tabs.end(); ++i) {
    925     const SessionTab* tab = *i;
    926     if (!IsValidSessionTab(*tab))
    927       continue;
    928     session_window->add_tab(tab->tab_id.id());
    929   }
    930 }
    931 
    932 bool SessionModelAssociator::SyncLocalWindowToSyncModel(
    933     const SessionWindow& window) {
    934   DCHECK(CalledOnValidThread());
    935   DCHECK(tab_map_.empty());
    936   for (size_t i = 0; i < window.tabs.size(); ++i) {
    937     SessionTab* tab = window.tabs[i];
    938     int64 id = tab_pool_.GetFreeTabNode();
    939     if (id == -1) {
    940       LOG(ERROR) << "Failed to find/generate free sync node for tab.";
    941       return false;
    942     }
    943 
    944     sync_api::WriteTransaction trans(sync_service_->GetUserShare());
    945     if (!WriteSessionTabToSyncModel(*tab, id, &trans)) {
    946       return false;
    947     }
    948 
    949     TabLinks t(id, tab);
    950     tab_map_[tab->tab_id.id()] = t;
    951   }
    952   return true;
    953 }
    954 
    955 bool SessionModelAssociator::WriteSessionTabToSyncModel(
    956     const SessionTab& tab,
    957     const int64 sync_id,
    958     sync_api::WriteTransaction* trans) {
    959   DCHECK(CalledOnValidThread());
    960   sync_api::WriteNode tab_node(trans);
    961   if (!tab_node.InitByIdLookup(sync_id)) {
    962     LOG(ERROR) << "Failed to look up tab node " << sync_id;
    963     return false;
    964   }
    965 
    966   sync_pb::SessionSpecifics specifics;
    967   specifics.set_session_tag(GetCurrentMachineTag());
    968   sync_pb::SessionTab* tab_s = specifics.mutable_tab();
    969   PopulateSessionSpecificsTab(tab, tab_s);
    970   tab_node.SetSessionSpecifics(specifics);
    971   return true;
    972 }
    973 
    974 // See PopulateSessionSpecificsWindow for use.
    975 void SessionModelAssociator::PopulateSessionSpecificsTab(
    976     const SessionTab& tab,
    977     sync_pb::SessionTab* session_tab) {
    978   DCHECK(CalledOnValidThread());
    979   session_tab->set_tab_id(tab.tab_id.id());
    980   session_tab->set_window_id(tab.window_id.id());
    981   session_tab->set_tab_visual_index(tab.tab_visual_index);
    982   session_tab->set_current_navigation_index(
    983       tab.current_navigation_index);
    984   session_tab->set_pinned(tab.pinned);
    985   session_tab->set_extension_app_id(tab.extension_app_id);
    986   for (std::vector<TabNavigation>::const_iterator i =
    987       tab.navigations.begin(); i != tab.navigations.end(); ++i) {
    988     const TabNavigation navigation = *i;
    989     sync_pb::TabNavigation* tab_navigation =
    990         session_tab->add_navigation();
    991     PopulateSessionSpecificsNavigation(&navigation, tab_navigation);
    992   }
    993 }
    994 
    995 bool SessionModelAssociator::CryptoReadyIfNecessary() {
    996   // We only access the cryptographer while holding a transaction.
    997   sync_api::ReadTransaction trans(sync_service_->GetUserShare());
    998   syncable::ModelTypeSet encrypted_types;
    999   sync_service_->GetEncryptedDataTypes(&encrypted_types);
   1000   return encrypted_types.count(syncable::SESSIONS) == 0 ||
   1001          sync_service_->IsCryptographerReady(&trans);
   1002 }
   1003 
   1004 }  // namespace browser_sync
   1005