// Copyright 2023 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/tab_switcher/tab_grid/grid/incognito/incognito_grid_mediator.h"
#import "base/feature_list.h"
#import "base/metrics/histogram_functions.h"
#import "base/metrics/user_metrics.h"
#import "base/metrics/user_metrics_action.h"
#import "components/prefs/ios/pref_observer_bridge.h"
#import "components/prefs/pref_change_registrar.h"
#import "components/signin/public/identity_manager/identity_manager.h"
#import "components/signin/public/identity_manager/tribool.h"
#import "components/supervised_user/core/browser/supervised_user_capabilities.h"
#import "components/supervised_user/core/browser/supervised_user_preferences.h"
#import "components/supervised_user/core/common/features.h"
#import "components/supervised_user/core/common/pref_names.h"
#import "ios/chrome/browser/incognito_reauth/ui_bundled/incognito_reauth_scene_agent.h"
#import "ios/chrome/browser/policy/model/policy_util.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/shared/public/commands/tab_groups_commands.h"
#import "ios/chrome/browser/snapshots/model/snapshot_browser_agent.h"
#import "ios/chrome/browser/supervised_user/model/supervised_user_capabilities_observer_bridge.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/base_grid_mediator.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_consumer.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_toolbars_mutator.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/incognito/incognito_grid_mediator_delegate.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_idle_status_handler.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_metrics.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_mode_holder.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_paging.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/tab_grid_toolbars_configuration.h"
#import "ios/web/public/web_state_id.h"
// TODO(crbug.com/40273478): Needed for `TabPresentationDelegate`, should be
// refactored.
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.h"
@interface IncognitoGridMediator () <IncognitoReauthObserver,
PrefObserverDelegate,
SupervisedUserCapabilitiesObserving>
@end
@implementation IncognitoGridMediator {
// Pref observer to track changes to prefs.
std::unique_ptr<PrefObserverBridge> _prefObserverBridge;
// Registrar for pref changes notifications.
std::unique_ptr<PrefChangeRegistrar> _prefChangeRegistrar;
// YES if incognito is disabled.
BOOL _incognitoDisabled;
// Whether this grid is currently selected.
BOOL _selected;
// Identity manager providing AccountInfo capabilities to determine
// supervision status. This identity manager is not available for
// the incognito browser state and need to be passed in.
raw_ptr<signin::IdentityManager> _identityManager;
// Observer to track changes to supervision-related capabilities.
std::unique_ptr<supervised_user::SupervisedUserCapabilitiesObserverBridge>
_supervisedUserCapabilitiesObserver;
}
// TODO(crbug.com/40273478): Refactor the grid commands to have the same
// function name to close all.
#pragma mark - GridCommands
- (void)closeItemWithID:(web::WebStateID)itemID {
// Record when an incognito tab is closed.
base::RecordAction(base::UserMetricsAction("MobileTabGridCloseIncognitoTab"));
[super closeItemWithID:itemID];
}
- (void)closeAllItems {
RecordTabGridCloseTabsCount(self.webStateList->count());
base::RecordAction(
base::UserMetricsAction("MobileTabGridCloseAllIncognitoTabs"));
// This is a no-op if `webStateList` is already empty.
CloseAllWebStates(*self.webStateList, WebStateList::CLOSE_USER_ACTION);
SnapshotBrowserAgent::FromBrowser(self.browser)->RemoveAllSnapshots();
}
- (void)saveAndCloseAllItems {
NOTREACHED() << "Incognito tabs should not be saved before closing.";
}
- (void)undoCloseAllItems {
NOTREACHED() << "Incognito tabs are not saved before closing.";
}
- (void)discardSavedClosedItems {
NOTREACHED() << "Incognito tabs cannot be saved.";
}
- (void)setPinState:(BOOL)pinState forItemWithID:(web::WebStateID)itemID {
NOTREACHED() << "Should not be called in incognito.";
}
#pragma mark - TabGridPageMutator
- (void)currentlySelectedGrid:(BOOL)selected {
_selected = selected;
if (selected) {
base::RecordAction(
base::UserMetricsAction("MobileTabGridSelectIncognitoPanel"));
[self configureToolbarsButtons];
}
}
- (void)setPageAsActive {
[self.gridConsumer setActivePageFromPage:TabGridPageIncognitoTabs];
}
#pragma mark - TabGridToolbarsGridDelegate
- (void)closeAllButtonTapped:(id)sender {
[self closeAllItems];
}
- (void)newTabButtonTapped:(id)sender {
// Ignore the tap if the current page is disabled for some reason, by policy
// for instance. This is to avoid situations where the tap action from an
// enabled page can make it to a disabled page by releasing the
// button press after switching to the disabled page (b/273416844 is an
// example).
if (_incognitoDisabled) {
return;
}
[self.tabGridIdleStatusHandler
tabGridDidPerformAction:TabGridActionType::kInPageAction];
base::RecordAction(base::UserMetricsAction("MobileTabNewTab"));
[self.gridConsumer prepareForDismissal];
// Present the tab only if it have been added.
if ([self addNewItem]) {
[self displayActiveTab];
base::RecordAction(
base::UserMetricsAction("MobileTabGridCreateIncognitoTab"));
} else {
base::RecordAction(
base::UserMetricsAction("MobileTabGridFailedCreateIncognitoTab"));
}
}
#pragma mark - Parent's function
- (void)disconnect {
_prefChangeRegistrar.reset();
_prefObserverBridge.reset();
_supervisedUserCapabilitiesObserver.reset();
_identityManager = nil;
[_reauthSceneAgent removeObserver:self];
[super disconnect];
}
- (void)configureToolbarsButtons {
if (!_selected) {
return;
}
// Start to configure the delegate, so configured buttons will depend on the
// correct delegate.
[self.toolbarsMutator setToolbarsButtonsDelegate:self];
BOOL authenticationRequired = self.reauthSceneAgent.authenticationRequired;
if (_incognitoDisabled || authenticationRequired) {
[self.toolbarsMutator
setToolbarConfiguration:
[TabGridToolbarsConfiguration
disabledConfigurationForPage:TabGridPageIncognitoTabs]];
return;
}
TabGridToolbarsConfiguration* toolbarsConfiguration =
[[TabGridToolbarsConfiguration alloc]
initWithPage:TabGridPageIncognitoTabs];
if (self.modeHolder.mode == TabGridMode::kSelection) {
[self configureButtonsInSelectionMode:toolbarsConfiguration];
} else {
toolbarsConfiguration.closeAllButton = !self.webStateList->empty();
toolbarsConfiguration.doneButton = !self.webStateList->empty();
toolbarsConfiguration.newTabButton = YES;
toolbarsConfiguration.searchButton = YES;
toolbarsConfiguration.selectTabsButton = !self.webStateList->empty();
}
[self.toolbarsMutator setToolbarConfiguration:toolbarsConfiguration];
}
- (void)displayActiveTab {
[self.gridConsumer setActivePageFromPage:TabGridPageIncognitoTabs];
[self.tabPresentationDelegate showActiveTabInPage:TabGridPageIncognitoTabs
focusOmnibox:NO];
}
#pragma mark - PrefObserverDelegate
- (void)onPreferenceChanged:(const std::string&)preferenceName {
if (!base::FeatureList::IsEnabled(
supervised_user::
kReplaceSupervisionPrefsWithAccountCapabilitiesOnIOS) &&
preferenceName == prefs::kSupervisedUserId) {
BOOL isDisabled = [self isIncognitoModeDisabled];
if (_incognitoDisabled != isDisabled) {
_incognitoDisabled = isDisabled;
[self.incognitoDelegate shouldDisableIncognito:_incognitoDisabled];
}
[self configureToolbarsButtons];
}
}
#pragma mark - Properties
- (void)setBrowser:(Browser*)browser {
_prefChangeRegistrar.reset();
_prefObserverBridge.reset();
[super setBrowser:browser];
if (browser) {
PrefService* prefService = browser->GetBrowserState()->GetPrefs();
DCHECK(prefService);
if (!base::FeatureList::IsEnabled(
supervised_user::
kReplaceSupervisionPrefsWithAccountCapabilitiesOnIOS)) {
_prefChangeRegistrar = std::make_unique<PrefChangeRegistrar>();
_prefChangeRegistrar->Init(prefService);
// Register to observe any changes on supervised_user status.
_prefObserverBridge = std::make_unique<PrefObserverBridge>(self);
_prefObserverBridge->ObserveChangesForPreference(
prefs::kSupervisedUserId, _prefChangeRegistrar.get());
}
_incognitoDisabled = [self isIncognitoModeDisabled];
}
}
- (PrefService*)prefService {
Browser* browser = self.browser;
DCHECK(browser);
return browser->GetBrowserState()->GetPrefs();
}
- (void)setReauthSceneAgent:(IncognitoReauthSceneAgent*)reauthSceneAgent {
if (_reauthSceneAgent == reauthSceneAgent) {
return;
}
[_reauthSceneAgent removeObserver:self];
_reauthSceneAgent = reauthSceneAgent;
[_reauthSceneAgent addObserver:self];
}
#pragma mark - IncognitoReauthObserver
- (void)reauthAgent:(IncognitoReauthSceneAgent*)agent
didUpdateAuthenticationRequirement:(BOOL)isRequired {
if (isRequired) {
[self.tabGroupsHandler hideTabGroup];
}
if (_selected) {
if (isRequired) {
self.modeHolder.mode = TabGridMode::kNormal;
}
[self configureToolbarsButtons];
}
}
#pragma mark - SupervisedUserCapabilitiesObserving
- (void)onIsSubjectToParentalControlsCapabilityChanged:
(supervised_user::CapabilityUpdateState)capabilityUpdateState {
if (base::FeatureList::IsEnabled(
supervised_user::
kReplaceSupervisionPrefsWithAccountCapabilitiesOnIOS)) {
BOOL isDisabled = [self isIncognitoModeDisabled];
if (_incognitoDisabled != isDisabled) {
_incognitoDisabled = isDisabled;
[self.incognitoDelegate shouldDisableIncognito:_incognitoDisabled];
}
[self configureToolbarsButtons];
}
}
#pragma mark - Public
- (void)initializeSupervisedUserCapabilitiesObserver:
(signin::IdentityManager*)identityManager {
if (base::FeatureList::IsEnabled(
supervised_user::
kReplaceSupervisionPrefsWithAccountCapabilitiesOnIOS)) {
DCHECK(identityManager);
_identityManager = identityManager;
_supervisedUserCapabilitiesObserver = std::make_unique<
supervised_user::SupervisedUserCapabilitiesObserverBridge>(
_identityManager, self);
_incognitoDisabled = [self isIncognitoModeDisabled];
}
}
#pragma mark - Private
// Returns YES if incognito is disabled.
- (BOOL)isIncognitoModeDisabled {
DCHECK(self.browser);
PrefService* prefService = self.browser->GetBrowserState()->GetPrefs();
if (IsIncognitoModeDisabled(prefService)) {
return YES;
}
// Incognito mode is disabled for supervised users.
if (base::FeatureList::IsEnabled(
supervised_user::
kReplaceSupervisionPrefsWithAccountCapabilitiesOnIOS)) {
return _identityManager &&
supervised_user::IsPrimaryAccountSubjectToParentalControls(
_identityManager) == signin::Tribool::kTrue;
} else {
return supervised_user::IsSubjectToParentalControls(*prefService);
}
}
@end