Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / content / browser / web_contents / web_contents_view_gtk.cc
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/frame_host/interstitial_page_impl.h"
17 #include "content/browser/renderer_host/render_view_host_factory.h"
18 #include "content/browser/renderer_host/render_view_host_impl.h"
19 #include "content/browser/renderer_host/render_widget_host_view_gtk.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
32 using blink::WebDragOperation;
33 using blink::WebDragOperationsMask;
34
35 namespace content {
36 namespace {
37
38 // Called when the mouse leaves the widget. We notify our delegate.
39 gboolean 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.
48 gboolean 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.
57 gboolean 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
79 WebContentsViewPort* 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
88 WebContentsViewGtk::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
107 WebContentsViewGtk::~WebContentsViewGtk() {
108   expanded_.Destroy();
109 }
110
111 gfx::NativeView WebContentsViewGtk::GetNativeView() const {
112   if (delegate_)
113     return delegate_->GetNativeView();
114
115   return expanded_.get();
116 }
117
118 gfx::NativeView WebContentsViewGtk::GetContentNativeView() const {
119   RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
120   if (!rwhv)
121     return NULL;
122   return rwhv->GetNativeView();
123 }
124
125 gfx::NativeWindow WebContentsViewGtk::GetTopLevelNativeWindow() const {
126   GtkWidget* window = gtk_widget_get_ancestor(GetNativeView(), GTK_TYPE_WINDOW);
127   return window ? GTK_WINDOW(window) : NULL;
128 }
129
130 void 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
147 void WebContentsViewGtk::OnTabCrashed(base::TerminationStatus status,
148                                       int error_code) {
149 }
150
151 void WebContentsViewGtk::Focus() {
152   if (web_contents_->ShowingInterstitialPage()) {
153     web_contents_->GetInterstitialPage()->Focus();
154   } else if (delegate_) {
155     delegate_->Focus();
156   }
157 }
158
159 void WebContentsViewGtk::SetInitialFocus() {
160   if (web_contents_->FocusLocationBarByDefault())
161     web_contents_->SetFocusToLocationBar(false);
162   else
163     Focus();
164 }
165
166 void WebContentsViewGtk::StoreFocus() {
167   focus_store_.Store(GetNativeView());
168 }
169
170 void WebContentsViewGtk::RestoreFocus() {
171   if (focus_store_.widget())
172     gtk_widget_grab_focus(focus_store_.widget());
173   else
174     SetInitialFocus();
175 }
176
177 DropData* WebContentsViewGtk::GetDropData() const {
178   if (!drag_dest_)
179     return NULL;
180   return drag_dest_->current_drop_data();
181 }
182
183 gfx::Rect WebContentsViewGtk::GetViewBounds() const {
184   gfx::Rect rect;
185   GdkWindow* window = gtk_widget_get_window(GetNativeView());
186   if (!window) {
187     rect.SetRect(0, 0, requested_size_.width(), requested_size_.height());
188     return rect;
189   }
190   int x = 0, y = 0, w, h;
191   gdk_window_get_geometry(window, &x, &y, &w, &h, NULL);
192   rect.SetRect(x, y, w, h);
193   return rect;
194 }
195
196 void WebContentsViewGtk::CreateView(
197     const gfx::Size& initial_size, gfx::NativeView context) {
198   requested_size_ = initial_size;
199 }
200
201 RenderWidgetHostView* WebContentsViewGtk::CreateViewForWidget(
202     RenderWidgetHost* render_widget_host) {
203   if (render_widget_host->GetView()) {
204     // During testing, the view will already be set up in most cases to the
205     // test view, so we don't want to clobber it with a real one. To verify that
206     // this actually is happening (and somebody isn't accidentally creating the
207     // view twice), we check for the RVH Factory, which will be set when we're
208     // making special ones (which go along with the special views).
209     DCHECK(RenderViewHostFactory::has_factory());
210     return render_widget_host->GetView();
211   }
212
213   RenderWidgetHostView* view =
214       RenderWidgetHostView::CreateViewForWidget(render_widget_host);
215   view->InitAsChild(NULL);
216   gfx::NativeView content_view = view->GetNativeView();
217   g_signal_connect(content_view, "focus", G_CALLBACK(OnFocusThunk), this);
218   g_signal_connect(content_view, "leave-notify-event",
219                    G_CALLBACK(OnLeaveNotify), web_contents_);
220   g_signal_connect(content_view, "motion-notify-event",
221                    G_CALLBACK(OnMouseMove), web_contents_);
222   g_signal_connect(content_view, "scroll-event",
223                    G_CALLBACK(OnMouseScroll), web_contents_);
224   gtk_widget_add_events(content_view, GDK_LEAVE_NOTIFY_MASK |
225                         GDK_POINTER_MOTION_MASK);
226   InsertIntoContentArea(content_view);
227
228   if (render_widget_host->IsRenderView()) {
229     RenderViewHost* rvh = RenderViewHost::From(render_widget_host);
230     // If |rvh| is already the current render view host for the web contents, we
231     // need to initialize |drag_dest_| for drags to be properly handled.
232     // Otherwise, |drag_dest_| will be updated in RenderViewSwappedIn. The
233     // reason we can't simply check that this isn't a swapped-out view is
234     // because there are navigations that create non-swapped-out views that may
235     // never be displayed, e.g. a navigation that becomes a download.
236     if (rvh == web_contents_->GetRenderViewHost()) {
237       UpdateDragDest(rvh);
238     }
239   }
240
241   return view;
242 }
243
244 RenderWidgetHostView* WebContentsViewGtk::CreateViewForPopupWidget(
245     RenderWidgetHost* render_widget_host) {
246   return RenderWidgetHostViewPort::CreateViewForWidget(render_widget_host);
247 }
248
249 void WebContentsViewGtk::SetPageTitle(const base::string16& title) {
250   // Set the window name to include the page title so it's easier to spot
251   // when debugging (e.g. via xwininfo -tree).
252   gfx::NativeView content_view = GetContentNativeView();
253   if (content_view) {
254     GdkWindow* content_window = gtk_widget_get_window(content_view);
255     if (content_window) {
256       gdk_window_set_title(content_window, base::UTF16ToUTF8(title).c_str());
257     }
258   }
259 }
260
261 void WebContentsViewGtk::SizeContents(const gfx::Size& size) {
262   // We don't need to manually set the size of of widgets in GTK+, but we do
263   // need to pass the sizing information on to the RWHV which will pass the
264   // sizing information on to the renderer.
265   requested_size_ = size;
266   RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
267   if (rwhv)
268     rwhv->SetSize(size);
269 }
270
271 void WebContentsViewGtk::RenderViewCreated(RenderViewHost* host) {
272 }
273
274 void WebContentsViewGtk::RenderViewSwappedIn(RenderViewHost* host) {
275   UpdateDragDest(host);
276 }
277
278 void WebContentsViewGtk::SetOverscrollControllerEnabled(bool enabled) {
279 }
280
281 WebContents* WebContentsViewGtk::web_contents() {
282   return web_contents_;
283 }
284
285 void WebContentsViewGtk::UpdateDragCursor(WebDragOperation operation) {
286   if (!drag_dest_)
287     return;
288   drag_dest_->UpdateDragStatus(operation);
289 }
290
291 void WebContentsViewGtk::GotFocus() {
292   // This is only used in the views FocusManager stuff but it bleeds through
293   // all subclasses. http://crbug.com/21875
294 }
295
296 // This is called when the renderer asks us to take focus back (i.e., it has
297 // iterated past the last focusable element on the page).
298 void WebContentsViewGtk::TakeFocus(bool reverse) {
299   if (!web_contents_->GetDelegate())
300     return;
301   if (!web_contents_->GetDelegate()->TakeFocus(web_contents_, reverse) &&
302       GetTopLevelNativeWindow()) {
303     gtk_widget_child_focus(GTK_WIDGET(GetTopLevelNativeWindow()),
304         reverse ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD);
305   }
306 }
307
308 void WebContentsViewGtk::InsertIntoContentArea(GtkWidget* widget) {
309   gtk_container_add(GTK_CONTAINER(expanded_.get()), widget);
310 }
311
312 void WebContentsViewGtk::UpdateDragDest(RenderViewHost* host) {
313   // Drag-and-drop is entirely managed by BrowserPluginGuest for guest
314   // processes in a largely platform independent way. WebDragDestGtk
315   // will result in spurious messages being sent to the guest process which
316   // will violate assumptions.
317   if (host->GetProcess() && host->GetProcess()->IsGuest()) {
318     DCHECK(!drag_dest_);
319     return;
320   }
321
322   gfx::NativeView content_view = host->GetView()->GetNativeView();
323
324   // If the host is already used by the drag_dest_, there's no point in deleting
325   // the old one to create an identical copy.
326   if (drag_dest_.get() && drag_dest_->widget() == content_view)
327     return;
328
329   // Clear the currently connected drag drop signals by deleting the old
330   // drag_dest_ before creating the new one.
331   drag_dest_.reset();
332   // Create the new drag_dest_.
333   drag_dest_.reset(new WebDragDestGtk(web_contents_, content_view));
334
335   if (delegate_)
336     drag_dest_->set_delegate(delegate_->GetDragDestDelegate());
337 }
338
339 // Called when the content view gtk widget is tabbed to, or after the call to
340 // gtk_widget_child_focus() in TakeFocus(). We return true
341 // and grab focus if we don't have it. The call to
342 // FocusThroughTabTraversal(bool) forwards the "move focus forward" effect to
343 // webkit.
344 gboolean WebContentsViewGtk::OnFocus(GtkWidget* widget,
345                                      GtkDirectionType focus) {
346   // Give our view wrapper first chance at this event.
347   if (delegate_) {
348     gboolean return_value = FALSE;
349     if (delegate_->OnNativeViewFocusEvent(widget, focus, &return_value))
350       return return_value;
351   }
352
353   // If we already have focus, let the next widget have a shot at it. We will
354   // reach this situation after the call to gtk_widget_child_focus() in
355   // TakeFocus().
356   if (gtk_widget_is_focus(widget))
357     return FALSE;
358
359   gtk_widget_grab_focus(widget);
360   bool reverse = focus == GTK_DIR_TAB_BACKWARD;
361   web_contents_->FocusThroughTabTraversal(reverse);
362   return TRUE;
363 }
364
365 void WebContentsViewGtk::ShowContextMenu(RenderFrameHost* render_frame_host,
366                                          const ContextMenuParams& params) {
367   if (delegate_)
368     delegate_->ShowContextMenu(render_frame_host, params);
369   else
370     DLOG(ERROR) << "Cannot show context menus without a delegate.";
371 }
372
373 // Render view DnD -------------------------------------------------------------
374
375 void WebContentsViewGtk::StartDragging(const DropData& drop_data,
376                                        WebDragOperationsMask ops,
377                                        const gfx::ImageSkia& image,
378                                        const gfx::Vector2d& image_offset,
379                                        const DragEventSourceInfo& event_info) {
380   DCHECK(GetContentNativeView());
381
382   RenderWidgetHostViewGtk* view_gtk = static_cast<RenderWidgetHostViewGtk*>(
383       web_contents_->GetRenderWidgetHostView());
384   if (!view_gtk || !view_gtk->GetLastMouseDown() ||
385       !drag_source_->StartDragging(drop_data, ops, view_gtk->GetLastMouseDown(),
386                                    *image.bitmap(), image_offset)) {
387     web_contents_->SystemDragEnded();
388   }
389 }
390
391 // -----------------------------------------------------------------------------
392
393 void WebContentsViewGtk::OnChildSizeRequest(GtkWidget* widget,
394                                             GtkWidget* child,
395                                             GtkRequisition* requisition) {
396   if (web_contents_->GetDelegate()) {
397     requisition->height +=
398         web_contents_->GetDelegate()->GetExtraRenderViewHeight();
399   }
400 }
401
402 void WebContentsViewGtk::OnSizeAllocate(GtkWidget* widget,
403                                         GtkAllocation* allocation) {
404   int width = allocation->width;
405   int height = allocation->height;
406   // |delegate()| can be NULL here during browser teardown.
407   if (web_contents_->GetDelegate())
408     height += web_contents_->GetDelegate()->GetExtraRenderViewHeight();
409   gfx::Size size(width, height);
410   requested_size_ = size;
411
412   // We manually tell our RWHV to resize the renderer content.  This avoids
413   // spurious resizes from GTK+.
414   RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
415   if (rwhv)
416     rwhv->SetSize(size);
417   if (web_contents_->GetInterstitialPage())
418     web_contents_->GetInterstitialPage()->SetSize(size);
419 }
420
421 }  // namespace content