1// Copyright (c) 2012 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/browser/web_contents/web_contents_view_gtk.h"
6
7#include <gdk/gdk.h>
8#include <gdk/gdkkeysyms.h>
9#include <gtk/gtk.h>
10
11#include <algorithm>
12
13#include "base/strings/string_util.h"
14#include "base/strings/utf_string_conversions.h"
15#include "build/build_config.h"
16#include "content/browser/renderer_host/render_view_host_factory.h"
17#include "content/browser/renderer_host/render_view_host_impl.h"
18#include "content/browser/renderer_host/render_widget_host_view_gtk.h"
19#include "content/browser/web_contents/interstitial_page_impl.h"
20#include "content/browser/web_contents/web_contents_impl.h"
21#include "content/browser/web_contents/web_drag_dest_gtk.h"
22#include "content/browser/web_contents/web_drag_source_gtk.h"
23#include "content/public/browser/web_contents_delegate.h"
24#include "content/public/browser/web_contents_view_delegate.h"
25#include "content/public/common/drop_data.h"
26#include "ui/base/gtk/gtk_expanded_container.h"
27#include "ui/gfx/image/image_skia.h"
28#include "ui/gfx/point.h"
29#include "ui/gfx/rect.h"
30#include "ui/gfx/size.h"
31
32using WebKit::WebDragOperation;
33using WebKit::WebDragOperationsMask;
34
35namespace content {
36namespace {
37
38// Called when the mouse leaves the widget. We notify our delegate.
39gboolean OnLeaveNotify(GtkWidget* widget, GdkEventCrossing* event,
40                       WebContentsImpl* web_contents) {
41  if (web_contents->GetDelegate())
42    web_contents->GetDelegate()->ContentsMouseEvent(
43        web_contents, gfx::Point(event->x_root, event->y_root), false);
44  return FALSE;
45}
46
47// Called when the mouse moves within the widget. We notify our delegate.
48gboolean OnMouseMove(GtkWidget* widget, GdkEventMotion* event,
49                     WebContentsImpl* web_contents) {
50  if (web_contents->GetDelegate())
51    web_contents->GetDelegate()->ContentsMouseEvent(
52        web_contents, gfx::Point(event->x_root, event->y_root), true);
53  return FALSE;
54}
55
56// See tab_contents_view_views.cc for discussion of mouse scroll zooming.
57gboolean OnMouseScroll(GtkWidget* widget, GdkEventScroll* event,
58                       WebContentsImpl* web_contents) {
59  if ((event->state & gtk_accelerator_get_default_mod_mask()) !=
60      GDK_CONTROL_MASK) {
61    return FALSE;
62  }
63
64  WebContentsDelegate* delegate = web_contents->GetDelegate();
65  if (!delegate)
66    return FALSE;
67
68  if (!(event->direction == GDK_SCROLL_DOWN ||
69        event->direction == GDK_SCROLL_UP)) {
70    return FALSE;
71  }
72
73  delegate->ContentsZoomChange(event->direction == GDK_SCROLL_UP);
74  return TRUE;
75}
76
77}  // namespace
78
79WebContentsViewPort* CreateWebContentsView(
80    WebContentsImpl* web_contents,
81    WebContentsViewDelegate* delegate,
82    RenderViewHostDelegateView** render_view_host_delegate_view) {
83  WebContentsViewGtk* rv = new WebContentsViewGtk(web_contents, delegate);
84  *render_view_host_delegate_view = rv;
85  return rv;
86}
87
88WebContentsViewGtk::WebContentsViewGtk(
89    WebContentsImpl* web_contents,
90    WebContentsViewDelegate* delegate)
91    : web_contents_(web_contents),
92      expanded_(gtk_expanded_container_new()),
93      delegate_(delegate) {
94  gtk_widget_set_name(expanded_.get(), "chrome-web-contents-view");
95  g_signal_connect(expanded_.get(), "size-allocate",
96                   G_CALLBACK(OnSizeAllocateThunk), this);
97  g_signal_connect(expanded_.get(), "child-size-request",
98                   G_CALLBACK(OnChildSizeRequestThunk), this);
99
100  gtk_widget_show(expanded_.get());
101  drag_source_.reset(new WebDragSourceGtk(web_contents));
102
103  if (delegate_)
104    delegate_->Initialize(expanded_.get(), &focus_store_);
105}
106
107WebContentsViewGtk::~WebContentsViewGtk() {
108  expanded_.Destroy();
109}
110
111gfx::NativeView WebContentsViewGtk::GetNativeView() const {
112  if (delegate_)
113    return delegate_->GetNativeView();
114
115  return expanded_.get();
116}
117
118gfx::NativeView WebContentsViewGtk::GetContentNativeView() const {
119  RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
120  if (!rwhv)
121    return NULL;
122  return rwhv->GetNativeView();
123}
124
125gfx::NativeWindow WebContentsViewGtk::GetTopLevelNativeWindow() const {
126  GtkWidget* window = gtk_widget_get_ancestor(GetNativeView(), GTK_TYPE_WINDOW);
127  return window ? GTK_WINDOW(window) : NULL;
128}
129
130void WebContentsViewGtk::GetContainerBounds(gfx::Rect* out) const {
131  // This is used for positioning the download shelf arrow animation,
132  // as well as sizing some other widgets in Windows.  In GTK the size is
133  // managed for us, so it appears to be only used for the download shelf
134  // animation.
135  int x = 0;
136  int y = 0;
137  GdkWindow* expanded_window = gtk_widget_get_window(expanded_.get());
138  if (expanded_window)
139    gdk_window_get_origin(expanded_window, &x, &y);
140
141  GtkAllocation allocation;
142  gtk_widget_get_allocation(expanded_.get(), &allocation);
143  out->SetRect(x + allocation.x, y + allocation.y,
144               requested_size_.width(), requested_size_.height());
145}
146
147void WebContentsViewGtk::OnTabCrashed(base::TerminationStatus status,
148                                      int error_code) {
149}
150
151void WebContentsViewGtk::Focus() {
152  if (web_contents_->ShowingInterstitialPage()) {
153    web_contents_->GetInterstitialPage()->Focus();
154  } else if (delegate_) {
155    delegate_->Focus();
156  }
157}
158
159void WebContentsViewGtk::SetInitialFocus() {
160  if (web_contents_->FocusLocationBarByDefault())
161    web_contents_->SetFocusToLocationBar(false);
162  else
163    Focus();
164}
165
166void WebContentsViewGtk::StoreFocus() {
167  focus_store_.Store(GetNativeView());
168}
169
170void WebContentsViewGtk::RestoreFocus() {
171  if (focus_store_.widget())
172    gtk_widget_grab_focus(focus_store_.widget());
173  else
174    SetInitialFocus();
175}
176
177DropData* WebContentsViewGtk::GetDropData() const {
178  return drag_dest_->current_drop_data();
179}
180
181gfx::Rect WebContentsViewGtk::GetViewBounds() const {
182  gfx::Rect rect;
183  GdkWindow* window = gtk_widget_get_window(GetNativeView());
184  if (!window) {
185    rect.SetRect(0, 0, requested_size_.width(), requested_size_.height());
186    return rect;
187  }
188  int x = 0, y = 0, w, h;
189  gdk_window_get_geometry(window, &x, &y, &w, &h, NULL);
190  rect.SetRect(x, y, w, h);
191  return rect;
192}
193
194void WebContentsViewGtk::CreateView(
195    const gfx::Size& initial_size, gfx::NativeView context) {
196  requested_size_ = initial_size;
197}
198
199RenderWidgetHostView* WebContentsViewGtk::CreateViewForWidget(
200    RenderWidgetHost* render_widget_host) {
201  if (render_widget_host->GetView()) {
202    // During testing, the view will already be set up in most cases to the
203    // test view, so we don't want to clobber it with a real one. To verify that
204    // this actually is happening (and somebody isn't accidentally creating the
205    // view twice), we check for the RVH Factory, which will be set when we're
206    // making special ones (which go along with the special views).
207    DCHECK(RenderViewHostFactory::has_factory());
208    return render_widget_host->GetView();
209  }
210
211  RenderWidgetHostView* view =
212      RenderWidgetHostView::CreateViewForWidget(render_widget_host);
213  view->InitAsChild(NULL);
214  gfx::NativeView content_view = view->GetNativeView();
215  g_signal_connect(content_view, "focus", G_CALLBACK(OnFocusThunk), this);
216  g_signal_connect(content_view, "leave-notify-event",
217                   G_CALLBACK(OnLeaveNotify), web_contents_);
218  g_signal_connect(content_view, "motion-notify-event",
219                   G_CALLBACK(OnMouseMove), web_contents_);
220  g_signal_connect(content_view, "scroll-event",
221                   G_CALLBACK(OnMouseScroll), web_contents_);
222  gtk_widget_add_events(content_view, GDK_LEAVE_NOTIFY_MASK |
223                        GDK_POINTER_MOTION_MASK);
224  InsertIntoContentArea(content_view);
225
226  if (render_widget_host->IsRenderView()) {
227    RenderViewHost* rvh = RenderViewHost::From(render_widget_host);
228    // If |rvh| is already the current render view host for the web contents, we
229    // need to initialize |drag_dest_| for drags to be properly handled.
230    // Otherwise, |drag_dest_| will be updated in RenderViewSwappedIn. The
231    // reason we can't simply check that this isn't a swapped-out view is
232    // because there are navigations that create non-swapped-out views that may
233    // never be displayed, e.g. a navigation that becomes a download.
234    if (rvh == web_contents_->GetRenderViewHost()) {
235      UpdateDragDest(rvh);
236    }
237  }
238
239  return view;
240}
241
242RenderWidgetHostView* WebContentsViewGtk::CreateViewForPopupWidget(
243    RenderWidgetHost* render_widget_host) {
244  return RenderWidgetHostViewPort::CreateViewForWidget(render_widget_host);
245}
246
247void WebContentsViewGtk::SetPageTitle(const string16& title) {
248  // Set the window name to include the page title so it's easier to spot
249  // when debugging (e.g. via xwininfo -tree).
250  gfx::NativeView content_view = GetContentNativeView();
251  if (content_view) {
252    GdkWindow* content_window = gtk_widget_get_window(content_view);
253    if (content_window) {
254      gdk_window_set_title(content_window, UTF16ToUTF8(title).c_str());
255    }
256  }
257}
258
259void WebContentsViewGtk::SizeContents(const gfx::Size& size) {
260  // We don't need to manually set the size of of widgets in GTK+, but we do
261  // need to pass the sizing information on to the RWHV which will pass the
262  // sizing information on to the renderer.
263  requested_size_ = size;
264  RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
265  if (rwhv)
266    rwhv->SetSize(size);
267}
268
269void WebContentsViewGtk::RenderViewCreated(RenderViewHost* host) {
270}
271
272void WebContentsViewGtk::RenderViewSwappedIn(RenderViewHost* host) {
273  UpdateDragDest(host);
274}
275
276void WebContentsViewGtk::SetOverscrollControllerEnabled(bool enabled) {
277}
278
279WebContents* WebContentsViewGtk::web_contents() {
280  return web_contents_;
281}
282
283void WebContentsViewGtk::UpdateDragCursor(WebDragOperation operation) {
284  drag_dest_->UpdateDragStatus(operation);
285}
286
287void WebContentsViewGtk::GotFocus() {
288  // This is only used in the views FocusManager stuff but it bleeds through
289  // all subclasses. http://crbug.com/21875
290}
291
292// This is called when the renderer asks us to take focus back (i.e., it has
293// iterated past the last focusable element on the page).
294void WebContentsViewGtk::TakeFocus(bool reverse) {
295  if (!web_contents_->GetDelegate())
296    return;
297  if (!web_contents_->GetDelegate()->TakeFocus(web_contents_, reverse) &&
298      GetTopLevelNativeWindow()) {
299    gtk_widget_child_focus(GTK_WIDGET(GetTopLevelNativeWindow()),
300        reverse ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD);
301  }
302}
303
304void WebContentsViewGtk::InsertIntoContentArea(GtkWidget* widget) {
305  gtk_container_add(GTK_CONTAINER(expanded_.get()), widget);
306}
307
308void WebContentsViewGtk::UpdateDragDest(RenderViewHost* host) {
309  gfx::NativeView content_view = host->GetView()->GetNativeView();
310
311  // If the host is already used by the drag_dest_, there's no point in deleting
312  // the old one to create an identical copy.
313  if (drag_dest_.get() && drag_dest_->widget() == content_view)
314    return;
315
316  // Clear the currently connected drag drop signals by deleting the old
317  // drag_dest_ before creating the new one.
318  drag_dest_.reset();
319  // Create the new drag_dest_.
320  drag_dest_.reset(new WebDragDestGtk(web_contents_, content_view));
321
322  if (delegate_)
323    drag_dest_->set_delegate(delegate_->GetDragDestDelegate());
324}
325
326// Called when the content view gtk widget is tabbed to, or after the call to
327// gtk_widget_child_focus() in TakeFocus(). We return true
328// and grab focus if we don't have it. The call to
329// FocusThroughTabTraversal(bool) forwards the "move focus forward" effect to
330// webkit.
331gboolean WebContentsViewGtk::OnFocus(GtkWidget* widget,
332                                     GtkDirectionType focus) {
333  // Give our view wrapper first chance at this event.
334  if (delegate_) {
335    gboolean return_value = FALSE;
336    if (delegate_->OnNativeViewFocusEvent(widget, focus, &return_value))
337      return return_value;
338  }
339
340  // If we already have focus, let the next widget have a shot at it. We will
341  // reach this situation after the call to gtk_widget_child_focus() in
342  // TakeFocus().
343  if (gtk_widget_is_focus(widget))
344    return FALSE;
345
346  gtk_widget_grab_focus(widget);
347  bool reverse = focus == GTK_DIR_TAB_BACKWARD;
348  web_contents_->FocusThroughTabTraversal(reverse);
349  return TRUE;
350}
351
352void WebContentsViewGtk::ShowContextMenu(const ContextMenuParams& params) {
353  if (delegate_)
354    delegate_->ShowContextMenu(params);
355  else
356    DLOG(ERROR) << "Cannot show context menus without a delegate.";
357}
358
359void WebContentsViewGtk::ShowPopupMenu(const gfx::Rect& bounds,
360                                       int item_height,
361                                       double item_font_size,
362                                       int selected_item,
363                                       const std::vector<MenuItem>& items,
364                                       bool right_aligned,
365                                       bool allow_multiple_selection) {
366  // External popup menus are only used on Mac and Android.
367  NOTIMPLEMENTED();
368}
369
370// Render view DnD -------------------------------------------------------------
371
372void WebContentsViewGtk::StartDragging(const DropData& drop_data,
373                                       WebDragOperationsMask ops,
374                                       const gfx::ImageSkia& image,
375                                       const gfx::Vector2d& image_offset,
376                                       const DragEventSourceInfo& event_info) {
377  DCHECK(GetContentNativeView());
378
379  RenderWidgetHostViewGtk* view_gtk = static_cast<RenderWidgetHostViewGtk*>(
380      web_contents_->GetRenderWidgetHostView());
381  if (!view_gtk || !view_gtk->GetLastMouseDown() ||
382      !drag_source_->StartDragging(drop_data, ops, view_gtk->GetLastMouseDown(),
383                                   *image.bitmap(), image_offset)) {
384    web_contents_->SystemDragEnded();
385  }
386}
387
388// -----------------------------------------------------------------------------
389
390void WebContentsViewGtk::OnChildSizeRequest(GtkWidget* widget,
391                                            GtkWidget* child,
392                                            GtkRequisition* requisition) {
393  if (web_contents_->GetDelegate()) {
394    requisition->height +=
395        web_contents_->GetDelegate()->GetExtraRenderViewHeight();
396  }
397}
398
399void WebContentsViewGtk::OnSizeAllocate(GtkWidget* widget,
400                                        GtkAllocation* allocation) {
401  int width = allocation->width;
402  int height = allocation->height;
403  // |delegate()| can be NULL here during browser teardown.
404  if (web_contents_->GetDelegate())
405    height += web_contents_->GetDelegate()->GetExtraRenderViewHeight();
406  gfx::Size size(width, height);
407  requested_size_ = size;
408
409  // We manually tell our RWHV to resize the renderer content.  This avoids
410  // spurious resizes from GTK+.
411  RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
412  if (rwhv)
413    rwhv->SetSize(size);
414  if (web_contents_->GetInterstitialPage())
415    web_contents_->GetInterstitialPage()->SetSize(size);
416}
417
418}  // namespace content
419