| // Copyright 2017 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_VIEW_VIEWS_H_ |
| #define CHROME_BROWSER_UI_VIEWS_AUTOFILL_POPUP_POPUP_VIEW_VIEWS_H_ |
| |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/timer/timer.h" |
| #include "chrome/browser/ui/autofill/autofill_popup_view.h" |
| #include "chrome/browser/ui/views/autofill/popup/popup_base_view.h" |
| #include "chrome/browser/ui/views/autofill/popup/popup_row_view.h" |
| #include "chrome/browser/ui/views/autofill/popup/popup_search_bar_view.h" |
| #include "components/autofill/core/common/aliases.h" |
| #include "content/public/common/input/native_web_keyboard_event.h" |
| #include "third_party/abseil-cpp/absl/types/variant.h" |
| #include "ui/accessibility/ax_action_data.h" |
| #include "ui/base/metadata/metadata_header_macros.h" |
| #include "ui/events/event.h" |
| #include "ui/views/widget/widget.h" |
| |
| namespace views { |
| class BoxLayoutView; |
| class ScrollView; |
| } // namespace views |
| |
| namespace autofill { |
| |
| class AutofillPopupController; |
| class AutofillSuggestionController; |
| class PopupSeparatorView; |
| class PopupTitleView; |
| class PopupWarningView; |
| |
| // Sub-popups and their parent popups are connected by providing children |
| // with links to their parents. This interface defines the API exposed by |
| // these links. |
| class ExpandablePopupParentView { |
| private: |
| friend class PopupViewViews; |
| |
| // Callbacks to notify the parent of the children about hover state changes. |
| // The calls are also propagated to grandparents, so that, no matter how |
| // long the chain of sub-popups is, lower level popups know the hover |
| // status in (grand)children. |
| virtual void OnMouseEnteredInChildren() = 0; |
| virtual void OnMouseExitedInChildren() = 0; |
| }; |
| |
| struct PopupViewSearchBarConfig { |
| // This setting controls the search bar's visibility. If set to 'false', |
| // the search bar won't be displayed, and other settings become irrelevant. |
| bool enabled = false; |
| std::u16string placeholder; |
| }; |
| |
| // Views implementation for the autofill and password suggestion. |
| class PopupViewViews : public PopupBaseView, |
| public AutofillPopupView, |
| public PopupRowView::SelectionDelegate, |
| public ExpandablePopupParentView, |
| public PopupSearchBarView::Delegate { |
| METADATA_HEADER(PopupViewViews, PopupBaseView) |
| |
| public: |
| using RowPointer = absl::variant<PopupRowView*, |
| PopupSeparatorView*, |
| PopupTitleView*, |
| PopupWarningView*>; |
| |
| // The time it takes for a selected cell to open a sub-popup if it has one. |
| static constexpr base::TimeDelta kMouseOpenSubPopupDelay = |
| base::Milliseconds(250); |
| static constexpr base::TimeDelta kNonMouseOpenSubPopupDelay = |
| kMouseOpenSubPopupDelay / 10; |
| |
| // The delay for closing the sub-popup after having no cell selected, |
| // sub-popup cells are also taken into account. |
| static constexpr base::TimeDelta kNoSelectionHideSubPopupDelay = |
| base::Milliseconds(2500); |
| |
| // Constructor for creating sub-popups. |
| PopupViewViews(base::WeakPtr<AutofillPopupController> controller, |
| base::WeakPtr<ExpandablePopupParentView> parent, |
| views::Widget* parent_widget); |
| |
| // Constructor for creating root level popups. |
| explicit PopupViewViews(base::WeakPtr<AutofillPopupController> controller, |
| PopupViewSearchBarConfig search_bar_config = {}); |
| PopupViewViews(const PopupViewViews&) = delete; |
| PopupViewViews& operator=(const PopupViewViews&) = delete; |
| ~PopupViewViews() override; |
| |
| base::WeakPtr<AutofillPopupController> controller() { return controller_; } |
| // Gets and sets the currently selected cell. If an invalid `cell_index` is |
| // passed, `GetSelectedCell()` will return `std::nullopt` afterwards. |
| std::optional<CellIndex> GetSelectedCell() const override; |
| void SetSelectedCell(std::optional<CellIndex> cell_index, |
| PopupCellSelectionSource source) override; |
| |
| // views::View: |
| void GetAccessibleNodeData(ui::AXNodeData* node_data) override; |
| void OnMouseEntered(const ui::MouseEvent& event) override; |
| void OnMouseExited(const ui::MouseEvent& event) override; |
| |
| // AutofillPopupView: |
| bool Show(AutoselectFirstSuggestion autoselect_first_suggestion) override; |
| void Hide() override; |
| bool OverlapsWithPictureInPictureWindow() const override; |
| std::optional<int32_t> GetAxUniqueId() override; |
| void AxAnnounce(const std::u16string& text) override; |
| base::WeakPtr<AutofillPopupView> CreateSubPopupView( |
| base::WeakPtr<AutofillSuggestionController> controller) override; |
| std::optional<AutofillClient::PopupScreenLocation> GetPopupScreenLocation() |
| const override; |
| bool HasFocus() const override; |
| base::WeakPtr<AutofillPopupView> GetWeakPtr() override; |
| |
| // PopupBaseView: |
| void OnWidgetVisibilityChanged(views::Widget* widget, bool visible) override; |
| |
| // PopupSearchBarView::Delegate: |
| bool SearchBarHandleKeyPressed(const ui::KeyEvent& event) override; |
| |
| private: |
| friend class PopupViewViewsTestApi; |
| |
| // Sets the `cell_index` cell as selected (or just unselects the selected |
| // cell if `nullopt` is passed). Only one cell can be selected at a time. |
| // Depending on the cell type and the suggestion, it makes the cell or |
| // the whole row highlighted. It may also trigger the form preview or |
| // a sub-popup to open, which depends on the suggestion acceptability, whether |
| // it has children children and the cell type. `autoselect_first_suggestion` |
| // and `suppress_popup` are relevant for cases when setting the cell triggers |
| // a sub-popup to open. Setting `suppress_popup` to `true` prevents |
| // the sub-popup from opening in such cases. `autoselect_first_suggestion` |
| // controls whether the first suggestion in the sub-popup will be selected, |
| // also capturing the keyboard focus if it is `true`. |
| void SetSelectedCell(std::optional<CellIndex> cell_index, |
| PopupCellSelectionSource source, |
| AutoselectFirstSuggestion autoselect_first_suggestion, |
| bool suppress_popup = false); |
| |
| // Returns the `PopupRowView` at line number `index`. Assumes that there is |
| // such a view at that line number - otherwise the underlying variant will |
| // check false. |
| PopupRowView& GetPopupRowViewAt(size_t index) { |
| return *absl::get<PopupRowView*>(rows_[index]); |
| } |
| const PopupRowView& GetPopupRowViewAt(size_t index) const { |
| return *absl::get<PopupRowView*>(rows_[index]); |
| } |
| |
| // Returns whether the row at `index` exists and is a `PopupRowView`. |
| bool HasPopupRowViewAt(size_t index) const; |
| |
| // Instantiates the content of the popup. |
| void InitViews(PopupViewSearchBarConfig search_bar_config); |
| |
| // Creates child views based on the suggestions given by |controller_|. |
| // This method expects that all non-footer suggestions precede footer |
| // suggestions. |
| void CreateSuggestionViews(); |
| |
| // Applies certain rounding rules to the given width, such as matching the |
| // element width when possible. |
| int AdjustWidth(int width) const; |
| |
| // Selects the first row prior to the currently selected one that is |
| // selectable (e.g. not a separator). If no row is selected or no row prior to |
| // the current one is selectable, it tries to select the last row. If that one |
| // is unselectable, no row is selected. |
| void SelectPreviousRow(); |
| |
| // Analogous to previous row, just in the opposite direction: Tries to find |
| // the next selectable row after the currently selected one. If no row is |
| // selected or no row following the currently selected one is selectable, it |
| // tries to select the first row. If that one is unselectable, no row is |
| // selected. |
| void SelectNextRow(); |
| |
| // Selects the next/previous in horizontal direction (i.e. left to right or |
| // vice versa) cell, if there is one. Otherwise leaves the current selection. |
| // Does not wrap. |
| bool SelectNextHorizontalCell(); |
| bool SelectPreviousHorizontalCell(); |
| |
| // Attempts to accept the selected cell. It will return false if there is no |
| // selected cell or the cell does not trigger field filling or scanning a |
| // credit card. |
| bool AcceptSelectedContentOrCreditCardCell(); |
| |
| // Attempts to remove the selected cell. Only content cells are allowed to be |
| // selected. |
| bool RemoveSelectedCell(); |
| |
| // Reacts to key events under the assumption that the currently shown popup |
| // contains Compose content. |
| bool HandleKeyPressEventForCompose( |
| const content::NativeWebKeyboardEvent& event); |
| |
| // AutofillPopupView: |
| bool HandleKeyPressEvent( |
| const content::NativeWebKeyboardEvent& event) override; |
| void OnSuggestionsChanged() override; |
| |
| // PopupBaseView: |
| bool DoUpdateBoundsAndRedrawPopup() override; |
| |
| // ExpandablePopupParentView: |
| void OnMouseEnteredInChildren() override; |
| void OnMouseExitedInChildren() override; |
| |
| // Returns whether the footer container is scrollable with other suggestions |
| // or it is "sticky" (i.e. it has a fixed position, always visible and |
| // the non-footer suggestions are scrolled independently). |
| bool IsFooterScrollable() const; |
| |
| bool CanShowDropdownInBounds(const gfx::Rect& bounds) const; |
| |
| // Opens a sub-popup on a new row (and closes the open one if any), or just |
| // closes the existing if `std::nullopt` is passed. |
| void SetRowWithOpenSubPopup( |
| std::optional<size_t> row_index, |
| AutoselectFirstSuggestion autoselect_first_suggestion = |
| AutoselectFirstSuggestion(false)); |
| |
| // Returns true when fields `is_acceptable` and `apply_style_deactivated` are |
| // false for the suggestion as it indicates that the suggestion is a manual |
| // fallback suggestion. |
| bool CanOpenSubPopupSuggestion(const Suggestion& suggestion); |
| |
| // Callback passed to the search bar (if enabled). Hides the popup. |
| void OnSearchBarFocusLost(); |
| |
| // Callback passed to the search bar (if enabled). Updates the controller |
| // filter with the `query` argument. |
| void OnSearchBarInputChanged(const std::u16string& query); |
| |
| // Attempts to select the content cell of the row with the currently open |
| // sub-popup. This closes the sub-popup and has the effect of going one menu |
| // level up. Returns whether this was successful. |
| bool SelectParentPopupContentCell(); |
| |
| // Controller for this view. |
| base::WeakPtr<AutofillPopupController> controller_ = nullptr; |
| |
| // Parent's popup view. Present in sub-popups (non-root) only. |
| std::optional<base::WeakPtr<ExpandablePopupParentView>> parent_; |
| |
| // The index of the row with a selected cell. |
| std::optional<size_t> row_with_selected_cell_; |
| |
| // The latest row which was set as having a sub-popup open. Storing it |
| // is required to maintain the invariant of at most one such a row. |
| std::optional<size_t> row_with_open_sub_popup_; |
| |
| std::vector<RowPointer> rows_; |
| raw_ptr<PopupSearchBarView> search_bar_ = nullptr; |
| raw_ptr<views::BoxLayoutView> suggestions_container_ = nullptr; |
| raw_ptr<views::ScrollView> scroll_view_ = nullptr; |
| raw_ptr<views::BoxLayoutView> body_container_ = nullptr; |
| raw_ptr<views::BoxLayoutView> footer_container_ = nullptr; |
| |
| base::OneShotTimer open_sub_popup_timer_; |
| base::OneShotTimer no_selection_sub_popup_close_timer_; |
| |
| // Defines whether the popup handles keyboard events like UP/DOWN/ESC/etc. |
| // This value is important for defining which popup handles the event when |
| // a chain of (sub-)popups is open: having no focus for a sub-popup means |
| // that its parent will take care of handling it. |
| // It's automatically set `true` for the root popup (so that it always handles |
| // events) and when something is selected in sub-popups. The initial value is |
| // set in `Show()`, but after that once it is `true` the value never gets back |
| // to `false.` |
| bool has_keyboard_focus_ = false; |
| |
| // A boolean variable tracking the execution state of the `Show()` method. |
| // It's set to `true` when the method starts and `false` before it finishes. |
| // Required for the `HasFocus()` method, that might be called during |
| // the `Show()` execution. In this case, `GetWidget()->IsActive()` may not yet |
| // be `true`, and the `HasFocus()` returning `false` may potentially cause |
| // the popup to hide. |
| bool show_in_progress_ = false; |
| |
| base::WeakPtrFactory<PopupViewViews> weak_ptr_factory_{this}; |
| }; |
| |
| } // namespace autofill |
| |
| #endif // CHROME_BROWSER_UI_VIEWS_AUTOFILL_POPUP_POPUP_VIEW_VIEWS_H_ |