blob: 28b002fab483fb5ff7e419530096253d5482125c [file] [log] [blame]
// Copyright 2023 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_UI_VIEWS_AUTOFILL_POPUP_POPUP_ROW_VIEW_H_
#define CHROME_BROWSER_UI_VIEWS_AUTOFILL_POPUP_POPUP_ROW_VIEW_H_
#include <memory>
#include <optional>
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "chrome/browser/ui/autofill/next_idle_barrier.h"
#include "chrome/browser/ui/views/autofill/popup/popup_view_utils.h"
#include "components/input/native_web_keyboard_event.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/events/event_handler.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/views/view.h"
#include "ui/views/view_observer.h"
namespace content {
struct NativeWebKeyboardEvent;
} // namespace content
namespace autofill {
class AutofillPopupController;
class PopupRowContentView;
// `PopupRowView` represents a single selectable popup row. It contains logic
// common to all row types (like selection callbacks or a11y) but it is not
// responsible for the view layout. It expects a `PopupRowContentView` instead.
// It also supports the expanding control depending on whether the suggestion
// has children or not (see `Suggestion::children`).
class PopupRowView : public views::View, public views::ViewObserver {
METADATA_HEADER(PopupRowView, views::View)
public:
// Enum class describing the different cells that a `PopupRowView` can
// contain.
enum class CellType {
// The cell containing the main content of the row.
kContent = 0,
// The cell containing the control elements (such as a delete button).
kControl = 1
};
// Interface used to announce changes in selected cells to accessibility
// frameworks.
class AccessibilitySelectionDelegate {
public:
virtual ~AccessibilitySelectionDelegate() = default;
// Notify accessibility that an item represented by `view` has been
// selected.
virtual void NotifyAXSelection(views::View& view) = 0;
};
// Interface used to keep track of cell selection. This may be needed if the
// parent needs to keep state of which row is currently in order to process
// keyboard events.
class SelectionDelegate {
public:
using CellIndex = std::pair<size_t, PopupRowView::CellType>;
virtual ~SelectionDelegate() = default;
virtual std::optional<CellIndex> GetSelectedCell() const = 0;
virtual void SetSelectedCell(std::optional<CellIndex> cell_index,
PopupCellSelectionSource source) = 0;
};
// Returns the margin on the left and right of the row. When hovering in the
// content cell, this is the distance one sees between the updated
// background and the edge of the row.
static int GetHorizontalMargin();
PopupRowView(AccessibilitySelectionDelegate& a11y_selection_delegate,
SelectionDelegate& selection_delegate,
base::WeakPtr<AutofillPopupController> controller,
int line_number,
std::unique_ptr<PopupRowContentView> content_view);
PopupRowView(const PopupRowView&) = delete;
PopupRowView& operator=(const PopupRowView&) = delete;
~PopupRowView() override;
// views::View:
bool OnMouseDragged(const ui::MouseEvent& event) override;
bool OnMousePressed(const ui::MouseEvent& event) override;
void OnMouseExited(const ui::MouseEvent& event) override;
void OnMouseReleased(const ui::MouseEvent& event) override;
void OnGestureEvent(ui::GestureEvent* event) override;
void OnPaint(gfx::Canvas* canvas) override;
bool GetNeedsNotificationWhenVisibleBoundsChange() const override;
void OnVisibleBoundsChanged() override;
// views::ViewObserver:
void OnViewFocused(views::View* focused_now) override;
// Gets and sets the selected cell within this row.
std::optional<CellType> GetSelectedCell() const { return selected_cell_; }
virtual void SetSelectedCell(std::optional<CellType> cell);
// Sets whether the row's child suggestions are displayed in a sub-popup.
// Note that the row doesn't control the sub-popup, but rather should be
// synced with it by this method.
void SetChildSuggestionsDisplayed(bool child_suggestions_displayed);
// Returns the control cell's bounds. The cell must be present.
gfx::RectF GetControlCellBounds() const;
// Attempts to process a key press `event`. Returns true if it did (and the
// parent no longer needs to handle it).
virtual bool HandleKeyPressEvent(const input::NativeWebKeyboardEvent& event);
// Returns if the popup row is available for selection.
bool IsSelectable() const;
// Returns the view representing the content area of the row.
PopupRowContentView& GetContentView() { return *content_view_; }
// Returns the view representing the suggestions expanding control of the row.
views::View* GetExpandChildSuggestionsView() {
return expand_child_suggestions_view_.get();
}
protected:
base::WeakPtr<AutofillPopupController> controller() { return controller_; }
int line_number() const { return line_number_; }
AccessibilitySelectionDelegate& GetA11ySelectionDelegate() {
return a11y_selection_delegate_.get();
}
// Calls `selection_delegate_` when an event leading to selection is
// triggered on the view, e.g. `ui::EventType::kMouseEntered` or the root
// view is focused. `type` == `std::nullopt` means deselection.
virtual void OnCellSelected(std::optional<CellType> type,
PopupCellSelectionSource source);
private:
// Updates all UI parts that may have changed based on the current state,
// for now they are the background and expand control visibility.
void UpdateUI();
// Updates the background according to the control cell highlighting state.
void UpdateBackground();
// This method is just a getter for the `barrier_for_accepting_` which is
// set `true` when the view's visible part is big enough and was present on
// the screen long enough, see `OnVisibleBoundsChanged()` implementation for
// what these "enough"s mean.
bool IsViewVisibleEnough() const;
// The delegate used for accessibility announcements (implemented by the
// parent view).
const raw_ref<AccessibilitySelectionDelegate> a11y_selection_delegate_;
// The delegate used for selection control (implemented by the parent view).
const raw_ref<SelectionDelegate> selection_delegate_;
// The controller for the parent view.
const base::WeakPtr<AutofillPopupController> controller_;
// The position of the row in the vertical list of suggestions.
const int line_number_;
// Which (if any) cell of this row is currently selected.
std::optional<CellType> selected_cell_;
// The view wrapping the content area of the row.
raw_ptr<PopupRowContentView> content_view_ = nullptr;
base::ScopedObservation<PopupRowContentView, views::ViewObserver>
content_view_observer_{this};
// The view wrapping the control area of the row.
raw_ptr<views::View> expand_child_suggestions_view_ = nullptr;
base::ScopedObservation<views::View, views::ViewObserver>
expand_child_suggestions_view_observer_{this};
// Overriding event handles for the content and control views.
std::unique_ptr<ui::EventHandler> content_event_handler_;
std::unique_ptr<ui::EventHandler> control_event_handler_;
// We want a mouse click to accept a suggestion only if the user has made an
// explicit choice. Therefore, we shall ignore mouse clicks unless the mouse
// has been moved into the item's screen bounds. For example, if the item is
// hovered by the mouse at the time it's first shown, we want to ignore clicks
// until the mouse has left and re-entered the bounds of the item
// (crbug.com/1240472, crbug.com/1241585, crbug.com/1287364).
// This is particularly relevant because mouse click interactions may be
// processed with a delay, making it seem as if the two click interactions of
// a double click were executed at intervals larger than the threshold (500ms)
// checked in the controller (crbug.com/1418837).
bool mouse_observed_outside_item_bounds_ = false;
// Whether the `mouse_observed_outside_item_bounds_` will be ignored or not.
// Today this happens when:
// 1. The AutofillSuggestionTriggerSource is `kManualFallbackAddress`. This is
// because in this situation even though the popup could appear behind the
// cursor, the user intention about opening it is explicit.
//
// 2. The suggestions are of autocomplete type and were regenerated due to a
// suggestion being removed. We want to ignore the check in this case because
// the cursor can be above the popup after a row is deleted. This however does
// not mean that the popup just showed up to the user so there is no need to
// move the cursor out and in.
const bool should_ignore_mouse_observed_outside_item_bounds_check_;
// Whether the row's child suggestions (see `Suggestion::children`) are
// displayed in a sub-popup.
bool child_suggestions_displayed_ = false;
// This is used to protected users from accepting suggestions too quickly.
// This is often used in various attacks against their data when the user is
// tricked to press a key combination or click a specific place on the screen
// (e.g. in a game). Having a delay gives the user a chance to notice/overview
// what they are about to expose to the website.
// The timer starts in `OnVisibleBoundsChanged()` only when the view is
// visible enough and checked before triggering acceptance on the controller.
std::optional<NextIdleBarrier> barrier_for_accepting_;
// Has the same value as `Suggestion::is_acceptable` of the underlying
// suggestion. If `false` the content part is not highlighted separately,
// but the whole row is highlighted instead as for the control view.
const bool suggestion_is_acceptable_;
};
} // namespace autofill
#endif // CHROME_BROWSER_UI_VIEWS_AUTOFILL_POPUP_POPUP_ROW_VIEW_H_