| // Copyright 2026 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/safe_browsing/android/suspicious_site_controller_android.h" |
| |
| #include <utility> |
| |
| #include "base/metrics/histogram_functions.h" |
| #include "base/no_destructor.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/types/pass_key.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| #include "chrome/browser/history/history_service_factory.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/safe_browsing/android/safe_browsing_referring_app_bridge_android.h" |
| #include "chrome/browser/safe_browsing/safe_browsing_service.h" |
| #include "chrome/browser/ui/android/safe_browsing/suspicious_site_dialog_view_android.h" |
| #include "chrome/browser/ui/hats/hats_service.h" |
| #include "chrome/browser/ui/hats/hats_service_factory.h" |
| #include "chrome/browser/ui/hats/survey_config.h" |
| #include "chrome/common/url_constants.h" |
| #include "chrome/common/webui_url_constants.h" |
| #include "components/content_settings/core/browser/host_content_settings_map.h" |
| #include "components/safe_browsing/content/browser/ui_manager.h" |
| #include "components/safe_browsing/core/browser/suspicious_site_warning_allowlist.h" |
| #include "components/safe_browsing/core/common/features.h" |
| #include "components/security_interstitials/content/security_interstitial_tab_helper.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/referrer.h" |
| #include "ui/android/window_android.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/page_transition_types.h" |
| #include "ui/base/window_open_disposition.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| namespace safe_browsing { |
| namespace { |
| |
| base::OnceClosure* GetShownCallback() { |
| static base::NoDestructor<base::OnceClosure> callback; |
| return callback.get(); |
| } |
| |
| base::OnceClosure* GetDismissedCallback() { |
| static base::NoDestructor<base::OnceClosure> callback; |
| return callback.get(); |
| } |
| |
| const char* UserChoiceToString( |
| SuspiciousSiteControllerAndroid::UserChoice choice) { |
| switch (choice) { |
| case SuspiciousSiteControllerAndroid::UserChoice::kMarkAsSafe: |
| return "mark_as_safe"; |
| case SuspiciousSiteControllerAndroid::UserChoice::kBackToSafety: |
| return "back_to_safety"; |
| case SuspiciousSiteControllerAndroid::UserChoice::kDismiss: |
| return "dismiss"; |
| case SuspiciousSiteControllerAndroid::UserChoice::kManualNavigation: |
| return "manual_navigation"; |
| } |
| NOTREACHED(); |
| } |
| |
| } // namespace |
| |
| WEB_CONTENTS_USER_DATA_KEY_IMPL(SuspiciousSiteControllerAndroid); |
| |
| SuspiciousSiteControllerAndroid::SuspiciousSiteControllerAndroid( |
| content::WebContents* web_contents) |
| : content::WebContentsObserver(web_contents), |
| content::WebContentsUserData<SuspiciousSiteControllerAndroid>( |
| *web_contents) { |
| CHECK(web_contents->GetPrimaryMainFrame()); |
| } |
| |
| SuspiciousSiteControllerAndroid::~SuspiciousSiteControllerAndroid() { |
| // If the dialog was shown, log the tracked warning outcome directly. |
| if (has_shown_) { |
| base::UmaHistogramEnumeration( |
| "SafeBrowsing.SuspiciousSiteWarning.WarningOutcome", warning_outcome_); |
| } |
| |
| // Remove the active suspicious site entry from AllowlistUrlSet when this |
| // controller is destroyed (e.g. when navigating away or closing the tab) so |
| // that the security state is restored. |
| if (!current_suspicious_url_.is_empty() && web_contents()) { |
| SafeBrowsingService* sb_service = |
| g_browser_process->safe_browsing_service(); |
| if (sb_service && sb_service->ui_manager()) { |
| sb_service->ui_manager()->RemoveAllowlistUrlSetThreatType( |
| base::PassKey<SuspiciousSiteControllerAndroid>(), |
| current_suspicious_url_, navigation_id_, web_contents(), |
| /*from_pending_only=*/true, |
| SBThreatType::SB_THREAT_TYPE_WARNABLE_SUSPICIOUS_SITE); |
| } |
| } |
| } |
| |
| // static |
| void SuspiciousSiteControllerAndroid::ShowForWebContents( |
| content::WebContents* web_contents, |
| const security_interstitials::UnsafeResource& resource) { |
| if (FromWebContents(web_contents)) { |
| web_contents->RemoveUserData(UserDataKey()); |
| } |
| CreateForWebContents(web_contents); |
| auto* controller = FromWebContents(web_contents); |
| controller->resource_ = resource; |
| controller->navigation_id_ = resource.navigation_id; |
| controller->is_suspended_ = true; |
| |
| auto* tracker = AsyncCheckTracker::FromWebContents(web_contents); |
| if (tracker && !controller->async_check_observation_.IsObserving()) { |
| controller->async_check_observation_.Observe(tracker); |
| } |
| |
| controller->MaybeShowDialog(); |
| } |
| |
| void SuspiciousSiteControllerAndroid::DidFinishNavigation( |
| content::NavigationHandle* navigation_handle) { |
| if (!navigation_handle->IsInPrimaryMainFrame() || |
| !navigation_handle->HasCommitted() || |
| navigation_handle->IsSameDocument()) { |
| return; |
| } |
| |
| // If this navigation failed or if a different navigation completed, clean up. |
| if (navigation_handle->IsErrorPage() || !navigation_id_.has_value() || |
| navigation_handle->GetNavigationId() != navigation_id_.value()) { |
| // The user navigated away while the warning was showing. The Java modal |
| // dialog manager normally reports this as DismissalCause::NAVIGATE when the |
| // navigation starts, but not when the navigation was already in flight |
| // when the dialog appeared or when the activity has no tab-modal lifetime |
| // handler, so record the outcome here if it is still undecided. |
| if (has_shown_ && !is_suspended_ && |
| warning_outcome_ == WarningOutcome::kUnknown) { |
| CloseDialog(ui::ModalDialogWrapper::DismissalCause::NAVIGATE); |
| } |
| web_contents()->RemoveUserData(UserDataKey()); |
| return; |
| } |
| |
| navigation_committed_ = true; |
| MaybeShowDialog(); |
| } |
| |
| void SuspiciousSiteControllerAndroid::OnVisibilityChanged( |
| content::Visibility visibility) { |
| if (visibility == content::Visibility::VISIBLE && is_suspended_) { |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&SuspiciousSiteControllerAndroid::MaybeShowDialog, |
| weak_ptr_factory_.GetWeakPtr())); |
| } else if (visibility == content::Visibility::HIDDEN) { |
| is_suspended_ = true; |
| } |
| } |
| |
| void SuspiciousSiteControllerAndroid::OnAsyncSafeBrowsingCheckCompleted() { |
| MaybeShowDialog(); |
| } |
| |
| void SuspiciousSiteControllerAndroid:: |
| OnAsyncSafeBrowsingCheckTrackerDestructed() { |
| async_check_observation_.Reset(); |
| } |
| |
| void SuspiciousSiteControllerAndroid::MaybeShowDialog() { |
| if (!web_contents() || !navigation_id_.has_value()) { |
| return; |
| } |
| |
| // 1. Interstitials take precedence: suppress and clean up if an interstitial |
| // is pending/active. |
| auto* interstitial_helper = |
| security_interstitials::SecurityInterstitialTabHelper::FromWebContents( |
| web_contents()); |
| if (interstitial_helper && |
| interstitial_helper->HasPendingOrActiveInterstitial()) { |
| web_contents()->RemoveUserData(UserDataKey()); |
| return; |
| } |
| |
| // 2. Defer if background async Safe Browsing checks are still in-flight. |
| auto* tracker = AsyncCheckTracker::FromWebContents(web_contents()); |
| if (tracker && tracker->IsNavigationPending(navigation_id_.value())) { |
| is_suspended_ = true; |
| return; |
| } |
| |
| // 3. Defer if target navigation has not committed yet. |
| if (!navigation_committed_) { |
| if (tracker && |
| tracker->GetNavigationCommittedTimestamp(navigation_id_.value()) |
| .has_value()) { |
| navigation_committed_ = true; |
| } else { |
| is_suspended_ = true; |
| return; |
| } |
| } |
| |
| // 4. Suppress and clean up if the site has been allowlisted by the user. |
| if (web_contents()->GetBrowserContext()) { |
| Profile* profile = |
| Profile::FromBrowserContext(web_contents()->GetBrowserContext()); |
| if (profile) { |
| HostContentSettingsMap* hcsm = |
| HostContentSettingsMapFactory::GetForProfile(profile); |
| if (hcsm && |
| SuspiciousSiteWarningAllowlist(hcsm).IsSiteAllowedForHost( |
| std::string(web_contents()->GetLastCommittedURL().host()))) { |
| web_contents()->RemoveUserData(UserDataKey()); |
| return; |
| } |
| } |
| } |
| |
| // 5. Defer if native Android window is not attached or web contents is |
| // hidden. |
| ui::WindowAndroid* window_android = web_contents()->GetTopLevelNativeWindow(); |
| if (!window_android || |
| web_contents()->GetVisibility() != content::Visibility::VISIBLE) { |
| is_suspended_ = true; |
| return; |
| } |
| |
| is_suspended_ = false; |
| ShowDialog(); |
| } |
| |
| void SuspiciousSiteControllerAndroid::ShowDialog() { |
| if (GetShownCallback() && !GetShownCallback()->is_null()) { |
| std::move(*GetShownCallback()).Run(); |
| } |
| |
| // Reset closing state for the current dialog presentation. |
| is_closing_ = false; |
| |
| ui::WindowAndroid* window_android = web_contents()->GetTopLevelNativeWindow(); |
| if (!window_android) { |
| is_suspended_ = true; |
| return; |
| } |
| |
| dialog_shown_time_ = base::TimeTicks::Now(); |
| SafeBrowsingService* sb_service = g_browser_process->safe_browsing_service(); |
| if (sb_service && sb_service->ui_manager()) { |
| // If a previous suspicious site URL was registered for this tab, clear it |
| // first before setting the new one. |
| if (!current_suspicious_url_.is_empty()) { |
| sb_service->ui_manager()->RemoveAllowlistUrlSetThreatType( |
| base::PassKey<SuspiciousSiteControllerAndroid>(), |
| current_suspicious_url_, navigation_id_, web_contents(), |
| /*from_pending_only=*/true, |
| SBThreatType::SB_THREAT_TYPE_WARNABLE_SUSPICIOUS_SITE); |
| } |
| } |
| |
| // Populate current_suspicious_url_ and current_referrer_url_ for the active |
| // warning session. |
| current_suspicious_url_ = web_contents()->GetLastCommittedURL(); |
| if (auto* entry = web_contents()->GetController().GetLastCommittedEntry()) { |
| current_referrer_url_ = entry->GetReferrer().url; |
| } |
| |
| // Pre-fetch repeat visit count right after populating |
| // current_suspicious_url_. |
| FetchRepeatVisitCount(); |
| |
| if (sb_service && sb_service->ui_manager()) { |
| // Add the suspicious site URL to AllowlistUrlSet with pending=true. |
| // AllowlistUrlSet stores the threat type for WebContents, enabling |
| // the chrome_security_state computation to return |
| // MALICIOUS_CONTENT_STATUS_WARNABLE_SUSPICIOUS_SITE so the red warning icon |
| // remains active in the Omnibox and Page Info even if the dialog is closed. |
| sb_service->ui_manager()->AddToAllowlistUrlSet( |
| current_suspicious_url_, navigation_id_, web_contents(), |
| /*is_pending=*/true, |
| SBThreatType::SB_THREAT_TYPE_WARNABLE_SUSPICIOUS_SITE); |
| } |
| dialog_view_.reset(); |
| dialog_view_ = std::make_unique<SuspiciousSiteDialogViewAndroid>(*this); |
| // TODO(crbug.com/532598569): Investigate if destroying an existing dialog, |
| // creating a new one, and displaying it causes UI flicker. |
| dialog_view_->Show(*window_android); |
| if (!has_shown_) { |
| sb_service->ui_manager()->CreateAndSendClientSafeBrowsingWarningShownReport( |
| resource_.value()); |
| base::UmaHistogramEnumeration( |
| "SafeBrowsing.SuspiciousSiteWarning.UserInteraction", |
| UserInteraction::kShown); |
| has_shown_ = true; |
| } |
| } |
| |
| void SuspiciousSiteControllerAndroid::CloseDialog( |
| ui::ModalDialogWrapper::DismissalCause dismissal_cause) { |
| if (is_closing_) { |
| return; |
| } |
| |
| // Prevent false telemetry/HaTS triggers from phantom interactions while |
| // hidden, but allow lifecycle and terminal system events to properly update |
| // state and clean up backgrounded tabs. |
| if (is_suspended_ && |
| dismissal_cause != ui::ModalDialogWrapper::DismissalCause::TAB_SWITCHED && |
| dismissal_cause != |
| ui::ModalDialogWrapper::DismissalCause::ACTIVITY_DESTROYED && |
| dismissal_cause != |
| ui::ModalDialogWrapper::DismissalCause::DIALOG_INTERACTION_DEFERRED && |
| dismissal_cause != |
| ui::ModalDialogWrapper::DismissalCause::TAB_DESTROYED && |
| dismissal_cause != |
| ui::ModalDialogWrapper::DismissalCause::WEB_CONTENTS_DESTROYED) { |
| return; |
| } |
| |
| if (GetDismissedCallback() && !GetDismissedCallback()->is_null()) { |
| std::move(*GetDismissedCallback()).Run(); |
| } |
| |
| switch (dismissal_cause) { |
| case ui::ModalDialogWrapper::DismissalCause::NAVIGATE_BACK: |
| case ui::ModalDialogWrapper::DismissalCause::NAVIGATE_BACK_OR_TOUCH_OUTSIDE: |
| HandleBackNavigation(UserInteraction::kSystemBack); |
| return; |
| case ui::ModalDialogWrapper::DismissalCause::NAVIGATE: |
| is_closing_ = true; |
| warning_outcome_ = WarningOutcome::kAdhered; |
| base::UmaHistogramEnumeration( |
| "SafeBrowsing.SuspiciousSiteWarning.UserInteraction", |
| UserInteraction::kManualNavigation); |
| MaybeTriggerHatsSurvey(UserChoice::kManualNavigation); |
| is_suspended_ = true; |
| break; |
| case ui::ModalDialogWrapper::DismissalCause::TAB_SWITCHED: |
| case ui::ModalDialogWrapper::DismissalCause::DIALOG_INTERACTION_DEFERRED: |
| case ui::ModalDialogWrapper::DismissalCause::ACTIVITY_DESTROYED: |
| if (!dialog_shown_time_.is_null()) { |
| dialog_state_.accumulated_visible_time += |
| base::TimeTicks::Now() - dialog_shown_time_; |
| dialog_shown_time_ = base::TimeTicks(); |
| } |
| is_suspended_ = true; |
| break; |
| case ui::ModalDialogWrapper::DismissalCause::TAB_DESTROYED: |
| case ui::ModalDialogWrapper::DismissalCause::WEB_CONTENTS_DESTROYED: |
| is_closing_ = true; |
| warning_outcome_ = WarningOutcome::kAdhered; |
| base::UmaHistogramEnumeration( |
| "SafeBrowsing.SuspiciousSiteWarning.UserInteraction", |
| UserInteraction::kCloseTab); |
| is_suspended_ = true; |
| break; |
| case ui::ModalDialogWrapper::DismissalCause::TOUCH_OUTSIDE: |
| case ui::ModalDialogWrapper::DismissalCause::ACTION_ON_CONTENT: |
| is_closing_ = true; |
| warning_outcome_ = WarningOutcome::kBypassed; |
| base::UmaHistogramEnumeration( |
| "SafeBrowsing.SuspiciousSiteWarning.UserInteraction", |
| UserInteraction::kDismissed); |
| MaybeTriggerHatsSurvey(UserChoice::kDismiss); |
| is_suspended_ = false; |
| break; |
| default: |
| is_closing_ = true; |
| is_suspended_ = false; |
| break; |
| } |
| |
| dialog_view_.reset(); |
| is_closing_ = false; |
| } |
| |
| void SuspiciousSiteControllerAndroid::HandleBackNavigation( |
| UserInteraction interaction_type) { |
| if (is_closing_) { |
| return; |
| } |
| is_closing_ = true; |
| has_shown_ = true; |
| warning_outcome_ = WarningOutcome::kAdhered; |
| dialog_view_.reset(); |
| |
| base::UmaHistogramEnumeration( |
| "SafeBrowsing.SuspiciousSiteWarning.UserInteraction", interaction_type); |
| |
| MaybeTriggerHatsSurvey(UserChoice::kBackToSafety); |
| |
| content::WebContents* contents = web_contents(); |
| CHECK(contents); |
| auto& controller = contents->GetController(); |
| if (controller.CanGoBack()) { |
| controller.GoBack(); |
| } else { |
| controller.LoadURLWithParams(content::NavigationController::LoadURLParams( |
| GURL(chrome::kChromeUINewTabURL))); |
| } |
| |
| // NOTE: Calling RemoveUserData synchronously destroys this object, so there |
| // must be no member accesses after this point. |
| contents->RemoveUserData(UserDataKey()); |
| } |
| |
| void SuspiciousSiteControllerAndroid::OnContinueButtonClicked() { |
| if (is_closing_) { |
| return; |
| } |
| is_closing_ = true; |
| has_shown_ = true; |
| warning_outcome_ = WarningOutcome::kBypassed; |
| dialog_view_.reset(); |
| |
| base::UmaHistogramEnumeration( |
| "SafeBrowsing.SuspiciousSiteWarning.UserInteraction", |
| UserInteraction::kMarkAsSafe); |
| |
| MaybeTriggerHatsSurvey(UserChoice::kMarkAsSafe); |
| |
| if (web_contents()->GetBrowserContext()) { |
| Profile* profile = |
| Profile::FromBrowserContext(web_contents()->GetBrowserContext()); |
| if (profile) { |
| HostContentSettingsMap* hcsm = |
| HostContentSettingsMapFactory::GetForProfile(profile); |
| if (hcsm) { |
| SuspiciousSiteWarningAllowlist(hcsm).AllowSiteForHost( |
| std::string(web_contents()->GetLastCommittedURL().host())); |
| } |
| } |
| } |
| // NOTE: Calling RemoveUserData synchronously destroys this object, so there |
| // must be no member accesses after this point. |
| web_contents()->RemoveUserData(UserDataKey()); |
| } |
| |
| void SuspiciousSiteControllerAndroid::FetchRepeatVisitCount() { |
| if (dialog_state_.has_fetched_history || !web_contents() || |
| !web_contents()->GetBrowserContext()) { |
| return; |
| } |
| GURL site_url = web_contents()->GetLastCommittedURL(); |
| if (!site_url.is_valid()) { |
| return; |
| } |
| |
| dialog_state_.has_fetched_history = true; |
| Profile* profile = |
| Profile::FromBrowserContext(web_contents()->GetBrowserContext()); |
| if (!profile) { |
| return; |
| } |
| // Use IMPLICIT_ACCESS because this history query is an automated background |
| // feature check for survey telemetry rather than a direct user interaction |
| // with the History UI. |
| history::HistoryService* history_service = |
| HistoryServiceFactory::GetForProfile(profile, |
| ServiceAccessType::IMPLICIT_ACCESS); |
| if (history_service) { |
| history_service->GetVisibleVisitCountToHost( |
| site_url, |
| base::BindOnce(&SuspiciousSiteControllerAndroid::OnGetVisibleVisitCount, |
| weak_ptr_factory_.GetWeakPtr()), |
| &history_task_tracker_); |
| } |
| } |
| |
| void SuspiciousSiteControllerAndroid::OnGetVisibleVisitCount( |
| history::VisibleVisitCountToHostResult result) { |
| if (result.success && result.count > 1) { |
| dialog_state_.repeat_visit = true; |
| } |
| } |
| |
| void SuspiciousSiteControllerAndroid::MaybeTriggerHatsSurvey( |
| UserChoice user_choice) { |
| if (!base::FeatureList::IsEnabled( |
| safe_browsing::kSuspiciousSiteWarningSurvey)) { |
| return; |
| } |
| if (!web_contents() || !web_contents()->GetBrowserContext()) { |
| return; |
| } |
| Profile* profile = |
| Profile::FromBrowserContext(web_contents()->GetBrowserContext()); |
| if (!profile) { |
| return; |
| } |
| HatsService* hats_service = |
| HatsServiceFactory::GetForProfile(profile, /*create_if_necessary=*/true); |
| if (!hats_service) { |
| return; |
| } |
| |
| base::TimeDelta total_visible_time = dialog_state_.accumulated_visible_time; |
| if (!dialog_shown_time_.is_null()) { |
| total_visible_time += base::TimeTicks::Now() - dialog_shown_time_; |
| } |
| std::string visible_time_str = |
| base::StringPrintf("%.2f", total_visible_time.InSecondsF()); |
| |
| std::string referring_app; |
| internal::ReferringAppInfo referring_app_info = |
| GetReferringAppInfo(web_contents(), /*get_webapk_info=*/false); |
| if (referring_app_info.has_referring_app()) { |
| referring_app = referring_app_info.referring_app_name; |
| } |
| |
| std::string referrer_origin; |
| if (current_referrer_url_.is_valid()) { |
| url::Origin origin = url::Origin::Create(current_referrer_url_); |
| if (!origin.opaque()) { |
| referrer_origin = origin.Serialize(); |
| } |
| } |
| |
| std::string site_origin = |
| current_suspicious_url_.is_valid() |
| ? url::Origin::Create(current_suspicious_url_).Serialize() |
| : ""; |
| |
| SurveyBitsData bits_data = { |
| {"did_proceed", warning_outcome_ == WarningOutcome::kBypassed}, |
| {"learn_more_clicked", dialog_state_.learn_more_clicked}, |
| {"repeat_visit", dialog_state_.repeat_visit}}; |
| SurveyStringData string_data = { |
| {"site_origin", site_origin}, |
| {"user_choice", UserChoiceToString(user_choice)}, |
| {"time_prompt_visible", visible_time_str}, |
| {"referrer_origin", referrer_origin}, |
| {"referring_app", referring_app}}; |
| |
| std::string trigger_id = |
| warning_outcome_ == WarningOutcome::kBypassed |
| ? safe_browsing::kSuspiciousSiteWarningSurveyProceedTriggerId.Get() |
| : safe_browsing::kSuspiciousSiteWarningSurveyHeedTriggerId.Get(); |
| |
| HatsService::SurveyOptions survey_options( |
| /*custom_invitation=*/ |
| l10n_util::GetStringUTF16(IDS_SAFE_BROWSING_HATS_CUSTOM_INVITATION)); |
| hats_service->LaunchSurveyForWebContents( |
| kHatsSurveyTriggerSuspiciousSiteWarning, web_contents(), bits_data, |
| string_data, /*success_callback=*/base::DoNothing(), |
| /*failure_callback=*/base::DoNothing(), |
| trigger_id.empty() ? std::nullopt : std::make_optional(trigger_id), |
| survey_options); |
| } |
| |
| void SuspiciousSiteControllerAndroid::OnHelpCenterLinkClicked() { |
| if (!dialog_state_.learn_more_clicked) { |
| dialog_state_.learn_more_clicked = true; |
| base::UmaHistogramEnumeration( |
| "SafeBrowsing.SuspiciousSiteWarning.UserInteraction", |
| UserInteraction::kLearnMore); |
| } |
| content::WebContents* contents = web_contents(); |
| CHECK(contents); |
| content::OpenURLParams params = |
| content::OpenURLParams::CreateBrowserInitiated( |
| GURL(chrome::kUnsafeSiteWarningHelpCenterURL), |
| WindowOpenDisposition::NEW_FOREGROUND_TAB, ui::PAGE_TRANSITION_LINK); |
| |
| contents->OpenURL(params, /*navigation_handle_callback=*/{}); |
| } |
| |
| std::u16string SuspiciousSiteControllerAndroid::GetPrimaryButtonText() const { |
| return l10n_util::GetStringUTF16(IDS_SUSPICIOUS_SITE_BACK_TO_SAFETY); |
| } |
| |
| std::u16string SuspiciousSiteControllerAndroid::GetSecondaryButtonText() const { |
| return l10n_util::GetStringUTF16(IDS_SUSPICIOUS_SITE_PROCEED_BUTTON); |
| } |
| |
| std::u16string SuspiciousSiteControllerAndroid::GetTitle() const { |
| return l10n_util::GetStringUTF16(IDS_SUSPICIOUS_SITE_TITLE); |
| } |
| |
| std::u16string SuspiciousSiteControllerAndroid::GetWarningDetailText() const { |
| return l10n_util::GetStringUTF16(IDS_SUSPICIOUS_SITE_MESSAGE); |
| } |
| |
| // static |
| void SuspiciousSiteControllerAndroid::SetDialogShownCallbackForTesting( |
| base::OnceClosure callback) { |
| *GetShownCallback() = std::move(callback); |
| } |
| |
| // static |
| void SuspiciousSiteControllerAndroid::SetDialogDismissedCallbackForTesting( |
| base::OnceClosure callback) { |
| *GetDismissedCallback() = std::move(callback); |
| } |
| |
| } // namespace safe_browsing |