• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
OnLeaveNotify(GtkWidget * widget,GdkEventCrossing * event,TabContents * tab_contents)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.
OnMouseMove(GtkWidget * widget,GdkEventMotion * event,TabContents * tab_contents)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.
OnMouseScroll(GtkWidget * widget,GdkEventScroll * event,TabContents * tab_contents)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
Create(TabContents * tab_contents)83 TabContentsView* TabContentsView::Create(TabContents* tab_contents) {
84   return new TabContentsViewGtk(tab_contents);
85 }
86 
TabContentsViewGtk(TabContents * tab_contents)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 
~TabContentsViewGtk()108 TabContentsViewGtk::~TabContentsViewGtk() {
109   floating_.Destroy();
110 }
111 
AttachConstrainedWindow(ConstrainedWindowGtk * constrained_window)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 
RemoveConstrainedWindow(ConstrainedWindowGtk * constrained_window)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 
CreateView(const gfx::Size & initial_size)130 void TabContentsViewGtk::CreateView(const gfx::Size& initial_size) {
131   requested_size_ = initial_size;
132 }
133 
CreateViewForWidget(RenderWidgetHost * render_widget_host)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 
GetNativeView() const167 gfx::NativeView TabContentsViewGtk::GetNativeView() const {
168   return floating_.get();
169 }
170 
GetContentNativeView() const171 gfx::NativeView TabContentsViewGtk::GetContentNativeView() const {
172   RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView();
173   if (!rwhv)
174     return NULL;
175   return rwhv->GetNativeView();
176 }
177 
GetTopLevelNativeWindow() const178 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 
GetContainerBounds(gfx::Rect * out) const183 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 
SetPageTitle(const std::wstring & title)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 
OnTabCrashed(base::TerminationStatus status,int error_code)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 
SizeContents(const gfx::Size & size)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 
Focus()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 
SetInitialFocus()236 void TabContentsViewGtk::SetInitialFocus() {
237   if (tab_contents()->FocusLocationBarByDefault())
238     tab_contents()->SetFocusToLocationBar(false);
239   else
240     Focus();
241 }
242 
StoreFocus()243 void TabContentsViewGtk::StoreFocus() {
244   focus_store_.Store(GetNativeView());
245 }
246 
RestoreFocus()247 void TabContentsViewGtk::RestoreFocus() {
248   if (focus_store_.widget())
249     gtk_widget_grab_focus(focus_store_.widget());
250   else
251     SetInitialFocus();
252 }
253 
GetViewBounds(gfx::Rect * out) const254 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 
SetFocusedWidget(GtkWidget * widget)264 void TabContentsViewGtk::SetFocusedWidget(GtkWidget* widget) {
265   focus_store_.SetWidget(widget);
266 }
267 
UpdateDragCursor(WebDragOperation operation)268 void TabContentsViewGtk::UpdateDragCursor(WebDragOperation operation) {
269   drag_dest_->UpdateDragStatus(operation);
270 }
271 
GotFocus()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).
TakeFocus(bool reverse)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 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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 
ShowContextMenu(const ContextMenuParams & params)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 
ShowPopupMenu(const gfx::Rect & bounds,int item_height,double item_font_size,int selected_item,const std::vector<WebMenuItem> & items,bool right_aligned)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 
StartDragging(const WebDropData & drop_data,WebDragOperationsMask ops,const SkBitmap & image,const gfx::Point & image_offset)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 
InsertIntoContentArea(GtkWidget * widget)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.
OnFocus(GtkWidget * widget,GtkDirectionType focus)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 
OnChildSizeRequest(GtkWidget * widget,GtkWidget * child,GtkRequisition * requisition)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 
OnSizeAllocate(GtkWidget * widget,GtkAllocation * allocation)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 
OnSetFloatingPosition(GtkWidget * floating_container,GtkAllocation * allocation)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