// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/shared/model/web_state_list/browser_util.h"
#import <memory>
#import "base/memory/raw_ptr.h"
#import "components/tab_groups/tab_group_id.h"
#import "ios/chrome/browser/sessions/model/fake_tab_restore_service.h"
#import "ios/chrome/browser/sessions/model/ios_chrome_tab_restore_service_factory.h"
#import "ios/chrome/browser/shared/model/browser/browser_list.h"
#import "ios/chrome/browser/shared/model/browser/browser_list_factory.h"
#import "ios/chrome/browser/shared/model/browser/test/test_browser.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_opener.h"
#import "ios/chrome/browser/snapshots/model/snapshot_browser_agent.h"
#import "ios/chrome/browser/snapshots/model/snapshot_id.h"
#import "ios/chrome/browser/snapshots/model/snapshot_storage_wrapper.h"
#import "ios/chrome/browser/snapshots/model/snapshot_tab_helper.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "ios/web/public/test/web_task_environment.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"
#import "ui/base/test/ios/ui_image_test_utils.h"
using ui::test::uiimage_utils::UIImagesAreEqual;
using ui::test::uiimage_utils::UIImageWithSizeAndSolidColor;
namespace {
// Name of the directory where snapshots are saved.
const char kIdentifier0[] = "Identifier0";
const char kIdentifier1[] = "Identifier1";
} // namespace
// Test fixture for testing functions in browser_util.h/mm.
class BrowserUtilTest : public PlatformTest {
protected:
BrowserUtilTest() {
TestChromeBrowserState::Builder test_browser_state_builder;
test_browser_state_builder.AddTestingFactory(
IOSChromeTabRestoreServiceFactory::GetInstance(),
FakeTabRestoreService::GetTestingFactory());
chrome_browser_state_ = std::move(test_browser_state_builder).Build();
browser_ = std::make_unique<TestBrowser>(chrome_browser_state_.get());
other_browser_ = std::make_unique<TestBrowser>(chrome_browser_state_.get());
incognito_browser_ = std::make_unique<TestBrowser>(
chrome_browser_state_->GetOffTheRecordChromeBrowserState());
other_incognito_browser_ = std::make_unique<TestBrowser>(
chrome_browser_state_->GetOffTheRecordChromeBrowserState());
browser_list_ =
BrowserListFactory::GetForBrowserState(chrome_browser_state_.get());
browser_list_->AddBrowser(browser_.get());
browser_list_->AddBrowser(other_browser_.get());
browser_list_->AddBrowser(incognito_browser_.get());
browser_list_->AddBrowser(other_incognito_browser_.get());
SnapshotBrowserAgent::CreateForBrowser(browser_.get());
SnapshotBrowserAgent::CreateForBrowser(other_browser_.get());
SnapshotBrowserAgent::CreateForBrowser(incognito_browser_.get());
SnapshotBrowserAgent::CreateForBrowser(other_incognito_browser_.get());
AppendNewWebState(browser_.get());
AppendNewWebState(browser_.get());
AppendNewWebState(browser_.get());
AppendNewWebState(incognito_browser_.get());
tab_restore_service_ =
IOSChromeTabRestoreServiceFactory::GetForBrowserState(
chrome_browser_state_.get());
}
// Appends a new web state in the web state list of `browser`.
web::FakeWebState* AppendNewWebState(Browser* browser) {
auto fake_web_state = std::make_unique<web::FakeWebState>();
web::FakeWebState* inserted_web_state = fake_web_state.get();
SnapshotTabHelper::CreateForWebState(inserted_web_state);
browser->GetWebStateList()->InsertWebState(
std::move(fake_web_state),
WebStateList::InsertionParams::Automatic().Activate());
return inserted_web_state;
}
// Returns the tab ID for the web state at `index` in `browser`.
web::WebStateID GetTabIDForWebStateAt(int index, Browser* browser) {
web::WebState* web_state = browser->GetWebStateList()->GetWebStateAt(index);
return web_state->GetUniqueIdentifier();
}
// Returns the cached snapshot for the given snapshot ID in the given snapshot
// cache.
UIImage* GetSnapshot(SnapshotStorageWrapper* snapshot_storage,
SnapshotID snapshot_id) {
CHECK(snapshot_storage);
base::RunLoop run_loop;
base::RunLoop* run_loop_ptr = &run_loop;
__block UIImage* snapshot = nil;
[snapshot_storage retrieveImageForSnapshotID:snapshot_id
callback:^(UIImage* cached_snapshot) {
snapshot = cached_snapshot;
run_loop_ptr->Quit();
}];
run_loop.Run();
return snapshot;
}
web::WebTaskEnvironment task_environment_;
std::unique_ptr<TestChromeBrowserState> chrome_browser_state_;
std::unique_ptr<Browser> browser_;
std::unique_ptr<Browser> other_browser_;
std::unique_ptr<Browser> incognito_browser_;
std::unique_ptr<Browser> other_incognito_browser_;
raw_ptr<BrowserList> browser_list_;
raw_ptr<sessions::TabRestoreService> tab_restore_service_;
};
// Tests that an incognito tab is moved from one incognito browser to another.
TEST_F(BrowserUtilTest, TestMoveTabAcrossIncognitoBrowsers) {
ASSERT_EQ(1, incognito_browser_->GetWebStateList()->count());
ASSERT_TRUE(other_incognito_browser_->GetWebStateList()->empty());
ASSERT_TRUE(tab_restore_service_->entries().empty());
web::WebStateID tab_id = GetTabIDForWebStateAt(0, incognito_browser_.get());
BrowserAndIndex tab_info = FindBrowserAndIndex(
tab_id,
browser_list_->BrowsersOfType(BrowserList::BrowserType::kIncognito));
ASSERT_EQ(tab_info.tab_index, 0);
ASSERT_EQ(tab_info.browser, incognito_browser_.get());
MoveTabToBrowser(tab_id, other_incognito_browser_.get(),
/*destination_index=*/0);
ASSERT_TRUE(tab_restore_service_->entries().empty());
EXPECT_TRUE(incognito_browser_->GetWebStateList()->empty());
EXPECT_EQ(1, other_incognito_browser_->GetWebStateList()->count());
}
// Tests that a tab is moved from one regular browser (with several tabs) to
// another browser.
TEST_F(BrowserUtilTest, TestMoveTabAcrossRegularBrowsers) {
ASSERT_EQ(3, browser_->GetWebStateList()->count());
ASSERT_TRUE(other_browser_->GetWebStateList()->empty());
ASSERT_TRUE(tab_restore_service_->entries().empty());
web::WebStateID tab_id = GetTabIDForWebStateAt(1, browser_.get());
BrowserAndIndex tab_info = FindBrowserAndIndex(
tab_id, browser_list_->BrowsersOfType(
BrowserList::BrowserType::kRegularAndInactive));
ASSERT_EQ(tab_info.tab_index, 1);
ASSERT_EQ(tab_info.browser, browser_.get());
MoveTabToBrowser(tab_id, other_browser_.get(), /*destination_index=*/0);
ASSERT_TRUE(tab_restore_service_->entries().empty());
EXPECT_EQ(2, browser_->GetWebStateList()->count());
EXPECT_EQ(1, other_browser_->GetWebStateList()->count());
EXPECT_NE(tab_id, GetTabIDForWebStateAt(1, browser_.get()));
EXPECT_EQ(tab_id, GetTabIDForWebStateAt(0, other_browser_.get()));
}
// Tests `FindBrowserAndIndex:` with an unknown tab_id.
TEST_F(BrowserUtilTest, TestFindBrowserAndIndexWithUnknownId) {
web::WebStateID tab_id = web::WebStateID::NewUnique();
BrowserAndIndex tab_info = FindBrowserAndIndex(
tab_id, browser_list_->BrowsersOfType(
BrowserList::BrowserType::kRegularAndInactive));
ASSERT_EQ(tab_info.tab_index, WebStateList::kInvalidIndex);
EXPECT_NE(tab_info.browser, browser_.get());
tab_info = FindBrowserAndIndex(
tab_id,
browser_list_->BrowsersOfType(BrowserList::BrowserType::kIncognito));
ASSERT_EQ(tab_info.tab_index, WebStateList::kInvalidIndex);
EXPECT_NE(tab_info.browser, incognito_browser_.get());
}
// Tests that a tab is reordered within the same browser.
TEST_F(BrowserUtilTest, TestReorderTabWithinSameBrowser) {
ASSERT_EQ(3, browser_->GetWebStateList()->count());
ASSERT_TRUE(tab_restore_service_->entries().empty());
web::WebStateID tab_id = GetTabIDForWebStateAt(0, browser_.get());
BrowserAndIndex tab_info = FindBrowserAndIndex(
tab_id, browser_list_->BrowsersOfType(
BrowserList::BrowserType::kRegularAndInactive));
ASSERT_EQ(tab_info.tab_index, 0);
ASSERT_EQ(tab_info.browser, browser_.get());
MoveTabToBrowser(tab_id, browser_.get(), /*destination_index=*/2);
ASSERT_TRUE(tab_restore_service_->entries().empty());
EXPECT_EQ(3, browser_->GetWebStateList()->count());
EXPECT_NE(tab_id, GetTabIDForWebStateAt(0, browser_.get()));
EXPECT_EQ(tab_id, GetTabIDForWebStateAt(2, browser_.get()));
}
// Tests that snapshots are correctly moved when moving a web state from active
// to inactive browser.
TEST_F(BrowserUtilTest, TestMovedSnapshot) {
// Set a snapshot to the first web state of `browser_`.
web::WebState* web_state = browser_->GetWebStateList()->GetWebStateAt(0);
SnapshotBrowserAgent::CreateForBrowser(browser_.get());
SnapshotBrowserAgent* agent =
SnapshotBrowserAgent::FromBrowser(browser_.get());
agent->SetSessionID(kIdentifier0);
SnapshotStorageWrapper* snapshot_storage = agent->snapshot_storage();
ASSERT_NE(nil, snapshot_storage);
UIImage* snapshot = UIImageWithSizeAndSolidColor({10, 20}, UIColor.redColor);
ASSERT_NE(nil, snapshot);
SnapshotTabHelper* snapshot_tab_helper =
SnapshotTabHelper::FromWebState(web_state);
SnapshotID snapshot_id = snapshot_tab_helper->GetSnapshotID();
[snapshot_storage setImage:snapshot withSnapshotID:snapshot_id];
ASSERT_TRUE(
UIImagesAreEqual(snapshot, GetSnapshot(snapshot_storage, snapshot_id)));
// Check that the other browser doesn’t have a snapshot for that identifier.
SnapshotBrowserAgent::CreateForBrowser(other_browser_.get());
SnapshotBrowserAgent* other_agent =
SnapshotBrowserAgent::FromBrowser(other_browser_.get());
other_agent->SetSessionID(kIdentifier1);
SnapshotStorageWrapper* other_snapshot_storage =
other_agent->snapshot_storage();
ASSERT_NE(nil, other_snapshot_storage);
ASSERT_EQ(nil, GetSnapshot(other_snapshot_storage, snapshot_id));
// Migrate the tab between browsers.
MoveTabFromBrowserToBrowser(browser_.get(), 0, other_browser_.get(), 0);
EXPECT_EQ(nil, GetSnapshot(snapshot_storage, snapshot_id));
EXPECT_TRUE(UIImagesAreEqual(
snapshot, GetSnapshot(other_snapshot_storage, snapshot_id)));
}