ui/views/input_protection/README.md
This directory contains the input protection system for the Views framework. The goal of this system is to prevent potentially unintended user interactions with sensitive UI elements. These are elements that perform actions with security, privacy, or system-state implications (e.g., "Allow" buttons on prompts, "Install" buttons for extensions, or "Confirm" buttons for purchases).
By protecting these elements, the system mitigates risks like clickjacking, rapid-fire clicking, and sudden UI appearances (e.g., a dialog popping up right under the user's cursor).
The system consists of a manager and one or more policies:
┌──────────────┐
│ View │
└──────┬───────┘
│
│ Owns
v
┌─────────────────────────────┐
│InputEventActivationProtector│
└──────────────┬──────────────┘
│
│ Delegates to
v
┌──────────────────────────┼──────────────────────────┐
│ │ │
v v v
┌──────────────────────┐ ┌──────────────────────┐ ┌──────────────────────┐
│DefaultInputProtection│ │WindowActivationInput │ │OcclusionAwareInput │
│Policy │ │ProtectionPolicy │ │ProtectionPolicy │
└──────────────────────┘ └──────────────────────┘ └──────────────────────┘
InputEventActivationProtectorThis is the main manager class. It is typically owned by a View that requires
protection (for example, DialogClientView or BubbleFrameView).
IsPossiblyUnintendedInteraction(event, target_view) to check if
an input event should be blocked.InputProtectionPolicy objects and delegates the check
to them. If any policy recommends blocking, the event is blocked.InputProtectionPolicyThe abstract base class for all protection rules. Subclasses must implement:
IsPossiblyUnintendedInteraction(event, target_view, protector): Evaluates if
the event should be blocked.Subclasses can optionally override lifecycle methods to manage their internal state:
OnProtectionStarted(): Called when the protected target becomes visible.OnProtectionStopped(): Called when the protected target is hidden or
destroyed.OnProtectionReset(): Called when a UI change (e.g., layout or window
stationarity change) requires restarting the cooldown.DefaultInputProtectionPolicy:
View's visibility when initialized with the view, or rely
on manual visibility forwarding from the protector otherwise.WindowActivationInputProtectionPolicy:
OcclusionAwareInputProtectionPolicy:
OccludedWidgetInputProtector).To protect a view, add an InputEventActivationProtector member to your view
class.
The constructor you use determines whether the protector automatically installs the default policy or uses a custom configuration.
If you instantiate the protector using the default constructor, it automatically
installs a DefaultInputProtectionPolicy initialized without a view.
First, initialize the protector and add any additional policies (typically in your view's constructor):
// Instantiates with DefaultInputProtectionPolicy installed automatically.
input_protector_ = std::make_unique<InputEventActivationProtector>();
// Add additional policies if needed (all registered policies will be active).
input_protector_->AddPolicy(
std::make_unique<WindowActivationInputProtectionPolicy>(widget));
Then, because the automatically installed default policy does not observe the view, you must manually forward visibility events when the protected view's visibility changes:
input_protector_->VisibilityChanged(is_visible);
If you use the parameterized constructor, the protector installs only the passed policy. Use this to configure custom policies (e.g., in tests to bypass the default cooldown, or for specialized UIs).
// Instantiates with ONLY the window activation policy.
input_protector_ = std::make_unique<InputEventActivationProtector>(
std::make_unique<WindowActivationInputProtectionPolicy>(widget));
// You can still add more policies later if needed:
input_protector_->AddPolicy(std::make_unique<MyCustomPolicy>());
Before handling a sensitive event (e.g., a button click), query the protector:
void MyView::OnButtonPressed(const ui::Event& event) {
if (input_protector_->IsPossiblyUnintendedInteraction(event, this)) {
return; // Block the event
}
// Handle the event...
}
To create a new protection policy:
InputProtectionPolicy.IsPossiblyUnintendedInteraction to define your blocking logic.OnProtectionStarted, OnProtectionStopped, and/or OnProtectionReset to
manage your state (e.g., updating timestamps).views::WidgetObserver) and manage the observation lifecycle.InputEventActivationProtector using
AddPolicy.Example template:
class MyCustomPolicy : public InputProtectionPolicy {
public:
MyCustomPolicy() = default;
~MyCustomPolicy() override = default;
// InputProtectionPolicy:
bool IsPossiblyUnintendedInteraction(
const ui::Event& event,
const View* target_view,
const InputEventActivationProtector& protector) override {
// Return true if the event should be blocked based on your custom logic.
return SomeConditionIsMet(event);
}
void OnProtectionStarted() override {
// Initialize state when target becomes visible.
}
};