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