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