// Copyright 2016 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/ui/main/browser_view_wrangler.h"
#import <UIKit/UIKit.h>
#import "base/scoped_multi_source_observation.h"
#import "base/scoped_observation.h"
#import "base/test/scoped_feature_list.h"
#import "components/bookmarks/test/bookmark_test_helpers.h"
#import "ios/chrome/browser/bookmarks/model/bookmark_model_factory.h"
#import "ios/chrome/browser/browser_view/ui_bundled/browser_view_controller.h"
#import "ios/chrome/browser/favicon/model/favicon_service_factory.h"
#import "ios/chrome/browser/favicon/model/ios_chrome_favicon_loader_factory.h"
#import "ios/chrome/browser/favicon/model/ios_chrome_large_icon_service_factory.h"
#import "ios/chrome/browser/history/model/history_service_factory.h"
#import "ios/chrome/browser/prerender/model/prerender_service_factory.h"
#import "ios/chrome/browser/search_engines/model/template_url_service_factory.h"
#import "ios/chrome/browser/sessions/model/session_restoration_observer.h"
#import "ios/chrome/browser/sessions/model/session_restoration_service.h"
#import "ios/chrome/browser/sessions/model/session_restoration_service_factory.h"
#import "ios/chrome/browser/sessions/model/test_session_restoration_observer.h"
#import "ios/chrome/browser/sessions/model/test_session_restoration_service.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_util_test_support.h"
#import "ios/chrome/browser/shared/model/browser/browser.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_list_observer.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/signin/model/authentication_service_factory.h"
#import "ios/chrome/browser/signin/model/fake_authentication_service_delegate.h"
#import "ios/chrome/browser/sync/model/send_tab_to_self_sync_service_factory.h"
#import "ios/chrome/browser/tabs/model/inactive_tabs/features.h"
#import "ios/chrome/browser/ui/main/wrangled_browser.h"
#import "ios/chrome/test/ios_chrome_scoped_testing_local_state.h"
#import "ios/web/public/test/web_task_environment.h"
#import "testing/platform_test.h"
#import "ui/base/device_form_factor.h"
class BrowserViewWranglerTest : public PlatformTest {
protected:
BrowserViewWranglerTest() {
fake_scene_ = FakeSceneWithIdentifier([[NSUUID UUID] UUIDString]);
scene_state_ = [[SceneStateWithFakeScene alloc] initWithScene:fake_scene_
appState:nil];
TestChromeBrowserState::Builder test_cbs_builder;
test_cbs_builder.AddTestingFactory(
SendTabToSelfSyncServiceFactory::GetInstance(),
SendTabToSelfSyncServiceFactory::GetDefaultFactory());
test_cbs_builder.AddTestingFactory(
ios::TemplateURLServiceFactory::GetInstance(),
ios::TemplateURLServiceFactory::GetDefaultFactory());
test_cbs_builder.AddTestingFactory(
IOSChromeFaviconLoaderFactory::GetInstance(),
IOSChromeFaviconLoaderFactory::GetDefaultFactory());
test_cbs_builder.AddTestingFactory(
IOSChromeLargeIconServiceFactory::GetInstance(),
IOSChromeLargeIconServiceFactory::GetDefaultFactory());
test_cbs_builder.AddTestingFactory(
ios::FaviconServiceFactory::GetInstance(),
ios::FaviconServiceFactory::GetDefaultFactory());
test_cbs_builder.AddTestingFactory(
ios::HistoryServiceFactory::GetInstance(),
ios::HistoryServiceFactory::GetDefaultFactory());
test_cbs_builder.AddTestingFactory(
PrerenderServiceFactory::GetInstance(),
PrerenderServiceFactory::GetDefaultFactory());
test_cbs_builder.AddTestingFactory(
ios::BookmarkModelFactory::GetInstance(),
ios::BookmarkModelFactory::GetDefaultFactory());
test_cbs_builder.AddTestingFactory(
AuthenticationServiceFactory::GetInstance(),
AuthenticationServiceFactory::GetDefaultFactory());
test_cbs_builder.AddTestingFactory(
SessionRestorationServiceFactory::GetInstance(),
TestSessionRestorationService::GetTestingFactory());
chrome_browser_state_ = std::move(test_cbs_builder).Build();
chrome_browser_state_->CreateOffTheRecordBrowserStateWithTestingFactories(
{TestChromeBrowserState::TestingFactory{
SessionRestorationServiceFactory::GetInstance(),
TestSessionRestorationService::GetTestingFactory(),
}});
AuthenticationServiceFactory::CreateAndInitializeForBrowserState(
chrome_browser_state_.get(),
std::make_unique<FakeAuthenticationServiceDelegate>());
scoped_session_restoration_observation_.AddObservation(
SessionRestorationServiceFactory::GetForBrowserState(
chrome_browser_state_.get()));
scoped_session_restoration_observation_.AddObservation(
SessionRestorationServiceFactory::GetForBrowserState(
chrome_browser_state_->GetOffTheRecordChromeBrowserState()));
scoped_browser_list_observation_.Observe(
BrowserListFactory::GetForBrowserState(chrome_browser_state_.get()));
}
void RecreateOffTheRecordChromeBrowserState() {
scoped_session_restoration_observation_.RemoveObservation(
SessionRestorationServiceFactory::GetForBrowserState(
chrome_browser_state_->GetOffTheRecordChromeBrowserState()));
chrome_browser_state_->DestroyOffTheRecordChromeBrowserState();
chrome_browser_state_->CreateOffTheRecordBrowserStateWithTestingFactories(
{TestChromeBrowserState::TestingFactory{
SessionRestorationServiceFactory::GetInstance(),
TestSessionRestorationService::GetTestingFactory(),
}});
scoped_session_restoration_observation_.AddObservation(
SessionRestorationServiceFactory::GetForBrowserState(
chrome_browser_state_->GetOffTheRecordChromeBrowserState()));
}
ChromeBrowserState* chrome_browser_state() {
return chrome_browser_state_.get();
}
SceneState* scene_state() { return scene_state_; }
TestSessionRestorationObserver& session_restoration_observer() {
return session_restoration_observer_;
}
TestBrowserListObserver& browser_list_observer() {
return browser_list_observer_;
}
private:
web::WebTaskEnvironment task_environment_;
IOSChromeScopedTestingLocalState scoped_testing_local_state_;
std::unique_ptr<TestChromeBrowserState> chrome_browser_state_;
id fake_scene_;
SceneState* scene_state_;
// SessionRestorationObserver and its scoped observation.
TestSessionRestorationObserver session_restoration_observer_;
base::ScopedMultiSourceObservation<SessionRestorationService,
SessionRestorationObserver>
scoped_session_restoration_observation_{&session_restoration_observer_};
// TestBrowserListObserver and its scoped observation.
TestBrowserListObserver browser_list_observer_;
base::ScopedObservation<BrowserList, BrowserListObserver>
scoped_browser_list_observation_{&browser_list_observer_};
};
TEST_F(BrowserViewWranglerTest, TestInitNilObserver) {
// `task_environment_` must outlive all objects created by BVC, because those
// objects may rely on threading API in dealloc.
@autoreleasepool {
BrowserViewWrangler* wrangler =
[[BrowserViewWrangler alloc] initWithBrowserState:chrome_browser_state()
sceneState:scene_state()
applicationEndpoint:nil
settingsEndpoint:nil];
[wrangler createMainCoordinatorAndInterface];
// Test that BVC is created on demand.
UIViewController* bvc = wrangler.mainInterface.viewController;
EXPECT_NE(bvc, nil);
// Test that SceneState is associated with the browser.
SceneState* main_browser_scene_state =
wrangler.mainInterface.browser->GetSceneState();
EXPECT_EQ(scene_state(), main_browser_scene_state);
// Test that once created the BVC isn't re-created.
EXPECT_EQ(bvc, wrangler.mainInterface.viewController);
// Test that the OTR objects are (a) OTR and (b) not the same as the non-OTR
// objects.
EXPECT_NE(bvc, wrangler.incognitoInterface.viewController);
EXPECT_TRUE(wrangler.incognitoInterface.browserState->IsOffTheRecord());
// Test that the OTR browser has SceneState associated with it.
SceneState* otr_browser_scene_state =
wrangler.incognitoInterface.browser->GetSceneState();
EXPECT_EQ(scene_state(), otr_browser_scene_state);
[wrangler shutdown];
}
}
TEST_F(BrowserViewWranglerTest, TestBrowserList) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures(
{/* Enabled features */},
{/* Disabled features */ kTabInactivityThreshold});
BrowserViewWrangler* wrangler =
[[BrowserViewWrangler alloc] initWithBrowserState:chrome_browser_state()
sceneState:scene_state()
applicationEndpoint:nil
settingsEndpoint:nil];
BrowserList* browser_list =
BrowserListFactory::GetForBrowserState(chrome_browser_state());
// Create the coordinator and interface. This is required to get access
// to the Browser via the -mainInterface/-incognitoInterface providers.
[wrangler createMainCoordinatorAndInterface];
// The BrowserViewWrangler creates all browser in its initializer. The
// first created CL is the main Browser, the second one the inactive
// Browser, and then the OTR Browser.
EXPECT_EQ(2UL,
browser_list
->BrowsersOfType(BrowserList::BrowserType::kRegularAndInactive)
.size());
EXPECT_EQ(1UL,
browser_list->BrowsersOfType(BrowserList::BrowserType::kIncognito)
.size());
EXPECT_EQ(wrangler.mainInterface.inactiveBrowser,
browser_list_observer().GetLastAddedBrowser());
EXPECT_EQ(wrangler.incognitoInterface.browser,
browser_list_observer().GetLastAddedIncognitoBrowser());
// Record the old Browser before it is destroyed. This will be dangling
// after the call to -willDestroyIncognitoBrowserState.
Browser* prior_otr_browser = wrangler.incognitoInterface.browser;
[wrangler willDestroyIncognitoBrowserState];
RecreateOffTheRecordChromeBrowserState();
[wrangler incognitoBrowserStateCreated];
// Expect that the prior OTR browser was removed, and a new one was added.
EXPECT_EQ(prior_otr_browser,
browser_list_observer().GetLastRemovedIncognitoBrowser());
EXPECT_EQ(wrangler.incognitoInterface.browser,
browser_list_observer().GetLastAddedIncognitoBrowser());
// There still should be one OTR browser.
EXPECT_EQ(1UL,
browser_list->BrowsersOfType(BrowserList::BrowserType::kIncognito)
.size());
// Store unsafe pointers to the current browsers.
Browser* pre_shutdown_main_browser = wrangler.mainInterface.browser;
Browser* pre_shutdown_incognito_browser = wrangler.incognitoInterface.browser;
// After shutdown all browsers are destroyed.
[wrangler shutdown];
// There should be no browsers in the BrowserList.
EXPECT_EQ(0UL,
browser_list
->BrowsersOfType(BrowserList::BrowserType::kRegularAndInactive)
.size());
EXPECT_EQ(0UL,
browser_list->BrowsersOfType(BrowserList::BrowserType::kIncognito)
.size());
// Both browser removals should have been observed.
EXPECT_EQ(pre_shutdown_main_browser,
browser_list_observer().GetLastRemovedBrowser());
EXPECT_EQ(pre_shutdown_incognito_browser,
browser_list_observer().GetLastRemovedIncognitoBrowser());
}
TEST_F(BrowserViewWranglerTest, TestInactiveInterface) {
// No inactive tabs on iPad.
if (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET) {
return;
}
// Enabled inactive tabs feature.
base::test::ScopedFeatureList feature_list;
std::map<std::string, std::string> parameters;
parameters[kTabInactivityThresholdParameterName] =
kTabInactivityThresholdOneWeekParam;
feature_list.InitAndEnableFeatureWithParameters(kTabInactivityThreshold,
parameters);
BrowserViewWrangler* wrangler =
[[BrowserViewWrangler alloc] initWithBrowserState:chrome_browser_state()
sceneState:scene_state()
applicationEndpoint:nil
settingsEndpoint:nil];
BrowserList* browser_list =
BrowserListFactory::GetForBrowserState(chrome_browser_state());
[wrangler createMainCoordinatorAndInterface];
EXPECT_EQ(2UL,
browser_list
->BrowsersOfType(BrowserList::BrowserType::kRegularAndInactive)
.size());
EXPECT_EQ(wrangler.mainInterface.inactiveBrowser,
browser_list_observer().GetLastAddedBrowser());
// After shutdown all browsers are destroyed.
[wrangler shutdown];
EXPECT_EQ(0UL,
browser_list
->BrowsersOfType(BrowserList::BrowserType::kRegularAndInactive)
.size());
}
// Tests the session restoration logic.
TEST_F(BrowserViewWranglerTest, TestSessionRestorationLogic) {
BrowserViewWrangler* wrangler =
[[BrowserViewWrangler alloc] initWithBrowserState:chrome_browser_state()
sceneState:scene_state()
applicationEndpoint:nil
settingsEndpoint:nil];
// Create the coordinator and interface. This is required to get access
// to the Browser via the -mainInterface/-incognitoInterface providers.
[wrangler createMainCoordinatorAndInterface];
EXPECT_EQ(0, session_restoration_observer().session_restoration_call_count());
// Load the session for all Browser. There should be one for the main
// Browser, one for the inactive Browser and one for the OTR Browser.
[wrangler loadSession];
EXPECT_EQ(3, session_restoration_observer().session_restoration_call_count());
// Destroing and rebuilding the incognito browser should not restore the
// sessions.
[wrangler willDestroyIncognitoBrowserState];
RecreateOffTheRecordChromeBrowserState();
[wrangler incognitoBrowserStateCreated];
EXPECT_EQ(3, session_restoration_observer().session_restoration_call_count());
[wrangler shutdown];
}