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