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