Home | History | Annotate | Download | only in tab_contents
      1 // Copyright (c) 2011 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 "chrome/browser/tab_contents/tab_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/string_util.h"
     14 #include "base/utf_string_conversions.h"
     15 #include "build/build_config.h"
     16 #include "chrome/browser/download/download_shelf.h"
     17 #include "chrome/browser/renderer_host/render_widget_host_view_gtk.h"
     18 #include "chrome/browser/tab_contents/render_view_context_menu_gtk.h"
     19 #include "chrome/browser/tab_contents/web_drag_dest_gtk.h"
     20 #include "chrome/browser/ui/gtk/browser_window_gtk.h"
     21 #include "chrome/browser/ui/gtk/constrained_window_gtk.h"
     22 #include "chrome/browser/ui/gtk/gtk_expanded_container.h"
     23 #include "chrome/browser/ui/gtk/gtk_floating_container.h"
     24 #include "chrome/browser/ui/gtk/gtk_util.h"
     25 #include "chrome/browser/ui/gtk/sad_tab_gtk.h"
     26 #include "chrome/browser/ui/gtk/tab_contents_drag_source.h"
     27 #include "content/browser/renderer_host/render_process_host.h"
     28 #include "content/browser/renderer_host/render_view_host.h"
     29 #include "content/browser/renderer_host/render_view_host_factory.h"
     30 #include "content/browser/tab_contents/interstitial_page.h"
     31 #include "content/browser/tab_contents/tab_contents.h"
     32 #include "content/browser/tab_contents/tab_contents_delegate.h"
     33 #include "content/common/notification_source.h"
     34 #include "content/common/notification_type.h"
     35 #include "ui/gfx/point.h"
     36 #include "ui/gfx/rect.h"
     37 #include "ui/gfx/size.h"
     38 #include "webkit/glue/webdropdata.h"
     39 
     40 using WebKit::WebDragOperation;
     41 using WebKit::WebDragOperationsMask;
     42 
     43 namespace {
     44 
     45 // Called when the mouse leaves the widget. We notify our delegate.
     46 gboolean OnLeaveNotify(GtkWidget* widget, GdkEventCrossing* event,
     47                        TabContents* tab_contents) {
     48   if (tab_contents->delegate())
     49     tab_contents->delegate()->ContentsMouseEvent(
     50         tab_contents, gfx::Point(event->x_root, event->y_root), false);
     51   return FALSE;
     52 }
     53 
     54 // Called when the mouse moves within the widget. We notify our delegate.
     55 gboolean OnMouseMove(GtkWidget* widget, GdkEventMotion* event,
     56                      TabContents* tab_contents) {
     57   if (tab_contents->delegate())
     58     tab_contents->delegate()->ContentsMouseEvent(
     59         tab_contents, gfx::Point(event->x_root, event->y_root), true);
     60   return FALSE;
     61 }
     62 
     63 // See tab_contents_view_views.cc for discussion of mouse scroll zooming.
     64 gboolean OnMouseScroll(GtkWidget* widget, GdkEventScroll* event,
     65                        TabContents* tab_contents) {
     66   if ((event->state & gtk_accelerator_get_default_mod_mask()) ==
     67       GDK_CONTROL_MASK) {
     68     if (event->direction == GDK_SCROLL_DOWN) {
     69       tab_contents->delegate()->ContentsZoomChange(false);
     70       return TRUE;
     71     } else if (event->direction == GDK_SCROLL_UP) {
     72       tab_contents->delegate()->ContentsZoomChange(true);
     73       return TRUE;
     74     }
     75   }
     76 
     77   return FALSE;
     78 }
     79 
     80 }  // namespace
     81 
     82 // static
     83 TabContentsView* TabContentsView::Create(TabContents* tab_contents) {
     84   return new TabContentsViewGtk(tab_contents);
     85 }
     86 
     87 TabContentsViewGtk::TabContentsViewGtk(TabContents* tab_contents)
     88     : TabContentsView(tab_contents),
     89       floating_(gtk_floating_container_new()),
     90       expanded_(gtk_expanded_container_new()),
     91       constrained_window_(NULL) {
     92   gtk_widget_set_name(expanded_, "chrome-tab-contents-view");
     93   g_signal_connect(expanded_, "size-allocate",
     94                    G_CALLBACK(OnSizeAllocateThunk), this);
     95   g_signal_connect(expanded_, "child-size-request",
     96                    G_CALLBACK(OnChildSizeRequestThunk), this);
     97   g_signal_connect(floating_.get(), "set-floating-position",
     98                    G_CALLBACK(OnSetFloatingPositionThunk), this);
     99 
    100   gtk_container_add(GTK_CONTAINER(floating_.get()), expanded_);
    101   gtk_widget_show(expanded_);
    102   gtk_widget_show(floating_.get());
    103   registrar_.Add(this, NotificationType::TAB_CONTENTS_CONNECTED,
    104                  Source<TabContents>(tab_contents));
    105   drag_source_.reset(new TabContentsDragSource(this));
    106 }
    107 
    108 TabContentsViewGtk::~TabContentsViewGtk() {
    109   floating_.Destroy();
    110 }
    111 
    112 void TabContentsViewGtk::AttachConstrainedWindow(
    113     ConstrainedWindowGtk* constrained_window) {
    114   DCHECK(constrained_window_ == NULL);
    115 
    116   constrained_window_ = constrained_window;
    117   gtk_floating_container_add_floating(GTK_FLOATING_CONTAINER(floating_.get()),
    118                                       constrained_window->widget());
    119 }
    120 
    121 void TabContentsViewGtk::RemoveConstrainedWindow(
    122     ConstrainedWindowGtk* constrained_window) {
    123   DCHECK(constrained_window == constrained_window_);
    124 
    125   constrained_window_ = NULL;
    126   gtk_container_remove(GTK_CONTAINER(floating_.get()),
    127                        constrained_window->widget());
    128 }
    129 
    130 void TabContentsViewGtk::CreateView(const gfx::Size& initial_size) {
    131   requested_size_ = initial_size;
    132 }
    133 
    134 RenderWidgetHostView* TabContentsViewGtk::CreateViewForWidget(
    135     RenderWidgetHost* render_widget_host) {
    136   if (render_widget_host->view()) {
    137     // During testing, the view will already be set up in most cases to the
    138     // test view, so we don't want to clobber it with a real one. To verify that
    139     // this actually is happening (and somebody isn't accidentally creating the
    140     // view twice), we check for the RVH Factory, which will be set when we're
    141     // making special ones (which go along with the special views).
    142     DCHECK(RenderViewHostFactory::has_factory());
    143     return render_widget_host->view();
    144   }
    145 
    146   RenderWidgetHostViewGtk* view =
    147       new RenderWidgetHostViewGtk(render_widget_host);
    148   view->InitAsChild();
    149   gfx::NativeView content_view = view->native_view();
    150   g_signal_connect(content_view, "focus", G_CALLBACK(OnFocusThunk), this);
    151   g_signal_connect(content_view, "leave-notify-event",
    152                    G_CALLBACK(OnLeaveNotify), tab_contents());
    153   g_signal_connect(content_view, "motion-notify-event",
    154                    G_CALLBACK(OnMouseMove), tab_contents());
    155   g_signal_connect(content_view, "scroll-event",
    156                    G_CALLBACK(OnMouseScroll), tab_contents());
    157   gtk_widget_add_events(content_view, GDK_LEAVE_NOTIFY_MASK |
    158                         GDK_POINTER_MOTION_MASK);
    159   InsertIntoContentArea(content_view);
    160 
    161   // Renderer target DnD.
    162   drag_dest_.reset(new WebDragDestGtk(tab_contents(), content_view));
    163 
    164   return view;
    165 }
    166 
    167 gfx::NativeView TabContentsViewGtk::GetNativeView() const {
    168   return floating_.get();
    169 }
    170 
    171 gfx::NativeView TabContentsViewGtk::GetContentNativeView() const {
    172   RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView();
    173   if (!rwhv)
    174     return NULL;
    175   return rwhv->GetNativeView();
    176 }
    177 
    178 gfx::NativeWindow TabContentsViewGtk::GetTopLevelNativeWindow() const {
    179   GtkWidget* window = gtk_widget_get_ancestor(GetNativeView(), GTK_TYPE_WINDOW);
    180   return window ? GTK_WINDOW(window) : NULL;
    181 }
    182 
    183 void TabContentsViewGtk::GetContainerBounds(gfx::Rect* out) const {
    184   // This is used for positioning the download shelf arrow animation,
    185   // as well as sizing some other widgets in Windows.  In GTK the size is
    186   // managed for us, so it appears to be only used for the download shelf
    187   // animation.
    188   int x = 0;
    189   int y = 0;
    190   if (expanded_->window)
    191     gdk_window_get_origin(expanded_->window, &x, &y);
    192   out->SetRect(x + expanded_->allocation.x, y + expanded_->allocation.y,
    193                requested_size_.width(), requested_size_.height());
    194 }
    195 
    196 void TabContentsViewGtk::SetPageTitle(const std::wstring& title) {
    197   // Set the window name to include the page title so it's easier to spot
    198   // when debugging (e.g. via xwininfo -tree).
    199   gfx::NativeView content_view = GetContentNativeView();
    200   if (content_view && content_view->window)
    201     gdk_window_set_title(content_view->window, WideToUTF8(title).c_str());
    202 }
    203 
    204 void TabContentsViewGtk::OnTabCrashed(base::TerminationStatus status,
    205                                       int error_code) {
    206   if (tab_contents() != NULL && !sad_tab_.get()) {
    207     sad_tab_.reset(new SadTabGtk(
    208         tab_contents(),
    209         status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED ?
    210         SadTabGtk::KILLED : SadTabGtk::CRASHED));
    211     InsertIntoContentArea(sad_tab_->widget());
    212     gtk_widget_show(sad_tab_->widget());
    213   }
    214 }
    215 
    216 void TabContentsViewGtk::SizeContents(const gfx::Size& size) {
    217   // We don't need to manually set the size of of widgets in GTK+, but we do
    218   // need to pass the sizing information on to the RWHV which will pass the
    219   // sizing information on to the renderer.
    220   requested_size_ = size;
    221   RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView();
    222   if (rwhv)
    223     rwhv->SetSize(size);
    224 }
    225 
    226 void TabContentsViewGtk::Focus() {
    227   if (tab_contents()->showing_interstitial_page()) {
    228     tab_contents()->interstitial_page()->Focus();
    229   } else if (!constrained_window_) {
    230     GtkWidget* widget = GetContentNativeView();
    231     if (widget)
    232       gtk_widget_grab_focus(widget);
    233   }
    234 }
    235 
    236 void TabContentsViewGtk::SetInitialFocus() {
    237   if (tab_contents()->FocusLocationBarByDefault())
    238     tab_contents()->SetFocusToLocationBar(false);
    239   else
    240     Focus();
    241 }
    242 
    243 void TabContentsViewGtk::StoreFocus() {
    244   focus_store_.Store(GetNativeView());
    245 }
    246 
    247 void TabContentsViewGtk::RestoreFocus() {
    248   if (focus_store_.widget())
    249     gtk_widget_grab_focus(focus_store_.widget());
    250   else
    251     SetInitialFocus();
    252 }
    253 
    254 void TabContentsViewGtk::GetViewBounds(gfx::Rect* out) const {
    255   if (!floating_->window) {
    256     out->SetRect(0, 0, requested_size_.width(), requested_size_.height());
    257     return;
    258   }
    259   int x = 0, y = 0, w, h;
    260   gdk_window_get_geometry(floating_->window, &x, &y, &w, &h, NULL);
    261   out->SetRect(x, y, w, h);
    262 }
    263 
    264 void TabContentsViewGtk::SetFocusedWidget(GtkWidget* widget) {
    265   focus_store_.SetWidget(widget);
    266 }
    267 
    268 void TabContentsViewGtk::UpdateDragCursor(WebDragOperation operation) {
    269   drag_dest_->UpdateDragStatus(operation);
    270 }
    271 
    272 void TabContentsViewGtk::GotFocus() {
    273   // This is only used in the views FocusManager stuff but it bleeds through
    274   // all subclasses. http://crbug.com/21875
    275 }
    276 
    277 // This is called when we the renderer asks us to take focus back (i.e., it has
    278 // iterated past the last focusable element on the page).
    279 void TabContentsViewGtk::TakeFocus(bool reverse) {
    280   if (!tab_contents()->delegate()->TakeFocus(reverse)) {
    281     gtk_widget_child_focus(GTK_WIDGET(GetTopLevelNativeWindow()),
    282         reverse ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD);
    283   }
    284 }
    285 
    286 void TabContentsViewGtk::Observe(NotificationType type,
    287                                  const NotificationSource& source,
    288                                  const NotificationDetails& details) {
    289   switch (type.value) {
    290     case NotificationType::TAB_CONTENTS_CONNECTED: {
    291       // No need to remove the SadTabGtk's widget from the container since
    292       // the new RenderWidgetHostViewGtk instance already removed all the
    293       // vbox's children.
    294       sad_tab_.reset();
    295       break;
    296     }
    297     default:
    298       NOTREACHED() << "Got a notification we didn't register for.";
    299       break;
    300   }
    301 }
    302 
    303 void TabContentsViewGtk::ShowContextMenu(const ContextMenuParams& params) {
    304   // Find out the RenderWidgetHostView that corresponds to the render widget on
    305   // which this context menu is showed, so that we can retrieve the last mouse
    306   // down event on the render widget and use it as the timestamp of the
    307   // activation event to show the context menu.
    308   RenderWidgetHostView* view = NULL;
    309   if (params.custom_context.render_widget_id !=
    310       webkit_glue::CustomContextMenuContext::kCurrentRenderWidget) {
    311     IPC::Channel::Listener* listener =
    312         tab_contents()->render_view_host()->process()->GetListenerByID(
    313             params.custom_context.render_widget_id);
    314     if (!listener) {
    315       NOTREACHED();
    316       return;
    317     }
    318     view = static_cast<RenderWidgetHost*>(listener)->view();
    319   } else {
    320     view = tab_contents()->GetRenderWidgetHostView();
    321   }
    322   RenderWidgetHostViewGtk* view_gtk =
    323       static_cast<RenderWidgetHostViewGtk*>(view);
    324   if (!view_gtk || !view_gtk->last_mouse_down())
    325     return;
    326 
    327   context_menu_.reset(new RenderViewContextMenuGtk(
    328       tab_contents(), params, view_gtk->last_mouse_down()->time));
    329   context_menu_->Init();
    330 
    331   gfx::Rect bounds;
    332   GetContainerBounds(&bounds);
    333   gfx::Point point = bounds.origin();
    334   point.Offset(params.x, params.y);
    335   context_menu_->Popup(point);
    336 }
    337 
    338 void TabContentsViewGtk::ShowPopupMenu(const gfx::Rect& bounds,
    339                                        int item_height,
    340                                        double item_font_size,
    341                                        int selected_item,
    342                                        const std::vector<WebMenuItem>& items,
    343                                        bool right_aligned) {
    344   // We are not using external popup menus on Linux, they are rendered by
    345   // WebKit.
    346   NOTREACHED();
    347 }
    348 
    349 // Render view DnD -------------------------------------------------------------
    350 
    351 void TabContentsViewGtk::StartDragging(const WebDropData& drop_data,
    352                                        WebDragOperationsMask ops,
    353                                        const SkBitmap& image,
    354                                        const gfx::Point& image_offset) {
    355   DCHECK(GetContentNativeView());
    356 
    357   RenderWidgetHostViewGtk* view_gtk = static_cast<RenderWidgetHostViewGtk*>(
    358       tab_contents()->GetRenderWidgetHostView());
    359   if (!view_gtk || !view_gtk->last_mouse_down())
    360     return;
    361 
    362   drag_source_->StartDragging(drop_data, ops, view_gtk->last_mouse_down(),
    363                               image, image_offset);
    364 }
    365 
    366 // -----------------------------------------------------------------------------
    367 
    368 void TabContentsViewGtk::InsertIntoContentArea(GtkWidget* widget) {
    369   gtk_container_add(GTK_CONTAINER(expanded_), widget);
    370 }
    371 
    372 // Called when the content view gtk widget is tabbed to, or after the call to
    373 // gtk_widget_child_focus() in TakeFocus(). We return true
    374 // and grab focus if we don't have it. The call to
    375 // FocusThroughTabTraversal(bool) forwards the "move focus forward" effect to
    376 // webkit.
    377 gboolean TabContentsViewGtk::OnFocus(GtkWidget* widget,
    378                                      GtkDirectionType focus) {
    379   // If we are showing a constrained window, don't allow the native view to take
    380   // focus.
    381   if (constrained_window_) {
    382     // If we return false, it will revert to the default handler, which will
    383     // take focus. We don't want that. But if we return true, the event will
    384     // stop being propagated, leaving focus wherever it is currently. That is
    385     // also bad. So we return false to let the default handler run, but take
    386     // focus first so as to trick it into thinking the view was already focused
    387     // and allowing the event to propagate.
    388     gtk_widget_grab_focus(widget);
    389     return FALSE;
    390   }
    391 
    392   // If we already have focus, let the next widget have a shot at it. We will
    393   // reach this situation after the call to gtk_widget_child_focus() in
    394   // TakeFocus().
    395   if (gtk_widget_is_focus(widget))
    396     return FALSE;
    397 
    398   gtk_widget_grab_focus(widget);
    399   bool reverse = focus == GTK_DIR_TAB_BACKWARD;
    400   tab_contents()->FocusThroughTabTraversal(reverse);
    401   return TRUE;
    402 }
    403 
    404 void TabContentsViewGtk::OnChildSizeRequest(GtkWidget* widget,
    405                                             GtkWidget* child,
    406                                             GtkRequisition* requisition) {
    407   if (tab_contents()->delegate()) {
    408     requisition->height +=
    409         tab_contents()->delegate()->GetExtraRenderViewHeight();
    410   }
    411 }
    412 
    413 void TabContentsViewGtk::OnSizeAllocate(GtkWidget* widget,
    414                                         GtkAllocation* allocation) {
    415   int width = allocation->width;
    416   int height = allocation->height;
    417   // |delegate()| can be NULL here during browser teardown.
    418   if (tab_contents()->delegate())
    419     height += tab_contents()->delegate()->GetExtraRenderViewHeight();
    420   gfx::Size size(width, height);
    421   requested_size_ = size;
    422 
    423   // We manually tell our RWHV to resize the renderer content.  This avoids
    424   // spurious resizes from GTK+.
    425   RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView();
    426   if (rwhv)
    427     rwhv->SetSize(size);
    428   if (tab_contents()->interstitial_page())
    429     tab_contents()->interstitial_page()->SetSize(size);
    430 }
    431 
    432 void TabContentsViewGtk::OnSetFloatingPosition(
    433     GtkWidget* floating_container, GtkAllocation* allocation) {
    434   if (!constrained_window_)
    435     return;
    436 
    437   // Place each ConstrainedWindow in the center of the view.
    438   GtkWidget* widget = constrained_window_->widget();
    439   DCHECK(widget->parent == floating_.get());
    440 
    441   GtkRequisition requisition;
    442   gtk_widget_size_request(widget, &requisition);
    443 
    444   GValue value = { 0, };
    445   g_value_init(&value, G_TYPE_INT);
    446 
    447   int child_x = std::max((allocation->width - requisition.width) / 2, 0);
    448   g_value_set_int(&value, child_x);
    449   gtk_container_child_set_property(GTK_CONTAINER(floating_container),
    450                                    widget, "x", &value);
    451 
    452   int child_y = std::max((allocation->height - requisition.height) / 2, 0);
    453   g_value_set_int(&value, child_y);
    454   gtk_container_child_set_property(GTK_CONTAINER(floating_container),
    455                                    widget, "y", &value);
    456   g_value_unset(&value);
    457 }
    458