blob: 8f0a30b0cd1ccbe528e6633363f8c39d91668ed9 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/webui/tab_search/tab_search_page_handler.h"
#include "base/test/bind.h"
#include "base/timer/mock_timer.h"
#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 "components/tab_groups/tab_group_color.h"
#include "components/tab_groups/tab_group_id.h"
#include "components/tab_groups/tab_group_visual_data.h"
#include "content/public/test/test_web_ui.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/gfx/color_utils.h"
using testing::_;
using testing::Truly;
namespace {
constexpr char kTabUrl1[] = "http://foo/1";
constexpr char kTabUrl2[] = "http://foo/2";
constexpr char kTabUrl3[] = "http://foo/3";
constexpr char kTabUrl4[] = "http://foo/4";
constexpr char kTabUrl5[] = "http://foo/5";
constexpr char kTabUrl6[] = "http://foo/6";
constexpr char kTabName1[] = "Tab 1";
constexpr char kTabName2[] = "Tab 2";
constexpr char kTabName3[] = "Tab 3";
constexpr char kTabName4[] = "Tab 4";
constexpr char kTabName5[] = "Tab 5";
constexpr char kTabName6[] = "Tab 6";
class MockPage : public tab_search::mojom::Page {
public:
MockPage() = default;
~MockPage() override = default;
mojo::PendingRemote<tab_search::mojom::Page> BindAndGetRemote() {
DCHECK(!receiver_.is_bound());
return receiver_.BindNewPipeAndPassRemote();
}
mojo::Receiver<tab_search::mojom::Page> receiver_{this};
MOCK_METHOD1(TabsChanged, void(tab_search::mojom::ProfileDataPtr));
MOCK_METHOD1(TabUpdated, void(tab_search::mojom::TabPtr));
MOCK_METHOD1(TabsRemoved, void(const std::vector<int32_t>& tab_ids));
};
void ExpectNewTab(const tab_search::mojom::Tab* tab,
const std::string url,
const std::string title,
int index) {
EXPECT_EQ(index, tab->index);
EXPECT_LT(0, tab->tab_id);
EXPECT_FALSE(tab->group_id.has_value());
EXPECT_FALSE(tab->pinned);
EXPECT_EQ(title, tab->title);
EXPECT_EQ(url, tab->url);
EXPECT_TRUE(tab->favicon_url.has_value());
EXPECT_TRUE(tab->is_default_favicon);
EXPECT_TRUE(tab->show_icon);
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();
ASSERT_EQ(2u, window1->tabs.size());
ASSERT_FALSE(window1->tabs[0]->active);
ASSERT_TRUE(window1->tabs[1]->active);
auto* window2 = profile_tabs->windows[1].get();
ASSERT_EQ(1u, window2->tabs.size());
ASSERT_TRUE(window2->tabs[0]->active);
}
class TestTabSearchPageHandler : public TabSearchPageHandler {
public:
TestTabSearchPageHandler(mojo::PendingRemote<tab_search::mojom::Page> page,
content::WebUI* web_ui,
ui::MojoBubbleWebUIController* webui_controller)
: TabSearchPageHandler(
mojo::PendingReceiver<tab_search::mojom::PageHandler>(),
std::move(page),
web_ui,
webui_controller) {
mock_debounce_timer_ = new base::MockRetainingOneShotTimer();
SetTimerForTesting(base::WrapUnique(mock_debounce_timer_));
}
base::MockRetainingOneShotTimer* mock_debounce_timer() {
return mock_debounce_timer_;
}
private:
base::MockRetainingOneShotTimer* mock_debounce_timer_;
};
class TabSearchPageHandlerTest : public BrowserWithTestWindowTest {
public:
void SetUp() override {
BrowserWithTestWindowTest::SetUp();
web_contents_ = content::WebContents::Create(
content::WebContents::CreateParams(profile()));
web_ui_.set_web_contents(web_contents_.get());
profile2_ = profile_manager()->CreateTestingProfile(
"testing_profile2", nullptr, std::u16string(), 0, std::string(),
GetTestingFactories());
browser2_ = CreateTestBrowser(profile1(), false);
browser3_ = CreateTestBrowser(
browser()->profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true),
false);
browser4_ = CreateTestBrowser(profile2(), false);
browser5_ = CreateTestBrowser(profile1(), true);
BrowserList::SetLastActive(browser1());
webui_controller_ =
std::make_unique<ui::MojoBubbleWebUIController>(web_ui());
handler_ = std::make_unique<TestTabSearchPageHandler>(
page_.BindAndGetRemote(), web_ui(), webui_controller_.get());
}
void TearDown() override {
browser1()->tab_strip_model()->CloseAllTabs();
browser2()->tab_strip_model()->CloseAllTabs();
browser3()->tab_strip_model()->CloseAllTabs();
browser4()->tab_strip_model()->CloseAllTabs();
browser5()->tab_strip_model()->CloseAllTabs();
browser2_.reset();
browser3_.reset();
browser4_.reset();
browser5_.reset();
web_contents_.reset();
BrowserWithTestWindowTest::TearDown();
}
content::TestWebUI* web_ui() { return &web_ui_; }
Profile* profile1() { return browser()->profile(); }
Profile* profile2() { return profile2_; }
// The default browser.
Browser* browser1() { return browser(); }
// Browser with the same profile of the default browser.
Browser* browser2() { return browser2_.get(); }
// Browser with incognito profile.
Browser* browser3() { return browser3_.get(); }
// Browser with a different profile of the default browser.
Browser* browser4() { return browser4_.get(); }
// Browser with the same profile but not normal type.
Browser* browser5() { return browser5_.get(); }
TestTabSearchPageHandler* handler() { return handler_.get(); }
void FireTimer() { handler_->mock_debounce_timer()->Fire(); }
bool IsTimerRunning() { return handler_->mock_debounce_timer()->IsRunning(); }
static std::unique_ptr<KeyedService> GetTabRestoreService(
content::BrowserContext* browser_context) {
return std::make_unique<sessions::TabRestoreServiceImpl>(
std::make_unique<ChromeTabRestoreServiceClient>(
Profile::FromBrowserContext(browser_context)),
nullptr, nullptr);
}
protected:
void AddTabWithTitle(Browser* browser,
const GURL url,
const std::string title) {
AddTab(browser, url);
NavigateAndCommitActiveTabWithTitle(browser, url,
base::ASCIIToUTF16(title));
}
void HideWebContents() {
web_contents_->WasHidden();
ASSERT_FALSE(handler_->IsWebContentsVisible());
}
testing::StrictMock<MockPage> page_;
private:
std::unique_ptr<Browser> CreateTestBrowser(Profile* profile, bool popup) {
auto window = std::make_unique<TestBrowserWindow>();
Browser::Type type = popup ? Browser::TYPE_POPUP : Browser::TYPE_NORMAL;
std::unique_ptr<Browser> browser =
CreateBrowser(profile, type, false, window.get());
BrowserList::SetLastActive(browser.get());
new TestBrowserWindowOwner(window.release());
return browser;
}
std::unique_ptr<content::WebContents> web_contents_;
content::TestWebUI web_ui_;
Profile* profile2_;
std::unique_ptr<Browser> browser2_;
std::unique_ptr<Browser> browser3_;
std::unique_ptr<Browser> browser4_;
std::unique_ptr<Browser> browser5_;
std::unique_ptr<TestTabSearchPageHandler> handler_;
std::unique_ptr<ui::MojoBubbleWebUIController> webui_controller_;
};
TEST_F(TabSearchPageHandlerTest, GetTabs) {
// Browser3 and browser4 are using different profiles, browser5 is not a
// normal type browser, thus their tabs should not be accessible.
AddTabWithTitle(browser5(), GURL(kTabUrl6), kTabName6);
AddTabWithTitle(browser4(), GURL(kTabUrl5), kTabName5);
AddTabWithTitle(browser3(), GURL(kTabUrl4), kTabName4);
AddTabWithTitle(browser2(), GURL(kTabUrl3), kTabName3);
AddTabWithTitle(browser1(), GURL(kTabUrl2), kTabName2);
AddTabWithTitle(browser1(), GURL(kTabUrl1), kTabName1);
EXPECT_CALL(page_, TabsChanged(_)).Times(1);
EXPECT_CALL(page_, TabUpdated(_)).Times(2);
EXPECT_CALL(page_, TabsRemoved(_)).Times(2);
handler()->mock_debounce_timer()->Fire();
int32_t tab_id2 = 0;
int32_t tab_id3 = 0;
// Get Tabs.
tab_search::mojom::PageHandler::GetProfileDataCallback callback1 =
base::BindLambdaForTesting(
[&](tab_search::mojom::ProfileDataPtr profile_tabs) {
ASSERT_EQ(2u, profile_tabs->windows.size());
auto* window1 = profile_tabs->windows[0].get();
ASSERT_TRUE(window1->active);
ASSERT_EQ(2u, window1->tabs.size());
auto* tab1 = window1->tabs[0].get();
ExpectNewTab(tab1, kTabUrl1, kTabName1, 0);
ASSERT_TRUE(tab1->active);
auto* tab2 = window1->tabs[1].get();
ExpectNewTab(tab2, kTabUrl2, kTabName2, 1);
ASSERT_FALSE(tab2->active);
auto* window2 = profile_tabs->windows[1].get();
ASSERT_FALSE(window2->active);
ASSERT_EQ(1u, window2->tabs.size());
auto* tab3 = window2->tabs[0].get();
ExpectNewTab(tab3, kTabUrl3, kTabName3, 0);
ASSERT_TRUE(tab3->active);
tab_id2 = tab2->tab_id;
tab_id3 = tab3->tab_id;
});
handler()->GetProfileData(std::move(callback1));
// Switch to 2nd tab.
auto switch_to_tab_info = tab_search::mojom::SwitchToTabInfo::New();
switch_to_tab_info->tab_id = tab_id2;
handler()->SwitchToTab(std::move(switch_to_tab_info));
// Get Tabs again to verify tab switch.
tab_search::mojom::PageHandler::GetProfileDataCallback callback2 =
base::BindLambdaForTesting(
[&](tab_search::mojom::ProfileDataPtr profile_tabs) {
ExpectProfileTabs(profile_tabs.get());
});
handler()->GetProfileData(std::move(callback2));
// Switch to 3rd tab.
switch_to_tab_info = tab_search::mojom::SwitchToTabInfo::New();
switch_to_tab_info->tab_id = tab_id3;
handler()->SwitchToTab(std::move(switch_to_tab_info));
// Get Tabs again to verify tab switch.
tab_search::mojom::PageHandler::GetProfileDataCallback callback3 =
base::BindLambdaForTesting(
[&](tab_search::mojom::ProfileDataPtr profile_tabs) {
ExpectProfileTabs(profile_tabs.get());
});
handler()->GetProfileData(std::move(callback3));
}
TEST_F(TabSearchPageHandlerTest, TabsAndGroups) {
TabRestoreServiceFactory::GetInstance()->SetTestingFactory(
profile(),
base::BindRepeating(&TabSearchPageHandlerTest::GetTabRestoreService));
// Add tabs to a browser.
AddTabWithTitle(browser1(), GURL(kTabUrl1), kTabName1);
AddTabWithTitle(browser1(), GURL(kTabUrl2), kTabName2);
TabStripModel* tab_strip_model = browser1()->tab_strip_model();
TabGroupModel* tab_group_model = tab_strip_model->group_model();
// Associate a tab to a given tab group.
tab_groups::TabGroupId group1 = tab_strip_model->AddToNewGroup({0});
std::u16string sample_title = u"Sample title";
const tab_groups::TabGroupColorId sample_color =
tab_groups::TabGroupColorId::kGrey;
tab_groups::TabGroupVisualData visual_data1(sample_title, sample_color);
tab_group_model->GetTabGroup(group1)->SetVisualData(visual_data1);
// Get Tabs and Tab Group details.
tab_search::mojom::PageHandler::GetProfileDataCallback callback1 =
base::BindLambdaForTesting(
[&](tab_search::mojom::ProfileDataPtr profile_tabs) {
ASSERT_EQ(2u, profile_tabs->windows.size());
auto* window1 = profile_tabs->windows[0].get();
ASSERT_TRUE(window1->active);
ASSERT_EQ(2u, window1->tabs.size());
ASSERT_EQ(1u, profile_tabs->tab_groups.size());
auto* tab_group = profile_tabs->tab_groups[0].get();
ASSERT_EQ(sample_color, tab_group->color);
ASSERT_EQ(base::UTF16ToUTF8(sample_title), tab_group->title);
});
handler()->GetProfileData(std::move(callback1));
// Close a group's tab.
int tab_id = extensions::ExtensionTabUtil::GetTabId(
browser1()->tab_strip_model()->GetWebContentsAt(0));
handler()->CloseTab(tab_id);
// Assert the closed tab's data is correct in ProfileData.
tab_search::mojom::PageHandler::GetProfileDataCallback callback2 =
base::BindLambdaForTesting(
[&](tab_search::mojom::ProfileDataPtr profile_tabs) {
ASSERT_EQ(2u, profile_tabs->windows.size());
auto* window1 = profile_tabs->windows[0].get();
ASSERT_TRUE(window1->active);
ASSERT_EQ(1u, window1->tabs.size());
auto& tab_groups = profile_tabs->tab_groups;
ASSERT_EQ(1u, tab_groups.size());
tab_search::mojom::TabGroup* tab_group = tab_groups[0].get();
ASSERT_EQ(sample_color, tab_group->color);
ASSERT_EQ(base::UTF16ToUTF8(sample_title), tab_group->title);
auto& recently_closed_tabs = profile_tabs->recently_closed_tabs;
ASSERT_EQ(1u, recently_closed_tabs.size());
tab_search::mojom::RecentlyClosedTab* tab =
recently_closed_tabs[0].get();
ExpectRecentlyClosedTab(tab, kTabUrl2, kTabName2);
ASSERT_TRUE(tab->group_id);
ASSERT_EQ(tab_group->id, tab->group_id);
});
handler()->GetProfileData(std::move(callback2));
EXPECT_CALL(page_, TabUpdated(_)).Times(1);
EXPECT_CALL(page_, TabsRemoved(_)).Times(2);
}
TEST_F(TabSearchPageHandlerTest, RecentlyClosedTabGroup) {
TabRestoreServiceFactory::GetInstance()->SetTestingFactory(
profile(),
base::BindRepeating(&TabSearchPageHandlerTest::GetTabRestoreService));
// Add tabs to a browser.
AddTabWithTitle(browser1(), GURL(kTabUrl1), kTabName1);
AddTabWithTitle(browser1(), GURL(kTabUrl2), kTabName2);
TabStripModel* tab_strip_model = browser1()->tab_strip_model();
TabGroupModel* tab_group_model = tab_strip_model->group_model();
// Associate a tab to a given tab group.
tab_groups::TabGroupId group1 = tab_strip_model->AddToNewGroup({0});
std::u16string sample_title = u"Sample title";
const tab_groups::TabGroupColorId sample_color =
tab_groups::TabGroupColorId::kGrey;
tab_groups::TabGroupVisualData visual_data1(sample_title, sample_color);
tab_group_model->GetTabGroup(group1)->SetVisualData(visual_data1);
// Close a group and its tabs.
tab_strip_model->CloseAllTabsInGroup(group1);
// Assert the closed tab group and tab data is correct in ProfileData.
tab_search::mojom::PageHandler::GetProfileDataCallback callback =
base::BindLambdaForTesting(
[&](tab_search::mojom::ProfileDataPtr profile_tabs) {
ASSERT_EQ(2u, profile_tabs->windows.size());
auto* window1 = profile_tabs->windows[0].get();
ASSERT_TRUE(window1->active);
ASSERT_EQ(1u, window1->tabs.size());
ASSERT_EQ(1u, profile_tabs->tab_groups.size());
auto& recently_closed_tab_groups =
profile_tabs->recently_closed_tab_groups;
ASSERT_EQ(1u, recently_closed_tab_groups.size());
tab_search::mojom::RecentlyClosedTabGroup* tab_group =
recently_closed_tab_groups[0].get();
ASSERT_EQ(sample_color, tab_group->color);
ASSERT_EQ(base::UTF16ToUTF8(sample_title), tab_group->title);
auto& recently_closed_tabs = profile_tabs->recently_closed_tabs;
ASSERT_EQ(1u, recently_closed_tabs.size());
tab_search::mojom::RecentlyClosedTab* tab =
recently_closed_tabs[0].get();
ExpectRecentlyClosedTab(tab, kTabUrl2, kTabName2);
ASSERT_TRUE(tab->group_id);
ASSERT_EQ(tab_group->id, tab->group_id);
});
handler()->GetProfileData(std::move(callback));
EXPECT_CALL(page_, TabUpdated(_)).Times(1);
EXPECT_CALL(page_, TabsRemoved(_)).Times(2);
}
TEST_F(TabSearchPageHandlerTest, RecentlyClosedWindowWithGroupTabs) {
TabRestoreServiceFactory::GetInstance()->SetTestingFactory(
profile(),
base::BindRepeating(&TabSearchPageHandlerTest::GetTabRestoreService));
// Add tabs to browser windows.
AddTabWithTitle(browser1(), GURL(kTabUrl1), kTabName1);
AddTabWithTitle(browser1(), GURL(kTabUrl2), kTabName2);
AddTabWithTitle(browser2(), GURL(kTabUrl3), kTabName3);
AddTabWithTitle(browser2(), GURL(kTabUrl4), kTabName4);
// Associate a tab to a given tab group.
TabStripModel* tab_strip_model = browser1()->tab_strip_model();
tab_groups::TabGroupId group1 = tab_strip_model->AddToNewGroup({0});
std::u16string sample_title = u"Sample title";
const tab_groups::TabGroupColorId sample_color =
tab_groups::TabGroupColorId::kGrey;
tab_groups::TabGroupVisualData visual_data1(sample_title, sample_color);
TabGroupModel* tab_group_model = tab_strip_model->group_model();
tab_group_model->GetTabGroup(group1)->SetVisualData(visual_data1);
// Close the tabs associated with a browser.
browser1()->tab_strip_model()->CloseAllTabs();
// Assert that the tabs that were in groups in the closed window contain the
// associated group data necessary to render properly.
tab_search::mojom::PageHandler::GetProfileDataCallback callback =
base::BindLambdaForTesting(
[&](tab_search::mojom::ProfileDataPtr profile_tabs) {
ASSERT_EQ(2u, profile_tabs->windows.size());
auto* window2 = profile_tabs->windows[1].get();
ASSERT_EQ(2u, window2->tabs.size());
auto& tab_groups = profile_tabs->tab_groups;
ASSERT_EQ(1u, tab_groups.size());
tab_search::mojom::TabGroup* tab_group = tab_groups[0].get();
ASSERT_EQ(sample_color, tab_group->color);
ASSERT_EQ(base::UTF16ToUTF8(sample_title), tab_group->title);
auto& recently_closed_tabs = profile_tabs->recently_closed_tabs;
ASSERT_EQ(2u, recently_closed_tabs.size());
tab_search::mojom::RecentlyClosedTab* tab =
recently_closed_tabs[0].get();
ExpectRecentlyClosedTab(tab, kTabUrl2, kTabName2);
ASSERT_TRUE(tab->group_id);
});
handler()->GetProfileData(std::move(callback));
EXPECT_CALL(page_, TabUpdated(_)).Times(2);
EXPECT_CALL(page_, TabsRemoved(_)).Times(2);
}
// Ensure that repeated tab model changes do not result in repeated calls to
// TabsChanged() and TabsChanged() is only called when the page handler's
// timer fires.
TEST_F(TabSearchPageHandlerTest, TabsChanged) {
EXPECT_CALL(page_, TabsChanged(_)).Times(3);
EXPECT_CALL(page_, TabUpdated(_)).Times(1);
EXPECT_CALL(page_, TabsRemoved(_)).Times(3);
FireTimer(); // Will call TabsChanged().
// Add 2 tabs in browser1.
ASSERT_FALSE(IsTimerRunning());
AddTabWithTitle(browser1(), GURL(kTabUrl1),
kTabName1); // Will kick off timer.
ASSERT_TRUE(IsTimerRunning());
AddTabWithTitle(browser1(), GURL(kTabUrl2), kTabName2);
// Subsequent tabs change will not change the state of the timer.
ASSERT_TRUE(IsTimerRunning());
FireTimer(); // Will call TabsChanged().
// Add 1 tab in browser2.
ASSERT_FALSE(IsTimerRunning());
AddTabWithTitle(browser2(), GURL(kTabUrl3), kTabName3);
ASSERT_TRUE(IsTimerRunning());
FireTimer(); // Will call TabsChanged().
// Close a tab in browser 1.
ASSERT_FALSE(IsTimerRunning());
browser1()->tab_strip_model()->CloseWebContentsAt(
0, TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
ASSERT_FALSE(IsTimerRunning());
}
// Assert that no browser -> renderer messages are sent when the WebUI is not
// visible.
TEST_F(TabSearchPageHandlerTest, EventsDoNotPropagatedWhenWebUIIsHidden) {
HideWebContents();
EXPECT_CALL(page_, TabsChanged(_)).Times(0);
EXPECT_CALL(page_, TabUpdated(_)).Times(0);
EXPECT_CALL(page_, TabsRemoved(_)).Times(0);
FireTimer();
// Inserting tabs should not cause the debounce timer to start running.
ASSERT_FALSE(IsTimerRunning());
AddTabWithTitle(browser1(), GURL(kTabUrl1), kTabName1);
ASSERT_FALSE(IsTimerRunning());
// Adding the following tab would usually trigger TabUpdated() for the first
// tab since the tab index will change from 0 to 1
AddTabWithTitle(browser1(), GURL(kTabUrl2), kTabName2);
// Closing a tab would usually result in a call to TabsRemoved().
browser1()->tab_strip_model()->CloseWebContentsAt(
0, TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
}
// Ensure that tab model changes in a browser with a different profile
// will not call TabsChanged().
TEST_F(TabSearchPageHandlerTest, TabsNotChanged) {
EXPECT_CALL(page_, TabsChanged(_)).Times(1);
EXPECT_CALL(page_, TabUpdated(_)).Times(0);
FireTimer(); // Will call TabsChanged().
ASSERT_FALSE(IsTimerRunning());
AddTabWithTitle(browser3(), GURL(kTabUrl1),
kTabName1); // Will not kick off timer.
ASSERT_FALSE(IsTimerRunning());
AddTabWithTitle(browser4(), GURL(kTabUrl2),
kTabName2); // Will not kick off timer.
ASSERT_FALSE(IsTimerRunning());
}
bool VerifyTabUpdated(const tab_search::mojom::TabPtr& tab) {
ExpectNewTab(tab.get(), kTabUrl1, kTabName1, 1);
return true;
}
// Verify tab update event is called correctly with data
TEST_F(TabSearchPageHandlerTest, TabUpdated) {
EXPECT_CALL(page_, TabsChanged(_)).Times(1);
EXPECT_CALL(page_, TabUpdated(Truly(VerifyTabUpdated))).Times(1);
EXPECT_CALL(page_, TabsRemoved(_)).Times(1);
AddTabWithTitle(browser1(), GURL(kTabUrl1), kTabName1);
// Adding the following tab will trigger TabUpdated() to the first tab
// since the tab index will change from 0 to 1
AddTabWithTitle(browser1(), GURL(kTabUrl2), kTabName2);
FireTimer();
}
TEST_F(TabSearchPageHandlerTest, CloseTab) {
AddTabWithTitle(browser1(), GURL(kTabUrl1), kTabName1);
AddTabWithTitle(browser2(), GURL(kTabUrl2), kTabName2);
AddTabWithTitle(browser2(), GURL(kTabUrl2), kTabName2);
ASSERT_EQ(1, browser1()->tab_strip_model()->count());
ASSERT_EQ(2, browser2()->tab_strip_model()->count());
int tab_id = extensions::ExtensionTabUtil::GetTabId(
browser2()->tab_strip_model()->GetWebContentsAt(0));
EXPECT_CALL(page_, TabUpdated(_)).Times(1);
EXPECT_CALL(page_, TabsRemoved(_)).Times(3);
handler()->CloseTab(tab_id);
ASSERT_EQ(1, browser1()->tab_strip_model()->count());
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);
}
TEST_F(TabSearchPageHandlerTest, OpenRecentlyClosedTab) {
TabRestoreServiceFactory::GetInstance()->SetTestingFactory(
profile(),
base::BindRepeating(&TabSearchPageHandlerTest::GetTabRestoreService));
AddTabWithTitle(browser1(), GURL(kTabUrl1), kTabName1);
AddTabWithTitle(browser1(), GURL(kTabUrl2), kTabName2);
int tab_id = extensions::ExtensionTabUtil::GetTabId(
browser1()->tab_strip_model()->GetWebContentsAt(0));
handler()->CloseTab(tab_id);
tab_search::mojom::PageHandler::GetProfileDataCallback callback1 =
base::BindLambdaForTesting(
[&](tab_search::mojom::ProfileDataPtr profile_tabs) {
auto& tabs = profile_tabs->windows[0]->tabs;
ASSERT_EQ(1u, tabs.size());
ExpectNewTab(tabs[0].get(), kTabUrl1, kTabName1, 0);
auto& recently_closed_tabs = profile_tabs->recently_closed_tabs;
ASSERT_EQ(1u, recently_closed_tabs.size());
ExpectRecentlyClosedTab(recently_closed_tabs[0].get(), kTabUrl2,
kTabName2);
tab_id = recently_closed_tabs[0]->tab_id;
});
handler()->GetProfileData(std::move(callback1));
handler()->OpenRecentlyClosedEntry(tab_id);
tab_search::mojom::PageHandler::GetProfileDataCallback callback2 =
base::BindLambdaForTesting(
[&](tab_search::mojom::ProfileDataPtr profile_tabs) {
auto& tabs = profile_tabs->windows[0]->tabs;
ASSERT_EQ(2u, tabs.size());
ExpectNewTab(tabs[0].get(), kTabUrl1, kTabName1, 0);
ExpectNewTab(tabs[1].get(), kTabUrl2, kTabName2, 1);
auto& recently_closed_tabs = profile_tabs->recently_closed_tabs;
ASSERT_EQ(0u, recently_closed_tabs.size());
});
handler()->GetProfileData(std::move(callback2));
EXPECT_CALL(page_, TabUpdated(_)).Times(1);
EXPECT_CALL(page_, TabsRemoved(_)).Times(2);
}
TEST_F(TabSearchPageHandlerTest, RecentlyClosedTabsHaveNoRepeatedURLEntry) {
TabRestoreServiceFactory::GetInstance()->SetTestingFactory(
profile(),
base::BindRepeating(&TabSearchPageHandlerTest::GetTabRestoreService));
AddTabWithTitle(browser1(), GURL(kTabUrl1), kTabName1);
AddTabWithTitle(browser1(), GURL(kTabUrl1), kTabName1);
browser1()->tab_strip_model()->CloseAllTabs();
EXPECT_CALL(page_, TabsRemoved(_)).Times(1);
EXPECT_CALL(page_, TabUpdated(_)).Times(1);
tab_search::mojom::PageHandler::GetProfileDataCallback callback1 =
base::BindLambdaForTesting(
[&](tab_search::mojom::ProfileDataPtr profile_tabs) {
auto& recently_closed_tabs = profile_tabs->recently_closed_tabs;
ASSERT_EQ(1u, recently_closed_tabs.size());
ExpectRecentlyClosedTab(recently_closed_tabs[0].get(), kTabUrl1,
kTabName1);
});
handler()->GetProfileData(std::move(callback1));
}
TEST_F(TabSearchPageHandlerTest,
RecentlyClosedTabGroupsHaveNoRepeatedURLEntries) {
TabRestoreServiceFactory::GetInstance()->SetTestingFactory(
profile(),
base::BindRepeating(&TabSearchPageHandlerTest::GetTabRestoreService));
// Add tabs to a browser.
AddTabWithTitle(browser1(), GURL(kTabUrl1), kTabName1);
AddTabWithTitle(browser1(), GURL(kTabUrl1), kTabName1);
AddTabWithTitle(browser2(), GURL(kTabUrl1), kTabName1);
AddTabWithTitle(browser2(), GURL(kTabUrl1), kTabName1);
// Associate tabs to a given tab group.
TabStripModel* tab_strip_model = browser1()->tab_strip_model();
tab_groups::TabGroupId group1 = tab_strip_model->AddToNewGroup({0, 1});
std::u16string sample_title = u"Sample title";
const tab_groups::TabGroupColorId sample_color =
tab_groups::TabGroupColorId::kGrey;
tab_groups::TabGroupVisualData visual_data1(sample_title, sample_color);
TabGroupModel* tab_group_model = tab_strip_model->group_model();
tab_group_model->GetTabGroup(group1)->SetVisualData(visual_data1);
browser1()->tab_strip_model()->CloseAllTabs();
browser2()->tab_strip_model()->CloseAllTabs();
tab_search::mojom::PageHandler::GetProfileDataCallback callback1 =
base::BindLambdaForTesting(
[&](tab_search::mojom::ProfileDataPtr profile_tabs) {
auto& recently_closed_tabs = profile_tabs->recently_closed_tabs;
ASSERT_EQ(2u, recently_closed_tabs.size());
ExpectRecentlyClosedTab(recently_closed_tabs[0].get(), kTabUrl1,
kTabName1);
ExpectRecentlyClosedTab(recently_closed_tabs[1].get(), kTabUrl1,
kTabName1);
});
handler()->GetProfileData(std::move(callback1));
EXPECT_CALL(page_, TabsRemoved(_)).Times(2);
EXPECT_CALL(page_, TabUpdated(_)).Times(2);
}
TEST_F(TabSearchPageHandlerTest, RecentlyClosedTabEntriesFilterOpenTabUrls) {
TabRestoreServiceFactory::GetInstance()->SetTestingFactory(
profile(),
base::BindRepeating(&TabSearchPageHandlerTest::GetTabRestoreService));
AddTabWithTitle(browser1(), GURL(kTabUrl1), kTabName1);
AddTabWithTitle(browser1(), GURL(kTabUrl1), kTabName1);
int tab_id = extensions::ExtensionTabUtil::GetTabId(
browser1()->tab_strip_model()->GetWebContentsAt(0));
handler()->CloseTab(tab_id);
EXPECT_CALL(page_, TabsRemoved(_)).Times(2);
EXPECT_CALL(page_, TabUpdated(_)).Times(1);
tab_search::mojom::PageHandler::GetProfileDataCallback callback1 =
base::BindLambdaForTesting(
[&](tab_search::mojom::ProfileDataPtr profile_tabs) {
auto& tabs = profile_tabs->windows[0]->tabs;
ASSERT_EQ(1u, tabs.size());
ExpectNewTab(tabs[0].get(), kTabUrl1, kTabName1, 0);
auto& recently_closed_tabs = profile_tabs->recently_closed_tabs;
ASSERT_EQ(0u, recently_closed_tabs.size());
});
handler()->GetProfileData(std::move(callback1));
}
} // namespace