// Copyright 2019 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/coordinator/scene/scene_controller.h"
#import <MaterialComponents/MaterialSnackbar.h>
#import "base/feature_list.h"
#import "base/functional/callback_helpers.h"
#import "base/i18n/message_formatter.h"
#import "base/ios/ios_util.h"
#import "base/logging.h"
#import "base/metrics/histogram_functions.h"
#import "base/metrics/histogram_macros.h"
#import "base/metrics/user_metrics.h"
#import "base/metrics/user_metrics_action.h"
#import "base/strings/sys_string_conversions.h"
#import "base/time/time.h"
#import "components/autofill/core/browser/data_model/credit_card.h"
#import "components/breadcrumbs/core/breadcrumbs_status.h"
#import "components/feature_engagement/public/event_constants.h"
#import "components/feature_engagement/public/tracker.h"
#import "components/infobars/core/infobar_manager.h"
#import "components/password_manager/core/browser/ui/credential_ui_entry.h"
#import "components/password_manager/core/browser/ui/password_check_referrer.h"
#import "components/policy/core/common/cloud/user_cloud_policy_manager.h"
#import "components/prefs/pref_service.h"
#import "components/previous_session_info/previous_session_info.h"
#import "components/signin/public/base/signin_metrics.h"
#import "components/signin/public/base/signin_pref_names.h"
#import "components/signin/public/identity_manager/identity_manager.h"
#import "components/supervised_user/core/browser/kids_management_api_fetcher.h"
#import "components/supervised_user/core/browser/proto/kidsmanagement_messages.pb.h"
#import "components/supervised_user/core/browser/proto_fetcher_status.h"
#import "components/supervised_user/core/browser/supervised_user_utils.h"
#import "components/url_formatter/url_formatter.h"
#import "components/version_info/version_info.h"
#import "components/web_resource/web_resource_pref_names.h"
#import "ios/chrome/app/application_delegate/app_state.h"
#import "ios/chrome/app/application_delegate/startup_information.h"
#import "ios/chrome/app/application_delegate/url_opener.h"
#import "ios/chrome/app/application_delegate/url_opener_params.h"
#import "ios/chrome/app/application_mode.h"
#import "ios/chrome/app/chrome_overlay_window.h"
#import "ios/chrome/app/deferred_initialization_runner.h"
#import "ios/chrome/app/profile/profile_state.h"
#import "ios/chrome/app/tests_hook.h"
#import "ios/chrome/browser/app_store_rating/ui_bundled/app_store_rating_scene_agent.h"
#import "ios/chrome/browser/app_store_rating/ui_bundled/features.h"
#import "ios/chrome/browser/appearance/ui_bundled/appearance_customization.h"
#import "ios/chrome/browser/browser_view/ui_bundled/browser_view_controller.h"
#import "ios/chrome/browser/browsing_data/model/browsing_data_remove_mask.h"
#import "ios/chrome/browser/browsing_data/model/browsing_data_remover.h"
#import "ios/chrome/browser/browsing_data/model/browsing_data_remover_factory.h"
#import "ios/chrome/browser/crash_report/model/breadcrumbs/breadcrumb_manager_browser_agent.h"
#import "ios/chrome/browser/crash_report/model/crash_keys_helper.h"
#import "ios/chrome/browser/crash_report/model/crash_loop_detection_util.h"
#import "ios/chrome/browser/crash_report/model/crash_report_helper.h"
#import "ios/chrome/browser/credential_provider_promo/ui_bundled/credential_provider_promo_scene_agent.h"
#import "ios/chrome/browser/default_browser/model/default_browser_interest_signals.h"
#import "ios/chrome/browser/default_browser/model/promo_source.h"
#import "ios/chrome/browser/default_browser/model/utils.h"
#import "ios/chrome/browser/enterprise/model/idle/idle_service.h"
#import "ios/chrome/browser/enterprise/model/idle/idle_service_factory.h"
#import "ios/chrome/browser/feature_engagement/model/tracker_factory.h"
#import "ios/chrome/browser/first_run/model/first_run.h"
#import "ios/chrome/browser/geolocation/model/geolocation_manager.h"
#import "ios/chrome/browser/history/ui_bundled/history_coordinator.h"
#import "ios/chrome/browser/history/ui_bundled/history_coordinator_delegate.h"
#import "ios/chrome/browser/incognito_interstitial/ui_bundled/incognito_interstitial_coordinator.h"
#import "ios/chrome/browser/incognito_interstitial/ui_bundled/incognito_interstitial_coordinator_delegate.h"
#import "ios/chrome/browser/incognito_reauth/ui_bundled/incognito_reauth_scene_agent.h"
#import "ios/chrome/browser/infobars/model/infobar_manager_impl.h"
#import "ios/chrome/browser/intents/user_activity_browser_agent.h"
#import "ios/chrome/browser/mailto_handler/model/mailto_handler_service.h"
#import "ios/chrome/browser/mailto_handler/model/mailto_handler_service_factory.h"
#import "ios/chrome/browser/metrics/model/tab_usage_recorder_browser_agent.h"
#import "ios/chrome/browser/ntp/model/new_tab_page_tab_helper.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_feature.h"
#import "ios/chrome/browser/passwords/model/ios_chrome_password_check_manager.h"
#import "ios/chrome/browser/passwords/model/ios_chrome_password_check_manager_factory.h"
#import "ios/chrome/browser/passwords/model/password_checkup_utils.h"
#import "ios/chrome/browser/policy/model/cloud/user_policy_signin_service_factory.h"
#import "ios/chrome/browser/policy/model/policy_util.h"
#import "ios/chrome/browser/policy/model/policy_watcher_browser_agent.h"
#import "ios/chrome/browser/policy/model/policy_watcher_browser_agent_observer_bridge.h"
#import "ios/chrome/browser/policy/ui_bundled/idle/idle_timeout_policy_scene_agent.h"
#import "ios/chrome/browser/policy/ui_bundled/signin_policy_scene_agent.h"
#import "ios/chrome/browser/policy/ui_bundled/user_policy_scene_agent.h"
#import "ios/chrome/browser/policy/ui_bundled/user_policy_util.h"
#import "ios/chrome/browser/promos_manager/model/features.h"
#import "ios/chrome/browser/promos_manager/model/promos_manager_factory.h"
#import "ios/chrome/browser/reading_list/model/reading_list_browser_agent.h"
#import "ios/chrome/browser/screenshot/model/screenshot_delegate.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/session_saving_scene_agent.h"
#import "ios/chrome/browser/shared/coordinator/default_browser_promo/non_modal_default_browser_promo_scheduler_scene_agent.h"
#import "ios/chrome/browser/shared/coordinator/layout_guide/layout_guide_scene_agent.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_ui_provider.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.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/browser_provider_interface.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/profile/profile_attributes_storage_ios.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/profile/profile_manager_ios.h"
#import "ios/chrome/browser/shared/model/url/chrome_url_constants.h"
#import "ios/chrome/browser/shared/model/url/url_util.h"
#import "ios/chrome/browser/shared/model/web_state_list/browser_util.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_list_observer_bridge.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_opener.h"
#import "ios/chrome/browser/shared/public/commands/application_commands.h"
#import "ios/chrome/browser/shared/public/commands/bookmarks_commands.h"
#import "ios/chrome/browser/shared/public/commands/browser_commands.h"
#import "ios/chrome/browser/shared/public/commands/browser_coordinator_commands.h"
#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
#import "ios/chrome/browser/shared/public/commands/help_commands.h"
#import "ios/chrome/browser/shared/public/commands/lens_commands.h"
#import "ios/chrome/browser/shared/public/commands/omnibox_commands.h"
#import "ios/chrome/browser/shared/public/commands/open_lens_input_selection_command.h"
#import "ios/chrome/browser/shared/public/commands/open_new_tab_command.h"
#import "ios/chrome/browser/shared/public/commands/policy_change_commands.h"
#import "ios/chrome/browser/shared/public/commands/qr_scanner_commands.h"
#import "ios/chrome/browser/shared/public/commands/show_signin_command.h"
#import "ios/chrome/browser/shared/public/commands/snackbar_commands.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/util/snackbar_util.h"
#import "ios/chrome/browser/shared/ui/util/top_view_controller.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/signin/model/authentication_service.h"
#import "ios/chrome/browser/signin/model/authentication_service_factory.h"
#import "ios/chrome/browser/signin/model/capabilities_types.h"
#import "ios/chrome/browser/signin/model/chrome_account_manager_service.h"
#import "ios/chrome/browser/signin/model/chrome_account_manager_service_factory.h"
#import "ios/chrome/browser/signin/model/constants.h"
#import "ios/chrome/browser/signin/model/identity_manager_factory.h"
#import "ios/chrome/browser/signin/model/system_identity_manager.h"
#import "ios/chrome/browser/snapshots/model/snapshot_tab_helper.h"
#import "ios/chrome/browser/start_surface/ui_bundled/start_surface_features.h"
#import "ios/chrome/browser/start_surface/ui_bundled/start_surface_recent_tab_browser_agent.h"
#import "ios/chrome/browser/start_surface/ui_bundled/start_surface_scene_agent.h"
#import "ios/chrome/browser/start_surface/ui_bundled/start_surface_util.h"
#import "ios/chrome/browser/tab_insertion/model/tab_insertion_browser_agent.h"
#import "ios/chrome/browser/ui/authentication/signin/signin_coordinator.h"
#import "ios/chrome/browser/ui/authentication/signin/signin_utils.h"
#import "ios/chrome/browser/ui/authentication/signin_notification_infobar_delegate.h"
#import "ios/chrome/browser/ui/lens/lens_entrypoint.h"
#import "ios/chrome/browser/ui/main/browser_view_wrangler.h"
#import "ios/chrome/browser/ui/main/default_browser_promo_scene_agent.h"
#import "ios/chrome/browser/ui/main/incognito_blocker_scene_agent.h"
#import "ios/chrome/browser/ui/main/ui_blocker_scene_agent.h"
#import "ios/chrome/browser/ui/main/wrangled_browser.h"
#import "ios/chrome/browser/ui/promos_manager/promos_manager_scene_agent.h"
#import "ios/chrome/browser/ui/promos_manager/utils.h"
#import "ios/chrome/browser/ui/scoped_ui_blocker/scoped_ui_blocker.h"
#import "ios/chrome/browser/ui/settings/clear_browsing_data/features.h"
#import "ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_coordinator.h"
#import "ios/chrome/browser/ui/settings/password/passwords_coordinator.h"
#import "ios/chrome/browser/ui/settings/password/passwords_mediator.h"
#import "ios/chrome/browser/ui/settings/settings_navigation_controller.h"
#import "ios/chrome/browser/ui/settings/utils/password_utils.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator_delegate.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_utils.h"
#import "ios/chrome/browser/ui/whats_new/promo/whats_new_scene_agent.h"
#import "ios/chrome/browser/url_loading/model/scene_url_loading_service.h"
#import "ios/chrome/browser/url_loading/model/url_loading_browser_agent.h"
#import "ios/chrome/browser/url_loading/model/url_loading_params.h"
#import "ios/chrome/browser/web/model/page_placeholder_browser_agent.h"
#import "ios/chrome/browser/web_state_list/model/session_metrics.h"
#import "ios/chrome/browser/web_state_list/model/web_usage_enabler/web_usage_enabler_browser_agent.h"
#import "ios/chrome/browser/window_activities/model/window_activity_helpers.h"
#import "ios/chrome/common/ui/reauthentication/reauthentication_module.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/public/provider/chrome/browser/signin/choice_api.h"
#import "ios/public/provider/chrome/browser/ui_utils/ui_utils_api.h"
#import "ios/public/provider/chrome/browser/user_feedback/user_feedback_api.h"
#import "ios/public/provider/chrome/browser/user_feedback/user_feedback_data.h"
#import "ios/web/public/navigation/navigation_item.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/navigation/navigation_util.h"
#import "ios/web/public/session/proto/storage.pb.h"
#import "ios/web/public/thread/web_task_traits.h"
#import "ios/web/public/thread/web_thread.h"
#import "ios/web/public/web_state.h"
#import "net/base/apple/url_conversions.h"
#import "services/network/public/cpp/shared_url_loader_factory.h"
#import "ui/base/l10n/l10n_util.h"
namespace {
// Killswitch, can be removed around February 2024. If enabled,
// createInitialUI will call makeKeyAndVisible before mainCoordinator start.
// When disabled, this fix resolves a flicker when starting the app in light
// mode
BASE_FEATURE(kMakeKeyAndVisibleBeforeMainCoordinatorStart,
"MakeKeyAndVisibleBeforeMainCoordinatorStart",
base::FEATURE_DISABLED_BY_DEFAULT);
// Feature to control whether Search Intents (Widgets, Application
// Shortcuts menu) forcibly open a new tab, rather than reusing an
// existing NTP. See http://crbug.com/1363375 for details.
BASE_FEATURE(kForceNewTabForIntentSearch,
"ForceNewTabForIntentSearch",
base::FEATURE_DISABLED_BY_DEFAULT);
// A rough estimate of the expected duration of a view controller transition
// animation. It's used to temporarily disable mutally exclusive chrome
// commands that trigger a view controller presentation.
const int64_t kExpectedTransitionDurationInNanoSeconds = 0.2 * NSEC_PER_SEC;
// Used to update the current BVC mode if a new tab is added while the tab
// switcher view is being dismissed. This is different than ApplicationMode in
// that it can be set to `NONE` when not in use.
enum class TabSwitcherDismissalMode { NONE, NORMAL, INCOGNITO };
// Key of the UMA IOS.MultiWindow.OpenInNewWindow histogram.
const char kMultiWindowOpenInNewWindowHistogram[] =
"IOS.MultiWindow.OpenInNewWindow";
// TODO(crbug.com/40788009): Use the Authentication Service sign-in status API
// instead of this when available.
bool IsSigninForcedByPolicy() {
BrowserSigninMode policy_mode = static_cast<BrowserSigninMode>(
GetApplicationContext()->GetLocalState()->GetInteger(
prefs::kBrowserSigninPolicy));
return policy_mode == BrowserSigninMode::kForced;
}
#if !BUILDFLAG(GOOGLE_CHROME_BRANDING)
NSString* const kAddLotsOfTabs = @"AddLotsOfTabs";
void InjectUnrealizedWebStates(Browser* browser, int count) {
WebStateList* web_state_list = browser->GetWebStateList();
SessionRestorationService* service =
SessionRestorationServiceFactory::GetForBrowserState(
browser->GetBrowserState());
auto scoped_lock = web_state_list->StartBatchOperation();
for (int i = 0; i < count; ++i) {
std::string string_url = base::StringPrintf("http://google.com/%d", i);
// Create the serialized representation of a WebState
// with one navigation to `string_url` (defaulting the
// title to the URL).
web::proto::WebStateStorage storage = web::CreateWebStateStorage(
web::NavigationManager::WebLoadParams(GURL(string_url)),
base::UTF8ToUTF16(string_url.c_str()),
/*created_with_opener=*/false, web::UserAgentType::MOBILE,
base::Time::Now());
// Ask the SessionService to create an unrealized WebState
// and to prepare itself for it to be added to `browser`.
std::unique_ptr<web::WebState> web_state =
service->CreateUnrealizedWebState(browser, std::move(storage));
// Insert the new unrealized WebState in `browser`.
// Need to activate one WebState otherwise the session
// will not be saved with the legacy session storage.
int index = browser->GetWebStateList()->count();
web_state_list->InsertWebState(
std::move(web_state),
WebStateList::InsertionParams::Automatic().Activate(
index == 0 && !web_state_list->GetActiveWebState()));
}
}
#endif // !BUILDFLAG(GOOGLE_CHROME_BRANDING)
void InjectNTP(Browser* browser) {
// Don't inject an NTP for an empty web state list.
if (!browser->GetWebStateList()->count()) {
return;
}
// Don't inject an NTP on an NTP.
web::WebState* webState = browser->GetWebStateList()->GetActiveWebState();
if (IsUrlNtp(webState->GetVisibleURL())) {
return;
}
// Queue up start surface with active tab.
StartSurfaceRecentTabBrowserAgent* browser_agent =
StartSurfaceRecentTabBrowserAgent::FromBrowser(browser);
// This may be nil for an incognito browser.
if (browser_agent) {
browser_agent->SaveMostRecentTab();
}
// Inject a live NTP.
web::WebState::CreateParams create_params(browser->GetBrowserState());
std::unique_ptr<web::WebState> web_state =
web::WebState::Create(create_params);
std::vector<std::unique_ptr<web::NavigationItem>> items;
std::unique_ptr<web::NavigationItem> item(web::NavigationItem::Create());
item->SetURL(GURL(kChromeUINewTabURL));
items.push_back(std::move(item));
web_state->GetNavigationManager()->Restore(0, std::move(items));
if (!browser->GetBrowserState()->IsOffTheRecord()) {
NewTabPageTabHelper::CreateForWebState(web_state.get());
NewTabPageTabHelper::FromWebState(web_state.get())
->SetShowStartSurface(true);
}
browser->GetWebStateList()->InsertWebState(
std::move(web_state),
WebStateList::InsertionParams::Automatic().Activate());
}
// Updates `data` with the Family Link member role associated to the primary
// signed-in account, no-op if the account is not enrolled in Family Link.
void OnListFamilyMembersResponse(
const std::string& primary_account_gaia,
UserFeedbackData* data,
const supervised_user::ProtoFetcherStatus& status,
std::unique_ptr<kidsmanagement::ListMembersResponse> response) {
if (!status.IsOk()) {
return;
}
for (const kidsmanagement::FamilyMember& member : response->members()) {
if (member.user_id() == primary_account_gaia) {
data.familyMemberRole = base::SysUTF8ToNSString(
supervised_user::FamilyRoleToString(member.role()));
break;
}
}
}
} // namespace
@interface SceneController () <AppStateObserver,
HistoryCoordinatorDelegate,
IncognitoInterstitialCoordinatorDelegate,
PasswordCheckupCoordinatorDelegate,
PolicyWatcherBrowserAgentObserving,
SettingsNavigationControllerDelegate,
SceneUIProvider,
SceneURLLoadingServiceDelegate,
TabGridCoordinatorDelegate,
WebStateListObserving> {
std::unique_ptr<WebStateListObserverBridge> _webStateListForwardingObserver;
std::unique_ptr<PolicyWatcherBrowserAgentObserverBridge>
_policyWatcherObserverBridge;
std::unique_ptr<
base::ScopedObservation<WebStateList, WebStateListObserverBridge>>
_incognitoWebStateObserver;
std::unique_ptr<
base::ScopedObservation<WebStateList, WebStateListObserverBridge>>
_mainWebStateObserver;
std::unique_ptr<
base::ScopedObservation<PolicyWatcherBrowserAgent,
PolicyWatcherBrowserAgentObserverBridge>>
_policyWatcherObserver;
// The scene level component for url loading. Is passed down to
// browser state level UrlLoadingService instances.
std::unique_ptr<SceneUrlLoadingService> _sceneURLLoadingService;
// Map recording the number of tabs in WebStateList before the batch
// operation started.
std::map<WebStateList*, int> _tabCountBeforeBatchOperation;
// Fetches the Family Link member role asynchronously from KidsManagement API.
std::unique_ptr<supervised_user::ListFamilyMembersFetcher>
_family_members_fetcher;
}
// Navigation View controller for the settings.
@property(nonatomic, strong)
SettingsNavigationController* settingsNavigationController;
// Coordinator for display the Password Checkup.
@property(nonatomic, strong)
PasswordCheckupCoordinator* passwordCheckupCoordinator;
// Coordinator for displaying history.
@property(nonatomic, strong) HistoryCoordinator* historyCoordinator;
// Coordinates the creation of PDF screenshots with the window's content.
@property(nonatomic, strong) ScreenshotDelegate* screenshotDelegate;
// The tab switcher command and the voice search commands can be sent by views
// that reside in a different UIWindow leading to the fact that the exclusive
// touch property will be ineffective and a command for processing both
// commands may be sent in the same run of the runloop leading to
// inconsistencies. Those two boolean indicate if one of those commands have
// been processed in the last 200ms in order to only allow processing one at
// a time.
// TODO(crbug.com/40445992): Provide a general solution for handling mutually
// exclusive chrome commands sent at nearly the same time.
@property(nonatomic, assign) BOOL isProcessingTabSwitcherCommand;
@property(nonatomic, assign) BOOL isProcessingVoiceSearchCommand;
// If not NONE, the current BVC should be switched to this BVC on completion
// of tab switcher dismissal.
@property(nonatomic, assign)
TabSwitcherDismissalMode modeToDisplayOnTabSwitcherDismissal;
// A property to track which action to perform after dismissing the tab
// switcher. This is used to ensure certain post open actions that are
// presented by the BVC to only be triggered when the BVC is active.
@property(nonatomic, readwrite)
TabOpeningPostOpeningAction NTPActionAfterTabSwitcherDismissal;
// The main coordinator, lazily created the first time it is accessed. Manages
// the main view controller. This property should not be accessed before the
// browser has started up to the FOREGROUND stage.
@property(nonatomic, strong) TabGridCoordinator* mainCoordinator;
// YES while activating a new browser (often leading to dismissing the tab
// switcher.
@property(nonatomic, assign) BOOL activatingBrowser;
// YES if the scene has been backgrounded since it has last been
// SceneActivationLevelForegroundActive.
@property(nonatomic, assign) BOOL backgroundedSinceLastActivated;
// Wrangler to handle BVC and tab model creation, access, and related logic.
// Implements features exposed from this object through the
// BrowserViewInformation protocol.
@property(nonatomic, strong) BrowserViewWrangler* browserViewWrangler;
// The coordinator used to control sign-in UI flows. Lazily created the first
// time it is accessed. Use -[startSigninCoordinatorWithCompletion:] to start
// the coordinator.
@property(nonatomic, strong) SigninCoordinator* signinCoordinator;
// YES if the process of dismissing the sign-in prompt is from an external
// trigger and is currently ongoing. An external trigger isn't done from the
// signin prompt itself (i.e., tapping a button in the sign-in prompt that
// dismisses the prompt). For example, the -dismissModalDialogswithCompletion
// command is considered as an external trigger because it comes from something
// outside the sign-in prompt UI.
@property(nonatomic, assign) BOOL dismissingSigninPromptFromExternalTrigger;
// The coordinator used to present the Incognito interstitial on Incognito
// third-party intents. Created in
// `showIncognitoInterstitialWithUrlLoadParams:dismissOmnibox:completion:`
// and destroyed in
// `closePresentedViews`.
@property(nonatomic, strong)
IncognitoInterstitialCoordinator* incognitoInterstitialCoordinator;
// YES if the Settings view is being dismissed.
@property(nonatomic, assign) BOOL dismissingSettings;
// The state of the scene controlled by this object.
@property(nonatomic, weak, readonly) SceneState* sceneState;
@end
@implementation SceneController
@synthesize startupParameters = _startupParameters;
@synthesize startupParametersAreBeingHandled =
_startupParametersAreBeingHandled;
- (instancetype)initWithSceneState:(SceneState*)sceneState {
self = [super init];
if (self) {
_sceneState = sceneState;
[_sceneState addObserver:self];
[_sceneState.appState addObserver:self];
_sceneURLLoadingService = std::make_unique<SceneUrlLoadingService>();
_sceneURLLoadingService->SetDelegate(self);
_webStateListForwardingObserver =
std::make_unique<WebStateListObserverBridge>(self);
_policyWatcherObserverBridge =
std::make_unique<PolicyWatcherBrowserAgentObserverBridge>(self);
// Add agents.
[_sceneState addAgent:[[UIBlockerSceneAgent alloc] init]];
[_sceneState addAgent:[[IncognitoBlockerSceneAgent alloc] init]];
[_sceneState
addAgent:[[IncognitoReauthSceneAgent alloc]
initWithReauthModule:[[ReauthenticationModule alloc]
init]]];
[_sceneState addAgent:[[StartSurfaceSceneAgent alloc] init]];
[_sceneState addAgent:[[SessionSavingSceneAgent alloc] init]];
[_sceneState addAgent:[[LayoutGuideSceneAgent alloc] init]];
}
return self;
}
#pragma mark - Setters and getters
- (TabGridCoordinator*)mainCoordinator {
if (!_mainCoordinator) {
// Lazily create the main coordinator.
TabGridCoordinator* tabGridCoordinator = [[TabGridCoordinator alloc]
initWithWindow:self.sceneState.window
applicationCommandEndpoint:self
regularBrowser:self.mainInterface.browser
inactiveBrowser:self.mainInterface.inactiveBrowser
incognitoBrowser:self.incognitoInterface.browser];
tabGridCoordinator.delegate = self;
_mainCoordinator = tabGridCoordinator;
}
return _mainCoordinator;
}
- (WrangledBrowser*)mainInterface {
return self.browserViewWrangler.mainInterface;
}
- (WrangledBrowser*)currentInterface {
return self.browserViewWrangler.currentInterface;
}
- (WrangledBrowser*)incognitoInterface {
return self.browserViewWrangler.incognitoInterface;
}
- (id<BrowserProviderInterface>)browserProviderInterface {
return self.browserViewWrangler;
}
- (void)setStartupParameters:(AppStartupParameters*)parameters {
_startupParameters = parameters;
self.startupParametersAreBeingHandled = NO;
if (parameters.openedViaFirstPartyScheme) {
[[NonModalDefaultBrowserPromoSchedulerSceneAgent
agentFromScene:self.sceneState] logUserEnteredAppViaFirstPartyScheme];
}
Browser* mainBrowser =
self.sceneState.browserProviderInterface.mainBrowserProvider.browser;
if (!mainBrowser) {
return;
}
ChromeBrowserState* browserState = mainBrowser->GetBrowserState();
if (!browserState) {
return;
}
if (parameters.openedViaWidgetScheme) {
// Notify Default Browser promo that user opened Chrome with widget.
default_browser::NotifyStartWithWidget(
feature_engagement::TrackerFactory::GetForBrowserState(browserState));
}
if (parameters.openedWithURL) {
// An HTTP(S) URL open that opened Chrome (e.g. default browser open or
// explictly opened from first party apps) should be logged as significant
// activity for a potential user that would want Chrome as their default
// browser in case the user changes away from Chrome. This will leave a
// trace of this activity for re-prompting.
default_browser::NotifyStartWithURL(
feature_engagement::TrackerFactory::GetForBrowserState(browserState));
}
}
- (BOOL)isTabGridVisible {
return self.mainCoordinator.isTabGridActive;
}
#pragma mark - SceneStateObserver
- (void)sceneState:(SceneState*)sceneState
transitionedToActivationLevel:(SceneActivationLevel)level {
AppState* appState = self.sceneState.appState;
[self transitionToSceneActivationLevel:level appInitStage:appState.initStage];
}
- (void)handleExternalIntents {
if (![self canHandleIntents]) {
return;
}
UserActivityBrowserAgent* userActivityBrowserAgent =
UserActivityBrowserAgent::FromBrowser(self.currentInterface.browser);
// Handle URL opening from
// `UIWindowSceneDelegate scene:willConnectToSession:options:`.
for (UIOpenURLContext* context in self.sceneState.connectionOptions
.URLContexts) {
URLOpenerParams* params =
[[URLOpenerParams alloc] initWithUIOpenURLContext:context];
[self
openTabFromLaunchWithParams:params
startupInformation:self.sceneState.appState.startupInformation
appState:self.sceneState.appState];
}
if (self.sceneState.connectionOptions.shortcutItem) {
userActivityBrowserAgent->Handle3DTouchApplicationShortcuts(
self.sceneState.connectionOptions.shortcutItem);
}
// See if this scene launched as part of a multiwindow URL opening.
// If so, load that URL (this also creates a new tab to load the URL
// in). No other UI will show in this case.
NSUserActivity* activityWithCompletion;
for (NSUserActivity* activity in self.sceneState.connectionOptions
.userActivities) {
if (ActivityIsURLLoad(activity)) {
UrlLoadParams params = LoadParamsFromActivity(activity);
ApplicationMode mode = params.in_incognito ? ApplicationMode::INCOGNITO
: ApplicationMode::NORMAL;
[self openOrReuseTabInMode:mode
withUrlLoadParams:params
tabOpenedCompletion:nil];
} else if (ActivityIsTabMove(activity)) {
if ([self isTabActivityValid:activity]) {
[self handleTabMoveActivity:activity];
} else {
// If the tab does not exist, open a new tab.
UrlLoadParams params =
UrlLoadParams::InNewTab(GURL(kChromeUINewTabURL));
ApplicationMode mode = self.currentInterface.incognito
? ApplicationMode::INCOGNITO
: ApplicationMode::NORMAL;
[self openOrReuseTabInMode:mode
withUrlLoadParams:params
tabOpenedCompletion:nil];
}
} else if (!activityWithCompletion) {
// Completion involves user interaction.
// Only one can be triggered.
activityWithCompletion = activity;
}
}
if (activityWithCompletion) {
// This function is called when the scene is activated (or unblocked).
// Consider the scene as still not active at this point as the handling
// of startup parameters is not yet done (and will be later in this
// function).
userActivityBrowserAgent->ContinueUserActivity(activityWithCompletion, NO);
}
self.sceneState.connectionOptions = nil;
if (self.startupParameters) {
if ([self isIncognitoForced]) {
[self.startupParameters
setUnexpectedMode:self.startupParameters.applicationMode ==
ApplicationModeForTabOpening::NORMAL];
// When only incognito mode is available.
[self.startupParameters
setApplicationMode:ApplicationModeForTabOpening::INCOGNITO];
} else if ([self isIncognitoDisabled]) {
[self.startupParameters
setUnexpectedMode:self.startupParameters.applicationMode ==
ApplicationModeForTabOpening::INCOGNITO];
// When incognito mode is disabled.
[self.startupParameters
setApplicationMode:ApplicationModeForTabOpening::NORMAL];
}
userActivityBrowserAgent->RouteToCorrectTab();
// Show a toast if the browser is opened in an unexpected mode.
if (self.startupParameters.isUnexpectedMode) {
[self showToastWhenOpenExternalIntentInUnexpectedMode];
}
}
}
// Handles a tab move activity as part of an intent when launching a
// scene. This should only ever be an intent generated by Chrome.
- (void)handleTabMoveActivity:(NSUserActivity*)activity {
DCHECK(ActivityIsTabMove(activity));
BOOL incognito = GetIncognitoFromTabMoveActivity(activity);
web::WebStateID tabID = GetTabIDFromActivity(activity);
WrangledBrowser* interface = self.currentInterface;
// It's expected that the current interface matches `incognito`.
DCHECK(interface.incognito == incognito);
// Move the tab to the current interface's browser.
MoveTabToBrowser(tabID, interface.browser, /*destination_tab_index=*/0);
}
- (void)recordWindowCreationForSceneState:(SceneState*)sceneState {
// Don't record window creation for single-window environments
if (!base::ios::IsMultipleScenesSupported()) {
return;
}
// Don't record restored window creation.
if (sceneState.currentOrigin == WindowActivityRestoredOrigin) {
return;
}
// If there's only one connected scene, and it isn't being restored, this
// must be the initial app launch with scenes, so don't record the window
// creation.
if (sceneState.appState.connectedScenes.count <= 1) {
return;
}
base::UmaHistogramEnumeration(kMultiWindowOpenInNewWindowHistogram,
sceneState.currentOrigin);
}
- (void)sceneState:(SceneState*)sceneState
hasPendingURLs:(NSSet<UIOpenURLContext*>*)URLContexts {
DCHECK(URLContexts);
// It is necessary to reset the URLContextsToOpen after opening them.
// Handle the opening asynchronously to avoid interfering with potential
// other observers.
dispatch_async(dispatch_get_main_queue(), ^{
[self openURLContexts:sceneState.URLContextsToOpen];
self.sceneState.URLContextsToOpen = nil;
});
}
- (void)performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
completionHandler:
(void (^)(BOOL succeeded))completionHandler {
if (self.sceneState.appState.initStage <= InitStageNormalUI ||
!self.currentInterface.browserState) {
// Don't handle the intent if the browser UI objects aren't yet initialized.
// This is the case when the app is in safe mode or may be the case when the
// app is going through an odd sequence of lifecyle events (shouldn't happen
// but happens somehow), see crbug.com/1211006 for more details.
return;
}
self.sceneState.startupHadExternalIntent = YES;
// Perform the action in incognito when only incognito mode is available.
if ([self isIncognitoForced]) {
[self.startupParameters
setApplicationMode:ApplicationModeForTabOpening::INCOGNITO];
}
UserActivityBrowserAgent* userActivityBrowserAgent =
UserActivityBrowserAgent::FromBrowser(self.currentInterface.browser);
BOOL handledShortcutItem =
userActivityBrowserAgent->Handle3DTouchApplicationShortcuts(shortcutItem);
if (completionHandler) {
completionHandler(handledShortcutItem);
}
}
- (void)sceneState:(SceneState*)sceneState
receivedUserActivity:(NSUserActivity*)userActivity {
if (!userActivity) {
return;
}
if (self.sceneState.appState.initStage <= InitStageNormalUI ||
!self.currentInterface.browserState) {
// Don't handle the intent if the browser UI objects aren't yet initialized.
// This is the case when the app is in safe mode or may be the case when the
// app is going through an odd sequence of lifecyle events (shouldn't happen
// but happens somehow), see crbug.com/1211006 for more details.
return;
}
BOOL sceneIsActive = [self canHandleIntents];
self.sceneState.startupHadExternalIntent = YES;
PrefService* prefs = self.currentInterface.browserState->GetPrefs();
UserActivityBrowserAgent* userActivityBrowserAgent =
UserActivityBrowserAgent::FromBrowser(self.currentInterface.browser);
if (IsIncognitoPolicyApplied(prefs) &&
!userActivityBrowserAgent->ProceedWithUserActivity(userActivity)) {
// If users request opening url in a unavailable mode, don't open the url
// but show a toast.
[self showToastWhenOpenExternalIntentInUnexpectedMode];
} else {
userActivityBrowserAgent->ContinueUserActivity(userActivity, sceneIsActive);
}
if (sceneIsActive) {
// It is necessary to reset the pendingUserActivity after handling it.
// Handle the reset asynchronously to avoid interfering with other
// observers.
dispatch_async(dispatch_get_main_queue(), ^{
self.sceneState.pendingUserActivity = nil;
});
}
}
- (void)sceneStateDidHideModalOverlay:(SceneState*)sceneState {
[self handleExternalIntents];
}
#pragma mark - AppStateObserver
- (void)appState:(AppState*)appState
didTransitionFromInitStage:(InitStage)previousInitStage {
[self transitionToSceneActivationLevel:self.sceneState.activationLevel
appInitStage:appState.initStage];
}
#pragma mark - private
// Creates, if needed, and presents saved passwords settings. Assumes all modal
// dialods are dismissed and `baseViewController` is available to present.
- (void)showSavedPasswordsSettingsAfterModalDismissFromViewController:
(UIViewController*)baseViewController
showCancelButton:
(BOOL)
showCancelButton {
if (!baseViewController) {
// TODO(crbug.com/41352590): Don't pass base view controller through
// dispatched command.
baseViewController = self.currentInterface.viewController;
}
DCHECK(!self.signinCoordinator)
<< "self.signinCoordinator: "
<< base::SysNSStringToUTF8([self.signinCoordinator description]);
if (self.settingsNavigationController) {
[self.settingsNavigationController
showSavedPasswordsSettingsFromViewController:baseViewController
showCancelButton:showCancelButton];
return;
}
Browser* browser = self.mainInterface.browser;
self.settingsNavigationController = [SettingsNavigationController
savePasswordsControllerForBrowser:browser
delegate:self
showCancelButton:showCancelButton];
[baseViewController presentViewController:self.settingsNavigationController
animated:YES
completion:nil];
}
// Creates a `SettingsNavigationController` (if it doesn't already exist) and
// `PasswordCheckupCoordinator` for `referrer`, then starts the
// `PasswordCheckupCoordinator`.
- (void)startPasswordCheckupCoordinator:
(password_manager::PasswordCheckReferrer)referrer {
Browser* browser = self.mainInterface.browser;
if (!self.settingsNavigationController) {
self.settingsNavigationController =
[SettingsNavigationController safetyCheckControllerForBrowser:browser
delegate:self
referrer:referrer];
}
self.passwordCheckupCoordinator = [[PasswordCheckupCoordinator alloc]
initWithBaseNavigationController:self.settingsNavigationController
browser:browser
reauthModule:nil
referrer:referrer];
self.passwordCheckupCoordinator.delegate = self;
[self.passwordCheckupCoordinator start];
}
// Shows the Incognito interstitial on top of `activeViewController`.
// Assumes the Incognito interstitial coordinator is currently not instantiated.
// Runs `completion` once the Incognito interstitial is presented.
- (void)showIncognitoInterstitialWithUrlLoadParams:
(const UrlLoadParams&)urlLoadParams {
DCHECK(self.incognitoInterstitialCoordinator == nil);
self.incognitoInterstitialCoordinator =
[[IncognitoInterstitialCoordinator alloc]
initWithBaseViewController:self.activeViewController
browser:self.currentInterface.browser];
self.incognitoInterstitialCoordinator.delegate = self;
self.incognitoInterstitialCoordinator.tabOpener = self;
self.incognitoInterstitialCoordinator.urlLoadParams = urlLoadParams;
[self.incognitoInterstitialCoordinator start];
}
// A sink for appState:didTransitionFromInitStage: and
// sceneState:transitionedToActivationLevel: events. Discussion: the scene
// controller cares both about the app and the scene init stages. This method is
// called from both observer callbacks and allows to handle all the transitions
// in one place.
- (void)transitionToSceneActivationLevel:(SceneActivationLevel)level
appInitStage:(InitStage)appInitStage {
// Update `backgroundedSinceLastActivated` and, if the scene has just been
// activated, mark its state before the current activation for future use.
BOOL transitionedToForegroundActiveFromBackground =
level == SceneActivationLevelForegroundActive &&
self.backgroundedSinceLastActivated;
if (level <= SceneActivationLevelBackground) {
self.backgroundedSinceLastActivated = YES;
} else if (level == SceneActivationLevelForegroundActive) {
self.backgroundedSinceLastActivated = NO;
}
if (level == SceneActivationLevelDisconnected) {
// The scene may become disconnected at any time. In that case, any UI that
// was already set-up should be torn down.
[self teardownUI];
}
if (appInitStage < InitStageNormalUI) {
// Nothing else per-scene should happen before the app completes the global
// setup, like executing Safe mode, or creating the main BrowserState.
return;
}
BOOL initializingUIInColdStart =
level > SceneActivationLevelBackground && !self.sceneState.UIEnabled;
if (initializingUIInColdStart) {
[self initializeUI];
// Add the scene to the list of connected scene, to restore in case of
// crashes.
[[PreviousSessionInfo sharedInstance]
addSceneSessionID:self.sceneState.sceneSessionID];
}
// When the scene transitions to inactive (such as when it's being shown in
// the OS app-switcher), update the title for display on iPadOS.
if (level == SceneActivationLevelForegroundInactive) {
self.sceneState.scene.title = [self displayTitleForAppSwitcher];
}
if (level == SceneActivationLevelForegroundActive &&
appInitStage == InitStageFinal) {
[self tryPresentSigninModalUI];
[self handleExternalIntents];
if (!initializingUIInColdStart &&
transitionedToForegroundActiveFromBackground &&
self.mainCoordinator.isTabGridActive &&
[self shouldOpenNTPTabOnActivationOfBrowser:self.currentInterface
.browser]) {
DCHECK(!self.activatingBrowser);
[self beginActivatingBrowser:self.mainInterface.browser focusOmnibox:NO];
OpenNewTabCommand* command = [OpenNewTabCommand commandWithIncognito:NO];
command.userInitiated = NO;
Browser* browser = self.currentInterface.browser;
id<ApplicationCommands> applicationHandler = HandlerForProtocol(
browser->GetCommandDispatcher(), ApplicationCommands);
[applicationHandler openURLInNewTab:command];
[self finishActivatingBrowserDismissingTabSwitcher];
}
[HandlerForProtocol(self.currentInterface.browser->GetCommandDispatcher(),
HelpCommands)
presentInProductHelpWithType:InProductHelpType::kTabGridToolbarItem];
}
if (level == SceneActivationLevelBackground) {
[self recordWindowCreationForSceneState:self.sceneState];
}
if (self.sceneState.UIEnabled && level <= SceneActivationLevelDisconnected) {
if (base::ios::IsMultipleScenesSupported()) {
// If Multiple scenes are not supported, the session shouldn't be
// removed as it can be used for normal restoration.
[[PreviousSessionInfo sharedInstance]
removeSceneSessionID:self.sceneState.sceneSessionID];
}
}
}
// Displays either the sign-in upgrade promo if it is eligible or the list
// of signed-in accounts if the user has recently updated their accounts. These
// two sign-in modal dialog are mutually exclusive (one is presented when the
// user is signed in to Chrome, the other when the user is signed out of
// Chrome).
- (void)tryPresentSigninModalUI {
// If the sign-in promo is not eligible, return immediately.
if (![self shouldPresentSigninUpgradePromo]) {
return;
}
ChromeAccountManagerService* accountManagerService =
ChromeAccountManagerServiceFactory::GetForBrowserState(
self.mainInterface.browser->GetBrowserState());
// The sign-in promo should not be presented if there is no identities.
id<SystemIdentity> defaultIdentity =
accountManagerService->GetDefaultIdentity();
DCHECK(defaultIdentity);
using CapabilityResult = SystemIdentityManager::CapabilityResult;
SystemIdentityManager* system_identity_manager =
GetApplicationContext()->GetSystemIdentityManager();
// Asynchronously checks whether the default identity can display extended
// sync promos and displays the sign-in promo if possible.
__weak SceneController* weakSelf = self;
base::Time fetch_start = base::Time::Now();
system_identity_manager->CanShowHistorySyncOptInsWithoutMinorModeRestrictions(
defaultIdentity, base::BindOnce(^(CapabilityResult result) {
base::TimeDelta fetch_duration = (base::Time::Now() - fetch_start);
base::UmaHistogramTimes(
"Signin.AccountCapabilities.GetFromSystemLibraryDuration."
"SigninUpgradePromo",
fetch_duration);
if (fetch_duration > signin::GetWaitThresholdForCapabilities() ||
result != CapabilityResult::kTrue) {
return;
}
[weakSelf presentSigninUpgradePromo];
}));
}
- (void)initializeUI {
if (self.sceneState.UIEnabled) {
return;
}
[self startUpChromeUI];
self.sceneState.UIEnabled = YES;
}
// Starts up a single chrome window and its UI.
- (void)startUpChromeUI {
DCHECK(!self.browserViewWrangler);
DCHECK(_sceneURLLoadingService.get());
DCHECK(self.sceneState.appState.mainProfile.browserState);
SceneState* sceneState = self.sceneState;
// TODO(crbug.com/353683683): do not use appState but instead use
// only profileState which should be set in SceneState initializer.
ChromeBrowserState* browserState =
self.sceneState.appState.mainProfile.browserState;
GetApplicationContext()
->GetProfileManager()
->GetProfileAttributesStorage()
->SetProfileNameForSceneID(
base::SysNSStringToUTF8(sceneState.sceneSessionID),
browserState->GetBrowserStateName());
self.browserViewWrangler =
[[BrowserViewWrangler alloc] initWithBrowserState:browserState
sceneState:sceneState
applicationEndpoint:self
settingsEndpoint:self];
// Create and start the BVC.
[self.browserViewWrangler createMainCoordinatorAndInterface];
Browser* mainBrowser = self.browserViewWrangler.mainInterface.browser;
PromosManager* promosManager =
PromosManagerFactory::GetForBrowserState(browserState);
DefaultBrowserPromoSceneAgent* defaultBrowserAgent =
[[DefaultBrowserPromoSceneAgent alloc] init];
defaultBrowserAgent.promosManager = promosManager;
[sceneState addAgent:defaultBrowserAgent];
[sceneState
addAgent:[[NonModalDefaultBrowserPromoSchedulerSceneAgent alloc] init]];
// Add scene agents that require CommandDispatcher.
CommandDispatcher* mainCommandDispatcher =
mainBrowser->GetCommandDispatcher();
id<ApplicationCommands> applicationCommandsHandler =
HandlerForProtocol(mainCommandDispatcher, ApplicationCommands);
id<PolicyChangeCommands> policyChangeCommandsHandler =
HandlerForProtocol(mainCommandDispatcher, PolicyChangeCommands);
[sceneState
addAgent:[[SigninPolicySceneAgent alloc]
initWithSceneUIProvider:self
applicationCommandsHandler:applicationCommandsHandler
policyChangeCommandsHandler:policyChangeCommandsHandler]];
PrefService* prefService = browserState->GetPrefs();
AuthenticationService* authService =
AuthenticationServiceFactory::GetForBrowserState(browserState);
policy::UserCloudPolicyManager* userPolicyManager =
browserState->GetUserCloudPolicyManager();
if (IsUserPolicyNotificationNeeded(authService, prefService,
userPolicyManager)) {
policy::UserPolicySigninService* userPolicyService =
policy::UserPolicySigninServiceFactory::GetForBrowserState(
browserState);
[sceneState
addAgent:[[UserPolicySceneAgent alloc]
initWithSceneUIProvider:self
authService:authService
applicationCommandsHandler:applicationCommandsHandler
prefService:prefService
mainBrowser:mainBrowser
policyService:userPolicyService
userPolicyManager:userPolicyManager]];
}
enterprise_idle::IdleService* idleService =
enterprise_idle::IdleServiceFactory::GetForBrowserState(
mainBrowser->GetBrowserState());
id<SnackbarCommands> snackbarCommandsHandler =
static_cast<id<SnackbarCommands>>(mainCommandDispatcher);
[sceneState addAgent:[[IdleTimeoutPolicySceneAgent alloc]
initWithSceneUIProvider:self
applicationCommandsHandler:applicationCommandsHandler
snackbarCommandsHandler:snackbarCommandsHandler
idleService:idleService
mainBrowser:mainBrowser]];
// Now that the main browser's command dispatcher is created and the newly
// started UI coordinators have registered with it, inject it into the
// PolicyWatcherBrowserAgent so it can start monitoring UI-impacting policy
// changes.
PolicyWatcherBrowserAgent* policyWatcherAgent =
PolicyWatcherBrowserAgent::FromBrowser(self.mainInterface.browser);
_policyWatcherObserver = std::make_unique<base::ScopedObservation<
PolicyWatcherBrowserAgent, PolicyWatcherBrowserAgentObserverBridge>>(
_policyWatcherObserverBridge.get());
_policyWatcherObserver->Observe(policyWatcherAgent);
policyWatcherAgent->Initialize(policyChangeCommandsHandler);
self.screenshotDelegate = [[ScreenshotDelegate alloc]
initWithBrowserProviderInterface:self.browserViewWrangler];
[sceneState.scene.screenshotService setDelegate:self.screenshotDelegate];
[self activateBVCAndMakeCurrentBVCPrimary];
[self.browserViewWrangler loadSession];
[self createInitialUI:[self initialUIMode]];
// Make sure the GeolocationManager is created to observe permission events.
[GeolocationManager sharedInstance];
if (ShouldPromoManagerDisplayPromos()) {
[sceneState addAgent:[[PromosManagerSceneAgent alloc]
initWithCommandDispatcher:mainCommandDispatcher]];
}
if (IsAppStoreRatingEnabled()) {
[sceneState addAgent:[[AppStoreRatingSceneAgent alloc]
initWithPromosManager:promosManager]];
}
[sceneState addAgent:[[WhatsNewSceneAgent alloc]
initWithPromosManager:promosManager]];
// Do not gate by feature flag so it can run for enabled -> disabled
// scenarios.
[sceneState addAgent:[[CredentialProviderPromoSceneAgent alloc]
initWithPromosManager:promosManager
prefService:prefService]];
}
// Determines the mode (normal or incognito) the initial UI should be in.
- (ApplicationMode)initialUIMode {
// When only incognito mode is available.
if ([self isIncognitoForced]) {
return ApplicationMode::INCOGNITO;
}
// When only incognito mode is disabled.
if ([self isIncognitoDisabled]) {
return ApplicationMode::NORMAL;
}
// Check if the UI is being created from an intent; if it is, open in the
// correct mode for that activity. Because all activities must be in the same
// mode, as soon as any activity reports being in incognito, switch to that
// mode.
for (NSUserActivity* activity in self.sceneState.connectionOptions
.userActivities) {
if (ActivityIsTabMove(activity)) {
return GetIncognitoFromTabMoveActivity(activity)
? ApplicationMode::INCOGNITO
: ApplicationMode::NORMAL;
}
}
// Launch in the mode that matches the state of the scene when the application
// was terminated. If the scene was showing the incognito UI, but there are
// no incognito tabs open (e.g. the tab switcher was active and user closed
// the last tab), then instead show the regular UI.
if (self.sceneState.incognitoContentVisible &&
!self.incognitoInterface.browser->GetWebStateList()->empty()) {
return ApplicationMode::INCOGNITO;
}
// In all other cases, default to normal mode.
return ApplicationMode::NORMAL;
}
// Creates and displays the initial UI in `launchMode`, performing other
// setup and configuration as needed.
- (void)createInitialUI:(ApplicationMode)launchMode {
// Set the Scene application URL loader on the URL loading browser interface
// for the regular and incognito interfaces. This will lazily instantiate the
// incognito interface if it isn't already created.
UrlLoadingBrowserAgent::FromBrowser(self.mainInterface.browser)
->SetSceneService(_sceneURLLoadingService.get());
UrlLoadingBrowserAgent::FromBrowser(self.incognitoInterface.browser)
->SetSceneService(_sceneURLLoadingService.get());
// Observe the web state lists for both browsers.
_incognitoWebStateObserver = std::make_unique<
base::ScopedObservation<WebStateList, WebStateListObserverBridge>>(
_webStateListForwardingObserver.get());
_incognitoWebStateObserver->Observe(
self.incognitoInterface.browser->GetWebStateList());
_mainWebStateObserver = std::make_unique<
base::ScopedObservation<WebStateList, WebStateListObserverBridge>>(
_webStateListForwardingObserver.get());
_mainWebStateObserver->Observe(self.mainInterface.browser->GetWebStateList());
if (base::FeatureList::IsEnabled(
kMakeKeyAndVisibleBeforeMainCoordinatorStart)) {
[self.sceneState.window makeKeyAndVisible];
}
// Lazy init of mainCoordinator.
[self.mainCoordinator start];
if (!base::FeatureList::IsEnabled(
kMakeKeyAndVisibleBeforeMainCoordinatorStart)) {
// Enables UI initializations to query the keyWindow's size. Do this after
// `mainCoordinator start` as it sets self.window.rootViewController to work
// around crbug.com/850387, causing a flicker if -makeKeyAndVisible has been
// called.
[self.sceneState.window makeKeyAndVisible];
}
if (!self.sceneState.appState.startupInformation.isFirstRun) {
[self reconcileEulaAsAccepted];
}
Browser* browser = (launchMode == ApplicationMode::INCOGNITO)
? self.incognitoInterface.browser
: self.mainInterface.browser;
// Inject a NTP before setting the interface, which will trigger a load of
// the current webState.
if (self.sceneState.appState.postCrashAction ==
PostCrashAction::kShowNTPWithReturnToTab) {
InjectNTP(browser);
}
#if !BUILDFLAG(GOOGLE_CHROME_BRANDING)
int tabCountToAdd =
[[NSUserDefaults standardUserDefaults] integerForKey:kAddLotsOfTabs];
// Also check an environment variable for some other test environments which
// expect a minimum number of tabs.
if (tabCountToAdd == 0) {
tabCountToAdd = [[NSProcessInfo.processInfo.environment
objectForKey:@"MINIMUM_TAB_COUNT"] intValue];
tabCountToAdd -= browser->GetWebStateList()->count();
}
if (tabCountToAdd > 0) {
[[NSUserDefaults standardUserDefaults] setInteger:0 forKey:kAddLotsOfTabs];
InjectUnrealizedWebStates(browser, tabCountToAdd);
}
#endif
if (launchMode == ApplicationMode::INCOGNITO) {
[self setCurrentInterfaceForMode:ApplicationMode::INCOGNITO];
} else {
[self setCurrentInterfaceForMode:ApplicationMode::NORMAL];
}
// Figure out what UI to show initially.
if (self.mainCoordinator.isTabGridActive) {
DCHECK(!self.activatingBrowser);
[self beginActivatingBrowser:self.mainInterface.browser focusOmnibox:NO];
[self finishActivatingBrowserDismissingTabSwitcher];
}
// If this web state list should have an NTP created when it activates, then
// create that tab.
if ([self shouldOpenNTPTabOnActivationOfBrowser:browser]) {
OpenNewTabCommand* command = [OpenNewTabCommand
commandWithIncognito:self.currentInterface.incognito];
command.userInitiated = NO;
Browser* currentBrowser = self.currentInterface.browser;
id<ApplicationCommands> applicationHandler = HandlerForProtocol(
currentBrowser->GetCommandDispatcher(), ApplicationCommands);
[applicationHandler openURLInNewTab:command];
}
}
- (void)teardownUI {
// The UI should be stopped before the models they observe are stopped.
// SigninCoordinator teardown is performed by the `signinCompletion` on
// termination of async events, do not add additional teardown here.
[self.signinCoordinator
interruptWithAction:SigninCoordinatorInterrupt::UIShutdownNoDismiss
completion:nil];
// `self.signinCoordinator.signinCompletion()` was called in the interrupt
// method. Therefore now `self.signinCoordinator` is now stopped, and
// `self.signinCoordinator` is now nil.
DCHECK(!self.signinCoordinator)
<< base::SysNSStringToUTF8([self.signinCoordinator description]);
[self.historyCoordinator stop];
self.historyCoordinator = nil;
// Force close the settings if open. This gives Settings the opportunity to
// unregister observers and destroy C++ objects before the application is
// shut down without depending on non-deterministic call to -dealloc.
[self settingsWasDismissed];
[_mainCoordinator stop];
_mainCoordinator = nil;
_incognitoWebStateObserver.reset();
_mainWebStateObserver.reset();
_policyWatcherObserver.reset();
// TODO(crbug.com/40778288): Consider moving this at the beginning of
// teardownUI to indicate that the UI is about to be torn down and that the
// dependencies depending on the browser UI models has to be cleaned up
// agent).
self.sceneState.UIEnabled = NO;
[[SessionSavingSceneAgent agentFromScene:self.sceneState]
saveSessionsIfNeeded];
[self.browserViewWrangler shutdown];
self.browserViewWrangler = nil;
[self.sceneState.appState removeObserver:self];
_sceneURLLoadingService.reset();
}
// Formats string for display on iPadOS application switcher with the
// domain of the foreground tab and the tab count. Assumes the scene is
// visible. Will return nil if there are no tabs.
- (NSString*)displayTitleForAppSwitcher {
DCHECK(self.currentInterface.browser);
web::WebState* webState =
self.currentInterface.browser->GetWebStateList()->GetActiveWebState();
if (!webState) {
return nil;
}
// At this point there is at least one tab.
int numberOfTabs = self.currentInterface.browser->GetWebStateList()->count();
DCHECK(numberOfTabs > 0);
GURL url = webState->GetVisibleURL();
std::u16string urlText = url_formatter::FormatUrl(
url,
url_formatter::kFormatUrlOmitDefaults |
url_formatter::kFormatUrlOmitTrivialSubdomains |
url_formatter::kFormatUrlOmitHTTPS |
url_formatter::kFormatUrlTrimAfterHost,
base::UnescapeRule::SPACES, nullptr, nullptr, nullptr);
std::u16string pattern =
l10n_util::GetStringUTF16(IDS_IOS_APP_SWITCHER_SCENE_TITLE);
std::u16string formattedTitle =
base::i18n::MessageFormatter::FormatWithNamedArgs(
pattern, "domain", urlText, "count", numberOfTabs - 1);
return base::SysUTF16ToNSString(formattedTitle);
}
// If users request to open tab or search and Chrome is not opened in the mode
// they expected, show a toast to clarify that the expected mode is not
// available.
- (void)showToastWhenOpenExternalIntentInUnexpectedMode {
id<SnackbarCommands> handler = HandlerForProtocol(
self.mainInterface.browser->GetCommandDispatcher(), SnackbarCommands);
BOOL inIncognitoMode = [self isIncognitoForced];
UrlLoadParams params = UrlLoadParams::InNewTab(GURL(kChromeUIManagementURL));
params.web_params.transition_type = ui::PAGE_TRANSITION_TYPED;
ProceduralBlock moreAction = ^{
[self dismissModalsAndMaybeOpenSelectedTabInMode:
inIncognitoMode ? ApplicationModeForTabOpening::INCOGNITO
: ApplicationModeForTabOpening::NORMAL
withUrlLoadParams:params
dismissOmnibox:YES
completion:nil];
};
MDCSnackbarMessageAction* action = [[MDCSnackbarMessageAction alloc] init];
action.handler = moreAction;
action.title = l10n_util::GetNSString(IDS_IOS_NAVIGATION_BAR_MORE_BUTTON);
action.accessibilityIdentifier =
l10n_util::GetNSString(IDS_IOS_NAVIGATION_BAR_MORE_BUTTON);
NSString* text =
inIncognitoMode
? l10n_util::GetNSString(IDS_IOS_SNACKBAR_MESSAGE_INCOGNITO_FORCED)
: l10n_util::GetNSString(IDS_IOS_SNACKBAR_MESSAGE_INCOGNITO_DISABLED);
MDCSnackbarMessage* message = CreateSnackbarMessage(text);
message.action = action;
[handler showSnackbarMessage:message
withHapticType:UINotificationFeedbackTypeError];
}
- (BOOL)isIncognitoDisabled {
return IsIncognitoModeDisabled(
self.mainInterface.browser->GetBrowserState()->GetPrefs());
}
// YES if incognito mode is forced by enterprise policy.
- (BOOL)isIncognitoForced {
return IsIncognitoModeForced(
self.incognitoInterface.browser->GetBrowserState()->GetPrefs());
}
// Returns 'YES' if the tabID from the given `activity` is valid.
- (BOOL)isTabActivityValid:(NSUserActivity*)activity {
web::WebStateID tabID = GetTabIDFromActivity(activity);
ChromeBrowserState* browserState = self.currentInterface.browserState;
BrowserList* browserList =
BrowserListFactory::GetForBrowserState(browserState);
const BrowserList::BrowserType browser_types =
self.currentInterface.incognito
? BrowserList::BrowserType::kIncognito
: BrowserList::BrowserType::kRegularAndInactive;
std::set<Browser*> browsers = browserList->BrowsersOfType(browser_types);
BrowserAndIndex tabInfo = FindBrowserAndIndex(tabID, browsers);
return tabInfo.tab_index != WebStateList::kInvalidIndex;
}
// Sets a LocalState pref marking the TOS EULA as accepted.
// If this function is called, the EULA flag is not set but the FRE was not
// displayed.
// This can only happen if the EULA flag has not been set correctly on a
// previous session.
- (void)reconcileEulaAsAccepted {
static dispatch_once_t once_token = 0;
dispatch_once(&once_token, ^{
PrefService* prefs = GetApplicationContext()->GetLocalState();
if (!FirstRun::IsChromeFirstRun() &&
!prefs->GetBoolean(prefs::kEulaAccepted)) {
prefs->SetBoolean(prefs::kEulaAccepted, true);
prefs->CommitPendingWrite();
base::UmaHistogramBoolean("IOS.ReconcileEULAPref", true);
}
});
}
// Returns YES if the sign-in upgrade promo should be presented.
- (BOOL)shouldPresentSigninUpgradePromo {
if (![self isTabAvailableToPresentViewController]) {
return NO;
}
if (!signin::ShouldPresentUserSigninUpgrade(
self.sceneState.browserProviderInterface.mainBrowserProvider.browser
->GetBrowserState(),
GetApplicationContext()->GetLocalState(),
version_info::GetVersion())) {
return NO;
}
// Don't show the promo in Incognito mode.
if (self.currentInterface == self.incognitoInterface) {
return NO;
}
// Don't show promos if the app was launched from a URL.
if (self.startupParameters) {
return NO;
}
// Don't show the promo if the window is not active.
if (self.sceneState.activationLevel < SceneActivationLevelForegroundActive) {
return NO;
}
// Don't show the promo if already presented.
if (self.sceneState.appState.signinUpgradePromoPresentedOnce) {
return NO;
}
return YES;
}
// Presents the sign-in upgrade promo.
- (void)presentSigninUpgradePromo {
// It is possible during a slow asynchronous call that the user changes their
// state so as to no longer be eligible for sign-in promos. Return early in
// this case.
if (![self shouldPresentSigninUpgradePromo]) {
return;
}
self.sceneState.appState.signinUpgradePromoPresentedOnce = YES;
DCHECK(!self.signinCoordinator)
<< "self.signinCoordinator: "
<< base::SysNSStringToUTF8([self.signinCoordinator description]);
Browser* browser = self.mainInterface.browser;
self.signinCoordinator = [SigninCoordinator
upgradeSigninPromoCoordinatorWithBaseViewController:self.mainInterface
.viewController
browser:browser];
[self startSigninCoordinatorWithCompletion:nil];
}
- (BOOL)canHandleIntents {
if (self.sceneState.activationLevel < SceneActivationLevelForegroundActive) {
return NO;
}
if (self.sceneState.appState.initStage <= InitStageFirstRun) {
return NO;
}
if (self.sceneState.presentingModalOverlay) {
return NO;
}
if (IsSigninForcedByPolicy()) {
if (self.signinCoordinator) {
// Return NO because intents cannot be handled when using
// `self.signinCoordinator` for the forced sign-in prompt.
return NO;
}
if (![self isSignedIn]) {
// Return NO if the forced sign-in policy is enabled while the browser is
// signed out because intent can only be processed when the browser is
// signed-in in that case. This condition may be reached at startup before
// `self.signinCoordinator` is set to show the forced sign-in prompt.
return NO;
}
}
return YES;
}
- (BOOL)isSignedIn {
AuthenticationService* authenticationService =
AuthenticationServiceFactory::GetForBrowserState(
self.sceneState.browserProviderInterface.mainBrowserProvider.browser
->GetBrowserState());
DCHECK(authenticationService);
DCHECK(authenticationService->initialized());
return authenticationService->HasPrimaryIdentity(
signin::ConsentLevel::kSignin);
}
#pragma mark - ApplicationCommands
- (void)dismissModalDialogsWithCompletion:(ProceduralBlock)completion {
[self dismissModalDialogsWithCompletion:completion dismissOmnibox:YES];
}
- (void)showHistory {
CHECK(!self.currentInterface.incognito)
<< "Current interface is incognito and should NOT show history. Call "
"this on regular interface.";
self.historyCoordinator = [[HistoryCoordinator alloc]
initWithBaseViewController:self.currentInterface.viewController
browser:self.mainInterface.browser];
self.historyCoordinator.loadStrategy = UrlLoadStrategy::NORMAL;
self.historyCoordinator.delegate = self;
[self.historyCoordinator start];
}
// Opens an url from a link in the settings UI.
- (void)closeSettingsUIAndOpenURL:(OpenNewTabCommand*)command {
DCHECK([command fromChrome]);
UrlLoadParams params = UrlLoadParams::InNewTab([command URL]);
params.web_params.transition_type = ui::PAGE_TRANSITION_TYPED;
ProceduralBlock completion = ^{
ApplicationModeForTabOpening mode =
[self isIncognitoForced] ? ApplicationModeForTabOpening::INCOGNITO
: ApplicationModeForTabOpening::NORMAL;
[self dismissModalsAndMaybeOpenSelectedTabInMode:mode
withUrlLoadParams:params
dismissOmnibox:YES
completion:nil];
};
[self closePresentedViews:YES completion:completion];
}
- (void)closeSettingsUI {
[self closePresentedViews:YES completion:nullptr];
}
- (void)prepareTabSwitcher {
web::WebState* currentWebState =
self.currentInterface.browser->GetWebStateList()->GetActiveWebState();
if (currentWebState) {
SnapshotTabHelper::FromWebState(currentWebState)
->UpdateSnapshotWithCallback(nil);
}
}
- (void)displayTabGridInMode:(TabGridOpeningMode)mode {
if (self.mainCoordinator.isTabGridActive) {
return;
}
if (!self.isProcessingVoiceSearchCommand) {
BOOL incognito = self.currentInterface.incognito;
if (mode == TabGridOpeningMode::kRegular && incognito) {
[self setCurrentInterfaceForMode:ApplicationMode::NORMAL];
} else if (mode == TabGridOpeningMode::kIncognito && !incognito) {
[self setCurrentInterfaceForMode:ApplicationMode::INCOGNITO];
}
[self showTabSwitcher];
self.isProcessingTabSwitcherCommand = YES;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
kExpectedTransitionDurationInNanoSeconds),
dispatch_get_main_queue(), ^{
self.isProcessingTabSwitcherCommand = NO;
});
}
}
- (void)showPrivacySettingsFromViewController:
(UIViewController*)baseViewController {
if (self.settingsNavigationController) {
return;
}
Browser* browser = self.mainInterface.browser;
self.settingsNavigationController =
[SettingsNavigationController privacyControllerForBrowser:browser
delegate:self];
[baseViewController presentViewController:self.settingsNavigationController
animated:YES
completion:nil];
}
- (void)showReportAnIssueFromViewController:
(UIViewController*)baseViewController
sender:(UserFeedbackSender)sender {
[self showReportAnIssueFromViewController:baseViewController
sender:sender
specificProductData:nil];
}
- (void)
showReportAnIssueFromViewController:(UIViewController*)baseViewController
sender:(UserFeedbackSender)sender
specificProductData:(NSDictionary<NSString*, NSString*>*)
specificProductData {
DCHECK(baseViewController);
// This dispatch is necessary to give enough time for the tools menu to
// disappear before taking a screenshot.
__weak SceneController* weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
// Set the delay timeout to capture about 85% of users (approx. 2 seconds),
// see Signin.ListFamilyMembersRequest.OverallLatency.
[weakSelf presentReportAnIssueViewController:baseViewController
sender:sender
specificProductData:specificProductData
timeout:base::Seconds(2)
completion:base::DoNothing()];
});
}
using UserFeedbackDataCallback =
base::RepeatingCallback<void(UserFeedbackData*)>;
- (void)presentReportAnIssueViewController:(UIViewController*)baseViewController
sender:(UserFeedbackSender)sender
specificProductData:(NSDictionary<NSString*, NSString*>*)
specificProductData
timeout:(base::TimeDelta)timeout
completion:
(UserFeedbackDataCallback)completion {
UserFeedbackData* userFeedbackData =
[self createUserFeedbackDataForSender:sender
specificProductData:specificProductData];
[self presentReportAnIssueViewController:baseViewController
sender:sender
userFeedbackData:userFeedbackData
timeout:timeout
completion:std::move(completion)];
}
- (void)presentReportAnIssueViewController:(UIViewController*)baseViewController
sender:(UserFeedbackSender)sender
userFeedbackData:(UserFeedbackData*)data
timeout:(base::TimeDelta)timeout
completion:
(UserFeedbackDataCallback)completion {
DCHECK(!self.signinCoordinator)
<< "self.signinCoordinator: "
<< base::SysNSStringToUTF8([self.signinCoordinator description]);
if (self.settingsNavigationController) {
return;
}
signin::IdentityManager* identity_manager =
IdentityManagerFactory::GetForBrowserState(
self.mainInterface.browserState);
CoreAccountInfo primary_account =
identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
// Retrieves the Family Link member role for the signed-in account and
// populates the corresponding `UserFeedbackData` property.
if (!primary_account.IsEmpty()) {
__weak SceneController* weakSelf = self;
_family_members_fetcher = supervised_user::FetchListFamilyMembers(
*identity_manager,
self.mainInterface.browserState->GetSharedURLLoaderFactory(),
base::BindOnce(&OnListFamilyMembersResponse, primary_account.gaia, data)
.Then(base::BindOnce(^{
[weakSelf presentUserFeedbackViewController:baseViewController
withUserFeedbackData:data
cancelFamilyMembersFetch:NO
completion:completion];
})));
// Timeout the request to list family members.
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, base::BindOnce(^{
[weakSelf presentUserFeedbackViewController:baseViewController
withUserFeedbackData:data
cancelFamilyMembersFetch:YES
completion:completion];
}),
timeout);
return;
}
[self presentUserFeedbackViewController:baseViewController
withUserFeedbackData:data
cancelFamilyMembersFetch:NO
completion:completion];
}
- (void)presentUserFeedbackViewController:(UIViewController*)baseViewController
withUserFeedbackData:(UserFeedbackData*)data
cancelFamilyMembersFetch:(BOOL)cancelFamilyMembersFetch
completion:(UserFeedbackDataCallback)completion {
// Cancel any list family member requests in progress.
if (cancelFamilyMembersFetch) {
_family_members_fetcher.reset();
}
Browser* browser = self.mainInterface.browser;
id<ApplicationCommands> handler =
HandlerForProtocol(browser->GetCommandDispatcher(), ApplicationCommands);
if (ios::provider::CanUseStartUserFeedbackFlow()) {
UserFeedbackConfiguration* configuration =
[[UserFeedbackConfiguration alloc] init];
configuration.data = data;
configuration.handler = handler;
configuration.singleSignOnService =
GetApplicationContext()->GetSingleSignOnService();
NSError* error;
ios::provider::StartUserFeedbackFlow(configuration, baseViewController,
&error);
UMA_HISTOGRAM_BOOLEAN("IOS.FeedbackKit.UserFlowStartedSuccess",
error == nil);
} else {
self.settingsNavigationController =
[SettingsNavigationController userFeedbackControllerForBrowser:browser
delegate:self
userFeedbackData:data];
[baseViewController presentViewController:self.settingsNavigationController
animated:YES
completion:nil];
}
std::move(completion).Run(data);
}
- (UserFeedbackData*)createUserFeedbackDataForSender:(UserFeedbackSender)sender
specificProductData:
(NSDictionary<NSString*, NSString*>*)
specificProductData {
UserFeedbackData* data = [[UserFeedbackData alloc] init];
data.origin = sender;
data.currentPageIsIncognito = self.currentInterface.incognito;
CGFloat scale = 0.0;
if (self.mainCoordinator.isTabGridActive) {
// For screenshots of the tab switcher we need to use a scale of 1.0 to
// avoid spending too much time since the tab switcher can have lots of
// subviews.
scale = 1.0;
}
UIView* lastView = self.mainCoordinator.activeViewController.view;
DCHECK(lastView);
data.currentPageScreenshot = CaptureView(lastView, scale);
ChromeBrowserState* browserState = self.currentInterface.browserState;
if (browserState->IsOffTheRecord()) {
data.currentPageIsIncognito = YES;
} else {
data.currentPageIsIncognito = NO;
}
data.productSpecificData = specificProductData;
return data;
}
- (void)openURLInNewTab:(OpenNewTabCommand*)command {
if (command.inIncognito) {
IncognitoReauthSceneAgent* reauthAgent =
[IncognitoReauthSceneAgent agentFromScene:self.sceneState];
if (reauthAgent.authenticationRequired) {
__weak SceneController* weakSelf = self;
[reauthAgent
authenticateIncognitoContentWithCompletionBlock:^(BOOL success) {
if (success) {
[weakSelf openURLInNewTab:command];
}
}];
return;
}
}
UrlLoadParams params =
UrlLoadParams::InNewTab(command.URL, command.virtualURL);
params.SetInBackground(command.inBackground);
params.web_params.referrer = command.referrer;
params.in_incognito = command.inIncognito;
params.append_to = command.appendTo;
params.origin_point = command.originPoint;
params.from_chrome = command.fromChrome;
params.user_initiated = command.userInitiated;
params.should_focus_omnibox = command.shouldFocusOmnibox;
params.inherit_opener = !command.inBackground;
_sceneURLLoadingService->LoadUrlInNewTab(params);
}
// TODO(crbug.com/41352590) : Do not pass `baseViewController` through
// dispatcher.
- (void)showSignin:(ShowSigninCommand*)command
baseViewController:(UIViewController*)baseViewController {
// Calling this method when there is a signinCoordinator alive is incorrect
// as there should not be 2 signinCoordinators alive at the same time (note
// that allocating the second one will dealloc the first and this crashes in
// various ways).
if (command.skipIfUINotAvaible &&
(baseViewController.presentedViewController ||
![self isTabAvailableToPresentViewController])) {
// Make sure the UI is available to present the sign-in view.
return;
}
if (self.signinCoordinator) {
// As of M121, the CHECK bellow is known to fire in various cases. The goal
// of the histograms below is to detect the number of incorrect cases and
// for which of the access points they are triggered.
base::UmaHistogramEnumeration(
"Signin.ShowSigninCoordinatorWhenAlreadyPresent.NewAccessPoint",
command.accessPoint, signin_metrics::AccessPoint::ACCESS_POINT_MAX);
base::UmaHistogramEnumeration(
"Signin.ShowSigninCoordinatorWhenAlreadyPresent.OldAccessPoint",
self.signinCoordinator.accessPoint,
signin_metrics::AccessPoint::ACCESS_POINT_MAX);
// The goal of this histogram is to understand if the issue is related to
// a double tap (duration less than 1s), or if `self.signinCoordinator`
// is not visible anymore on the screen (duration more than 1s).
const base::TimeDelta duration =
base::TimeTicks::Now() - self.signinCoordinator.creationTimeTicks;
UmaHistogramTimes("Signin.ShowSigninCoordinatorWhenAlreadyPresent."
"DurationBetweenTwoSigninCoordinatorCreation",
duration);
}
// TODO(crbug.com/40071586): Change this to a CHECK once this invariant is
// correct.
DCHECK(!self.signinCoordinator)
<< "self.signinCoordinator: "
<< base::SysNSStringToUTF8([self.signinCoordinator description]);
Browser* mainBrowser = self.mainInterface.browser;
switch (command.operation) {
case AuthenticationOperation::kPrimaryAccountReauth:
self.signinCoordinator = [SigninCoordinator
primaryAccountReauthCoordinatorWithBaseViewController:
baseViewController
browser:mainBrowser
accessPoint:command
.accessPoint
promoAction:
command.promoAction];
break;
case AuthenticationOperation::kResignin:
self.signinCoordinator = [SigninCoordinator
signinAndSyncReauthCoordinatorWithBaseViewController:
baseViewController
browser:mainBrowser
accessPoint:command
.accessPoint
promoAction:
command.promoAction];
break;
case AuthenticationOperation::kSigninOnly:
self.signinCoordinator = [SigninCoordinator
consistencyPromoSigninCoordinatorWithBaseViewController:
baseViewController
browser:mainBrowser
accessPoint:
command.accessPoint];
break;
case AuthenticationOperation::kAddAccount:
self.signinCoordinator = [SigninCoordinator
addAccountCoordinatorWithBaseViewController:baseViewController
browser:mainBrowser
accessPoint:command.accessPoint];
break;
case AuthenticationOperation::kForcedSigninAndSync:
self.signinCoordinator = [SigninCoordinator
forcedSigninCoordinatorWithBaseViewController:baseViewController
browser:mainBrowser
accessPoint:command.accessPoint];
break;
case AuthenticationOperation::kInstantSignin:
self.signinCoordinator = [SigninCoordinator
instantSigninCoordinatorWithBaseViewController:baseViewController
browser:mainBrowser
identity:command.identity
accessPoint:command.accessPoint
promoAction:command.promoAction];
break;
case AuthenticationOperation::kSheetSigninAndHistorySync:
self.signinCoordinator = [SigninCoordinator
sheetSigninAndHistorySyncCoordinatorWithBaseViewController:
baseViewController
browser:mainBrowser
accessPoint:
command.accessPoint
promoAction:
command
.promoAction];
break;
}
[self startSigninCoordinatorWithCompletion:command.callback];
}
- (void)
showTrustedVaultReauthForFetchKeysFromViewController:
(UIViewController*)viewController
securityDomainID:
(trusted_vault::SecurityDomainId)
securityDomainID
trigger:
(syncer::
TrustedVaultUserActionTriggerForUMA)
trigger
accessPoint:
(signin_metrics::AccessPoint)
accessPoint {
[self
showTrustedVaultDialogFromViewController:viewController
intent:
SigninTrustedVaultDialogIntentFetchKeys
securityDomainID:securityDomainID
trigger:trigger
accessPoint:accessPoint];
}
- (void)
showTrustedVaultReauthForDegradedRecoverabilityFromViewController:
(UIViewController*)viewController
securityDomainID:
(trusted_vault::
SecurityDomainId)
securityDomainID
trigger:
(syncer::
TrustedVaultUserActionTriggerForUMA)
trigger
accessPoint:
(signin_metrics::
AccessPoint)
accessPoint {
[self
showTrustedVaultDialogFromViewController:viewController
intent:
SigninTrustedVaultDialogIntentDegradedRecoverability
securityDomainID:securityDomainID
trigger:trigger
accessPoint:accessPoint];
}
- (void)showWebSigninPromoFromViewController:
(UIViewController*)baseViewController
URL:(const GURL&)url {
// Do not display the web sign-in promo if there is any UI on the screen.
if (baseViewController.presentedViewController ||
![self isTabAvailableToPresentViewController]) {
return;
}
if (!signin::ShouldPresentWebSignin(self.mainInterface.browserState)) {
return;
}
self.signinCoordinator = [SigninCoordinator
consistencyPromoSigninCoordinatorWithBaseViewController:baseViewController
browser:self.mainInterface
.browser
accessPoint:
signin_metrics::AccessPoint::
ACCESS_POINT_WEB_SIGNIN];
if (!self.signinCoordinator) {
return;
}
__weak SceneController* weakSelf = self;
// Copy the URL so it can be safely captured in the block.
GURL copiedURL = url;
[self startSigninCoordinatorWithCompletion:^(
SigninCoordinatorResult result,
SigninCompletionInfo* completionInfo) {
// If the sign-in is not successful or the scene controller is shut down do
// not load the continuation URL.
BOOL success = result == SigninCoordinatorResultSuccess;
if (!success || !weakSelf) {
return;
}
UrlLoadingBrowserAgent::FromBrowser(weakSelf.mainInterface.browser)
->Load(UrlLoadParams::InCurrentTab(copiedURL));
}];
}
- (void)showSigninAccountNotificationFromViewController:
(UIViewController*)baseViewController {
web::WebState* webState =
self.mainInterface.browser->GetWebStateList()->GetActiveWebState();
DCHECK(webState);
infobars::InfoBarManager* infoBarManager =
InfoBarManagerImpl::FromWebState(webState);
DCHECK(infoBarManager);
CommandDispatcher* dispatcher =
self.mainInterface.browser->GetCommandDispatcher();
id<SettingsCommands> settingsHandler =
HandlerForProtocol(dispatcher, SettingsCommands);
SigninNotificationInfoBarDelegate::Create(
infoBarManager, self.mainInterface.browser->GetBrowserState(),
settingsHandler, baseViewController);
}
- (void)setIncognitoContentVisible:(BOOL)incognitoContentVisible {
self.sceneState.incognitoContentVisible = incognitoContentVisible;
}
- (void)startVoiceSearch {
if (!self.isProcessingTabSwitcherCommand) {
[self startVoiceSearchInCurrentBVC];
self.isProcessingVoiceSearchCommand = YES;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
kExpectedTransitionDurationInNanoSeconds),
dispatch_get_main_queue(), ^{
self.isProcessingVoiceSearchCommand = NO;
});
}
}
- (void)showSettingsFromViewController:(UIViewController*)baseViewController {
BOOL hasDefaultBrowserBlueDot = NO;
Browser* browser = self.mainInterface.browser;
if (browser) {
feature_engagement::Tracker* tracker =
feature_engagement::TrackerFactory::GetForBrowserState(
browser->GetBrowserState());
if (tracker) {
hasDefaultBrowserBlueDot =
ShouldTriggerDefaultBrowserHighlightFeature(tracker);
}
}
if (hasDefaultBrowserBlueDot) {
RecordDefaultBrowserBlueDotFirstDisplay();
}
[self showSettingsFromViewController:baseViewController
hasDefaultBrowserBlueDot:hasDefaultBrowserBlueDot];
}
- (void)showSettingsFromViewController:(UIViewController*)baseViewController
hasDefaultBrowserBlueDot:(BOOL)hasDefaultBrowserBlueDot {
if (!baseViewController) {
baseViewController = self.currentInterface.viewController;
}
DCHECK(!self.signinCoordinator)
<< "self.signinCoordinator: "
<< base::SysNSStringToUTF8([self.signinCoordinator description]);
if (self.settingsNavigationController) {
return;
}
[[DeferredInitializationRunner sharedInstance]
runBlockIfNecessary:kPrefObserverInit];
Browser* browser = self.mainInterface.browser;
self.settingsNavigationController = [SettingsNavigationController
mainSettingsControllerForBrowser:browser
delegate:self
hasDefaultBrowserBlueDot:hasDefaultBrowserBlueDot];
[baseViewController presentViewController:self.settingsNavigationController
animated:YES
completion:nil];
}
- (void)openNewWindowWithActivity:(NSUserActivity*)userActivity {
if (!base::ios::IsMultipleScenesSupported()) {
return; // silent no-op.
}
UISceneActivationRequestOptions* options =
[[UISceneActivationRequestOptions alloc] init];
options.requestingScene = self.sceneState.scene;
if (self.mainInterface) {
PrefService* prefs = self.mainInterface.browserState->GetPrefs();
if (IsIncognitoModeForced(prefs)) {
userActivity = AdaptUserActivityToIncognito(userActivity, true);
} else if (IsIncognitoModeDisabled(prefs)) {
userActivity = AdaptUserActivityToIncognito(userActivity, false);
}
[UIApplication.sharedApplication
requestSceneSessionActivation:nil /* make a new scene */
userActivity:userActivity
options:options
errorHandler:nil];
}
}
- (void)prepareToPresentModal:(ProceduralBlock)completion {
__weak __typeof(self) weakSelf = self;
ProceduralBlock ensureNTP = ^{
[weakSelf ensureNTP];
completion();
};
if (self.mainCoordinator.isTabGridActive ||
(self.currentInterface.incognito && ![self isIncognitoForced])) {
[self closePresentedViews:YES
completion:^{
[weakSelf openNonIncognitoTab:ensureNTP];
}];
return;
}
[self dismissModalDialogsWithCompletion:ensureNTP];
}
// Returns YES if the current Tab is available to present a view controller.
- (BOOL)isTabAvailableToPresentViewController {
if (self.signinCoordinator) {
return NO;
}
if (self.settingsNavigationController) {
return NO;
}
if (self.sceneState.appState.initStage <= InitStageFirstRun) {
return NO;
}
if (self.sceneState.appState.currentUIBlocker) {
return NO;
}
if (self.mainCoordinator.isTabGridActive) {
return NO;
}
return YES;
}
#pragma mark - SettingsCommands
// TODO(crbug.com/41352590) : Remove show settings from MainController.
- (void)showAccountsSettingsFromViewController:
(UIViewController*)baseViewController
skipIfUINotAvailable:(BOOL)skipIfUINotAvailable {
if (skipIfUINotAvailable && (baseViewController.presentedViewController ||
![self isTabAvailableToPresentViewController])) {
return;
}
DCHECK(!self.signinCoordinator)
<< "self.signinCoordinator: "
<< base::SysNSStringToUTF8([self.signinCoordinator description]);
if (!baseViewController) {
DCHECK_EQ(self.currentInterface.viewController,
self.mainCoordinator.activeViewController);
baseViewController = self.currentInterface.viewController;
}
if (self.currentInterface.incognito) {
NOTREACHED_IN_MIGRATION();
return;
}
if (self.settingsNavigationController) {
[self.settingsNavigationController
showAccountsSettingsFromViewController:baseViewController
skipIfUINotAvailable:NO];
return;
}
Browser* browser = self.mainInterface.browser;
self.settingsNavigationController =
[SettingsNavigationController accountsControllerForBrowser:browser
delegate:self];
[baseViewController presentViewController:self.settingsNavigationController
animated:YES
completion:nil];
}
// TODO(crbug.com/41352590) : Remove Google services settings from
// MainController.
- (void)showGoogleServicesSettingsFromViewController:
(UIViewController*)baseViewController {
DCHECK(!self.signinCoordinator)
<< "self.signinCoordinator: "
<< base::SysNSStringToUTF8([self.signinCoordinator description]);
if (!baseViewController) {
DCHECK_EQ(self.currentInterface.viewController,
self.mainCoordinator.activeViewController);
baseViewController = self.currentInterface.viewController;
}
if (self.settingsNavigationController) {
// Navigate to the Google services settings if the settings dialog is
// already opened.
[self.settingsNavigationController
showGoogleServicesSettingsFromViewController:baseViewController];
return;
}
Browser* browser = self.mainInterface.browser;
self.settingsNavigationController =
[SettingsNavigationController googleServicesControllerForBrowser:browser
delegate:self];
[baseViewController presentViewController:self.settingsNavigationController
animated:YES
completion:nil];
}
// TODO(crbug.com/41352590) : Remove show settings commands from MainController.
- (void)showSyncSettingsFromViewController:
(UIViewController*)baseViewController {
DCHECK(!self.signinCoordinator)
<< "self.signinCoordinator: "
<< base::SysNSStringToUTF8([self.signinCoordinator description]);
if (self.settingsNavigationController) {
[self.settingsNavigationController
showSyncSettingsFromViewController:baseViewController];
return;
}
Browser* browser = self.mainInterface.browser;
self.settingsNavigationController =
[SettingsNavigationController syncSettingsControllerForBrowser:browser
delegate:self];
[baseViewController presentViewController:self.settingsNavigationController
animated:YES
completion:nil];
}
// TODO(crbug.com/41352590) : Remove show settings commands from MainController.
- (void)showSyncPassphraseSettingsFromViewController:
(UIViewController*)baseViewController {
DCHECK(!self.signinCoordinator)
<< "self.signinCoordinator: "
<< base::SysNSStringToUTF8([self.signinCoordinator description]);
if (self.settingsNavigationController) {
[self.settingsNavigationController
showSyncPassphraseSettingsFromViewController:baseViewController];
return;
}
Browser* browser = self.mainInterface.browser;
self.settingsNavigationController =
[SettingsNavigationController syncPassphraseControllerForBrowser:browser
delegate:self];
[baseViewController presentViewController:self.settingsNavigationController
animated:YES
completion:nil];
}
// TODO(crbug.com/41352590) : Remove show settings commands from MainController.
- (void)showSavedPasswordsSettingsFromViewController:
(UIViewController*)baseViewController
showCancelButton:(BOOL)showCancelButton {
// Wait for dismiss to complete before trying to present a new view.
__weak SceneController* weakSelf = self;
[self dismissModalDialogsWithCompletion:^{
[weakSelf
showSavedPasswordsSettingsAfterModalDismissFromViewController:
baseViewController
showCancelButton:
showCancelButton];
}];
}
// Shows the Password Checkup page for `referrer`.
- (void)showPasswordCheckupPageForReferrer:
(password_manager::PasswordCheckReferrer)referrer {
UIViewController* baseViewController = self.currentInterface.viewController;
[self startPasswordCheckupCoordinator:referrer];
[baseViewController presentViewController:self.settingsNavigationController
animated:YES
completion:nil];
}
- (void)showPasswordDetailsForCredential:
(password_manager::CredentialUIEntry)credential
inEditMode:(BOOL)editMode
showCancelButton:(BOOL)showCancelButton {
UIViewController* baseViewController = self.currentInterface.viewController;
if (self.settingsNavigationController) {
[self.settingsNavigationController
showPasswordDetailsForCredential:credential
inEditMode:editMode
showCancelButton:showCancelButton];
return;
}
Browser* browser = self.mainInterface.browser;
self.settingsNavigationController = [SettingsNavigationController
passwordDetailsControllerForBrowser:browser
delegate:self
credential:credential
inEditMode:editMode
showCancelButton:showCancelButton];
[baseViewController presentViewController:self.settingsNavigationController
animated:YES
completion:nil];
}
// Opens the Password Issues list displaying compromised, weak or reused
// credentials for `warningType` and `referrer`.
- (void)
showPasswordIssuesWithWarningType:(password_manager::WarningType)warningType
referrer:(password_manager::PasswordCheckReferrer)
referrer {
UIViewController* baseViewController = self.currentInterface.viewController;
[self startPasswordCheckupCoordinator:referrer];
[self.passwordCheckupCoordinator
showPasswordIssuesWithWarningType:warningType];
[baseViewController presentViewController:self.settingsNavigationController
animated:YES
completion:nil];
}
- (void)showAddressDetails:(const autofill::AutofillProfile*)address
inEditMode:(BOOL)editMode
offerMigrateToAccount:(BOOL)offerMigrateToAccount {
UIViewController* baseViewController = self.currentInterface.viewController;
if (self.settingsNavigationController) {
[self.settingsNavigationController
showAddressDetails:address
inEditMode:editMode
offerMigrateToAccount:offerMigrateToAccount];
return;
}
Browser* browser = self.mainInterface.browser;
self.settingsNavigationController = [SettingsNavigationController
addressDetailsControllerForBrowser:browser
delegate:self
address:address
inEditMode:editMode
offerMigrateToAccount:offerMigrateToAccount];
[baseViewController presentViewController:self.settingsNavigationController
animated:YES
completion:nil];
}
// TODO(crbug.com/41352590) : Remove show settings commands from MainController.
- (void)showProfileSettingsFromViewController:
(UIViewController*)baseViewController {
DCHECK(!self.signinCoordinator)
<< "self.signinCoordinator: "
<< base::SysNSStringToUTF8([self.signinCoordinator description]);
if (self.settingsNavigationController) {
[self.settingsNavigationController
showProfileSettingsFromViewController:baseViewController];
return;
}
Browser* browser = self.mainInterface.browser;
self.settingsNavigationController =
[SettingsNavigationController autofillProfileControllerForBrowser:browser
delegate:self];
[baseViewController presentViewController:self.settingsNavigationController
animated:YES
completion:nil];
}
// TODO(crbug.com/41352590) : Remove show settings commands from MainController.
- (void)showCreditCardSettings {
DCHECK(!self.signinCoordinator)
<< "self.signinCoordinator: "
<< base::SysNSStringToUTF8([self.signinCoordinator description]);
if (self.settingsNavigationController) {
[self.settingsNavigationController showCreditCardSettings];
return;
}
Browser* browser = self.mainInterface.browser;
self.settingsNavigationController = [SettingsNavigationController
autofillCreditCardControllerForBrowser:browser
delegate:self];
[self.currentInterface.viewController
presentViewController:self.settingsNavigationController
animated:YES
completion:nil];
}
- (void)showCreditCardDetails:(const autofill::CreditCard*)creditCard
inEditMode:(BOOL)editMode {
UIViewController* baseViewController = self.currentInterface.viewController;
if (self.settingsNavigationController) {
[self.settingsNavigationController showCreditCardDetails:creditCard
inEditMode:editMode];
return;
}
Browser* browser = self.mainInterface.browser;
self.settingsNavigationController = [SettingsNavigationController
autofillCreditCardEditControllerForBrowser:browser
delegate:self
creditCard:creditCard
inEditMode:editMode];
[baseViewController presentViewController:self.settingsNavigationController
animated:YES
completion:nil];
}
- (void)showDefaultBrowserSettingsFromViewController:
(UIViewController*)baseViewController
sourceForUMA:
(DefaultBrowserSettingsPageSource)
source {
if (!baseViewController) {
baseViewController = self.currentInterface.viewController;
}
if (self.settingsNavigationController) {
[self.settingsNavigationController
showDefaultBrowserSettingsFromViewController:baseViewController
sourceForUMA:source];
return;
}
Browser* browser = self.mainInterface.browser;
self.settingsNavigationController =
[SettingsNavigationController defaultBrowserControllerForBrowser:browser
delegate:self
sourceForUMA:source];
[baseViewController presentViewController:self.settingsNavigationController
animated:YES
completion:nil];
}
- (void)showClearBrowsingDataSettings {
CHECK(!IsIosQuickDeleteEnabled());
UIViewController* baseViewController = self.currentInterface.viewController;
if (self.settingsNavigationController) {
[self.settingsNavigationController showClearBrowsingDataSettings];
return;
}
Browser* browser = self.mainInterface.browser;
self.settingsNavigationController = [SettingsNavigationController
clearBrowsingDataControllerForBrowser:browser
delegate:self];
[baseViewController presentViewController:self.settingsNavigationController
animated:YES
completion:nil];
}
// Displays the Safety Check (via Settings) for `referrer`.
- (void)showAndStartSafetyCheckForReferrer:
(password_manager::PasswordCheckReferrer)referrer {
UIViewController* baseViewController = self.currentInterface.viewController;
if (self.settingsNavigationController) {
[self.settingsNavigationController
showAndStartSafetyCheckForReferrer:referrer];
return;
}
Browser* browser = self.mainInterface.browser;
self.settingsNavigationController = [SettingsNavigationController
safetyCheckControllerForBrowser:browser
delegate:self
referrer:referrer];
[baseViewController presentViewController:self.settingsNavigationController
animated:YES
completion:nil];
}
// TODO(crbug.com/330562969): Remove the deprecated function and its
// invocations.
- (void)showSafeBrowsingSettings {
UIViewController* baseViewController = self.currentInterface.viewController;
[self showSafeBrowsingSettingsFromViewController:baseViewController];
}
- (void)showSafeBrowsingSettingsFromViewController:
(UIViewController*)baseViewController {
if (self.settingsNavigationController) {
[self.settingsNavigationController showSafeBrowsingSettings];
return;
}
Browser* browser = self.mainInterface.browser;
self.settingsNavigationController =
[SettingsNavigationController safeBrowsingControllerForBrowser:browser
delegate:self];
[baseViewController presentViewController:self.settingsNavigationController
animated:YES
completion:nil];
}
- (void)showSafeBrowsingSettingsFromPromoInteraction {
DCHECK(self.settingsNavigationController);
[self.settingsNavigationController
showSafeBrowsingSettingsFromPromoInteraction];
}
- (void)showPasswordSearchPage {
UIViewController* baseViewController = self.currentInterface.viewController;
if (self.settingsNavigationController) {
[self.settingsNavigationController showPasswordSearchPage];
return;
}
Browser* browser = self.mainInterface.browser;
self.settingsNavigationController = [SettingsNavigationController
passwordManagerSearchControllerForBrowser:browser
delegate:self];
[baseViewController presentViewController:self.settingsNavigationController
animated:YES
completion:nil];
}
- (void)showContentsSettingsFromViewController:
(UIViewController*)baseViewController {
if (self.settingsNavigationController) {
[self.settingsNavigationController
showContentsSettingsFromViewController:baseViewController];
return;
}
Browser* browser = self.mainInterface.browser;
self.settingsNavigationController =
[SettingsNavigationController contentSettingsControllerForBrowser:browser
delegate:self];
[baseViewController presentViewController:self.settingsNavigationController
animated:YES
completion:nil];
}
- (void)showNotificationsSettings {
UIViewController* baseViewController = self.currentInterface.viewController;
if (self.settingsNavigationController) {
[self.settingsNavigationController showNotificationsSettings];
return;
}
Browser* browser = self.mainInterface.browser;
self.settingsNavigationController = [SettingsNavigationController
notificationsSettingsControllerForBrowser:browser
delegate:self];
[baseViewController presentViewController:self.settingsNavigationController
animated:YES
completion:nil];
}
#pragma mark - SettingsNavigationControllerDelegate
- (void)closeSettings {
[self closeSettingsUI];
}
- (void)settingsWasDismissed {
// Cleanup Password Checkup after its UI was dismissed.
[self stopPasswordCheckupCoordinator];
[self.settingsNavigationController cleanUpSettings];
self.settingsNavigationController = nil;
}
#pragma mark - TabGridCoordinatorDelegate
- (void)tabGrid:(TabGridCoordinator*)tabGrid
shouldActivateBrowser:(Browser*)browser
focusOmnibox:(BOOL)focusOmnibox {
[self beginActivatingBrowser:browser focusOmnibox:focusOmnibox];
}
- (void)tabGridDismissTransitionDidEnd:(TabGridCoordinator*)tabGrid {
if (!self.sceneState.UIEnabled) {
return;
}
[self finishActivatingBrowserDismissingTabSwitcher];
}
// Begins the process of activating the given current model, switching which BVC
// is suspended if necessary. The omnibox will be focused after the tab switcher
// dismissal is completed if `focusOmnibox` is YES.
- (void)beginActivatingBrowser:(Browser*)browser
focusOmnibox:(BOOL)focusOmnibox {
DCHECK(browser == self.mainInterface.browser ||
browser == self.incognitoInterface.browser);
self.activatingBrowser = YES;
ApplicationMode mode = (browser == self.mainInterface.browser)
? ApplicationMode::NORMAL
: ApplicationMode::INCOGNITO;
[self setCurrentInterfaceForMode:mode];
// The call to set currentBVC above does not actually display the BVC, because
// _activatingBrowser is YES. So: Force the BVC transition to start.
[self displayCurrentBVCAndFocusOmnibox:focusOmnibox];
}
// Completes the process of activating the given browser. If necessary, also
// finishes dismissing the tab switcher, removing it from the
// screen and showing the appropriate BVC.
- (void)finishActivatingBrowserDismissingTabSwitcher {
// In real world devices, it is possible to have an empty tab model at the
// finishing block of a BVC presentation animation. This can happen when the
// following occur: a) There is JS that closes the last incognito tab, b) that
// JS was paused while the user was in the tab switcher, c) the user enters
// the tab, activating the JS while the tab is being presented. Effectively,
// the BVC finishes the presentation animation, but there are no tabs to
// display. The only appropriate action is to dismiss the BVC and return the
// user to the tab switcher.
if (self.currentInterface.browser &&
self.currentInterface.browser->GetWebStateList() &&
self.currentInterface.browser->GetWebStateList()->count() == 0U) {
self.activatingBrowser = NO;
self.modeToDisplayOnTabSwitcherDismissal = TabSwitcherDismissalMode::NONE;
self.NTPActionAfterTabSwitcherDismissal = NO_ACTION;
[self showTabSwitcher];
return;
}
if (self.modeToDisplayOnTabSwitcherDismissal ==
TabSwitcherDismissalMode::NORMAL) {
[self setCurrentInterfaceForMode:ApplicationMode::NORMAL];
} else if (self.modeToDisplayOnTabSwitcherDismissal ==
TabSwitcherDismissalMode::INCOGNITO) {
[self setCurrentInterfaceForMode:ApplicationMode::INCOGNITO];
}
self.activatingBrowser = NO;
self.modeToDisplayOnTabSwitcherDismissal = TabSwitcherDismissalMode::NONE;
ProceduralBlock action = [self completionBlockForTriggeringAction:
self.NTPActionAfterTabSwitcherDismissal];
self.NTPActionAfterTabSwitcherDismissal = NO_ACTION;
if (action) {
action();
}
}
#pragma mark Tab opening utility methods.
- (ProceduralBlock)completionBlockForTriggeringAction:
(TabOpeningPostOpeningAction)action {
__weak __typeof(self) weakSelf = self;
switch (action) {
case START_VOICE_SEARCH:
return ^{
[weakSelf startVoiceSearchInCurrentBVC];
};
case START_QR_CODE_SCANNER:
return ^{
[weakSelf startQRCodeScanner];
};
case START_LENS_FROM_HOME_SCREEN_WIDGET:
return ^{
[weakSelf startLensWithEntryPoint:LensEntrypoint::HomeScreenWidget];
};
case START_LENS_FROM_APP_ICON_LONG_PRESS:
return ^{
[weakSelf startLensWithEntryPoint:LensEntrypoint::AppIconLongPress];
};
case START_LENS_FROM_SPOTLIGHT:
return ^{
[weakSelf startLensWithEntryPoint:LensEntrypoint::Spotlight];
};
case START_LENS_FROM_INTENTS:
return ^{
[weakSelf startLensWithEntryPoint:LensEntrypoint::Intents];
};
case FOCUS_OMNIBOX:
return ^{
[weakSelf focusOmnibox];
};
case SHOW_DEFAULT_BROWSER_SETTINGS:
return ^{
[weakSelf showDefaultBrowserSettingsWithSourceForUMA:
DefaultBrowserSettingsPageSource::kExternalIntent];
};
case SEARCH_PASSWORDS:
return ^{
[weakSelf startPasswordSearch];
};
case OPEN_READING_LIST:
return ^{
[weakSelf openReadingList];
};
case OPEN_BOOKMARKS:
return ^{
[weakSelf openBookmarks];
};
case OPEN_RECENT_TABS:
return ^{
[weakSelf openRecentTabs];
};
case OPEN_TAB_GRID:
return ^{
[weakSelf showTabSwitcher];
};
case SET_CHROME_DEFAULT_BROWSER:
return ^{
[weakSelf showDefaultBrowserSettingsWithSourceForUMA:
DefaultBrowserSettingsPageSource::kExternalIntent];
};
case VIEW_HISTORY:
return ^{
[weakSelf showHistory];
};
case OPEN_PAYMENT_METHODS:
return ^{
[weakSelf openPaymentMethods];
};
case RUN_SAFETY_CHECK:
return ^{
[weakSelf showAndStartSafetyCheckForReferrer:
password_manager::PasswordCheckReferrer::
kSafetyCheckMagicStack];
};
case MANAGE_PASSWORDS:
return ^{
[weakSelf showPasswordSearchPage];
};
case MANAGE_SETTINGS:
return ^{
[weakSelf showSettingsFromViewController:weakSelf.currentInterface
.viewController];
};
case OPEN_LATEST_TAB:
return ^{
[weakSelf openLatestTab];
};
case OPEN_CLEAR_BROWSING_DATA_DIALOG:
return ^{
[weakSelf openClearBrowsingDataDialog];
};
case ADD_BOOKMARKS:
return ^{
[weakSelf addBookmarks:weakSelf.startupParameters.inputURLs];
};
case ADD_READING_LIST_ITEMS:
return ^{
[weakSelf addReadingListItems:weakSelf.startupParameters.inputURLs];
};
case EXTERNAL_ACTION_SHOW_BROWSER_SETTINGS:
return ^{
[weakSelf showDefaultBrowserSettingsWithSourceForUMA:
DefaultBrowserSettingsPageSource::kExternalAction];
};
default:
return nil;
}
}
// Starts a voice search on the current BVC.
- (void)startVoiceSearchInCurrentBVC {
// If the background (non-current) BVC is playing TTS audio, call
// -startVoiceSearch on it to stop the TTS.
WrangledBrowser* interface = self.mainInterface == self.currentInterface
? self.incognitoInterface
: self.mainInterface;
if (interface.playingTTS) {
[interface.bvc startVoiceSearch];
} else {
[self.currentInterface.bvc startVoiceSearch];
}
}
- (void)startQRCodeScanner {
if (!self.currentInterface.browser) {
return;
}
id<QRScannerCommands> QRHandler = HandlerForProtocol(
self.currentInterface.browser->GetCommandDispatcher(), QRScannerCommands);
[QRHandler showQRScanner];
}
- (void)startLensWithEntryPoint:(LensEntrypoint)entryPoint {
if (!self.currentInterface.browser) {
return;
}
id<LensCommands> lensHandler = HandlerForProtocol(
self.currentInterface.browser->GetCommandDispatcher(), LensCommands);
OpenLensInputSelectionCommand* command = [[OpenLensInputSelectionCommand
alloc]
initWithEntryPoint:entryPoint
presentationStyle:LensInputSelectionPresentationStyle::SlideFromRight
presentationCompletion:nil];
[lensHandler openLensInputSelection:command];
}
- (void)focusOmnibox {
if (!self.currentInterface.browser) {
return;
}
id<OmniboxCommands> omniboxCommandsHandler = HandlerForProtocol(
self.currentInterface.browser->GetCommandDispatcher(), OmniboxCommands);
[omniboxCommandsHandler focusOmnibox];
}
- (void)showDefaultBrowserSettingsWithSourceForUMA:
(DefaultBrowserSettingsPageSource)sourceForUMA {
if (!self.currentInterface.browser) {
return;
}
id<SettingsCommands> settingsHandler = HandlerForProtocol(
self.currentInterface.browser->GetCommandDispatcher(), SettingsCommands);
[settingsHandler showDefaultBrowserSettingsFromViewController:nil
sourceForUMA:sourceForUMA];
}
- (void)startPasswordSearch {
Browser* browser = self.currentInterface.browser;
if (!browser) {
return;
}
feature_engagement::Tracker* tracker =
feature_engagement::TrackerFactory::GetForBrowserState(
browser->GetBrowserState());
if (tracker) {
tracker->NotifyEvent(
feature_engagement::events::kPasswordManagerWidgetPromoUsed);
}
id<SettingsCommands> settingsHandler =
HandlerForProtocol(browser->GetCommandDispatcher(), SettingsCommands);
[settingsHandler showPasswordSearchPage];
}
- (void)openReadingList {
if (!self.currentInterface.browser) {
return;
}
id<BrowserCoordinatorCommands> browserCoordinatorCommandsHandler =
HandlerForProtocol(self.currentInterface.browser->GetCommandDispatcher(),
BrowserCoordinatorCommands);
[browserCoordinatorCommandsHandler showReadingList];
}
- (void)openBookmarks {
if (!self.currentInterface.browser) {
return;
}
id<BrowserCoordinatorCommands> browserCoordinatorCommandsHandler =
HandlerForProtocol(self.currentInterface.browser->GetCommandDispatcher(),
BrowserCoordinatorCommands);
[browserCoordinatorCommandsHandler showBookmarksManager];
}
- (void)openRecentTabs {
if (!self.currentInterface.browser) {
return;
}
id<BrowserCoordinatorCommands> browserCoordinatorCommandsHandler =
HandlerForProtocol(self.currentInterface.browser->GetCommandDispatcher(),
BrowserCoordinatorCommands);
[browserCoordinatorCommandsHandler showRecentTabs];
}
- (void)openPaymentMethods {
if (!self.currentInterface.browser) {
return;
}
id<SettingsCommands> settingsHandler = HandlerForProtocol(
self.currentInterface.browser->GetCommandDispatcher(), SettingsCommands);
[settingsHandler showCreditCardSettings];
}
- (void)openClearBrowsingDataDialog {
if (!self.currentInterface.browser) {
return;
}
id<SettingsCommands> settingsHandler = HandlerForProtocol(
self.currentInterface.browser->GetCommandDispatcher(), SettingsCommands);
[settingsHandler showClearBrowsingDataSettings];
}
- (void)openLatestTab {
WebStateList* webStateList = self.currentInterface.browser->GetWebStateList();
web::WebState* webState = StartSurfaceRecentTabBrowserAgent::FromBrowser(
self.currentInterface.browser)
->most_recent_tab();
if (!webState) {
return;
}
int index = webStateList->GetIndexOfWebState(webState);
webStateList->ActivateWebStateAt(index);
}
- (void)addBookmarks:(NSArray<NSURL*>*)URLs {
if (!self.currentInterface.browser || [URLs count] < 1) {
return;
}
id<BookmarksCommands> bookmarksCommandsHandler = HandlerForProtocol(
self.currentInterface.browser->GetCommandDispatcher(), BookmarksCommands);
[bookmarksCommandsHandler bulkCreateBookmarksWithURLs:URLs];
}
- (void)addReadingListItems:(NSArray<NSURL*>*)URLs {
if (!self.currentInterface.browser || [URLs count] < 1) {
return;
}
ReadingListBrowserAgent* readingListBrowserAgent =
ReadingListBrowserAgent::FromBrowser(self.currentInterface.browser);
readingListBrowserAgent->BulkAddURLsToReadingListWithViewSnackbar(URLs);
}
#pragma mark - TabOpening implementation.
- (void)dismissModalsAndMaybeOpenSelectedTabInMode:
(ApplicationModeForTabOpening)targetMode
withUrlLoadParams:
(const UrlLoadParams&)urlLoadParams
dismissOmnibox:(BOOL)dismissOmnibox
completion:(ProceduralBlock)completion {
// Fallback to NORMAL or INCOGNITO mode if the Incognito interstitial is not
// available.
if (targetMode == ApplicationModeForTabOpening::UNDETERMINED) {
PrefService* prefs = self.mainInterface.browserState->GetPrefs();
BOOL canShowIncognitoInterstitial =
prefs->GetBoolean(prefs::kIncognitoInterstitialEnabled);
if (!canShowIncognitoInterstitial) {
targetMode = [self isIncognitoForced]
? ApplicationModeForTabOpening::INCOGNITO
: ApplicationModeForTabOpening::NORMAL;
}
}
UrlLoadParams copyOfUrlLoadParams = urlLoadParams;
__weak SceneController* weakSelf = self;
void (^dismissModalsCompletion)() = ^{
if (targetMode == ApplicationModeForTabOpening::UNDETERMINED) {
[weakSelf showIncognitoInterstitialWithUrlLoadParams:copyOfUrlLoadParams];
completion();
} else {
[weakSelf openSelectedTabInMode:targetMode
withUrlLoadParams:copyOfUrlLoadParams
completion:completion];
}
};
// Wrap the post-dismiss-modals action with the incognito auth check.
if (targetMode == ApplicationModeForTabOpening::INCOGNITO) {
IncognitoReauthSceneAgent* reauthAgent =
[IncognitoReauthSceneAgent agentFromScene:self.sceneState];
if (reauthAgent.authenticationRequired) {
void (^wrappedDismissModalCompletion)() = dismissModalsCompletion;
dismissModalsCompletion = ^{
[reauthAgent
authenticateIncognitoContentWithCompletionBlock:^(BOOL success) {
if (success) {
wrappedDismissModalCompletion();
} else {
// Do not open the tab, but still call completion.
if (completion) {
completion();
}
}
}];
};
}
}
[self dismissModalDialogsWithCompletion:dismissModalsCompletion
dismissOmnibox:dismissOmnibox];
}
- (void)dismissModalsAndOpenMultipleTabsWithURLs:(const std::vector<GURL>&)URLs
inIncognitoMode:(BOOL)incognitoMode
dismissOmnibox:(BOOL)dismissOmnibox
completion:(ProceduralBlock)completion {
__weak SceneController* weakSelf = self;
std::vector<GURL> copyURLs = URLs;
ApplicationModeForTabOpening targetMode =
incognitoMode ? ApplicationModeForTabOpening::INCOGNITO
: ApplicationModeForTabOpening::NORMAL;
WrangledBrowser* targetInterface =
[self extractInterfaceBaseOnMode:targetMode];
web::WebState* currentWebState =
targetInterface.browser->GetWebStateList()->GetActiveWebState();
if (currentWebState) {
web::NavigationManager* navigation_manager =
currentWebState->GetNavigationManager();
// Check if the current tab is in the process of restoration and whether it
// is an NTP. If so, add the tabs-opening action to the
// RestoreCompletionCallback queue so that the tabs are opened only after
// the NTP finishes restoring. This is to avoid an edge where multiple tabs
// are trying to open in the middle of NTP restoration, as this will cause
// all tabs trying to load into the same NTP, causing a race condition that
// results in wrong behavior.
if (navigation_manager->IsRestoreSessionInProgress() &&
IsUrlNtp(currentWebState->GetVisibleURL())) {
navigation_manager->AddRestoreCompletionCallback(base::BindOnce(^{
[self
dismissModalDialogsWithCompletion:^{
[weakSelf openMultipleTabsWithURLs:copyURLs
inIncognitoMode:incognitoMode
completion:completion];
}
dismissOmnibox:dismissOmnibox];
}));
return;
}
}
[self
dismissModalDialogsWithCompletion:^{
[weakSelf openMultipleTabsWithURLs:copyURLs
inIncognitoMode:incognitoMode
completion:completion];
}
dismissOmnibox:dismissOmnibox];
}
- (void)openTabFromLaunchWithParams:(URLOpenerParams*)params
startupInformation:(id<StartupInformation>)startupInformation
appState:(AppState*)appState {
if (params) {
[URLOpener
handleLaunchOptions:params
tabOpener:self
connectionInformation:self
startupInformation:startupInformation
appState:appState
prefService:self.currentInterface.browserState->GetPrefs()];
}
}
- (BOOL)URLIsOpenedInRegularMode:(const GURL&)URL {
WebStateList* webStateList = self.mainInterface.browser->GetWebStateList();
return webStateList && webStateList->GetIndexOfWebStateWithURL(URL) !=
WebStateList::kInvalidIndex;
}
- (BOOL)shouldOpenNTPTabOnActivationOfBrowser:(Browser*)browser {
// Check if there are pending actions that would result in opening a new tab.
// In that case, it is not useful to open another tab.
for (NSUserActivity* activity in self.sceneState.connectionOptions
.userActivities) {
if (ActivityIsURLLoad(activity) || ActivityIsTabMove(activity)) {
return NO;
}
}
if (self.startupParameters) {
return NO;
}
if (self.mainCoordinator.isTabGridActive) {
Browser* mainBrowser = self.mainInterface.browser;
Browser* otrBrowser = self.incognitoInterface.browser;
// Only attempt to dismiss the tab switcher and open a new tab if:
// - there are no tabs open in either tab model, and
// - the tab switcher controller is not directly or indirectly presenting
// another view controller.
if (!(mainBrowser->GetWebStateList()->empty()) ||
!(otrBrowser->GetWebStateList()->empty())) {
return NO;
}
// If the tabSwitcher is contained, check if the parent container is
// presenting another view controller.
if ([self.mainCoordinator.baseViewController
.parentViewController presentedViewController]) {
return NO;
}
// Check if the tabSwitcher is directly presenting another view controller.
if (self.mainCoordinator.baseViewController.presentedViewController) {
return NO;
}
return YES;
}
return browser->GetWebStateList()->empty();
}
#pragma mark - SceneURLLoadingServiceDelegate
// Note that the current tab of `browserCoordinator`'s BVC will normally be
// reloaded by this method. If a new tab is about to be added, call
// expectNewForegroundTab on the BVC first to avoid extra work and possible page
// load side-effects for the tab being replaced.
- (void)setCurrentInterfaceForMode:(ApplicationMode)mode {
DCHECK(self.browserViewWrangler);
BOOL incognito = mode == ApplicationMode::INCOGNITO;
WrangledBrowser* currentInterface = self.currentInterface;
WrangledBrowser* newInterface =
incognito ? self.incognitoInterface : self.mainInterface;
if (currentInterface && currentInterface == newInterface) {
return;
}
// Update the snapshot before switching another application mode. This
// ensures that the snapshot is correct when links are opened in a different
// application mode.
[self updateActiveWebStateSnapshot];
self.browserViewWrangler.currentInterface = newInterface;
if (!self.activatingBrowser) {
[self displayCurrentBVCAndFocusOmnibox:NO];
}
// Tell the BVC that was made current that it can use the web.
[self activateBVCAndMakeCurrentBVCPrimary];
}
- (void)dismissModalDialogsWithCompletion:(ProceduralBlock)completion
dismissOmnibox:(BOOL)dismissOmnibox {
// Immediately hide modals from the provider (alert views, action sheets,
// popovers). They will be ultimately dismissed by their owners, but at least,
// they are not visible.
ios::provider::HideModalViewStack();
// Exit fullscreen mode for web page when we re-enter app through external
// intents.
web::WebState* webState =
self.mainInterface.browser->GetWebStateList()->GetActiveWebState();
if (webState && webState->IsWebPageInFullscreenMode()) {
webState->CloseMediaPresentations();
}
// ChromeIdentityService is responsible for the dialogs displayed by the
// services it wraps.
GetApplicationContext()->GetSystemIdentityManager()->DismissDialogs();
// MailtoHandlerService is responsible for the dialogs displayed by the
// services it wraps.
MailtoHandlerServiceFactory::GetForBrowserState(
self.currentInterface.browserState)
->DismissAllMailtoHandlerInterfaces();
// Then, depending on what the SSO view controller is presented on, dismiss
// it.
ProceduralBlock completionWithBVC = ^{
DCHECK(self.currentInterface.viewController);
DCHECK(!self.mainCoordinator.isTabGridActive);
DCHECK(!self.signinCoordinator)
<< "self.signinCoordinator: "
<< base::SysNSStringToUTF8([self.signinCoordinator description]);
// This will dismiss the SSO view controller.
[self.currentInterface clearPresentedStateWithCompletion:completion
dismissOmnibox:dismissOmnibox];
};
ProceduralBlock completionWithoutBVC = ^{
// `self.currentInterface.bvc` may exist but tab switcher should be
// active.
DCHECK(self.mainCoordinator.isTabGridActive);
DCHECK(!self.signinCoordinator)
<< "self.signinCoordinator: "
<< base::SysNSStringToUTF8([self.signinCoordinator description]);
// History coordinator can be started on top of the tab grid.
// This is not true of the other tab switchers.
DCHECK(self.mainCoordinator);
[self.mainCoordinator stopChildCoordinatorsWithCompletion:completion];
};
// Select a completion based on whether the BVC is shown.
ProceduralBlock chosenCompletion = self.mainCoordinator.isTabGridActive
? completionWithoutBVC
: completionWithBVC;
[self closePresentedViews:NO completion:chosenCompletion];
// Verify that no modal views are left presented.
ios::provider::LogIfModalViewsArePresented();
}
- (void)openMultipleTabsWithURLs:(const std::vector<GURL>&)URLs
inIncognitoMode:(BOOL)openInIncognito
completion:(ProceduralBlock)completion {
[self recursiveOpenURLs:URLs
inIncognitoMode:openInIncognito
currentIndex:0
totalCount:URLs.size()
completion:completion];
}
// Call `dismissModalsAndMaybeOpenSelectedTabInMode` recursively to open the
// list of URLs contained in `URLs`. Achieved through chaining
// `dismissModalsAndMaybeOpenSelectedTabInMode` in its completion handler.
- (void)recursiveOpenURLs:(const std::vector<GURL>&)URLs
inIncognitoMode:(BOOL)incognitoMode
currentIndex:(size_t)currentIndex
totalCount:(size_t)totalCount
completion:(ProceduralBlock)completion {
if (currentIndex >= totalCount) {
if (completion) {
completion();
}
return;
}
GURL webpageGURL = URLs.at(currentIndex);
__weak SceneController* weakSelf = self;
if (!webpageGURL.is_valid()) {
[self recursiveOpenURLs:URLs
inIncognitoMode:incognitoMode
currentIndex:(currentIndex + 1)
totalCount:totalCount
completion:completion];
return;
}
UrlLoadParams param = UrlLoadParams::InNewTab(webpageGURL, webpageGURL);
std::vector<GURL> copyURLs = URLs;
ApplicationModeForTabOpening mode =
incognitoMode ? ApplicationModeForTabOpening::INCOGNITO
: ApplicationModeForTabOpening::NORMAL;
[self
dismissModalsAndMaybeOpenSelectedTabInMode:mode
withUrlLoadParams:param
dismissOmnibox:YES
completion:^{
[weakSelf
recursiveOpenURLs:copyURLs
inIncognitoMode:incognitoMode
currentIndex:(currentIndex + 1)
totalCount:totalCount
completion:completion];
}];
}
// Opens a tab in the target BVC, and switches to it in a way that's appropriate
// to the current UI, based on the `dismissModals` flag:
// - If a modal dialog is showing and `dismissModals` is NO, the selected tab of
// the main tab model will change in the background, but the view won't change.
// - Otherwise, any modal view will be dismissed, the tab switcher will animate
// out if it is showing, the target BVC will become active, and the new tab will
// be shown.
// If the current tab in `targetMode` is a NTP, it can be reused to open URL.
// `completion` is executed after the tab is opened. After Tab is open the
// virtual URL is set to the pending navigation item.
- (void)openSelectedTabInMode:(ApplicationModeForTabOpening)tabOpeningTargetMode
withUrlLoadParams:(const UrlLoadParams&)urlLoadParams
completion:(ProceduralBlock)completion {
DCHECK(tabOpeningTargetMode != ApplicationModeForTabOpening::UNDETERMINED);
// Update the snapshot before opening a new tab. This ensures that the
// snapshot is correct when tabs are openned via the dispatcher.
[self updateActiveWebStateSnapshot];
ApplicationMode targetMode;
if (tabOpeningTargetMode == ApplicationModeForTabOpening::CURRENT) {
targetMode = self.currentInterface.incognito ? ApplicationMode::INCOGNITO
: ApplicationMode::NORMAL;
} else if (tabOpeningTargetMode == ApplicationModeForTabOpening::NORMAL) {
targetMode = ApplicationMode::NORMAL;
} else {
targetMode = ApplicationMode::INCOGNITO;
}
WrangledBrowser* targetInterface = targetMode == ApplicationMode::NORMAL
? self.mainInterface
: self.incognitoInterface;
ProceduralBlock startupCompletion =
[self completionBlockForTriggeringAction:[self.startupParameters
postOpeningAction]];
ProceduralBlock tabOpenedCompletion = nil;
if (startupCompletion && completion) {
tabOpenedCompletion = ^{
// Order is important here. `completion` may do cleaning tasks that will
// invalidate `startupCompletion`.
startupCompletion();
completion();
};
} else if (startupCompletion) {
tabOpenedCompletion = startupCompletion;
} else {
tabOpenedCompletion = completion;
}
if (self.mainCoordinator.isTabGridActive) {
// If the tab switcher is already being dismissed, simply add the tab and
// note that when the tab switcher finishes dismissing, the current BVC
// should be switched to be the main BVC if necessary.
if (self.activatingBrowser) {
self.modeToDisplayOnTabSwitcherDismissal =
targetMode == ApplicationMode::NORMAL
? TabSwitcherDismissalMode::NORMAL
: TabSwitcherDismissalMode::INCOGNITO;
[targetInterface.bvc appendTabAddedCompletion:tabOpenedCompletion];
UrlLoadParams savedParams = urlLoadParams;
savedParams.in_incognito = targetMode == ApplicationMode::INCOGNITO;
UrlLoadingBrowserAgent::FromBrowser(targetInterface.browser)
->Load(savedParams);
} else {
// Voice search, QRScanner, Lens, and the omnibox are presented by the
// BVC. They must be started after the BVC view is added in the
// hierarchy.
self.NTPActionAfterTabSwitcherDismissal =
[self.startupParameters postOpeningAction];
[self setStartupParameters:nil];
UrlLoadParams paramsToLoad = urlLoadParams;
// If the url to load is empty (such as with Lens) open a new tab page.
if (urlLoadParams.web_params.url.is_empty()) {
paramsToLoad = UrlLoadParams(urlLoadParams);
paramsToLoad.web_params.url = GURL(kChromeUINewTabURL);
}
[self addANewTabAndPresentBrowser:targetInterface.browser
withURLLoadParams:paramsToLoad];
// In this particular usage, there should be no postOpeningAction,
// as triggering voice search while there are multiple windows opened is
// probably a bad idea both technically and as a user experience. It
// should be the caller duty to not set a completion if they don't need
// it.
if (completion) {
completion();
}
}
} else {
if (!self.currentInterface.viewController.presentedViewController) {
PagePlaceholderBrowserAgent* pagePlaceholderBrowserAgent =
PagePlaceholderBrowserAgent::FromBrowser(targetInterface.browser);
pagePlaceholderBrowserAgent->ExpectNewForegroundTab();
}
[self setCurrentInterfaceForMode:targetMode];
[self openOrReuseTabInMode:targetMode
withUrlLoadParams:urlLoadParams
tabOpenedCompletion:tabOpenedCompletion];
}
}
- (void)expectNewForegroundTabForMode:(ApplicationMode)targetMode {
WrangledBrowser* interface = targetMode == ApplicationMode::INCOGNITO
? self.incognitoInterface
: self.mainInterface;
DCHECK(interface);
PagePlaceholderBrowserAgent* pagePlaceholderBrowserAgent =
PagePlaceholderBrowserAgent::FromBrowser(interface.browser);
pagePlaceholderBrowserAgent->ExpectNewForegroundTab();
}
- (void)openNewTabFromOriginPoint:(CGPoint)originPoint
focusOmnibox:(BOOL)focusOmnibox
inheritOpener:(BOOL)inheritOpener {
[self.currentInterface.bvc openNewTabFromOriginPoint:originPoint
focusOmnibox:focusOmnibox
inheritOpener:inheritOpener];
}
- (Browser*)currentBrowserForURLLoading {
return self.currentInterface.browser;
}
// Asks the respective Snapshot helper to update the snapshot for the active
// WebState.
- (void)updateActiveWebStateSnapshot {
// Durinhg startup, there may be no current interface. Do nothing in that
// case.
if (!self.currentInterface) {
return;
}
WebStateList* webStateList = self.currentInterface.browser->GetWebStateList();
web::WebState* webState = webStateList->GetActiveWebState();
if (webState) {
SnapshotTabHelper::FromWebState(webState)->UpdateSnapshotWithCallback(nil);
}
}
// Checks the target BVC's current tab's URL. If `urlLoadParams` has an empty
// URL, no new tab will be opened and `tabOpenedCompletion` will be run. If this
// URL is chrome://newtab, loads `urlLoadParams` in this tab. Otherwise, open
// `urlLoadParams` in a new tab in the target BVC. `tabOpenedCompletion` will be
// called on the new tab (if not nil).
- (void)openOrReuseTabInMode:(ApplicationMode)targetMode
withUrlLoadParams:(const UrlLoadParams&)urlLoadParams
tabOpenedCompletion:(ProceduralBlock)tabOpenedCompletion {
WrangledBrowser* targetInterface = targetMode == ApplicationMode::NORMAL
? self.mainInterface
: self.incognitoInterface;
// If the url to load is empty, create a new tab if no tabs are open and run
// the completion.
if (urlLoadParams.web_params.url.is_empty()) {
if (tabOpenedCompletion) {
tabOpenedCompletion();
}
return;
}
BrowserViewController* targetBVC = targetInterface.bvc;
web::WebState* currentWebState =
targetInterface.browser->GetWebStateList()->GetActiveWebState();
BOOL alwaysInsertNewTab =
base::FeatureList::IsEnabled(kForceNewTabForIntentSearch) &&
(self.startupParameters.postOpeningAction == FOCUS_OMNIBOX);
// Don't call loadWithParams for chrome://newtab when it's already loaded.
// Note that it's safe to use -GetVisibleURL here, as it doesn't matter if the
// NTP hasn't finished loading.
if (!alwaysInsertNewTab && currentWebState &&
IsUrlNtp(currentWebState->GetVisibleURL()) &&
IsUrlNtp(urlLoadParams.web_params.url)) {
if (tabOpenedCompletion) {
tabOpenedCompletion();
}
return;
}
if (urlLoadParams.disposition == WindowOpenDisposition::SWITCH_TO_TAB) {
// Check if it's already the displayed tab and no switch is necessary
if (currentWebState &&
currentWebState->GetVisibleURL() == urlLoadParams.web_params.url) {
if (tabOpenedCompletion) {
tabOpenedCompletion();
}
return;
}
// Check if this tab exists in this web state list.
// If not, fall back to opening a new tab instead.
if (targetInterface.browser->GetWebStateList()->GetIndexOfWebStateWithURL(
urlLoadParams.web_params.url) != WebStateList::kInvalidIndex) {
UrlLoadingBrowserAgent::FromBrowser(targetInterface.browser)
->Load(urlLoadParams);
if (tabOpenedCompletion) {
tabOpenedCompletion();
}
return;
}
}
// If the current tab isn't an NTP, open a new tab. Be sure to use
// -GetLastCommittedURL incase the NTP is still loading.
if (alwaysInsertNewTab ||
!(currentWebState && IsUrlNtp(currentWebState->GetVisibleURL()))) {
[targetBVC appendTabAddedCompletion:tabOpenedCompletion];
UrlLoadParams newTabParams = urlLoadParams;
newTabParams.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
newTabParams.in_incognito = targetMode == ApplicationMode::INCOGNITO;
UrlLoadingBrowserAgent::FromBrowser(targetInterface.browser)
->Load(newTabParams);
return;
}
// Otherwise, load `urlLoadParams` in the current tab.
UrlLoadParams sameTabParams = urlLoadParams;
sameTabParams.disposition = WindowOpenDisposition::CURRENT_TAB;
UrlLoadingBrowserAgent::FromBrowser(targetInterface.browser)
->Load(sameTabParams);
if (tabOpenedCompletion) {
tabOpenedCompletion();
}
}
// Displays current (incognito/normal) BVC and optionally focuses the omnibox.
- (void)displayCurrentBVCAndFocusOmnibox:(BOOL)focusOmnibox {
ProceduralBlock completion = nil;
if (focusOmnibox) {
id<OmniboxCommands> omniboxHandler = HandlerForProtocol(
self.currentInterface.browser->GetCommandDispatcher(), OmniboxCommands);
completion = ^{
[omniboxHandler focusOmnibox];
};
}
[self.mainCoordinator
showTabViewController:self.currentInterface.viewController
incognito:self.currentInterface.incognito
completion:completion];
[HandlerForProtocol(self.currentInterface.browser->GetCommandDispatcher(),
ApplicationCommands)
setIncognitoContentVisible:self.currentInterface.incognito];
}
#pragma mark - Sign In UI presentation
// Show trusted vault dialog.
// `intent` Dialog to present.
// `trigger` UI elements where the trusted vault reauth has been triggered.
- (void)
showTrustedVaultDialogFromViewController:(UIViewController*)viewController
intent:
(SigninTrustedVaultDialogIntent)intent
securityDomainID:(trusted_vault::SecurityDomainId)
securityDomainID
trigger:
(syncer::
TrustedVaultUserActionTriggerForUMA)
trigger
accessPoint:
(signin_metrics::AccessPoint)accessPoint {
DCHECK(!self.signinCoordinator)
<< "self.signinCoordinator: "
<< base::SysNSStringToUTF8([self.signinCoordinator description]);
Browser* mainBrowser = self.mainInterface.browser;
self.signinCoordinator = [SigninCoordinator
trustedVaultReAuthenticationCoordinatorWithBaseViewController:
viewController
browser:mainBrowser
intent:intent
securityDomainID:
securityDomainID
trigger:trigger
accessPoint:
accessPoint];
[self startSigninCoordinatorWithCompletion:nil];
}
// Close Settings, or Signin or the 3rd-party intents Incognito interstitial.
- (void)closePresentedViews:(BOOL)animated
completion:(ProceduralBlock)completion {
// If the Incognito interstitial is active, stop it.
[self.incognitoInterstitialCoordinator stop];
self.incognitoInterstitialCoordinator = nil;
__weak __typeof(self) weakSelf = self;
BOOL resetSigninState = self.signinCoordinator != nil;
completion = ^{
__typeof(self) strongSelf = weakSelf;
// Cleanup settings resources after dismissal.
[strongSelf settingsWasDismissed];
if (completion) {
completion();
}
if (resetSigninState) {
strongSelf.sceneState.signinInProgress = NO;
}
strongSelf.dismissingSigninPromptFromExternalTrigger = NO;
};
if (self.settingsNavigationController && !self.dismissingSettings) {
self.dismissingSettings = YES;
// Store a reference to the presentingViewController in case the user
// is dismissing the Signin screen and then dismisses Settings before
// the Signin screen is done animating, which will delay the execution of
// the `dismissSettings` block stopping the code from accessing
// the `presentingViewController` property.
__weak UIViewController* weakPresentingViewController =
[self.settingsNavigationController presentingViewController];
ProceduralBlock dismissSettings = ^() {
UIViewController* strongPresentingViewController =
weakPresentingViewController;
if (strongPresentingViewController) {
[strongPresentingViewController
dismissViewControllerAnimated:animated
completion:completion];
} else {
// The view is already dismissed. Completion should still be called.
completion();
}
weakSelf.dismissingSettings = NO;
};
// `self.signinCoordinator` can be presented on top of the settings, to
// present the Trusted Vault reauthentication `self.signinCoordinator` has
// to be closed first.
if (self.signinCoordinator) {
// If signinCoordinator is already dismissing, completion execution will
// happen when it is done animating.
[self interruptSigninCoordinatorAnimated:animated
completion:dismissSettings];
} else {
dismissSettings();
}
} else if (self.signinCoordinator) {
// `self.signinCoordinator` can be presented without settings, from the
// bookmarks or the recent tabs view.
[self interruptSigninCoordinatorAnimated:animated completion:completion];
} else {
completion();
}
}
- (UIViewController*)topPresentedViewController {
// TODO(crbug.com/40534720): Implement TopPresentedViewControllerFrom()
// privately.
return top_view_controller::TopPresentedViewControllerFrom(
self.mainCoordinator.baseViewController);
}
// Interrupts the sign-in coordinator actions and dismisses its views either
// with or without animation.
- (void)interruptSigninCoordinatorAnimated:(BOOL)animated
completion:(ProceduralBlock)completion {
DCHECK(self.signinCoordinator);
SigninCoordinatorInterrupt action =
animated ? SigninCoordinatorInterrupt::DismissWithAnimation
: SigninCoordinatorInterrupt::DismissWithoutAnimation;
self.dismissingSigninPromptFromExternalTrigger = YES;
[self.signinCoordinator interruptWithAction:action completion:completion];
}
// Starts the sign-in coordinator with a default cleanup completion.
- (void)startSigninCoordinatorWithCompletion:
(ShowSigninCommandCompletionCallback)completion {
DCHECK(self.signinCoordinator);
AuthenticationService* authenticationService =
AuthenticationServiceFactory::GetForBrowserState(
self.sceneState.browserProviderInterface.mainBrowserProvider.browser
->GetBrowserState());
AuthenticationService::ServiceStatus statusService =
authenticationService->GetServiceStatus();
switch (statusService) {
case AuthenticationService::ServiceStatus::SigninDisabledByPolicy: {
if (completion) {
completion(SigninCoordinatorResultDisabled, nil);
}
[self.signinCoordinator stop];
id<PolicyChangeCommands> handler = HandlerForProtocol(
self.signinCoordinator.browser->GetCommandDispatcher(),
PolicyChangeCommands);
[handler showForceSignedOutPrompt];
self.signinCoordinator = nil;
return;
}
case AuthenticationService::ServiceStatus::SigninForcedByPolicy:
case AuthenticationService::ServiceStatus::SigninAllowed: {
break;
}
case AuthenticationService::ServiceStatus::SigninDisabledByInternal:
case AuthenticationService::ServiceStatus::SigninDisabledByUser: {
DUMP_WILL_BE_NOTREACHED()
<< "Status service: " << static_cast<int>(statusService);
break;
}
}
DCHECK(self.signinCoordinator);
self.sceneState.signinInProgress = YES;
__block std::unique_ptr<ScopedUIBlocker> uiBlocker =
std::make_unique<ScopedUIBlocker>(self.sceneState);
__weak SceneController* weakSelf = self;
self.signinCoordinator.signinCompletion =
^(SigninCoordinatorResult result, SigninCompletionInfo* info) {
if (!weakSelf) {
return;
}
__typeof(self) strongSelf = weakSelf;
[strongSelf.signinCoordinator stop];
strongSelf.signinCoordinator = nil;
uiBlocker.reset();
if (completion) {
completion(result, info);
}
if (!weakSelf.dismissingSigninPromptFromExternalTrigger) {
// If the coordinator isn't stopped by an external trigger, sign-in
// is done. Otherwise, there might be extra steps to be done before
// considering sign-in as done. This is up to the handler that sets
// `self.dismissingSigninPromptFromExternalTrigger` to YES to set
// back `signinInProgress` to NO.
weakSelf.sceneState.signinInProgress = NO;
}
switch (info.signinCompletionAction) {
case SigninCompletionActionNone:
break;
case SigninCompletionActionShowManagedLearnMore:
id<ApplicationCommands> dispatcher = HandlerForProtocol(
strongSelf.mainInterface.browser->GetCommandDispatcher(),
ApplicationCommands);
OpenNewTabCommand* command = [OpenNewTabCommand
commandWithURLFromChrome:GURL(kChromeUIManagementURL)];
[dispatcher closeSettingsUIAndOpenURL:command];
break;
}
if (IsSigninForcedByPolicy()) {
// Handle intents after sign-in is done when the forced sign-in policy
// is enabled.
[strongSelf handleExternalIntents];
}
};
[self.signinCoordinator start];
}
#pragma mark - WebStateListObserving
- (void)didChangeWebStateList:(WebStateList*)webStateList
change:(const WebStateListChange&)change
status:(const WebStateListStatus&)status {
switch (change.type()) {
case WebStateListChange::Type::kStatusOnly:
// Do nothing when a WebState is selected and its status is updated.
break;
case WebStateListChange::Type::kDetach: {
// Do nothing during batch operation.
if (webStateList->IsBatchInProgress()) {
break;
}
if (webStateList->empty()) {
[self onLastWebStateClosedForWebStateList:webStateList];
}
break;
}
case WebStateListChange::Type::kMove:
// Do nothing when a WebState is moved.
break;
case WebStateListChange::Type::kReplace:
// Do nothing when a WebState is replaced.
break;
case WebStateListChange::Type::kInsert:
// Do nothing when a WebState is inserted.
break;
case WebStateListChange::Type::kGroupCreate:
// Do nothing when a group is created.
break;
case WebStateListChange::Type::kGroupVisualDataUpdate:
// Do nothing when a tab group's visual data are updated.
break;
case WebStateListChange::Type::kGroupMove:
// Do nothing when a tab group is moved.
break;
case WebStateListChange::Type::kGroupDelete:
// Do nothing when a group is deleted.
break;
}
}
- (void)webStateListWillBeginBatchOperation:(WebStateList*)webStateList {
_tabCountBeforeBatchOperation.insert(
std::make_pair(webStateList, webStateList->count()));
}
- (void)webStateListBatchOperationEnded:(WebStateList*)webStateList {
auto iter = _tabCountBeforeBatchOperation.find(webStateList);
DCHECK(iter != _tabCountBeforeBatchOperation.end());
// Triggers the switcher view if the list is empty and at least one tab
// was closed (i.e. it was not empty before the batch operation).
if (webStateList->empty() && iter->second != 0) {
[self onLastWebStateClosedForWebStateList:webStateList];
}
_tabCountBeforeBatchOperation.erase(iter);
}
#pragma mark - Private methods
// Triggers the switcher view when the last WebState is closed on a device
// that uses the switcher.
- (void)onLastWebStateClosedForWebStateList:(WebStateList*)webStateList {
DCHECK(webStateList->empty());
if (webStateList == self.incognitoInterface.browser->GetWebStateList()) {
[self lastIncognitoTabClosed];
} else if (webStateList == self.mainInterface.browser->GetWebStateList()) {
[self lastRegularTabClosed];
}
}
// Open a non-incognito tab, if one exists. If one doesn't exist, open a new
// one. If incognito is forced, an incognito tab will be opened.
- (void)openNonIncognitoTab:(ProceduralBlock)completion {
if (self.mainInterface.browser->GetWebStateList()->GetActiveWebState()) {
// Reuse an existing tab, if one exists.
ApplicationMode mode = [self isIncognitoForced] ? ApplicationMode::INCOGNITO
: ApplicationMode::NORMAL;
[self setCurrentInterfaceForMode:mode];
if (self.mainCoordinator.isTabGridActive) {
[self.mainCoordinator
showTabViewController:self.currentInterface.viewController
incognito:self.currentInterface.incognito
completion:completion];
[self setIncognitoContentVisible:self.currentInterface.incognito];
} else {
if (completion) {
completion();
}
}
} else {
// Open a new NTP.
UrlLoadParams params = UrlLoadParams::InNewTab(GURL(kChromeUINewTabURL));
params.web_params.transition_type = ui::PAGE_TRANSITION_TYPED;
ApplicationModeForTabOpening mode =
[self isIncognitoForced] ? ApplicationModeForTabOpening::INCOGNITO
: ApplicationModeForTabOpening::NORMAL;
[self dismissModalsAndMaybeOpenSelectedTabInMode:mode
withUrlLoadParams:params
dismissOmnibox:YES
completion:completion];
}
}
// Ensures that a non-incognito NTP tab is open. If incognito is forced, then
// it will ensure an incognito NTP tab is open.
- (void)ensureNTP {
// If the tab does not exist, open a new tab.
UrlLoadParams params = UrlLoadParams::InCurrentTab(GURL(kChromeUINewTabURL));
ApplicationMode mode = self.currentInterface.incognito
? ApplicationMode::INCOGNITO
: ApplicationMode::NORMAL;
[self openOrReuseTabInMode:mode
withUrlLoadParams:params
tabOpenedCompletion:nil];
}
// Stops and deletes `passwordCheckupCoordinator`.
- (void)stopPasswordCheckupCoordinator {
[self.passwordCheckupCoordinator stop];
self.passwordCheckupCoordinator.delegate = nil;
self.passwordCheckupCoordinator = nil;
}
#pragma mark - IncognitoInterstitialCoordinatorDelegate
- (void)shouldStopIncognitoInterstitial:
(IncognitoInterstitialCoordinator*)incognitoInterstitial {
DCHECK(incognitoInterstitial == self.incognitoInterstitialCoordinator);
[self closePresentedViews:YES completion:nil];
}
#pragma mark - PasswordCheckupCoordinatorDelegate
- (void)passwordCheckupCoordinatorDidRemove:
(PasswordCheckupCoordinator*)coordinator {
DCHECK_EQ(self.passwordCheckupCoordinator, coordinator);
[self stopPasswordCheckupCoordinator];
}
#pragma mark - PasswordManagerReauthenticationDelegate
- (void)dismissPasswordManagerAfterFailedReauthentication {
[self closeSettingsUI];
}
#pragma mark - Helpers for web state list events
// Called when the last incognito tab was closed.
- (void)lastIncognitoTabClosed {
// If no other window has incognito tab, then destroy and rebuild the
// BrowserState. Otherwise, just do the state transition animation.
if ([self shouldDestroyAndRebuildIncognitoBrowserState]) {
// Incognito browser state cannot be deleted before all the requests are
// deleted. Queue empty task on IO thread and destroy the BrowserState
// when the task has executed, again verifying that no incognito tabs are
// present. When an incognito tab is moved between browsers, there is
// a point where the tab isn't attached to any web state list. However, when
// this queued cleanup step executes, the moved tab will be attached, so
// the cleanup shouldn't proceed.
auto cleanup = ^{
if ([self shouldDestroyAndRebuildIncognitoBrowserState]) {
[self destroyAndRebuildIncognitoBrowserState];
}
};
web::GetIOThreadTaskRunner({})->PostTaskAndReply(
FROM_HERE, base::DoNothing(), base::BindRepeating(cleanup));
}
// a) The first condition can happen when the last incognito tab is closed
// from the tab switcher.
// b) The second condition can happen if some other code (like JS) triggers
// closure of tabs from the otr tab model when it's not current.
// Nothing to do here. The next user action (like clicking on an existing
// regular tab or creating a new incognito tab from the settings menu) will
// take care of the logic to mode switch.
if (self.mainCoordinator.isTabGridActive ||
!self.currentInterface.incognito) {
return;
}
[self showTabSwitcher];
}
// Called when the last regular tab was closed.
- (void)lastRegularTabClosed {
// a) The first condition can happen when the last regular tab is closed from
// the tab switcher.
// b) The second condition can happen if some other code (like JS) triggers
// closure of tabs from the main tab model when the main tab model is not
// current.
// Nothing to do here.
if (self.mainCoordinator.isTabGridActive || self.currentInterface.incognito) {
return;
}
[self showTabSwitcher];
}
// Clears incognito data that is specific to iOS and won't be cleared by
// deleting the browser state.
- (void)clearIOSSpecificIncognitoData {
DCHECK(self.sceneState.browserProviderInterface.mainBrowserProvider.browser
->GetBrowserState()
->HasOffTheRecordChromeBrowserState());
ChromeBrowserState* otrBrowserState =
self.sceneState.browserProviderInterface.mainBrowserProvider.browser
->GetBrowserState()
->GetOffTheRecordChromeBrowserState();
__weak SceneController* weakSelf = self;
BrowsingDataRemover* browsingDataRemover =
BrowsingDataRemoverFactory::GetForBrowserState(otrBrowserState);
browsingDataRemover->Remove(browsing_data::TimePeriod::ALL_TIME,
BrowsingDataRemoveMask::REMOVE_ALL,
base::BindOnce(^{
[weakSelf activateBVCAndMakeCurrentBVCPrimary];
}));
}
- (void)activateBVCAndMakeCurrentBVCPrimary {
// If there are pending removal operations, the activation will be deferred
// until the callback is received.
BrowsingDataRemover* browsingDataRemover =
BrowsingDataRemoverFactory::GetForBrowserStateIfExists(
self.currentInterface.browserState);
if (browsingDataRemover && browsingDataRemover->IsRemoving()) {
return;
}
WebUsageEnablerBrowserAgent::FromBrowser(self.mainInterface.browser)
->SetWebUsageEnabled(true);
WebUsageEnablerBrowserAgent::FromBrowser(self.incognitoInterface.browser)
->SetWebUsageEnabled(true);
if (self.currentInterface) {
TabUsageRecorderBrowserAgent* tabUsageRecorder =
TabUsageRecorderBrowserAgent::FromBrowser(
self.currentInterface.browser);
if (tabUsageRecorder) {
tabUsageRecorder->RecordPrimaryBrowserChange(true);
}
}
}
// Shows the tab switcher UI.
- (void)showTabSwitcher {
DCHECK(self.mainCoordinator);
[self.mainCoordinator setActiveMode:TabGridMode::kNormal];
TabGridPage page =
(self.currentInterface.browser == self.incognitoInterface.browser)
? TabGridPageIncognitoTabs
: TabGridPageRegularTabs;
[self.mainCoordinator showTabGridPage:page];
}
- (void)openURLContexts:(NSSet<UIOpenURLContext*>*)URLContexts {
if (self.sceneState.appState.initStage <= InitStageNormalUI ||
!self.currentInterface.browserState) {
// Don't handle the intent if the browser UI objects aren't yet initialized.
// This is the case when the app is in safe mode or may be the case when the
// app is going through an odd sequence of lifecyle events (shouldn't happen
// but happens somehow), see crbug.com/1211006 for more details.
return;
}
NSMutableSet<URLOpenerParams*>* URLsToOpen = [[NSMutableSet alloc] init];
for (UIOpenURLContext* context : URLContexts) {
URLOpenerParams* options =
[[URLOpenerParams alloc] initWithUIOpenURLContext:context];
NSSet* URLContextSet = [NSSet setWithObject:context];
if (!GetApplicationContext()
->GetSystemIdentityManager()
->HandleSessionOpenURLContexts(self.sceneState.scene,
URLContextSet)) {
[URLsToOpen addObject:options];
}
}
// When opening with URLs for GetChromeIdentityService, it is expected that a
// single URL is passed.
DCHECK(URLsToOpen.count == URLContexts.count || URLContexts.count == 1);
BOOL active = [self canHandleIntents];
for (URLOpenerParams* options : URLsToOpen) {
[URLOpener openURL:options
applicationActive:active
tabOpener:self
connectionInformation:self
startupInformation:self.sceneState.appState.startupInformation
prefService:self.currentInterface.browserState->GetPrefs()
initStage:self.sceneState.appState.initStage];
}
}
- (WrangledBrowser*)extractInterfaceBaseOnMode:
(ApplicationModeForTabOpening)targetMode {
DCHECK(targetMode != ApplicationModeForTabOpening::UNDETERMINED);
ApplicationMode applicationMode;
if (targetMode == ApplicationModeForTabOpening::CURRENT) {
applicationMode = self.currentInterface.incognito
? ApplicationMode::INCOGNITO
: ApplicationMode::NORMAL;
} else if (targetMode == ApplicationModeForTabOpening::NORMAL) {
applicationMode = ApplicationMode::NORMAL;
} else {
applicationMode = ApplicationMode::INCOGNITO;
}
WrangledBrowser* targetInterface = applicationMode == ApplicationMode::NORMAL
? self.mainInterface
: self.incognitoInterface;
return targetInterface;
}
#pragma mark - TabGrid helpers
// Adds a new tab to the `browser` based on `urlLoadParams` and then presents
// it.
- (void)addANewTabAndPresentBrowser:(Browser*)browser
withURLLoadParams:(const UrlLoadParams&)urlLoadParams {
TabInsertion::Params tabInsertionParams;
tabInsertionParams.should_skip_new_tab_animation =
urlLoadParams.from_external;
TabInsertionBrowserAgent::FromBrowser(browser)->InsertWebState(
urlLoadParams.web_params, tabInsertionParams);
[self beginActivatingBrowser:browser focusOmnibox:NO];
}
#pragma mark - Handling of destroying the incognito BrowserState
// The incognito BrowserState should be closed when the last incognito tab is
// closed (i.e. if there are other incognito tabs open in another Scene, the
// BrowserState must not be destroyed).
- (BOOL)shouldDestroyAndRebuildIncognitoBrowserState {
ChromeBrowserState* mainBrowserState =
self.sceneState.browserProviderInterface.mainBrowserProvider.browser
->GetBrowserState();
if (!mainBrowserState->HasOffTheRecordChromeBrowserState()) {
return NO;
}
ChromeBrowserState* otrBrowserState =
mainBrowserState->GetOffTheRecordChromeBrowserState();
DCHECK(otrBrowserState);
BrowserList* browserList =
BrowserListFactory::GetForBrowserState(otrBrowserState);
for (Browser* browser :
browserList->BrowsersOfType(BrowserList::BrowserType::kIncognito)) {
WebStateList* webStateList = browser->GetWebStateList();
if (!webStateList->empty()) {
return NO;
}
}
return YES;
}
// Destroys and rebuilds the incognito BrowserState. This will inform all the
// other SceneController to destroy state tied to the BrowserState and to
// recreate it.
- (void)destroyAndRebuildIncognitoBrowserState {
// This seems the best place to mark the start of destroying the incognito
// browser state.
crash_keys::SetDestroyingAndRebuildingIncognitoBrowserState(
/*in_progress=*/true);
[self clearIOSSpecificIncognitoData];
ChromeBrowserState* mainBrowserState =
self.sceneState.browserProviderInterface.mainBrowserProvider.browser
->GetBrowserState();
DCHECK(mainBrowserState->HasOffTheRecordChromeBrowserState());
ChromeBrowserState* otrBrowserState =
mainBrowserState->GetOffTheRecordChromeBrowserState();
NSMutableArray<SceneController*>* sceneControllers =
[[NSMutableArray alloc] init];
for (SceneState* sceneState in [self.sceneState.appState connectedScenes]) {
SceneController* sceneController = sceneState.controller;
// In some circumstances, the scene state may still exist while the
// corresponding scene controller has been deallocated.
// (see crbug.com/1142782).
if (sceneController) {
[sceneControllers addObject:sceneController];
}
}
for (SceneController* sceneController in sceneControllers) {
[sceneController willDestroyIncognitoBrowserState];
}
// Record off-the-record metrics before detroying the BrowserState.
SessionMetrics::FromBrowserState(otrBrowserState)
->RecordAndClearSessionMetrics(MetricsToRecordFlags::kNoMetrics);
// Destroy and recreate the off-the-record BrowserState.
mainBrowserState->DestroyOffTheRecordChromeBrowserState();
mainBrowserState->GetOffTheRecordChromeBrowserState();
for (SceneController* sceneController in sceneControllers) {
[sceneController incognitoBrowserStateCreated];
}
// This seems the best place to deem the destroying and rebuilding the
// incognito browser state to be completed.
crash_keys::SetDestroyingAndRebuildingIncognitoBrowserState(
/*in_progress=*/false);
}
- (void)willDestroyIncognitoBrowserState {
// Clear the Incognito Browser and notify the TabGrid that its otrBrowser
// will be destroyed.
self.mainCoordinator.incognitoBrowser = nil;
if (breadcrumbs::IsEnabled(GetApplicationContext()->GetLocalState())) {
BreadcrumbManagerBrowserAgent::FromBrowser(self.incognitoInterface.browser)
->SetLoggingEnabled(false);
}
_incognitoWebStateObserver.reset();
[self.browserViewWrangler willDestroyIncognitoBrowserState];
}
- (void)incognitoBrowserStateCreated {
[self.browserViewWrangler incognitoBrowserStateCreated];
// There should be a new URL loading browser agent for the incognito browser,
// so set the scene URL loading service on it.
UrlLoadingBrowserAgent::FromBrowser(self.incognitoInterface.browser)
->SetSceneService(_sceneURLLoadingService.get());
_incognitoWebStateObserver = std::make_unique<
base::ScopedObservation<WebStateList, WebStateListObserverBridge>>(
_webStateListForwardingObserver.get());
_incognitoWebStateObserver->Observe(
self.incognitoInterface.browser->GetWebStateList());
if (self.currentInterface.incognito) {
[self activateBVCAndMakeCurrentBVCPrimary];
}
// Always set the new otr Browser for the tablet or grid switcher.
// Notify the TabGrid with the new Incognito Browser.
self.mainCoordinator.incognitoBrowser = self.incognitoInterface.browser;
}
#pragma mark - PolicyWatcherBrowserAgentObserving
- (void)policyWatcherBrowserAgentNotifySignInDisabled:
(PolicyWatcherBrowserAgent*)policyWatcher {
auto signinInterrupted = ^{
policyWatcher->SignInUIDismissed();
};
if (self.signinCoordinator) {
[self interruptSigninCoordinatorAnimated:YES completion:signinInterrupted];
UMA_HISTOGRAM_BOOLEAN(
"Enterprise.BrowserSigninIOS.SignInInterruptedByPolicy", true);
}
}
#pragma mark - SceneUIProvider
- (UIViewController*)activeViewController {
return self.mainCoordinator.activeViewController;
}
#pragma mark - HistoryCoordinatorDelegate
- (void)closeHistoryWithCompletion:(ProceduralBlock)completion {
__weak __typeof(self) weakSelf = self;
[self.historyCoordinator dismissWithCompletion:^{
if (completion) {
completion();
}
[weakSelf.historyCoordinator stop];
weakSelf.historyCoordinator = nil;
}];
}
@end