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