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