// 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/browser/browser_list.h"
#import <memory>
#import "base/test/task_environment.h"
#import "ios/chrome/browser/shared/model/browser/test/test_browser.h"
#import "ios/chrome/browser/shared/model/browser/test/test_browser_list_observer.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/web/public/test/web_task_environment.h"
#import "testing/platform_test.h"
using BrowserType = BrowserList::BrowserType;
class BrowserListTest : public PlatformTest {
public:
BrowserListTest() {
browser_state_ = TestChromeBrowserState::Builder().Build();
}
BrowserList* browser_list() { return &browser_list_; }
ChromeBrowserState* browser_state() { return browser_state_.get(); }
private:
base::test::TaskEnvironment task_environment_;
std::unique_ptr<TestChromeBrowserState> browser_state_;
BrowserList browser_list_;
};
// Tests main add/remove logic.
TEST_F(BrowserListTest, AddRemoveBrowsers) {
// Browser list should start empty
EXPECT_EQ(0UL, browser_list()->BrowsersOfType(BrowserType::kAll).size());
TestBrowser browser_1(browser_state());
// Adding a browser should result in it appearing in the list.
browser_list()->AddBrowser(&browser_1);
std::set<Browser*> browsers =
browser_list()->BrowsersOfType(BrowserType::kRegular);
EXPECT_EQ(1UL, browsers.size());
auto found_browser = browsers.find(&browser_1);
EXPECT_EQ(&browser_1, *found_browser);
TestBrowser browser_2(browser_state());
// Removing a browser not in the list is a no-op.
browser_list()->RemoveBrowser(&browser_2);
EXPECT_EQ(1UL, browser_list()->BrowsersOfType(BrowserType::kRegular).size());
// More than one browset can be added to the list.
browser_list()->AddBrowser(&browser_2);
EXPECT_EQ(2UL, browser_list()->BrowsersOfType(BrowserType::kRegular).size());
// Removing a browser works -- the list gets smaller, and the removed browser
// isn't on it.
browser_list()->RemoveBrowser(&browser_2);
browsers = browser_list()->BrowsersOfType(BrowserType::kRegular);
EXPECT_EQ(1UL, browsers.size());
found_browser = browsers.find(&browser_2);
EXPECT_EQ(browsers.end(), found_browser);
// Removing a browser a second time does nothing.
browser_list()->RemoveBrowser(&browser_2);
EXPECT_EQ(1UL, browser_list()->BrowsersOfType(BrowserType::kRegular).size());
// Removing the last browser, even multiple times, works as expected.
browser_list()->RemoveBrowser(&browser_1);
browser_list()->RemoveBrowser(&browser_1);
EXPECT_EQ(0UL, browser_list()->BrowsersOfType(BrowserType::kRegular).size());
}
// Tests regular/incognito/inactive interactions.
TEST_F(BrowserListTest, AddRemoveIncognitoAndInactiveBrowsers) {
// Incognito browser list starts empty.
EXPECT_EQ(0UL, browser_list()->BrowsersOfType(BrowserType::kAll).size());
TestBrowser browser_1(browser_state());
Browser* inactive_browser_1 = browser_1.CreateInactiveBrowser();
ChromeBrowserState* incognito_browser_state =
browser_state()->GetOffTheRecordChromeBrowserState();
TestBrowser incognito_browser_1(incognito_browser_state);
// Adding a regular browser doesn't affect the incognito/inactive lists.
browser_list()->AddBrowser(&browser_1);
EXPECT_EQ(1UL, browser_list()->BrowsersOfType(BrowserType::kRegular).size());
EXPECT_EQ(0UL,
browser_list()->BrowsersOfType(BrowserType::kIncognito).size());
EXPECT_EQ(0UL, browser_list()->BrowsersOfType(BrowserType::kInactive).size());
EXPECT_EQ(1UL, browser_list()->BrowsersOfType(BrowserType::kAll).size());
// Adding an incognito browser doesn't affect the regular/inactive lists.
browser_list()->AddBrowser(&incognito_browser_1);
EXPECT_EQ(1UL, browser_list()->BrowsersOfType(BrowserType::kRegular).size());
EXPECT_EQ(1UL,
browser_list()->BrowsersOfType(BrowserType::kIncognito).size());
EXPECT_EQ(0UL, browser_list()->BrowsersOfType(BrowserType::kInactive).size());
EXPECT_EQ(2UL, browser_list()->BrowsersOfType(BrowserType::kAll).size());
// Adding an inactive browser doesn't affect the regular/incognito lists.
browser_list()->AddBrowser(inactive_browser_1);
EXPECT_EQ(1UL, browser_list()->BrowsersOfType(BrowserType::kRegular).size());
EXPECT_EQ(1UL,
browser_list()->BrowsersOfType(BrowserType::kIncognito).size());
EXPECT_EQ(1UL, browser_list()->BrowsersOfType(BrowserType::kInactive).size());
EXPECT_EQ(3UL, browser_list()->BrowsersOfType(BrowserType::kAll).size());
// An added incognito browser is in the list.
std::set<Browser*> browsers =
browser_list()->BrowsersOfType(BrowserType::kIncognito);
auto found_browser = browsers.find(&incognito_browser_1);
EXPECT_EQ(&incognito_browser_1, *found_browser);
// An added inactive browser is in the list.
std::set<Browser*> inactive_browsers =
browser_list()->BrowsersOfType(BrowserType::kInactive);
auto found_inactive_browser = inactive_browsers.find(inactive_browser_1);
EXPECT_EQ(inactive_browser_1, *found_inactive_browser);
// Removing browsers from works as expected.
browser_list()->RemoveBrowser(&browser_1);
browser_list()->RemoveBrowser(&incognito_browser_1);
browser_list()->RemoveBrowser(inactive_browser_1);
EXPECT_EQ(0UL, browser_list()->BrowsersOfType(BrowserType::kAll).size());
}
// Tests that destroyed browsers are auto-removed.
TEST_F(BrowserListTest, AutoRemoveBrowsers) {
{
// Create and add scoped browsers
TestBrowser browser_1(browser_state());
browser_list()->AddBrowser(&browser_1);
EXPECT_EQ(1UL,
browser_list()->BrowsersOfType(BrowserType::kRegular).size());
ChromeBrowserState* incognito_browser_state =
browser_state()->GetOffTheRecordChromeBrowserState();
TestBrowser incognito_browser_1(incognito_browser_state);
browser_list()->AddBrowser(&incognito_browser_1);
EXPECT_EQ(1UL,
browser_list()->BrowsersOfType(BrowserType::kIncognito).size());
}
// Expect that the browsers going out of scope will have triggered removal.
EXPECT_EQ(0UL, browser_list()->BrowsersOfType(BrowserType::kAll).size());
}
// Tests that values returned from BrowsersOfType aren't affected by subsequent
// changes to the browser list.
TEST_F(BrowserListTest, AllBrowserValuesDontChange) {
TestBrowser browser_1(browser_state());
// Add a browser and get the current set of browsers.
browser_list()->AddBrowser(&browser_1);
std::set<Browser*> browsers =
browser_list()->BrowsersOfType(BrowserType::kAll);
EXPECT_EQ(1UL, browsers.size());
// Remove the browser.
browser_list()->RemoveBrowser(&browser_1);
EXPECT_EQ(0UL, browser_list()->BrowsersOfType(BrowserType::kAll).size());
EXPECT_EQ(1UL, browsers.size());
}
// Checks that an observer is informed of additions and removals to both the
// regular and incognito browser lists.
TEST_F(BrowserListTest, BrowserListObserver) {
TestBrowserListObserver observer;
browser_list()->AddObserver(&observer);
TestBrowser browser_1(browser_state());
ChromeBrowserState* incognito_browser_state =
browser_state()->GetOffTheRecordChromeBrowserState();
TestBrowser incognito_browser_1(incognito_browser_state);
// Check that a regular addition is observed.
browser_list()->AddBrowser(&browser_1);
EXPECT_EQ(&browser_1, observer.GetLastAddedBrowser());
EXPECT_EQ(1UL, observer.GetLastBrowsers().size());
// Check that a no-op removal is *not* observed.
browser_list()->RemoveBrowser(&incognito_browser_1);
EXPECT_EQ(nullptr, observer.GetLastRemovedBrowser());
// Check that a regular removal is observed.
browser_list()->RemoveBrowser(&browser_1);
EXPECT_EQ(&browser_1, observer.GetLastRemovedBrowser());
EXPECT_EQ(0UL, observer.GetLastBrowsers().size());
// Check that an incognito addition is observed.
browser_list()->AddBrowser(&incognito_browser_1);
EXPECT_EQ(&incognito_browser_1, observer.GetLastAddedIncognitoBrowser());
EXPECT_EQ(1UL, observer.GetLastIncognitoBrowsers().size());
// Check that an incognito removal is observed.
browser_list()->RemoveBrowser(&incognito_browser_1);
EXPECT_EQ(&incognito_browser_1, observer.GetLastRemovedIncognitoBrowser());
EXPECT_EQ(0UL, observer.GetLastIncognitoBrowsers().size());
browser_list()->RemoveObserver(&observer);
}
// Checks that destroying the BrowserList informs the observer.
// TestBrowserListObserver knows to remove itself as an Observer
// when BrowserList::OnBrowserListShutdown() is called.
TEST_F(BrowserListTest, DeleteBrowserState) {
TestBrowserListObserver observer;
// Use a locally scoped BrowserList to control destruction.
{
BrowserList browser_list;
browser_list.AddObserver(&observer);
// Use a locally scoped Browser to control destruction.
{
TestBrowser browser_1(browser_state());
browser_list.AddBrowser(&browser_1);
// Destroy the Browser, nothing should break.
}
// Destroy the BrowserList, nothing should break.
}
}
// Checks that the BrowserList is still functional after the destruction of
// the off-the-record ChromeBrowserState (since this happen during normal
// use of the application).
TEST_F(BrowserListTest, ShutdownOTRBrowserState) {
TestBrowserListObserver observer;
browser_list()->AddObserver(&observer);
TestBrowser browser_1(browser_state());
// Use a block to ensure that the Browser pointing to the off-the-record
// ChromeBrowserState does not outlive the object (which would cause an
// use-after-free access in when BrowserList is informed of the Browser
// destruction).
{
ChromeBrowserState* incognito_browser_state =
browser_state()->GetOffTheRecordChromeBrowserState();
TestBrowser incognito_browser_1(incognito_browser_state);
browser_list()->AddBrowser(&incognito_browser_1);
// Check that adding/removing incognito is observed.
EXPECT_EQ(&incognito_browser_1, observer.GetLastAddedIncognitoBrowser());
EXPECT_EQ(1UL, observer.GetLastIncognitoBrowsers().size());
browser_list()->AddBrowser(&browser_1);
// Check that a regular addition is observed.
EXPECT_EQ(&browser_1, observer.GetLastAddedBrowser());
EXPECT_EQ(1UL, observer.GetLastBrowsers().size());
}
// Destroy the off-the-record ChromeBrowserState.
browser_state()->DestroyOffTheRecordChromeBrowserState();
ASSERT_FALSE(browser_state()->HasOffTheRecordChromeBrowserState());
TestBrowser browser_2(browser_state());
browser_list()->AddBrowser(&browser_2);
// Check that another regular addition is observed.
EXPECT_EQ(&browser_2, observer.GetLastAddedBrowser());
EXPECT_EQ(2UL, observer.GetLastBrowsers().size());
// Check that a regular removal is observed.
browser_list()->RemoveBrowser(&browser_1);
EXPECT_EQ(&browser_1, observer.GetLastRemovedBrowser());
EXPECT_EQ(1UL, observer.GetLastBrowsers().size());
browser_list()->RemoveObserver(&observer);
}