session_service.cc revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
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 "chrome/browser/sessions/session_service.h" 6 7#include <algorithm> 8#include <set> 9#include <utility> 10#include <vector> 11 12#include "base/bind.h" 13#include "base/bind_helpers.h" 14#include "base/command_line.h" 15#include "base/message_loop/message_loop.h" 16#include "base/metrics/histogram.h" 17#include "base/pickle.h" 18#include "base/threading/thread.h" 19#include "chrome/browser/background/background_mode_manager.h" 20#include "chrome/browser/browser_process.h" 21#include "chrome/browser/chrome_notification_types.h" 22#include "chrome/browser/defaults.h" 23#include "chrome/browser/extensions/tab_helper.h" 24#include "chrome/browser/prefs/session_startup_pref.h" 25#include "chrome/browser/profiles/profile.h" 26#include "chrome/browser/profiles/profile_manager.h" 27#include "chrome/browser/sessions/session_backend.h" 28#include "chrome/browser/sessions/session_command.h" 29#include "chrome/browser/sessions/session_data_deleter.h" 30#include "chrome/browser/sessions/session_restore.h" 31#include "chrome/browser/sessions/session_tab_helper.h" 32#include "chrome/browser/sessions/session_types.h" 33#include "chrome/browser/ui/browser_iterator.h" 34#include "chrome/browser/ui/browser_list.h" 35#include "chrome/browser/ui/browser_tabstrip.h" 36#include "chrome/browser/ui/browser_window.h" 37#include "chrome/browser/ui/host_desktop.h" 38#include "chrome/browser/ui/startup/startup_browser_creator.h" 39#include "chrome/browser/ui/tabs/tab_strip_model.h" 40#include "components/startup_metric_utils/startup_metric_utils.h" 41#include "content/public/browser/navigation_details.h" 42#include "content/public/browser/navigation_entry.h" 43#include "content/public/browser/notification_details.h" 44#include "content/public/browser/notification_service.h" 45#include "content/public/browser/session_storage_namespace.h" 46#include "content/public/browser/web_contents.h" 47#include "extensions/common/extension.h" 48 49#if defined(OS_MACOSX) 50#include "chrome/browser/app_controller_mac.h" 51#endif 52 53using base::Time; 54using content::NavigationEntry; 55using content::WebContents; 56using sessions::SerializedNavigationEntry; 57 58// Identifier for commands written to file. 59static const SessionCommand::id_type kCommandSetTabWindow = 0; 60// OBSOLETE Superseded by kCommandSetWindowBounds3. 61// static const SessionCommand::id_type kCommandSetWindowBounds = 1; 62static const SessionCommand::id_type kCommandSetTabIndexInWindow = 2; 63// Original kCommandTabClosed/kCommandWindowClosed. See comment in 64// MigrateClosedPayload for details on why they were replaced. 65static const SessionCommand::id_type kCommandTabClosedObsolete = 3; 66static const SessionCommand::id_type kCommandWindowClosedObsolete = 4; 67static const SessionCommand::id_type 68 kCommandTabNavigationPathPrunedFromBack = 5; 69static const SessionCommand::id_type kCommandUpdateTabNavigation = 6; 70static const SessionCommand::id_type kCommandSetSelectedNavigationIndex = 7; 71static const SessionCommand::id_type kCommandSetSelectedTabInIndex = 8; 72static const SessionCommand::id_type kCommandSetWindowType = 9; 73// OBSOLETE Superseded by kCommandSetWindowBounds3. Except for data migration. 74// static const SessionCommand::id_type kCommandSetWindowBounds2 = 10; 75static const SessionCommand::id_type 76 kCommandTabNavigationPathPrunedFromFront = 11; 77static const SessionCommand::id_type kCommandSetPinnedState = 12; 78static const SessionCommand::id_type kCommandSetExtensionAppID = 13; 79static const SessionCommand::id_type kCommandSetWindowBounds3 = 14; 80static const SessionCommand::id_type kCommandSetWindowAppName = 15; 81static const SessionCommand::id_type kCommandTabClosed = 16; 82static const SessionCommand::id_type kCommandWindowClosed = 17; 83static const SessionCommand::id_type kCommandSetTabUserAgentOverride = 18; 84static const SessionCommand::id_type kCommandSessionStorageAssociated = 19; 85static const SessionCommand::id_type kCommandSetActiveWindow = 20; 86 87// Every kWritesPerReset commands triggers recreating the file. 88static const int kWritesPerReset = 250; 89 90namespace { 91 92// Various payload structures. 93struct ClosedPayload { 94 SessionID::id_type id; 95 int64 close_time; 96}; 97 98struct WindowBoundsPayload2 { 99 SessionID::id_type window_id; 100 int32 x; 101 int32 y; 102 int32 w; 103 int32 h; 104 bool is_maximized; 105}; 106 107struct WindowBoundsPayload3 { 108 SessionID::id_type window_id; 109 int32 x; 110 int32 y; 111 int32 w; 112 int32 h; 113 int32 show_state; 114}; 115 116typedef SessionID::id_type ActiveWindowPayload; 117 118struct IDAndIndexPayload { 119 SessionID::id_type id; 120 int32 index; 121}; 122 123typedef IDAndIndexPayload TabIndexInWindowPayload; 124 125typedef IDAndIndexPayload TabNavigationPathPrunedFromBackPayload; 126 127typedef IDAndIndexPayload SelectedNavigationIndexPayload; 128 129typedef IDAndIndexPayload SelectedTabInIndexPayload; 130 131typedef IDAndIndexPayload WindowTypePayload; 132 133typedef IDAndIndexPayload TabNavigationPathPrunedFromFrontPayload; 134 135struct PinnedStatePayload { 136 SessionID::id_type tab_id; 137 bool pinned_state; 138}; 139 140// Persisted versions of ui::WindowShowState that are written to disk and can 141// never change. 142enum PersistedWindowShowState { 143 // SHOW_STATE_DEFAULT (0) never persisted. 144 PERSISTED_SHOW_STATE_NORMAL = 1, 145 PERSISTED_SHOW_STATE_MINIMIZED = 2, 146 PERSISTED_SHOW_STATE_MAXIMIZED = 3, 147 // SHOW_STATE_INACTIVE (4) never persisted. 148 PERSISTED_SHOW_STATE_FULLSCREEN = 5, 149 PERSISTED_SHOW_STATE_DETACHED_DEPRECATED = 6, 150 PERSISTED_SHOW_STATE_END = 6 151}; 152 153// Assert to ensure PersistedWindowShowState is updated if ui::WindowShowState 154// is changed. 155COMPILE_ASSERT(ui::SHOW_STATE_END == 156 static_cast<ui::WindowShowState>(PERSISTED_SHOW_STATE_END), 157 persisted_show_state_mismatch); 158 159// Returns the show state to store to disk based |state|. 160PersistedWindowShowState ShowStateToPersistedShowState( 161 ui::WindowShowState state) { 162 switch (state) { 163 case ui::SHOW_STATE_NORMAL: 164 return PERSISTED_SHOW_STATE_NORMAL; 165 case ui::SHOW_STATE_MINIMIZED: 166 return PERSISTED_SHOW_STATE_MINIMIZED; 167 case ui::SHOW_STATE_MAXIMIZED: 168 return PERSISTED_SHOW_STATE_MAXIMIZED; 169 case ui::SHOW_STATE_FULLSCREEN: 170 return PERSISTED_SHOW_STATE_FULLSCREEN; 171 172 case ui::SHOW_STATE_DEFAULT: 173 case ui::SHOW_STATE_INACTIVE: 174 return PERSISTED_SHOW_STATE_NORMAL; 175 176 case ui::SHOW_STATE_END: 177 break; 178 } 179 NOTREACHED(); 180 return PERSISTED_SHOW_STATE_NORMAL; 181} 182 183// Lints show state values when read back from persited disk. 184ui::WindowShowState PersistedShowStateToShowState(int state) { 185 switch (state) { 186 case PERSISTED_SHOW_STATE_NORMAL: 187 return ui::SHOW_STATE_NORMAL; 188 case PERSISTED_SHOW_STATE_MINIMIZED: 189 return ui::SHOW_STATE_MINIMIZED; 190 case PERSISTED_SHOW_STATE_MAXIMIZED: 191 return ui::SHOW_STATE_MAXIMIZED; 192 case PERSISTED_SHOW_STATE_FULLSCREEN: 193 return ui::SHOW_STATE_FULLSCREEN; 194 case PERSISTED_SHOW_STATE_DETACHED_DEPRECATED: 195 return ui::SHOW_STATE_NORMAL; 196 } 197 NOTREACHED(); 198 return ui::SHOW_STATE_NORMAL; 199} 200 201// Migrates a |ClosedPayload|, returning true on success (migration was 202// necessary and happened), or false (migration was not necessary or was not 203// successful). 204bool MigrateClosedPayload(const SessionCommand& command, 205 ClosedPayload* payload) { 206#if defined(OS_CHROMEOS) 207 // Pre M17 versions of chromeos were 32bit. Post M17 is 64 bit. Apparently the 208 // 32 bit versions of chrome on pre M17 resulted in a sizeof 12 for the 209 // ClosedPayload, where as post M17 64-bit gives a sizeof 16 (presumably the 210 // struct is padded). 211 if ((command.id() == kCommandWindowClosedObsolete || 212 command.id() == kCommandTabClosedObsolete) && 213 command.size() == 12 && sizeof(payload->id) == 4 && 214 sizeof(payload->close_time) == 8) { 215 memcpy(&payload->id, command.contents(), 4); 216 memcpy(&payload->close_time, command.contents() + 4, 8); 217 return true; 218 } else { 219 return false; 220 } 221#else 222 return false; 223#endif 224} 225 226} // namespace 227 228// SessionService ------------------------------------------------------------- 229 230SessionService::SessionService(Profile* profile) 231 : BaseSessionService(SESSION_RESTORE, profile, base::FilePath()), 232 has_open_trackable_browsers_(false), 233 move_on_new_browser_(false), 234 save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)), 235 save_delay_in_mins_(base::TimeDelta::FromMinutes(10)), 236 save_delay_in_hrs_(base::TimeDelta::FromHours(8)), 237 force_browser_not_alive_with_no_windows_(false) { 238 Init(); 239} 240 241SessionService::SessionService(const base::FilePath& save_path) 242 : BaseSessionService(SESSION_RESTORE, NULL, save_path), 243 has_open_trackable_browsers_(false), 244 move_on_new_browser_(false), 245 save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)), 246 save_delay_in_mins_(base::TimeDelta::FromMinutes(10)), 247 save_delay_in_hrs_(base::TimeDelta::FromHours(8)), 248 force_browser_not_alive_with_no_windows_(false) { 249 Init(); 250} 251 252SessionService::~SessionService() { 253 // The BrowserList should outlive the SessionService since it's static and 254 // the SessionService is a KeyedService. 255 BrowserList::RemoveObserver(this); 256 Save(); 257} 258 259bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open) { 260 return RestoreIfNecessary(urls_to_open, NULL); 261} 262 263void SessionService::ResetFromCurrentBrowsers() { 264 ScheduleReset(); 265} 266 267void SessionService::MoveCurrentSessionToLastSession() { 268 pending_tab_close_ids_.clear(); 269 window_closing_ids_.clear(); 270 pending_window_close_ids_.clear(); 271 272 Save(); 273 274 RunTaskOnBackendThread( 275 FROM_HERE, base::Bind(&SessionBackend::MoveCurrentSessionToLastSession, 276 backend())); 277} 278 279void SessionService::SetTabWindow(const SessionID& window_id, 280 const SessionID& tab_id) { 281 if (!ShouldTrackChangesToWindow(window_id)) 282 return; 283 284 ScheduleCommand(CreateSetTabWindowCommand(window_id, tab_id)); 285} 286 287void SessionService::SetWindowBounds(const SessionID& window_id, 288 const gfx::Rect& bounds, 289 ui::WindowShowState show_state) { 290 if (!ShouldTrackChangesToWindow(window_id)) 291 return; 292 293 ScheduleCommand(CreateSetWindowBoundsCommand(window_id, bounds, show_state)); 294} 295 296void SessionService::SetTabIndexInWindow(const SessionID& window_id, 297 const SessionID& tab_id, 298 int new_index) { 299 if (!ShouldTrackChangesToWindow(window_id)) 300 return; 301 302 ScheduleCommand(CreateSetTabIndexInWindowCommand(tab_id, new_index)); 303} 304 305void SessionService::SetPinnedState(const SessionID& window_id, 306 const SessionID& tab_id, 307 bool is_pinned) { 308 if (!ShouldTrackChangesToWindow(window_id)) 309 return; 310 311 ScheduleCommand(CreatePinnedStateCommand(tab_id, is_pinned)); 312} 313 314void SessionService::TabClosed(const SessionID& window_id, 315 const SessionID& tab_id, 316 bool closed_by_user_gesture) { 317 if (!tab_id.id()) 318 return; // Hapens when the tab is replaced. 319 320 if (!ShouldTrackChangesToWindow(window_id)) 321 return; 322 323 IdToRange::iterator i = tab_to_available_range_.find(tab_id.id()); 324 if (i != tab_to_available_range_.end()) 325 tab_to_available_range_.erase(i); 326 327 if (find(pending_window_close_ids_.begin(), pending_window_close_ids_.end(), 328 window_id.id()) != pending_window_close_ids_.end()) { 329 // Tab is in last window. Don't commit it immediately, instead add it to the 330 // list of tabs to close. If the user creates another window, the close is 331 // committed. 332 pending_tab_close_ids_.insert(tab_id.id()); 333 } else if (find(window_closing_ids_.begin(), window_closing_ids_.end(), 334 window_id.id()) != window_closing_ids_.end() || 335 !IsOnlyOneTabLeft() || 336 closed_by_user_gesture) { 337 // Close is the result of one of the following: 338 // . window close (and it isn't the last window). 339 // . closing a tab and there are other windows/tabs open. 340 // . closed by a user gesture. 341 // In all cases we need to mark the tab as explicitly closed. 342 ScheduleCommand(CreateTabClosedCommand(tab_id.id())); 343 } else { 344 // User closed the last tab in the last tabbed browser. Don't mark the 345 // tab closed. 346 pending_tab_close_ids_.insert(tab_id.id()); 347 has_open_trackable_browsers_ = false; 348 } 349} 350 351void SessionService::WindowOpened(Browser* browser) { 352 if (!ShouldTrackBrowser(browser)) 353 return; 354 355 AppType app_type = browser->is_app() ? TYPE_APP : TYPE_NORMAL; 356 RestoreIfNecessary(std::vector<GURL>(), browser); 357 SetWindowType(browser->session_id(), browser->type(), app_type); 358 SetWindowAppName(browser->session_id(), browser->app_name()); 359} 360 361void SessionService::WindowClosing(const SessionID& window_id) { 362 if (!ShouldTrackChangesToWindow(window_id)) 363 return; 364 365 // The window is about to close. If there are other tabbed browsers with the 366 // same original profile commit the close immediately. 367 // 368 // NOTE: if the user chooses the exit menu item session service is destroyed 369 // and this code isn't hit. 370 if (has_open_trackable_browsers_) { 371 // Closing a window can never make has_open_trackable_browsers_ go from 372 // false to true, so only update it if already true. 373 has_open_trackable_browsers_ = HasOpenTrackableBrowsers(window_id); 374 } 375 bool use_pending_close = !has_open_trackable_browsers_; 376 if (!use_pending_close) { 377 // Somewhat outside of "normal behavior" is profile locking. In this case 378 // (when IsSiginRequired has already been set True), we're closing all 379 // browser windows in turn but want them all to be restored when the user 380 // unlocks. To accomplish this, we do a "pending close" on all windows 381 // instead of just the last one (which has no open_trackable_browsers). 382 // http://crbug.com/356818 383 // 384 // Some editions (like iOS) don't have a profile_manager and some tests 385 // don't supply one so be lenient. 386 if (g_browser_process) { 387 ProfileManager* profile_manager = g_browser_process->profile_manager(); 388 if (profile_manager) { 389 ProfileInfoCache& profile_info = 390 profile_manager->GetProfileInfoCache(); 391 size_t profile_index = profile_info.GetIndexOfProfileWithPath( 392 profile()->GetPath()); 393 use_pending_close = profile_index != std::string::npos && 394 profile_info.ProfileIsSigninRequiredAtIndex(profile_index); 395 } 396 } 397 } 398 if (use_pending_close) 399 pending_window_close_ids_.insert(window_id.id()); 400 else 401 window_closing_ids_.insert(window_id.id()); 402} 403 404void SessionService::WindowClosed(const SessionID& window_id) { 405 if (!ShouldTrackChangesToWindow(window_id)) { 406 // The last window may be one that is not tracked. 407 MaybeDeleteSessionOnlyData(); 408 return; 409 } 410 411 windows_tracking_.erase(window_id.id()); 412 413 if (window_closing_ids_.find(window_id.id()) != window_closing_ids_.end()) { 414 window_closing_ids_.erase(window_id.id()); 415 ScheduleCommand(CreateWindowClosedCommand(window_id.id())); 416 } else if (pending_window_close_ids_.find(window_id.id()) == 417 pending_window_close_ids_.end()) { 418 // We'll hit this if user closed the last tab in a window. 419 has_open_trackable_browsers_ = HasOpenTrackableBrowsers(window_id); 420 if (!has_open_trackable_browsers_) 421 pending_window_close_ids_.insert(window_id.id()); 422 else 423 ScheduleCommand(CreateWindowClosedCommand(window_id.id())); 424 } 425 MaybeDeleteSessionOnlyData(); 426} 427 428void SessionService::SetWindowType(const SessionID& window_id, 429 Browser::Type type, 430 AppType app_type) { 431 if (!should_track_changes_for_browser_type(type, app_type)) 432 return; 433 434 windows_tracking_.insert(window_id.id()); 435 436 // The user created a new tabbed browser with our profile. Commit any 437 // pending closes. 438 CommitPendingCloses(); 439 440 has_open_trackable_browsers_ = true; 441 move_on_new_browser_ = true; 442 443 ScheduleCommand( 444 CreateSetWindowTypeCommand(window_id, WindowTypeForBrowserType(type))); 445} 446 447void SessionService::SetWindowAppName( 448 const SessionID& window_id, 449 const std::string& app_name) { 450 if (!ShouldTrackChangesToWindow(window_id)) 451 return; 452 453 ScheduleCommand(CreateSetTabExtensionAppIDCommand( 454 kCommandSetWindowAppName, 455 window_id.id(), 456 app_name)); 457} 458 459void SessionService::TabNavigationPathPrunedFromBack(const SessionID& window_id, 460 const SessionID& tab_id, 461 int count) { 462 if (!ShouldTrackChangesToWindow(window_id)) 463 return; 464 465 TabNavigationPathPrunedFromBackPayload payload = { 0 }; 466 payload.id = tab_id.id(); 467 payload.index = count; 468 SessionCommand* command = 469 new SessionCommand(kCommandTabNavigationPathPrunedFromBack, 470 sizeof(payload)); 471 memcpy(command->contents(), &payload, sizeof(payload)); 472 ScheduleCommand(command); 473} 474 475void SessionService::TabNavigationPathPrunedFromFront( 476 const SessionID& window_id, 477 const SessionID& tab_id, 478 int count) { 479 if (!ShouldTrackChangesToWindow(window_id)) 480 return; 481 482 // Update the range of indices. 483 if (tab_to_available_range_.find(tab_id.id()) != 484 tab_to_available_range_.end()) { 485 std::pair<int, int>& range = tab_to_available_range_[tab_id.id()]; 486 range.first = std::max(0, range.first - count); 487 range.second = std::max(0, range.second - count); 488 } 489 490 TabNavigationPathPrunedFromFrontPayload payload = { 0 }; 491 payload.id = tab_id.id(); 492 payload.index = count; 493 SessionCommand* command = 494 new SessionCommand(kCommandTabNavigationPathPrunedFromFront, 495 sizeof(payload)); 496 memcpy(command->contents(), &payload, sizeof(payload)); 497 ScheduleCommand(command); 498} 499 500void SessionService::UpdateTabNavigation( 501 const SessionID& window_id, 502 const SessionID& tab_id, 503 const SerializedNavigationEntry& navigation) { 504 if (!ShouldTrackEntry(navigation.virtual_url()) || 505 !ShouldTrackChangesToWindow(window_id)) { 506 return; 507 } 508 509 if (tab_to_available_range_.find(tab_id.id()) != 510 tab_to_available_range_.end()) { 511 std::pair<int, int>& range = tab_to_available_range_[tab_id.id()]; 512 range.first = std::min(navigation.index(), range.first); 513 range.second = std::max(navigation.index(), range.second); 514 } 515 ScheduleCommand(CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation, 516 tab_id.id(), navigation)); 517} 518 519void SessionService::TabRestored(WebContents* tab, bool pinned) { 520 SessionTabHelper* session_tab_helper = SessionTabHelper::FromWebContents(tab); 521 if (!ShouldTrackChangesToWindow(session_tab_helper->window_id())) 522 return; 523 524 BuildCommandsForTab(session_tab_helper->window_id(), tab, -1, 525 pinned, &pending_commands(), NULL); 526 StartSaveTimer(); 527} 528 529void SessionService::SetSelectedNavigationIndex(const SessionID& window_id, 530 const SessionID& tab_id, 531 int index) { 532 if (!ShouldTrackChangesToWindow(window_id)) 533 return; 534 535 if (tab_to_available_range_.find(tab_id.id()) != 536 tab_to_available_range_.end()) { 537 if (index < tab_to_available_range_[tab_id.id()].first || 538 index > tab_to_available_range_[tab_id.id()].second) { 539 // The new index is outside the range of what we've archived, schedule 540 // a reset. 541 ResetFromCurrentBrowsers(); 542 return; 543 } 544 } 545 ScheduleCommand(CreateSetSelectedNavigationIndexCommand(tab_id, index)); 546} 547 548void SessionService::SetSelectedTabInWindow(const SessionID& window_id, 549 int index) { 550 if (!ShouldTrackChangesToWindow(window_id)) 551 return; 552 553 ScheduleCommand(CreateSetSelectedTabInWindow(window_id, index)); 554} 555 556void SessionService::SetTabUserAgentOverride( 557 const SessionID& window_id, 558 const SessionID& tab_id, 559 const std::string& user_agent_override) { 560 if (!ShouldTrackChangesToWindow(window_id)) 561 return; 562 563 ScheduleCommand(CreateSetTabUserAgentOverrideCommand( 564 kCommandSetTabUserAgentOverride, tab_id.id(), user_agent_override)); 565} 566 567base::CancelableTaskTracker::TaskId SessionService::GetLastSession( 568 const SessionCallback& callback, 569 base::CancelableTaskTracker* tracker) { 570 // OnGotSessionCommands maps the SessionCommands to browser state, then run 571 // the callback. 572 return ScheduleGetLastSessionCommands( 573 base::Bind(&SessionService::OnGotSessionCommands, 574 base::Unretained(this), callback), 575 tracker); 576} 577 578void SessionService::Save() { 579 bool had_commands = !pending_commands().empty(); 580 BaseSessionService::Save(); 581 if (had_commands) { 582 RecordSessionUpdateHistogramData(chrome::NOTIFICATION_SESSION_SERVICE_SAVED, 583 &last_updated_save_time_); 584 content::NotificationService::current()->Notify( 585 chrome::NOTIFICATION_SESSION_SERVICE_SAVED, 586 content::Source<Profile>(profile()), 587 content::NotificationService::NoDetails()); 588 } 589} 590 591void SessionService::Init() { 592 // Register for the notifications we're interested in. 593 registrar_.Add(this, content::NOTIFICATION_NAV_LIST_PRUNED, 594 content::NotificationService::AllSources()); 595 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_CHANGED, 596 content::NotificationService::AllSources()); 597 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, 598 content::NotificationService::AllSources()); 599 registrar_.Add( 600 this, chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED, 601 content::NotificationService::AllSources()); 602 603 BrowserList::AddObserver(this); 604} 605 606bool SessionService::processed_any_commands() { 607 return backend()->inited() || !pending_commands().empty(); 608} 609 610bool SessionService::ShouldNewWindowStartSession() { 611 // ChromeOS and OSX have different ideas of application lifetime than 612 // the other platforms. 613 // On ChromeOS opening a new window should never start a new session. 614#if defined(OS_CHROMEOS) 615 if (!force_browser_not_alive_with_no_windows_) 616 return false; 617#endif 618 if (!has_open_trackable_browsers_ && 619 !StartupBrowserCreator::InSynchronousProfileLaunch() && 620 !SessionRestore::IsRestoring(profile()) 621#if defined(OS_MACOSX) 622 // On OSX, a new window should not start a new session if it was opened 623 // from the dock or the menubar. 624 && !app_controller_mac::IsOpeningNewWindow() 625#endif // OS_MACOSX 626 ) { 627 return true; 628 } 629 return false; 630} 631 632bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open, 633 Browser* browser) { 634 if (ShouldNewWindowStartSession()) { 635 // We're going from no tabbed browsers to a tabbed browser (and not in 636 // process startup), restore the last session. 637 if (move_on_new_browser_) { 638 // Make the current session the last. 639 MoveCurrentSessionToLastSession(); 640 move_on_new_browser_ = false; 641 } 642 SessionStartupPref pref = StartupBrowserCreator::GetSessionStartupPref( 643 *CommandLine::ForCurrentProcess(), profile()); 644 if (pref.type == SessionStartupPref::LAST) { 645 SessionRestore::RestoreSession( 646 profile(), browser, 647 browser ? browser->host_desktop_type() : chrome::GetActiveDesktop(), 648 browser ? 0 : SessionRestore::ALWAYS_CREATE_TABBED_BROWSER, 649 urls_to_open); 650 return true; 651 } 652 } 653 return false; 654} 655 656void SessionService::Observe(int type, 657 const content::NotificationSource& source, 658 const content::NotificationDetails& details) { 659 // All of our messages have the NavigationController as the source. 660 switch (type) { 661 case content::NOTIFICATION_NAV_LIST_PRUNED: { 662 WebContents* web_contents = 663 content::Source<content::NavigationController>(source).ptr()-> 664 GetWebContents(); 665 SessionTabHelper* session_tab_helper = 666 SessionTabHelper::FromWebContents(web_contents); 667 if (!session_tab_helper || web_contents->GetBrowserContext() != profile()) 668 return; 669 content::Details<content::PrunedDetails> pruned_details(details); 670 if (pruned_details->from_front) { 671 TabNavigationPathPrunedFromFront( 672 session_tab_helper->window_id(), 673 session_tab_helper->session_id(), 674 pruned_details->count); 675 } else { 676 TabNavigationPathPrunedFromBack( 677 session_tab_helper->window_id(), 678 session_tab_helper->session_id(), 679 web_contents->GetController().GetEntryCount()); 680 } 681 RecordSessionUpdateHistogramData(type, 682 &last_updated_nav_list_pruned_time_); 683 break; 684 } 685 686 case content::NOTIFICATION_NAV_ENTRY_CHANGED: { 687 WebContents* web_contents = 688 content::Source<content::NavigationController>(source).ptr()-> 689 GetWebContents(); 690 SessionTabHelper* session_tab_helper = 691 SessionTabHelper::FromWebContents(web_contents); 692 if (!session_tab_helper || web_contents->GetBrowserContext() != profile()) 693 return; 694 content::Details<content::EntryChangedDetails> changed(details); 695 const SerializedNavigationEntry navigation = 696 SerializedNavigationEntry::FromNavigationEntry( 697 changed->index, *changed->changed_entry); 698 UpdateTabNavigation(session_tab_helper->window_id(), 699 session_tab_helper->session_id(), 700 navigation); 701 break; 702 } 703 704 case content::NOTIFICATION_NAV_ENTRY_COMMITTED: { 705 WebContents* web_contents = 706 content::Source<content::NavigationController>(source).ptr()-> 707 GetWebContents(); 708 SessionTabHelper* session_tab_helper = 709 SessionTabHelper::FromWebContents(web_contents); 710 if (!session_tab_helper || web_contents->GetBrowserContext() != profile()) 711 return; 712 int current_entry_index = 713 web_contents->GetController().GetCurrentEntryIndex(); 714 SetSelectedNavigationIndex( 715 session_tab_helper->window_id(), 716 session_tab_helper->session_id(), 717 current_entry_index); 718 const SerializedNavigationEntry navigation = 719 SerializedNavigationEntry::FromNavigationEntry( 720 current_entry_index, 721 *web_contents->GetController().GetEntryAtIndex( 722 current_entry_index)); 723 UpdateTabNavigation( 724 session_tab_helper->window_id(), 725 session_tab_helper->session_id(), 726 navigation); 727 content::Details<content::LoadCommittedDetails> changed(details); 728 if (changed->type == content::NAVIGATION_TYPE_NEW_PAGE || 729 changed->type == content::NAVIGATION_TYPE_EXISTING_PAGE) { 730 RecordSessionUpdateHistogramData(type, 731 &last_updated_nav_entry_commit_time_); 732 } 733 break; 734 } 735 736 case chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED: { 737 extensions::TabHelper* extension_tab_helper = 738 content::Source<extensions::TabHelper>(source).ptr(); 739 if (extension_tab_helper->web_contents()->GetBrowserContext() != 740 profile()) { 741 return; 742 } 743 if (extension_tab_helper->extension_app()) { 744 SessionTabHelper* session_tab_helper = 745 SessionTabHelper::FromWebContents( 746 extension_tab_helper->web_contents()); 747 SetTabExtensionAppID(session_tab_helper->window_id(), 748 session_tab_helper->session_id(), 749 extension_tab_helper->extension_app()->id()); 750 } 751 break; 752 } 753 754 default: 755 NOTREACHED(); 756 } 757} 758 759void SessionService::OnBrowserSetLastActive(Browser* browser) { 760 if (ShouldTrackBrowser(browser)) 761 ScheduleCommand(CreateSetActiveWindowCommand(browser->session_id())); 762} 763 764void SessionService::SetTabExtensionAppID( 765 const SessionID& window_id, 766 const SessionID& tab_id, 767 const std::string& extension_app_id) { 768 if (!ShouldTrackChangesToWindow(window_id)) 769 return; 770 771 ScheduleCommand(CreateSetTabExtensionAppIDCommand(kCommandSetExtensionAppID, 772 tab_id.id(), extension_app_id)); 773} 774 775SessionCommand* SessionService::CreateSetSelectedTabInWindow( 776 const SessionID& window_id, 777 int index) { 778 SelectedTabInIndexPayload payload = { 0 }; 779 payload.id = window_id.id(); 780 payload.index = index; 781 SessionCommand* command = new SessionCommand(kCommandSetSelectedTabInIndex, 782 sizeof(payload)); 783 memcpy(command->contents(), &payload, sizeof(payload)); 784 return command; 785} 786 787SessionCommand* SessionService::CreateSetTabWindowCommand( 788 const SessionID& window_id, 789 const SessionID& tab_id) { 790 SessionID::id_type payload[] = { window_id.id(), tab_id.id() }; 791 SessionCommand* command = 792 new SessionCommand(kCommandSetTabWindow, sizeof(payload)); 793 memcpy(command->contents(), payload, sizeof(payload)); 794 return command; 795} 796 797SessionCommand* SessionService::CreateSetWindowBoundsCommand( 798 const SessionID& window_id, 799 const gfx::Rect& bounds, 800 ui::WindowShowState show_state) { 801 WindowBoundsPayload3 payload = { 0 }; 802 payload.window_id = window_id.id(); 803 payload.x = bounds.x(); 804 payload.y = bounds.y(); 805 payload.w = bounds.width(); 806 payload.h = bounds.height(); 807 payload.show_state = ShowStateToPersistedShowState(show_state); 808 SessionCommand* command = new SessionCommand(kCommandSetWindowBounds3, 809 sizeof(payload)); 810 memcpy(command->contents(), &payload, sizeof(payload)); 811 return command; 812} 813 814SessionCommand* SessionService::CreateSetTabIndexInWindowCommand( 815 const SessionID& tab_id, 816 int new_index) { 817 TabIndexInWindowPayload payload = { 0 }; 818 payload.id = tab_id.id(); 819 payload.index = new_index; 820 SessionCommand* command = 821 new SessionCommand(kCommandSetTabIndexInWindow, sizeof(payload)); 822 memcpy(command->contents(), &payload, sizeof(payload)); 823 return command; 824} 825 826SessionCommand* SessionService::CreateTabClosedCommand( 827 const SessionID::id_type tab_id) { 828 ClosedPayload payload; 829 // Because of what appears to be a compiler bug setting payload to {0} doesn't 830 // set the padding to 0, resulting in Purify reporting an UMR when we write 831 // the structure to disk. To avoid this we explicitly memset the struct. 832 memset(&payload, 0, sizeof(payload)); 833 payload.id = tab_id; 834 payload.close_time = Time::Now().ToInternalValue(); 835 SessionCommand* command = 836 new SessionCommand(kCommandTabClosed, sizeof(payload)); 837 memcpy(command->contents(), &payload, sizeof(payload)); 838 return command; 839} 840 841SessionCommand* SessionService::CreateWindowClosedCommand( 842 const SessionID::id_type window_id) { 843 ClosedPayload payload; 844 // See comment in CreateTabClosedCommand as to why we do this. 845 memset(&payload, 0, sizeof(payload)); 846 payload.id = window_id; 847 payload.close_time = Time::Now().ToInternalValue(); 848 SessionCommand* command = 849 new SessionCommand(kCommandWindowClosed, sizeof(payload)); 850 memcpy(command->contents(), &payload, sizeof(payload)); 851 return command; 852} 853 854SessionCommand* SessionService::CreateSetSelectedNavigationIndexCommand( 855 const SessionID& tab_id, 856 int index) { 857 SelectedNavigationIndexPayload payload = { 0 }; 858 payload.id = tab_id.id(); 859 payload.index = index; 860 SessionCommand* command = new SessionCommand( 861 kCommandSetSelectedNavigationIndex, sizeof(payload)); 862 memcpy(command->contents(), &payload, sizeof(payload)); 863 return command; 864} 865 866SessionCommand* SessionService::CreateSetWindowTypeCommand( 867 const SessionID& window_id, 868 WindowType type) { 869 WindowTypePayload payload = { 0 }; 870 payload.id = window_id.id(); 871 payload.index = static_cast<int32>(type); 872 SessionCommand* command = new SessionCommand( 873 kCommandSetWindowType, sizeof(payload)); 874 memcpy(command->contents(), &payload, sizeof(payload)); 875 return command; 876} 877 878SessionCommand* SessionService::CreatePinnedStateCommand( 879 const SessionID& tab_id, 880 bool is_pinned) { 881 PinnedStatePayload payload = { 0 }; 882 payload.tab_id = tab_id.id(); 883 payload.pinned_state = is_pinned; 884 SessionCommand* command = 885 new SessionCommand(kCommandSetPinnedState, sizeof(payload)); 886 memcpy(command->contents(), &payload, sizeof(payload)); 887 return command; 888} 889 890SessionCommand* SessionService::CreateSessionStorageAssociatedCommand( 891 const SessionID& tab_id, 892 const std::string& session_storage_persistent_id) { 893 Pickle pickle; 894 pickle.WriteInt(tab_id.id()); 895 pickle.WriteString(session_storage_persistent_id); 896 return new SessionCommand(kCommandSessionStorageAssociated, pickle); 897} 898 899SessionCommand* SessionService::CreateSetActiveWindowCommand( 900 const SessionID& window_id) { 901 ActiveWindowPayload payload = 0; 902 payload = window_id.id(); 903 SessionCommand* command = 904 new SessionCommand(kCommandSetActiveWindow, sizeof(payload)); 905 memcpy(command->contents(), &payload, sizeof(payload)); 906 return command; 907} 908 909void SessionService::OnGotSessionCommands( 910 const SessionCallback& callback, 911 ScopedVector<SessionCommand> commands) { 912 ScopedVector<SessionWindow> valid_windows; 913 SessionID::id_type active_window_id = 0; 914 915 RestoreSessionFromCommands( 916 commands.get(), &valid_windows.get(), &active_window_id); 917 callback.Run(valid_windows.Pass(), active_window_id); 918} 919 920void SessionService::RestoreSessionFromCommands( 921 const std::vector<SessionCommand*>& commands, 922 std::vector<SessionWindow*>* valid_windows, 923 SessionID::id_type* active_window_id) { 924 std::map<int, SessionTab*> tabs; 925 std::map<int, SessionWindow*> windows; 926 927 VLOG(1) << "RestoreSessionFromCommands " << commands.size(); 928 if (CreateTabsAndWindows(commands, &tabs, &windows, active_window_id)) { 929 AddTabsToWindows(&tabs, &windows); 930 SortTabsBasedOnVisualOrderAndPrune(&windows, valid_windows); 931 UpdateSelectedTabIndex(valid_windows); 932 } 933 STLDeleteValues(&tabs); 934 // Don't delete conents of windows, that is done by the caller as all 935 // valid windows are added to valid_windows. 936} 937 938void SessionService::UpdateSelectedTabIndex( 939 std::vector<SessionWindow*>* windows) { 940 for (std::vector<SessionWindow*>::const_iterator i = windows->begin(); 941 i != windows->end(); ++i) { 942 // See note in SessionWindow as to why we do this. 943 int new_index = 0; 944 for (std::vector<SessionTab*>::const_iterator j = (*i)->tabs.begin(); 945 j != (*i)->tabs.end(); ++j) { 946 if ((*j)->tab_visual_index == (*i)->selected_tab_index) { 947 new_index = static_cast<int>(j - (*i)->tabs.begin()); 948 break; 949 } 950 } 951 (*i)->selected_tab_index = new_index; 952 } 953} 954 955SessionWindow* SessionService::GetWindow( 956 SessionID::id_type window_id, 957 IdToSessionWindow* windows) { 958 std::map<int, SessionWindow*>::iterator i = windows->find(window_id); 959 if (i == windows->end()) { 960 SessionWindow* window = new SessionWindow(); 961 window->window_id.set_id(window_id); 962 (*windows)[window_id] = window; 963 return window; 964 } 965 return i->second; 966} 967 968SessionTab* SessionService::GetTab( 969 SessionID::id_type tab_id, 970 IdToSessionTab* tabs) { 971 DCHECK(tabs); 972 std::map<int, SessionTab*>::iterator i = tabs->find(tab_id); 973 if (i == tabs->end()) { 974 SessionTab* tab = new SessionTab(); 975 tab->tab_id.set_id(tab_id); 976 (*tabs)[tab_id] = tab; 977 return tab; 978 } 979 return i->second; 980} 981 982std::vector<SerializedNavigationEntry>::iterator 983 SessionService::FindClosestNavigationWithIndex( 984 std::vector<SerializedNavigationEntry>* navigations, 985 int index) { 986 DCHECK(navigations); 987 for (std::vector<SerializedNavigationEntry>::iterator 988 i = navigations->begin(); i != navigations->end(); ++i) { 989 if (i->index() >= index) 990 return i; 991 } 992 return navigations->end(); 993} 994 995// Function used in sorting windows. Sorting is done based on window id. As 996// window ids increment for each new window, this effectively sorts by creation 997// time. 998static bool WindowOrderSortFunction(const SessionWindow* w1, 999 const SessionWindow* w2) { 1000 return w1->window_id.id() < w2->window_id.id(); 1001} 1002 1003// Compares the two tabs based on visual index. 1004static bool TabVisualIndexSortFunction(const SessionTab* t1, 1005 const SessionTab* t2) { 1006 const int delta = t1->tab_visual_index - t2->tab_visual_index; 1007 return delta == 0 ? (t1->tab_id.id() < t2->tab_id.id()) : (delta < 0); 1008} 1009 1010void SessionService::SortTabsBasedOnVisualOrderAndPrune( 1011 std::map<int, SessionWindow*>* windows, 1012 std::vector<SessionWindow*>* valid_windows) { 1013 std::map<int, SessionWindow*>::iterator i = windows->begin(); 1014 while (i != windows->end()) { 1015 SessionWindow* window = i->second; 1016 AppType app_type = window->app_name.empty() ? TYPE_NORMAL : TYPE_APP; 1017 if (window->tabs.empty() || window->is_constrained || 1018 !should_track_changes_for_browser_type( 1019 static_cast<Browser::Type>(window->type), 1020 app_type)) { 1021 delete window; 1022 windows->erase(i++); 1023 } else { 1024 // Valid window; sort the tabs and add it to the list of valid windows. 1025 std::sort(window->tabs.begin(), window->tabs.end(), 1026 &TabVisualIndexSortFunction); 1027 // Otherwise, add the window such that older windows appear first. 1028 if (valid_windows->empty()) { 1029 valid_windows->push_back(window); 1030 } else { 1031 valid_windows->insert( 1032 std::upper_bound(valid_windows->begin(), valid_windows->end(), 1033 window, &WindowOrderSortFunction), 1034 window); 1035 } 1036 ++i; 1037 } 1038 } 1039} 1040 1041void SessionService::AddTabsToWindows(std::map<int, SessionTab*>* tabs, 1042 std::map<int, SessionWindow*>* windows) { 1043 VLOG(1) << "AddTabsToWindws"; 1044 VLOG(1) << "Tabs " << tabs->size() << ", windows " << windows->size(); 1045 std::map<int, SessionTab*>::iterator i = tabs->begin(); 1046 while (i != tabs->end()) { 1047 SessionTab* tab = i->second; 1048 if (tab->window_id.id() && !tab->navigations.empty()) { 1049 SessionWindow* window = GetWindow(tab->window_id.id(), windows); 1050 window->tabs.push_back(tab); 1051 tabs->erase(i++); 1052 1053 // See note in SessionTab as to why we do this. 1054 std::vector<SerializedNavigationEntry>::iterator j = 1055 FindClosestNavigationWithIndex(&(tab->navigations), 1056 tab->current_navigation_index); 1057 if (j == tab->navigations.end()) { 1058 tab->current_navigation_index = 1059 static_cast<int>(tab->navigations.size() - 1); 1060 } else { 1061 tab->current_navigation_index = 1062 static_cast<int>(j - tab->navigations.begin()); 1063 } 1064 } else { 1065 // Never got a set tab index in window, or tabs are empty, nothing 1066 // to do. 1067 ++i; 1068 } 1069 } 1070} 1071 1072bool SessionService::CreateTabsAndWindows( 1073 const std::vector<SessionCommand*>& data, 1074 std::map<int, SessionTab*>* tabs, 1075 std::map<int, SessionWindow*>* windows, 1076 SessionID::id_type* active_window_id) { 1077 // If the file is corrupt (command with wrong size, or unknown command), we 1078 // still return true and attempt to restore what we we can. 1079 VLOG(1) << "CreateTabsAndWindows"; 1080 1081 startup_metric_utils::ScopedSlowStartupUMA 1082 scoped_timer("Startup.SlowStartupSessionServiceCreateTabsAndWindows"); 1083 1084 for (std::vector<SessionCommand*>::const_iterator i = data.begin(); 1085 i != data.end(); ++i) { 1086 const SessionCommand::id_type kCommandSetWindowBounds2 = 10; 1087 const SessionCommand* command = *i; 1088 1089 VLOG(1) << "Read command " << (int) command->id(); 1090 switch (command->id()) { 1091 case kCommandSetTabWindow: { 1092 SessionID::id_type payload[2]; 1093 if (!command->GetPayload(payload, sizeof(payload))) { 1094 VLOG(1) << "Failed reading command " << command->id(); 1095 return true; 1096 } 1097 GetTab(payload[1], tabs)->window_id.set_id(payload[0]); 1098 break; 1099 } 1100 1101 // This is here for forward migration only. New data is saved with 1102 // |kCommandSetWindowBounds3|. 1103 case kCommandSetWindowBounds2: { 1104 WindowBoundsPayload2 payload; 1105 if (!command->GetPayload(&payload, sizeof(payload))) { 1106 VLOG(1) << "Failed reading command " << command->id(); 1107 return true; 1108 } 1109 GetWindow(payload.window_id, windows)->bounds.SetRect(payload.x, 1110 payload.y, 1111 payload.w, 1112 payload.h); 1113 GetWindow(payload.window_id, windows)->show_state = 1114 payload.is_maximized ? 1115 ui::SHOW_STATE_MAXIMIZED : ui::SHOW_STATE_NORMAL; 1116 break; 1117 } 1118 1119 case kCommandSetWindowBounds3: { 1120 WindowBoundsPayload3 payload; 1121 if (!command->GetPayload(&payload, sizeof(payload))) { 1122 VLOG(1) << "Failed reading command " << command->id(); 1123 return true; 1124 } 1125 GetWindow(payload.window_id, windows)->bounds.SetRect(payload.x, 1126 payload.y, 1127 payload.w, 1128 payload.h); 1129 GetWindow(payload.window_id, windows)->show_state = 1130 PersistedShowStateToShowState(payload.show_state); 1131 break; 1132 } 1133 1134 case kCommandSetTabIndexInWindow: { 1135 TabIndexInWindowPayload payload; 1136 if (!command->GetPayload(&payload, sizeof(payload))) { 1137 VLOG(1) << "Failed reading command " << command->id(); 1138 return true; 1139 } 1140 GetTab(payload.id, tabs)->tab_visual_index = payload.index; 1141 break; 1142 } 1143 1144 case kCommandTabClosedObsolete: 1145 case kCommandWindowClosedObsolete: 1146 case kCommandTabClosed: 1147 case kCommandWindowClosed: { 1148 ClosedPayload payload; 1149 if (!command->GetPayload(&payload, sizeof(payload)) && 1150 !MigrateClosedPayload(*command, &payload)) { 1151 VLOG(1) << "Failed reading command " << command->id(); 1152 return true; 1153 } 1154 if (command->id() == kCommandTabClosed || 1155 command->id() == kCommandTabClosedObsolete) { 1156 delete GetTab(payload.id, tabs); 1157 tabs->erase(payload.id); 1158 } else { 1159 delete GetWindow(payload.id, windows); 1160 windows->erase(payload.id); 1161 } 1162 break; 1163 } 1164 1165 case kCommandTabNavigationPathPrunedFromBack: { 1166 TabNavigationPathPrunedFromBackPayload payload; 1167 if (!command->GetPayload(&payload, sizeof(payload))) { 1168 VLOG(1) << "Failed reading command " << command->id(); 1169 return true; 1170 } 1171 SessionTab* tab = GetTab(payload.id, tabs); 1172 tab->navigations.erase( 1173 FindClosestNavigationWithIndex(&(tab->navigations), payload.index), 1174 tab->navigations.end()); 1175 break; 1176 } 1177 1178 case kCommandTabNavigationPathPrunedFromFront: { 1179 TabNavigationPathPrunedFromFrontPayload payload; 1180 if (!command->GetPayload(&payload, sizeof(payload)) || 1181 payload.index <= 0) { 1182 VLOG(1) << "Failed reading command " << command->id(); 1183 return true; 1184 } 1185 SessionTab* tab = GetTab(payload.id, tabs); 1186 1187 // Update the selected navigation index. 1188 tab->current_navigation_index = 1189 std::max(-1, tab->current_navigation_index - payload.index); 1190 1191 // And update the index of existing navigations. 1192 for (std::vector<SerializedNavigationEntry>::iterator 1193 i = tab->navigations.begin(); 1194 i != tab->navigations.end();) { 1195 i->set_index(i->index() - payload.index); 1196 if (i->index() < 0) 1197 i = tab->navigations.erase(i); 1198 else 1199 ++i; 1200 } 1201 break; 1202 } 1203 1204 case kCommandUpdateTabNavigation: { 1205 SerializedNavigationEntry navigation; 1206 SessionID::id_type tab_id; 1207 if (!RestoreUpdateTabNavigationCommand( 1208 *command, &navigation, &tab_id)) { 1209 VLOG(1) << "Failed reading command " << command->id(); 1210 return true; 1211 } 1212 SessionTab* tab = GetTab(tab_id, tabs); 1213 std::vector<SerializedNavigationEntry>::iterator i = 1214 FindClosestNavigationWithIndex(&(tab->navigations), 1215 navigation.index()); 1216 if (i != tab->navigations.end() && i->index() == navigation.index()) 1217 *i = navigation; 1218 else 1219 tab->navigations.insert(i, navigation); 1220 break; 1221 } 1222 1223 case kCommandSetSelectedNavigationIndex: { 1224 SelectedNavigationIndexPayload payload; 1225 if (!command->GetPayload(&payload, sizeof(payload))) { 1226 VLOG(1) << "Failed reading command " << command->id(); 1227 return true; 1228 } 1229 GetTab(payload.id, tabs)->current_navigation_index = payload.index; 1230 break; 1231 } 1232 1233 case kCommandSetSelectedTabInIndex: { 1234 SelectedTabInIndexPayload payload; 1235 if (!command->GetPayload(&payload, sizeof(payload))) { 1236 VLOG(1) << "Failed reading command " << command->id(); 1237 return true; 1238 } 1239 GetWindow(payload.id, windows)->selected_tab_index = payload.index; 1240 break; 1241 } 1242 1243 case kCommandSetWindowType: { 1244 WindowTypePayload payload; 1245 if (!command->GetPayload(&payload, sizeof(payload))) { 1246 VLOG(1) << "Failed reading command " << command->id(); 1247 return true; 1248 } 1249 GetWindow(payload.id, windows)->is_constrained = false; 1250 GetWindow(payload.id, windows)->type = 1251 BrowserTypeForWindowType( 1252 static_cast<WindowType>(payload.index)); 1253 break; 1254 } 1255 1256 case kCommandSetPinnedState: { 1257 PinnedStatePayload payload; 1258 if (!command->GetPayload(&payload, sizeof(payload))) { 1259 VLOG(1) << "Failed reading command " << command->id(); 1260 return true; 1261 } 1262 GetTab(payload.tab_id, tabs)->pinned = payload.pinned_state; 1263 break; 1264 } 1265 1266 case kCommandSetWindowAppName: { 1267 SessionID::id_type window_id; 1268 std::string app_name; 1269 if (!RestoreSetWindowAppNameCommand(*command, &window_id, &app_name)) 1270 return true; 1271 1272 GetWindow(window_id, windows)->app_name.swap(app_name); 1273 break; 1274 } 1275 1276 case kCommandSetExtensionAppID: { 1277 SessionID::id_type tab_id; 1278 std::string extension_app_id; 1279 if (!RestoreSetTabExtensionAppIDCommand( 1280 *command, &tab_id, &extension_app_id)) { 1281 VLOG(1) << "Failed reading command " << command->id(); 1282 return true; 1283 } 1284 1285 GetTab(tab_id, tabs)->extension_app_id.swap(extension_app_id); 1286 break; 1287 } 1288 1289 case kCommandSetTabUserAgentOverride: { 1290 SessionID::id_type tab_id; 1291 std::string user_agent_override; 1292 if (!RestoreSetTabUserAgentOverrideCommand( 1293 *command, &tab_id, &user_agent_override)) { 1294 return true; 1295 } 1296 1297 GetTab(tab_id, tabs)->user_agent_override.swap(user_agent_override); 1298 break; 1299 } 1300 1301 case kCommandSessionStorageAssociated: { 1302 scoped_ptr<Pickle> command_pickle(command->PayloadAsPickle()); 1303 SessionID::id_type command_tab_id; 1304 std::string session_storage_persistent_id; 1305 PickleIterator iter(*command_pickle.get()); 1306 if (!command_pickle->ReadInt(&iter, &command_tab_id) || 1307 !command_pickle->ReadString(&iter, &session_storage_persistent_id)) 1308 return true; 1309 // Associate the session storage back. 1310 GetTab(command_tab_id, tabs)->session_storage_persistent_id = 1311 session_storage_persistent_id; 1312 break; 1313 } 1314 1315 case kCommandSetActiveWindow: { 1316 ActiveWindowPayload payload; 1317 if (!command->GetPayload(&payload, sizeof(payload))) { 1318 VLOG(1) << "Failed reading command " << command->id(); 1319 return true; 1320 } 1321 *active_window_id = payload; 1322 break; 1323 } 1324 1325 default: 1326 VLOG(1) << "Failed reading an unknown command " << command->id(); 1327 return true; 1328 } 1329 } 1330 return true; 1331} 1332 1333void SessionService::BuildCommandsForTab(const SessionID& window_id, 1334 WebContents* tab, 1335 int index_in_window, 1336 bool is_pinned, 1337 std::vector<SessionCommand*>* commands, 1338 IdToRange* tab_to_available_range) { 1339 DCHECK(tab && commands && window_id.id()); 1340 SessionTabHelper* session_tab_helper = SessionTabHelper::FromWebContents(tab); 1341 const SessionID& session_id(session_tab_helper->session_id()); 1342 commands->push_back(CreateSetTabWindowCommand(window_id, session_id)); 1343 1344 const int current_index = tab->GetController().GetCurrentEntryIndex(); 1345 const int min_index = std::max(0, 1346 current_index - max_persist_navigation_count); 1347 const int max_index = 1348 std::min(current_index + max_persist_navigation_count, 1349 tab->GetController().GetEntryCount()); 1350 const int pending_index = tab->GetController().GetPendingEntryIndex(); 1351 if (tab_to_available_range) { 1352 (*tab_to_available_range)[session_id.id()] = 1353 std::pair<int, int>(min_index, max_index); 1354 } 1355 1356 if (is_pinned) { 1357 commands->push_back(CreatePinnedStateCommand(session_id, true)); 1358 } 1359 1360 extensions::TabHelper* extensions_tab_helper = 1361 extensions::TabHelper::FromWebContents(tab); 1362 if (extensions_tab_helper->extension_app()) { 1363 commands->push_back( 1364 CreateSetTabExtensionAppIDCommand( 1365 kCommandSetExtensionAppID, session_id.id(), 1366 extensions_tab_helper->extension_app()->id())); 1367 } 1368 1369 const std::string& ua_override = tab->GetUserAgentOverride(); 1370 if (!ua_override.empty()) { 1371 commands->push_back( 1372 CreateSetTabUserAgentOverrideCommand( 1373 kCommandSetTabUserAgentOverride, session_id.id(), ua_override)); 1374 } 1375 1376 for (int i = min_index; i < max_index; ++i) { 1377 const NavigationEntry* entry = (i == pending_index) ? 1378 tab->GetController().GetPendingEntry() : 1379 tab->GetController().GetEntryAtIndex(i); 1380 DCHECK(entry); 1381 if (ShouldTrackEntry(entry->GetVirtualURL())) { 1382 const SerializedNavigationEntry navigation = 1383 SerializedNavigationEntry::FromNavigationEntry(i, *entry); 1384 commands->push_back( 1385 CreateUpdateTabNavigationCommand( 1386 kCommandUpdateTabNavigation, session_id.id(), navigation)); 1387 } 1388 } 1389 commands->push_back( 1390 CreateSetSelectedNavigationIndexCommand(session_id, current_index)); 1391 1392 if (index_in_window != -1) { 1393 commands->push_back( 1394 CreateSetTabIndexInWindowCommand(session_id, index_in_window)); 1395 } 1396 1397 // Record the association between the sessionStorage namespace and the tab. 1398 content::SessionStorageNamespace* session_storage_namespace = 1399 tab->GetController().GetDefaultSessionStorageNamespace(); 1400 ScheduleCommand(CreateSessionStorageAssociatedCommand( 1401 session_tab_helper->session_id(), 1402 session_storage_namespace->persistent_id())); 1403} 1404 1405void SessionService::BuildCommandsForBrowser( 1406 Browser* browser, 1407 std::vector<SessionCommand*>* commands, 1408 IdToRange* tab_to_available_range, 1409 std::set<SessionID::id_type>* windows_to_track) { 1410 DCHECK(browser && commands); 1411 DCHECK(browser->session_id().id()); 1412 1413 commands->push_back( 1414 CreateSetWindowBoundsCommand(browser->session_id(), 1415 browser->window()->GetRestoredBounds(), 1416 browser->window()->GetRestoredState())); 1417 1418 commands->push_back(CreateSetWindowTypeCommand( 1419 browser->session_id(), WindowTypeForBrowserType(browser->type()))); 1420 1421 if (!browser->app_name().empty()) { 1422 commands->push_back(CreateSetWindowAppNameCommand( 1423 kCommandSetWindowAppName, 1424 browser->session_id().id(), 1425 browser->app_name())); 1426 } 1427 1428 windows_to_track->insert(browser->session_id().id()); 1429 TabStripModel* tab_strip = browser->tab_strip_model(); 1430 for (int i = 0; i < tab_strip->count(); ++i) { 1431 WebContents* tab = tab_strip->GetWebContentsAt(i); 1432 DCHECK(tab); 1433 BuildCommandsForTab(browser->session_id(), tab, i, 1434 tab_strip->IsTabPinned(i), 1435 commands, tab_to_available_range); 1436 } 1437 1438 commands->push_back( 1439 CreateSetSelectedTabInWindow(browser->session_id(), 1440 browser->tab_strip_model()->active_index())); 1441} 1442 1443void SessionService::BuildCommandsFromBrowsers( 1444 std::vector<SessionCommand*>* commands, 1445 IdToRange* tab_to_available_range, 1446 std::set<SessionID::id_type>* windows_to_track) { 1447 DCHECK(commands); 1448 for (chrome::BrowserIterator it; !it.done(); it.Next()) { 1449 Browser* browser = *it; 1450 // Make sure the browser has tabs and a window. Browser's destructor 1451 // removes itself from the BrowserList. When a browser is closed the 1452 // destructor is not necessarily run immediately. This means it's possible 1453 // for us to get a handle to a browser that is about to be removed. If 1454 // the tab count is 0 or the window is NULL, the browser is about to be 1455 // deleted, so we ignore it. 1456 if (ShouldTrackBrowser(browser) && browser->tab_strip_model()->count() && 1457 browser->window()) { 1458 BuildCommandsForBrowser(browser, commands, tab_to_available_range, 1459 windows_to_track); 1460 } 1461 } 1462} 1463 1464void SessionService::ScheduleReset() { 1465 set_pending_reset(true); 1466 STLDeleteElements(&pending_commands()); 1467 tab_to_available_range_.clear(); 1468 windows_tracking_.clear(); 1469 BuildCommandsFromBrowsers(&pending_commands(), &tab_to_available_range_, 1470 &windows_tracking_); 1471 if (!windows_tracking_.empty()) { 1472 // We're lazily created on startup and won't get an initial batch of 1473 // SetWindowType messages. Set these here to make sure our state is correct. 1474 has_open_trackable_browsers_ = true; 1475 move_on_new_browser_ = true; 1476 } 1477 StartSaveTimer(); 1478} 1479 1480bool SessionService::ReplacePendingCommand(SessionCommand* command) { 1481 // We optimize page navigations, which can happen quite frequently and 1482 // are expensive. And activation is like Highlander, there can only be one! 1483 if (command->id() != kCommandUpdateTabNavigation && 1484 command->id() != kCommandSetActiveWindow) { 1485 return false; 1486 } 1487 for (std::vector<SessionCommand*>::reverse_iterator i = 1488 pending_commands().rbegin(); i != pending_commands().rend(); ++i) { 1489 SessionCommand* existing_command = *i; 1490 if (command->id() == kCommandUpdateTabNavigation && 1491 existing_command->id() == kCommandUpdateTabNavigation) { 1492 scoped_ptr<Pickle> command_pickle(command->PayloadAsPickle()); 1493 PickleIterator iterator(*command_pickle); 1494 SessionID::id_type command_tab_id; 1495 int command_nav_index; 1496 if (!command_pickle->ReadInt(&iterator, &command_tab_id) || 1497 !command_pickle->ReadInt(&iterator, &command_nav_index)) { 1498 return false; 1499 } 1500 SessionID::id_type existing_tab_id; 1501 int existing_nav_index; 1502 { 1503 // Creating a pickle like this means the Pickle references the data from 1504 // the command. Make sure we delete the pickle before the command, else 1505 // the pickle references deleted memory. 1506 scoped_ptr<Pickle> existing_pickle(existing_command->PayloadAsPickle()); 1507 iterator = PickleIterator(*existing_pickle); 1508 if (!existing_pickle->ReadInt(&iterator, &existing_tab_id) || 1509 !existing_pickle->ReadInt(&iterator, &existing_nav_index)) { 1510 return false; 1511 } 1512 } 1513 if (existing_tab_id == command_tab_id && 1514 existing_nav_index == command_nav_index) { 1515 // existing_command is an update for the same tab/index pair. Replace 1516 // it with the new one. We need to add to the end of the list just in 1517 // case there is a prune command after the update command. 1518 delete existing_command; 1519 pending_commands().erase(i.base() - 1); 1520 pending_commands().push_back(command); 1521 return true; 1522 } 1523 return false; 1524 } 1525 if (command->id() == kCommandSetActiveWindow && 1526 existing_command->id() == kCommandSetActiveWindow) { 1527 *i = command; 1528 delete existing_command; 1529 return true; 1530 } 1531 } 1532 return false; 1533} 1534 1535void SessionService::ScheduleCommand(SessionCommand* command) { 1536 DCHECK(command); 1537 if (ReplacePendingCommand(command)) 1538 return; 1539 BaseSessionService::ScheduleCommand(command); 1540 // Don't schedule a reset on tab closed/window closed. Otherwise we may 1541 // lose tabs/windows we want to restore from if we exit right after this. 1542 if (!pending_reset() && pending_window_close_ids_.empty() && 1543 commands_since_reset() >= kWritesPerReset && 1544 (command->id() != kCommandTabClosed && 1545 command->id() != kCommandWindowClosed)) { 1546 ScheduleReset(); 1547 } 1548} 1549 1550void SessionService::CommitPendingCloses() { 1551 for (PendingTabCloseIDs::iterator i = pending_tab_close_ids_.begin(); 1552 i != pending_tab_close_ids_.end(); ++i) { 1553 ScheduleCommand(CreateTabClosedCommand(*i)); 1554 } 1555 pending_tab_close_ids_.clear(); 1556 1557 for (PendingWindowCloseIDs::iterator i = pending_window_close_ids_.begin(); 1558 i != pending_window_close_ids_.end(); ++i) { 1559 ScheduleCommand(CreateWindowClosedCommand(*i)); 1560 } 1561 pending_window_close_ids_.clear(); 1562} 1563 1564bool SessionService::IsOnlyOneTabLeft() const { 1565 if (!profile() || profile()->AsTestingProfile()) { 1566 // We're testing, always return false. 1567 return false; 1568 } 1569 1570 int window_count = 0; 1571 for (chrome::BrowserIterator it; !it.done(); it.Next()) { 1572 Browser* browser = *it; 1573 const SessionID::id_type window_id = browser->session_id().id(); 1574 if (ShouldTrackBrowser(browser) && 1575 window_closing_ids_.find(window_id) == window_closing_ids_.end()) { 1576 if (++window_count > 1) 1577 return false; 1578 // By the time this is invoked the tab has been removed. As such, we use 1579 // > 0 here rather than > 1. 1580 if (browser->tab_strip_model()->count() > 0) 1581 return false; 1582 } 1583 } 1584 return true; 1585} 1586 1587bool SessionService::HasOpenTrackableBrowsers( 1588 const SessionID& window_id) const { 1589 if (!profile() || profile()->AsTestingProfile()) { 1590 // We're testing, always return true. 1591 return true; 1592 } 1593 1594 for (chrome::BrowserIterator it; !it.done(); it.Next()) { 1595 Browser* browser = *it; 1596 const SessionID::id_type browser_id = browser->session_id().id(); 1597 if (browser_id != window_id.id() && 1598 window_closing_ids_.find(browser_id) == window_closing_ids_.end() && 1599 ShouldTrackBrowser(browser)) { 1600 return true; 1601 } 1602 } 1603 return false; 1604} 1605 1606bool SessionService::ShouldTrackChangesToWindow( 1607 const SessionID& window_id) const { 1608 return windows_tracking_.find(window_id.id()) != windows_tracking_.end(); 1609} 1610 1611bool SessionService::ShouldTrackBrowser(Browser* browser) const { 1612 if (browser->profile() != profile()) 1613 return false; 1614 // Never track app popup windows that do not have a trusted source (i.e. 1615 // popup windows spawned by an app). If this logic changes, be sure to also 1616 // change SessionRestoreImpl::CreateRestoredBrowser(). 1617 if (browser->is_app() && browser->is_type_popup() && 1618 !browser->is_trusted_source()) { 1619 return false; 1620 } 1621 AppType app_type = browser->is_app() ? TYPE_APP : TYPE_NORMAL; 1622 return should_track_changes_for_browser_type(browser->type(), app_type); 1623} 1624 1625bool SessionService::should_track_changes_for_browser_type(Browser::Type type, 1626 AppType app_type) { 1627#if defined(OS_CHROMEOS) 1628 // Restore app popups for chromeos alone. 1629 if (type == Browser::TYPE_POPUP && app_type == TYPE_APP) 1630 return true; 1631#endif 1632 1633 return type == Browser::TYPE_TABBED; 1634} 1635 1636SessionService::WindowType SessionService::WindowTypeForBrowserType( 1637 Browser::Type type) { 1638 switch (type) { 1639 case Browser::TYPE_POPUP: 1640 return TYPE_POPUP; 1641 case Browser::TYPE_TABBED: 1642 return TYPE_TABBED; 1643 default: 1644 DCHECK(false); 1645 return TYPE_TABBED; 1646 } 1647} 1648 1649Browser::Type SessionService::BrowserTypeForWindowType(WindowType type) { 1650 switch (type) { 1651 case TYPE_POPUP: 1652 return Browser::TYPE_POPUP; 1653 case TYPE_TABBED: 1654 default: 1655 return Browser::TYPE_TABBED; 1656 } 1657} 1658 1659void SessionService::RecordSessionUpdateHistogramData(int type, 1660 base::TimeTicks* last_updated_time) { 1661 if (!last_updated_time->is_null()) { 1662 base::TimeDelta delta = base::TimeTicks::Now() - *last_updated_time; 1663 // We're interested in frequent updates periods longer than 1664 // 10 minutes. 1665 bool use_long_period = false; 1666 if (delta >= save_delay_in_mins_) { 1667 use_long_period = true; 1668 } 1669 switch (type) { 1670 case chrome::NOTIFICATION_SESSION_SERVICE_SAVED : 1671 RecordUpdatedSaveTime(delta, use_long_period); 1672 RecordUpdatedSessionNavigationOrTab(delta, use_long_period); 1673 break; 1674 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED: 1675 RecordUpdatedTabClosed(delta, use_long_period); 1676 RecordUpdatedSessionNavigationOrTab(delta, use_long_period); 1677 break; 1678 case content::NOTIFICATION_NAV_LIST_PRUNED: 1679 RecordUpdatedNavListPruned(delta, use_long_period); 1680 RecordUpdatedSessionNavigationOrTab(delta, use_long_period); 1681 break; 1682 case content::NOTIFICATION_NAV_ENTRY_COMMITTED: 1683 RecordUpdatedNavEntryCommit(delta, use_long_period); 1684 RecordUpdatedSessionNavigationOrTab(delta, use_long_period); 1685 break; 1686 default: 1687 NOTREACHED() << "Bad type sent to RecordSessionUpdateHistogramData"; 1688 break; 1689 } 1690 } 1691 (*last_updated_time) = base::TimeTicks::Now(); 1692} 1693 1694void SessionService::RecordUpdatedTabClosed(base::TimeDelta delta, 1695 bool use_long_period) { 1696 std::string name("SessionRestore.TabClosedPeriod"); 1697 UMA_HISTOGRAM_CUSTOM_TIMES(name, 1698 delta, 1699 // 2500ms is the default save delay. 1700 save_delay_in_millis_, 1701 save_delay_in_mins_, 1702 50); 1703 if (use_long_period) { 1704 std::string long_name_("SessionRestore.TabClosedLongPeriod"); 1705 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_, 1706 delta, 1707 save_delay_in_mins_, 1708 save_delay_in_hrs_, 1709 50); 1710 } 1711} 1712 1713void SessionService::RecordUpdatedNavListPruned(base::TimeDelta delta, 1714 bool use_long_period) { 1715 std::string name("SessionRestore.NavigationListPrunedPeriod"); 1716 UMA_HISTOGRAM_CUSTOM_TIMES(name, 1717 delta, 1718 // 2500ms is the default save delay. 1719 save_delay_in_millis_, 1720 save_delay_in_mins_, 1721 50); 1722 if (use_long_period) { 1723 std::string long_name_("SessionRestore.NavigationListPrunedLongPeriod"); 1724 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_, 1725 delta, 1726 save_delay_in_mins_, 1727 save_delay_in_hrs_, 1728 50); 1729 } 1730} 1731 1732void SessionService::RecordUpdatedNavEntryCommit(base::TimeDelta delta, 1733 bool use_long_period) { 1734 std::string name("SessionRestore.NavEntryCommittedPeriod"); 1735 UMA_HISTOGRAM_CUSTOM_TIMES(name, 1736 delta, 1737 // 2500ms is the default save delay. 1738 save_delay_in_millis_, 1739 save_delay_in_mins_, 1740 50); 1741 if (use_long_period) { 1742 std::string long_name_("SessionRestore.NavEntryCommittedLongPeriod"); 1743 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_, 1744 delta, 1745 save_delay_in_mins_, 1746 save_delay_in_hrs_, 1747 50); 1748 } 1749} 1750 1751void SessionService::RecordUpdatedSessionNavigationOrTab(base::TimeDelta delta, 1752 bool use_long_period) { 1753 std::string name("SessionRestore.NavOrTabUpdatePeriod"); 1754 UMA_HISTOGRAM_CUSTOM_TIMES(name, 1755 delta, 1756 // 2500ms is the default save delay. 1757 save_delay_in_millis_, 1758 save_delay_in_mins_, 1759 50); 1760 if (use_long_period) { 1761 std::string long_name_("SessionRestore.NavOrTabUpdateLongPeriod"); 1762 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_, 1763 delta, 1764 save_delay_in_mins_, 1765 save_delay_in_hrs_, 1766 50); 1767 } 1768} 1769 1770void SessionService::RecordUpdatedSaveTime(base::TimeDelta delta, 1771 bool use_long_period) { 1772 std::string name("SessionRestore.SavePeriod"); 1773 UMA_HISTOGRAM_CUSTOM_TIMES(name, 1774 delta, 1775 // 2500ms is the default save delay. 1776 save_delay_in_millis_, 1777 save_delay_in_mins_, 1778 50); 1779 if (use_long_period) { 1780 std::string long_name_("SessionRestore.SaveLongPeriod"); 1781 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_, 1782 delta, 1783 save_delay_in_mins_, 1784 save_delay_in_hrs_, 1785 50); 1786 } 1787} 1788 1789void SessionService::TabInserted(WebContents* contents) { 1790 SessionTabHelper* session_tab_helper = 1791 SessionTabHelper::FromWebContents(contents); 1792 if (!ShouldTrackChangesToWindow(session_tab_helper->window_id())) 1793 return; 1794 SetTabWindow(session_tab_helper->window_id(), 1795 session_tab_helper->session_id()); 1796 extensions::TabHelper* extensions_tab_helper = 1797 extensions::TabHelper::FromWebContents(contents); 1798 if (extensions_tab_helper && 1799 extensions_tab_helper->extension_app()) { 1800 SetTabExtensionAppID( 1801 session_tab_helper->window_id(), 1802 session_tab_helper->session_id(), 1803 extensions_tab_helper->extension_app()->id()); 1804 } 1805 1806 // Record the association between the SessionStorageNamespace and the 1807 // tab. 1808 // 1809 // TODO(ajwong): This should be processing the whole map rather than 1810 // just the default. This in particular will not work for tabs with only 1811 // isolated apps which won't have a default partition. 1812 content::SessionStorageNamespace* session_storage_namespace = 1813 contents->GetController().GetDefaultSessionStorageNamespace(); 1814 ScheduleCommand(CreateSessionStorageAssociatedCommand( 1815 session_tab_helper->session_id(), 1816 session_storage_namespace->persistent_id())); 1817 session_storage_namespace->SetShouldPersist(true); 1818} 1819 1820void SessionService::TabClosing(WebContents* contents) { 1821 // Allow the associated sessionStorage to get deleted; it won't be needed 1822 // in the session restore. 1823 content::SessionStorageNamespace* session_storage_namespace = 1824 contents->GetController().GetDefaultSessionStorageNamespace(); 1825 session_storage_namespace->SetShouldPersist(false); 1826 SessionTabHelper* session_tab_helper = 1827 SessionTabHelper::FromWebContents(contents); 1828 TabClosed(session_tab_helper->window_id(), 1829 session_tab_helper->session_id(), 1830 contents->GetClosedByUserGesture()); 1831 RecordSessionUpdateHistogramData(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 1832 &last_updated_tab_closed_time_); 1833} 1834 1835void SessionService::MaybeDeleteSessionOnlyData() { 1836 // Don't try anything if we're testing. The browser_process is not fully 1837 // created and DeleteSession will crash if we actually attempt it. 1838 if (!profile() || profile()->AsTestingProfile()) 1839 return; 1840 1841 // Clear session data if the last window for a profile has been closed and 1842 // closing the last window would normally close Chrome, unless background mode 1843 // is active. Tests don't have a background_mode_manager. 1844 if (has_open_trackable_browsers_ || 1845 browser_defaults::kBrowserAliveWithNoWindows || 1846 g_browser_process->background_mode_manager()->IsBackgroundModeActive()) { 1847 return; 1848 } 1849 1850 // Check for any open windows for the current profile that we aren't tracking. 1851 for (chrome::BrowserIterator it; !it.done(); it.Next()) { 1852 if ((*it)->profile() == profile()) 1853 return; 1854 } 1855 DeleteSessionOnlyData(profile()); 1856} 1857