1 // Copyright 2020 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 "content/shell/browser/shell_platform_delegate.h"
6
7 #include <stddef.h>
8
9 #include <memory>
10
11 #include "base/command_line.h"
12 #include "base/stl_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "build/build_config.h"
15 #include "content/public/browser/context_factory.h"
16 #include "content/public/browser/render_widget_host_view.h"
17 #include "content/public/browser/web_contents.h"
18 #include "content/shell/browser/shell.h"
19 #include "ui/aura/env.h"
20 #include "ui/aura/window.h"
21 #include "ui/aura/window_event_dispatcher.h"
22 #include "ui/aura/window_tree_host.h"
23 #include "ui/base/clipboard/clipboard.h"
24 #include "ui/base/resource/resource_bundle.h"
25 #include "ui/events/event.h"
26 #include "ui/native_theme/native_theme_color_id.h"
27 #include "ui/views/background.h"
28 #include "ui/views/controls/button/md_text_button.h"
29 #include "ui/views/controls/textfield/textfield.h"
30 #include "ui/views/controls/textfield/textfield_controller.h"
31 #include "ui/views/controls/webview/webview.h"
32 #include "ui/views/layout/fill_layout.h"
33 #include "ui/views/layout/grid_layout.h"
34 #include "ui/views/test/desktop_test_views_delegate.h"
35 #include "ui/views/view.h"
36 #include "ui/views/widget/widget.h"
37 #include "ui/views/widget/widget_delegate.h"
38
39 #if defined(OS_CHROMEOS)
40 #include "ui/wm/test/wm_test_helper.h"
41 #else // !defined(OS_CHROMEOS)
42 #include "ui/views/widget/desktop_aura/desktop_screen.h"
43 #include "ui/wm/core/wm_state.h"
44 #endif
45
46 #if defined(OS_WIN)
47 #include <fcntl.h>
48 #include <io.h>
49 #endif
50
51 namespace content {
52
53 struct ShellPlatformDelegate::ShellData {
54 gfx::Size content_size;
55 // Self-owned Widget, destroyed through CloseNow().
56 views::Widget* window_widget = nullptr;
57 };
58
59 struct ShellPlatformDelegate::PlatformData {
60 #if defined(OS_CHROMEOS)
61 std::unique_ptr<wm::WMTestHelper> wm_test_helper;
62 #else
63 std::unique_ptr<wm::WMState> wm_state;
64 #endif
65
66 // TODO(danakj): This looks unused?
67 std::unique_ptr<views::ViewsDelegate> views_delegate;
68 };
69
70 namespace {
71
72 // Maintain the UI controls and web view for content shell
73 class ShellView : public views::View, public views::TextfieldController {
74 public:
75 enum UIControl { BACK_BUTTON, FORWARD_BUTTON, STOP_BUTTON };
76
ShellView(Shell * shell)77 explicit ShellView(Shell* shell) : shell_(shell) { InitShellWindow(); }
78
~ShellView()79 ~ShellView() override {}
80
81 // Update the state of UI controls
SetAddressBarURL(const GURL & url)82 void SetAddressBarURL(const GURL& url) {
83 url_entry_->SetText(base::ASCIIToUTF16(url.spec()));
84 }
85
SetWebContents(WebContents * web_contents,const gfx::Size & size)86 void SetWebContents(WebContents* web_contents, const gfx::Size& size) {
87 contents_view_->SetLayoutManager(std::make_unique<views::FillLayout>());
88 // If there was a previous WebView in this Shell it should be removed and
89 // deleted.
90 if (web_view_) {
91 contents_view_->RemoveChildView(web_view_);
92 delete web_view_;
93 }
94 auto web_view =
95 std::make_unique<views::WebView>(web_contents->GetBrowserContext());
96 web_view->SetWebContents(web_contents);
97 web_view->SetPreferredSize(size);
98 web_contents->Focus();
99 web_view_ = contents_view_->AddChildView(std::move(web_view));
100 Layout();
101
102 // Resize the widget, keeping the same origin.
103 gfx::Rect bounds = GetWidget()->GetWindowBoundsInScreen();
104 bounds.set_size(GetWidget()->GetRootView()->GetPreferredSize());
105 GetWidget()->SetBounds(bounds);
106
107 // Resizing a widget on chromeos doesn't automatically resize the root, need
108 // to explicitly do that.
109 #if defined(OS_CHROMEOS)
110 GetWidget()->GetNativeWindow()->GetHost()->SetBoundsInPixels(bounds);
111 #endif
112 }
113
EnableUIControl(UIControl control,bool is_enabled)114 void EnableUIControl(UIControl control, bool is_enabled) {
115 if (control == BACK_BUTTON) {
116 back_button_->SetState(is_enabled ? views::Button::STATE_NORMAL
117 : views::Button::STATE_DISABLED);
118 } else if (control == FORWARD_BUTTON) {
119 forward_button_->SetState(is_enabled ? views::Button::STATE_NORMAL
120 : views::Button::STATE_DISABLED);
121 } else if (control == STOP_BUTTON) {
122 stop_button_->SetState(is_enabled ? views::Button::STATE_NORMAL
123 : views::Button::STATE_DISABLED);
124 }
125 }
126
127 private:
128 // Initialize the UI control contained in shell window
InitShellWindow()129 void InitShellWindow() {
130 SetBackground(CreateThemedSolidBackground(
131 this, ui::NativeTheme::kColorId_WindowBackground));
132
133 auto contents_view = std::make_unique<views::View>();
134 auto toolbar_view = std::make_unique<views::View>();
135
136 views::GridLayout* layout =
137 SetLayoutManager(std::make_unique<views::GridLayout>());
138
139 using ColumnSize = views::GridLayout::ColumnSize;
140 views::ColumnSet* column_set = layout->AddColumnSet(0);
141 if (!Shell::ShouldHideToolbar())
142 column_set->AddPaddingColumn(0, 2);
143 column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
144 ColumnSize::kUsePreferred, 0, 0);
145 if (!Shell::ShouldHideToolbar())
146 column_set->AddPaddingColumn(0, 2);
147
148 // Add toolbar buttons and URL text field
149 if (!Shell::ShouldHideToolbar()) {
150 layout->AddPaddingRow(0, 2);
151 layout->StartRow(0, 0);
152 views::GridLayout* toolbar_layout =
153 toolbar_view->SetLayoutManager(std::make_unique<views::GridLayout>());
154
155 views::ColumnSet* toolbar_column_set = toolbar_layout->AddColumnSet(0);
156 // Back button
157 // Using Unretained (here and below) is safe since the View itself has the
158 // same lifetime as |shell_| (both are torn down implicitly during
159 // destruction).
160 auto back_button = std::make_unique<views::MdTextButton>(
161 base::BindRepeating(&Shell::GoBackOrForward,
162 base::Unretained(shell_.get()), -1),
163 base::ASCIIToUTF16("Back"));
164 gfx::Size back_button_size = back_button->GetPreferredSize();
165 toolbar_column_set->AddColumn(
166 views::GridLayout::CENTER, views::GridLayout::CENTER, 0,
167 ColumnSize::kFixed, back_button_size.width(),
168 back_button_size.width() / 2);
169 // Forward button
170 auto forward_button = std::make_unique<views::MdTextButton>(
171 base::BindRepeating(&Shell::GoBackOrForward,
172 base::Unretained(shell_.get()), 1),
173 base::ASCIIToUTF16("Forward"));
174 gfx::Size forward_button_size = forward_button->GetPreferredSize();
175 toolbar_column_set->AddColumn(
176 views::GridLayout::CENTER, views::GridLayout::CENTER, 0,
177 ColumnSize::kFixed, forward_button_size.width(),
178 forward_button_size.width() / 2);
179 // Refresh button
180 auto refresh_button = std::make_unique<views::MdTextButton>(
181 base::BindRepeating(&Shell::Reload, base::Unretained(shell_.get())),
182 base::ASCIIToUTF16("Refresh"));
183 gfx::Size refresh_button_size = refresh_button->GetPreferredSize();
184 toolbar_column_set->AddColumn(
185 views::GridLayout::CENTER, views::GridLayout::CENTER, 0,
186 ColumnSize::kFixed, refresh_button_size.width(),
187 refresh_button_size.width() / 2);
188 // Stop button
189 auto stop_button = std::make_unique<views::MdTextButton>(
190 base::BindRepeating(&Shell::Stop, base::Unretained(shell_.get())),
191 base::ASCIIToUTF16("Stop"));
192 gfx::Size stop_button_size = stop_button->GetPreferredSize();
193 toolbar_column_set->AddColumn(
194 views::GridLayout::CENTER, views::GridLayout::CENTER, 0,
195 ColumnSize::kFixed, stop_button_size.width(),
196 stop_button_size.width() / 2);
197 toolbar_column_set->AddPaddingColumn(0, 2);
198 // URL entry
199 auto url_entry = std::make_unique<views::Textfield>();
200 url_entry->SetAccessibleName(base::ASCIIToUTF16("Enter URL"));
201 url_entry->set_controller(this);
202 url_entry->SetTextInputType(ui::TextInputType::TEXT_INPUT_TYPE_URL);
203 toolbar_column_set->AddColumn(views::GridLayout::FILL,
204 views::GridLayout::FILL, 1,
205 ColumnSize::kUsePreferred, 0, 0);
206 toolbar_column_set->AddPaddingColumn(0, 2);
207
208 // Fill up the first row
209 toolbar_layout->StartRow(0, 0);
210 back_button_ = toolbar_layout->AddView(std::move(back_button));
211 forward_button_ = toolbar_layout->AddView(std::move(forward_button));
212 refresh_button_ = toolbar_layout->AddView(std::move(refresh_button));
213 stop_button_ = toolbar_layout->AddView(std::move(stop_button));
214 url_entry_ = toolbar_layout->AddView(std::move(url_entry));
215
216 toolbar_view_ = layout->AddView(std::move(toolbar_view));
217
218 layout->AddPaddingRow(0, 5);
219 }
220
221 // Add web contents view as the second row
222 {
223 layout->StartRow(1, 0);
224 contents_view_ = layout->AddView(std::move(contents_view));
225 }
226
227 if (!Shell::ShouldHideToolbar())
228 layout->AddPaddingRow(0, 5);
229 }
InitAccelerators()230 void InitAccelerators() {
231 // This function must be called when part of the widget hierarchy.
232 DCHECK(GetWidget());
233 static const ui::KeyboardCode keys[] = {ui::VKEY_F5, ui::VKEY_BROWSER_BACK,
234 ui::VKEY_BROWSER_FORWARD};
235 for (size_t i = 0; i < base::size(keys); ++i) {
236 GetFocusManager()->RegisterAccelerator(
237 ui::Accelerator(keys[i], ui::EF_NONE),
238 ui::AcceleratorManager::kNormalPriority, this);
239 }
240 }
241 // Overridden from TextfieldController
ContentsChanged(views::Textfield * sender,const base::string16 & new_contents)242 void ContentsChanged(views::Textfield* sender,
243 const base::string16& new_contents) override {}
HandleKeyEvent(views::Textfield * sender,const ui::KeyEvent & key_event)244 bool HandleKeyEvent(views::Textfield* sender,
245 const ui::KeyEvent& key_event) override {
246 if (key_event.type() == ui::ET_KEY_PRESSED && sender == url_entry_ &&
247 key_event.key_code() == ui::VKEY_RETURN) {
248 std::string text = base::UTF16ToUTF8(url_entry_->GetText());
249 GURL url(text);
250 if (!url.has_scheme()) {
251 url = GURL(std::string("http://") + std::string(text));
252 url_entry_->SetText(base::ASCIIToUTF16(url.spec()));
253 }
254 shell_->LoadURL(url);
255 return true;
256 }
257 return false;
258 }
259
260 // Overridden from View
GetMinimumSize() const261 gfx::Size GetMinimumSize() const override {
262 // We want to be able to make the window smaller than its initial
263 // (preferred) size.
264 return gfx::Size();
265 }
AddedToWidget()266 void AddedToWidget() override { InitAccelerators(); }
267
268 // Overridden from AcceleratorTarget:
AcceleratorPressed(const ui::Accelerator & accelerator)269 bool AcceleratorPressed(const ui::Accelerator& accelerator) override {
270 switch (accelerator.key_code()) {
271 case ui::VKEY_F5:
272 shell_->Reload();
273 return true;
274 case ui::VKEY_BROWSER_BACK:
275 shell_->GoBackOrForward(-1);
276 return true;
277 case ui::VKEY_BROWSER_FORWARD:
278 shell_->GoBackOrForward(1);
279 return true;
280 default:
281 return views::View::AcceleratorPressed(accelerator);
282 }
283 }
284
285 private:
286 std::unique_ptr<Shell> shell_;
287
288 // Window title
289 base::string16 title_;
290
291 // Toolbar view contains forward/backward/reload button and URL entry
292 View* toolbar_view_ = nullptr;
293 views::Button* back_button_ = nullptr;
294 views::Button* forward_button_ = nullptr;
295 views::Button* refresh_button_ = nullptr;
296 views::Button* stop_button_ = nullptr;
297 views::Textfield* url_entry_ = nullptr;
298
299 // Contents view contains the web contents view
300 View* contents_view_ = nullptr;
301 views::WebView* web_view_ = nullptr;
302
303 DISALLOW_COPY_AND_ASSIGN(ShellView);
304 };
305
ShellViewForWidget(views::Widget * widget)306 ShellView* ShellViewForWidget(views::Widget* widget) {
307 return static_cast<ShellView*>(widget->widget_delegate()->GetContentsView());
308 }
309
310 } // namespace
311
312 ShellPlatformDelegate::ShellPlatformDelegate() = default;
313
Initialize(const gfx::Size & default_window_size)314 void ShellPlatformDelegate::Initialize(const gfx::Size& default_window_size) {
315 #if defined(OS_WIN)
316 _setmode(_fileno(stdout), _O_BINARY);
317 _setmode(_fileno(stderr), _O_BINARY);
318 #endif
319
320 platform_ = std::make_unique<PlatformData>();
321
322 #if defined(OS_CHROMEOS)
323 platform_->wm_test_helper =
324 std::make_unique<wm::WMTestHelper>(default_window_size);
325 #else
326 platform_->wm_state = std::make_unique<wm::WMState>();
327 views::InstallDesktopScreenIfNecessary();
328 #endif
329
330 platform_->views_delegate =
331 std::make_unique<views::DesktopTestViewsDelegate>();
332 }
333
334 ShellPlatformDelegate::~ShellPlatformDelegate() = default;
335
CreatePlatformWindow(Shell * shell,const gfx::Size & initial_size)336 void ShellPlatformDelegate::CreatePlatformWindow(
337 Shell* shell,
338 const gfx::Size& initial_size) {
339 DCHECK(!base::Contains(shell_data_map_, shell));
340 ShellData& shell_data = shell_data_map_[shell];
341
342 shell_data.content_size = initial_size;
343
344 auto delegate = std::make_unique<views::WidgetDelegate>();
345 delegate->SetContentsView(std::make_unique<ShellView>(shell));
346 delegate->SetHasWindowSizeControls(true);
347 delegate->SetOwnedByWidget(true);
348
349 #if defined(OS_CHROMEOS)
350 shell_data.window_widget = views::Widget::CreateWindowWithContext(
351 std::move(delegate),
352 platform_->wm_test_helper->GetDefaultParent(nullptr, gfx::Rect()),
353 gfx::Rect(initial_size));
354 #else
355 shell_data.window_widget = new views::Widget();
356 views::Widget::InitParams params;
357 params.bounds = gfx::Rect(initial_size);
358 params.delegate = delegate.release();
359 params.wm_class_class = "chromium-content_shell";
360 params.wm_class_name = params.wm_class_class;
361 shell_data.window_widget->Init(std::move(params));
362 #endif
363
364 // |window_widget| is made visible in PlatformSetContents(), so that the
365 // platform-window size does not need to change due to layout again.
366 }
367
GetNativeWindow(Shell * shell)368 gfx::NativeWindow ShellPlatformDelegate::GetNativeWindow(Shell* shell) {
369 DCHECK(base::Contains(shell_data_map_, shell));
370 ShellData& shell_data = shell_data_map_[shell];
371
372 return shell_data.window_widget->GetNativeWindow();
373 }
374
CleanUp(Shell * shell)375 void ShellPlatformDelegate::CleanUp(Shell* shell) {
376 DCHECK(base::Contains(shell_data_map_, shell));
377 shell_data_map_.erase(shell);
378 }
379
SetContents(Shell * shell)380 void ShellPlatformDelegate::SetContents(Shell* shell) {
381 DCHECK(base::Contains(shell_data_map_, shell));
382 ShellData& shell_data = shell_data_map_[shell];
383
384 ShellViewForWidget(shell_data.window_widget)
385 ->SetWebContents(shell->web_contents(), shell_data.content_size);
386 shell_data.window_widget->GetNativeWindow()->GetHost()->Show();
387 shell_data.window_widget->Show();
388 }
389
ResizeWebContent(Shell * shell,const gfx::Size & content_size)390 void ShellPlatformDelegate::ResizeWebContent(Shell* shell,
391 const gfx::Size& content_size) {
392 shell->web_contents()->GetRenderWidgetHostView()->SetSize(content_size);
393 }
394
EnableUIControl(Shell * shell,UIControl control,bool is_enabled)395 void ShellPlatformDelegate::EnableUIControl(Shell* shell,
396 UIControl control,
397 bool is_enabled) {
398 if (Shell::ShouldHideToolbar())
399 return;
400
401 DCHECK(base::Contains(shell_data_map_, shell));
402 ShellData& shell_data = shell_data_map_[shell];
403
404 auto* view = ShellViewForWidget(shell_data.window_widget);
405 if (control == BACK_BUTTON) {
406 view->EnableUIControl(ShellView::BACK_BUTTON, is_enabled);
407 } else if (control == FORWARD_BUTTON) {
408 view->EnableUIControl(ShellView::FORWARD_BUTTON, is_enabled);
409 } else if (control == STOP_BUTTON) {
410 view->EnableUIControl(ShellView::STOP_BUTTON, is_enabled);
411 }
412 }
413
SetAddressBarURL(Shell * shell,const GURL & url)414 void ShellPlatformDelegate::SetAddressBarURL(Shell* shell, const GURL& url) {
415 if (Shell::ShouldHideToolbar())
416 return;
417
418 DCHECK(base::Contains(shell_data_map_, shell));
419 ShellData& shell_data = shell_data_map_[shell];
420
421 ShellViewForWidget(shell_data.window_widget)->SetAddressBarURL(url);
422 }
423
SetIsLoading(Shell * shell,bool loading)424 void ShellPlatformDelegate::SetIsLoading(Shell* shell, bool loading) {}
425
SetTitle(Shell * shell,const base::string16 & title)426 void ShellPlatformDelegate::SetTitle(Shell* shell,
427 const base::string16& title) {
428 DCHECK(base::Contains(shell_data_map_, shell));
429 ShellData& shell_data = shell_data_map_[shell];
430
431 shell_data.window_widget->widget_delegate()->SetTitle(title);
432 }
433
RenderViewReady(Shell * shell)434 void ShellPlatformDelegate::RenderViewReady(Shell* shell) {}
435
DestroyShell(Shell * shell)436 bool ShellPlatformDelegate::DestroyShell(Shell* shell) {
437 DCHECK(base::Contains(shell_data_map_, shell));
438 ShellData& shell_data = shell_data_map_[shell];
439
440 shell_data.window_widget->CloseNow();
441 return true; // The CloseNow() will do the destruction of Shell.
442 }
443
444 } // namespace content
445