| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/views/autofill/autofill_popup_view_native_views.h" |
| |
| #include <algorithm> |
| |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/ui/autofill/autofill_popup_controller.h" |
| #include "chrome/browser/ui/autofill/autofill_popup_layout_model.h" |
| #include "chrome/browser/ui/autofill/popup_view_common.h" |
| #include "chrome/browser/ui/views/harmony/chrome_layout_provider.h" |
| #include "chrome/browser/ui/views/harmony/chrome_typography.h" |
| #include "chrome/browser/ui/views/harmony/harmony_typography_provider.h" |
| #include "components/autofill/core/browser/popup_item_ids.h" |
| #include "components/autofill/core/browser/suggestion.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/accessibility/ax_node_data.h" |
| #include "ui/accessibility/platform/ax_platform_node.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/gfx/font.h" |
| #include "ui/gfx/geometry/rect_conversions.h" |
| #include "ui/gfx/shadow_value.h" |
| #include "ui/native_theme/native_theme.h" |
| #include "ui/views/accessibility/view_accessibility.h" |
| #include "ui/views/background.h" |
| #include "ui/views/border.h" |
| #include "ui/views/controls/image_view.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/controls/menu/menu_config.h" |
| #include "ui/views/controls/scroll_view.h" |
| #include "ui/views/controls/separator.h" |
| #include "ui/views/layout/box_layout.h" |
| #include "ui/views/layout/fill_layout.h" |
| #include "ui/views/style/typography.h" |
| #include "ui/views/view.h" |
| #include "ui/views/widget/widget.h" |
| |
| namespace { |
| |
| // By spec, dropdowns should have a min width of 64, a max width of 456, and |
| // should always have a width which is a multiple of 12. |
| const int kAutofillPopupWidthMultiple = 12; |
| const int kAutofillPopupMinWidth = 64; |
| const int kAutofillPopupMaxWidth = 456; |
| |
| // A space between the input element and the dropdown, so that the dropdown's |
| // border doesn't look too close to the element. |
| constexpr int kElementBorderPadding = 1; |
| |
| int GetContentsVerticalPadding() { |
| return ChromeLayoutProvider::Get()->GetDistanceMetric( |
| DISTANCE_CONTENT_LIST_VERTICAL_MULTI); |
| } |
| |
| int GetHorizontalMargin() { |
| return views::MenuConfig::instance().item_horizontal_padding + |
| autofill::AutofillPopupBaseView::GetCornerRadius(); |
| } |
| |
| } // namespace |
| |
| namespace autofill { |
| |
| namespace { |
| |
| // This represents a single selectable item. Subclasses distinguish between |
| // footer and suggestion rows, which are structurally similar but have |
| // distinct styling. |
| class AutofillPopupItemView : public AutofillPopupRowView { |
| public: |
| ~AutofillPopupItemView() override = default; |
| |
| // views::View: |
| void GetAccessibleNodeData(ui::AXNodeData* node_data) override; |
| void OnMouseEntered(const ui::MouseEvent& event) override; |
| void OnMouseExited(const ui::MouseEvent& event) override; |
| void OnMouseReleased(const ui::MouseEvent& event) override; |
| |
| protected: |
| AutofillPopupItemView(AutofillPopupViewNativeViews* popup_view, |
| int line_number, |
| int extra_height = 0) |
| : AutofillPopupRowView(popup_view, line_number), |
| extra_height_(extra_height) {} |
| |
| // AutofillPopupRowView: |
| void CreateContent() override; |
| void RefreshStyle() override; |
| |
| protected: |
| virtual int GetPrimaryTextStyle() = 0; |
| |
| private: |
| void AddSpacerWithSize(int spacer_width, |
| bool resize, |
| views::BoxLayout* layout); |
| |
| const int extra_height_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AutofillPopupItemView); |
| }; |
| |
| // This represents a suggestion; i.e., a row containing data that will be filled |
| // into the page if selected. |
| class AutofillPopupSuggestionView : public AutofillPopupItemView { |
| public: |
| ~AutofillPopupSuggestionView() override = default; |
| |
| static AutofillPopupSuggestionView* Create( |
| AutofillPopupViewNativeViews* popup_view, |
| int line_number); |
| |
| protected: |
| // AutofillPopupItemView: |
| std::unique_ptr<views::Background> CreateBackground() override; |
| int GetPrimaryTextStyle() override; |
| |
| private: |
| AutofillPopupSuggestionView(AutofillPopupViewNativeViews* popup_view, |
| int line_number); |
| |
| DISALLOW_COPY_AND_ASSIGN(AutofillPopupSuggestionView); |
| }; |
| |
| // This represents an option which appears in the footer of the dropdown, such |
| // as a row which will open the Autofill settings page when selected. |
| class AutofillPopupFooterView : public AutofillPopupItemView { |
| public: |
| ~AutofillPopupFooterView() override = default; |
| |
| static AutofillPopupFooterView* Create( |
| AutofillPopupViewNativeViews* popup_view, |
| int line_number); |
| |
| protected: |
| // AutofillPopupItemView: |
| void CreateContent() override; |
| std::unique_ptr<views::Background> CreateBackground() override; |
| int GetPrimaryTextStyle() override; |
| |
| private: |
| AutofillPopupFooterView(AutofillPopupViewNativeViews* popup_view, |
| int line_number); |
| |
| DISALLOW_COPY_AND_ASSIGN(AutofillPopupFooterView); |
| }; |
| |
| // Draws a separator between sections of the dropdown, namely between datalist |
| // and Autofill suggestions. Note that this is NOT the same as the border on top |
| // of the footer section or the border between footer items. |
| class AutofillPopupSeparatorView : public AutofillPopupRowView { |
| public: |
| ~AutofillPopupSeparatorView() override = default; |
| |
| static AutofillPopupSeparatorView* Create( |
| AutofillPopupViewNativeViews* popup_view, |
| int line_number); |
| |
| // views::View: |
| void GetAccessibleNodeData(ui::AXNodeData* node_data) override; |
| void OnMouseEntered(const ui::MouseEvent& event) override {} |
| void OnMouseExited(const ui::MouseEvent& event) override {} |
| void OnMouseReleased(const ui::MouseEvent& event) override {} |
| |
| protected: |
| // AutofillPopupRowView: |
| void CreateContent() override; |
| void RefreshStyle() override; |
| std::unique_ptr<views::Background> CreateBackground() override; |
| |
| private: |
| AutofillPopupSeparatorView(AutofillPopupViewNativeViews* popup_view, |
| int line_number); |
| |
| DISALLOW_COPY_AND_ASSIGN(AutofillPopupSeparatorView); |
| }; |
| |
| // Draws a row which contains a warning message. |
| class AutofillPopupWarningView : public AutofillPopupRowView { |
| public: |
| ~AutofillPopupWarningView() override = default; |
| |
| static AutofillPopupWarningView* Create( |
| AutofillPopupViewNativeViews* popup_view, |
| int line_number); |
| |
| // views::View: |
| void GetAccessibleNodeData(ui::AXNodeData* node_data) override; |
| void OnMouseEntered(const ui::MouseEvent& event) override {} |
| void OnMouseReleased(const ui::MouseEvent& event) override {} |
| |
| protected: |
| // AutofillPopupRowView: |
| void CreateContent() override; |
| void RefreshStyle() override {} |
| std::unique_ptr<views::Background> CreateBackground() override; |
| |
| private: |
| AutofillPopupWarningView(AutofillPopupViewNativeViews* popup_view, |
| int line_number) |
| : AutofillPopupRowView(popup_view, line_number) {} |
| |
| DISALLOW_COPY_AND_ASSIGN(AutofillPopupWarningView); |
| }; |
| |
| void AutofillPopupItemView::GetAccessibleNodeData(ui::AXNodeData* node_data) { |
| AutofillPopupController* controller = popup_view_->controller(); |
| auto suggestion = controller->GetSuggestionAt(line_number_); |
| std::vector<base::string16> text; |
| text.push_back(suggestion.value); |
| text.push_back(suggestion.label); |
| |
| base::string16 icon_description; |
| if (!suggestion.icon.empty()) { |
| const int id = controller->layout_model().GetIconAccessibleNameResourceId( |
| suggestion.icon); |
| if (id > 0) |
| text.push_back(l10n_util::GetStringUTF16(id)); |
| } |
| node_data->SetName(base::JoinString(text, base::ASCIIToUTF16(" "))); |
| |
| // Options are selectable. |
| node_data->role = ax::mojom::Role::kMenuItem; |
| node_data->AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, |
| is_selected_); |
| |
| // Compute set size and position in set, which must not include separators. |
| int set_size = 0; |
| int pos_in_set = line_number_ + 1; |
| for (int i = 0; i < controller->GetLineCount(); ++i) { |
| if (controller->GetSuggestionAt(i).frontend_id == |
| autofill::POPUP_ITEM_ID_SEPARATOR) { |
| if (i < line_number_) |
| --pos_in_set; |
| } else { |
| ++set_size; |
| } |
| } |
| node_data->AddIntAttribute(ax::mojom::IntAttribute::kSetSize, set_size); |
| node_data->AddIntAttribute(ax::mojom::IntAttribute::kPosInSet, pos_in_set); |
| } |
| |
| void AutofillPopupItemView::OnMouseEntered(const ui::MouseEvent& event) { |
| AutofillPopupController* controller = popup_view_->controller(); |
| if (controller) |
| controller->SetSelectedLine(line_number_); |
| } |
| |
| void AutofillPopupItemView::OnMouseExited(const ui::MouseEvent& event) { |
| AutofillPopupController* controller = popup_view_->controller(); |
| if (controller) |
| controller->SelectionCleared(); |
| } |
| |
| void AutofillPopupItemView::OnMouseReleased(const ui::MouseEvent& event) { |
| AutofillPopupController* controller = popup_view_->controller(); |
| if (controller && event.IsOnlyLeftMouseButton() && |
| HitTestPoint(event.location())) { |
| controller->AcceptSuggestion(line_number_); |
| } |
| } |
| |
| void AutofillPopupItemView::CreateContent() { |
| AutofillPopupController* controller = popup_view_->controller(); |
| auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::kHorizontal, gfx::Insets(0, GetHorizontalMargin()))); |
| |
| layout->set_cross_axis_alignment( |
| views::BoxLayout::CrossAxisAlignment::CROSS_AXIS_ALIGNMENT_STRETCH); |
| layout->set_minimum_cross_axis_size( |
| views::MenuConfig::instance().touchable_menu_height + extra_height_); |
| |
| // TODO(crbug.com/831603): Remove elision responsibilities from controller. |
| views::Label* text_label = new views::Label( |
| controller->GetElidedValueAt(line_number_), |
| {views::style::GetFont(ChromeTextContext::CONTEXT_BODY_TEXT_LARGE, |
| GetPrimaryTextStyle())}); |
| text_label->SetEnabledColor( |
| views::style::GetColor(*this, ChromeTextContext::CONTEXT_BODY_TEXT_LARGE, |
| GetPrimaryTextStyle())); |
| |
| AddChildView(text_label); |
| |
| AddSpacerWithSize(views::MenuConfig::instance().item_horizontal_padding, |
| /*resize=*/true, layout); |
| |
| const base::string16& description_text = |
| controller->GetElidedLabelAt(line_number_); |
| if (!description_text.empty()) { |
| views::Label* subtext_label = new views::Label( |
| description_text, |
| {views::style::GetFont(ChromeTextContext::CONTEXT_BODY_TEXT_LARGE, |
| ChromeTextStyle::STYLE_SECONDARY)}); |
| subtext_label->SetEnabledColor(views::style::GetColor( |
| *this, ChromeTextContext::CONTEXT_BODY_TEXT_LARGE, |
| ChromeTextStyle::STYLE_SECONDARY)); |
| |
| AddChildView(subtext_label); |
| } |
| |
| const gfx::ImageSkia icon = |
| controller->layout_model().GetIconImage(line_number_); |
| if (!icon.isNull()) { |
| AddSpacerWithSize(views::MenuConfig::instance().item_horizontal_padding, |
| /*resize=*/false, layout); |
| |
| auto* image_view = new views::ImageView(); |
| image_view->SetImage(icon); |
| |
| AddChildView(image_view); |
| } |
| } |
| |
| void AutofillPopupItemView::RefreshStyle() { |
| SetBackground(CreateBackground()); |
| SchedulePaint(); |
| } |
| |
| void AutofillPopupItemView::AddSpacerWithSize(int spacer_width, |
| bool resize, |
| views::BoxLayout* layout) { |
| auto* spacer = new views::View; |
| spacer->SetPreferredSize(gfx::Size(spacer_width, 1)); |
| AddChildView(spacer); |
| layout->SetFlexForView(spacer, /*flex=*/resize ? 1 : 0); |
| } |
| |
| // static |
| AutofillPopupSuggestionView* AutofillPopupSuggestionView::Create( |
| AutofillPopupViewNativeViews* popup_view, |
| int line_number) { |
| AutofillPopupSuggestionView* result = |
| new AutofillPopupSuggestionView(popup_view, line_number); |
| result->Init(); |
| return result; |
| } |
| |
| std::unique_ptr<views::Background> |
| AutofillPopupSuggestionView::CreateBackground() { |
| return views::CreateSolidBackground( |
| is_selected_ ? AutofillPopupBaseView::kSelectedBackgroundColor |
| : AutofillPopupBaseView::kBackgroundColor); |
| } |
| |
| int AutofillPopupSuggestionView::GetPrimaryTextStyle() { |
| return views::style::TextStyle::STYLE_PRIMARY; |
| } |
| |
| AutofillPopupSuggestionView::AutofillPopupSuggestionView( |
| AutofillPopupViewNativeViews* popup_view, |
| int line_number) |
| : AutofillPopupItemView(popup_view, line_number) { |
| SetFocusBehavior(FocusBehavior::ALWAYS); |
| } |
| |
| // static |
| AutofillPopupFooterView* AutofillPopupFooterView::Create( |
| AutofillPopupViewNativeViews* popup_view, |
| int line_number) { |
| AutofillPopupFooterView* result = |
| new AutofillPopupFooterView(popup_view, line_number); |
| result->Init(); |
| return result; |
| } |
| |
| void AutofillPopupFooterView::CreateContent() { |
| SetBorder(views::CreateSolidSidedBorder( |
| /*top=*/views::MenuConfig::instance().separator_thickness, |
| /*left=*/0, |
| /*bottom=*/0, |
| /*right=*/0, |
| /*color=*/AutofillPopupBaseView::kSeparatorColor)); |
| AutofillPopupItemView::CreateContent(); |
| } |
| |
| std::unique_ptr<views::Background> AutofillPopupFooterView::CreateBackground() { |
| return views::CreateSolidBackground( |
| is_selected_ ? AutofillPopupBaseView::kSelectedBackgroundColor |
| : AutofillPopupBaseView::kFooterBackgroundColor); |
| } |
| |
| int AutofillPopupFooterView::GetPrimaryTextStyle() { |
| return ChromeTextStyle::STYLE_SECONDARY; |
| } |
| |
| AutofillPopupFooterView::AutofillPopupFooterView( |
| AutofillPopupViewNativeViews* popup_view, |
| int line_number) |
| : AutofillPopupItemView(popup_view, |
| line_number, |
| AutofillPopupBaseView::GetCornerRadius()) { |
| SetFocusBehavior(FocusBehavior::ALWAYS); |
| } |
| |
| // static |
| AutofillPopupSeparatorView* AutofillPopupSeparatorView::Create( |
| AutofillPopupViewNativeViews* popup_view, |
| int line_number) { |
| AutofillPopupSeparatorView* result = |
| new AutofillPopupSeparatorView(popup_view, line_number); |
| result->Init(); |
| return result; |
| } |
| |
| void AutofillPopupSeparatorView::GetAccessibleNodeData( |
| ui::AXNodeData* node_data) { |
| // Separators are not selectable. |
| node_data->role = ax::mojom::Role::kSplitter; |
| } |
| |
| void AutofillPopupSeparatorView::CreateContent() { |
| SetLayoutManager(std::make_unique<views::FillLayout>()); |
| |
| views::Separator* separator = new views::Separator(); |
| separator->SetColor(AutofillPopupBaseView::kSeparatorColor); |
| // Add some spacing between the the previous item and the separator. |
| separator->SetPreferredHeight( |
| views::MenuConfig::instance().separator_thickness); |
| separator->SetBorder(views::CreateEmptyBorder( |
| /*top=*/GetContentsVerticalPadding(), |
| /*left=*/0, |
| /*bottom=*/0, |
| /*right=*/0)); |
| AddChildView(separator); |
| } |
| |
| void AutofillPopupSeparatorView::RefreshStyle() { |
| SchedulePaint(); |
| } |
| |
| std::unique_ptr<views::Background> |
| AutofillPopupSeparatorView::CreateBackground() { |
| return views::CreateSolidBackground(SK_ColorWHITE); |
| } |
| |
| AutofillPopupSeparatorView::AutofillPopupSeparatorView( |
| AutofillPopupViewNativeViews* popup_view, |
| int line_number) |
| : AutofillPopupRowView(popup_view, line_number) { |
| SetFocusBehavior(FocusBehavior::NEVER); |
| } |
| |
| // static |
| AutofillPopupWarningView* AutofillPopupWarningView::Create( |
| AutofillPopupViewNativeViews* popup_view, |
| int line_number) { |
| AutofillPopupWarningView* result = |
| new AutofillPopupWarningView(popup_view, line_number); |
| result->Init(); |
| return result; |
| } |
| |
| void AutofillPopupWarningView::GetAccessibleNodeData( |
| ui::AXNodeData* node_data) { |
| AutofillPopupController* controller = popup_view_->controller(); |
| if (controller) |
| return; |
| |
| node_data->SetName(controller->GetSuggestionAt(line_number_).value); |
| node_data->role = ax::mojom::Role::kStaticText; |
| } |
| |
| void AutofillPopupWarningView::CreateContent() { |
| AutofillPopupController* controller = popup_view_->controller(); |
| |
| int horizontal_margin = GetHorizontalMargin(); |
| int vertical_margin = AutofillPopupBaseView::GetCornerRadius(); |
| |
| SetLayoutManager(std::make_unique<views::FillLayout>()); |
| SetBorder(views::CreateEmptyBorder( |
| gfx::Insets(vertical_margin, horizontal_margin))); |
| |
| views::Label* text_label = new views::Label( |
| controller->GetElidedValueAt(line_number_), |
| {views::style::GetFont(ChromeTextContext::CONTEXT_BODY_TEXT_LARGE, |
| ChromeTextStyle::STYLE_RED)}); |
| text_label->SetEnabledColor(AutofillPopupBaseView::kWarningColor); |
| text_label->SetMultiLine(true); |
| int max_width = |
| std::min(kAutofillPopupMaxWidth, |
| PopupViewCommon().CalculateMaxWidth( |
| gfx::ToEnclosingRect(controller->element_bounds()), |
| controller->container_view())); |
| max_width -= 2 * horizontal_margin; |
| text_label->SetMaximumWidth(max_width); |
| text_label->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT); |
| |
| AddChildView(text_label); |
| } |
| |
| std::unique_ptr<views::Background> |
| AutofillPopupWarningView::CreateBackground() { |
| return views::CreateSolidBackground(SK_ColorWHITE); |
| } |
| |
| } // namespace |
| |
| void AutofillPopupRowView::SetSelected(bool is_selected) { |
| if (is_selected == is_selected_) |
| return; |
| |
| is_selected_ = is_selected; |
| NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true); |
| RefreshStyle(); |
| } |
| |
| bool AutofillPopupRowView::OnMouseDragged(const ui::MouseEvent& event) { |
| return true; |
| } |
| |
| bool AutofillPopupRowView::OnMousePressed(const ui::MouseEvent& event) { |
| return true; |
| } |
| |
| AutofillPopupRowView::AutofillPopupRowView( |
| AutofillPopupViewNativeViews* popup_view, |
| int line_number) |
| : popup_view_(popup_view), line_number_(line_number) { |
| set_notify_enter_exit_on_child(true); |
| } |
| |
| void AutofillPopupRowView::Init() { |
| CreateContent(); |
| RefreshStyle(); |
| } |
| |
| AutofillPopupViewNativeViews::AutofillPopupViewNativeViews( |
| AutofillPopupController* controller, |
| views::Widget* parent_widget) |
| : AutofillPopupBaseView(controller, parent_widget), |
| controller_(controller) { |
| layout_ = SetLayoutManager( |
| std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical)); |
| layout_->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_START); |
| |
| CreateChildViews(); |
| SetBackground(views::CreateSolidBackground(kBackgroundColor)); |
| } |
| |
| AutofillPopupViewNativeViews::~AutofillPopupViewNativeViews() {} |
| |
| void AutofillPopupViewNativeViews::Show() { |
| DoShow(); |
| } |
| |
| void AutofillPopupViewNativeViews::Hide() { |
| // The controller is no longer valid after it hides us. |
| controller_ = nullptr; |
| |
| DoHide(); |
| } |
| |
| void AutofillPopupViewNativeViews::VisibilityChanged(View* starting_from, |
| bool is_visible) { |
| if (is_visible) { |
| // TODO(https://crbug.com/848427) Call this when suggestions become |
| // available at all, even if it not currently visible. |
| ui::AXPlatformNode::OnInputSuggestionsAvailable(); |
| // Fire these the first time a menu is visible. By firing these and the |
| // matching end events, we are telling screen readers that the focus |
| // is only changing temporarily, and the screen reader will restore the |
| // focus back to the appropriate textfield when the menu closes. |
| NotifyAccessibilityEvent(ax::mojom::Event::kMenuStart, true); |
| } else { |
| // TODO(https://crbug.com/848427) Only call if suggestions are actually no |
| // longer available. The suggestions could be hidden but still available, as |
| // is the case when the Escape key is pressed. |
| ui::AXPlatformNode::OnInputSuggestionsUnavailable(); |
| NotifyAccessibilityEvent(ax::mojom::Event::kMenuEnd, true); |
| } |
| } |
| |
| void AutofillPopupViewNativeViews::OnSelectedRowChanged( |
| base::Optional<int> previous_row_selection, |
| base::Optional<int> current_row_selection) { |
| if (previous_row_selection) { |
| rows_[*previous_row_selection]->SetSelected(false); |
| } |
| |
| if (current_row_selection) |
| rows_[*current_row_selection]->SetSelected(true); |
| } |
| |
| void AutofillPopupViewNativeViews::OnSuggestionsChanged() { |
| CreateChildViews(); |
| DoUpdateBoundsAndRedrawPopup(); |
| } |
| |
| void AutofillPopupViewNativeViews::CreateChildViews() { |
| RemoveAllChildViews(true /* delete_children */); |
| rows_.clear(); |
| |
| // Create one container to wrap the "regular" (non-footer) rows. |
| views::View* body_container = new views::View(); |
| views::BoxLayout* body_layout = body_container->SetLayoutManager( |
| std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical)); |
| body_layout->set_main_axis_alignment( |
| views::BoxLayout::MAIN_AXIS_ALIGNMENT_START); |
| |
| int line_number = 0; |
| bool has_footer = false; |
| |
| // Process and add all the suggestions which are in the primary container. |
| // Stop once the first footer item is found, or there are no more items. |
| while (line_number < controller_->GetLineCount()) { |
| switch (controller_->GetSuggestionAt(line_number).frontend_id) { |
| case autofill::PopupItemId::POPUP_ITEM_ID_CLEAR_FORM: |
| case autofill::PopupItemId::POPUP_ITEM_ID_AUTOFILL_OPTIONS: |
| case autofill::PopupItemId::POPUP_ITEM_ID_SCAN_CREDIT_CARD: |
| case autofill::PopupItemId::POPUP_ITEM_ID_CREDIT_CARD_SIGNIN_PROMO: |
| case autofill::PopupItemId::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY: |
| // This is a footer, so this suggestion will be processed later. Don't |
| // increment |line_number|, or else it will be skipped when adding |
| // footer rows below. |
| has_footer = true; |
| break; |
| |
| case autofill::PopupItemId::POPUP_ITEM_ID_SEPARATOR: |
| rows_.push_back(AutofillPopupSeparatorView::Create(this, line_number)); |
| break; |
| |
| case autofill::PopupItemId:: |
| POPUP_ITEM_ID_INSECURE_CONTEXT_PAYMENT_DISABLED_MESSAGE: |
| rows_.push_back(AutofillPopupWarningView::Create(this, line_number)); |
| break; |
| |
| default: |
| rows_.push_back(AutofillPopupSuggestionView::Create(this, line_number)); |
| } |
| |
| if (has_footer) |
| break; |
| body_container->AddChildView(rows_.back()); |
| line_number++; |
| } |
| |
| scroll_view_ = new views::ScrollView(); |
| scroll_view_->set_hide_horizontal_scrollbar(true); |
| scroll_view_->SetContents(body_container); |
| scroll_view_->set_draw_overflow_indicator(false); |
| scroll_view_->ClipHeightTo(0, body_container->GetPreferredSize().height()); |
| |
| // Use an additional container to apply padding outside the scroll view. |
| // This ensures that the rounded corners appear properly by cutting them |
| // out of normal, static padding. |
| views::View* padding_wrapper = new views::View(); |
| padding_wrapper->SetBorder( |
| views::CreateEmptyBorder(gfx::Insets(GetContentsVerticalPadding(), 0))); |
| padding_wrapper->SetLayoutManager(std::make_unique<views::FillLayout>()); |
| padding_wrapper->AddChildView(scroll_view_); |
| AddChildView(padding_wrapper); |
| layout_->SetFlexForView(padding_wrapper, 1); |
| |
| // All the remaining rows (where index >= |line_number|) are part of the |
| // footer. This needs to be in its own container because it should not be |
| // affected by scrolling behavior (it's "sticky") and because it has a |
| // special background color. |
| if (has_footer) { |
| views::View* footer_container = new views::View(); |
| footer_container->SetBackground( |
| views::CreateSolidBackground(kFooterBackgroundColor)); |
| |
| views::BoxLayout* footer_layout = footer_container->SetLayoutManager( |
| std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical)); |
| footer_layout->set_main_axis_alignment( |
| views::BoxLayout::MAIN_AXIS_ALIGNMENT_START); |
| |
| while (line_number < controller_->GetLineCount()) { |
| rows_.push_back(AutofillPopupFooterView::Create(this, line_number)); |
| footer_container->AddChildView(rows_.back()); |
| line_number++; |
| } |
| |
| AddChildView(footer_container); |
| layout_->SetFlexForView(footer_container, 0); |
| } |
| } |
| |
| int AutofillPopupViewNativeViews::AdjustWidth(int width) const { |
| if (width >= kAutofillPopupMaxWidth) |
| return kAutofillPopupMaxWidth; |
| |
| int elem_width = gfx::ToEnclosingRect(controller_->element_bounds()).width(); |
| |
| // If the element width is within the range of legal sizes for the popup, use |
| // it as the min width, so that the popup will align with its edges when |
| // possible. |
| int min_width = (kAutofillPopupMinWidth <= elem_width && |
| elem_width < kAutofillPopupMaxWidth) |
| ? elem_width |
| : kAutofillPopupMinWidth; |
| |
| if (width <= min_width) |
| return min_width; |
| |
| // The popup size is being determined by the contents, rather than the min/max |
| // or the element bounds. Round up to a multiple of |
| // |kAutofillPopupWidthMultiple|. |
| if (width % kAutofillPopupWidthMultiple) { |
| width += |
| (kAutofillPopupWidthMultiple - (width % kAutofillPopupWidthMultiple)); |
| } |
| |
| return width; |
| } |
| |
| void AutofillPopupViewNativeViews::DoUpdateBoundsAndRedrawPopup() { |
| gfx::Size size = CalculatePreferredSize(); |
| gfx::Rect popup_bounds; |
| |
| // When a bubble border is shown, the contents area (inside the shadow) is |
| // supposed to be aligned with input element boundaries. |
| gfx::Rect element_bounds = |
| gfx::ToEnclosingRect(controller_->element_bounds()); |
| // Consider the element is |kElementBorderPadding| pixels larger at the top |
| // and at the bottom in order to reposition the dropdown, so that it doesn't |
| // look too close to the element. |
| element_bounds.Inset(/*horizontal=*/0, /*vertical=*/-kElementBorderPadding); |
| |
| PopupViewCommon().CalculatePopupVerticalBounds(size.height(), element_bounds, |
| controller_->container_view(), |
| &popup_bounds); |
| |
| // Adjust the width to compensate for a scroll bar, if necessary, and for |
| // other rules. |
| int scroll_width = 0; |
| if (size.height() > popup_bounds.height()) { |
| size.set_height(popup_bounds.height()); |
| |
| // Because the preferred size is greater than the bounds available, the |
| // contents will have to scroll. The scroll bar will steal width from the |
| // content and smoosh everything together. Instead, add to the width to |
| // compensate. |
| scroll_width = scroll_view_->GetScrollBarLayoutWidth(); |
| } |
| size.set_width(AdjustWidth(size.width() + scroll_width)); |
| |
| PopupViewCommon().CalculatePopupHorizontalBounds( |
| size.width(), element_bounds, controller_->container_view(), |
| controller_->IsRTL(), &popup_bounds); |
| |
| SetSize(size); |
| |
| popup_bounds.Inset(-GetWidget()->GetRootView()->border()->GetInsets()); |
| GetWidget()->SetBounds(popup_bounds); |
| SetClipPath(); |
| |
| SchedulePaint(); |
| } |
| |
| } // namespace autofill |