1 // Copyright (c) 2012 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 "base/logging.h" 6 #include "base/stl_util.h" 7 #include "base/strings/utf_string_conversions.h" 8 #include "chrome/browser/sync/glue/synced_session_tracker.h" 9 10 namespace browser_sync { 11 12 SyncedSessionTracker::SyncedSessionTracker() { 13 } 14 15 SyncedSessionTracker::~SyncedSessionTracker() { 16 Clear(); 17 } 18 19 void SyncedSessionTracker::SetLocalSessionTag( 20 const std::string& local_session_tag) { 21 local_session_tag_ = local_session_tag; 22 } 23 24 bool SyncedSessionTracker::LookupAllForeignSessions( 25 std::vector<const SyncedSession*>* sessions) const { 26 DCHECK(sessions); 27 sessions->clear(); 28 // Fill vector of sessions from our synced session map. 29 for (SyncedSessionMap::const_iterator i = 30 synced_session_map_.begin(); i != synced_session_map_.end(); ++i) { 31 // Only include foreign sessions with open tabs. 32 SyncedSession* foreign_session = i->second; 33 if (i->first != local_session_tag_ && !foreign_session->windows.empty()) { 34 bool found_tabs = false; 35 for (SyncedSession::SyncedWindowMap::const_iterator iter = 36 foreign_session->windows.begin(); 37 iter != foreign_session->windows.end(); ++iter) { 38 if (!SessionWindowHasNoTabsToSync(*(iter->second))) { 39 found_tabs = true; 40 break; 41 } 42 } 43 if (found_tabs) 44 sessions->push_back(foreign_session); 45 } 46 } 47 48 return !sessions->empty(); 49 } 50 51 bool SyncedSessionTracker::LookupSessionWindows( 52 const std::string& session_tag, 53 std::vector<const SessionWindow*>* windows) const { 54 DCHECK(windows); 55 windows->clear(); 56 SyncedSessionMap::const_iterator iter = synced_session_map_.find(session_tag); 57 if (iter == synced_session_map_.end()) 58 return false; 59 windows->clear(); 60 for (SyncedSession::SyncedWindowMap::const_iterator window_iter = 61 iter->second->windows.begin(); 62 window_iter != iter->second->windows.end(); window_iter++) { 63 windows->push_back(window_iter->second); 64 } 65 return true; 66 } 67 68 bool SyncedSessionTracker::LookupSessionTab( 69 const std::string& tag, 70 SessionID::id_type tab_id, 71 const SessionTab** tab) const { 72 DCHECK(tab); 73 SyncedTabMap::const_iterator tab_map_iter = synced_tab_map_.find(tag); 74 if (tab_map_iter == synced_tab_map_.end()) { 75 // We have no record of this session. 76 *tab = NULL; 77 return false; 78 } 79 IDToSessionTabMap::const_iterator tab_iter = 80 tab_map_iter->second.find(tab_id); 81 if (tab_iter == tab_map_iter->second.end()) { 82 // We have no record of this tab. 83 *tab = NULL; 84 return false; 85 } 86 *tab = tab_iter->second.tab_ptr; 87 return true; 88 } 89 90 bool SyncedSessionTracker::LookupTabNodeIds( 91 const std::string& session_tag, std::set<int>* tab_node_ids) { 92 tab_node_ids->clear(); 93 SyncedTabMap::const_iterator tab_map_iter = 94 synced_tab_map_.find(session_tag); 95 if (tab_map_iter == synced_tab_map_.end()) 96 return false; 97 98 IDToSessionTabMap::const_iterator tab_iter = tab_map_iter->second.begin(); 99 while (tab_iter != tab_map_iter->second.end()) { 100 if (tab_iter->second.tab_node_id != TabNodePool::kInvalidTabNodeID) 101 tab_node_ids->insert(tab_iter->second.tab_node_id); 102 ++tab_iter; 103 } 104 return true; 105 } 106 107 bool SyncedSessionTracker::LookupLocalSession(const SyncedSession** output) 108 const { 109 SyncedSessionMap::const_iterator it = 110 synced_session_map_.find(local_session_tag_); 111 if (it != synced_session_map_.end()) { 112 *output = it->second; 113 return true; 114 } 115 return false; 116 } 117 118 SyncedSession* SyncedSessionTracker::GetSession( 119 const std::string& session_tag) { 120 SyncedSession* synced_session = NULL; 121 if (synced_session_map_.find(session_tag) != 122 synced_session_map_.end()) { 123 synced_session = synced_session_map_[session_tag]; 124 } else { 125 synced_session = new SyncedSession; 126 DVLOG(1) << "Creating new session with tag " << session_tag << " at " 127 << synced_session; 128 synced_session->session_tag = session_tag; 129 synced_session_map_[session_tag] = synced_session; 130 } 131 DCHECK(synced_session); 132 return synced_session; 133 } 134 135 bool SyncedSessionTracker::DeleteSession(const std::string& session_tag) { 136 bool found_session = false; 137 SyncedSessionMap::iterator iter = synced_session_map_.find(session_tag); 138 if (iter != synced_session_map_.end()) { 139 SyncedSession* session = iter->second; 140 synced_session_map_.erase(iter); 141 delete session; // Delete the SyncedSession object. 142 found_session = true; 143 } 144 synced_window_map_.erase(session_tag); 145 // It's possible there was no header node but there were tab nodes. 146 if (synced_tab_map_.erase(session_tag) > 0) { 147 found_session = true; 148 } 149 return found_session; 150 } 151 152 void SyncedSessionTracker::ResetSessionTracking( 153 const std::string& session_tag) { 154 // Reset window tracking. 155 GetSession(session_tag)->windows.clear(); 156 SyncedWindowMap::iterator window_iter = synced_window_map_.find(session_tag); 157 if (window_iter != synced_window_map_.end()) { 158 for (IDToSessionWindowMap::iterator window_map_iter = 159 window_iter->second.begin(); 160 window_map_iter != window_iter->second.end(); ++window_map_iter) { 161 window_map_iter->second.owned = false; 162 // We clear out the tabs to prevent double referencing of the same tab. 163 // All tabs that are in use will be added back as needed. 164 window_map_iter->second.window_ptr->tabs.clear(); 165 } 166 } 167 168 // Reset tab tracking. 169 SyncedTabMap::iterator tab_iter = synced_tab_map_.find(session_tag); 170 if (tab_iter != synced_tab_map_.end()) { 171 for (IDToSessionTabMap::iterator tab_map_iter = 172 tab_iter->second.begin(); 173 tab_map_iter != tab_iter->second.end(); ++tab_map_iter) { 174 tab_map_iter->second.owned = false; 175 } 176 } 177 } 178 179 bool SyncedSessionTracker::DeleteOldSessionWindowIfNecessary( 180 SessionWindowWrapper window_wrapper) { 181 // Clear the tabs first, since we don't want the destructor to destroy 182 // them. Their deletion will be handled by DeleteOldSessionTab below. 183 if (!window_wrapper.owned) { 184 DVLOG(1) << "Deleting closed window " 185 << window_wrapper.window_ptr->window_id.id(); 186 window_wrapper.window_ptr->tabs.clear(); 187 delete window_wrapper.window_ptr; 188 return true; 189 } 190 return false; 191 } 192 193 bool SyncedSessionTracker::DeleteOldSessionTabIfNecessary( 194 SessionTabWrapper tab_wrapper) { 195 if (!tab_wrapper.owned) { 196 if (VLOG_IS_ON(1)) { 197 SessionTab* tab_ptr = tab_wrapper.tab_ptr; 198 std::string title; 199 if (tab_ptr->navigations.size() > 0) { 200 title = " (" + base::UTF16ToUTF8( 201 tab_ptr->navigations[tab_ptr->navigations.size()-1].title()) + ")"; 202 } 203 DVLOG(1) << "Deleting closed tab " << tab_ptr->tab_id.id() << title 204 << " from window " << tab_ptr->window_id.id(); 205 } 206 unmapped_tabs_.erase(tab_wrapper.tab_ptr); 207 delete tab_wrapper.tab_ptr; 208 return true; 209 } 210 return false; 211 } 212 213 void SyncedSessionTracker::CleanupSession(const std::string& session_tag) { 214 // Go through and delete any windows or tabs without owners. 215 SyncedWindowMap::iterator window_iter = synced_window_map_.find(session_tag); 216 if (window_iter != synced_window_map_.end()) { 217 for (IDToSessionWindowMap::iterator iter = window_iter->second.begin(); 218 iter != window_iter->second.end();) { 219 SessionWindowWrapper window_wrapper = iter->second; 220 if (DeleteOldSessionWindowIfNecessary(window_wrapper)) 221 window_iter->second.erase(iter++); 222 else 223 ++iter; 224 } 225 } 226 227 SyncedTabMap::iterator tab_iter = synced_tab_map_.find(session_tag); 228 if (tab_iter != synced_tab_map_.end()) { 229 for (IDToSessionTabMap::iterator iter = tab_iter->second.begin(); 230 iter != tab_iter->second.end();) { 231 SessionTabWrapper tab_wrapper = iter->second; 232 if (DeleteOldSessionTabIfNecessary(tab_wrapper)) 233 tab_iter->second.erase(iter++); 234 else 235 ++iter; 236 } 237 } 238 } 239 240 void SyncedSessionTracker::PutWindowInSession(const std::string& session_tag, 241 SessionID::id_type window_id) { 242 SessionWindow* window_ptr = NULL; 243 IDToSessionWindowMap::iterator iter = 244 synced_window_map_[session_tag].find(window_id); 245 if (iter != synced_window_map_[session_tag].end()) { 246 iter->second.owned = true; 247 window_ptr = iter->second.window_ptr; 248 DVLOG(1) << "Putting seen window " << window_id << " at " << window_ptr 249 << "in " << (session_tag == local_session_tag_ ? 250 "local session" : session_tag); 251 } else { 252 // Create the window. 253 window_ptr = new SessionWindow(); 254 window_ptr->window_id.set_id(window_id); 255 synced_window_map_[session_tag][window_id] = 256 SessionWindowWrapper(window_ptr, IS_OWNED); 257 DVLOG(1) << "Putting new window " << window_id << " at " << window_ptr 258 << "in " << (session_tag == local_session_tag_ ? 259 "local session" : session_tag); 260 } 261 DCHECK(window_ptr); 262 DCHECK_EQ(window_ptr->window_id.id(), window_id); 263 DCHECK_EQ(reinterpret_cast<SessionWindow*>(NULL), 264 GetSession(session_tag)->windows[window_id]); 265 GetSession(session_tag)->windows[window_id] = window_ptr; 266 } 267 268 void SyncedSessionTracker::PutTabInWindow(const std::string& session_tag, 269 SessionID::id_type window_id, 270 SessionID::id_type tab_id, 271 size_t tab_index) { 272 // We're called here for two reasons. 1) We've received an update to the 273 // SessionWindow information of a SessionHeader node for a foreign session, 274 // and 2) The SessionHeader node for our local session changed. In both cases 275 // we need to update our tracking state to reflect the change. 276 // 277 // Because the SessionHeader nodes are separate from the individual tab nodes 278 // and we don't store tab_node_ids in the header / SessionWindow specifics, 279 // the tab_node_ids are not always available when processing headers. 280 // We know that we will eventually process (via GetTab) every single tab node 281 // in the system, so we permit ourselves to use kInvalidTabNodeID here and 282 // rely on the later update to build the mapping (or a restart). 283 // TODO(tim): Bug 98892. Update comment when Sync API conversion finishes to 284 // mention that in the meantime, the only ill effect is that we may not be 285 // able to fully clean up a stale foreign session, but it will get garbage 286 // collected eventually. 287 SessionTab* tab_ptr = GetTabImpl( 288 session_tag, tab_id, TabNodePool::kInvalidTabNodeID); 289 290 // It's up to the caller to ensure this never happens. Tabs should not 291 // belong to more than one window or appear twice within the same window. 292 // 293 // If this condition were violated, we would double-free during shutdown. 294 // That could cause all sorts of hard to diagnose crashes, possibly in code 295 // far away from here. We crash early to avoid this. 296 // 297 // See http://crbug.com/360822. 298 CHECK(!synced_tab_map_[session_tag][tab_id].owned); 299 300 unmapped_tabs_.erase(tab_ptr); 301 synced_tab_map_[session_tag][tab_id].owned = true; 302 303 tab_ptr->window_id.set_id(window_id); 304 DVLOG(1) << " - tab " << tab_id << " added to window "<< window_id; 305 DCHECK(GetSession(session_tag)->windows.find(window_id) != 306 GetSession(session_tag)->windows.end()); 307 std::vector<SessionTab*>& window_tabs = 308 GetSession(session_tag)->windows[window_id]->tabs; 309 if (window_tabs.size() <= tab_index) { 310 window_tabs.resize(tab_index+1, NULL); 311 } 312 DCHECK(!window_tabs[tab_index]); 313 window_tabs[tab_index] = tab_ptr; 314 } 315 316 SessionTab* SyncedSessionTracker::GetTab( 317 const std::string& session_tag, 318 SessionID::id_type tab_id, 319 int tab_node_id) { 320 DCHECK_NE(TabNodePool::kInvalidTabNodeID, tab_node_id); 321 return GetTabImpl(session_tag, tab_id, tab_node_id); 322 } 323 324 SessionTab* SyncedSessionTracker::GetTabImpl( 325 const std::string& session_tag, 326 SessionID::id_type tab_id, 327 int tab_node_id) { 328 SessionTab* tab_ptr = NULL; 329 IDToSessionTabMap::iterator iter = 330 synced_tab_map_[session_tag].find(tab_id); 331 if (iter != synced_tab_map_[session_tag].end()) { 332 tab_ptr = iter->second.tab_ptr; 333 if (tab_node_id != TabNodePool::kInvalidTabNodeID && 334 tab_id != TabNodePool::kInvalidTabID) { 335 // TabIDs are not stable across restarts of a client. Consider this 336 // example with two tabs: 337 // 338 // http://a.com TabID1 --> NodeIDA 339 // http://b.com TabID2 --> NodeIDB 340 // 341 // After restart, tab ids are reallocated. e.g, one possibility: 342 // http://a.com TabID2 --> NodeIDA 343 // http://b.com TabID1 --> NodeIDB 344 // 345 // If that happend on a remote client, here we will see an update to 346 // TabID1 with tab_node_id changing from NodeIDA to NodeIDB, and TabID2 347 // with tab_node_id changing from NodeIDB to NodeIDA. 348 // 349 // We can also wind up here if we created this tab as an out-of-order 350 // update to the header node for this session before actually associating 351 // the tab itself, so the tab node id wasn't available at the time and 352 // is currenlty kInvalidTabNodeID. 353 // 354 // In both cases, we update the tab_node_id. 355 iter->second.tab_node_id = tab_node_id; 356 } 357 358 if (VLOG_IS_ON(1)) { 359 std::string title; 360 if (tab_ptr->navigations.size() > 0) { 361 title = " (" + base::UTF16ToUTF8( 362 tab_ptr->navigations[tab_ptr->navigations.size()-1].title()) + ")"; 363 } 364 DVLOG(1) << "Getting " 365 << (session_tag == local_session_tag_ ? 366 "local session" : session_tag) 367 << "'s seen tab " << tab_id << " at " << tab_ptr << title; 368 } 369 } else { 370 tab_ptr = new SessionTab(); 371 tab_ptr->tab_id.set_id(tab_id); 372 synced_tab_map_[session_tag][tab_id] = SessionTabWrapper(tab_ptr, 373 NOT_OWNED, 374 tab_node_id); 375 unmapped_tabs_.insert(tab_ptr); 376 DVLOG(1) << "Getting " 377 << (session_tag == local_session_tag_ ? 378 "local session" : session_tag) 379 << "'s new tab " << tab_id << " at " << tab_ptr; 380 } 381 DCHECK(tab_ptr); 382 DCHECK_EQ(tab_ptr->tab_id.id(), tab_id); 383 return tab_ptr; 384 } 385 386 void SyncedSessionTracker::Clear() { 387 // Delete SyncedSession objects (which also deletes all their windows/tabs). 388 STLDeleteValues(&synced_session_map_); 389 390 // Go through and delete any tabs we had allocated but had not yet placed into 391 // a SyncedSessionobject. 392 STLDeleteElements(&unmapped_tabs_); 393 394 // Get rid of our Window/Tab maps (does not delete the actual Window/Tabs 395 // themselves; they should have all been deleted above). 396 synced_window_map_.clear(); 397 synced_tab_map_.clear(); 398 399 local_session_tag_.clear(); 400 } 401 402 } // namespace browser_sync 403