root/content/browser/web_contents/web_contents_view_gtk.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. OnLeaveNotify
  2. OnMouseMove
  3. OnMouseScroll
  4. CreateWebContentsView
  5. delegate_
  6. GetNativeView
  7. GetContentNativeView
  8. GetTopLevelNativeWindow
  9. GetContainerBounds
  10. OnTabCrashed
  11. Focus
  12. SetInitialFocus
  13. StoreFocus
  14. RestoreFocus
  15. GetDropData
  16. GetViewBounds
  17. CreateView
  18. CreateViewForWidget
  19. CreateViewForPopupWidget
  20. SetPageTitle
  21. SizeContents
  22. RenderViewCreated
  23. RenderViewSwappedIn
  24. SetOverscrollControllerEnabled
  25. web_contents
  26. UpdateDragCursor
  27. GotFocus
  28. TakeFocus
  29. InsertIntoContentArea
  30. UpdateDragDest
  31. OnFocus
  32. ShowContextMenu
  33. StartDragging
  34. OnChildSizeRequest
  35. OnSizeAllocate

// 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, base::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(RenderFrameHost* render_frame_host,
                                         const ContextMenuParams& params) {
  if (delegate_)
    delegate_->ShowContextMenu(render_frame_host, params);
  else
    DLOG(ERROR) << "Cannot show context menus without a delegate.";
}

// 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

/* [<][>][^][v][top][bottom][index][help] */