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