1ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// Copyright (c) 2011 The Chromium Authors. All rights reserved. 2c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Use of this source code is governed by a BSD-style license that can be 3c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// found in the LICENSE file. 4c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 5c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "chrome/browser/sessions/session_service.h" 6c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 7c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include <algorithm> 8c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include <limits> 9c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include <set> 10c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include <vector> 11c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 12ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen#include "base/file_util.h" 13731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick#include "base/memory/scoped_vector.h" 14c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "base/message_loop.h" 153345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick#include "base/metrics/histogram.h" 163345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick#include "base/pickle.h" 17c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "base/threading/thread.h" 18c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "chrome/browser/extensions/extension_tab_helper.h" 193345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick#include "chrome/browser/prefs/session_startup_pref.h" 20c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "chrome/browser/profiles/profile.h" 21c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "chrome/browser/sessions/session_backend.h" 22c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "chrome/browser/sessions/session_command.h" 23c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "chrome/browser/sessions/session_restore.h" 24c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "chrome/browser/sessions/session_types.h" 25c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "chrome/browser/tabs/tab_strip_model.h" 26c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "chrome/browser/ui/browser_init.h" 27c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "chrome/browser/ui/browser_list.h" 28c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "chrome/browser/ui/browser_window.h" 29c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" 30c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "chrome/common/extensions/extension.h" 31c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "content/browser/tab_contents/navigation_controller.h" 32c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "content/browser/tab_contents/navigation_entry.h" 33c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "content/browser/tab_contents/tab_contents.h" 34c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "content/common/notification_details.h" 35c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "content/common/notification_service.h" 36c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 37c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#if defined(OS_MACOSX) 38c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "chrome/browser/app_controller_cppsafe_mac.h" 39c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#endif 40c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 41c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochusing base::Time; 42c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 43c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Identifier for commands written to file. 44c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstatic const SessionCommand::id_type kCommandSetTabWindow = 0; 45c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// kCommandSetWindowBounds is no longer used (it's superseded by 46c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// kCommandSetWindowBounds2). I leave it here to document what it was. 47c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// static const SessionCommand::id_type kCommandSetWindowBounds = 1; 48c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstatic const SessionCommand::id_type kCommandSetTabIndexInWindow = 2; 49c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstatic const SessionCommand::id_type kCommandTabClosed = 3; 50c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstatic const SessionCommand::id_type kCommandWindowClosed = 4; 51c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstatic const SessionCommand::id_type 52c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch kCommandTabNavigationPathPrunedFromBack = 5; 53c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstatic const SessionCommand::id_type kCommandUpdateTabNavigation = 6; 54c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstatic const SessionCommand::id_type kCommandSetSelectedNavigationIndex = 7; 55c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstatic const SessionCommand::id_type kCommandSetSelectedTabInIndex = 8; 56c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstatic const SessionCommand::id_type kCommandSetWindowType = 9; 57c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstatic const SessionCommand::id_type kCommandSetWindowBounds2 = 10; 58c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstatic const SessionCommand::id_type 59c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch kCommandTabNavigationPathPrunedFromFront = 11; 60c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstatic const SessionCommand::id_type kCommandSetPinnedState = 12; 61c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstatic const SessionCommand::id_type kCommandSetExtensionAppID = 13; 623345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick 63c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Every kWritesPerReset commands triggers recreating the file. 64c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstatic const int kWritesPerReset = 250; 65c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 66c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochnamespace { 67c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 68c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// The callback from GetLastSession is internally routed to SessionService 69c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// first and then the caller. This is done so that the SessionWindows can be 70c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// recreated from the SessionCommands and the SessionWindows passed to the 71c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// caller. The following class is used for this. 72c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochclass InternalSessionRequest 73c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch : public BaseSessionService::InternalGetCommandsRequest { 74c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch public: 75c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch InternalSessionRequest( 763345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick CallbackType* callback, 77c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch SessionService::SessionCallback* real_callback) 78c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch : BaseSessionService::InternalGetCommandsRequest(callback), 79c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch real_callback(real_callback) { 80c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 81c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 82c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // The callback supplied to GetLastSession and GetCurrentSession. 83c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch scoped_ptr<SessionService::SessionCallback> real_callback; 84c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 85c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch private: 86c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ~InternalSessionRequest() {} 87c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 88c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch DISALLOW_COPY_AND_ASSIGN(InternalSessionRequest); 89c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}; 90c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 91c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Various payload structures. 92c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstruct ClosedPayload { 93c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch SessionID::id_type id; 94c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch int64 close_time; 95c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}; 96c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 97c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstruct WindowBoundsPayload2 { 98c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch SessionID::id_type window_id; 99c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch int32 x; 100c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch int32 y; 101c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch int32 w; 102c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch int32 h; 103c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch bool is_maximized; 104c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}; 105c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 106c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstruct IDAndIndexPayload { 107c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch SessionID::id_type id; 108c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch int32 index; 109c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}; 110c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 111c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochtypedef IDAndIndexPayload TabIndexInWindowPayload; 112c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 113c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochtypedef IDAndIndexPayload TabNavigationPathPrunedFromBackPayload; 114c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 115c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochtypedef IDAndIndexPayload SelectedNavigationIndexPayload; 116c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 117c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochtypedef IDAndIndexPayload SelectedTabInIndexPayload; 118c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 119c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochtypedef IDAndIndexPayload WindowTypePayload; 120c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 121c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochtypedef IDAndIndexPayload TabNavigationPathPrunedFromFrontPayload; 122c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 123c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochstruct PinnedStatePayload { 124c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch SessionID::id_type tab_id; 125c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch bool pinned_state; 126c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}; 127c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 128c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} // namespace 129c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 130c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// SessionService ------------------------------------------------------------- 131c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 132c407dc5cd9bdc5668497f21b26b09d988ab439deBen MurdochSessionService::SessionService(Profile* profile) 133c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch : BaseSessionService(SESSION_RESTORE, profile, FilePath()), 134c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch has_open_trackable_browsers_(false), 135c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch move_on_new_browser_(false), 136c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)), 137c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch save_delay_in_mins_(base::TimeDelta::FromMinutes(10)), 138c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch save_delay_in_hrs_(base::TimeDelta::FromHours(8)) { 139c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Init(); 140c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 141c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 142c407dc5cd9bdc5668497f21b26b09d988ab439deBen MurdochSessionService::SessionService(const FilePath& save_path) 143c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch : BaseSessionService(SESSION_RESTORE, NULL, save_path), 144c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch has_open_trackable_browsers_(false), 145c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch move_on_new_browser_(false), 146c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)), 147c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch save_delay_in_mins_(base::TimeDelta::FromMinutes(10)), 148c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch save_delay_in_hrs_(base::TimeDelta::FromHours(8)) { 149c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Init(); 150c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 151c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 152c407dc5cd9bdc5668497f21b26b09d988ab439deBen MurdochSessionService::~SessionService() { 153c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch Save(); 154c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 155c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 156c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochbool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open) { 157c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return RestoreIfNecessary(urls_to_open, NULL); 158c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 159c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 160c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid SessionService::ResetFromCurrentBrowsers() { 161c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch ScheduleReset(); 162c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 163c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 164c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid SessionService::MoveCurrentSessionToLastSession() { 165c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch pending_tab_close_ids_.clear(); 166c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch window_closing_ids_.clear(); 167c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch pending_window_close_ids_.clear(); 168 169 Save(); 170 171 if (!backend_thread()) { 172 backend()->MoveCurrentSessionToLastSession(); 173 } else { 174 backend_thread()->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( 175 backend(), &SessionBackend::MoveCurrentSessionToLastSession)); 176 } 177} 178 179void SessionService::SetTabWindow(const SessionID& window_id, 180 const SessionID& tab_id) { 181 if (!ShouldTrackChangesToWindow(window_id)) 182 return; 183 184 ScheduleCommand(CreateSetTabWindowCommand(window_id, tab_id)); 185} 186 187void SessionService::SetWindowBounds(const SessionID& window_id, 188 const gfx::Rect& bounds, 189 bool is_maximized) { 190 if (!ShouldTrackChangesToWindow(window_id)) 191 return; 192 193 ScheduleCommand(CreateSetWindowBoundsCommand(window_id, bounds, 194 is_maximized)); 195} 196 197void SessionService::SetTabIndexInWindow(const SessionID& window_id, 198 const SessionID& tab_id, 199 int new_index) { 200 if (!ShouldTrackChangesToWindow(window_id)) 201 return; 202 203 ScheduleCommand(CreateSetTabIndexInWindowCommand(tab_id, new_index)); 204} 205 206void SessionService::SetPinnedState(const SessionID& window_id, 207 const SessionID& tab_id, 208 bool is_pinned) { 209 if (!ShouldTrackChangesToWindow(window_id)) 210 return; 211 212 ScheduleCommand(CreatePinnedStateCommand(tab_id, is_pinned)); 213} 214 215void SessionService::TabClosed(const SessionID& window_id, 216 const SessionID& tab_id, 217 bool closed_by_user_gesture) { 218 if (!tab_id.id()) 219 return; // Hapens when the tab is replaced. 220 221 if (!ShouldTrackChangesToWindow(window_id)) 222 return; 223 224 IdToRange::iterator i = tab_to_available_range_.find(tab_id.id()); 225 if (i != tab_to_available_range_.end()) 226 tab_to_available_range_.erase(i); 227 228 if (find(pending_window_close_ids_.begin(), pending_window_close_ids_.end(), 229 window_id.id()) != pending_window_close_ids_.end()) { 230 // Tab is in last window. Don't commit it immediately, instead add it to the 231 // list of tabs to close. If the user creates another window, the close is 232 // committed. 233 pending_tab_close_ids_.insert(tab_id.id()); 234 } else if (find(window_closing_ids_.begin(), window_closing_ids_.end(), 235 window_id.id()) != window_closing_ids_.end() || 236 !IsOnlyOneTabLeft() || 237 closed_by_user_gesture) { 238 // Close is the result of one of the following: 239 // . window close (and it isn't the last window). 240 // . closing a tab and there are other windows/tabs open. 241 // . closed by a user gesture. 242 // In all cases we need to mark the tab as explicitly closed. 243 ScheduleCommand(CreateTabClosedCommand(tab_id.id())); 244 } else { 245 // User closed the last tab in the last tabbed browser. Don't mark the 246 // tab closed. 247 pending_tab_close_ids_.insert(tab_id.id()); 248 has_open_trackable_browsers_ = false; 249 } 250} 251 252void SessionService::WindowClosing(const SessionID& window_id) { 253 if (!ShouldTrackChangesToWindow(window_id)) 254 return; 255 256 // The window is about to close. If there are other tabbed browsers with the 257 // same original profile commit the close immediately. 258 // 259 // NOTE: if the user chooses the exit menu item session service is destroyed 260 // and this code isn't hit. 261 if (has_open_trackable_browsers_) { 262 // Closing a window can never make has_open_trackable_browsers_ go from 263 // false to true, so only update it if already true. 264 has_open_trackable_browsers_ = HasOpenTrackableBrowsers(window_id); 265 } 266 if (should_record_close_as_pending()) 267 pending_window_close_ids_.insert(window_id.id()); 268 else 269 window_closing_ids_.insert(window_id.id()); 270} 271 272void SessionService::WindowClosed(const SessionID& window_id) { 273 if (!ShouldTrackChangesToWindow(window_id)) 274 return; 275 276 windows_tracking_.erase(window_id.id()); 277 278 if (window_closing_ids_.find(window_id.id()) != window_closing_ids_.end()) { 279 window_closing_ids_.erase(window_id.id()); 280 ScheduleCommand(CreateWindowClosedCommand(window_id.id())); 281 } else if (pending_window_close_ids_.find(window_id.id()) == 282 pending_window_close_ids_.end()) { 283 // We'll hit this if user closed the last tab in a window. 284 has_open_trackable_browsers_ = HasOpenTrackableBrowsers(window_id); 285 if (should_record_close_as_pending()) 286 pending_window_close_ids_.insert(window_id.id()); 287 else 288 ScheduleCommand(CreateWindowClosedCommand(window_id.id())); 289 } 290} 291 292void SessionService::SetWindowType(const SessionID& window_id, 293 Browser::Type type) { 294 if (!should_track_changes_for_browser_type(type)) 295 return; 296 297 windows_tracking_.insert(window_id.id()); 298 299 // The user created a new tabbed browser with our profile. Commit any 300 // pending closes. 301 CommitPendingCloses(); 302 303 has_open_trackable_browsers_ = true; 304 move_on_new_browser_ = true; 305 306 ScheduleCommand( 307 CreateSetWindowTypeCommand(window_id, WindowTypeForBrowserType(type))); 308} 309 310void SessionService::TabNavigationPathPrunedFromBack(const SessionID& window_id, 311 const SessionID& tab_id, 312 int count) { 313 if (!ShouldTrackChangesToWindow(window_id)) 314 return; 315 316 TabNavigationPathPrunedFromBackPayload payload = { 0 }; 317 payload.id = tab_id.id(); 318 payload.index = count; 319 SessionCommand* command = 320 new SessionCommand(kCommandTabNavigationPathPrunedFromBack, 321 sizeof(payload)); 322 memcpy(command->contents(), &payload, sizeof(payload)); 323 ScheduleCommand(command); 324} 325 326void SessionService::TabNavigationPathPrunedFromFront( 327 const SessionID& window_id, 328 const SessionID& tab_id, 329 int count) { 330 if (!ShouldTrackChangesToWindow(window_id)) 331 return; 332 333 // Update the range of indices. 334 if (tab_to_available_range_.find(tab_id.id()) != 335 tab_to_available_range_.end()) { 336 std::pair<int, int>& range = tab_to_available_range_[tab_id.id()]; 337 range.first = std::max(0, range.first - count); 338 range.second = std::max(0, range.second - count); 339 } 340 341 TabNavigationPathPrunedFromFrontPayload payload = { 0 }; 342 payload.id = tab_id.id(); 343 payload.index = count; 344 SessionCommand* command = 345 new SessionCommand(kCommandTabNavigationPathPrunedFromFront, 346 sizeof(payload)); 347 memcpy(command->contents(), &payload, sizeof(payload)); 348 ScheduleCommand(command); 349} 350 351void SessionService::UpdateTabNavigation(const SessionID& window_id, 352 const SessionID& tab_id, 353 int index, 354 const NavigationEntry& entry) { 355 if (!ShouldTrackEntry(entry) || !ShouldTrackChangesToWindow(window_id)) 356 return; 357 358 if (tab_to_available_range_.find(tab_id.id()) != 359 tab_to_available_range_.end()) { 360 std::pair<int, int>& range = tab_to_available_range_[tab_id.id()]; 361 range.first = std::min(index, range.first); 362 range.second = std::max(index, range.second); 363 } 364 ScheduleCommand(CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation, 365 tab_id.id(), index, entry)); 366} 367 368void SessionService::TabRestored(NavigationController* controller, 369 bool pinned) { 370 if (!ShouldTrackChangesToWindow(controller->window_id())) 371 return; 372 373 BuildCommandsForTab(controller->window_id(), controller, -1, 374 pinned, &pending_commands(), NULL); 375 StartSaveTimer(); 376} 377 378void SessionService::SetSelectedNavigationIndex(const SessionID& window_id, 379 const SessionID& tab_id, 380 int index) { 381 if (!ShouldTrackChangesToWindow(window_id)) 382 return; 383 384 if (tab_to_available_range_.find(tab_id.id()) != 385 tab_to_available_range_.end()) { 386 if (index < tab_to_available_range_[tab_id.id()].first || 387 index > tab_to_available_range_[tab_id.id()].second) { 388 // The new index is outside the range of what we've archived, schedule 389 // a reset. 390 ResetFromCurrentBrowsers(); 391 return; 392 } 393 } 394 ScheduleCommand(CreateSetSelectedNavigationIndexCommand(tab_id, index)); 395} 396 397void SessionService::SetSelectedTabInWindow(const SessionID& window_id, 398 int index) { 399 if (!ShouldTrackChangesToWindow(window_id)) 400 return; 401 402 ScheduleCommand(CreateSetSelectedTabInWindow(window_id, index)); 403} 404 405SessionService::Handle SessionService::GetLastSession( 406 CancelableRequestConsumerBase* consumer, 407 SessionCallback* callback) { 408 return ScheduleGetLastSessionCommands( 409 new InternalSessionRequest( 410 NewCallback(this, &SessionService::OnGotSessionCommands), 411 callback), consumer); 412} 413 414SessionService::Handle SessionService::GetCurrentSession( 415 CancelableRequestConsumerBase* consumer, 416 SessionCallback* callback) { 417 if (pending_window_close_ids_.empty()) { 418 // If there are no pending window closes, we can get the current session 419 // from memory. 420 scoped_refptr<InternalSessionRequest> request(new InternalSessionRequest( 421 NewCallback(this, &SessionService::OnGotSessionCommands), 422 callback)); 423 AddRequest(request, consumer); 424 IdToRange tab_to_available_range; 425 std::set<SessionID::id_type> windows_to_track; 426 BuildCommandsFromBrowsers(&(request->commands), 427 &tab_to_available_range, 428 &windows_to_track); 429 request->ForwardResult( 430 BaseSessionService::InternalGetCommandsRequest::TupleType( 431 request->handle(), request)); 432 return request->handle(); 433 } else { 434 // If there are pending window closes, read the current session from disk. 435 return ScheduleGetCurrentSessionCommands( 436 new InternalSessionRequest( 437 NewCallback(this, &SessionService::OnGotSessionCommands), 438 callback), consumer); 439 } 440} 441 442void SessionService::Save() { 443 bool had_commands = !pending_commands().empty(); 444 BaseSessionService::Save(); 445 if (had_commands) { 446 RecordSessionUpdateHistogramData(NotificationType::SESSION_SERVICE_SAVED, 447 &last_updated_save_time_); 448 NotificationService::current()->Notify( 449 NotificationType::SESSION_SERVICE_SAVED, 450 Source<Profile>(profile()), 451 NotificationService::NoDetails()); 452 } 453} 454 455void SessionService::Init() { 456 // Register for the notifications we're interested in. 457 registrar_.Add(this, NotificationType::TAB_PARENTED, 458 NotificationService::AllSources()); 459 registrar_.Add(this, NotificationType::TAB_CLOSED, 460 NotificationService::AllSources()); 461 registrar_.Add(this, NotificationType::NAV_LIST_PRUNED, 462 NotificationService::AllSources()); 463 registrar_.Add(this, NotificationType::NAV_ENTRY_CHANGED, 464 NotificationService::AllSources()); 465 registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED, 466 NotificationService::AllSources()); 467 registrar_.Add(this, NotificationType::BROWSER_OPENED, 468 NotificationService::AllSources()); 469 registrar_.Add(this, 470 NotificationType::TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED, 471 NotificationService::AllSources()); 472} 473 474bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open, 475 Browser* browser) { 476 if (!has_open_trackable_browsers_ && !BrowserInit::InProcessStartup() && 477 !SessionRestore::IsRestoring() 478#if defined(OS_MACOSX) 479 // OSX has a fairly different idea of application lifetime than the 480 // other platforms. We need to check that we aren't opening a window 481 // from the dock or the menubar. 482 && !app_controller_mac::IsOpeningNewWindow() 483#endif 484 ) { 485 // We're going from no tabbed browsers to a tabbed browser (and not in 486 // process startup), restore the last session. 487 if (move_on_new_browser_) { 488 // Make the current session the last. 489 MoveCurrentSessionToLastSession(); 490 move_on_new_browser_ = false; 491 } 492 SessionStartupPref pref = SessionStartupPref::GetStartupPref(profile()); 493 if (pref.type == SessionStartupPref::LAST) { 494 SessionRestore::RestoreSession( 495 profile(), browser, false, browser ? false : true, urls_to_open); 496 return true; 497 } 498 } 499 return false; 500} 501 502void SessionService::Observe(NotificationType type, 503 const NotificationSource& source, 504 const NotificationDetails& details) { 505 // All of our messages have the NavigationController as the source. 506 switch (type.value) { 507 case NotificationType::BROWSER_OPENED: { 508 Browser* browser = Source<Browser>(source).ptr(); 509 if (browser->profile() != profile() || 510 !should_track_changes_for_browser_type(browser->type())) { 511 return; 512 } 513 514 RestoreIfNecessary(std::vector<GURL>(), browser); 515 SetWindowType(browser->session_id(), browser->type()); 516 break; 517 } 518 519 case NotificationType::TAB_PARENTED: { 520 NavigationController* controller = 521 Source<NavigationController>(source).ptr(); 522 SetTabWindow(controller->window_id(), controller->session_id()); 523 TabContentsWrapper* wrapper = 524 TabContentsWrapper::GetCurrentWrapperForContents( 525 controller->tab_contents()); 526 if (wrapper->extension_tab_helper()->extension_app()) { 527 SetTabExtensionAppID( 528 controller->window_id(), 529 controller->session_id(), 530 wrapper->extension_tab_helper()->extension_app()->id()); 531 } 532 break; 533 } 534 535 case NotificationType::TAB_CLOSED: { 536 NavigationController* controller = 537 Source<NavigationController>(source).ptr(); 538 TabClosed(controller->window_id(), controller->session_id(), 539 controller->tab_contents()->closed_by_user_gesture()); 540 RecordSessionUpdateHistogramData(NotificationType::TAB_CLOSED, 541 &last_updated_tab_closed_time_); 542 break; 543 } 544 545 case NotificationType::NAV_LIST_PRUNED: { 546 NavigationController* controller = 547 Source<NavigationController>(source).ptr(); 548 Details<NavigationController::PrunedDetails> pruned_details(details); 549 if (pruned_details->from_front) { 550 TabNavigationPathPrunedFromFront(controller->window_id(), 551 controller->session_id(), 552 pruned_details->count); 553 } else { 554 TabNavigationPathPrunedFromBack(controller->window_id(), 555 controller->session_id(), 556 controller->entry_count()); 557 } 558 RecordSessionUpdateHistogramData(NotificationType::NAV_LIST_PRUNED, 559 &last_updated_nav_list_pruned_time_); 560 break; 561 } 562 563 case NotificationType::NAV_ENTRY_CHANGED: { 564 NavigationController* controller = 565 Source<NavigationController>(source).ptr(); 566 Details<NavigationController::EntryChangedDetails> changed(details); 567 UpdateTabNavigation(controller->window_id(), controller->session_id(), 568 changed->index, *changed->changed_entry); 569 break; 570 } 571 572 case NotificationType::NAV_ENTRY_COMMITTED: { 573 NavigationController* controller = 574 Source<NavigationController>(source).ptr(); 575 int current_entry_index = controller->GetCurrentEntryIndex(); 576 SetSelectedNavigationIndex(controller->window_id(), 577 controller->session_id(), 578 current_entry_index); 579 UpdateTabNavigation(controller->window_id(), controller->session_id(), 580 current_entry_index, 581 *controller->GetEntryAtIndex(current_entry_index)); 582 Details<NavigationController::LoadCommittedDetails> changed(details); 583 if (changed->type == NavigationType::NEW_PAGE || 584 changed->type == NavigationType::EXISTING_PAGE) { 585 RecordSessionUpdateHistogramData(NotificationType::NAV_ENTRY_COMMITTED, 586 &last_updated_nav_entry_commit_time_); 587 } 588 break; 589 } 590 591 case NotificationType::TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED: { 592 ExtensionTabHelper* extension_tab_helper = 593 Source<ExtensionTabHelper>(source).ptr(); 594 if (extension_tab_helper->extension_app()) { 595 SetTabExtensionAppID( 596 extension_tab_helper->tab_contents()->controller().window_id(), 597 extension_tab_helper->tab_contents()->controller().session_id(), 598 extension_tab_helper->extension_app()->id()); 599 } 600 break; 601 } 602 603 default: 604 NOTREACHED(); 605 } 606} 607 608void SessionService::SetTabExtensionAppID( 609 const SessionID& window_id, 610 const SessionID& tab_id, 611 const std::string& extension_app_id) { 612 if (!ShouldTrackChangesToWindow(window_id)) 613 return; 614 615 ScheduleCommand(CreateSetTabExtensionAppIDCommand( 616 kCommandSetExtensionAppID, 617 tab_id.id(), 618 extension_app_id)); 619} 620 621SessionCommand* SessionService::CreateSetSelectedTabInWindow( 622 const SessionID& window_id, 623 int index) { 624 SelectedTabInIndexPayload payload = { 0 }; 625 payload.id = window_id.id(); 626 payload.index = index; 627 SessionCommand* command = new SessionCommand(kCommandSetSelectedTabInIndex, 628 sizeof(payload)); 629 memcpy(command->contents(), &payload, sizeof(payload)); 630 return command; 631} 632 633SessionCommand* SessionService::CreateSetTabWindowCommand( 634 const SessionID& window_id, 635 const SessionID& tab_id) { 636 SessionID::id_type payload[] = { window_id.id(), tab_id.id() }; 637 SessionCommand* command = 638 new SessionCommand(kCommandSetTabWindow, sizeof(payload)); 639 memcpy(command->contents(), payload, sizeof(payload)); 640 return command; 641} 642 643SessionCommand* SessionService::CreateSetWindowBoundsCommand( 644 const SessionID& window_id, 645 const gfx::Rect& bounds, 646 bool is_maximized) { 647 WindowBoundsPayload2 payload = { 0 }; 648 payload.window_id = window_id.id(); 649 payload.x = bounds.x(); 650 payload.y = bounds.y(); 651 payload.w = bounds.width(); 652 payload.h = bounds.height(); 653 payload.is_maximized = is_maximized; 654 SessionCommand* command = new SessionCommand(kCommandSetWindowBounds2, 655 sizeof(payload)); 656 memcpy(command->contents(), &payload, sizeof(payload)); 657 return command; 658} 659 660SessionCommand* SessionService::CreateSetTabIndexInWindowCommand( 661 const SessionID& tab_id, 662 int new_index) { 663 TabIndexInWindowPayload payload = { 0 }; 664 payload.id = tab_id.id(); 665 payload.index = new_index; 666 SessionCommand* command = 667 new SessionCommand(kCommandSetTabIndexInWindow, sizeof(payload)); 668 memcpy(command->contents(), &payload, sizeof(payload)); 669 return command; 670} 671 672SessionCommand* SessionService::CreateTabClosedCommand( 673 const SessionID::id_type tab_id) { 674 ClosedPayload payload; 675 // Because of what appears to be a compiler bug setting payload to {0} doesn't 676 // set the padding to 0, resulting in Purify reporting an UMR when we write 677 // the structure to disk. To avoid this we explicitly memset the struct. 678 memset(&payload, 0, sizeof(payload)); 679 payload.id = tab_id; 680 payload.close_time = Time::Now().ToInternalValue(); 681 SessionCommand* command = 682 new SessionCommand(kCommandTabClosed, sizeof(payload)); 683 memcpy(command->contents(), &payload, sizeof(payload)); 684 return command; 685} 686 687SessionCommand* SessionService::CreateWindowClosedCommand( 688 const SessionID::id_type window_id) { 689 ClosedPayload payload; 690 // See comment in CreateTabClosedCommand as to why we do this. 691 memset(&payload, 0, sizeof(payload)); 692 payload.id = window_id; 693 payload.close_time = Time::Now().ToInternalValue(); 694 SessionCommand* command = 695 new SessionCommand(kCommandWindowClosed, sizeof(payload)); 696 memcpy(command->contents(), &payload, sizeof(payload)); 697 return command; 698} 699 700SessionCommand* SessionService::CreateSetSelectedNavigationIndexCommand( 701 const SessionID& tab_id, 702 int index) { 703 SelectedNavigationIndexPayload payload = { 0 }; 704 payload.id = tab_id.id(); 705 payload.index = index; 706 SessionCommand* command = new SessionCommand( 707 kCommandSetSelectedNavigationIndex, sizeof(payload)); 708 memcpy(command->contents(), &payload, sizeof(payload)); 709 return command; 710} 711 712SessionCommand* SessionService::CreateSetWindowTypeCommand( 713 const SessionID& window_id, 714 WindowType type) { 715 WindowTypePayload payload = { 0 }; 716 payload.id = window_id.id(); 717 payload.index = static_cast<int32>(type); 718 SessionCommand* command = new SessionCommand( 719 kCommandSetWindowType, sizeof(payload)); 720 memcpy(command->contents(), &payload, sizeof(payload)); 721 return command; 722} 723 724SessionCommand* SessionService::CreatePinnedStateCommand( 725 const SessionID& tab_id, 726 bool is_pinned) { 727 PinnedStatePayload payload = { 0 }; 728 payload.tab_id = tab_id.id(); 729 payload.pinned_state = is_pinned; 730 SessionCommand* command = 731 new SessionCommand(kCommandSetPinnedState, sizeof(payload)); 732 memcpy(command->contents(), &payload, sizeof(payload)); 733 return command; 734} 735 736void SessionService::OnGotSessionCommands( 737 Handle handle, 738 scoped_refptr<InternalGetCommandsRequest> request) { 739 if (request->canceled()) 740 return; 741 ScopedVector<SessionWindow> valid_windows; 742 RestoreSessionFromCommands( 743 request->commands, &(valid_windows.get())); 744 static_cast<InternalSessionRequest*>(request.get())-> 745 real_callback->RunWithParams( 746 SessionCallback::TupleType(request->handle(), 747 &(valid_windows.get()))); 748} 749 750void SessionService::RestoreSessionFromCommands( 751 const std::vector<SessionCommand*>& commands, 752 std::vector<SessionWindow*>* valid_windows) { 753 std::map<int, SessionTab*> tabs; 754 std::map<int, SessionWindow*> windows; 755 756 if (CreateTabsAndWindows(commands, &tabs, &windows)) { 757 AddTabsToWindows(&tabs, &windows); 758 SortTabsBasedOnVisualOrderAndPrune(&windows, valid_windows); 759 UpdateSelectedTabIndex(valid_windows); 760 } 761 STLDeleteValues(&tabs); 762 // Don't delete conents of windows, that is done by the caller as all 763 // valid windows are added to valid_windows. 764} 765 766void SessionService::UpdateSelectedTabIndex( 767 std::vector<SessionWindow*>* windows) { 768 for (std::vector<SessionWindow*>::const_iterator i = windows->begin(); 769 i != windows->end(); ++i) { 770 // See note in SessionWindow as to why we do this. 771 int new_index = 0; 772 for (std::vector<SessionTab*>::const_iterator j = (*i)->tabs.begin(); 773 j != (*i)->tabs.end(); ++j) { 774 if ((*j)->tab_visual_index == (*i)->selected_tab_index) { 775 new_index = static_cast<int>(j - (*i)->tabs.begin()); 776 break; 777 } 778 } 779 (*i)->selected_tab_index = new_index; 780 } 781} 782 783SessionWindow* SessionService::GetWindow( 784 SessionID::id_type window_id, 785 IdToSessionWindow* windows) { 786 std::map<int, SessionWindow*>::iterator i = windows->find(window_id); 787 if (i == windows->end()) { 788 SessionWindow* window = new SessionWindow(); 789 window->window_id.set_id(window_id); 790 (*windows)[window_id] = window; 791 return window; 792 } 793 return i->second; 794} 795 796SessionTab* SessionService::GetTab( 797 SessionID::id_type tab_id, 798 IdToSessionTab* tabs) { 799 DCHECK(tabs); 800 std::map<int, SessionTab*>::iterator i = tabs->find(tab_id); 801 if (i == tabs->end()) { 802 SessionTab* tab = new SessionTab(); 803 tab->tab_id.set_id(tab_id); 804 (*tabs)[tab_id] = tab; 805 return tab; 806 } 807 return i->second; 808} 809 810std::vector<TabNavigation>::iterator 811 SessionService::FindClosestNavigationWithIndex( 812 std::vector<TabNavigation>* navigations, 813 int index) { 814 DCHECK(navigations); 815 for (std::vector<TabNavigation>::iterator i = navigations->begin(); 816 i != navigations->end(); ++i) { 817 if (i->index() >= index) 818 return i; 819 } 820 return navigations->end(); 821} 822 823// Function used in sorting windows. Sorting is done based on window id. As 824// window ids increment for each new window, this effectively sorts by creation 825// time. 826static bool WindowOrderSortFunction(const SessionWindow* w1, 827 const SessionWindow* w2) { 828 return w1->window_id.id() < w2->window_id.id(); 829} 830 831// Compares the two tabs based on visual index. 832static bool TabVisualIndexSortFunction(const SessionTab* t1, 833 const SessionTab* t2) { 834 const int delta = t1->tab_visual_index - t2->tab_visual_index; 835 return delta == 0 ? (t1->tab_id.id() < t2->tab_id.id()) : (delta < 0); 836} 837 838void SessionService::SortTabsBasedOnVisualOrderAndPrune( 839 std::map<int, SessionWindow*>* windows, 840 std::vector<SessionWindow*>* valid_windows) { 841 std::map<int, SessionWindow*>::iterator i = windows->begin(); 842 while (i != windows->end()) { 843 if (i->second->tabs.empty() || i->second->is_constrained || 844 !should_track_changes_for_browser_type( 845 static_cast<Browser::Type>(i->second->type))) { 846 delete i->second; 847 windows->erase(i++); 848 } else { 849 // Valid window; sort the tabs and add it to the list of valid windows. 850 std::sort(i->second->tabs.begin(), i->second->tabs.end(), 851 &TabVisualIndexSortFunction); 852 // Add the window such that older windows appear first. 853 if (valid_windows->empty()) { 854 valid_windows->push_back(i->second); 855 } else { 856 valid_windows->insert( 857 std::upper_bound(valid_windows->begin(), valid_windows->end(), 858 i->second, &WindowOrderSortFunction), 859 i->second); 860 } 861 ++i; 862 } 863 } 864} 865 866void SessionService::AddTabsToWindows(std::map<int, SessionTab*>* tabs, 867 std::map<int, SessionWindow*>* windows) { 868 std::map<int, SessionTab*>::iterator i = tabs->begin(); 869 while (i != tabs->end()) { 870 SessionTab* tab = i->second; 871 if (tab->window_id.id() && !tab->navigations.empty()) { 872 SessionWindow* window = GetWindow(tab->window_id.id(), windows); 873 window->tabs.push_back(tab); 874 tabs->erase(i++); 875 876 // See note in SessionTab as to why we do this. 877 std::vector<TabNavigation>::iterator j = 878 FindClosestNavigationWithIndex(&(tab->navigations), 879 tab->current_navigation_index); 880 if (j == tab->navigations.end()) { 881 tab->current_navigation_index = 882 static_cast<int>(tab->navigations.size() - 1); 883 } else { 884 tab->current_navigation_index = 885 static_cast<int>(j - tab->navigations.begin()); 886 } 887 } else { 888 // Never got a set tab index in window, or tabs are empty, nothing 889 // to do. 890 ++i; 891 } 892 } 893} 894 895bool SessionService::CreateTabsAndWindows( 896 const std::vector<SessionCommand*>& data, 897 std::map<int, SessionTab*>* tabs, 898 std::map<int, SessionWindow*>* windows) { 899 // If the file is corrupt (command with wrong size, or unknown command), we 900 // still return true and attempt to restore what we we can. 901 902 for (std::vector<SessionCommand*>::const_iterator i = data.begin(); 903 i != data.end(); ++i) { 904 const SessionCommand* command = *i; 905 906 switch (command->id()) { 907 case kCommandSetTabWindow: { 908 SessionID::id_type payload[2]; 909 if (!command->GetPayload(payload, sizeof(payload))) 910 return true; 911 GetTab(payload[1], tabs)->window_id.set_id(payload[0]); 912 break; 913 } 914 915 case kCommandSetWindowBounds2: { 916 WindowBoundsPayload2 payload; 917 if (!command->GetPayload(&payload, sizeof(payload))) 918 return true; 919 GetWindow(payload.window_id, windows)->bounds.SetRect(payload.x, 920 payload.y, 921 payload.w, 922 payload.h); 923 GetWindow(payload.window_id, windows)->is_maximized = 924 payload.is_maximized; 925 break; 926 } 927 928 case kCommandSetTabIndexInWindow: { 929 TabIndexInWindowPayload payload; 930 if (!command->GetPayload(&payload, sizeof(payload))) 931 return true; 932 GetTab(payload.id, tabs)->tab_visual_index = payload.index; 933 break; 934 } 935 936 case kCommandTabClosed: 937 case kCommandWindowClosed: { 938 ClosedPayload payload; 939 if (!command->GetPayload(&payload, sizeof(payload))) 940 return true; 941 if (command->id() == kCommandTabClosed) { 942 delete GetTab(payload.id, tabs); 943 tabs->erase(payload.id); 944 } else { 945 delete GetWindow(payload.id, windows); 946 windows->erase(payload.id); 947 } 948 break; 949 } 950 951 case kCommandTabNavigationPathPrunedFromBack: { 952 TabNavigationPathPrunedFromBackPayload payload; 953 if (!command->GetPayload(&payload, sizeof(payload))) 954 return true; 955 SessionTab* tab = GetTab(payload.id, tabs); 956 tab->navigations.erase( 957 FindClosestNavigationWithIndex(&(tab->navigations), payload.index), 958 tab->navigations.end()); 959 break; 960 } 961 962 case kCommandTabNavigationPathPrunedFromFront: { 963 TabNavigationPathPrunedFromFrontPayload payload; 964 if (!command->GetPayload(&payload, sizeof(payload)) || 965 payload.index <= 0) { 966 return true; 967 } 968 SessionTab* tab = GetTab(payload.id, tabs); 969 970 // Update the selected navigation index. 971 tab->current_navigation_index = 972 std::max(-1, tab->current_navigation_index - payload.index); 973 974 // And update the index of existing navigations. 975 for (std::vector<TabNavigation>::iterator i = tab->navigations.begin(); 976 i != tab->navigations.end();) { 977 i->set_index(i->index() - payload.index); 978 if (i->index() < 0) 979 i = tab->navigations.erase(i); 980 else 981 ++i; 982 } 983 break; 984 } 985 986 case kCommandUpdateTabNavigation: { 987 TabNavigation navigation; 988 SessionID::id_type tab_id; 989 if (!RestoreUpdateTabNavigationCommand(*command, &navigation, &tab_id)) 990 return true; 991 992 SessionTab* tab = GetTab(tab_id, tabs); 993 std::vector<TabNavigation>::iterator i = 994 FindClosestNavigationWithIndex(&(tab->navigations), 995 navigation.index()); 996 if (i != tab->navigations.end() && i->index() == navigation.index()) 997 *i = navigation; 998 else 999 tab->navigations.insert(i, navigation); 1000 break; 1001 } 1002 1003 case kCommandSetSelectedNavigationIndex: { 1004 SelectedNavigationIndexPayload payload; 1005 if (!command->GetPayload(&payload, sizeof(payload))) 1006 return true; 1007 GetTab(payload.id, tabs)->current_navigation_index = payload.index; 1008 break; 1009 } 1010 1011 case kCommandSetSelectedTabInIndex: { 1012 SelectedTabInIndexPayload payload; 1013 if (!command->GetPayload(&payload, sizeof(payload))) 1014 return true; 1015 GetWindow(payload.id, windows)->selected_tab_index = payload.index; 1016 break; 1017 } 1018 1019 case kCommandSetWindowType: { 1020 WindowTypePayload payload; 1021 if (!command->GetPayload(&payload, sizeof(payload))) 1022 return true; 1023 GetWindow(payload.id, windows)->is_constrained = false; 1024 GetWindow(payload.id, windows)->type = 1025 BrowserTypeForWindowType( 1026 static_cast<WindowType>(payload.index)); 1027 break; 1028 } 1029 1030 case kCommandSetPinnedState: { 1031 PinnedStatePayload payload; 1032 if (!command->GetPayload(&payload, sizeof(payload))) 1033 return true; 1034 GetTab(payload.tab_id, tabs)->pinned = payload.pinned_state; 1035 break; 1036 } 1037 1038 case kCommandSetExtensionAppID: { 1039 SessionID::id_type tab_id; 1040 std::string extension_app_id; 1041 if (!RestoreSetTabExtensionAppIDCommand( 1042 *command, &tab_id, &extension_app_id)) { 1043 return true; 1044 } 1045 1046 GetTab(tab_id, tabs)->extension_app_id.swap(extension_app_id); 1047 break; 1048 } 1049 1050 default: 1051 return true; 1052 } 1053 } 1054 return true; 1055} 1056 1057void SessionService::BuildCommandsForTab( 1058 const SessionID& window_id, 1059 NavigationController* controller, 1060 int index_in_window, 1061 bool is_pinned, 1062 std::vector<SessionCommand*>* commands, 1063 IdToRange* tab_to_available_range) { 1064 DCHECK(controller && commands && window_id.id()); 1065 commands->push_back( 1066 CreateSetTabWindowCommand(window_id, controller->session_id())); 1067 const int current_index = controller->GetCurrentEntryIndex(); 1068 const int min_index = std::max(0, 1069 current_index - max_persist_navigation_count); 1070 const int max_index = std::min(current_index + max_persist_navigation_count, 1071 controller->entry_count()); 1072 const int pending_index = controller->pending_entry_index(); 1073 if (tab_to_available_range) { 1074 (*tab_to_available_range)[controller->session_id().id()] = 1075 std::pair<int, int>(min_index, max_index); 1076 } 1077 if (is_pinned) { 1078 commands->push_back( 1079 CreatePinnedStateCommand(controller->session_id(), true)); 1080 } 1081 TabContentsWrapper* wrapper = 1082 TabContentsWrapper::GetCurrentWrapperForContents( 1083 controller->tab_contents()); 1084 if (wrapper->extension_tab_helper()->extension_app()) { 1085 commands->push_back( 1086 CreateSetTabExtensionAppIDCommand( 1087 kCommandSetExtensionAppID, 1088 controller->session_id().id(), 1089 wrapper->extension_tab_helper()->extension_app()->id())); 1090 } 1091 for (int i = min_index; i < max_index; ++i) { 1092 const NavigationEntry* entry = (i == pending_index) ? 1093 controller->pending_entry() : controller->GetEntryAtIndex(i); 1094 DCHECK(entry); 1095 if (ShouldTrackEntry(*entry)) { 1096 commands->push_back( 1097 CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation, 1098 controller->session_id().id(), 1099 i, 1100 *entry)); 1101 } 1102 } 1103 commands->push_back( 1104 CreateSetSelectedNavigationIndexCommand(controller->session_id(), 1105 current_index)); 1106 1107 if (index_in_window != -1) { 1108 commands->push_back( 1109 CreateSetTabIndexInWindowCommand(controller->session_id(), 1110 index_in_window)); 1111 } 1112} 1113 1114void SessionService::BuildCommandsForBrowser( 1115 Browser* browser, 1116 std::vector<SessionCommand*>* commands, 1117 IdToRange* tab_to_available_range, 1118 std::set<SessionID::id_type>* windows_to_track) { 1119 DCHECK(browser && commands); 1120 DCHECK(browser->session_id().id()); 1121 1122 commands->push_back( 1123 CreateSetWindowBoundsCommand(browser->session_id(), 1124 browser->window()->GetRestoredBounds(), 1125 browser->window()->IsMaximized())); 1126 1127 commands->push_back(CreateSetWindowTypeCommand( 1128 browser->session_id(), WindowTypeForBrowserType(browser->type()))); 1129 1130 bool added_to_windows_to_track = false; 1131 for (int i = 0; i < browser->tab_count(); ++i) { 1132 TabContents* tab = browser->GetTabContentsAt(i); 1133 DCHECK(tab); 1134 if (tab->profile() == profile() || profile() == NULL) { 1135 BuildCommandsForTab(browser->session_id(), &tab->controller(), i, 1136 browser->tabstrip_model()->IsTabPinned(i), 1137 commands, tab_to_available_range); 1138 if (windows_to_track && !added_to_windows_to_track) { 1139 windows_to_track->insert(browser->session_id().id()); 1140 added_to_windows_to_track = true; 1141 } 1142 } 1143 } 1144 commands->push_back( 1145 CreateSetSelectedTabInWindow(browser->session_id(), 1146 browser->active_index())); 1147} 1148 1149void SessionService::BuildCommandsFromBrowsers( 1150 std::vector<SessionCommand*>* commands, 1151 IdToRange* tab_to_available_range, 1152 std::set<SessionID::id_type>* windows_to_track) { 1153 DCHECK(commands); 1154 for (BrowserList::const_iterator i = BrowserList::begin(); 1155 i != BrowserList::end(); ++i) { 1156 // Make sure the browser has tabs and a window. Browsers destructor 1157 // removes itself from the BrowserList. When a browser is closed the 1158 // destructor is not necessarily run immediately. This means its possible 1159 // for us to get a handle to a browser that is about to be removed. If 1160 // the tab count is 0 or the window is NULL, the browser is about to be 1161 // deleted, so we ignore it. 1162 if (should_track_changes_for_browser_type((*i)->type()) && 1163 (*i)->tab_count() && (*i)->window()) { 1164 BuildCommandsForBrowser(*i, commands, tab_to_available_range, 1165 windows_to_track); 1166 } 1167 } 1168} 1169 1170void SessionService::ScheduleReset() { 1171 set_pending_reset(true); 1172 STLDeleteElements(&pending_commands()); 1173 tab_to_available_range_.clear(); 1174 windows_tracking_.clear(); 1175 BuildCommandsFromBrowsers(&pending_commands(), &tab_to_available_range_, 1176 &windows_tracking_); 1177 if (!windows_tracking_.empty()) { 1178 // We're lazily created on startup and won't get an initial batch of 1179 // SetWindowType messages. Set these here to make sure our state is correct. 1180 has_open_trackable_browsers_ = true; 1181 move_on_new_browser_ = true; 1182 } 1183 StartSaveTimer(); 1184} 1185 1186bool SessionService::ReplacePendingCommand(SessionCommand* command) { 1187 // We only optimize page navigations, which can happen quite frequently and 1188 // are expensive. If necessary, other commands could be searched for as 1189 // well. 1190 if (command->id() != kCommandUpdateTabNavigation) 1191 return false; 1192 void* iterator = NULL; 1193 scoped_ptr<Pickle> command_pickle(command->PayloadAsPickle()); 1194 SessionID::id_type command_tab_id; 1195 int command_nav_index; 1196 if (!command_pickle->ReadInt(&iterator, &command_tab_id) || 1197 !command_pickle->ReadInt(&iterator, &command_nav_index)) { 1198 return false; 1199 } 1200 for (std::vector<SessionCommand*>::reverse_iterator i = 1201 pending_commands().rbegin(); i != pending_commands().rend(); ++i) { 1202 SessionCommand* existing_command = *i; 1203 if (existing_command->id() == kCommandUpdateTabNavigation) { 1204 SessionID::id_type existing_tab_id; 1205 int existing_nav_index; 1206 { 1207 // Creating a pickle like this means the Pickle references the data from 1208 // the command. Make sure we delete the pickle before the command, else 1209 // the pickle references deleted memory. 1210 scoped_ptr<Pickle> existing_pickle(existing_command->PayloadAsPickle()); 1211 iterator = NULL; 1212 if (!existing_pickle->ReadInt(&iterator, &existing_tab_id) || 1213 !existing_pickle->ReadInt(&iterator, &existing_nav_index)) { 1214 return false; 1215 } 1216 } 1217 if (existing_tab_id == command_tab_id && 1218 existing_nav_index == command_nav_index) { 1219 // existing_command is an update for the same tab/index pair. Replace 1220 // it with the new one. We need to add to the end of the list just in 1221 // case there is a prune command after the update command. 1222 delete existing_command; 1223 pending_commands().erase(i.base() - 1); 1224 pending_commands().push_back(command); 1225 return true; 1226 } 1227 return false; 1228 } 1229 } 1230 return false; 1231} 1232 1233void SessionService::ScheduleCommand(SessionCommand* command) { 1234 DCHECK(command); 1235 if (ReplacePendingCommand(command)) 1236 return; 1237 BaseSessionService::ScheduleCommand(command); 1238 // Don't schedule a reset on tab closed/window closed. Otherwise we may 1239 // lose tabs/windows we want to restore from if we exit right after this. 1240 if (!pending_reset() && pending_window_close_ids_.empty() && 1241 commands_since_reset() >= kWritesPerReset && 1242 (command->id() != kCommandTabClosed && 1243 command->id() != kCommandWindowClosed)) { 1244 ScheduleReset(); 1245 } 1246} 1247 1248void SessionService::CommitPendingCloses() { 1249 for (PendingTabCloseIDs::iterator i = pending_tab_close_ids_.begin(); 1250 i != pending_tab_close_ids_.end(); ++i) { 1251 ScheduleCommand(CreateTabClosedCommand(*i)); 1252 } 1253 pending_tab_close_ids_.clear(); 1254 1255 for (PendingWindowCloseIDs::iterator i = pending_window_close_ids_.begin(); 1256 i != pending_window_close_ids_.end(); ++i) { 1257 ScheduleCommand(CreateWindowClosedCommand(*i)); 1258 } 1259 pending_window_close_ids_.clear(); 1260} 1261 1262bool SessionService::IsOnlyOneTabLeft() { 1263 if (!profile()) { 1264 // We're testing, always return false. 1265 return false; 1266 } 1267 1268 int window_count = 0; 1269 for (BrowserList::const_iterator i = BrowserList::begin(); 1270 i != BrowserList::end(); ++i) { 1271 const SessionID::id_type window_id = (*i)->session_id().id(); 1272 if (should_track_changes_for_browser_type((*i)->type()) && 1273 (*i)->profile() == profile() && 1274 window_closing_ids_.find(window_id) == window_closing_ids_.end()) { 1275 if (++window_count > 1) 1276 return false; 1277 // By the time this is invoked the tab has been removed. As such, we use 1278 // > 0 here rather than > 1. 1279 if ((*i)->tab_count() > 0) 1280 return false; 1281 } 1282 } 1283 return true; 1284} 1285 1286bool SessionService::HasOpenTrackableBrowsers(const SessionID& window_id) { 1287 if (!profile()) { 1288 // We're testing, always return false. 1289 return true; 1290 } 1291 1292 for (BrowserList::const_iterator i = BrowserList::begin(); 1293 i != BrowserList::end(); ++i) { 1294 Browser* browser = *i; 1295 const SessionID::id_type browser_id = browser->session_id().id(); 1296 if (browser_id != window_id.id() && 1297 window_closing_ids_.find(browser_id) == window_closing_ids_.end() && 1298 should_track_changes_for_browser_type(browser->type()) && 1299 browser->profile() == profile()) { 1300 return true; 1301 } 1302 } 1303 return false; 1304} 1305 1306bool SessionService::ShouldTrackChangesToWindow(const SessionID& window_id) { 1307 return windows_tracking_.find(window_id.id()) != windows_tracking_.end(); 1308} 1309 1310 1311SessionService::WindowType SessionService::WindowTypeForBrowserType( 1312 Browser::Type type) { 1313 // We don't support masks here, only discrete types. 1314 switch (type) { 1315 case Browser::TYPE_POPUP: 1316 return TYPE_POPUP; 1317 case Browser::TYPE_APP: 1318 return TYPE_APP; 1319 case Browser::TYPE_APP_POPUP: 1320 return TYPE_APP_POPUP; 1321 case Browser::TYPE_DEVTOOLS: 1322 return TYPE_DEVTOOLS; 1323 case Browser::TYPE_APP_PANEL: 1324 return TYPE_APP_PANEL; 1325 case Browser::TYPE_NORMAL: 1326 default: 1327 return TYPE_NORMAL; 1328 } 1329} 1330 1331Browser::Type SessionService::BrowserTypeForWindowType( 1332 SessionService::WindowType type) { 1333 switch (type) { 1334 case TYPE_POPUP: 1335 return Browser::TYPE_POPUP; 1336 case TYPE_APP: 1337 return Browser::TYPE_APP; 1338 case TYPE_APP_POPUP: 1339 return Browser::TYPE_APP_POPUP; 1340 case TYPE_DEVTOOLS: 1341 return Browser::TYPE_DEVTOOLS; 1342 case TYPE_APP_PANEL: 1343 return Browser::TYPE_APP_PANEL; 1344 case TYPE_NORMAL: 1345 default: 1346 return Browser::TYPE_NORMAL; 1347 } 1348} 1349 1350void SessionService::RecordSessionUpdateHistogramData(NotificationType type, 1351 base::TimeTicks* last_updated_time) { 1352 if (!last_updated_time->is_null()) { 1353 base::TimeDelta delta = base::TimeTicks::Now() - *last_updated_time; 1354 // We're interested in frequent updates periods longer than 1355 // 10 minutes. 1356 bool use_long_period = false; 1357 if (delta >= save_delay_in_mins_) { 1358 use_long_period = true; 1359 } 1360 switch (type.value) { 1361 case NotificationType::SESSION_SERVICE_SAVED : 1362 RecordUpdatedSaveTime(delta, use_long_period); 1363 RecordUpdatedSessionNavigationOrTab(delta, use_long_period); 1364 break; 1365 case NotificationType::TAB_CLOSED: 1366 RecordUpdatedTabClosed(delta, use_long_period); 1367 RecordUpdatedSessionNavigationOrTab(delta, use_long_period); 1368 break; 1369 case NotificationType::NAV_LIST_PRUNED: 1370 RecordUpdatedNavListPruned(delta, use_long_period); 1371 RecordUpdatedSessionNavigationOrTab(delta, use_long_period); 1372 break; 1373 case NotificationType::NAV_ENTRY_COMMITTED: 1374 RecordUpdatedNavEntryCommit(delta, use_long_period); 1375 RecordUpdatedSessionNavigationOrTab(delta, use_long_period); 1376 break; 1377 default: 1378 NOTREACHED() << "Bad type sent to RecordSessionUpdateHistogramData"; 1379 break; 1380 } 1381 } 1382 (*last_updated_time) = base::TimeTicks::Now(); 1383} 1384 1385void SessionService::RecordUpdatedTabClosed(base::TimeDelta delta, 1386 bool use_long_period) { 1387 std::string name("SessionRestore.TabClosedPeriod"); 1388 UMA_HISTOGRAM_CUSTOM_TIMES(name, 1389 delta, 1390 // 2500ms is the default save delay. 1391 save_delay_in_millis_, 1392 save_delay_in_mins_, 1393 50); 1394 if (use_long_period) { 1395 std::string long_name_("SessionRestore.TabClosedLongPeriod"); 1396 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_, 1397 delta, 1398 save_delay_in_mins_, 1399 save_delay_in_hrs_, 1400 50); 1401 } 1402} 1403 1404void SessionService::RecordUpdatedNavListPruned(base::TimeDelta delta, 1405 bool use_long_period) { 1406 std::string name("SessionRestore.NavigationListPrunedPeriod"); 1407 UMA_HISTOGRAM_CUSTOM_TIMES(name, 1408 delta, 1409 // 2500ms is the default save delay. 1410 save_delay_in_millis_, 1411 save_delay_in_mins_, 1412 50); 1413 if (use_long_period) { 1414 std::string long_name_("SessionRestore.NavigationListPrunedLongPeriod"); 1415 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_, 1416 delta, 1417 save_delay_in_mins_, 1418 save_delay_in_hrs_, 1419 50); 1420 } 1421} 1422 1423void SessionService::RecordUpdatedNavEntryCommit(base::TimeDelta delta, 1424 bool use_long_period) { 1425 std::string name("SessionRestore.NavEntryCommittedPeriod"); 1426 UMA_HISTOGRAM_CUSTOM_TIMES(name, 1427 delta, 1428 // 2500ms is the default save delay. 1429 save_delay_in_millis_, 1430 save_delay_in_mins_, 1431 50); 1432 if (use_long_period) { 1433 std::string long_name_("SessionRestore.NavEntryCommittedLongPeriod"); 1434 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_, 1435 delta, 1436 save_delay_in_mins_, 1437 save_delay_in_hrs_, 1438 50); 1439 } 1440} 1441 1442void SessionService::RecordUpdatedSessionNavigationOrTab(base::TimeDelta delta, 1443 bool use_long_period) { 1444 std::string name("SessionRestore.NavOrTabUpdatePeriod"); 1445 UMA_HISTOGRAM_CUSTOM_TIMES(name, 1446 delta, 1447 // 2500ms is the default save delay. 1448 save_delay_in_millis_, 1449 save_delay_in_mins_, 1450 50); 1451 if (use_long_period) { 1452 std::string long_name_("SessionRestore.NavOrTabUpdateLongPeriod"); 1453 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_, 1454 delta, 1455 save_delay_in_mins_, 1456 save_delay_in_hrs_, 1457 50); 1458 } 1459} 1460 1461void SessionService::RecordUpdatedSaveTime(base::TimeDelta delta, 1462 bool use_long_period) { 1463 std::string name("SessionRestore.SavePeriod"); 1464 UMA_HISTOGRAM_CUSTOM_TIMES(name, 1465 delta, 1466 // 2500ms is the default save delay. 1467 save_delay_in_millis_, 1468 save_delay_in_mins_, 1469 50); 1470 if (use_long_period) { 1471 std::string long_name_("SessionRestore.SaveLongPeriod"); 1472 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_, 1473 delta, 1474 save_delay_in_mins_, 1475 save_delay_in_hrs_, 1476 50); 1477 } 1478} 1479