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
SyncedSessionTracker()12 SyncedSessionTracker::SyncedSessionTracker() {
13 }
14
~SyncedSessionTracker()15 SyncedSessionTracker::~SyncedSessionTracker() {
16 Clear();
17 }
18
SetLocalSessionTag(const std::string & local_session_tag)19 void SyncedSessionTracker::SetLocalSessionTag(
20 const std::string& local_session_tag) {
21 local_session_tag_ = local_session_tag;
22 }
23
LookupAllForeignSessions(std::vector<const SyncedSession * > * sessions) const24 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
LookupSessionWindows(const std::string & session_tag,std::vector<const SessionWindow * > * windows) const51 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
LookupSessionTab(const std::string & tag,SessionID::id_type tab_id,const SessionTab ** tab) const68 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
LookupTabNodeIds(const std::string & session_tag,std::set<int> * tab_node_ids)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
GetSession(const std::string & session_tag)107 SyncedSession* SyncedSessionTracker::GetSession(
108 const std::string& session_tag) {
109 SyncedSession* synced_session = NULL;
110 if (synced_session_map_.find(session_tag) !=
111 synced_session_map_.end()) {
112 synced_session = synced_session_map_[session_tag];
113 } else {
114 synced_session = new SyncedSession;
115 DVLOG(1) << "Creating new session with tag " << session_tag << " at "
116 << synced_session;
117 synced_session->session_tag = session_tag;
118 synced_session_map_[session_tag] = synced_session;
119 }
120 DCHECK(synced_session);
121 return synced_session;
122 }
123
DeleteSession(const std::string & session_tag)124 bool SyncedSessionTracker::DeleteSession(const std::string& session_tag) {
125 bool found_session = false;
126 SyncedSessionMap::iterator iter = synced_session_map_.find(session_tag);
127 if (iter != synced_session_map_.end()) {
128 SyncedSession* session = iter->second;
129 synced_session_map_.erase(iter);
130 delete session; // Delete the SyncedSession object.
131 found_session = true;
132 }
133 synced_window_map_.erase(session_tag);
134 // It's possible there was no header node but there were tab nodes.
135 if (synced_tab_map_.erase(session_tag) > 0) {
136 found_session = true;
137 }
138 return found_session;
139 }
140
ResetSessionTracking(const std::string & session_tag)141 void SyncedSessionTracker::ResetSessionTracking(
142 const std::string& session_tag) {
143 // Reset window tracking.
144 GetSession(session_tag)->windows.clear();
145 SyncedWindowMap::iterator window_iter = synced_window_map_.find(session_tag);
146 if (window_iter != synced_window_map_.end()) {
147 for (IDToSessionWindowMap::iterator window_map_iter =
148 window_iter->second.begin();
149 window_map_iter != window_iter->second.end(); ++window_map_iter) {
150 window_map_iter->second.owned = false;
151 // We clear out the tabs to prevent double referencing of the same tab.
152 // All tabs that are in use will be added back as needed.
153 window_map_iter->second.window_ptr->tabs.clear();
154 }
155 }
156
157 // Reset tab tracking.
158 SyncedTabMap::iterator tab_iter = synced_tab_map_.find(session_tag);
159 if (tab_iter != synced_tab_map_.end()) {
160 for (IDToSessionTabMap::iterator tab_map_iter =
161 tab_iter->second.begin();
162 tab_map_iter != tab_iter->second.end(); ++tab_map_iter) {
163 tab_map_iter->second.owned = false;
164 }
165 }
166 }
167
DeleteOldSessionWindowIfNecessary(SessionWindowWrapper window_wrapper)168 bool SyncedSessionTracker::DeleteOldSessionWindowIfNecessary(
169 SessionWindowWrapper window_wrapper) {
170 // Clear the tabs first, since we don't want the destructor to destroy
171 // them. Their deletion will be handled by DeleteOldSessionTab below.
172 if (!window_wrapper.owned) {
173 DVLOG(1) << "Deleting closed window "
174 << window_wrapper.window_ptr->window_id.id();
175 window_wrapper.window_ptr->tabs.clear();
176 delete window_wrapper.window_ptr;
177 return true;
178 }
179 return false;
180 }
181
DeleteOldSessionTabIfNecessary(SessionTabWrapper tab_wrapper)182 bool SyncedSessionTracker::DeleteOldSessionTabIfNecessary(
183 SessionTabWrapper tab_wrapper) {
184 if (!tab_wrapper.owned) {
185 if (VLOG_IS_ON(1)) {
186 SessionTab* tab_ptr = tab_wrapper.tab_ptr;
187 std::string title;
188 if (tab_ptr->navigations.size() > 0) {
189 title = " (" + UTF16ToUTF8(
190 tab_ptr->navigations[tab_ptr->navigations.size()-1].title()) + ")";
191 }
192 DVLOG(1) << "Deleting closed tab " << tab_ptr->tab_id.id() << title
193 << " from window " << tab_ptr->window_id.id();
194 }
195 unmapped_tabs_.erase(tab_wrapper.tab_ptr);
196 delete tab_wrapper.tab_ptr;
197 return true;
198 }
199 return false;
200 }
201
CleanupSession(const std::string & session_tag)202 void SyncedSessionTracker::CleanupSession(const std::string& session_tag) {
203 // Go through and delete any windows or tabs without owners.
204 SyncedWindowMap::iterator window_iter = synced_window_map_.find(session_tag);
205 if (window_iter != synced_window_map_.end()) {
206 for (IDToSessionWindowMap::iterator iter = window_iter->second.begin();
207 iter != window_iter->second.end();) {
208 SessionWindowWrapper window_wrapper = iter->second;
209 if (DeleteOldSessionWindowIfNecessary(window_wrapper))
210 window_iter->second.erase(iter++);
211 else
212 ++iter;
213 }
214 }
215
216 SyncedTabMap::iterator tab_iter = synced_tab_map_.find(session_tag);
217 if (tab_iter != synced_tab_map_.end()) {
218 for (IDToSessionTabMap::iterator iter = tab_iter->second.begin();
219 iter != tab_iter->second.end();) {
220 SessionTabWrapper tab_wrapper = iter->second;
221 if (DeleteOldSessionTabIfNecessary(tab_wrapper))
222 tab_iter->second.erase(iter++);
223 else
224 ++iter;
225 }
226 }
227 }
228
PutWindowInSession(const std::string & session_tag,SessionID::id_type window_id)229 void SyncedSessionTracker::PutWindowInSession(const std::string& session_tag,
230 SessionID::id_type window_id) {
231 SessionWindow* window_ptr = NULL;
232 IDToSessionWindowMap::iterator iter =
233 synced_window_map_[session_tag].find(window_id);
234 if (iter != synced_window_map_[session_tag].end()) {
235 iter->second.owned = true;
236 window_ptr = iter->second.window_ptr;
237 DVLOG(1) << "Putting seen window " << window_id << " at " << window_ptr
238 << "in " << (session_tag == local_session_tag_ ?
239 "local session" : session_tag);
240 } else {
241 // Create the window.
242 window_ptr = new SessionWindow();
243 window_ptr->window_id.set_id(window_id);
244 synced_window_map_[session_tag][window_id] =
245 SessionWindowWrapper(window_ptr, IS_OWNED);
246 DVLOG(1) << "Putting new window " << window_id << " at " << window_ptr
247 << "in " << (session_tag == local_session_tag_ ?
248 "local session" : session_tag);
249 }
250 DCHECK(window_ptr);
251 DCHECK_EQ(window_ptr->window_id.id(), window_id);
252 DCHECK_EQ(reinterpret_cast<SessionWindow*>(NULL),
253 GetSession(session_tag)->windows[window_id]);
254 GetSession(session_tag)->windows[window_id] = window_ptr;
255 }
256
PutTabInWindow(const std::string & session_tag,SessionID::id_type window_id,SessionID::id_type tab_id,size_t tab_index)257 void SyncedSessionTracker::PutTabInWindow(const std::string& session_tag,
258 SessionID::id_type window_id,
259 SessionID::id_type tab_id,
260 size_t tab_index) {
261 // We're called here for two reasons. 1) We've received an update to the
262 // SessionWindow information of a SessionHeader node for a foreign session,
263 // and 2) The SessionHeader node for our local session changed. In both cases
264 // we need to update our tracking state to reflect the change.
265 //
266 // Because the SessionHeader nodes are separate from the individual tab nodes
267 // and we don't store tab_node_ids in the header / SessionWindow specifics,
268 // the tab_node_ids are not always available when processing headers.
269 // We know that we will eventually process (via GetTab) every single tab node
270 // in the system, so we permit ourselves to use kInvalidTabNodeID here and
271 // rely on the later update to build the mapping (or a restart).
272 // TODO(tim): Bug 98892. Update comment when Sync API conversion finishes to
273 // mention that in the meantime, the only ill effect is that we may not be
274 // able to fully clean up a stale foreign session, but it will get garbage
275 // collected eventually.
276 SessionTab* tab_ptr = GetTabImpl(
277 session_tag, tab_id, TabNodePool::kInvalidTabNodeID);
278 unmapped_tabs_.erase(tab_ptr);
279 synced_tab_map_[session_tag][tab_id].owned = true;
280 tab_ptr->window_id.set_id(window_id);
281 DVLOG(1) << " - tab " << tab_id << " added to window "<< window_id;
282 DCHECK(GetSession(session_tag)->windows.find(window_id) !=
283 GetSession(session_tag)->windows.end());
284 std::vector<SessionTab*>& window_tabs =
285 GetSession(session_tag)->windows[window_id]->tabs;
286 if (window_tabs.size() <= tab_index) {
287 window_tabs.resize(tab_index+1, NULL);
288 }
289 DCHECK(!window_tabs[tab_index]);
290 window_tabs[tab_index] = tab_ptr;
291 }
292
GetTab(const std::string & session_tag,SessionID::id_type tab_id,int tab_node_id)293 SessionTab* SyncedSessionTracker::GetTab(
294 const std::string& session_tag,
295 SessionID::id_type tab_id,
296 int tab_node_id) {
297 DCHECK_NE(TabNodePool::kInvalidTabNodeID, tab_node_id);
298 return GetTabImpl(session_tag, tab_id, tab_node_id);
299 }
300
GetTabImpl(const std::string & session_tag,SessionID::id_type tab_id,int tab_node_id)301 SessionTab* SyncedSessionTracker::GetTabImpl(
302 const std::string& session_tag,
303 SessionID::id_type tab_id,
304 int tab_node_id) {
305 SessionTab* tab_ptr = NULL;
306 IDToSessionTabMap::iterator iter =
307 synced_tab_map_[session_tag].find(tab_id);
308 if (iter != synced_tab_map_[session_tag].end()) {
309 tab_ptr = iter->second.tab_ptr;
310 if (tab_node_id != TabNodePool::kInvalidTabNodeID &&
311 tab_id != TabNodePool::kInvalidTabID) {
312 // We likely created this tab as an out-of-order update to the header
313 // node for this session before actually associating the tab itself, so
314 // the tab node id wasn't available at the time. Update it.
315
316 if (iter->second.tab_node_id != tab_node_id &&
317 iter->second.tab_node_id != TabNodePool::kInvalidTabNodeID) {
318 // We are updating tab_node_id for a valid tab_id, ideally this should
319 // never happen, but there are a few existing foreign sessions that
320 // may violate this constraint.
321 // TODO(shashishekhar): Introduce a DCHECK here to enforce this
322 // constraint in future.
323 DLOG(ERROR)
324 << "Updating tab_node_id for " << session_tag << " tab: " << tab_id
325 << " from: " << iter->second.tab_node_id << " to: " << tab_node_id;
326 }
327 iter->second.tab_node_id = tab_node_id;
328 }
329
330 if (VLOG_IS_ON(1)) {
331 std::string title;
332 if (tab_ptr->navigations.size() > 0) {
333 title = " (" + UTF16ToUTF8(
334 tab_ptr->navigations[tab_ptr->navigations.size()-1].title()) + ")";
335 }
336 DVLOG(1) << "Getting "
337 << (session_tag == local_session_tag_ ?
338 "local session" : session_tag)
339 << "'s seen tab " << tab_id << " at " << tab_ptr << title;
340 }
341 } else {
342 tab_ptr = new SessionTab();
343 tab_ptr->tab_id.set_id(tab_id);
344 synced_tab_map_[session_tag][tab_id] = SessionTabWrapper(tab_ptr,
345 NOT_OWNED,
346 tab_node_id);
347 unmapped_tabs_.insert(tab_ptr);
348 DVLOG(1) << "Getting "
349 << (session_tag == local_session_tag_ ?
350 "local session" : session_tag)
351 << "'s new tab " << tab_id << " at " << tab_ptr;
352 }
353 DCHECK(tab_ptr);
354 DCHECK_EQ(tab_ptr->tab_id.id(), tab_id);
355 return tab_ptr;
356 }
357
Clear()358 void SyncedSessionTracker::Clear() {
359 // Delete SyncedSession objects (which also deletes all their windows/tabs).
360 STLDeleteValues(&synced_session_map_);
361
362 // Go through and delete any tabs we had allocated but had not yet placed into
363 // a SyncedSessionobject.
364 STLDeleteElements(&unmapped_tabs_);
365
366 // Get rid of our Window/Tab maps (does not delete the actual Window/Tabs
367 // themselves; they should have all been deleted above).
368 synced_window_map_.clear();
369 synced_tab_map_.clear();
370
371 local_session_tag_.clear();
372 }
373
374 } // namespace browser_sync
375