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
10namespace browser_sync {
11
12SyncedSessionTracker::SyncedSessionTracker() {
13}
14
15SyncedSessionTracker::~SyncedSessionTracker() {
16  Clear();
17}
18
19void SyncedSessionTracker::SetLocalSessionTag(
20    const std::string& local_session_tag) {
21  local_session_tag_ = local_session_tag;
22}
23
24bool 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
51bool 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
68bool 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
90bool 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
107bool 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
118SyncedSession* 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
135bool 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
152void 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
179bool 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
193bool 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
213void 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
240void 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
268void 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
316SessionTab* 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
324SessionTab* 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
386void 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