| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/tabs/public/tab_features.h" |
| |
| #include <memory> |
| |
| #include "base/feature_list.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/no_destructor.h" |
| #include "chrome/browser/actor/actor_keyed_service.h" |
| #include "chrome/browser/actor/actor_tab_data.h" |
| #include "chrome/browser/actor/ui/actor_ui_tab_controller.h" |
| #include "chrome/browser/bookmarks/bookmark_model_factory.h" |
| #include "chrome/browser/browsing_topics/browsing_topics_service_factory.h" |
| #include "chrome/browser/commerce/shopping_service_factory.h" |
| #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| #include "chrome/browser/contextual_cueing/contextual_cueing_helper.h" |
| #include "chrome/browser/enterprise/data_protection/data_protection_navigation_controller.h" |
| #include "chrome/browser/fingerprinting_protection/chrome_fingerprinting_protection_web_contents_helper_factory.h" |
| #include "chrome/browser/image_fetcher/image_fetcher_service_factory.h" |
| #include "chrome/browser/loader/from_gws_navigation_and_keep_alive_request_observer.h" |
| #include "chrome/browser/net/qwac_web_contents_observer.h" |
| #include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h" |
| #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h" |
| #include "chrome/browser/privacy_sandbox/incognito/privacy_sandbox_incognito_tab_observer.h" |
| #include "chrome/browser/privacy_sandbox/privacy_sandbox_tab_observer.h" |
| #include "chrome/browser/privacy_sandbox/tracking_protection_settings_factory.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_key.h" |
| #include "chrome/browser/signin/identity_manager_factory.h" |
| #include "chrome/browser/ssl/ask_before_http_dialog_controller.h" |
| #include "chrome/browser/sync/sessions/sync_sessions_router_tab_helper.h" |
| #include "chrome/browser/sync/sessions/sync_sessions_web_contents_router_factory.h" |
| #include "chrome/browser/sync/sync_service_factory.h" |
| #include "chrome/browser/tab_group_sync/tab_group_sync_service_factory.h" |
| #include "chrome/browser/task_manager/web_contents_tags.h" |
| #include "chrome/browser/themes/theme_service_factory.h" |
| #include "chrome/browser/ui/autofill/bubble_manager.h" |
| #include "chrome/browser/ui/browser_actions.h" |
| #include "chrome/browser/ui/browser_window/public/browser_window_interface.h" |
| #include "chrome/browser/ui/commerce/commerce_ui_tab_helper.h" |
| #include "chrome/browser/ui/lens/lens_overlay_controller.h" |
| #include "chrome/browser/ui/lens/lens_search_controller.h" |
| #include "chrome/browser/ui/page_action/page_action_icon_type.h" |
| #include "chrome/browser/ui/performance_controls/memory_saver_chip_controller.h" |
| #include "chrome/browser/ui/performance_controls/memory_saver_chip_tab_helper.h" |
| #include "chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper.h" |
| #include "chrome/browser/ui/tab_ui_helper.h" |
| #include "chrome/browser/ui/tabs/alert/tab_alert_controller.h" |
| #include "chrome/browser/ui/tabs/inactive_window_mouse_event_controller.h" |
| #include "chrome/browser/ui/tabs/public/tab_dialog_manager.h" |
| #include "chrome/browser/ui/tabs/saved_tab_groups/collaboration_messaging_page_action_controller.h" |
| #include "chrome/browser/ui/tabs/saved_tab_groups/collaboration_messaging_tab_data.h" |
| #include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.h" |
| #include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_web_contents_listener.h" |
| #include "chrome/browser/ui/tabs/tab_creation_metrics_controller.h" |
| #include "chrome/browser/ui/tabs/tab_model.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h" |
| #include "chrome/browser/ui/toolbar/pinned_toolbar/pinned_toolbar_actions_model.h" |
| #include "chrome/browser/ui/toolbar/pinned_toolbar/pinned_translate_action_listener.h" |
| #include "chrome/browser/ui/ui_features.h" |
| #include "chrome/browser/ui/views/commerce/discounts_page_action_view_controller.h" |
| #include "chrome/browser/ui/views/commerce/price_insights_page_action_view_controller.h" |
| #include "chrome/browser/ui/views/file_system_access/file_system_access_page_action_controller.h" |
| #include "chrome/browser/ui/views/intent_picker/intent_picker_view_page_action_controller.h" |
| #include "chrome/browser/ui/views/page_action/action_ids.h" |
| #include "chrome/browser/ui/views/page_action/page_action_controller.h" |
| #include "chrome/browser/ui/views/page_action/page_action_properties_provider.h" |
| #include "chrome/browser/ui/views/passwords/manage_passwords_page_action_controller.h" |
| #include "chrome/browser/ui/views/side_panel/customize_chrome/side_panel_controller_views.h" |
| #include "chrome/browser/ui/views/side_panel/extensions/extension_side_panel_manager.h" |
| #include "chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_controller.h" |
| #include "chrome/browser/ui/views/translate/translate_page_action_controller.h" |
| #include "chrome/browser/ui/views/zoom/zoom_view_controller.h" |
| #include "chrome/browser/ui/web_applications/pwa_install_page_action.h" |
| #include "chrome/browser/ui/webui/webui_embedding_context.h" |
| #include "chrome/browser/web_applications/web_app_tab_helper.h" |
| #include "chrome/browser/web_applications/web_app_utils.h" |
| #include "chrome/common/chrome_features.h" |
| #include "components/autofill/core/common/autofill_features.h" |
| #include "components/browsing_topics/browsing_topics_service.h" |
| #include "components/favicon/content/content_favicon_driver.h" |
| #include "components/fingerprinting_protection_filter/common/fingerprinting_protection_filter_features.h" |
| #include "components/image_fetcher/core/image_fetcher_service.h" |
| #include "components/ip_protection/common/ip_protection_status.h" |
| #include "components/lens/tab_contextualization_controller.h" |
| #include "components/passage_embeddings/passage_embeddings_features.h" |
| #include "components/permissions/permission_indicators_tab_data.h" |
| #include "components/security_interstitials/core/features.h" |
| #include "components/tabs/public/tab_interface.h" |
| #include "components/wallet/content/browser/content_walletable_pass_ingestion_controller.h" |
| #include "components/wallet/core/common/wallet_features.h" |
| #include "net/base/features.h" |
| #include "ui/base/unowned_user_data/user_data_factory.h" |
| |
| #if BUILDFLAG(ENABLE_GLIC) |
| #include "chrome/browser/glic/browser_ui/glic_tab_indicator_helper.h" |
| #include "chrome/browser/glic/public/glic_enabling.h" |
| #endif |
| namespace tabs { |
| |
| TabFeatures::TabFeatures() = default; |
| TabFeatures::~TabFeatures() = default; |
| |
| LensOverlayController* TabFeatures::lens_overlay_controller() { |
| // LensSearchController won't exist on non-normal windows. |
| return lens_search_controller_ |
| ? lens_search_controller_->lens_overlay_controller() |
| : nullptr; |
| } |
| |
| const LensOverlayController* TabFeatures::lens_overlay_controller() const { |
| // LensSearchController won't exist on non-normal windows. |
| return lens_search_controller_ |
| ? lens_search_controller_->lens_overlay_controller() |
| : nullptr; |
| } |
| |
| void TabFeatures::Init(TabInterface& tab, Profile* profile) { |
| CHECK(!initialized_); |
| initialized_ = true; |
| |
| // In tests you may want to disable TabFeatures initialization. |
| // See tabs::TabModel::PreventFeatureInitializationForTesting |
| CHECK(tab.GetBrowserWindowInterface()); |
| |
| tab_subscriptions_.push_back( |
| tab.RegisterWillDiscardContents(base::BindRepeating( |
| &TabFeatures::WillDiscardContents, weak_factory_.GetWeakPtr()))); |
| tab_subscriptions_.push_back(webui::InitEmbeddingContext(&tab)); |
| |
| // TODO(crbug.com/346148554): Do not create a SidePanelRegistry or |
| // dependencies for non-normal browsers. |
| side_panel_registry_ = std::make_unique<SidePanelRegistry>(&tab); |
| |
| // This block instantiate the page action controllers. They do not require any |
| // pre-condition. Because some feature need them during their instantiation, |
| // therefore this block should come before the feature controllers |
| // instantiation. |
| if (base::FeatureList::IsEnabled(features::kPageActionsMigration)) { |
| auto* pinned_actions_model = PinnedToolbarActionsModel::Get(profile); |
| CHECK(pinned_actions_model); |
| auto page_action_controller = |
| std::make_unique<page_actions::PageActionControllerImpl>( |
| pinned_actions_model); |
| page_action_controller->Initialize( |
| tab, |
| std::vector<actions::ActionId>(page_actions::kActionIds.begin(), |
| page_actions::kActionIds.end()), |
| page_actions::PageActionPropertiesProvider()); |
| page_action_controller_ = std::move(page_action_controller); |
| |
| if (IsPageActionMigrated(PageActionIconType::kTranslate)) { |
| translate_page_action_controller_ = |
| std::make_unique<TranslatePageActionController>(tab); |
| } |
| |
| if (IsPageActionMigrated(PageActionIconType::kMemorySaver)) { |
| memory_saver_chip_controller_ = |
| std::make_unique<memory_saver::MemorySaverChipController>( |
| *page_action_controller_); |
| } |
| |
| if (IsPageActionMigrated(PageActionIconType::kIntentPicker)) { |
| intent_picker_view_page_action_controller_ = |
| std::make_unique<IntentPickerViewPageActionController>(tab); |
| } |
| |
| if (IsPageActionMigrated(PageActionIconType::kFileSystemAccess)) { |
| file_system_access_page_action_controller_ = |
| std::make_unique<FileSystemAccessPageActionController>(tab); |
| } |
| |
| if (IsPageActionMigrated(PageActionIconType::kZoom)) { |
| zoom_view_controller_ = std::make_unique<zoom::ZoomViewController>( |
| tab, *page_action_controller_); |
| } |
| |
| if (IsPageActionMigrated(PageActionIconType::kPwaInstall)) { |
| pwa_install_page_action_controller_ = |
| std::make_unique<PwaInstallPageActionController>( |
| tab, *page_action_controller_); |
| } |
| |
| if (IsPageActionMigrated(PageActionIconType::kPriceInsights)) { |
| commerce_price_insights_page_action_view_controller_ = |
| std::make_unique<commerce::PriceInsightsPageActionViewController>( |
| tab, *page_action_controller_); |
| } |
| |
| if (IsPageActionMigrated(PageActionIconType::kManagePasswords)) { |
| manage_passwords_page_action_controller_ = |
| std::make_unique<ManagePasswordsPageActionController>( |
| *page_action_controller_); |
| } |
| } |
| |
| // Features that are only enabled for normal browser windows. By default most |
| // features should be instantiated in this block. |
| if (tab.IsInNormalWindow()) { |
| lens_search_controller_ = |
| GetUserDataFactory().CreateInstance<LensSearchController>(tab, &tab); |
| lens_search_controller_->Initialize( |
| profile->GetVariationsClient(), |
| IdentityManagerFactory::GetForProfile(profile), profile->GetPrefs(), |
| SyncServiceFactory::GetForProfile(profile), |
| ThemeServiceFactory::GetForProfile(profile)); |
| |
| // Each time a new tab is created, validate the topics calculation schedule |
| // to help investigate a scheduling bug (crbug.com/343750866). |
| if (browsing_topics::BrowsingTopicsService* browsing_topics_service = |
| browsing_topics::BrowsingTopicsServiceFactory::GetForProfile( |
| profile)) { |
| browsing_topics_service->ValidateCalculationSchedule(); |
| } |
| |
| permission_indicators_tab_data_ = |
| std::make_unique<permissions::PermissionIndicatorsTabData>( |
| tab.GetContents()); |
| |
| pinned_translate_action_listener_ = |
| std::make_unique<PinnedTranslateActionListener>(&tab); |
| |
| if (!profile->IsIncognitoProfile()) { |
| // TODO(crbug.com/40863325): Consider using the in-memory cache instead. |
| commerce_ui_tab_helper_ = |
| GetUserDataFactory().CreateInstance<commerce::CommerceUiTabHelper>( |
| tab, tab, |
| commerce::ShoppingServiceFactory::GetForBrowserContext(profile), |
| BookmarkModelFactory::GetForBrowserContext(profile), |
| ImageFetcherServiceFactory::GetForKey(profile->GetProfileKey()) |
| ->GetImageFetcher( |
| image_fetcher::ImageFetcherConfig::kNetworkOnly), |
| side_panel_registry_.get()); |
| } |
| |
| contextual_cueing::ContextualCueingHelper::MaybeCreateForWebContents( |
| tab.GetContents()); |
| |
| privacy_sandbox_tab_observer_ = |
| std::make_unique<privacy_sandbox::PrivacySandboxTabObserver>( |
| tab.GetContents()); |
| |
| privacy_sandbox_incognito_tab_observer_ = |
| std::make_unique<privacy_sandbox::PrivacySandboxIncognitoTabObserver>( |
| tab.GetContents()); |
| |
| if (tab_groups::TabGroupSyncService* tab_group_sync_service = |
| tab_groups::TabGroupSyncServiceFactory::GetForProfile(profile)) { |
| saved_tab_group_web_contents_listener_ = |
| std::make_unique<tab_groups::SavedTabGroupWebContentsListener>( |
| tab_group_sync_service, &tab); |
| } |
| |
| if (tab_groups::SavedTabGroupUtils::SupportsSharedTabGroups()) { |
| collaboration_messaging_tab_data_ = |
| std::make_unique<tab_groups::CollaborationMessagingTabData>(profile); |
| } |
| |
| if (IsPageActionMigrated(PageActionIconType::kCollaborationMessaging) && |
| tab_groups::SavedTabGroupUtils::SupportsSharedTabGroups()) { |
| collaboration_messaging_page_action_controller_ = |
| GetUserDataFactory() |
| .CreateInstance<CollaborationMessagingPageActionController>( |
| tab, tab, *page_action_controller_, |
| *collaboration_messaging_tab_data_); |
| } |
| |
| #if BUILDFLAG(ENABLE_GLIC) |
| if (glic::GlicEnabling::IsProfileEligible( |
| tab.GetBrowserWindowInterface()->GetProfile())) { |
| glic_tab_indicator_helper_ = |
| GetUserDataFactory().CreateInstance<glic::GlicTabIndicatorHelper>( |
| tab, &tab); |
| } |
| #endif // BUILDFLAG(ENABLE_GLIC) |
| // TODO(crbug.com/433973411): Move this logic to a helper function. |
| if (base::FeatureList::IsEnabled(features::kGlicActorUi) && |
| profile->IsRegularProfile()) { |
| // The associated tab is passed to CreateInstance twice: for dependency |
| // injection callbacks and as a direct constructor argument. |
| actor_ui_tab_controller_ = |
| GetUserDataFactory().CreateInstance<actor::ui::ActorUiTabController>( |
| tab, tab, actor::ActorKeyedService::Get(profile), |
| std::make_unique<actor::ui::ActorUiTabControllerFactory>()); |
| } |
| actor_tab_data_ = |
| GetUserDataFactory().CreateInstance<actor::ActorTabData>(tab, &tab); |
| } // IsInNormalWindow() end. |
| |
| // This block instantiates the page action controllers that depends on the |
| // `commerce_ui_tab_helper_` and not need to be created before. |
| if (commerce_ui_tab_helper_) { |
| if (IsPageActionMigrated(PageActionIconType::kDiscounts)) { |
| commerce_discounts_page_action_view_controller_ = |
| std::make_unique<commerce::DiscountsPageActionViewController>( |
| tab, *page_action_controller_, *commerce_ui_tab_helper_); |
| } |
| } |
| |
| if (base::FeatureList::IsEnabled( |
| autofill::features::kAutofillShowBubblesBasedOnPriorities)) { |
| autofill_bubble_manager_ = autofill::BubbleManager::Create(); |
| } |
| |
| customize_chrome_side_panel_controller_ = |
| std::make_unique<customize_chrome::SidePanelControllerViews>(tab); |
| |
| extension_side_panel_manager_ = |
| std::make_unique<extensions::ExtensionSidePanelManager>( |
| profile, &tab, side_panel_registry_.get()); |
| |
| tab_dialog_manager_ = std::make_unique<TabDialogManager>(&tab); |
| |
| data_protection_tab_controller_ = std::make_unique< |
| enterprise_data_protection::DataProtectionNavigationController>(&tab); |
| |
| // TODO(https://crbug.com/355485153): Move this into the normal window block. |
| read_anything_side_panel_controller_ = |
| std::make_unique<ReadAnythingSidePanelController>( |
| &tab, side_panel_registry_.get()); |
| |
| if (fingerprinting_protection_filter::features:: |
| IsFingerprintingProtectionEnabledForIncognitoState( |
| profile->IsIncognitoProfile())) { |
| CreateFingerprintingProtectionWebContentsHelper( |
| tab.GetContents(), profile->GetPrefs(), |
| HostContentSettingsMapFactory::GetForProfile(profile), |
| TrackingProtectionSettingsFactory::GetForProfile(profile), |
| profile->IsIncognitoProfile()); |
| } |
| |
| // Only create the IpProtectionStatus if the User Bypass feature is enabled. |
| if (net::features::kIpPrivacyEnableUserBypass.Get()) { |
| ip_protection::IpProtectionStatus::CreateForWebContents(tab.GetContents()); |
| } |
| |
| if (web_app::AreWebAppsEnabled(profile)) { |
| web_app::WebAppTabHelper::Create(&tab, tab.GetContents()); |
| } |
| |
| sync_sessions_router_ = |
| std::make_unique<sync_sessions::SyncSessionsRouterTabHelper>( |
| tab.GetContents(), |
| sync_sessions::SyncSessionsWebContentsRouterFactory::GetForProfile( |
| profile), |
| ChromeTranslateClient::FromWebContents(tab.GetContents()), |
| favicon::ContentFaviconDriver::FromWebContents(tab.GetContents())); |
| |
| from_gws_navigation_and_keep_alive_request_observer_ = |
| FromGWSNavigationAndKeepAliveRequestObserver::MaybeCreateForWebContents( |
| tab.GetContents()); |
| |
| resource_usage_helper_ = std::make_unique<TabResourceUsageTabHelper>(tab); |
| |
| memory_saver_chip_helper_ = std::make_unique<MemorySaverChipTabHelper>(tab); |
| |
| tab_creation_metrics_controller_ = |
| std::make_unique<TabCreationMetricsController>(&tab); |
| |
| tab_ui_helper_ = std::make_unique<TabUIHelper>(tab); |
| |
| task_manager::WebContentsTags::CreateForTabContents(tab.GetContents()); |
| |
| #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \ |
| BUILDFLAG(IS_CHROMEOS) |
| inactive_window_mouse_event_controller_ = |
| std::make_unique<InactiveWindowMouseEventController>(); |
| |
| if (base::FeatureList::IsEnabled(wallet::kWalletablePassDetection)) { |
| if (auto* opt_guide = |
| OptimizationGuideKeyedServiceFactory::GetForProfile(profile)) { |
| wallet::ContentWalletablePassIngestionController::CreateForWebContents( |
| tab.GetContents(), opt_guide); |
| } |
| } |
| #endif |
| |
| if (base::FeatureList::IsEnabled(net::features::kVerifyQWACs)) { |
| qwac_web_contents_observer_ = |
| std::make_unique<QwacWebContentsObserver>(tab); |
| } |
| |
| if (base::FeatureList::IsEnabled( |
| security_interstitials::features::kHttpsFirstDialogUi)) { |
| ask_before_http_dialog_controller_ = |
| std::make_unique<AskBeforeHttpDialogController>(&tab); |
| } |
| |
| tab_alert_controller_ = |
| GetUserDataFactory().CreateInstance<TabAlertController>(tab, tab); |
| |
| tab_contextualization_controller_ = |
| GetUserDataFactory().CreateInstance<lens::TabContextualizationController>( |
| tab, &tab); |
| } |
| |
| TabResourceUsageTabHelper* TabFeatures::SetResourceUsageHelperForTesting( |
| std::unique_ptr<TabResourceUsageTabHelper> resource_usage_helper) { |
| resource_usage_helper_ = std::move(resource_usage_helper); |
| return resource_usage_helper_.get(); |
| } |
| |
| TabUIHelper* TabFeatures::SetTabUIHelperForTesting( |
| std::unique_ptr<TabUIHelper> tab_ui_helper) { |
| tab_ui_helper_ = std::move(tab_ui_helper); |
| return tab_ui_helper_.get(); |
| } |
| |
| void TabFeatures::WillDiscardContents(tabs::TabInterface* tab, |
| content::WebContents* old_contents, |
| content::WebContents* new_contents) { |
| DCHECK_EQ(old_contents, tab->GetContents()); |
| |
| Profile* profile = tab->GetBrowserWindowInterface()->GetProfile(); |
| |
| // This method is transiently used to reset features that do not handle tab |
| // discarding themselves. |
| read_anything_side_panel_controller_->ResetForTabDiscard(); |
| read_anything_side_panel_controller_.reset(); |
| read_anything_side_panel_controller_ = |
| std::make_unique<ReadAnythingSidePanelController>( |
| tab, side_panel_registry_.get()); |
| |
| // Deregister side-panel entries that are web-contents scoped rather than tab |
| // scoped. |
| side_panel_registry_->Deregister( |
| SidePanelEntry::Key(SidePanelEntry::Id::kAboutThisSite)); |
| |
| if (privacy_sandbox_tab_observer_) { |
| privacy_sandbox_tab_observer_.reset(); |
| privacy_sandbox_tab_observer_ = |
| std::make_unique<privacy_sandbox::PrivacySandboxTabObserver>( |
| new_contents); |
| } |
| |
| if (privacy_sandbox_incognito_tab_observer_) { |
| privacy_sandbox_incognito_tab_observer_.reset(); |
| privacy_sandbox_incognito_tab_observer_ = |
| std::make_unique<privacy_sandbox::PrivacySandboxIncognitoTabObserver>( |
| new_contents); |
| } |
| |
| if (web_app::AreWebAppsEnabled( |
| tab->GetBrowserWindowInterface()->GetProfile())) { |
| web_app::WebAppTabHelper::Create(tab, new_contents); |
| } |
| |
| sync_sessions_router_.reset(); |
| sync_sessions_router_ = |
| std::make_unique<sync_sessions::SyncSessionsRouterTabHelper>( |
| new_contents, |
| sync_sessions::SyncSessionsWebContentsRouterFactory::GetForProfile( |
| profile), |
| ChromeTranslateClient::FromWebContents(new_contents), |
| favicon::ContentFaviconDriver::FromWebContents(new_contents)); |
| |
| if (permission_indicators_tab_data_) { |
| permission_indicators_tab_data_ = |
| std::make_unique<permissions::PermissionIndicatorsTabData>( |
| new_contents); |
| } |
| } |
| |
| customize_chrome::SidePanelController* |
| TabFeatures::SetCustomizeChromeSidePanelControllerForTesting( |
| std::unique_ptr<customize_chrome::SidePanelController> |
| customize_chrome_side_panel_controller) { |
| customize_chrome_side_panel_controller_ = |
| std::move(customize_chrome_side_panel_controller); |
| return customize_chrome_side_panel_controller_.get(); |
| } |
| |
| // static |
| ui::UserDataFactoryWithOwner<TabInterface>& TabFeatures::GetUserDataFactory() { |
| static base::NoDestructor<ui::UserDataFactoryWithOwner<TabInterface>> factory; |
| return *factory; |
| } |
| |
| // static |
| ui::UserDataFactoryWithOwner<TabInterface>& |
| TabFeatures::GetUserDataFactoryForTesting() { |
| return GetUserDataFactory(); |
| } |
| |
| } // namespace tabs |