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