diff --git a/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc b/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc index 01b635dc797f4..af67eab33650c 100644 --- a/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc +++ b/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc @@ -19,6 +19,7 @@ #include "chrome/browser/extensions/extension_tab_util.h" #include "chrome/browser/favicon/favicon_utils.h" #include "chrome/browser/profiles/profile.h" +#include "chrome/browser/sessions/tab_restore_service_factory.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/browser_list.h" @@ -31,13 +32,14 @@ namespace { constexpr base::TimeDelta kTabsChangeDelay = base::TimeDelta::FromMilliseconds(50); +constexpr int kMaxRecentlyClosedTabCount = 100; #if BUILDFLAG(IS_CHROMEOS_ASH) constexpr char kFeedbackCategoryTag[] = "FromTabSearch"; #else constexpr char kFeedbackCategoryTag[] = "FromTabSearchBrowser"; #endif -} +} // namespace TabSearchPageHandler::TabSearchPageHandler( mojo::PendingReceiver receiver, @@ -171,14 +173,49 @@ tab_search::mojom::ProfileDataPtr TabSearchPageHandler::CreateProfileData() { window->height = browser->window()->GetContentsSize().height(); for (int i = 0; i < tab_strip_model->count(); ++i) { window->tabs.push_back( - GetTabData(tab_strip_model, tab_strip_model->GetWebContentsAt(i), i)); + GetTab(tab_strip_model, tab_strip_model->GetWebContentsAt(i), i)); } profile_data->windows.push_back(std::move(window)); } + + CreateRecentlyClosedTabs(profile_data->recently_closed_tabs); return profile_data; } -tab_search::mojom::TabPtr TabSearchPageHandler::GetTabData( +void TabSearchPageHandler::CreateRecentlyClosedTabs( + std::vector& + recently_closed_tabs) { + sessions::TabRestoreService* tab_restore_service = + TabRestoreServiceFactory::GetForProfile(browser_->profile()); + // TabRestoreService is only available for non off the record profiles. + if (tab_restore_service) { + // Flatten tab restore service entries into tabs + for (auto& entry : tab_restore_service->entries()) { + if (entry->type == sessions::TabRestoreService::Type::WINDOW) { + sessions::TabRestoreService::Window* window = + static_cast(entry.get()); + for (auto& tab : window->tabs) { + if (tab->navigations.size() == 0) + continue; + if (recently_closed_tabs.size() >= kMaxRecentlyClosedTabCount) + break; + recently_closed_tabs.push_back(GetRecentlyClosedTab(tab.get())); + } + } else if (entry->type == sessions::TabRestoreService::Type::TAB) { + sessions::TabRestoreService::Tab* tab = + static_cast(entry.get()); + if (tab->navigations.size() == 0) + continue; + if (recently_closed_tabs.size() >= kMaxRecentlyClosedTabCount) + break; + recently_closed_tabs.push_back(GetRecentlyClosedTab(tab)); + } + } + } + DCHECK(recently_closed_tabs.size() <= kMaxRecentlyClosedTabCount); +} + +tab_search::mojom::TabPtr TabSearchPageHandler::GetTab( TabStripModel* tab_strip_model, content::WebContents* contents, int index) { @@ -214,6 +251,19 @@ tab_search::mojom::TabPtr TabSearchPageHandler::GetTabData( return tab_data; } +tab_search::mojom::RecentlyClosedTabPtr +TabSearchPageHandler::GetRecentlyClosedTab( + sessions::TabRestoreService::Tab* tab) { + auto recently_closed_tab = tab_search::mojom::RecentlyClosedTab::New(); + DCHECK(tab->navigations.size() > 0); + sessions::SerializedNavigationEntry& entry = + tab->navigations[tab->current_navigation_index]; + recently_closed_tab->title = base::UTF16ToUTF8(entry.title()); + recently_closed_tab->url = entry.original_request_url().spec(); + recently_closed_tab->last_active_time_ticks = entry.timestamp(); + return recently_closed_tab; +} + void TabSearchPageHandler::OnTabStripModelChanged( TabStripModel* tab_strip_model, const TabStripModelChange& change, @@ -247,7 +297,7 @@ void TabSearchPageHandler::TabChangedAt(content::WebContents* contents, if (!browser) return; TRACE_EVENT0("browser", "custom_metric:TabSearchPageHandler:TabChangedAt"); - page_->TabUpdated(GetTabData(browser->tab_strip_model(), contents, index)); + page_->TabUpdated(GetTab(browser->tab_strip_model(), contents, index)); } void TabSearchPageHandler::ScheduleDebounce() { diff --git a/chrome/browser/ui/webui/tab_search/tab_search_page_handler.h b/chrome/browser/ui/webui/tab_search/tab_search_page_handler.h index 6d46b00411014..2666e9b512259 100644 --- a/chrome/browser/ui/webui/tab_search/tab_search_page_handler.h +++ b/chrome/browser/ui/webui/tab_search/tab_search_page_handler.h @@ -13,6 +13,7 @@ #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/ui/tabs/tab_strip_model_observer.h" #include "chrome/browser/ui/webui/tab_search/tab_search.mojom.h" +#include "components/sessions/core/tab_restore_service.h" #include "content/public/browser/web_contents_observer.h" #include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/pending_remote.h" @@ -87,9 +88,16 @@ class TabSearchPageHandler : public tab_search::mojom::PageHandler, tab_search::mojom::ProfileDataPtr CreateProfileData(); - tab_search::mojom::TabPtr GetTabData(TabStripModel* tab_strip_model, - content::WebContents* contents, - int index); + void CreateRecentlyClosedTabs( + std::vector& + recently_closed_tabs); + + tab_search::mojom::TabPtr GetTab(TabStripModel* tab_strip_model, + content::WebContents* contents, + int index); + tab_search::mojom::RecentlyClosedTabPtr GetRecentlyClosedTab( + sessions::TabRestoreService::Tab* tab); + // Returns tab details required to perform an action on the tab. base::Optional GetTabDetails(int32_t tab_id); diff --git a/chrome/browser/ui/webui/tab_search/tab_search_page_handler_unittest.cc b/chrome/browser/ui/webui/tab_search/tab_search_page_handler_unittest.cc index 13839460d86eb..9893e26b259d8 100644 --- a/chrome/browser/ui/webui/tab_search/tab_search_page_handler_unittest.cc +++ b/chrome/browser/ui/webui/tab_search/tab_search_page_handler_unittest.cc @@ -10,11 +10,14 @@ #include "build/build_config.h" #include "build/chromeos_buildflags.h" #include "chrome/browser/extensions/extension_tab_util.h" +#include "chrome/browser/sessions/chrome_tab_restore_service_client.h" +#include "chrome/browser/sessions/tab_restore_service_factory.h" #include "chrome/browser/ui/browser_commands.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/test/base/browser_with_test_window_test.h" #include "chrome/test/base/test_browser_window.h" #include "chrome/test/base/testing_profile_manager.h" +#include "components/sessions/core/tab_restore_service_impl.h" #include "components/sync_preferences/pref_service_syncable.h" #include "content/public/test/test_web_ui.h" #include "testing/gmock/include/gmock/gmock.h" @@ -71,6 +74,13 @@ void ExpectNewTab(const tab_search::mojom::Tab* tab, EXPECT_GT(tab->last_active_time_ticks, base::TimeTicks()); } +void ExpectRecentlyClosedTab(const tab_search::mojom::RecentlyClosedTab* tab, + const std::string url, + const std::string title) { + EXPECT_EQ(url, tab->url); + EXPECT_EQ(title, tab->title); +} + void ExpectProfileTabs(tab_search::mojom::ProfileData* profile_tabs) { ASSERT_EQ(2u, profile_tabs->windows.size()); auto* window1 = profile_tabs->windows[0].get(); @@ -158,6 +168,14 @@ class TabSearchPageHandlerTest : public BrowserWithTestWindowTest { void FireTimer() { handler_->mock_debounce_timer()->Fire(); } bool IsTimerRunning() { return handler_->mock_debounce_timer()->IsRunning(); } + static std::unique_ptr GetTabRestoreService( + content::BrowserContext* browser_context) { + return std::make_unique( + std::make_unique( + Profile::FromBrowserContext(browser_context)), + nullptr, nullptr); + } + protected: void AddTabWithTitle(Browser* browser, const GURL url, @@ -346,6 +364,35 @@ TEST_F(TabSearchPageHandlerTest, CloseTab) { ASSERT_EQ(1, browser2()->tab_strip_model()->count()); } +TEST_F(TabSearchPageHandlerTest, RecentlyClosedTab) { + TabRestoreServiceFactory::GetInstance()->SetTestingFactory( + profile(), + base::BindRepeating(&TabSearchPageHandlerTest::GetTabRestoreService)); + AddTabWithTitle(browser1(), GURL(kTabUrl1), kTabName1); + AddTabWithTitle(browser1(), GURL(kTabUrl2), kTabName2); + AddTabWithTitle(browser2(), GURL(kTabUrl3), kTabName3); + AddTabWithTitle(browser2(), GURL(kTabUrl4), kTabName4); + AddTabWithTitle(browser3(), GURL(kTabUrl5), kTabName5); + + int tab_id = extensions::ExtensionTabUtil::GetTabId( + browser1()->tab_strip_model()->GetWebContentsAt(0)); + handler()->CloseTab(tab_id); + browser2()->tab_strip_model()->CloseAllTabs(); + browser3()->tab_strip_model()->CloseAllTabs(); + tab_search::mojom::PageHandler::GetProfileDataCallback callback = + base::BindLambdaForTesting( + [&](tab_search::mojom::ProfileDataPtr profile_tabs) { + auto& tabs = profile_tabs->recently_closed_tabs; + ASSERT_EQ(3u, tabs.size()); + ExpectRecentlyClosedTab(tabs[0].get(), kTabUrl4, kTabName4); + ExpectRecentlyClosedTab(tabs[1].get(), kTabUrl3, kTabName3); + ExpectRecentlyClosedTab(tabs[2].get(), kTabUrl2, kTabName2); + }); + handler()->GetProfileData(std::move(callback)); + EXPECT_CALL(page_, TabUpdated(_)).Times(2); + EXPECT_CALL(page_, TabsRemoved(_)).Times(3); +} + // TODO(crbug.com/1128855): Fix the test for Lacros build. #if BUILDFLAG(IS_CHROMEOS_LACROS) #define MAYBE_ShowFeedbackPage DISABLED_ShowFeedbackPage