1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/ui/views/webauthn/authenticator_request_sheet_view.h"
6 
7 #include <utility>
8 
9 #include "chrome/browser/ui/views/accessibility/non_accessible_image_view.h"
10 #include "chrome/browser/ui/views/chrome_layout_provider.h"
11 #include "chrome/browser/ui/views/chrome_typography.h"
12 #include "chrome/browser/ui/webauthn/authenticator_request_sheet_model.h"
13 #include "chrome/browser/webauthn/authenticator_request_dialog_model.h"
14 #include "chrome/grit/generated_resources.h"
15 #include "components/strings/grit/components_strings.h"
16 #include "components/vector_icons/vector_icons.h"
17 #include "ui/base/l10n/l10n_util.h"
18 #include "ui/gfx/color_utils.h"
19 #include "ui/gfx/paint_vector_icon.h"
20 #include "ui/native_theme/native_theme.h"
21 #include "ui/views/controls/button/image_button.h"
22 #include "ui/views/controls/button/image_button_factory.h"
23 #include "ui/views/controls/label.h"
24 #include "ui/views/controls/progress_bar.h"
25 #include "ui/views/layout/box_layout.h"
26 #include "ui/views/layout/fill_layout.h"
27 
28 namespace {
29 
30 // Fixed height of the illustration shown in the top half of the sheet.
31 constexpr int kIllustrationHeight = 148;
32 
33 // Height of the progress bar style activity indicator shown at the top of some
34 // sheets.
35 constexpr int kActivityIndicatorHeight = 4;
36 
37 using ImageColorScheme = AuthenticatorRequestSheetModel::ImageColorScheme;
38 
39 }  // namespace
40 
41 using views::BoxLayout;
42 
AuthenticatorRequestSheetView(std::unique_ptr<AuthenticatorRequestSheetModel> model)43 AuthenticatorRequestSheetView::AuthenticatorRequestSheetView(
44     std::unique_ptr<AuthenticatorRequestSheetModel> model)
45     : model_(std::move(model)) {}
46 
47 AuthenticatorRequestSheetView::~AuthenticatorRequestSheetView() = default;
48 
ReInitChildViews()49 void AuthenticatorRequestSheetView::ReInitChildViews() {
50   RemoveAllChildViews(true /* delete_children */);
51 
52   // No need to add further spacing between the upper and lower half. The image
53   // is designed to fill the dialog's top half without any border/margins, and
54   // the |lower_half| will already contain the standard dialog borders.
55   SetLayoutManager(std::make_unique<BoxLayout>(
56       BoxLayout::Orientation::kVertical, gfx::Insets(),
57       0 /* between_child_spacing */));
58 
59   std::unique_ptr<views::View> upper_half = CreateIllustrationWithOverlays();
60   std::unique_ptr<views::View> lower_half = CreateContentsBelowIllustration();
61   AddChildView(upper_half.release());
62   AddChildView(lower_half.release());
63   InvalidateLayout();
64 }
65 
GetInitiallyFocusedView()66 views::View* AuthenticatorRequestSheetView::GetInitiallyFocusedView() {
67   return step_specific_content_;
68 }
69 
70 std::unique_ptr<views::View>
BuildStepSpecificContent()71 AuthenticatorRequestSheetView::BuildStepSpecificContent() {
72   return nullptr;
73 }
74 
75 std::unique_ptr<views::View>
CreateIllustrationWithOverlays()76 AuthenticatorRequestSheetView::CreateIllustrationWithOverlays() {
77   const int illustration_width = ChromeLayoutProvider::Get()->GetDistanceMetric(
78       views::DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH);
79   const gfx::Size illustration_size(illustration_width, kIllustrationHeight);
80 
81   // The container view has no layout, so its preferred size is hardcoded to
82   // match the size of the image, and all overlays are absolutely positioned.
83   auto image_with_overlays = std::make_unique<views::View>();
84   image_with_overlays->SetPreferredSize(illustration_size);
85 
86   auto image_view = std::make_unique<NonAccessibleImageView>();
87   step_illustration_ = image_view.get();
88   UpdateIconImageFromModel();
89   image_view->SetSize(illustration_size);
90   image_view->SetVerticalAlignment(views::ImageView::Alignment::kLeading);
91   image_with_overlays->AddChildView(image_view.release());
92 
93   if (model()->IsActivityIndicatorVisible()) {
94     auto activity_indicator = std::make_unique<views::ProgressBar>(
95         kActivityIndicatorHeight, false /* allow_round_corner */);
96     activity_indicator->SetValue(-1 /* inifinite animation */);
97     activity_indicator->SetBackgroundColor(SK_ColorTRANSPARENT);
98     activity_indicator->SetPreferredSize(
99         gfx::Size(illustration_width, kActivityIndicatorHeight));
100     activity_indicator->SizeToPreferredSize();
101     image_with_overlays->AddChildView(activity_indicator.release());
102   }
103 
104   if (model()->IsBackButtonVisible()) {
105     auto back_arrow = views::CreateVectorImageButton(base::BindRepeating(
106         &AuthenticatorRequestSheetModel::OnBack, base::Unretained(model())));
107     back_arrow->SetAccessibleName(l10n_util::GetStringUTF16(
108         IDS_BACK_BUTTON_AUTHENTICATOR_REQUEST_DIALOG));
109 
110     // Position the back button so that there is the standard amount of padding
111     // between the top/left side of the back button and the dialog borders.
112     const gfx::Insets dialog_insets =
113         views::LayoutProvider::Get()->GetDialogInsetsForContentType(
114             views::CONTROL, views::CONTROL);
115     auto color_reference = std::make_unique<views::Label>(
116         base::string16(), views::style::CONTEXT_DIALOG_TITLE,
117         views::style::STYLE_PRIMARY);
118     back_arrow->SizeToPreferredSize();
119     back_arrow->SetX(dialog_insets.left());
120     back_arrow->SetY(dialog_insets.top());
121     back_arrow_ = back_arrow.get();
122     back_arrow_button_ =
123         image_with_overlays->AddChildView(std::move(back_arrow));
124     UpdateIconColors();
125   }
126 
127   return image_with_overlays;
128 }
129 
130 std::unique_ptr<views::View>
CreateContentsBelowIllustration()131 AuthenticatorRequestSheetView::CreateContentsBelowIllustration() {
132   auto contents = std::make_unique<views::View>();
133   BoxLayout* contents_layout =
134       contents->SetLayoutManager(std::make_unique<BoxLayout>(
135           BoxLayout::Orientation::kVertical, gfx::Insets(),
136           views::LayoutProvider::Get()->GetDistanceMetric(
137               views::DISTANCE_UNRELATED_CONTROL_VERTICAL)));
138 
139   contents->SetBorder(views::CreateEmptyBorder(
140       views::LayoutProvider::Get()->GetDialogInsetsForContentType(
141           views::CONTROL, views::CONTROL)));
142 
143   auto label_container = std::make_unique<views::View>();
144   label_container->SetLayoutManager(std::make_unique<BoxLayout>(
145       BoxLayout::Orientation::kVertical, gfx::Insets(),
146       views::LayoutProvider::Get()->GetDistanceMetric(
147           views::DISTANCE_RELATED_CONTROL_VERTICAL)));
148 
149   auto title_label = std::make_unique<views::Label>(
150       model()->GetStepTitle(), views::style::CONTEXT_DIALOG_TITLE,
151       views::style::STYLE_PRIMARY);
152   title_label->SetMultiLine(true);
153   title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
154   title_label->SetAllowCharacterBreak(true);
155   label_container->AddChildView(title_label.release());
156 
157   base::string16 description = model()->GetStepDescription();
158   if (!description.empty()) {
159     auto description_label = std::make_unique<views::Label>(
160         std::move(description), views::style::CONTEXT_DIALOG_BODY_TEXT);
161     description_label->SetMultiLine(true);
162     description_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
163     description_label->SetAllowCharacterBreak(true);
164     label_container->AddChildView(description_label.release());
165   }
166 
167   base::string16 additional_desciption = model()->GetAdditionalDescription();
168   if (!additional_desciption.empty()) {
169     auto label =
170         std::make_unique<views::Label>(std::move(additional_desciption),
171                                        views::style::CONTEXT_DIALOG_BODY_TEXT);
172     label->SetMultiLine(true);
173     label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
174     label->SetAllowCharacterBreak(true);
175     label_container->AddChildView(label.release());
176   }
177 
178   contents->AddChildView(label_container.release());
179 
180   std::unique_ptr<views::View> step_specific_content =
181       BuildStepSpecificContent();
182   if (step_specific_content) {
183     step_specific_content_ = step_specific_content.get();
184     contents->AddChildView(step_specific_content.release());
185     contents_layout->SetFlexForView(step_specific_content_, 1);
186   }
187 
188   base::string16 error = model()->GetError();
189   if (!error.empty()) {
190     auto error_label = std::make_unique<views::Label>(
191         std::move(error), views::style::CONTEXT_LABEL, STYLE_RED);
192     error_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
193     error_label->SetMultiLine(true);
194     error_label_ = contents->AddChildView(std::move(error_label));
195   }
196 
197   return contents;
198 }
199 
OnThemeChanged()200 void AuthenticatorRequestSheetView::OnThemeChanged() {
201   views::View::OnThemeChanged();
202   UpdateIconImageFromModel();
203   UpdateIconColors();
204 }
205 
UpdateIconImageFromModel()206 void AuthenticatorRequestSheetView::UpdateIconImageFromModel() {
207   if (!step_illustration_)
208     return;
209 
210   gfx::IconDescription icon_description(model()->GetStepIllustration(
211       GetNativeTheme()->ShouldUseDarkColors() ? ImageColorScheme::kDark
212                                               : ImageColorScheme::kLight));
213   step_illustration_->SetImage(gfx::CreateVectorIcon(icon_description));
214 }
215 
UpdateIconColors()216 void AuthenticatorRequestSheetView::UpdateIconColors() {
217   if (back_arrow_) {
218     views::SetImageFromVectorIcon(
219         back_arrow_, vector_icons::kBackArrowIcon,
220         color_utils::DeriveDefaultIconColor(views::style::GetColor(
221             *this, views::style::CONTEXT_LABEL, views::style::STYLE_PRIMARY)));
222   }
223 }
224