blob: 0811d04e31ec637acfc28df8862e6eb868230aca [file]
// 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.
#ifndef CHROME_BROWSER_SAFE_BROWSING_ANDROID_SUSPICIOUS_SITE_CONTROLLER_ANDROID_H_
#define CHROME_BROWSER_SAFE_BROWSING_ANDROID_SUSPICIOUS_SITE_CONTROLLER_ANDROID_H_
#include <memory>
#include <optional>
#include <string>
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "base/task/cancelable_task_tracker.h"
#include "base/time/time.h"
#include "components/history/core/browser/history_service.h"
#include "components/safe_browsing/content/browser/async_check_tracker.h"
#include "components/safe_browsing/core/browser/suspicious_site_warning_allowlist.h"
#include "components/security_interstitials/core/unsafe_resource.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
#include "ui/android/modal_dialog_wrapper.h"
namespace content {
class NavigationHandle;
class WebContents;
} // namespace content
namespace safe_browsing {
class SuspiciousSiteDialogViewAndroid;
// A tab helper controller responsible for displaying a suspicious site warning
// dialog on Android for a specific navigation ID. Observes WebContents and
// AsyncCheckTracker to defer displaying the warning until Safe Browsing checks
// complete and the navigation commits, while prioritizing full-page
// interstitials.
class SuspiciousSiteControllerAndroid
: public content::WebContentsObserver,
public content::WebContentsUserData<SuspiciousSiteControllerAndroid>,
public AsyncCheckTracker::Observer {
public:
using WarningOutcome = safe_browsing::SuspiciousSiteWarningOutcome;
using UserInteraction = safe_browsing::SuspiciousSiteWarningUserInteraction;
// Represents user interaction choices for the suspicious site warning HaTS.
enum class UserChoice {
kMarkAsSafe,
kBackToSafety,
kDismiss,
kManualNavigation,
};
SuspiciousSiteControllerAndroid(const SuspiciousSiteControllerAndroid&) =
delete;
SuspiciousSiteControllerAndroid& operator=(
const SuspiciousSiteControllerAndroid&) = delete;
~SuspiciousSiteControllerAndroid() override;
// Displays or stages the suspicious site warning dialog for the given
// navigation.
static void ShowForWebContents(
content::WebContents* web_contents,
const security_interstitials::UnsafeResource& resource);
// WebContentsObserver overrides:
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override;
void OnVisibilityChanged(content::Visibility visibility) override;
// AsyncCheckTracker::Observer overrides:
void OnAsyncSafeBrowsingCheckCompleted() override;
void OnAsyncSafeBrowsingCheckTrackerDestructed() override;
// Called by the dialog view when the dialog is dismissed.
void CloseDialog(ui::ModalDialogWrapper::DismissalCause dismissal_cause);
// Button and link event handlers from the dialog UI:
void HandleBackNavigation(UserInteraction interaction_type);
void OnContinueButtonClicked();
void OnHelpCenterLinkClicked();
// Text getters for the dialog UI:
std::u16string GetPrimaryButtonText() const;
std::u16string GetSecondaryButtonText() const;
std::u16string GetTitle() const;
std::u16string GetWarningDetailText() const;
// Creates and renders the dialog view on the window.
void ShowDialog();
// Testing helpers:
static void SetDialogShownCallbackForTesting(base::OnceClosure callback);
static void SetDialogDismissedCallbackForTesting(base::OnceClosure callback);
private:
friend class content::WebContentsUserData<SuspiciousSiteControllerAndroid>;
friend class SuspiciousSiteControllerAndroidTest;
WEB_CONTENTS_USER_DATA_KEY_DECL();
explicit SuspiciousSiteControllerAndroid(content::WebContents* web_contents);
// Validates all preconditions and triggers ShowDialog if all gates pass.
void MaybeShowDialog();
// Triggers HaTS survey for suspicious site warnings if feature enabled.
void MaybeTriggerHatsSurvey(UserChoice user_choice);
// Queries HistoryService to check if user has visited the host before.
void FetchRepeatVisitCount();
void OnGetVisibleVisitCount(history::VisibleVisitCountToHostResult result);
// Timestamp when the warning dialog was shown.
base::TimeTicks dialog_shown_time_;
// Encapsulates user interaction state and metadata specific to a single
// warning dialog presentation. This state is naturally cleared when the
// controller is destroyed (e.g., when the user navigates away or bypasses).
struct DialogState {
// Tracks if the user clicked the "Learn More" help center link.
bool learn_more_clicked = false;
// Tracks if user has visited the suspicious site host previously.
bool repeat_visit = false;
// Tracks if a history query has already been dispatched for this
// navigation.
bool has_fetched_history = false;
// Tracks accumulated prompt visible time across tab switches.
base::TimeDelta accumulated_visible_time;
};
DialogState dialog_state_;
// Tracker for async history queries.
base::CancelableTaskTracker history_task_tracker_;
// The UnsafeResource that triggered the suspicious site warning. This is
// std::nullopt when the controller is first created, before
// ShowForWebContents() is called for a specific navigation.
std::optional<security_interstitials::UnsafeResource> resource_;
// Navigation ID of the main frame navigation that triggered the suspicious
// site warning. This is std::nullopt when the controller is first created,
// before ShowForWebContents() is called for a specific navigation.
std::optional<int64_t> navigation_id_;
// Tracks whether the target navigation has committed.
bool navigation_committed_ = false;
// Whether the dialog display is currently suspended or waiting for checks to
// finish.
bool is_suspended_ = false;
// Tracks whether the dialog is in the process of closing to prevent
// re-entrancy.
bool is_closing_ = false;
// Tracks whether the dialog has been shown on screen.
bool has_shown_ = false;
// Tracks the warning outcome decision as the user interacts with the warning.
WarningOutcome warning_outcome_ = WarningOutcome::kUnknown;
// Tracks if this controller is observing AsyncCheckTracker.
base::ScopedObservation<AsyncCheckTracker, AsyncCheckTracker::Observer>
async_check_observation_{this};
// The suspicious URL currently added to the allowlist for this warning.
GURL current_suspicious_url_;
// The referrer URL of the suspicious navigation when the warning was shown.
GURL current_referrer_url_;
// View bridge for the Android modal dialog.
std::unique_ptr<SuspiciousSiteDialogViewAndroid> dialog_view_;
base::WeakPtrFactory<SuspiciousSiteControllerAndroid> weak_ptr_factory_{this};
};
} // namespace safe_browsing
#endif // CHROME_BROWSER_SAFE_BROWSING_ANDROID_SUSPICIOUS_SITE_CONTROLLER_ANDROID_H_