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