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