1// Copyright 2013 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.h"
6
7#include "base/command_line.h"
8#include "base/strings/utf_string_conversions.h"
9#include "content/public/browser/web_contents.h"
10#include "content/public/browser/web_contents_view.h"
11#include "ui/aura/env.h"
12#include "ui/aura/root_window.h"
13#include "ui/aura/window.h"
14#include "ui/base/accessibility/accessibility_types.h"
15#include "ui/base/clipboard/clipboard.h"
16#include "ui/base/resource/resource_bundle.h"
17#include "ui/events/event.h"
18#include "ui/gfx/screen.h"
19#include "ui/views/background.h"
20#include "ui/views/controls/button/label_button.h"
21#include "ui/views/controls/textfield/textfield.h"
22#include "ui/views/controls/textfield/textfield_controller.h"
23#include "ui/views/controls/webview/webview.h"
24#include "ui/views/layout/fill_layout.h"
25#include "ui/views/layout/grid_layout.h"
26#include "ui/views/test/desktop_test_views_delegate.h"
27#include "ui/views/view.h"
28#include "ui/views/widget/desktop_aura/desktop_screen.h"
29#include "ui/views/widget/widget.h"
30#include "ui/views/widget/widget_delegate.h"
31
32#if defined(OS_CHROMEOS)
33#include "chromeos/dbus/dbus_thread_manager.h"
34#include "ui/aura/test/test_screen.h"
35#include "ui/wm/test/wm_test_helper.h"
36#endif
37
38#if defined(OS_WIN)
39#include <fcntl.h>
40#include <io.h>
41#endif
42
43namespace content {
44
45namespace {
46// ViewDelegate implementation for aura content shell
47class ShellViewsDelegateAura : public views::DesktopTestViewsDelegate {
48 public:
49  ShellViewsDelegateAura() : use_transparent_windows_(false) {
50  }
51
52  virtual ~ShellViewsDelegateAura() {
53  }
54
55  void SetUseTransparentWindows(bool transparent) {
56    use_transparent_windows_ = transparent;
57  }
58
59  // Overridden from views::TestViewsDelegate:
60  virtual bool UseTransparentWindows() const OVERRIDE {
61    return use_transparent_windows_;
62  }
63
64 private:
65  bool use_transparent_windows_;
66
67  DISALLOW_COPY_AND_ASSIGN(ShellViewsDelegateAura);
68};
69
70// Maintain the UI controls and web view for content shell
71class ShellWindowDelegateView : public views::WidgetDelegateView,
72                                public views::TextfieldController,
73                                public views::ButtonListener {
74 public:
75  enum UIControl {
76    BACK_BUTTON,
77    FORWARD_BUTTON,
78    STOP_BUTTON
79  };
80
81  ShellWindowDelegateView(Shell* shell)
82    : shell_(shell),
83      toolbar_view_(new View),
84      contents_view_(new View) {
85  }
86  virtual ~ShellWindowDelegateView() {}
87
88  // Update the state of UI controls
89  void SetAddressBarURL(const GURL& url) {
90    url_entry_->SetText(ASCIIToUTF16(url.spec()));
91  }
92  void SetWebContents(WebContents* web_contents, const gfx::Size& size) {
93    contents_view_->SetLayoutManager(new views::FillLayout());
94    web_view_ = new views::WebView(web_contents->GetBrowserContext());
95    web_view_->SetWebContents(web_contents);
96    web_view_->SetPreferredSize(size);
97    web_contents->GetView()->Focus();
98    contents_view_->AddChildView(web_view_);
99    Layout();
100
101    // Resize the widget, keeping the same origin.
102    gfx::Rect bounds = GetWidget()->GetWindowBoundsInScreen();
103    bounds.set_size(GetWidget()->GetRootView()->GetPreferredSize());
104    GetWidget()->SetBounds(bounds);
105
106    // Resizing a widget on chromeos doesn't automatically resize the root, need
107    // to explicitly do that.
108#if defined(OS_CHROMEOS)
109    GetWidget()->GetNativeWindow()->GetDispatcher()->SetHostSize(
110        bounds.size());
111#endif
112  }
113
114  void SetWindowTitle(const base::string16& title) { title_ = title; }
115  void EnableUIControl(UIControl control, bool is_enabled) {
116    if (control == BACK_BUTTON) {
117      back_button_->SetState(is_enabled ? views::CustomButton::STATE_NORMAL
118          : views::CustomButton::STATE_DISABLED);
119    } else if (control == FORWARD_BUTTON) {
120      forward_button_->SetState(is_enabled ? views::CustomButton::STATE_NORMAL
121          : views::CustomButton::STATE_DISABLED);
122    } else if (control == STOP_BUTTON) {
123      stop_button_->SetState(is_enabled ? views::CustomButton::STATE_NORMAL
124          : views::CustomButton::STATE_DISABLED);
125    }
126  }
127
128 private:
129  // Initialize the UI control contained in shell window
130  void InitShellWindow() {
131    set_background(views::Background::CreateStandardPanelBackground());
132
133    views::GridLayout* layout = new views::GridLayout(this);
134    SetLayoutManager(layout);
135
136    views::ColumnSet* column_set = layout->AddColumnSet(0);
137    column_set->AddPaddingColumn(0, 2);
138    column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
139                          views::GridLayout::USE_PREF, 0, 0);
140    column_set->AddPaddingColumn(0, 2);
141
142    layout->AddPaddingRow(0, 2);
143
144    // Add toolbar buttons and URL text field
145    {
146      layout->StartRow(0, 0);
147      views::GridLayout* toolbar_layout = new views::GridLayout(toolbar_view_);
148      toolbar_view_->SetLayoutManager(toolbar_layout);
149
150      views::ColumnSet* toolbar_column_set =
151          toolbar_layout->AddColumnSet(0);
152      // Back button
153      back_button_ = new views::LabelButton(this, ASCIIToUTF16("Back"));
154      back_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
155      gfx::Size back_button_size = back_button_->GetPreferredSize();
156      toolbar_column_set->AddColumn(views::GridLayout::CENTER,
157                                    views::GridLayout::CENTER, 0,
158                                    views::GridLayout::FIXED,
159                                    back_button_size.width(),
160                                    back_button_size.width() / 2);
161      // Forward button
162      forward_button_ = new views::LabelButton(this, ASCIIToUTF16("Forward"));
163      forward_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
164      gfx::Size forward_button_size = forward_button_->GetPreferredSize();
165      toolbar_column_set->AddColumn(views::GridLayout::CENTER,
166                                    views::GridLayout::CENTER, 0,
167                                    views::GridLayout::FIXED,
168                                    forward_button_size.width(),
169                                    forward_button_size.width() / 2);
170      // Refresh button
171      refresh_button_ = new views::LabelButton(this, ASCIIToUTF16("Refresh"));
172      refresh_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
173      gfx::Size refresh_button_size = refresh_button_->GetPreferredSize();
174      toolbar_column_set->AddColumn(views::GridLayout::CENTER,
175                                    views::GridLayout::CENTER, 0,
176                                    views::GridLayout::FIXED,
177                                    refresh_button_size.width(),
178                                    refresh_button_size.width() / 2);
179      // Stop button
180      stop_button_ = new views::LabelButton(this, ASCIIToUTF16("Stop"));
181      stop_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
182      gfx::Size stop_button_size = stop_button_->GetPreferredSize();
183      toolbar_column_set->AddColumn(views::GridLayout::CENTER,
184                                    views::GridLayout::CENTER, 0,
185                                    views::GridLayout::FIXED,
186                                    stop_button_size.width(),
187                                    stop_button_size.width() / 2);
188      toolbar_column_set->AddPaddingColumn(0, 2);
189      // URL entry
190      url_entry_ = new views::Textfield();
191      url_entry_->SetController(this);
192      toolbar_column_set->AddColumn(views::GridLayout::FILL,
193                                    views::GridLayout::FILL, 1,
194                                    views::GridLayout::USE_PREF, 0, 0);
195
196      // Fill up the first row
197      toolbar_layout->StartRow(0, 0);
198      toolbar_layout->AddView(back_button_);
199      toolbar_layout->AddView(forward_button_);
200      toolbar_layout->AddView(refresh_button_);
201      toolbar_layout->AddView(stop_button_);
202      toolbar_layout->AddView(url_entry_);
203
204      layout->AddView(toolbar_view_);
205    }
206
207    layout->AddPaddingRow(0, 5);
208
209    // Add web contents view as the second row
210    {
211      layout->StartRow(1, 0);
212      layout->AddView(contents_view_);
213    }
214
215    layout->AddPaddingRow(0, 5);
216  }
217  // Overridden from TextfieldController
218  virtual void ContentsChanged(views::Textfield* sender,
219                               const base::string16& new_contents) OVERRIDE {
220  }
221  virtual bool HandleKeyEvent(views::Textfield* sender,
222                              const ui::KeyEvent& key_event) OVERRIDE {
223   if (sender == url_entry_ && key_event.key_code() == ui::VKEY_RETURN) {
224     std::string text = UTF16ToUTF8(url_entry_->text());
225     GURL url(text);
226     if (!url.has_scheme()) {
227       url = GURL(std::string("http://") + std::string(text));
228       url_entry_->SetText(ASCIIToUTF16(url.spec()));
229     }
230     shell_->LoadURL(url);
231     return true;
232   }
233   return false;
234  }
235
236  // Overridden from ButtonListener
237  virtual void ButtonPressed(views::Button* sender,
238                             const ui::Event& event) OVERRIDE {
239    if (sender == back_button_)
240      shell_->GoBackOrForward(-1);
241    else if (sender == forward_button_)
242      shell_->GoBackOrForward(1);
243    else if (sender == refresh_button_)
244      shell_->Reload();
245    else if (sender == stop_button_)
246      shell_->Stop();
247  }
248
249  // Overridden from WidgetDelegateView
250  virtual bool CanResize() const OVERRIDE { return true; }
251  virtual bool CanMaximize() const OVERRIDE { return true; }
252  virtual base::string16 GetWindowTitle() const OVERRIDE {
253    return title_;
254  }
255  virtual void WindowClosing() OVERRIDE {
256    if (shell_) {
257      delete shell_;
258      shell_ = NULL;
259    }
260  }
261  virtual View* GetContentsView() OVERRIDE { return this; }
262
263  // Overridden from View
264  virtual void ViewHierarchyChanged(
265      const ViewHierarchyChangedDetails& details) OVERRIDE {
266    if (details.is_add && details.child == this) {
267      InitShellWindow();
268    }
269  }
270
271 private:
272  // Hold a reference of Shell for deleting it when the window is closing
273  Shell* shell_;
274
275  // Window title
276  base::string16 title_;
277
278  // Toolbar view contains forward/backward/reload button and URL entry
279  View* toolbar_view_;
280  views::LabelButton* back_button_;
281  views::LabelButton* forward_button_;
282  views::LabelButton* refresh_button_;
283  views::LabelButton* stop_button_;
284  views::Textfield* url_entry_;
285
286  // Contents view contains the web contents view
287  View* contents_view_;
288  views::WebView* web_view_;
289
290  DISALLOW_COPY_AND_ASSIGN(ShellWindowDelegateView);
291};
292
293}  // namespace
294
295#if defined(OS_CHROMEOS)
296wm::WMTestHelper* Shell::wm_test_helper_ = NULL;
297#endif
298views::ViewsDelegate* Shell::views_delegate_ = NULL;
299
300// static
301void Shell::PlatformInitialize(const gfx::Size& default_window_size) {
302#if defined(OS_WIN)
303  _setmode(_fileno(stdout), _O_BINARY);
304  _setmode(_fileno(stderr), _O_BINARY);
305#endif
306#if defined(OS_CHROMEOS)
307  chromeos::DBusThreadManager::Initialize();
308  gfx::Screen::SetScreenInstance(
309      gfx::SCREEN_TYPE_NATIVE, aura::TestScreen::Create());
310  wm_test_helper_ = new wm::WMTestHelper(default_window_size);
311#else
312  gfx::Screen::SetScreenInstance(
313      gfx::SCREEN_TYPE_NATIVE, views::CreateDesktopScreen());
314#endif
315  views_delegate_ = new ShellViewsDelegateAura();
316}
317
318void Shell::PlatformExit() {
319#if defined(OS_CHROMEOS)
320  if (wm_test_helper_)
321    delete wm_test_helper_;
322#endif
323  if (views_delegate_)
324    delete views_delegate_;
325#if defined(OS_CHROMEOS)
326  chromeos::DBusThreadManager::Shutdown();
327#endif
328  aura::Env::DeleteInstance();
329}
330
331void Shell::PlatformCleanUp() {
332}
333
334void Shell::PlatformEnableUIControl(UIControl control, bool is_enabled) {
335  ShellWindowDelegateView* delegate_view =
336    static_cast<ShellWindowDelegateView*>(window_widget_->widget_delegate());
337  if (control == BACK_BUTTON) {
338    delegate_view->EnableUIControl(ShellWindowDelegateView::BACK_BUTTON,
339        is_enabled);
340  } else if (control == FORWARD_BUTTON) {
341    delegate_view->EnableUIControl(ShellWindowDelegateView::FORWARD_BUTTON,
342        is_enabled);
343  } else if (control == STOP_BUTTON) {
344    delegate_view->EnableUIControl(ShellWindowDelegateView::STOP_BUTTON,
345        is_enabled);
346  }
347}
348
349void Shell::PlatformSetAddressBarURL(const GURL& url) {
350  ShellWindowDelegateView* delegate_view =
351    static_cast<ShellWindowDelegateView*>(window_widget_->widget_delegate());
352  delegate_view->SetAddressBarURL(url);
353}
354
355void Shell::PlatformSetIsLoading(bool loading) {
356}
357
358void Shell::PlatformCreateWindow(int width, int height) {
359#if defined(OS_CHROMEOS)
360  window_widget_ = views::Widget::CreateWindowWithContextAndBounds(
361      new ShellWindowDelegateView(this),
362      wm_test_helper_->GetDefaultParent(NULL, NULL, gfx::Rect()),
363      gfx::Rect(0, 0, width, height));
364#else
365  window_widget_ = views::Widget::CreateWindowWithBounds(
366      new ShellWindowDelegateView(this), gfx::Rect(0, 0, width, height));
367#endif
368
369  content_size_ = gfx::Size(width, height);
370
371  window_ = window_widget_->GetNativeWindow();
372  // Call ShowRootWindow on RootWindow created by WMTestHelper without
373  // which XWindow owned by RootWindow doesn't get mapped.
374  window_->GetDispatcher()->host()->Show();
375  window_widget_->Show();
376}
377
378void Shell::PlatformSetContents() {
379  ShellWindowDelegateView* delegate_view =
380      static_cast<ShellWindowDelegateView*>(window_widget_->widget_delegate());
381  delegate_view->SetWebContents(web_contents_.get(), content_size_);
382}
383
384void Shell::PlatformResizeSubViews() {
385}
386
387void Shell::Close() {
388  window_widget_->CloseNow();
389}
390
391void Shell::PlatformSetTitle(const base::string16& title) {
392  ShellWindowDelegateView* delegate_view =
393    static_cast<ShellWindowDelegateView*>(window_widget_->widget_delegate());
394  delegate_view->SetWindowTitle(title);
395  window_widget_->UpdateWindowTitle();
396}
397
398}  // namespace content
399