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.
5 #include "content/browser/web_contents/web_contents_view_gtk.h"
8 #include <gdk/gdkkeysyms.h>
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"
32 using blink::WebDragOperation
;
33 using blink::WebDragOperationsMask
;
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);
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);
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()) !=
64 WebContentsDelegate
* delegate
= web_contents
->GetDelegate();
68 if (!(event
->direction
== GDK_SCROLL_DOWN
||
69 event
->direction
== GDK_SCROLL_UP
)) {
73 delegate
->ContentsZoomChange(event
->direction
== GDK_SCROLL_UP
);
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
;
88 WebContentsViewGtk::WebContentsViewGtk(
89 WebContentsImpl
* web_contents
,
90 WebContentsViewDelegate
* delegate
)
91 : web_contents_(web_contents
),
92 expanded_(gtk_expanded_container_new()),
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);
100 gtk_widget_show(expanded_
.get());
101 drag_source_
.reset(new WebDragSourceGtk(web_contents
));
104 delegate_
->Initialize(expanded_
.get(), &focus_store_
);
107 WebContentsViewGtk::~WebContentsViewGtk() {
111 gfx::NativeView
WebContentsViewGtk::GetNativeView() const {
113 return delegate_
->GetNativeView();
115 return expanded_
.get();
118 gfx::NativeView
WebContentsViewGtk::GetContentNativeView() const {
119 RenderWidgetHostView
* rwhv
= web_contents_
->GetRenderWidgetHostView();
122 return rwhv
->GetNativeView();
125 gfx::NativeWindow
WebContentsViewGtk::GetTopLevelNativeWindow() const {
126 GtkWidget
* window
= gtk_widget_get_ancestor(GetNativeView(), GTK_TYPE_WINDOW
);
127 return window
? GTK_WINDOW(window
) : NULL
;
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
137 GdkWindow
* expanded_window
= gtk_widget_get_window(expanded_
.get());
139 gdk_window_get_origin(expanded_window
, &x
, &y
);
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());
147 void WebContentsViewGtk::OnTabCrashed(base::TerminationStatus status
,
151 void WebContentsViewGtk::Focus() {
152 if (web_contents_
->ShowingInterstitialPage()) {
153 web_contents_
->GetInterstitialPage()->Focus();
154 } else if (delegate_
) {
159 void WebContentsViewGtk::SetInitialFocus() {
160 if (web_contents_
->FocusLocationBarByDefault())
161 web_contents_
->SetFocusToLocationBar(false);
166 void WebContentsViewGtk::StoreFocus() {
167 focus_store_
.Store(GetNativeView());
170 void WebContentsViewGtk::RestoreFocus() {
171 if (focus_store_
.widget())
172 gtk_widget_grab_focus(focus_store_
.widget());
177 DropData
* WebContentsViewGtk::GetDropData() const {
180 return drag_dest_
->current_drop_data();
183 gfx::Rect
WebContentsViewGtk::GetViewBounds() const {
185 GdkWindow
* window
= gtk_widget_get_window(GetNativeView());
187 rect
.SetRect(0, 0, requested_size_
.width(), requested_size_
.height());
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
);
196 void WebContentsViewGtk::CreateView(
197 const gfx::Size
& initial_size
, gfx::NativeView context
) {
198 requested_size_
= initial_size
;
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();
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
);
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()) {
244 RenderWidgetHostView
* WebContentsViewGtk::CreateViewForPopupWidget(
245 RenderWidgetHost
* render_widget_host
) {
246 return RenderWidgetHostViewPort::CreateViewForWidget(render_widget_host
);
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();
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());
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();
271 void WebContentsViewGtk::RenderViewCreated(RenderViewHost
* host
) {
274 void WebContentsViewGtk::RenderViewSwappedIn(RenderViewHost
* host
) {
275 UpdateDragDest(host
);
278 void WebContentsViewGtk::SetOverscrollControllerEnabled(bool enabled
) {
281 WebContents
* WebContentsViewGtk::web_contents() {
282 return web_contents_
;
285 void WebContentsViewGtk::UpdateDragCursor(WebDragOperation operation
) {
288 drag_dest_
->UpdateDragStatus(operation
);
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
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())
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
);
308 void WebContentsViewGtk::InsertIntoContentArea(GtkWidget
* widget
) {
309 gtk_container_add(GTK_CONTAINER(expanded_
.get()), widget
);
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()) {
322 gfx::NativeView content_view
= host
->GetView()->GetNativeView();
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
)
329 // Clear the currently connected drag drop signals by deleting the old
330 // drag_dest_ before creating the new one.
332 // Create the new drag_dest_.
333 drag_dest_
.reset(new WebDragDestGtk(web_contents_
, content_view
));
336 drag_dest_
->set_delegate(delegate_
->GetDragDestDelegate());
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
344 gboolean
WebContentsViewGtk::OnFocus(GtkWidget
* widget
,
345 GtkDirectionType focus
) {
346 // Give our view wrapper first chance at this event.
348 gboolean return_value
= FALSE
;
349 if (delegate_
->OnNativeViewFocusEvent(widget
, focus
, &return_value
))
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
356 if (gtk_widget_is_focus(widget
))
359 gtk_widget_grab_focus(widget
);
360 bool reverse
= focus
== GTK_DIR_TAB_BACKWARD
;
361 web_contents_
->FocusThroughTabTraversal(reverse
);
365 void WebContentsViewGtk::ShowContextMenu(RenderFrameHost
* render_frame_host
,
366 const ContextMenuParams
& params
) {
368 delegate_
->ShowContextMenu(render_frame_host
, params
);
370 DLOG(ERROR
) << "Cannot show context menus without a delegate.";
373 // Render view DnD -------------------------------------------------------------
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());
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();
391 // -----------------------------------------------------------------------------
393 void WebContentsViewGtk::OnChildSizeRequest(GtkWidget
* widget
,
395 GtkRequisition
* requisition
) {
396 if (web_contents_
->GetDelegate()) {
397 requisition
->height
+=
398 web_contents_
->GetDelegate()->GetExtraRenderViewHeight();
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
;
412 // We manually tell our RWHV to resize the renderer content. This avoids
413 // spurious resizes from GTK+.
414 RenderWidgetHostView
* rwhv
= web_contents_
->GetRenderWidgetHostView();
417 if (web_contents_
->GetInterstitialPage())
418 web_contents_
->GetInterstitialPage()->SetSize(size
);
421 } // namespace content