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