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/context_factory.h"
10#include "content/public/browser/render_widget_host_view.h"
11#include "content/public/browser/web_contents.h"
12#include "content/public/common/context_menu_params.h"
13#include "content/shell/browser/shell_platform_data_aura.h"
14#include "ui/aura/client/screen_position_client.h"
15#include "ui/aura/env.h"
16#include "ui/aura/window.h"
17#include "ui/aura/window_event_dispatcher.h"
18#include "ui/base/clipboard/clipboard.h"
19#include "ui/base/models/simple_menu_model.h"
20#include "ui/base/resource/resource_bundle.h"
21#include "ui/events/event.h"
22#include "ui/gfx/screen.h"
23#include "ui/views/background.h"
24#include "ui/views/controls/button/label_button.h"
25#include "ui/views/controls/button/menu_button.h"
26#include "ui/views/controls/button/menu_button_listener.h"
27#include "ui/views/controls/menu/menu_runner.h"
28#include "ui/views/controls/textfield/textfield.h"
29#include "ui/views/controls/textfield/textfield_controller.h"
30#include "ui/views/controls/webview/webview.h"
31#include "ui/views/layout/fill_layout.h"
32#include "ui/views/layout/grid_layout.h"
33#include "ui/views/test/desktop_test_views_delegate.h"
34#include "ui/views/view.h"
35#include "ui/views/widget/widget.h"
36#include "ui/views/widget/widget_delegate.h"
37
38#if defined(OS_CHROMEOS)
39#include "chromeos/dbus/dbus_thread_manager.h"
40#include "ui/aura/test/test_screen.h"
41#include "ui/wm/test/wm_test_helper.h"
42#else  // !defined(OS_CHROMEOS)
43#include "ui/views/widget/desktop_aura/desktop_screen.h"
44#endif
45
46#if defined(OS_WIN)
47#include <fcntl.h>
48#include <io.h>
49#endif
50
51namespace content {
52
53namespace {
54// ViewDelegate implementation for aura content shell
55class ShellViewsDelegateAura : public views::DesktopTestViewsDelegate {
56 public:
57  ShellViewsDelegateAura() : use_transparent_windows_(false) {
58  }
59
60  virtual ~ShellViewsDelegateAura() {
61  }
62
63  void SetUseTransparentWindows(bool transparent) {
64    use_transparent_windows_ = transparent;
65  }
66
67 private:
68  bool use_transparent_windows_;
69
70  DISALLOW_COPY_AND_ASSIGN(ShellViewsDelegateAura);
71};
72
73// Model for the "Debug" menu
74class ContextMenuModel : public ui::SimpleMenuModel,
75                         public ui::SimpleMenuModel::Delegate {
76 public:
77  explicit ContextMenuModel(
78      Shell* shell, const content::ContextMenuParams& params)
79    : ui::SimpleMenuModel(this),
80      shell_(shell),
81      params_(params) {
82    AddItem(COMMAND_OPEN_DEVTOOLS, base::ASCIIToUTF16("Inspect Element"));
83  }
84
85  // ui::SimpleMenuModel::Delegate:
86  virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
87    return false;
88  }
89  virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
90    return true;
91  }
92  virtual bool GetAcceleratorForCommandId(
93      int command_id,
94      ui::Accelerator* accelerator) OVERRIDE { return false; }
95  virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
96    switch (command_id) {
97      case COMMAND_OPEN_DEVTOOLS:
98        shell_->ShowDevToolsForElementAt(params_.x, params_.y);
99        break;
100    };
101  }
102
103 private:
104  enum CommandID {
105    COMMAND_OPEN_DEVTOOLS
106  };
107
108  Shell* shell_;
109  content::ContextMenuParams params_;
110
111  DISALLOW_COPY_AND_ASSIGN(ContextMenuModel);
112};
113
114// Maintain the UI controls and web view for content shell
115class ShellWindowDelegateView : public views::WidgetDelegateView,
116                                public views::TextfieldController,
117                                public views::ButtonListener {
118 public:
119  enum UIControl {
120    BACK_BUTTON,
121    FORWARD_BUTTON,
122    STOP_BUTTON
123  };
124
125  ShellWindowDelegateView(Shell* shell)
126    : shell_(shell),
127      toolbar_view_(new View),
128      contents_view_(new View) {
129  }
130  virtual ~ShellWindowDelegateView() {}
131
132  // Update the state of UI controls
133  void SetAddressBarURL(const GURL& url) {
134    url_entry_->SetText(base::ASCIIToUTF16(url.spec()));
135  }
136  void SetWebContents(WebContents* web_contents, const gfx::Size& size) {
137    contents_view_->SetLayoutManager(new views::FillLayout());
138    web_view_ = new views::WebView(web_contents->GetBrowserContext());
139    web_view_->SetWebContents(web_contents);
140    web_view_->SetPreferredSize(size);
141    web_contents->Focus();
142    contents_view_->AddChildView(web_view_);
143    Layout();
144
145    // Resize the widget, keeping the same origin.
146    gfx::Rect bounds = GetWidget()->GetWindowBoundsInScreen();
147    bounds.set_size(GetWidget()->GetRootView()->GetPreferredSize());
148    GetWidget()->SetBounds(bounds);
149
150    // Resizing a widget on chromeos doesn't automatically resize the root, need
151    // to explicitly do that.
152#if defined(OS_CHROMEOS)
153    GetWidget()->GetNativeWindow()->GetHost()->SetBounds(bounds);
154#endif
155  }
156
157  void SetWindowTitle(const base::string16& title) { title_ = title; }
158  void EnableUIControl(UIControl control, bool is_enabled) {
159    if (control == BACK_BUTTON) {
160      back_button_->SetState(is_enabled ? views::CustomButton::STATE_NORMAL
161          : views::CustomButton::STATE_DISABLED);
162    } else if (control == FORWARD_BUTTON) {
163      forward_button_->SetState(is_enabled ? views::CustomButton::STATE_NORMAL
164          : views::CustomButton::STATE_DISABLED);
165    } else if (control == STOP_BUTTON) {
166      stop_button_->SetState(is_enabled ? views::CustomButton::STATE_NORMAL
167          : views::CustomButton::STATE_DISABLED);
168    }
169  }
170
171  void ShowWebViewContextMenu(const content::ContextMenuParams& params) {
172    gfx::Point screen_point(params.x, params.y);
173
174    // Convert from content coordinates to window coordinates.
175    // This code copied from chrome_web_contents_view_delegate_views.cc
176    aura::Window* web_contents_window =
177        shell_->web_contents()->GetNativeView();
178    aura::Window* root_window = web_contents_window->GetRootWindow();
179    aura::client::ScreenPositionClient* screen_position_client =
180        aura::client::GetScreenPositionClient(root_window);
181    if (screen_position_client) {
182        screen_position_client->ConvertPointToScreen(web_contents_window,
183                &screen_point);
184    }
185
186    context_menu_model_.reset(new ContextMenuModel(shell_, params));
187    context_menu_runner_.reset(new views::MenuRunner(
188        context_menu_model_.get(), views::MenuRunner::CONTEXT_MENU));
189
190    if (context_menu_runner_->RunMenuAt(web_view_->GetWidget(),
191                                        NULL,
192                                        gfx::Rect(screen_point, gfx::Size()),
193                                        views::MENU_ANCHOR_TOPRIGHT,
194                                        ui::MENU_SOURCE_NONE) ==
195        views::MenuRunner::MENU_DELETED) {
196      return;
197    }
198  }
199
200  void OnWebContentsFocused(content::WebContents* web_contents) {
201    if (web_view_->GetWebContents() == web_contents)
202      web_view_->OnWebContentsFocused(web_contents);
203  }
204
205 private:
206  // Initialize the UI control contained in shell window
207  void InitShellWindow() {
208    set_background(views::Background::CreateStandardPanelBackground());
209
210    views::GridLayout* layout = new views::GridLayout(this);
211    SetLayoutManager(layout);
212
213    views::ColumnSet* column_set = layout->AddColumnSet(0);
214    column_set->AddPaddingColumn(0, 2);
215    column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
216                          views::GridLayout::USE_PREF, 0, 0);
217    column_set->AddPaddingColumn(0, 2);
218
219    layout->AddPaddingRow(0, 2);
220
221    // Add toolbar buttons and URL text field
222    {
223      layout->StartRow(0, 0);
224      views::GridLayout* toolbar_layout = new views::GridLayout(toolbar_view_);
225      toolbar_view_->SetLayoutManager(toolbar_layout);
226
227      views::ColumnSet* toolbar_column_set =
228          toolbar_layout->AddColumnSet(0);
229      // Back button
230      back_button_ = new views::LabelButton(this, base::ASCIIToUTF16("Back"));
231      back_button_->SetStyle(views::Button::STYLE_BUTTON);
232      gfx::Size back_button_size = back_button_->GetPreferredSize();
233      toolbar_column_set->AddColumn(views::GridLayout::CENTER,
234                                    views::GridLayout::CENTER, 0,
235                                    views::GridLayout::FIXED,
236                                    back_button_size.width(),
237                                    back_button_size.width() / 2);
238      // Forward button
239      forward_button_ =
240          new views::LabelButton(this, base::ASCIIToUTF16("Forward"));
241      forward_button_->SetStyle(views::Button::STYLE_BUTTON);
242      gfx::Size forward_button_size = forward_button_->GetPreferredSize();
243      toolbar_column_set->AddColumn(views::GridLayout::CENTER,
244                                    views::GridLayout::CENTER, 0,
245                                    views::GridLayout::FIXED,
246                                    forward_button_size.width(),
247                                    forward_button_size.width() / 2);
248      // Refresh button
249      refresh_button_ =
250          new views::LabelButton(this, base::ASCIIToUTF16("Refresh"));
251      refresh_button_->SetStyle(views::Button::STYLE_BUTTON);
252      gfx::Size refresh_button_size = refresh_button_->GetPreferredSize();
253      toolbar_column_set->AddColumn(views::GridLayout::CENTER,
254                                    views::GridLayout::CENTER, 0,
255                                    views::GridLayout::FIXED,
256                                    refresh_button_size.width(),
257                                    refresh_button_size.width() / 2);
258      // Stop button
259      stop_button_ = new views::LabelButton(this, base::ASCIIToUTF16("Stop"));
260      stop_button_->SetStyle(views::Button::STYLE_BUTTON);
261      gfx::Size stop_button_size = stop_button_->GetPreferredSize();
262      toolbar_column_set->AddColumn(views::GridLayout::CENTER,
263                                    views::GridLayout::CENTER, 0,
264                                    views::GridLayout::FIXED,
265                                    stop_button_size.width(),
266                                    stop_button_size.width() / 2);
267      toolbar_column_set->AddPaddingColumn(0, 2);
268      // URL entry
269      url_entry_ = new views::Textfield();
270      url_entry_->set_controller(this);
271      toolbar_column_set->AddColumn(views::GridLayout::FILL,
272                                    views::GridLayout::FILL, 1,
273                                    views::GridLayout::USE_PREF, 0, 0);
274      toolbar_column_set->AddPaddingColumn(0, 2);
275
276      // Fill up the first row
277      toolbar_layout->StartRow(0, 0);
278      toolbar_layout->AddView(back_button_);
279      toolbar_layout->AddView(forward_button_);
280      toolbar_layout->AddView(refresh_button_);
281      toolbar_layout->AddView(stop_button_);
282      toolbar_layout->AddView(url_entry_);
283
284      layout->AddView(toolbar_view_);
285    }
286
287    layout->AddPaddingRow(0, 5);
288
289    // Add web contents view as the second row
290    {
291      layout->StartRow(1, 0);
292      layout->AddView(contents_view_);
293    }
294
295    layout->AddPaddingRow(0, 5);
296
297    InitAccelerators();
298  }
299  void InitAccelerators() {
300    static const ui::KeyboardCode keys[] = { ui::VKEY_F5,
301                                             ui::VKEY_BROWSER_BACK,
302                                             ui::VKEY_BROWSER_FORWARD };
303    for (size_t i = 0; i < arraysize(keys); ++i) {
304      GetFocusManager()->RegisterAccelerator(
305        ui::Accelerator(keys[i], ui::EF_NONE),
306        ui::AcceleratorManager::kNormalPriority,
307        this);
308    }
309  }
310  // Overridden from TextfieldController
311  virtual void ContentsChanged(views::Textfield* sender,
312                               const base::string16& new_contents) OVERRIDE {
313  }
314  virtual bool HandleKeyEvent(views::Textfield* sender,
315                              const ui::KeyEvent& key_event) OVERRIDE {
316   if (sender == url_entry_ && key_event.key_code() == ui::VKEY_RETURN) {
317     std::string text = base::UTF16ToUTF8(url_entry_->text());
318     GURL url(text);
319     if (!url.has_scheme()) {
320       url = GURL(std::string("http://") + std::string(text));
321       url_entry_->SetText(base::ASCIIToUTF16(url.spec()));
322     }
323     shell_->LoadURL(url);
324     return true;
325   }
326   return false;
327  }
328
329  // Overridden from ButtonListener
330  virtual void ButtonPressed(views::Button* sender,
331                             const ui::Event& event) OVERRIDE {
332    if (sender == back_button_)
333      shell_->GoBackOrForward(-1);
334    else if (sender == forward_button_)
335      shell_->GoBackOrForward(1);
336    else if (sender == refresh_button_)
337      shell_->Reload();
338    else if (sender == stop_button_)
339      shell_->Stop();
340  }
341
342  // Overridden from WidgetDelegateView
343  virtual bool CanResize() const OVERRIDE { return true; }
344  virtual bool CanMaximize() const OVERRIDE { return true; }
345  virtual bool CanMinimize() const OVERRIDE { return true; }
346  virtual base::string16 GetWindowTitle() const OVERRIDE {
347    return title_;
348  }
349  virtual void WindowClosing() OVERRIDE {
350    if (shell_) {
351      delete shell_;
352      shell_ = NULL;
353    }
354  }
355  virtual View* GetContentsView() OVERRIDE { return this; }
356
357  // Overridden from View
358  virtual gfx::Size GetMinimumSize() const OVERRIDE {
359    // We want to be able to make the window smaller than its initial
360    // (preferred) size.
361    return gfx::Size();
362  }
363  virtual void ViewHierarchyChanged(
364      const ViewHierarchyChangedDetails& details) OVERRIDE {
365    if (details.is_add && details.child == this) {
366      InitShellWindow();
367    }
368  }
369
370  // Overridden from AcceleratorTarget:
371  virtual bool AcceleratorPressed(const ui::Accelerator& accelerator) OVERRIDE {
372    switch (accelerator.key_code()) {
373    case ui::VKEY_F5:
374      shell_->Reload();
375      return true;
376    case ui::VKEY_BROWSER_BACK:
377      shell_->GoBackOrForward(-1);
378      return true;
379    case ui::VKEY_BROWSER_FORWARD:
380      shell_->GoBackOrForward(1);
381      return true;
382    default:
383      return views::WidgetDelegateView::AcceleratorPressed(accelerator);
384    }
385  }
386
387 private:
388  // Hold a reference of Shell for deleting it when the window is closing
389  Shell* shell_;
390
391  // Window title
392  base::string16 title_;
393
394  // Toolbar view contains forward/backward/reload button and URL entry
395  View* toolbar_view_;
396  views::LabelButton* back_button_;
397  views::LabelButton* forward_button_;
398  views::LabelButton* refresh_button_;
399  views::LabelButton* stop_button_;
400  views::Textfield* url_entry_;
401  scoped_ptr<ContextMenuModel> context_menu_model_;
402  scoped_ptr<views::MenuRunner> context_menu_runner_;
403
404  // Contents view contains the web contents view
405  View* contents_view_;
406  views::WebView* web_view_;
407
408  DISALLOW_COPY_AND_ASSIGN(ShellWindowDelegateView);
409};
410
411}  // namespace
412
413#if defined(OS_CHROMEOS)
414wm::WMTestHelper* Shell::wm_test_helper_ = NULL;
415gfx::Screen* Shell::test_screen_ = NULL;
416#endif
417views::ViewsDelegate* Shell::views_delegate_ = NULL;
418
419// static
420void Shell::PlatformInitialize(const gfx::Size& default_window_size) {
421#if defined(OS_WIN)
422  _setmode(_fileno(stdout), _O_BINARY);
423  _setmode(_fileno(stderr), _O_BINARY);
424#endif
425#if defined(OS_CHROMEOS)
426  chromeos::DBusThreadManager::Initialize();
427  test_screen_ = aura::TestScreen::Create(gfx::Size());
428  gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, test_screen_);
429  wm_test_helper_ = new wm::WMTestHelper(default_window_size,
430                                         GetContextFactory());
431#else
432  gfx::Screen::SetScreenInstance(
433      gfx::SCREEN_TYPE_NATIVE, views::CreateDesktopScreen());
434#endif
435  views_delegate_ = new ShellViewsDelegateAura();
436}
437
438void Shell::PlatformExit() {
439#if defined(OS_CHROMEOS)
440  delete wm_test_helper_;
441  wm_test_helper_ = NULL;
442
443  delete test_screen_;
444  test_screen_ = NULL;
445#endif
446  delete views_delegate_;
447  views_delegate_ = NULL;
448  delete platform_;
449  platform_ = NULL;
450#if defined(OS_CHROMEOS)
451  chromeos::DBusThreadManager::Shutdown();
452#endif
453  aura::Env::DeleteInstance();
454}
455
456void Shell::PlatformCleanUp() {
457}
458
459void Shell::PlatformEnableUIControl(UIControl control, bool is_enabled) {
460  if (headless_)
461    return;
462  ShellWindowDelegateView* delegate_view =
463    static_cast<ShellWindowDelegateView*>(window_widget_->widget_delegate());
464  if (control == BACK_BUTTON) {
465    delegate_view->EnableUIControl(ShellWindowDelegateView::BACK_BUTTON,
466        is_enabled);
467  } else if (control == FORWARD_BUTTON) {
468    delegate_view->EnableUIControl(ShellWindowDelegateView::FORWARD_BUTTON,
469        is_enabled);
470  } else if (control == STOP_BUTTON) {
471    delegate_view->EnableUIControl(ShellWindowDelegateView::STOP_BUTTON,
472        is_enabled);
473  }
474}
475
476void Shell::PlatformSetAddressBarURL(const GURL& url) {
477  if (headless_)
478    return;
479  ShellWindowDelegateView* delegate_view =
480    static_cast<ShellWindowDelegateView*>(window_widget_->widget_delegate());
481  delegate_view->SetAddressBarURL(url);
482}
483
484void Shell::PlatformSetIsLoading(bool loading) {
485}
486
487void Shell::PlatformCreateWindow(int width, int height) {
488  if (headless_) {
489    content_size_ = gfx::Size(width, height);
490    if (!platform_)
491      platform_ = new ShellPlatformDataAura(content_size_);
492    else
493      platform_->ResizeWindow(content_size_);
494    return;
495  }
496#if defined(OS_CHROMEOS)
497  window_widget_ = views::Widget::CreateWindowWithContextAndBounds(
498      new ShellWindowDelegateView(this),
499      wm_test_helper_->GetDefaultParent(NULL, NULL, gfx::Rect()),
500      gfx::Rect(0, 0, width, height));
501#else
502  window_widget_ = new views::Widget;
503  views::Widget::InitParams params;
504  params.bounds = gfx::Rect(0, 0, width, height);
505  params.delegate = new ShellWindowDelegateView(this);
506  window_widget_->Init(params);
507#endif
508
509  content_size_ = gfx::Size(width, height);
510
511  window_ = window_widget_->GetNativeWindow();
512  // Call ShowRootWindow on RootWindow created by WMTestHelper without
513  // which XWindow owned by RootWindow doesn't get mapped.
514  window_->GetHost()->Show();
515  window_widget_->Show();
516}
517
518void Shell::PlatformSetContents() {
519  if (headless_) {
520    CHECK(platform_);
521    aura::Window* content = web_contents_->GetNativeView();
522    aura::Window* parent = platform_->host()->window();
523    if (!parent->Contains(content)) {
524      parent->AddChild(content);
525      content->Show();
526    }
527    content->SetBounds(gfx::Rect(content_size_));
528    RenderWidgetHostView* host_view = web_contents_->GetRenderWidgetHostView();
529    if (host_view)
530      host_view->SetSize(content_size_);
531  } else {
532    views::WidgetDelegate* widget_delegate = window_widget_->widget_delegate();
533    ShellWindowDelegateView* delegate_view =
534        static_cast<ShellWindowDelegateView*>(widget_delegate);
535    delegate_view->SetWebContents(web_contents_.get(), content_size_);
536  }
537}
538
539void Shell::PlatformResizeSubViews() {
540}
541
542void Shell::Close() {
543  if (headless_)
544    delete this;
545  else
546    window_widget_->CloseNow();
547}
548
549void Shell::PlatformSetTitle(const base::string16& title) {
550  if (headless_)
551    return;
552  ShellWindowDelegateView* delegate_view =
553    static_cast<ShellWindowDelegateView*>(window_widget_->widget_delegate());
554  delegate_view->SetWindowTitle(title);
555  window_widget_->UpdateWindowTitle();
556}
557
558bool Shell::PlatformHandleContextMenu(
559    const content::ContextMenuParams& params) {
560  if (headless_)
561    return true;
562  ShellWindowDelegateView* delegate_view =
563    static_cast<ShellWindowDelegateView*>(window_widget_->widget_delegate());
564  delegate_view->ShowWebViewContextMenu(params);
565  return true;
566}
567
568void Shell::PlatformWebContentsFocused(WebContents* contents) {
569  if (headless_)
570    return;
571  ShellWindowDelegateView* delegate_view =
572    static_cast<ShellWindowDelegateView*>(window_widget_->widget_delegate());
573  delegate_view->OnWebContentsFocused(contents);
574}
575
576}  // namespace content
577