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 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 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 56 SessionModelAssociator::~SessionModelAssociator() { 57 DCHECK(CalledOnValidThread()); 58 } 59 60 bool SessionModelAssociator::InitSyncNodeFromChromeId( 61 const std::string& id, 62 sync_api::BaseNode* sync_node) { 63 NOTREACHED(); 64 return false; 65 } 66 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 83 int64 SessionModelAssociator::GetSyncIdFromChromeId(const size_t& id) { 84 DCHECK(CalledOnValidThread()); 85 return GetSyncIdFromSessionTag(TabIdToTag(GetCurrentMachineTag(), id)); 86 } 87 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* 98 SessionModelAssociator::GetChromeNodeFromSyncId(int64 sync_id) { 99 NOTREACHED(); 100 return NULL; 101 } 102 103 bool SessionModelAssociator::InitSyncNodeFromChromeId( 104 const size_t& id, 105 sync_api::BaseNode* sync_node) { 106 NOTREACHED(); 107 return false; 108 } 109 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. 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 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 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 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 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. 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 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 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 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 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 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 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 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 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 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 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 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 722 SessionModelAssociator::TabNodePool::TabNodePool( 723 ProfileSyncService* sync_service) 724 : tab_pool_fp_(-1), 725 sync_service_(sync_service) { 726 } 727 728 SessionModelAssociator::TabNodePool::~TabNodePool() {} 729 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 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 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 773 bool SessionModelAssociator::GetAllForeignSessions( 774 std::vector<const ForeignSession*>* sessions) { 775 DCHECK(CalledOnValidThread()); 776 return foreign_session_tracker_.LookupAllForeignSessions(sessions); 777 } 778 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 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 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? 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 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 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 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 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. 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 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 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. 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 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