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/policy/enterprise_startup_dialog_view.h"
6
7 #include <utility>
8
9 #include "base/bind.h"
10 #include "base/i18n/message_formatter.h"
11 #include "base/strings/string16.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/threading/thread_task_runner_handle.h"
14 #include "build/branding_buildflags.h"
15 #include "build/build_config.h"
16 #include "chrome/app/vector_icons/vector_icons.h"
17 #include "chrome/browser/ui/views/chrome_layout_provider.h"
18 #include "chrome/grit/chromium_strings.h"
19 #include "chrome/grit/theme_resources.h"
20 #include "components/constrained_window/constrained_window_views.h"
21 #include "ui/base/l10n/l10n_util.h"
22 #include "ui/base/resource/resource_bundle.h"
23 #include "ui/gfx/color_palette.h"
24 #include "ui/gfx/paint_vector_icon.h"
25 #include "ui/native_theme/native_theme.h"
26 #include "ui/views/background.h"
27 #include "ui/views/border.h"
28 #include "ui/views/controls/button/label_button.h"
29 #include "ui/views/controls/image_view.h"
30 #include "ui/views/controls/label.h"
31 #include "ui/views/controls/throbber.h"
32 #include "ui/views/layout/grid_layout.h"
33
34 #if defined(OS_MAC)
35 #include "base/task/current_thread.h"
36 #include "chrome/browser/ui/views/policy/enterprise_startup_dialog_mac_util.h"
37 #endif
38
39 namespace policy {
40 namespace {
41
42 constexpr int kDialogContentHeight = 190; // The height of dialog content area.
43 constexpr int kDialogContentWidth = 670; // The width of dialog content area.
44 constexpr int kIconSize = 24; // The size of throbber and error icon.
45 constexpr int kLineHeight = 22; // The height of text line.
46 constexpr int kFontSizeDelta = 3; // The font size of text.
47
48 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
49 constexpr int kLogoHeight = 20; // The height of Chrome enterprise logo.
50 #endif
51
GetDialogInsets()52 gfx::Insets GetDialogInsets() {
53 return ChromeLayoutProvider::Get()->GetDialogInsetsForContentType(
54 views::CONTROL, views::TEXT);
55 }
56
CreateText(const base::string16 & message)57 std::unique_ptr<views::Label> CreateText(const base::string16& message) {
58 auto text = std::make_unique<views::Label>(message);
59 text->SetFontList(gfx::FontList().Derive(kFontSizeDelta, gfx::Font::NORMAL,
60 gfx::Font::Weight::MEDIUM));
61 text->SetEnabledColor(
62 views::style::GetColor(*text, views::style::CONTEXT_DIALOG_BODY_TEXT,
63 views::style::STYLE_PRIMARY));
64 text->SetLineHeight(kLineHeight);
65 return text;
66 }
67
CreateLogoView()68 std::unique_ptr<views::View> CreateLogoView() {
69 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
70 // Show Google Chrome Enterprise logo only for official build.
71 auto logo_image = std::make_unique<views::ImageView>();
72 logo_image->SetImage(
73 ui::ResourceBundle::GetSharedInstance()
74 .GetImageNamed((logo_image->GetNativeTheme()->ShouldUseDarkColors())
75 ? IDR_PRODUCT_LOGO_ENTERPRISE_WHITE
76 : IDR_PRODUCT_LOGO_ENTERPRISE)
77 .AsImageSkia());
78 logo_image->SetTooltipText(
79 l10n_util::GetStringUTF16(IDS_PRODUCT_LOGO_ENTERPRISE_ALT_TEXT));
80 gfx::Rect logo_bounds = logo_image->GetImageBounds();
81 logo_image->SetImageSize(gfx::Size(
82 logo_bounds.width() * kLogoHeight / logo_bounds.height(), kLogoHeight));
83 logo_image->SetVerticalAlignment(views::ImageView::Alignment::kCenter);
84 return logo_image;
85 #else
86 return nullptr;
87 #endif
88 }
89
90 } // namespace
91
EnterpriseStartupDialogView(EnterpriseStartupDialog::DialogResultCallback callback)92 EnterpriseStartupDialogView::EnterpriseStartupDialogView(
93 EnterpriseStartupDialog::DialogResultCallback callback)
94 : callback_(std::move(callback)) {
95 set_draggable(true);
96 SetButtons(ui::DIALOG_BUTTON_OK);
97 SetExtraView(CreateLogoView());
98 SetAcceptCallback(
99 base::BindOnce(&EnterpriseStartupDialogView::RunDialogCallback,
100 base::Unretained(this), true));
101 SetCancelCallback(
102 base::BindOnce(&EnterpriseStartupDialogView::RunDialogCallback,
103 base::Unretained(this), false));
104 SetCloseCallback(
105 base::BindOnce(&EnterpriseStartupDialogView::RunDialogCallback,
106 base::Unretained(this), false));
107 SetBorder(views::CreateEmptyBorder(GetDialogInsets()));
108 CreateDialogWidget(this, nullptr, nullptr)->Show();
109 #if defined(OS_MAC)
110 base::ThreadTaskRunnerHandle::Get()->PostTask(
111 FROM_HERE, base::BindOnce(&EnterpriseStartupDialogView::StartModalDialog,
112 weak_factory_.GetWeakPtr()));
113 #endif
114 }
115
~EnterpriseStartupDialogView()116 EnterpriseStartupDialogView::~EnterpriseStartupDialogView() {}
117
DisplayLaunchingInformationWithThrobber(const base::string16 & information)118 void EnterpriseStartupDialogView::DisplayLaunchingInformationWithThrobber(
119 const base::string16& information) {
120 ResetDialog(false);
121
122 std::unique_ptr<views::Label> text = CreateText(information);
123 auto throbber = std::make_unique<views::Throbber>();
124 gfx::Size throbber_size = gfx::Size(kIconSize, kIconSize);
125 throbber->SetPreferredSize(throbber_size);
126 throbber->Start();
127
128 SetupLayout(std::move(throbber), std::move(text));
129 }
130
DisplayErrorMessage(const base::string16 & error_message,const base::Optional<base::string16> & accept_button)131 void EnterpriseStartupDialogView::DisplayErrorMessage(
132 const base::string16& error_message,
133 const base::Optional<base::string16>& accept_button) {
134 ResetDialog(accept_button.has_value());
135 std::unique_ptr<views::Label> text = CreateText(error_message);
136 auto error_icon = std::make_unique<views::ImageView>();
137 error_icon->SetImage(
138 gfx::CreateVectorIcon(kBrowserToolsErrorIcon, kIconSize,
139 GetNativeTheme()->GetSystemColor(
140 ui::NativeTheme::kColorId_AlertSeverityHigh)));
141
142 if (accept_button) {
143 // TODO(ellyjones): This should use SetButtonLabel()
144 // instead of changing the button text directly - this might break the
145 // dialog's layout.
146 GetOkButton()->SetText(*accept_button);
147 }
148 SetupLayout(std::move(error_icon), std::move(text));
149 }
150
CloseDialog()151 void EnterpriseStartupDialogView::CloseDialog() {
152 can_show_browser_window_ = true;
153 GetWidget()->Close();
154 }
155
AddWidgetObserver(views::WidgetObserver * observer)156 void EnterpriseStartupDialogView::AddWidgetObserver(
157 views::WidgetObserver* observer) {
158 GetWidget()->AddObserver(observer);
159 }
RemoveWidgetObserver(views::WidgetObserver * observer)160 void EnterpriseStartupDialogView::RemoveWidgetObserver(
161 views::WidgetObserver* observer) {
162 GetWidget()->RemoveObserver(observer);
163 }
164
StartModalDialog()165 void EnterpriseStartupDialogView::StartModalDialog() {
166 #if defined(OS_MAC)
167 base::CurrentThread::ScopedNestableTaskAllower allow_nested;
168 StartModal(GetWidget()->GetNativeWindow());
169 #endif
170 }
171
RunDialogCallback(bool was_accepted)172 void EnterpriseStartupDialogView::RunDialogCallback(bool was_accepted) {
173 #if defined(OS_MAC)
174 // On mac, we need to stop the modal message loop before returning the result
175 // to the caller who controls its own run loop.
176 StopModal();
177 if (can_show_browser_window_) {
178 std::move(callback_).Run(was_accepted, can_show_browser_window_);
179 } else {
180 base::ThreadTaskRunnerHandle::Get()->PostTask(
181 FROM_HERE, base::BindOnce(std::move(callback_), was_accepted,
182 can_show_browser_window_));
183 }
184 #else
185 std::move(callback_).Run(was_accepted, can_show_browser_window_);
186 #endif
187 }
188
ShouldShowWindowTitle() const189 bool EnterpriseStartupDialogView::ShouldShowWindowTitle() const {
190 return false;
191 }
192
GetModalType() const193 ui::ModalType EnterpriseStartupDialogView::GetModalType() const {
194 return ui::MODAL_TYPE_NONE;
195 }
196
CalculatePreferredSize() const197 gfx::Size EnterpriseStartupDialogView::CalculatePreferredSize() const {
198 return gfx::Size(kDialogContentWidth, kDialogContentHeight);
199 }
200
ResetDialog(bool show_accept_button)201 void EnterpriseStartupDialogView::ResetDialog(bool show_accept_button) {
202 DCHECK(GetOkButton());
203
204 GetOkButton()->SetVisible(show_accept_button);
205 RemoveAllChildViews(true);
206 }
207
SetupLayout(std::unique_ptr<views::View> icon,std::unique_ptr<views::View> text)208 void EnterpriseStartupDialogView::SetupLayout(
209 std::unique_ptr<views::View> icon,
210 std::unique_ptr<views::View> text) {
211 // Padding between icon and text
212 int text_padding = ChromeLayoutProvider::Get()->GetDistanceMetric(
213 views::DISTANCE_TEXTFIELD_HORIZONTAL_TEXT_PADDING);
214
215 views::GridLayout* layout =
216 SetLayoutManager(std::make_unique<views::GridLayout>());
217 auto* columnset = layout->AddColumnSet(0);
218 // Horizontally centre the content.
219 columnset->AddPaddingColumn(1.0, 0);
220 columnset->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
221 views::GridLayout::kFixedSize,
222 views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
223 columnset->AddPaddingColumn(views::GridLayout::kFixedSize, text_padding);
224 columnset->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
225 views::GridLayout::kFixedSize,
226 views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
227 columnset->AddPaddingColumn(1.0, 0);
228
229 layout->AddPaddingRow(1.0, 0);
230 layout->StartRow(views::GridLayout::kFixedSize, 0);
231 layout->AddView(std::move(icon));
232 layout->AddView(std::move(text));
233 layout->AddPaddingRow(1.0, 0);
234
235 // TODO(ellyjones): Why is this being done here?
236 GetWidget()->GetRootView()->Layout();
237 GetWidget()->GetRootView()->SchedulePaint();
238 }
239
240 /*
241 * EnterpriseStartupDialogImpl
242 */
243
EnterpriseStartupDialogImpl(DialogResultCallback callback)244 EnterpriseStartupDialogImpl::EnterpriseStartupDialogImpl(
245 DialogResultCallback callback) {
246 dialog_view_ = new EnterpriseStartupDialogView(std::move(callback));
247 dialog_view_->AddWidgetObserver(this);
248 }
249
~EnterpriseStartupDialogImpl()250 EnterpriseStartupDialogImpl::~EnterpriseStartupDialogImpl() {
251 if (dialog_view_) {
252 dialog_view_->RemoveWidgetObserver(this);
253 dialog_view_->CloseDialog();
254 }
255 CHECK(!IsInObserverList());
256 }
257
DisplayLaunchingInformationWithThrobber(const base::string16 & information)258 void EnterpriseStartupDialogImpl::DisplayLaunchingInformationWithThrobber(
259 const base::string16& information) {
260 if (dialog_view_)
261 dialog_view_->DisplayLaunchingInformationWithThrobber(information);
262 }
263
DisplayErrorMessage(const base::string16 & error_message,const base::Optional<base::string16> & accept_button)264 void EnterpriseStartupDialogImpl::DisplayErrorMessage(
265 const base::string16& error_message,
266 const base::Optional<base::string16>& accept_button) {
267 if (dialog_view_)
268 dialog_view_->DisplayErrorMessage(error_message, accept_button);
269 }
IsShowing()270 bool EnterpriseStartupDialogImpl::IsShowing() {
271 return dialog_view_;
272 }
273
274 // views::WidgetObserver:
OnWidgetClosing(views::Widget * widget)275 void EnterpriseStartupDialogImpl::OnWidgetClosing(views::Widget* widget) {
276 dialog_view_->RemoveWidgetObserver(this);
277 dialog_view_ = nullptr;
278 }
279
280 /*
281 * EnterpriseStartupDialog
282 */
283
284 // static
285 std::unique_ptr<EnterpriseStartupDialog>
CreateAndShowDialog(DialogResultCallback callback)286 EnterpriseStartupDialog::CreateAndShowDialog(DialogResultCallback callback) {
287 return std::make_unique<EnterpriseStartupDialogImpl>(std::move(callback));
288 }
289
290 } // namespace policy
291