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
26namespace browser_sync {
27
28namespace {
29static 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.
34static const int max_sync_navigation_count = 6;
35}  // namespace
36
37SessionModelAssociator::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
46SessionModelAssociator::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
56SessionModelAssociator::~SessionModelAssociator() {
57  DCHECK(CalledOnValidThread());
58}
59
60bool SessionModelAssociator::InitSyncNodeFromChromeId(
61    const std::string& id,
62    sync_api::BaseNode* sync_node) {
63  NOTREACHED();
64  return false;
65}
66
67bool 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
83int64 SessionModelAssociator::GetSyncIdFromChromeId(const size_t& id) {
84  DCHECK(CalledOnValidThread());
85  return GetSyncIdFromSessionTag(TabIdToTag(GetCurrentMachineTag(), id));
86}
87
88int64 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
97const TabContents*
98SessionModelAssociator::GetChromeNodeFromSyncId(int64 sync_id) {
99  NOTREACHED();
100  return NULL;
101}
102
103bool SessionModelAssociator::InitSyncNodeFromChromeId(
104    const size_t& id,
105    sync_api::BaseNode* sync_node) {
106  NOTREACHED();
107  return false;
108}
109
110void 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.
172bool 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
190void 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
200void 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
230void 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
245bool 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.
304void 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
378void 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
384bool 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
431bool 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
447void 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
464bool 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
511bool 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
565void SessionModelAssociator::DisassociateForeignSession(
566    const std::string& foreign_session_tag) {
567  DCHECK(CalledOnValidThread());
568  foreign_session_tracker_.DeleteForeignSession(foreign_session_tag);
569}
570
571// Static
572void 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
600void 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
624void 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
709void 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
722SessionModelAssociator::TabNodePool::TabNodePool(
723    ProfileSyncService* sync_service)
724    : tab_pool_fp_(-1),
725      sync_service_(sync_service) {
726}
727
728SessionModelAssociator::TabNodePool::~TabNodePool() {}
729
730void 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
735int64 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
767void 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
773bool SessionModelAssociator::GetAllForeignSessions(
774    std::vector<const ForeignSession*>* sessions) {
775  DCHECK(CalledOnValidThread());
776  return foreign_session_tracker_.LookupAllForeignSessions(sessions);
777}
778
779bool 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
786bool 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
795bool 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?
810bool 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
827bool 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
849SessionService* 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
859void 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
886void 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.
905void 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
932bool 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
955bool 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.
975void 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
995bool 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