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