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/ui/views/tab_contents/tab_contents_view_gtk.h"
      6 
      7 #include <gdk/gdk.h>
      8 #include <gtk/gtk.h>
      9 
     10 #include "base/string_util.h"
     11 #include "base/utf_string_conversions.h"
     12 #include "build/build_config.h"
     13 #include "chrome/browser/download/download_shelf.h"
     14 #include "chrome/browser/renderer_host/render_widget_host_view_gtk.h"
     15 #include "chrome/browser/tab_contents/web_drag_dest_gtk.h"
     16 #include "chrome/browser/ui/gtk/constrained_window_gtk.h"
     17 #include "chrome/browser/ui/gtk/tab_contents_drag_source.h"
     18 #include "chrome/browser/ui/views/sad_tab_view.h"
     19 #include "chrome/browser/ui/views/tab_contents/render_view_context_menu_views.h"
     20 #include "content/browser/renderer_host/render_view_host.h"
     21 #include "content/browser/renderer_host/render_view_host_factory.h"
     22 #include "content/browser/tab_contents/interstitial_page.h"
     23 #include "content/browser/tab_contents/tab_contents.h"
     24 #include "content/browser/tab_contents/tab_contents_delegate.h"
     25 #include "ui/gfx/canvas_skia_paint.h"
     26 #include "ui/gfx/point.h"
     27 #include "ui/gfx/rect.h"
     28 #include "ui/gfx/size.h"
     29 #include "views/controls/native/native_view_host.h"
     30 #include "views/focus/view_storage.h"
     31 #include "views/screen.h"
     32 #include "views/widget/root_view.h"
     33 #include "views/widget/widget_gtk.h"
     34 
     35 using WebKit::WebDragOperation;
     36 using WebKit::WebDragOperationsMask;
     37 using WebKit::WebInputEvent;
     38 
     39 
     40 namespace {
     41 
     42 // Called when the content view gtk widget is tabbed to, or after the call to
     43 // gtk_widget_child_focus() in TakeFocus(). We return true
     44 // and grab focus if we don't have it. The call to
     45 // FocusThroughTabTraversal(bool) forwards the "move focus forward" effect to
     46 // webkit.
     47 gboolean OnFocus(GtkWidget* widget, GtkDirectionType focus,
     48                  TabContents* tab_contents) {
     49   // If we already have focus, let the next widget have a shot at it. We will
     50   // reach this situation after the call to gtk_widget_child_focus() in
     51   // TakeFocus().
     52   if (gtk_widget_is_focus(widget))
     53     return FALSE;
     54 
     55   gtk_widget_grab_focus(widget);
     56   bool reverse = focus == GTK_DIR_TAB_BACKWARD;
     57   tab_contents->FocusThroughTabTraversal(reverse);
     58   return TRUE;
     59 }
     60 
     61 // Called when the mouse leaves the widget. We notify our delegate.
     62 // WidgetGtk also defines OnLeaveNotify, so we use the name OnLeaveNotify2
     63 // here.
     64 gboolean OnLeaveNotify2(GtkWidget* widget, GdkEventCrossing* event,
     65                         TabContents* tab_contents) {
     66   if (tab_contents->delegate())
     67     tab_contents->delegate()->ContentsMouseEvent(
     68         tab_contents, views::Screen::GetCursorScreenPoint(), false);
     69   return FALSE;
     70 }
     71 
     72 // Called when the mouse moves within the widget.
     73 gboolean CallMouseMove(GtkWidget* widget, GdkEventMotion* event,
     74                        TabContentsViewGtk* tab_contents_view) {
     75   return tab_contents_view->OnMouseMove(widget, event);
     76 }
     77 
     78 // See tab_contents_view_gtk.cc for discussion of mouse scroll zooming.
     79 gboolean OnMouseScroll(GtkWidget* widget, GdkEventScroll* event,
     80                        TabContents* tab_contents) {
     81   if ((event->state & gtk_accelerator_get_default_mod_mask()) ==
     82       GDK_CONTROL_MASK) {
     83     if (tab_contents->delegate()) {
     84       if (event->direction == GDK_SCROLL_DOWN) {
     85         tab_contents->delegate()->ContentsZoomChange(false);
     86         return TRUE;
     87       } else if (event->direction == GDK_SCROLL_UP) {
     88         tab_contents->delegate()->ContentsZoomChange(true);
     89         return TRUE;
     90       }
     91     }
     92   }
     93 
     94   return FALSE;
     95 }
     96 
     97 }  // namespace
     98 
     99 // static
    100 TabContentsView* TabContentsView::Create(TabContents* tab_contents) {
    101   return new TabContentsViewGtk(tab_contents);
    102 }
    103 
    104 TabContentsViewGtk::TabContentsViewGtk(TabContents* tab_contents)
    105     : TabContentsView(tab_contents),
    106       views::WidgetGtk(TYPE_CHILD),
    107       sad_tab_(NULL),
    108       ignore_next_char_event_(false) {
    109   drag_source_.reset(new TabContentsDragSource(this));
    110   last_focused_view_storage_id_ =
    111       views::ViewStorage::GetInstance()->CreateStorageID();
    112 }
    113 
    114 TabContentsViewGtk::~TabContentsViewGtk() {
    115   // Make sure to remove any stored view we may still have in the ViewStorage.
    116   //
    117   // It is possible the view went away before us, so we only do this if the
    118   // view is registered.
    119   views::ViewStorage* view_storage = views::ViewStorage::GetInstance();
    120   if (view_storage->RetrieveView(last_focused_view_storage_id_) != NULL)
    121     view_storage->RemoveView(last_focused_view_storage_id_);
    122 
    123   // Just deleting the object doesn't destroy the GtkWidget. We need to do that
    124   // manually, and synchronously, since subsequent signal handlers may expect
    125   // to locate this object.
    126   CloseNow();
    127 }
    128 
    129 void TabContentsViewGtk::AttachConstrainedWindow(
    130     ConstrainedWindowGtk* constrained_window) {
    131   DCHECK(find(constrained_windows_.begin(), constrained_windows_.end(),
    132               constrained_window) == constrained_windows_.end());
    133 
    134   constrained_windows_.push_back(constrained_window);
    135   AddChild(constrained_window->widget());
    136 
    137   gfx::Rect bounds;
    138   GetContainerBounds(&bounds);
    139   SetFloatingPosition(bounds.size());
    140 }
    141 
    142 void TabContentsViewGtk::RemoveConstrainedWindow(
    143     ConstrainedWindowGtk* constrained_window) {
    144   std::vector<ConstrainedWindowGtk*>::iterator item =
    145       find(constrained_windows_.begin(), constrained_windows_.end(),
    146            constrained_window);
    147   DCHECK(item != constrained_windows_.end());
    148   RemoveChild((*item)->widget());
    149   constrained_windows_.erase(item);
    150 }
    151 
    152 void TabContentsViewGtk::CreateView(const gfx::Size& initial_size) {
    153   set_delete_on_destroy(false);
    154   WidgetGtk::Init(NULL, gfx::Rect(0, 0, initial_size.width(),
    155                                   initial_size.height()));
    156   // We need to own the widget in order to attach/detach the native view
    157   // to container.
    158   gtk_object_ref(GTK_OBJECT(GetNativeView()));
    159 }
    160 
    161 RenderWidgetHostView* TabContentsViewGtk::CreateViewForWidget(
    162     RenderWidgetHost* render_widget_host) {
    163   if (render_widget_host->view()) {
    164     // During testing, the view will already be set up in most cases to the
    165     // test view, so we don't want to clobber it with a real one. To verify that
    166     // this actually is happening (and somebody isn't accidentally creating the
    167     // view twice), we check for the RVH Factory, which will be set when we're
    168     // making special ones (which go along with the special views).
    169     DCHECK(RenderViewHostFactory::has_factory());
    170     return render_widget_host->view();
    171   }
    172 
    173   // If we were showing sad tab, remove it now.
    174   if (sad_tab_ != NULL) {
    175     SetContentsView(new views::View());
    176     sad_tab_ = NULL;
    177   }
    178 
    179   RenderWidgetHostViewGtk* view =
    180       new RenderWidgetHostViewGtk(render_widget_host);
    181   view->InitAsChild();
    182   g_signal_connect(view->native_view(), "focus",
    183                    G_CALLBACK(OnFocus), tab_contents());
    184   g_signal_connect(view->native_view(), "leave-notify-event",
    185                    G_CALLBACK(OnLeaveNotify2), tab_contents());
    186   g_signal_connect(view->native_view(), "motion-notify-event",
    187                    G_CALLBACK(CallMouseMove), this);
    188   g_signal_connect(view->native_view(), "scroll-event",
    189                    G_CALLBACK(OnMouseScroll), tab_contents());
    190   gtk_widget_add_events(view->native_view(), GDK_LEAVE_NOTIFY_MASK |
    191                         GDK_POINTER_MOTION_MASK);
    192 
    193   // Let widget know that the tab contents has been painted.
    194   views::WidgetGtk::RegisterChildExposeHandler(view->native_view());
    195 
    196   // Renderer target DnD.
    197   if (tab_contents()->ShouldAcceptDragAndDrop())
    198     drag_dest_.reset(new WebDragDestGtk(tab_contents(), view->native_view()));
    199 
    200   gtk_fixed_put(GTK_FIXED(GetNativeView()), view->native_view(), 0, 0);
    201   return view;
    202 }
    203 
    204 gfx::NativeView TabContentsViewGtk::GetNativeView() const {
    205   return WidgetGtk::GetNativeView();
    206 }
    207 
    208 gfx::NativeView TabContentsViewGtk::GetContentNativeView() const {
    209   RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView();
    210   if (!rwhv)
    211     return NULL;
    212   return rwhv->GetNativeView();
    213 }
    214 
    215 gfx::NativeWindow TabContentsViewGtk::GetTopLevelNativeWindow() const {
    216   GtkWidget* window = gtk_widget_get_ancestor(GetNativeView(), GTK_TYPE_WINDOW);
    217   return window ? GTK_WINDOW(window) : NULL;
    218 }
    219 
    220 void TabContentsViewGtk::GetContainerBounds(gfx::Rect* out) const {
    221   // Callers expect the requested bounds not the actual bounds. For example,
    222   // during init callers expect 0x0, but Gtk layout enforces a min size of 1x1.
    223   *out = GetClientAreaScreenBounds();
    224 
    225   gfx::Size size;
    226   WidgetGtk::GetRequestedSize(&size);
    227   out->set_size(size);
    228 }
    229 
    230 void TabContentsViewGtk::StartDragging(const WebDropData& drop_data,
    231                                        WebDragOperationsMask ops,
    232                                        const SkBitmap& image,
    233                                        const gfx::Point& image_offset) {
    234   drag_source_->StartDragging(drop_data, ops, &last_mouse_down_,
    235                               image, image_offset);
    236 }
    237 
    238 void TabContentsViewGtk::SetPageTitle(const std::wstring& title) {
    239   // Set the window name to include the page title so it's easier to spot
    240   // when debugging (e.g. via xwininfo -tree).
    241   gfx::NativeView content_view = GetContentNativeView();
    242   if (content_view && content_view->window)
    243     gdk_window_set_title(content_view->window, WideToUTF8(title).c_str());
    244 }
    245 
    246 void TabContentsViewGtk::OnTabCrashed(base::TerminationStatus status,
    247                                       int /* error_code */) {
    248   SadTabView::Kind kind =
    249       status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED ?
    250       SadTabView::KILLED : SadTabView::CRASHED;
    251   sad_tab_ = new SadTabView(tab_contents(), kind);
    252   SetContentsView(sad_tab_);
    253 }
    254 
    255 void TabContentsViewGtk::SizeContents(const gfx::Size& size) {
    256   // TODO(brettw) this is a hack and should be removed. See tab_contents_view.h.
    257 
    258   // We're contained in a fixed. To have the fixed relay us out to |size|, set
    259   // the size request, which triggers OnSizeAllocate.
    260   gtk_widget_set_size_request(GetNativeView(), size.width(), size.height());
    261 
    262   // We need to send this immediately.
    263   RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView();
    264   if (rwhv)
    265     rwhv->SetSize(size);
    266 }
    267 
    268 void TabContentsViewGtk::Focus() {
    269   if (tab_contents()->interstitial_page()) {
    270     tab_contents()->interstitial_page()->Focus();
    271     return;
    272   }
    273 
    274   if (tab_contents()->is_crashed() && sad_tab_ != NULL) {
    275     sad_tab_->RequestFocus();
    276     return;
    277   }
    278 
    279   if (constrained_windows_.size()) {
    280     constrained_windows_.back()->FocusConstrainedWindow();
    281     return;
    282   }
    283 
    284   RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView();
    285   gtk_widget_grab_focus(rwhv ? rwhv->GetNativeView() : GetNativeView());
    286 }
    287 
    288 void TabContentsViewGtk::SetInitialFocus() {
    289   if (tab_contents()->FocusLocationBarByDefault())
    290     tab_contents()->SetFocusToLocationBar(false);
    291   else
    292     Focus();
    293 }
    294 
    295 void TabContentsViewGtk::StoreFocus() {
    296   views::ViewStorage* view_storage = views::ViewStorage::GetInstance();
    297 
    298   if (view_storage->RetrieveView(last_focused_view_storage_id_) != NULL)
    299     view_storage->RemoveView(last_focused_view_storage_id_);
    300 
    301   views::FocusManager* focus_manager =
    302       views::FocusManager::GetFocusManagerForNativeView(GetNativeView());
    303   if (focus_manager) {
    304     // |focus_manager| can be NULL if the tab has been detached but still
    305     // exists.
    306     views::View* focused_view = focus_manager->GetFocusedView();
    307     if (focused_view)
    308       view_storage->StoreView(last_focused_view_storage_id_, focused_view);
    309   }
    310 }
    311 
    312 void TabContentsViewGtk::RestoreFocus() {
    313   views::ViewStorage* view_storage = views::ViewStorage::GetInstance();
    314   views::View* last_focused_view =
    315       view_storage->RetrieveView(last_focused_view_storage_id_);
    316   if (!last_focused_view) {
    317     SetInitialFocus();
    318   } else {
    319     views::FocusManager* focus_manager =
    320         views::FocusManager::GetFocusManagerForNativeView(GetNativeView());
    321 
    322     // If you hit this DCHECK, please report it to Jay (jcampan).
    323     DCHECK(focus_manager != NULL) << "No focus manager when restoring focus.";
    324 
    325     if (last_focused_view->IsFocusableInRootView() && focus_manager &&
    326         focus_manager->ContainsView(last_focused_view)) {
    327       last_focused_view->RequestFocus();
    328     } else {
    329       // The focused view may not belong to the same window hierarchy (e.g.
    330       // if the location bar was focused and the tab is dragged out), or it may
    331       // no longer be focusable (e.g. if the location bar was focused and then
    332       // we switched to fullscreen mode).  In that case we default to the
    333       // default focus.
    334       SetInitialFocus();
    335     }
    336     view_storage->RemoveView(last_focused_view_storage_id_);
    337   }
    338 }
    339 
    340 void TabContentsViewGtk::GetViewBounds(gfx::Rect* out) const {
    341   *out = GetWindowScreenBounds();
    342 }
    343 
    344 void TabContentsViewGtk::UpdateDragCursor(WebDragOperation operation) {
    345   if (drag_dest_.get())
    346     drag_dest_->UpdateDragStatus(operation);
    347 }
    348 
    349 void TabContentsViewGtk::GotFocus() {
    350   if (tab_contents()->delegate())
    351     tab_contents()->delegate()->TabContentsFocused(tab_contents());
    352 }
    353 
    354 void TabContentsViewGtk::TakeFocus(bool reverse) {
    355   if (tab_contents()->delegate() &&
    356       !tab_contents()->delegate()->TakeFocus(reverse)) {
    357 
    358     views::FocusManager* focus_manager =
    359         views::FocusManager::GetFocusManagerForNativeView(GetNativeView());
    360 
    361     // We may not have a focus manager if the tab has been switched before this
    362     // message arrived.
    363     if (focus_manager)
    364       focus_manager->AdvanceFocus(reverse);
    365   }
    366 }
    367 
    368 void TabContentsViewGtk::ShowContextMenu(const ContextMenuParams& params) {
    369   // Allow delegates to handle the context menu operation first.
    370   if (tab_contents()->delegate()->HandleContextMenu(params))
    371     return;
    372 
    373   context_menu_.reset(new RenderViewContextMenuViews(tab_contents(), params));
    374   context_menu_->Init();
    375 
    376   gfx::Point screen_point(params.x, params.y);
    377   views::View::ConvertPointToScreen(GetRootView(), &screen_point);
    378 
    379   // Enable recursive tasks on the message loop so we can get updates while
    380   // the context menu is being displayed.
    381   bool old_state = MessageLoop::current()->NestableTasksAllowed();
    382   MessageLoop::current()->SetNestableTasksAllowed(true);
    383   context_menu_->RunMenuAt(screen_point.x(), screen_point.y());
    384   MessageLoop::current()->SetNestableTasksAllowed(old_state);
    385 }
    386 
    387 void TabContentsViewGtk::ShowPopupMenu(const gfx::Rect& bounds,
    388                                        int item_height,
    389                                        double item_font_size,
    390                                        int selected_item,
    391                                        const std::vector<WebMenuItem>& items,
    392                                        bool right_aligned) {
    393   // External popup menus are only used on Mac.
    394   NOTREACHED();
    395 }
    396 
    397 gboolean TabContentsViewGtk::OnButtonPress(GtkWidget* widget,
    398                                            GdkEventButton* event) {
    399   last_mouse_down_ = *event;
    400   return views::WidgetGtk::OnButtonPress(widget, event);
    401 }
    402 
    403 void TabContentsViewGtk::OnSizeAllocate(GtkWidget* widget,
    404                                         GtkAllocation* allocation) {
    405   gfx::Size new_size(allocation->width, allocation->height);
    406 
    407   // Always call WasSized() to allow checking to make sure the
    408   // RenderWidgetHostView is the right size.
    409   WasSized(new_size);
    410 }
    411 
    412 gboolean TabContentsViewGtk::OnPaint(GtkWidget* widget, GdkEventExpose* event) {
    413   if (tab_contents()->render_view_host() &&
    414       !tab_contents()->render_view_host()->IsRenderViewLive() &&
    415       sad_tab_) {
    416     gfx::CanvasSkiaPaint canvas(event);
    417     sad_tab_->Paint(&canvas);
    418   }
    419   return false;  // False indicates other widgets should get the event as well.
    420 }
    421 
    422 void TabContentsViewGtk::OnShow(GtkWidget* widget) {
    423   WasShown();
    424 }
    425 
    426 void TabContentsViewGtk::OnHide(GtkWidget* widget) {
    427   WasHidden();
    428 }
    429 
    430 void TabContentsViewGtk::WasHidden() {
    431   tab_contents()->HideContents();
    432 }
    433 
    434 void TabContentsViewGtk::WasShown() {
    435   tab_contents()->ShowContents();
    436 }
    437 
    438 void TabContentsViewGtk::WasSized(const gfx::Size& size) {
    439   // We have to check that the RenderWidgetHostView is the proper size.
    440   // It can be wrong in cases where the renderer has died and the host
    441   // view needed to be recreated.
    442   bool needs_resize = size != size_;
    443 
    444   if (needs_resize) {
    445     size_ = size;
    446     if (tab_contents()->interstitial_page())
    447       tab_contents()->interstitial_page()->SetSize(size);
    448   }
    449 
    450   RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView();
    451   if (rwhv && rwhv->GetViewBounds().size() != size)
    452     rwhv->SetSize(size);
    453   if (sad_tab_ && sad_tab_->size() != size)
    454     sad_tab_->SetSize(size);
    455 
    456   if (needs_resize)
    457     SetFloatingPosition(size);
    458 }
    459 
    460 void TabContentsViewGtk::SetFloatingPosition(const gfx::Size& size) {
    461   // Place each ConstrainedWindow in the center of the view.
    462   int half_view_width = size.width() / 2;
    463 
    464   typedef std::vector<ConstrainedWindowGtk*>::iterator iterator;
    465 
    466   for (iterator f = constrained_windows_.begin(),
    467                 l = constrained_windows_.end(); f != l; ++f) {
    468     GtkWidget* widget = (*f)->widget();
    469 
    470     GtkRequisition requisition;
    471     gtk_widget_size_request(widget, &requisition);
    472 
    473     int child_x = std::max(half_view_width - (requisition.width / 2), 0);
    474     PositionChild(widget, child_x, 0, 0, 0);
    475   }
    476 }
    477 
    478 // Called when the mouse moves within the widget. We notify SadTabView if it's
    479 // not NULL, else our delegate.
    480 gboolean TabContentsViewGtk::OnMouseMove(GtkWidget* widget,
    481                                          GdkEventMotion* event) {
    482   if (sad_tab_ != NULL)
    483     WidgetGtk::OnMotionNotify(widget, event);
    484   else if (tab_contents()->delegate())
    485     tab_contents()->delegate()->ContentsMouseEvent(
    486         tab_contents(), views::Screen::GetCursorScreenPoint(), true);
    487   return FALSE;
    488 }
    489