diff options
author | georgesak <georgesak@chromium.org> | 2015-05-22 15:08:07 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-05-22 22:09:02 +0000 |
commit | 5582cbe405f6dc48b14e2cae7254aa03abcf353b (patch) | |
tree | 8d47eeb46e67fca1cd2dd73aad73fb5ab5a5a256 | |
parent | f564cedfa2331f9fd4a0e4a1c511ca8459ae243a (diff) | |
download | chromium_src-5582cbe405f6dc48b14e2cae7254aa03abcf353b.zip chromium_src-5582cbe405f6dc48b14e2cae7254aa03abcf353b.tar.gz chromium_src-5582cbe405f6dc48b14e2cae7254aa03abcf353b.tar.bz2 |
[Session restore] Add MRU logic to loading of background pages.
This patch adds the notion of last activation time to tabs and when a session restores, background tabs are loaded using MRU.
BUG=472772
Review URL: https://codereview.chromium.org/1131373003
Cr-Commit-Position: refs/heads/master@{#331189}
-rw-r--r-- | chrome/browser/sessions/session_restore.cc | 37 | ||||
-rw-r--r-- | chrome/browser/sessions/session_restore_browsertest.cc | 139 | ||||
-rw-r--r-- | chrome/browser/sessions/session_restore_delegate.cc | 6 | ||||
-rw-r--r-- | chrome/browser/sessions/session_service.cc | 17 | ||||
-rw-r--r-- | chrome/browser/sessions/session_service.h | 5 | ||||
-rw-r--r-- | chrome/browser/ui/browser.cc | 14 | ||||
-rw-r--r-- | components/sessions/session_service_commands.cc | 30 | ||||
-rw-r--r-- | components/sessions/session_service_commands.h | 3 | ||||
-rw-r--r-- | components/sessions/session_types.h | 5 | ||||
-rw-r--r-- | content/browser/web_contents/web_contents_impl.cc | 4 | ||||
-rw-r--r-- | content/browser/web_contents/web_contents_impl.h | 1 | ||||
-rw-r--r-- | content/public/browser/web_contents.h | 5 |
12 files changed, 245 insertions, 21 deletions
diff --git a/chrome/browser/sessions/session_restore.cc b/chrome/browser/sessions/session_restore.cc index c669216..b1a7498 100644 --- a/chrome/browser/sessions/session_restore.cc +++ b/chrome/browser/sessions/session_restore.cc @@ -297,10 +297,11 @@ class SessionRestoreImpl : public content::NotificationObserver { } if (succeeded) { - // Start Loading tabs. if (SessionRestore::GetSmartRestoreMode() != - SessionRestore::SMART_RESTORE_MODE_OFF) + SessionRestore::SMART_RESTORE_MODE_OFF) { std::stable_sort(contents_created->begin(), contents_created->end()); + } + // Start Loading tabs. SessionRestoreDelegate::RestoreTabs(*contents_created, restore_started_); } @@ -507,7 +508,26 @@ class SessionRestoreImpl : public content::NotificationObserver { std::vector<RestoredTab>* created_contents) { DVLOG(1) << "RestoreTabsToBrowser " << window.tabs.size(); DCHECK(!window.tabs.empty()); + base::TimeTicks now = base::TimeTicks::Now(); + base::TimeTicks highest_time = base::TimeTicks::UnixEpoch(); if (initial_tab_count == 0) { + if (SessionRestore::GetSmartRestoreMode() == + SessionRestore::SMART_RESTORE_MODE_MRU) { + // The last active time of a WebContents is initially set to the + // creation time of the tab, which is not necessarly the same as the + // loading time, so we have to restore the values. Also, since TimeTicks + // only make sense in their current session, these values have to be + // sanitized first. To do so, we need to first figure out the largest + // time. This will then be used to set the last active time of + // each tab where the most recent tab will have its time set to |now| + // and the rest of the tabs will have theirs set earlier by the same + // delta as they originally had. + for (int i = 0; i < static_cast<int>(window.tabs.size()); ++i) { + const sessions::SessionTab& tab = *(window.tabs[i]); + if (tab.last_active_time > highest_time) + highest_time = tab.last_active_time; + } + } for (int i = 0; i < static_cast<int>(window.tabs.size()); ++i) { const sessions::SessionTab& tab = *(window.tabs[i]); @@ -520,6 +540,13 @@ class SessionRestoreImpl : public content::NotificationObserver { if (!contents) continue; + // Sanitize the last active time. + if (SessionRestore::GetSmartRestoreMode() == + SessionRestore::SMART_RESTORE_MODE_MRU) { + base::TimeDelta delta = highest_time - tab.last_active_time; + contents->SetLastActiveTime(now - delta); + } + RestoredTab restored_tab(contents, is_selected_tab, tab.extension_app_id.empty(), tab.pinned); created_contents->push_back(restored_tab); @@ -544,6 +571,12 @@ class SessionRestoreImpl : public content::NotificationObserver { WebContents* contents = RestoreTab(tab, tab_index_offset + i, browser, false); if (contents) { + // Sanitize the last active time. + if (SessionRestore::GetSmartRestoreMode() == + SessionRestore::SMART_RESTORE_MODE_MRU) { + base::TimeDelta delta = highest_time - tab.last_active_time; + contents->SetLastActiveTime(now - delta); + } RestoredTab restored_tab(contents, false, tab.extension_app_id.empty(), tab.pinned); created_contents->push_back(restored_tab); diff --git a/chrome/browser/sessions/session_restore_browsertest.cc b/chrome/browser/sessions/session_restore_browsertest.cc index 14966d7..647e499 100644 --- a/chrome/browser/sessions/session_restore_browsertest.cc +++ b/chrome/browser/sessions/session_restore_browsertest.cc @@ -212,12 +212,16 @@ class SessionRestoreTest : public InProcessBrowserTest { const BrowserList* active_browser_list_; }; -// Activates the smart restore behaviour and can track the loading of tabs. -class SmartSessionRestoreTest : public SessionRestoreTest, - public content::NotificationObserver { +// Activates the smart restore behaviour in "simple" mode and tracks the loading +// of tabs. +class SmartSessionRestoreSimpleTest : public SessionRestoreTest, + public content::NotificationObserver { public: - SmartSessionRestoreTest() {} + SmartSessionRestoreSimpleTest() {} void StartObserving(int num_tabs) { + // Start by clearing everything so it can be reused in the same test. + web_contents_.clear(); + registrar_.RemoveAll(); num_tabs_ = num_tabs; registrar_.Add(this, content::NOTIFICATION_LOAD_START, content::NotificationService::AllSources()); @@ -261,7 +265,24 @@ class SmartSessionRestoreTest : public SessionRestoreTest, scoped_refptr<content::MessageLoopRunner> message_loop_runner_; int num_tabs_; - DISALLOW_COPY_AND_ASSIGN(SmartSessionRestoreTest); + DISALLOW_COPY_AND_ASSIGN(SmartSessionRestoreSimpleTest); +}; + +class SmartSessionRestoreMRUTest : public SmartSessionRestoreSimpleTest { + public: + SmartSessionRestoreMRUTest() {} + + protected: + void SetUpCommandLine(base::CommandLine* command_line) override { + base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( + switches::kForceFieldTrials, "IntelligentSessionRestore/TestGroup/"); + base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( + switches::kForceFieldTrialParams, + "IntelligentSessionRestore.TestGroup:PrioritizeTabs/mru"); + } + + private: + DISALLOW_COPY_AND_ASSIGN(SmartSessionRestoreMRUTest); }; // Verifies that restored tabs have a root window. This is important @@ -1333,14 +1354,14 @@ IN_PROC_BROWSER_TEST_F(SessionRestoreTest, SessionStorageAfterTabReplace) { EXPECT_EQ(1, new_browser->tab_strip_model()->count()); } -IN_PROC_BROWSER_TEST_F(SmartSessionRestoreTest, CorrectLoadingOrder) { +IN_PROC_BROWSER_TEST_F(SmartSessionRestoreSimpleTest, CorrectLoadingOrder) { ASSERT_EQ(SessionRestore::SMART_RESTORE_MODE_SIMPLE, SessionRestore::GetSmartRestoreMode()); - const int NumTabs = 6; + const int num_tabs = 6; // Start observing the loading of tabs, to make sure the order is correct. - StartObserving(NumTabs); + StartObserving(num_tabs); struct TabInfo { GURL url; @@ -1348,7 +1369,7 @@ IN_PROC_BROWSER_TEST_F(SmartSessionRestoreTest, CorrectLoadingOrder) { int expected_load_order; }; - TabInfo tab_info[NumTabs] = { + TabInfo tab_info[num_tabs] = { // This will be the foreground tab and will always load first. {GURL("http://google.com/1"), false, 1}, {GURL("http://google.com/2"), false, 3}, @@ -1362,9 +1383,9 @@ IN_PROC_BROWSER_TEST_F(SmartSessionRestoreTest, CorrectLoadingOrder) { // Set up the restore data. std::vector<const sessions::SessionWindow*> session; sessions::SessionWindow window; - sessions::SessionTab tab[NumTabs]; + sessions::SessionTab tab[num_tabs]; - for (int i = 0; i < NumTabs; i++) { + for (int i = 0; i < num_tabs; i++) { SerializedNavigationEntry nav = SerializedNavigationEntryTestHelper::CreateNavigation( tab_info[i].url.spec(), tab_info[i].url.spec().c_str()); @@ -1379,17 +1400,16 @@ IN_PROC_BROWSER_TEST_F(SmartSessionRestoreTest, CorrectLoadingOrder) { session.push_back(&window); Profile* profile = browser()->profile(); - ui_test_utils::BrowserAddedObserver window_observer; std::vector<Browser*> browsers = SessionRestore::RestoreForeignSessionWindows( profile, browser()->host_desktop_type(), session.begin(), session.end()); ASSERT_EQ(1u, browsers.size()); ASSERT_TRUE(browsers[0]); - ASSERT_EQ(NumTabs, browsers[0]->tab_strip_model()->count()); + ASSERT_EQ(num_tabs, browsers[0]->tab_strip_model()->count()); WaitForAllTabsToStartLoading(); - ASSERT_EQ(static_cast<size_t>(NumTabs), web_contents().size()); + ASSERT_EQ(static_cast<size_t>(num_tabs), web_contents().size()); // Make sure that contents are loaded in the correct order, ie. each tab rank // is higher that its preceding one. @@ -1406,3 +1426,94 @@ IN_PROC_BROWSER_TEST_F(SmartSessionRestoreTest, CorrectLoadingOrder) { // here to avoid a crash. window.tabs.clear(); } + +IN_PROC_BROWSER_TEST_F(SmartSessionRestoreMRUTest, CorrectLoadingOrder) { + const int num_tabs = 6; + + Profile* profile = browser()->profile(); + + GURL urls[] = {GURL("http://google.com/1"), + GURL("http://google.com/2"), + GURL("http://google.com/3"), + GURL("http://google.com/4"), + GURL("http://google.com/5"), + GURL("http://google.com/6")}; + + int activation_order[] = {4, 2, 1, 5, 0, 3}; + int activation_order2[] = {4, 2, 5, 0, 3, 1}; + + // Replace the first tab and add the other tabs. + ui_test_utils::NavigateToURL(browser(), urls[0]); + for (int i = 1; i < num_tabs; i++) { + ui_test_utils::NavigateToURLWithDisposition( + browser(), urls[i], NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); + } + + ASSERT_EQ(num_tabs, browser()->tab_strip_model()->count()); + + // Activate the tabs one by one following the random activation order. + for (auto i : activation_order) + browser()->tab_strip_model()->ActivateTabAt(i, true); + + // Close the browser. + g_browser_process->AddRefModule(); + CloseBrowserSynchronously(browser()); + + StartObserving(num_tabs); + + // Create a new window, which should trigger session restore. + ui_test_utils::BrowserAddedObserver window_observer; + chrome::NewEmptyWindow(profile, chrome::HOST_DESKTOP_TYPE_NATIVE); + Browser* new_browser = window_observer.WaitForSingleNewBrowser(); + ASSERT_TRUE(new_browser != NULL); + WaitForAllTabsToStartLoading(); + g_browser_process->ReleaseModule(); + + ASSERT_EQ(static_cast<size_t>(num_tabs), web_contents().size()); + // Test that we have observed the tabs being loaded in the inverse order of + // their activation (MRU). Also validate that their last active time is in the + // correct order. + for (size_t i = 0; i < web_contents().size(); i++) { + GURL expected_url = urls[activation_order[num_tabs - i - 1]]; + ASSERT_EQ(expected_url, web_contents()[i]->GetLastCommittedURL()); + if (i > 0) { + ASSERT_GT(web_contents()[i - 1]->GetLastActiveTime(), + web_contents()[i]->GetLastActiveTime()); + } + } + + // Activate the 2nd tab then close the browser and open it again, to trigger + // another session restore. The goal is to ensure that activation time is + // persisted between session restores. + + new_browser->tab_strip_model()->ActivateTabAt(1, true); + + // Close the browser. + g_browser_process->AddRefModule(); + CloseBrowserSynchronously(new_browser); + + StartObserving(num_tabs); + + // Create a new window, which should trigger session restore. + ui_test_utils::BrowserAddedObserver window_observer2; + chrome::NewEmptyWindow(profile, chrome::HOST_DESKTOP_TYPE_NATIVE); + Browser* new_browser2 = window_observer2.WaitForSingleNewBrowser(); + ASSERT_TRUE(new_browser2 != NULL); + WaitForAllTabsToStartLoading(); + g_browser_process->ReleaseModule(); + + ASSERT_EQ(static_cast<size_t>(num_tabs), web_contents().size()); + + // Test that we have observed the tabs being loaded in the inverse order of + // their activation (MRU). Also validate that their last active time is in the + // correct order. + for (size_t i = 0; i < web_contents().size(); i++) { + GURL expected_url = urls[activation_order2[num_tabs - i - 1]]; + ASSERT_EQ(expected_url, web_contents()[i]->GetLastCommittedURL()); + if (i > 0) { + ASSERT_GT(web_contents()[i - 1]->GetLastActiveTime(), + web_contents()[i]->GetLastActiveTime()); + } + } +} diff --git a/chrome/browser/sessions/session_restore_delegate.cc b/chrome/browser/sessions/session_restore_delegate.cc index 9d5c312..52564f9 100644 --- a/chrome/browser/sessions/session_restore_delegate.cc +++ b/chrome/browser/sessions/session_restore_delegate.cc @@ -57,7 +57,11 @@ bool SessionRestoreDelegate::RestoredTab::operator<( // Apps should be loaded before normal tabs. if (is_app_ != right.is_app_) return is_app_; - // TODO(georgesak): Add criterion based on recency. + // Restore using MRU. Behind an experiment for now. + if (SessionRestore::GetSmartRestoreMode() == + SessionRestore::SMART_RESTORE_MODE_MRU) + return contents_->GetLastActiveTime() > + right.contents_->GetLastActiveTime(); return false; } diff --git a/chrome/browser/sessions/session_service.cc b/chrome/browser/sessions/session_service.cc index 99de720..0b8759a 100644 --- a/chrome/browser/sessions/session_service.cc +++ b/chrome/browser/sessions/session_service.cc @@ -487,6 +487,16 @@ void SessionService::SetTabExtensionAppID( tab_id, extension_app_id).Pass()); } +void SessionService::SetLastActiveTime(const SessionID& window_id, + const SessionID& tab_id, + base::TimeTicks last_active_time) { + if (!ShouldTrackChangesToWindow(window_id)) + return; + + ScheduleCommand( + sessions::CreateLastActiveTimeCommand(tab_id, last_active_time).Pass()); +} + base::CancelableTaskTracker::TaskId SessionService::GetLastSession( const SessionCallback& callback, base::CancelableTaskTracker* tracker) { @@ -727,6 +737,13 @@ void SessionService::BuildCommandsForTab(const SessionID& window_id, sessions::CreatePinnedStateCommand(session_id, true)); } + if (SessionRestore::GetSmartRestoreMode() == + SessionRestore::SMART_RESTORE_MODE_MRU) { + base_session_service_->AppendRebuildCommand( + sessions::CreateLastActiveTimeCommand(session_id, + tab->GetLastActiveTime())); + } + extensions::TabHelper* extensions_tab_helper = extensions::TabHelper::FromWebContents(tab); if (extensions_tab_helper->extension_app()) { diff --git a/chrome/browser/sessions/session_service.h b/chrome/browser/sessions/session_service.h index ca409cf..0b5212d 100644 --- a/chrome/browser/sessions/session_service.h +++ b/chrome/browser/sessions/session_service.h @@ -201,6 +201,11 @@ class SessionService : public BaseSessionServiceDelegateImpl, const SessionID& tab_id, const std::string& extension_app_id); + // Sets the last active time of the tab. + void SetLastActiveTime(const SessionID& window_id, + const SessionID& tab_id, + base::TimeTicks last_active_time); + // Callback from GetLastSession. // The second parameter is the id of the window that was last active. typedef base::Callback<void(ScopedVector<sessions::SessionWindow>, diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc index 1133a6f..ff1fedc 100644 --- a/chrome/browser/ui/browser.cc +++ b/chrome/browser/ui/browser.cc @@ -70,6 +70,7 @@ #include "chrome/browser/profiles/profiles_state.h" #include "chrome/browser/repost_form_warning_controller.h" #include "chrome/browser/search/search.h" +#include "chrome/browser/sessions/session_restore.h" #include "chrome/browser/sessions/session_service.h" #include "chrome/browser/sessions/session_service_factory.h" #include "chrome/browser/sessions/session_tab_helper.h" @@ -1071,13 +1072,22 @@ void Browser::ActiveTabChanged(WebContents* old_contents, find_bar_controller_->find_bar()->MoveWindowIfNecessary(gfx::Rect()); } - // Update sessions. Don't force creation of sessions. If sessions doesn't - // exist, the change will be picked up by sessions when created. + // Update sessions (selected tab index and last active time). Don't force + // creation of sessions. If sessions doesn't exist, the change will be picked + // up by sessions when created. SessionService* session_service = SessionServiceFactory::GetForProfileIfExisting(profile_); if (session_service && !tab_strip_model_->closing_all()) { session_service->SetSelectedTabInWindow(session_id(), tab_strip_model_->active_index()); + if (SessionRestore::GetSmartRestoreMode() == + SessionRestore::SMART_RESTORE_MODE_MRU) { + SessionTabHelper* session_tab_helper = + SessionTabHelper::FromWebContents(new_contents); + session_service->SetLastActiveTime(session_id(), + session_tab_helper->session_id(), + base::TimeTicks::Now()); + } } // This needs to be called after notifying SearchDelegate. diff --git a/components/sessions/session_service_commands.cc b/components/sessions/session_service_commands.cc index eca2b4d..a7f653d 100644 --- a/components/sessions/session_service_commands.cc +++ b/components/sessions/session_service_commands.cc @@ -38,6 +38,7 @@ static const SessionCommand::id_type kCommandWindowClosed = 17; static const SessionCommand::id_type kCommandSetTabUserAgentOverride = 18; static const SessionCommand::id_type kCommandSessionStorageAssociated = 19; static const SessionCommand::id_type kCommandSetActiveWindow = 20; +static const SessionCommand::id_type kCommandLastActiveTime = 21; namespace { @@ -89,6 +90,11 @@ struct PinnedStatePayload { bool pinned_state; }; +struct LastActiveTimePayload { + SessionID::id_type tab_id; + int64 last_active_time; +}; + // Persisted versions of ui::WindowShowState that are written to disk and can // never change. enum PersistedWindowShowState { @@ -558,6 +564,18 @@ bool CreateTabsAndWindows(const ScopedVector<SessionCommand>& data, break; } + case kCommandLastActiveTime: { + LastActiveTimePayload payload; + if (!command->GetPayload(&payload, sizeof(payload))) { + DVLOG(1) << "Failed reading command " << command->id(); + return true; + } + SessionTab* tab = GetTab(payload.tab_id, tabs); + tab->last_active_time = + base::TimeTicks::FromInternalValue(payload.last_active_time); + break; + } + default: // TODO(skuhne): This might call back into a callback handler to extend // the command set for specific implementations. @@ -704,6 +722,18 @@ scoped_ptr<SessionCommand> CreateSetActiveWindowCommand( return command; } +scoped_ptr<SessionCommand> CreateLastActiveTimeCommand( + const SessionID& tab_id, + base::TimeTicks last_active_time) { + LastActiveTimePayload payload = {0}; + payload.tab_id = tab_id.id(); + payload.last_active_time = last_active_time.ToInternalValue(); + scoped_ptr<SessionCommand> command( + new SessionCommand(kCommandLastActiveTime, sizeof(payload))); + memcpy(command->contents(), &payload, sizeof(payload)); + return command; +} + scoped_ptr<SessionCommand> CreateTabNavigationPathPrunedFromBackCommand( const SessionID& tab_id, int count) { diff --git a/components/sessions/session_service_commands.h b/components/sessions/session_service_commands.h index 6bf3be8..e1cca36 100644 --- a/components/sessions/session_service_commands.h +++ b/components/sessions/session_service_commands.h @@ -78,6 +78,9 @@ SESSIONS_EXPORT scoped_ptr<SessionCommand> CreateSetTabUserAgentOverrideCommand( SESSIONS_EXPORT scoped_ptr<SessionCommand> CreateSetWindowAppNameCommand( const SessionID& window_id, const std::string& app_name); +SESSIONS_EXPORT scoped_ptr<SessionCommand> CreateLastActiveTimeCommand( + const SessionID& tab_id, + base::TimeTicks last_active_time); // Searches for a pending command using |base_session_service| that can be // replaced with |command|. If one is found, pending command is removed, the diff --git a/components/sessions/session_types.h b/components/sessions/session_types.h index a7f0358..c71445e 100644 --- a/components/sessions/session_types.h +++ b/components/sessions/session_types.h @@ -97,6 +97,11 @@ struct SESSIONS_EXPORT SessionTab { // Timestamp for when this tab was last modified. base::Time timestamp; + // Timestamp for when this tab was last activated. As these use TimeTicks, + // they should not be compared with one another, unless it's within the same + // chrome session. + base::TimeTicks last_active_time; + std::vector<sessions::SerializedNavigationEntry> navigations; // For reassociating sessionStorage. diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc index 43d74a2..2a8a92c 100644 --- a/content/browser/web_contents/web_contents_impl.cc +++ b/content/browser/web_contents/web_contents_impl.cc @@ -1052,6 +1052,10 @@ base::TimeTicks WebContentsImpl::GetLastActiveTime() const { return last_active_time_; } +void WebContentsImpl::SetLastActiveTime(base::TimeTicks last_active_time) { + last_active_time_ = last_active_time; +} + void WebContentsImpl::WasShown() { controller_.SetActive(true); diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h index b0ebb5e..d95b2aca 100644 --- a/content/browser/web_contents/web_contents_impl.h +++ b/content/browser/web_contents/web_contents_impl.h @@ -272,6 +272,7 @@ class CONTENT_EXPORT WebContentsImpl bool IsBeingDestroyed() const override; void NotifyNavigationStateChanged(InvalidateTypes changed_flags) override; base::TimeTicks GetLastActiveTime() const override; + void SetLastActiveTime(base::TimeTicks last_active_time) override; void WasShown() override; void WasHidden() override; bool NeedToFireBeforeUnload() override; diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h index 62f09a8..86ac675 100644 --- a/content/public/browser/web_contents.h +++ b/content/public/browser/web_contents.h @@ -351,9 +351,10 @@ class WebContents : public PageNavigator, // change. virtual void NotifyNavigationStateChanged(InvalidateTypes changed_flags) = 0; - // Get the last time that the WebContents was made active (either when it was - // created or shown with WasShown()). + // Get/Set the last time that the WebContents was made active (either when it + // was created or shown with WasShown()). virtual base::TimeTicks GetLastActiveTime() const = 0; + virtual void SetLastActiveTime(base::TimeTicks last_active_time) = 0; // Invoked when the WebContents becomes shown/hidden. virtual void WasShown() = 0; |