blob: bf4dcb7fd3bb07363eeaf6d49af34f54c25d02cb [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/web_contents/web_contents_view_gtk.h"
#include <gdk/gdk.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include <algorithm>
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "content/browser/frame_host/interstitial_page_impl.h"
#include "content/browser/renderer_host/render_view_host_factory.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_gtk.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/web_contents/web_drag_dest_gtk.h"
#include "content/browser/web_contents/web_drag_source_gtk.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_contents_view_delegate.h"
#include "content/public/common/drop_data.h"
#include "ui/base/gtk/gtk_expanded_container.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/point.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/size.h"
using blink::WebDragOperation;
using blink::WebDragOperationsMask;
namespace content {
namespace {
// Called when the mouse leaves the widget. We notify our delegate.
gboolean OnLeaveNotify(GtkWidget* widget, GdkEventCrossing* event,
WebContentsImpl* web_contents) {
if (web_contents->GetDelegate())
web_contents->GetDelegate()->ContentsMouseEvent(
web_contents, gfx::Point(event->x_root, event->y_root), false);
return FALSE;
}
// Called when the mouse moves within the widget. We notify our delegate.
gboolean OnMouseMove(GtkWidget* widget, GdkEventMotion* event,
WebContentsImpl* web_contents) {
if (web_contents->GetDelegate())
web_contents->GetDelegate()->ContentsMouseEvent(
web_contents, gfx::Point(event->x_root, event->y_root), true);
return FALSE;
}
// See tab_contents_view_views.cc for discussion of mouse scroll zooming.
gboolean OnMouseScroll(GtkWidget* widget, GdkEventScroll* event,
WebContentsImpl* web_contents) {
if ((event->state & gtk_accelerator_get_default_mod_mask()) !=
GDK_CONTROL_MASK) {
return FALSE;
}
WebContentsDelegate* delegate = web_contents->GetDelegate();
if (!delegate)
return FALSE;
if (!(event->direction == GDK_SCROLL_DOWN ||
event->direction == GDK_SCROLL_UP)) {
return FALSE;
}
delegate->ContentsZoomChange(event->direction == GDK_SCROLL_UP);
return TRUE;
}
} // namespace
WebContentsViewPort* CreateWebContentsView(
WebContentsImpl* web_contents,
WebContentsViewDelegate* delegate,
RenderViewHostDelegateView** render_view_host_delegate_view) {
WebContentsViewGtk* rv = new WebContentsViewGtk(web_contents, delegate);
*render_view_host_delegate_view = rv;
return rv;
}
WebContentsViewGtk::WebContentsViewGtk(
WebContentsImpl* web_contents,
WebContentsViewDelegate* delegate)
: web_contents_(web_contents),
expanded_(gtk_expanded_container_new()),
delegate_(delegate) {
gtk_widget_set_name(expanded_.get(), "chrome-web-contents-view");
g_signal_connect(expanded_.get(), "size-allocate",
G_CALLBACK(OnSizeAllocateThunk), this);
g_signal_connect(expanded_.get(), "child-size-request",
G_CALLBACK(OnChildSizeRequestThunk), this);
gtk_widget_show(expanded_.get());
drag_source_.reset(new WebDragSourceGtk(web_contents));
if (delegate_)
delegate_->Initialize(expanded_.get(), &focus_store_);
}
WebContentsViewGtk::~WebContentsViewGtk() {
expanded_.Destroy();
}
gfx::NativeView WebContentsViewGtk::GetNativeView() const {
if (delegate_)
return delegate_->GetNativeView();
return expanded_.get();
}
gfx::NativeView WebContentsViewGtk::GetContentNativeView() const {
RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
if (!rwhv)
return NULL;
return rwhv->GetNativeView();
}
gfx::NativeWindow WebContentsViewGtk::GetTopLevelNativeWindow() const {
GtkWidget* window = gtk_widget_get_ancestor(GetNativeView(), GTK_TYPE_WINDOW);
return window ? GTK_WINDOW(window) : NULL;
}
void WebContentsViewGtk::GetContainerBounds(gfx::Rect* out) const {
// This is used for positioning the download shelf arrow animation,
// as well as sizing some other widgets in Windows. In GTK the size is
// managed for us, so it appears to be only used for the download shelf
// animation.
int x = 0;
int y = 0;
GdkWindow* expanded_window = gtk_widget_get_window(expanded_.get());
if (expanded_window)
gdk_window_get_origin(expanded_window, &x, &y);
GtkAllocation allocation;
gtk_widget_get_allocation(expanded_.get(), &allocation);
out->SetRect(x + allocation.x, y + allocation.y,
requested_size_.width(), requested_size_.height());
}
void WebContentsViewGtk::OnTabCrashed(base::TerminationStatus status,
int error_code) {
}
void WebContentsViewGtk::Focus() {
if (web_contents_->ShowingInterstitialPage()) {
web_contents_->GetInterstitialPage()->Focus();
} else if (delegate_) {
delegate_->Focus();
}
}
void WebContentsViewGtk::SetInitialFocus() {
if (web_contents_->FocusLocationBarByDefault())
web_contents_->SetFocusToLocationBar(false);
else
Focus();
}
void WebContentsViewGtk::StoreFocus() {
focus_store_.Store(GetNativeView());
}
void WebContentsViewGtk::RestoreFocus() {
if (focus_store_.widget())
gtk_widget_grab_focus(focus_store_.widget());
else
SetInitialFocus();
}
DropData* WebContentsViewGtk::GetDropData() const {
if (!drag_dest_)
return NULL;
return drag_dest_->current_drop_data();
}
gfx::Rect WebContentsViewGtk::GetViewBounds() const {
gfx::Rect rect;
GdkWindow* window = gtk_widget_get_window(GetNativeView());
if (!window) {
rect.SetRect(0, 0, requested_size_.width(), requested_size_.height());
return rect;
}
int x = 0, y = 0, w, h;
gdk_window_get_geometry(window, &x, &y, &w, &h, NULL);
rect.SetRect(x, y, w, h);
return rect;
}
void WebContentsViewGtk::CreateView(
const gfx::Size& initial_size, gfx::NativeView context) {
requested_size_ = initial_size;
}
RenderWidgetHostView* WebContentsViewGtk::CreateViewForWidget(
RenderWidgetHost* render_widget_host) {
if (render_widget_host->GetView()) {
// During testing, the view will already be set up in most cases to the
// test view, so we don't want to clobber it with a real one. To verify that
// this actually is happening (and somebody isn't accidentally creating the
// view twice), we check for the RVH Factory, which will be set when we're
// making special ones (which go along with the special views).
DCHECK(RenderViewHostFactory::has_factory());
return render_widget_host->GetView();
}
RenderWidgetHostView* view =
RenderWidgetHostView::CreateViewForWidget(render_widget_host);
view->InitAsChild(NULL);
gfx::NativeView content_view = view->GetNativeView();
g_signal_connect(content_view, "focus", G_CALLBACK(OnFocusThunk), this);
g_signal_connect(content_view, "leave-notify-event",
G_CALLBACK(OnLeaveNotify), web_contents_);
g_signal_connect(content_view, "motion-notify-event",
G_CALLBACK(OnMouseMove), web_contents_);
g_signal_connect(content_view, "scroll-event",
G_CALLBACK(OnMouseScroll), web_contents_);
gtk_widget_add_events(content_view, GDK_LEAVE_NOTIFY_MASK |
GDK_POINTER_MOTION_MASK);
InsertIntoContentArea(content_view);
if (render_widget_host->IsRenderView()) {
RenderViewHost* rvh = RenderViewHost::From(render_widget_host);
// If |rvh| is already the current render view host for the web contents, we
// need to initialize |drag_dest_| for drags to be properly handled.
// Otherwise, |drag_dest_| will be updated in RenderViewSwappedIn. The
// reason we can't simply check that this isn't a swapped-out view is
// because there are navigations that create non-swapped-out views that may
// never be displayed, e.g. a navigation that becomes a download.
if (rvh == web_contents_->GetRenderViewHost()) {
UpdateDragDest(rvh);
}
}
return view;
}
RenderWidgetHostView* WebContentsViewGtk::CreateViewForPopupWidget(
RenderWidgetHost* render_widget_host) {
return RenderWidgetHostViewPort::CreateViewForWidget(render_widget_host);
}
void WebContentsViewGtk::SetPageTitle(const base::string16& title) {
// Set the window name to include the page title so it's easier to spot
// when debugging (e.g. via xwininfo -tree).
gfx::NativeView content_view = GetContentNativeView();
if (content_view) {
GdkWindow* content_window = gtk_widget_get_window(content_view);
if (content_window) {
gdk_window_set_title(content_window, UTF16ToUTF8(title).c_str());
}
}
}
void WebContentsViewGtk::SizeContents(const gfx::Size& size) {
// We don't need to manually set the size of of widgets in GTK+, but we do
// need to pass the sizing information on to the RWHV which will pass the
// sizing information on to the renderer.
requested_size_ = size;
RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
if (rwhv)
rwhv->SetSize(size);
}
void WebContentsViewGtk::RenderViewCreated(RenderViewHost* host) {
}
void WebContentsViewGtk::RenderViewSwappedIn(RenderViewHost* host) {
UpdateDragDest(host);
}
void WebContentsViewGtk::SetOverscrollControllerEnabled(bool enabled) {
}
WebContents* WebContentsViewGtk::web_contents() {
return web_contents_;
}
void WebContentsViewGtk::UpdateDragCursor(WebDragOperation operation) {
if (!drag_dest_)
return;
drag_dest_->UpdateDragStatus(operation);
}
void WebContentsViewGtk::GotFocus() {
// This is only used in the views FocusManager stuff but it bleeds through
// all subclasses. http://crbug.com/21875
}
// This is called when the renderer asks us to take focus back (i.e., it has
// iterated past the last focusable element on the page).
void WebContentsViewGtk::TakeFocus(bool reverse) {
if (!web_contents_->GetDelegate())
return;
if (!web_contents_->GetDelegate()->TakeFocus(web_contents_, reverse) &&
GetTopLevelNativeWindow()) {
gtk_widget_child_focus(GTK_WIDGET(GetTopLevelNativeWindow()),
reverse ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD);
}
}
void WebContentsViewGtk::InsertIntoContentArea(GtkWidget* widget) {
gtk_container_add(GTK_CONTAINER(expanded_.get()), widget);
}
void WebContentsViewGtk::UpdateDragDest(RenderViewHost* host) {
// Drag-and-drop is entirely managed by BrowserPluginGuest for guest
// processes in a largely platform independent way. WebDragDestGtk
// will result in spurious messages being sent to the guest process which
// will violate assumptions.
if (host->GetProcess() && host->GetProcess()->IsGuest()) {
DCHECK(!drag_dest_);
return;
}
gfx::NativeView content_view = host->GetView()->GetNativeView();
// If the host is already used by the drag_dest_, there's no point in deleting
// the old one to create an identical copy.
if (drag_dest_.get() && drag_dest_->widget() == content_view)
return;
// Clear the currently connected drag drop signals by deleting the old
// drag_dest_ before creating the new one.
drag_dest_.reset();
// Create the new drag_dest_.
drag_dest_.reset(new WebDragDestGtk(web_contents_, content_view));
if (delegate_)
drag_dest_->set_delegate(delegate_->GetDragDestDelegate());
}
// Called when the content view gtk widget is tabbed to, or after the call to
// gtk_widget_child_focus() in TakeFocus(). We return true
// and grab focus if we don't have it. The call to
// FocusThroughTabTraversal(bool) forwards the "move focus forward" effect to
// webkit.
gboolean WebContentsViewGtk::OnFocus(GtkWidget* widget,
GtkDirectionType focus) {
// Give our view wrapper first chance at this event.
if (delegate_) {
gboolean return_value = FALSE;
if (delegate_->OnNativeViewFocusEvent(widget, focus, &return_value))
return return_value;
}
// If we already have focus, let the next widget have a shot at it. We will
// reach this situation after the call to gtk_widget_child_focus() in
// TakeFocus().
if (gtk_widget_is_focus(widget))
return FALSE;
gtk_widget_grab_focus(widget);
bool reverse = focus == GTK_DIR_TAB_BACKWARD;
web_contents_->FocusThroughTabTraversal(reverse);
return TRUE;
}
void WebContentsViewGtk::ShowContextMenu(const ContextMenuParams& params) {
if (delegate_)
delegate_->ShowContextMenu(params);
else
DLOG(ERROR) << "Cannot show context menus without a delegate.";
}
void WebContentsViewGtk::ShowPopupMenu(const gfx::Rect& bounds,
int item_height,
double item_font_size,
int selected_item,
const std::vector<MenuItem>& items,
bool right_aligned,
bool allow_multiple_selection) {
// External popup menus are only used on Mac and Android.
NOTIMPLEMENTED();
}
// Render view DnD -------------------------------------------------------------
void WebContentsViewGtk::StartDragging(const DropData& drop_data,
WebDragOperationsMask ops,
const gfx::ImageSkia& image,
const gfx::Vector2d& image_offset,
const DragEventSourceInfo& event_info) {
DCHECK(GetContentNativeView());
RenderWidgetHostViewGtk* view_gtk = static_cast<RenderWidgetHostViewGtk*>(
web_contents_->GetRenderWidgetHostView());
if (!view_gtk || !view_gtk->GetLastMouseDown() ||
!drag_source_->StartDragging(drop_data, ops, view_gtk->GetLastMouseDown(),
*image.bitmap(), image_offset)) {
web_contents_->SystemDragEnded();
}
}
// -----------------------------------------------------------------------------
void WebContentsViewGtk::OnChildSizeRequest(GtkWidget* widget,
GtkWidget* child,
GtkRequisition* requisition) {
if (web_contents_->GetDelegate()) {
requisition->height +=
web_contents_->GetDelegate()->GetExtraRenderViewHeight();
}
}
void WebContentsViewGtk::OnSizeAllocate(GtkWidget* widget,
GtkAllocation* allocation) {
int width = allocation->width;
int height = allocation->height;
// |delegate()| can be NULL here during browser teardown.
if (web_contents_->GetDelegate())
height += web_contents_->GetDelegate()->GetExtraRenderViewHeight();
gfx::Size size(width, height);
requested_size_ = size;
// We manually tell our RWHV to resize the renderer content. This avoids
// spurious resizes from GTK+.
RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
if (rwhv)
rwhv->SetSize(size);
if (web_contents_->GetInterstitialPage())
web_contents_->GetInterstitialPage()->SetSize(size);
}
} // namespace content