render_widget_host_view_gtk.cc revision 3f50c38dc070f4bb515c1b64450dae14f316474e
1// Copyright (c) 2010 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/renderer_host/render_widget_host_view_gtk.h"
6
7// If this gets included after the gtk headers, then a bunch of compiler
8// errors happen because of a "#define Status int" in Xlib.h, which interacts
9// badly with URLRequestStatus::Status.
10#include "chrome/common/render_messages.h"
11
12#include <cairo/cairo.h>
13#include <gdk/gdk.h>
14#include <gdk/gdkkeysyms.h>
15#include <gdk/gdkx.h>
16#include <gtk/gtk.h>
17
18#include <algorithm>
19#include <string>
20
21#include "app/l10n_util.h"
22#include "app/x11_util.h"
23#include "base/command_line.h"
24#include "base/logging.h"
25#include "base/message_loop.h"
26#include "base/metrics/histogram.h"
27#include "base/string_number_conversions.h"
28#include "base/time.h"
29#include "base/utf_string_conversions.h"
30#include "chrome/browser/gtk/gtk_util.h"
31#include "chrome/browser/renderer_host/backing_store_x.h"
32#include "chrome/browser/renderer_host/gtk_im_context_wrapper.h"
33#include "chrome/browser/renderer_host/gtk_key_bindings_handler.h"
34#include "chrome/browser/renderer_host/render_view_host.h"
35#include "chrome/browser/renderer_host/render_view_host_delegate.h"
36#include "chrome/browser/renderer_host/render_widget_host.h"
37#include "chrome/common/chrome_switches.h"
38#include "chrome/common/native_web_keyboard_event.h"
39#include "gfx/gtk_preserve_window.h"
40#include "third_party/WebKit/WebKit/chromium/public/gtk/WebInputEventFactory.h"
41#include "webkit/glue/webaccessibility.h"
42#include "webkit/glue/webcursor_gtk_data.h"
43#include "webkit/plugins/npapi/webplugin.h"
44
45#if defined(OS_CHROMEOS)
46#include "views/widget/tooltip_window_gtk.h"
47#endif  // defined(OS_CHROMEOS)
48
49namespace {
50
51const int kMaxWindowWidth = 4000;
52const int kMaxWindowHeight = 4000;
53const char* kRenderWidgetHostViewKey = "__RENDER_WIDGET_HOST_VIEW__";
54
55// The duration of the fade-out animation. See |overlay_animation_|.
56const int kFadeEffectDuration = 300;
57
58#if defined(OS_CHROMEOS)
59// TODO(davemoore) Under Chromeos we are increasing the rate that the trackpad
60// generates events to get better precisions. Eventually we will coordinate the
61// driver and this setting to ensure they match.
62const float kDefaultScrollPixelsPerTick = 20;
63#else
64// See WebInputEventFactor.cpp for a reason for this being the default
65// scroll size for linux.
66const float kDefaultScrollPixelsPerTick = 160.0f / 3.0f;
67#endif
68
69}  // namespace
70
71using WebKit::WebInputEventFactory;
72using WebKit::WebMouseWheelEvent;
73
74// This class is a simple convenience wrapper for Gtk functions. It has only
75// static methods.
76class RenderWidgetHostViewGtkWidget {
77 public:
78  static GtkWidget* CreateNewWidget(RenderWidgetHostViewGtk* host_view) {
79    GtkWidget* widget = gtk_preserve_window_new();
80    gtk_widget_set_name(widget, "chrome-render-widget-host-view");
81    // We manually double-buffer in Paint() because Paint() may or may not be
82    // called in repsonse to an "expose-event" signal.
83    gtk_widget_set_double_buffered(widget, FALSE);
84    gtk_widget_set_redraw_on_allocate(widget, FALSE);
85#if defined(NDEBUG)
86    gtk_widget_modify_bg(widget, GTK_STATE_NORMAL, &gtk_util::kGdkWhite);
87#else
88    gtk_widget_modify_bg(widget, GTK_STATE_NORMAL, &gtk_util::kGdkGreen);
89#endif
90    // Allow the browser window to be resized freely.
91    gtk_widget_set_size_request(widget, 0, 0);
92
93    gtk_widget_add_events(widget, GDK_EXPOSURE_MASK |
94                                  GDK_POINTER_MOTION_MASK |
95                                  GDK_BUTTON_PRESS_MASK |
96                                  GDK_BUTTON_RELEASE_MASK |
97                                  GDK_KEY_PRESS_MASK |
98                                  GDK_KEY_RELEASE_MASK |
99                                  GDK_FOCUS_CHANGE_MASK |
100                                  GDK_ENTER_NOTIFY_MASK |
101                                  GDK_LEAVE_NOTIFY_MASK);
102    GTK_WIDGET_SET_FLAGS(widget, GTK_CAN_FOCUS);
103
104    g_signal_connect(widget, "expose-event",
105                     G_CALLBACK(ExposeEvent), host_view);
106    g_signal_connect(widget, "key-press-event",
107                     G_CALLBACK(KeyPressReleaseEvent), host_view);
108    g_signal_connect(widget, "key-release-event",
109                     G_CALLBACK(KeyPressReleaseEvent), host_view);
110    g_signal_connect(widget, "focus-in-event",
111                     G_CALLBACK(OnFocusIn), host_view);
112    g_signal_connect(widget, "focus-out-event",
113                     G_CALLBACK(OnFocusOut), host_view);
114    g_signal_connect(widget, "grab-notify",
115                     G_CALLBACK(OnGrabNotify), host_view);
116    g_signal_connect(widget, "button-press-event",
117                     G_CALLBACK(ButtonPressReleaseEvent), host_view);
118    g_signal_connect(widget, "button-release-event",
119                     G_CALLBACK(ButtonPressReleaseEvent), host_view);
120    g_signal_connect(widget, "motion-notify-event",
121                     G_CALLBACK(MouseMoveEvent), host_view);
122    g_signal_connect(widget, "enter-notify-event",
123                     G_CALLBACK(CrossingEvent), host_view);
124    g_signal_connect(widget, "leave-notify-event",
125                     G_CALLBACK(CrossingEvent), host_view);
126    g_signal_connect(widget, "client-event",
127                     G_CALLBACK(ClientEvent), host_view);
128
129
130    // Connect after so that we are called after the handler installed by the
131    // TabContentsView which handles zoom events.
132    g_signal_connect_after(widget, "scroll-event",
133                           G_CALLBACK(MouseScrollEvent), host_view);
134
135    g_object_set_data(G_OBJECT(widget), kRenderWidgetHostViewKey,
136                      static_cast<RenderWidgetHostView*>(host_view));
137
138    return widget;
139  }
140
141 private:
142  static gboolean ExposeEvent(GtkWidget* widget, GdkEventExpose* expose,
143                              RenderWidgetHostViewGtk* host_view) {
144    const gfx::Rect damage_rect(expose->area);
145    host_view->Paint(damage_rect);
146    return FALSE;
147  }
148
149  static gboolean KeyPressReleaseEvent(GtkWidget* widget, GdkEventKey* event,
150                                       RenderWidgetHostViewGtk* host_view) {
151    if (host_view->IsPopup() && host_view->NeedsInputGrab() &&
152        GDK_Escape == event->keyval) {
153      // Force popups to close on Esc just in case the renderer is hung.  This
154      // allows us to release our keyboard grab.
155      host_view->host_->Shutdown();
156    } else {
157      // Send key event to input method.
158      host_view->im_context_->ProcessKeyEvent(event);
159    }
160
161    // We return TRUE because we did handle the event. If it turns out webkit
162    // can't handle the event, we'll deal with it in
163    // RenderView::UnhandledKeyboardEvent().
164    return TRUE;
165  }
166
167  static gboolean OnFocusIn(GtkWidget* widget, GdkEventFocus* focus,
168                            RenderWidgetHostViewGtk* host_view) {
169    int x, y;
170    gtk_widget_get_pointer(widget, &x, &y);
171    // http://crbug.com/13389
172    // If the cursor is in the render view, fake a mouse move event so that
173    // webkit updates its state. Otherwise webkit might think the cursor is
174    // somewhere it's not.
175    if (x >= 0 && y >= 0 && x < widget->allocation.width &&
176        y < widget->allocation.height) {
177      WebKit::WebMouseEvent fake_event;
178      fake_event.timeStampSeconds = base::Time::Now().ToDoubleT();
179      fake_event.modifiers = 0;
180      fake_event.windowX = fake_event.x = x;
181      fake_event.windowY = fake_event.y = y;
182      gdk_window_get_origin(widget->window, &x, &y);
183      fake_event.globalX = fake_event.x + x;
184      fake_event.globalY = fake_event.y + y;
185      fake_event.type = WebKit::WebInputEvent::MouseMove;
186      fake_event.button = WebKit::WebMouseEvent::ButtonNone;
187      host_view->GetRenderWidgetHost()->ForwardMouseEvent(fake_event);
188    }
189
190    host_view->ShowCurrentCursor();
191    host_view->GetRenderWidgetHost()->GotFocus();
192
193    // The only way to enable a GtkIMContext object is to call its focus in
194    // handler.
195    host_view->im_context_->OnFocusIn();
196
197    return TRUE;
198  }
199
200  static gboolean OnFocusOut(GtkWidget* widget, GdkEventFocus* focus,
201                             RenderWidgetHostViewGtk* host_view) {
202    // Whenever we lose focus, set the cursor back to that of our parent window,
203    // which should be the default arrow.
204    gdk_window_set_cursor(widget->window, NULL);
205    // If we are showing a context menu, maintain the illusion that webkit has
206    // focus.
207    if (!host_view->is_showing_context_menu_)
208      host_view->GetRenderWidgetHost()->Blur();
209
210    // Prevents us from stealing input context focus in OnGrabNotify() handler.
211    host_view->was_focused_before_grab_ = false;
212
213    // Disable the GtkIMContext object.
214    host_view->im_context_->OnFocusOut();
215
216    return TRUE;
217  }
218
219  // Called when we are shadowed or unshadowed by a keyboard grab (which will
220  // occur for activatable popups, such as dropdown menus). Popup windows do not
221  // take focus, so we never get a focus out or focus in event when they are
222  // shown, and must rely on this signal instead.
223  static void OnGrabNotify(GtkWidget* widget, gboolean was_grabbed,
224                           RenderWidgetHostViewGtk* host_view) {
225    if (was_grabbed) {
226      if (host_view->was_focused_before_grab_)
227        host_view->im_context_->OnFocusIn();
228    } else {
229      host_view->was_focused_before_grab_ = host_view->HasFocus();
230      if (host_view->was_focused_before_grab_) {
231        gdk_window_set_cursor(widget->window, NULL);
232        host_view->im_context_->OnFocusOut();
233      }
234    }
235  }
236
237  static gboolean ButtonPressReleaseEvent(
238      GtkWidget* widget, GdkEventButton* event,
239      RenderWidgetHostViewGtk* host_view) {
240#if defined (OS_CHROMEOS)
241    // We support buttons 8 & 9 for scrolling with an attached USB mouse
242    // in ChromeOS. We do this separately from the builtin scrolling support
243    // because we want to support the user's expectations about the amount
244    // scrolled on each event. xorg.conf on chromeos specifies buttons
245    // 8 & 9 for the scroll wheel for the attached USB mouse.
246    if (event->type == GDK_BUTTON_RELEASE &&
247        (event->button == 8 || event->button == 9)) {
248      GdkEventScroll scroll_event;
249      scroll_event.type = GDK_SCROLL;
250      scroll_event.window = event->window;
251      scroll_event.send_event = event->send_event;
252      scroll_event.time = event->time;
253      scroll_event.x = event->x;
254      scroll_event.y = event->y;
255      scroll_event.state = event->state;
256      if (event->state & GDK_SHIFT_MASK) {
257        scroll_event.direction =
258            event->button == 8 ? GDK_SCROLL_LEFT : GDK_SCROLL_RIGHT;
259      } else {
260        scroll_event.direction =
261            event->button == 8 ? GDK_SCROLL_UP : GDK_SCROLL_DOWN;
262      }
263      scroll_event.device = event->device;
264      scroll_event.x_root = event->x_root;
265      scroll_event.y_root = event->y_root;
266      WebMouseWheelEvent web_event =
267          WebInputEventFactory::mouseWheelEvent(&scroll_event);
268      host_view->GetRenderWidgetHost()->ForwardWheelEvent(web_event);
269    }
270#endif
271    if (!(event->button == 1 || event->button == 2 || event->button == 3))
272      return FALSE;  // We do not forward any other buttons to the renderer.
273    if (event->type == GDK_2BUTTON_PRESS || event->type == GDK_3BUTTON_PRESS)
274      return FALSE;
275
276    // If we don't have focus already, this mouse click will focus us.
277    if (!gtk_widget_is_focus(widget))
278      host_view->host_->OnMouseActivate();
279
280    // Confirm existing composition text on mouse click events, to make sure
281    // the input caret won't be moved with an ongoing composition session.
282    host_view->im_context_->ConfirmComposition();
283
284    // We want to translate the coordinates of events that do not originate
285    // from this widget to be relative to the top left of the widget.
286    GtkWidget* event_widget = gtk_get_event_widget(
287        reinterpret_cast<GdkEvent*>(event));
288    if (event_widget != widget) {
289      int x = 0;
290      int y = 0;
291      gtk_widget_get_pointer(widget, &x, &y);
292      // If the mouse event happens outside our popup, force the popup to
293      // close.  We do this so a hung renderer doesn't prevent us from
294      // releasing the x pointer grab.
295      bool click_in_popup = x >= 0 && y >= 0 && x < widget->allocation.width &&
296          y < widget->allocation.height;
297      // Only Shutdown on mouse downs. Mouse ups can occur outside the render
298      // view if the user drags for DnD or while using the scrollbar on a select
299      // dropdown. Don't shutdown if we are not a popup.
300      if (event->type != GDK_BUTTON_RELEASE && host_view->IsPopup() &&
301          !host_view->is_popup_first_mouse_release_ && !click_in_popup) {
302        host_view->host_->Shutdown();
303        return FALSE;
304      }
305      event->x = x;
306      event->y = y;
307    }
308
309    // TODO(evanm): why is this necessary here but not in test shell?
310    // This logic is the same as GtkButton.
311    if (event->type == GDK_BUTTON_PRESS && !GTK_WIDGET_HAS_FOCUS(widget))
312      gtk_widget_grab_focus(widget);
313
314    host_view->is_popup_first_mouse_release_ = false;
315    host_view->GetRenderWidgetHost()->ForwardMouseEvent(
316        WebInputEventFactory::mouseEvent(event));
317
318    // Although we did handle the mouse event, we need to let other handlers
319    // run (in particular the one installed by TabContentsViewGtk).
320    return FALSE;
321  }
322
323  static gboolean MouseMoveEvent(GtkWidget* widget, GdkEventMotion* event,
324                                 RenderWidgetHostViewGtk* host_view) {
325    // We want to translate the coordinates of events that do not originate
326    // from this widget to be relative to the top left of the widget.
327    GtkWidget* event_widget = gtk_get_event_widget(
328        reinterpret_cast<GdkEvent*>(event));
329    if (event_widget != widget) {
330      int x = 0;
331      int y = 0;
332      gtk_widget_get_pointer(widget, &x, &y);
333      event->x = x;
334      event->y = y;
335    }
336
337    host_view->ModifyEventForEdgeDragging(widget, event);
338    host_view->GetRenderWidgetHost()->ForwardMouseEvent(
339        WebInputEventFactory::mouseEvent(event));
340    return FALSE;
341  }
342
343  static gboolean CrossingEvent(GtkWidget* widget, GdkEventCrossing* event,
344                                RenderWidgetHostViewGtk* host_view) {
345    const int any_button_mask =
346        GDK_BUTTON1_MASK |
347        GDK_BUTTON2_MASK |
348        GDK_BUTTON3_MASK |
349        GDK_BUTTON4_MASK |
350        GDK_BUTTON5_MASK;
351
352    // Only forward crossing events if the mouse button is not down.
353    // (When the mouse button is down, the proper events are already being
354    // sent by ButtonPressReleaseEvent and MouseMoveEvent, above, and if we
355    // additionally send this crossing event with the state indicating the
356    // button is down, it causes problems with drag and drop in WebKit.)
357    if (!(event->state & any_button_mask)) {
358      host_view->GetRenderWidgetHost()->ForwardMouseEvent(
359          WebInputEventFactory::mouseEvent(event));
360    }
361
362    return FALSE;
363  }
364
365  static gboolean ClientEvent(GtkWidget* widget, GdkEventClient* event,
366                              RenderWidgetHostViewGtk* host_view) {
367    VLOG(1) << "client event type: " << event->message_type
368            << " data_format: " << event->data_format
369            << " data: " << event->data.l;
370    return true;
371  }
372
373  // Allow the vertical scroll delta to be overridden from the command line.
374  // This will allow us to test more easily to discover the amount
375  // (either hard coded or computed) that's best.
376  static float GetScrollPixelsPerTick() {
377    static float scroll_pixels = -1;
378    if (scroll_pixels < 0) {
379      // TODO(brettw): Remove the command line switch (crbug.com/63525)
380      scroll_pixels = kDefaultScrollPixelsPerTick;
381      CommandLine* command_line = CommandLine::ForCurrentProcess();
382      std::string scroll_pixels_option =
383          command_line->GetSwitchValueASCII(switches::kScrollPixels);
384      if (!scroll_pixels_option.empty()) {
385        double v;
386        if (base::StringToDouble(scroll_pixels_option, &v))
387          scroll_pixels = static_cast<float>(v);
388      }
389      DCHECK_GT(scroll_pixels, 0);
390    }
391    return scroll_pixels;
392  }
393
394  // Return the net up / down (or left / right) distance represented by events
395  // in the  events will be removed from the queue. We only look at the top of
396  // queue...any other type of event will cause us not to look farther.
397  // If there is a change to the set of modifier keys or scroll axis
398  // in the events we will stop looking as well.
399  static int GetPendingScrollDelta(bool vert, guint current_event_state) {
400    int num_clicks = 0;
401    GdkEvent* event;
402    bool event_coalesced = true;
403    while ((event = gdk_event_get()) && event_coalesced) {
404      event_coalesced = false;
405      if (event->type == GDK_SCROLL) {
406        GdkEventScroll scroll = event->scroll;
407        if (scroll.state & GDK_SHIFT_MASK) {
408          if (scroll.direction == GDK_SCROLL_UP)
409            scroll.direction = GDK_SCROLL_LEFT;
410          else if (scroll.direction == GDK_SCROLL_DOWN)
411            scroll.direction = GDK_SCROLL_RIGHT;
412        }
413        if (vert) {
414          if (scroll.direction == GDK_SCROLL_UP ||
415              scroll.direction == GDK_SCROLL_DOWN) {
416            if (scroll.state == current_event_state) {
417              num_clicks += (scroll.direction == GDK_SCROLL_UP ? 1 : -1);
418              gdk_event_free(event);
419              event_coalesced = true;
420            }
421          }
422        } else {
423          if (scroll.direction == GDK_SCROLL_LEFT ||
424              scroll.direction == GDK_SCROLL_RIGHT) {
425            if (scroll.state == current_event_state) {
426              num_clicks += (scroll.direction == GDK_SCROLL_LEFT ? 1 : -1);
427              gdk_event_free(event);
428              event_coalesced = true;
429            }
430          }
431        }
432      }
433    }
434    // If we have an event left we put it back on the queue.
435    if (event) {
436      gdk_event_put(event);
437      gdk_event_free(event);
438    }
439    return num_clicks * GetScrollPixelsPerTick();
440  }
441
442  static gboolean MouseScrollEvent(GtkWidget* widget, GdkEventScroll* event,
443                                   RenderWidgetHostViewGtk* host_view) {
444    // If the user is holding shift, translate it into a horizontal scroll. We
445    // don't care what other modifiers the user may be holding (zooming is
446    // handled at the TabContentsView level).
447    if (event->state & GDK_SHIFT_MASK) {
448      if (event->direction == GDK_SCROLL_UP)
449        event->direction = GDK_SCROLL_LEFT;
450      else if (event->direction == GDK_SCROLL_DOWN)
451        event->direction = GDK_SCROLL_RIGHT;
452    }
453
454    WebMouseWheelEvent web_event = WebInputEventFactory::mouseWheelEvent(event);
455    // We  peek ahead at the top of the queue to look for additional pending
456    // scroll events.
457    if (event->direction == GDK_SCROLL_UP ||
458        event->direction == GDK_SCROLL_DOWN) {
459      if (event->direction == GDK_SCROLL_UP)
460        web_event.deltaY = GetScrollPixelsPerTick();
461      else
462        web_event.deltaY = -GetScrollPixelsPerTick();
463      web_event.deltaY += GetPendingScrollDelta(true, event->state);
464    } else {
465      if (event->direction == GDK_SCROLL_LEFT)
466        web_event.deltaX = GetScrollPixelsPerTick();
467      else
468        web_event.deltaX = -GetScrollPixelsPerTick();
469      web_event.deltaX += GetPendingScrollDelta(false, event->state);
470    }
471    host_view->GetRenderWidgetHost()->ForwardWheelEvent(web_event);
472    return FALSE;
473  }
474
475  DISALLOW_IMPLICIT_CONSTRUCTORS(RenderWidgetHostViewGtkWidget);
476};
477
478// static
479RenderWidgetHostView* RenderWidgetHostView::CreateViewForWidget(
480    RenderWidgetHost* widget) {
481  return new RenderWidgetHostViewGtk(widget);
482}
483
484RenderWidgetHostViewGtk::RenderWidgetHostViewGtk(RenderWidgetHost* widget_host)
485    : host_(widget_host),
486      about_to_validate_and_paint_(false),
487      is_hidden_(false),
488      is_loading_(false),
489      is_showing_context_menu_(false),
490      overlay_color_(0),
491      overlay_animation_(this),
492      parent_host_view_(NULL),
493      parent_(NULL),
494      is_popup_first_mouse_release_(true),
495      was_focused_before_grab_(false),
496      do_x_grab_(false),
497      dragged_at_horizontal_edge_(0),
498      dragged_at_vertical_edge_(0) {
499  host_->set_view(this);
500}
501
502RenderWidgetHostViewGtk::~RenderWidgetHostViewGtk() {
503  view_.Destroy();
504}
505
506void RenderWidgetHostViewGtk::InitAsChild() {
507  view_.Own(RenderWidgetHostViewGtkWidget::CreateNewWidget(this));
508  // |im_context_| must be created after creating |view_| widget.
509  im_context_.reset(new GtkIMContextWrapper(this));
510  // |key_bindings_handler_| must be created after creating |view_| widget.
511  key_bindings_handler_.reset(new GtkKeyBindingsHandler(view_.get()));
512  plugin_container_manager_.set_host_widget(view_.get());
513
514#if defined(OS_CHROMEOS)
515  tooltip_window_.reset(new views::TooltipWindowGtk(view_.get()));
516#endif  // defined(OS_CHROMEOS)
517
518  overlay_animation_.SetDuration(kFadeEffectDuration);
519  overlay_animation_.SetSlideDuration(kFadeEffectDuration);
520
521  gtk_widget_show(view_.get());
522}
523
524void RenderWidgetHostViewGtk::InitAsPopup(
525    RenderWidgetHostView* parent_host_view, const gfx::Rect& pos) {
526  DoInitAsPopup(parent_host_view, GTK_WINDOW_POPUP, pos, false);
527}
528
529void RenderWidgetHostViewGtk::InitAsFullscreen(
530    RenderWidgetHostView* parent_host_view) {
531  DoInitAsPopup(parent_host_view, GTK_WINDOW_TOPLEVEL, gfx::Rect(), true);
532}
533
534RenderWidgetHost* RenderWidgetHostViewGtk::GetRenderWidgetHost() const {
535  return host_;
536}
537
538void RenderWidgetHostViewGtk::DidBecomeSelected() {
539  if (!is_hidden_)
540    return;
541
542  if (tab_switch_paint_time_.is_null())
543    tab_switch_paint_time_ = base::TimeTicks::Now();
544  is_hidden_ = false;
545  host_->WasRestored();
546}
547
548void RenderWidgetHostViewGtk::WasHidden() {
549  if (is_hidden_)
550    return;
551
552  // If we receive any more paint messages while we are hidden, we want to
553  // ignore them so we don't re-allocate the backing store.  We will paint
554  // everything again when we become selected again.
555  is_hidden_ = true;
556
557  // If we have a renderer, then inform it that we are being hidden so it can
558  // reduce its resource utilization.
559  GetRenderWidgetHost()->WasHidden();
560}
561
562void RenderWidgetHostViewGtk::SetSize(const gfx::Size& size) {
563  // This is called when webkit has sent us a Move message.
564  int width = std::min(size.width(), kMaxWindowWidth);
565  int height = std::min(size.height(), kMaxWindowHeight);
566  if (IsPopup()) {
567    // We're a popup, honor the size request.
568    gtk_widget_set_size_request(view_.get(), width, height);
569  } else {
570#if defined(TOOLKIT_VIEWS)
571    // TOOLKIT_VIEWS' resize logic flow matches windows. so we go ahead and
572    // size the widget.  In GTK+, the size of the widget is determined by its
573    // children.
574    gtk_widget_set_size_request(view_.get(), width, height);
575#endif
576  }
577  // Update the size of the RWH.
578  if (requested_size_.width() != width ||
579      requested_size_.height() != height) {
580    requested_size_ = gfx::Size(width, height);
581    host_->WasResized();
582  }
583}
584
585gfx::NativeView RenderWidgetHostViewGtk::GetNativeView() {
586  return view_.get();
587}
588
589void RenderWidgetHostViewGtk::MovePluginWindows(
590    const std::vector<webkit::npapi::WebPluginGeometry>& moves) {
591  for (size_t i = 0; i < moves.size(); ++i) {
592    plugin_container_manager_.MovePluginContainer(moves[i]);
593  }
594}
595
596void RenderWidgetHostViewGtk::Focus() {
597  gtk_widget_grab_focus(view_.get());
598}
599
600void RenderWidgetHostViewGtk::Blur() {
601  // TODO(estade): We should be clearing native focus as well, but I know of no
602  // way to do that without focusing another widget.
603  host_->Blur();
604}
605
606bool RenderWidgetHostViewGtk::HasFocus() {
607  return gtk_widget_is_focus(view_.get());
608}
609
610void RenderWidgetHostViewGtk::Show() {
611  gtk_widget_show(view_.get());
612}
613
614void RenderWidgetHostViewGtk::Hide() {
615  gtk_widget_hide(view_.get());
616}
617
618bool RenderWidgetHostViewGtk::IsShowing() {
619  // TODO(jcivelli): use gtk_widget_get_visible once we build with GTK 2.18.
620  return (GTK_WIDGET_FLAGS(view_.get()) & GTK_VISIBLE) != 0;
621}
622
623gfx::Rect RenderWidgetHostViewGtk::GetViewBounds() const {
624  GtkAllocation* alloc = &view_.get()->allocation;
625  return gfx::Rect(alloc->x, alloc->y,
626                   requested_size_.width(),
627                   requested_size_.height());
628}
629
630void RenderWidgetHostViewGtk::UpdateCursor(const WebCursor& cursor) {
631  // Optimize the common case, where the cursor hasn't changed.
632  // However, we can switch between different pixmaps, so only on the
633  // non-pixmap branch.
634  if (current_cursor_.GetCursorType() != GDK_CURSOR_IS_PIXMAP &&
635      current_cursor_.GetCursorType() == cursor.GetCursorType()) {
636    return;
637  }
638
639  current_cursor_ = cursor;
640  ShowCurrentCursor();
641}
642
643void RenderWidgetHostViewGtk::SetIsLoading(bool is_loading) {
644  is_loading_ = is_loading;
645  // Only call ShowCurrentCursor() when it will actually change the cursor.
646  if (current_cursor_.GetCursorType() == GDK_LAST_CURSOR)
647    ShowCurrentCursor();
648}
649
650void RenderWidgetHostViewGtk::ImeUpdateTextInputState(
651    WebKit::WebTextInputType type,
652    const gfx::Rect& caret_rect) {
653  im_context_->UpdateInputMethodState(type, caret_rect);
654}
655
656void RenderWidgetHostViewGtk::ImeCancelComposition() {
657  im_context_->CancelComposition();
658}
659
660void RenderWidgetHostViewGtk::DidUpdateBackingStore(
661    const gfx::Rect& scroll_rect, int scroll_dx, int scroll_dy,
662    const std::vector<gfx::Rect>& copy_rects) {
663  if (is_hidden_)
664    return;
665
666  // TODO(darin): Implement the equivalent of Win32's ScrollWindowEX.  Can that
667  // be done using XCopyArea?  Perhaps similar to
668  // BackingStore::ScrollBackingStore?
669  if (about_to_validate_and_paint_)
670    invalid_rect_ = invalid_rect_.Union(scroll_rect);
671  else
672    Paint(scroll_rect);
673
674  for (size_t i = 0; i < copy_rects.size(); ++i) {
675    // Avoid double painting.  NOTE: This is only relevant given the call to
676    // Paint(scroll_rect) above.
677    gfx::Rect rect = copy_rects[i].Subtract(scroll_rect);
678    if (rect.IsEmpty())
679      continue;
680
681    if (about_to_validate_and_paint_)
682      invalid_rect_ = invalid_rect_.Union(rect);
683    else
684      Paint(rect);
685  }
686}
687
688void RenderWidgetHostViewGtk::RenderViewGone(base::TerminationStatus status,
689                                             int error_code) {
690  Destroy();
691  plugin_container_manager_.set_host_widget(NULL);
692}
693
694void RenderWidgetHostViewGtk::Destroy() {
695  if (IsPopup()) {
696    if (do_x_grab_) {
697      // Undo the X grab.
698      GdkDisplay* display = gtk_widget_get_display(parent_);
699      gdk_display_pointer_ungrab(display, GDK_CURRENT_TIME);
700      gdk_display_keyboard_ungrab(display, GDK_CURRENT_TIME);
701    }
702    gtk_widget_destroy(gtk_widget_get_parent(view_.get()));
703  }
704
705  // Remove |view_| from all containers now, so nothing else can hold a
706  // reference to |view_|'s widget except possibly a gtk signal handler if
707  // this code is currently executing within the context of a gtk signal
708  // handler.  Note that |view_| is still alive after this call.  It will be
709  // deallocated in the destructor.
710  // See http://www.crbug.com/11847 for details.
711  gtk_widget_destroy(view_.get());
712
713  // The RenderWidgetHost's destruction led here, so don't call it.
714  host_ = NULL;
715
716  MessageLoop::current()->DeleteSoon(FROM_HERE, this);
717}
718
719void RenderWidgetHostViewGtk::SetTooltipText(const std::wstring& tooltip_text) {
720  // Maximum number of characters we allow in a tooltip.
721  const int kMaxTooltipLength = 8 << 10;
722  // Clamp the tooltip length to kMaxTooltipLength so that we don't
723  // accidentally DOS the user with a mega tooltip (since GTK doesn't do
724  // this itself).
725  // I filed https://bugzilla.gnome.org/show_bug.cgi?id=604641 upstream.
726  const string16 clamped_tooltip =
727      l10n_util::TruncateString(WideToUTF16Hack(tooltip_text),
728                                kMaxTooltipLength);
729
730  if (clamped_tooltip.empty()) {
731    gtk_widget_set_has_tooltip(view_.get(), FALSE);
732  } else {
733    gtk_widget_set_tooltip_text(view_.get(),
734                                UTF16ToUTF8(clamped_tooltip).c_str());
735#if defined(OS_CHROMEOS)
736    tooltip_window_->SetTooltipText(UTF16ToWideHack(clamped_tooltip));
737#endif  // defined(OS_CHROMEOS)
738  }
739}
740
741void RenderWidgetHostViewGtk::SelectionChanged(const std::string& text) {
742  if (!text.empty()) {
743    GtkClipboard* x_clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
744    gtk_clipboard_set_text(x_clipboard, text.c_str(), text.length());
745  }
746}
747
748void RenderWidgetHostViewGtk::ShowingContextMenu(bool showing) {
749  is_showing_context_menu_ = showing;
750}
751
752#if !defined(TOOLKIT_VIEWS)
753void RenderWidgetHostViewGtk::AppendInputMethodsContextMenu(MenuGtk* menu) {
754  im_context_->AppendInputMethodsContextMenu(menu);
755}
756#endif
757
758bool RenderWidgetHostViewGtk::NeedsInputGrab() {
759  return popup_type_ == WebKit::WebPopupTypeSelect;
760}
761
762bool RenderWidgetHostViewGtk::IsPopup() {
763  return popup_type_ != WebKit::WebPopupTypeNone;
764}
765
766BackingStore* RenderWidgetHostViewGtk::AllocBackingStore(
767    const gfx::Size& size) {
768  return new BackingStoreX(host_, size,
769                           x11_util::GetVisualFromGtkWidget(view_.get()),
770                           gtk_widget_get_visual(view_.get())->depth);
771}
772
773void RenderWidgetHostViewGtk::SetBackground(const SkBitmap& background) {
774  RenderWidgetHostView::SetBackground(background);
775  host_->Send(new ViewMsg_SetBackground(host_->routing_id(), background));
776}
777
778void RenderWidgetHostViewGtk::ModifyEventForEdgeDragging(
779    GtkWidget* widget, GdkEventMotion* event) {
780  // If the widget is aligned with an edge of the monitor its on and the user
781  // attempts to drag past that edge we track the number of times it has
782  // occurred, so that we can force the widget to scroll when it otherwise
783  // would be unable to, by modifying the (x,y) position in the drag
784  // event that we forward on to webkit. If we get a move that's no longer a
785  // drag or a drag indicating the user is no longer at that edge we stop
786  // altering the drag events.
787  int new_dragged_at_horizontal_edge = 0;
788  int new_dragged_at_vertical_edge = 0;
789  // Used for checking the edges of the monitor. We cache the values to save
790  // roundtrips to the X server.
791  static gfx::Size drag_monitor_size;
792  if (event->state & GDK_BUTTON1_MASK) {
793    if (drag_monitor_size.IsEmpty()) {
794      // We can safely cache the monitor size for the duration of a drag.
795      GdkScreen* screen = gtk_widget_get_screen(widget);
796      int monitor =
797          gdk_screen_get_monitor_at_point(screen, event->x_root, event->y_root);
798      GdkRectangle geometry;
799      gdk_screen_get_monitor_geometry(screen, monitor, &geometry);
800      drag_monitor_size.SetSize(geometry.width, geometry.height);
801    }
802
803    // Check X and Y independently, as the user could be dragging into a corner.
804    if (event->x == 0 && event->x_root == 0) {
805      new_dragged_at_horizontal_edge = dragged_at_horizontal_edge_ - 1;
806    } else if (widget->allocation.width - 1 == static_cast<gint>(event->x) &&
807        drag_monitor_size.width() - 1 == static_cast<gint>(event->x_root)) {
808      new_dragged_at_horizontal_edge = dragged_at_horizontal_edge_ + 1;
809    }
810
811    if (event->y == 0 && event->y_root == 0) {
812      new_dragged_at_vertical_edge = dragged_at_vertical_edge_ - 1;
813    } else if (widget->allocation.height - 1 == static_cast<gint>(event->y) &&
814        drag_monitor_size.height() - 1 == static_cast<gint>(event->y_root)) {
815      new_dragged_at_vertical_edge = dragged_at_vertical_edge_ + 1;
816    }
817
818    event->x_root += new_dragged_at_horizontal_edge;
819    event->x += new_dragged_at_horizontal_edge;
820    event->y_root += new_dragged_at_vertical_edge;
821    event->y += new_dragged_at_vertical_edge;
822  } else {
823    // Clear whenever we get a non-drag mouse move.
824    drag_monitor_size.SetSize(0, 0);
825  }
826  dragged_at_horizontal_edge_ = new_dragged_at_horizontal_edge;
827  dragged_at_vertical_edge_ = new_dragged_at_vertical_edge;
828}
829
830void RenderWidgetHostViewGtk::Paint(const gfx::Rect& damage_rect) {
831  // If the GPU process is rendering directly into the View,
832  // call the compositor directly.
833  RenderWidgetHost* render_widget_host = GetRenderWidgetHost();
834  if (render_widget_host->is_accelerated_compositing_active()) {
835    host_->ScheduleComposite();
836    return;
837  }
838
839  GdkWindow* window = view_.get()->window;
840  DCHECK(!about_to_validate_and_paint_);
841
842  invalid_rect_ = damage_rect;
843  about_to_validate_and_paint_ = true;
844  BackingStoreX* backing_store = static_cast<BackingStoreX*>(
845      host_->GetBackingStore(true));
846  // Calling GetBackingStore maybe have changed |invalid_rect_|...
847  about_to_validate_and_paint_ = false;
848
849  gfx::Rect paint_rect = gfx::Rect(0, 0, kMaxWindowWidth, kMaxWindowHeight);
850  paint_rect = paint_rect.Intersect(invalid_rect_);
851
852  if (backing_store) {
853    // Only render the widget if it is attached to a window; there's a short
854    // period where this object isn't attached to a window but hasn't been
855    // Destroy()ed yet and it receives paint messages...
856    if (window) {
857      if (SkColorGetA(overlay_color_) == 0) {
858        // In the common case, use XCopyArea. We don't draw more than once, so
859        // we don't need to double buffer.
860        backing_store->XShowRect(gfx::Point(0, 0),
861            paint_rect, x11_util::GetX11WindowFromGtkWidget(view_.get()));
862      } else {
863        // If the grey blend is showing, we make two drawing calls. Use double
864        // buffering to prevent flicker. Use CairoShowRect because XShowRect
865        // shortcuts GDK's double buffering. We won't be able to draw outside
866        // of |damage_rect|, so invalidate the difference between |paint_rect|
867        // and |damage_rect|.
868        if (paint_rect != damage_rect) {
869          GdkRectangle extra_gdk_rect =
870              paint_rect.Subtract(damage_rect).ToGdkRectangle();
871          gdk_window_invalidate_rect(window, &extra_gdk_rect, false);
872        }
873
874        GdkRectangle rect = { damage_rect.x(), damage_rect.y(),
875                              damage_rect.width(), damage_rect.height() };
876        gdk_window_begin_paint_rect(window, &rect);
877
878        backing_store->CairoShowRect(damage_rect, GDK_DRAWABLE(window));
879
880        cairo_t* cr = gdk_cairo_create(window);
881        gdk_cairo_rectangle(cr, &rect);
882        SkColor overlay = SkColorSetA(
883            overlay_color_,
884            SkColorGetA(overlay_color_) *
885                overlay_animation_.GetCurrentValue());
886        float r = SkColorGetR(overlay) / 255.;
887        float g = SkColorGetG(overlay) / 255.;
888        float b = SkColorGetB(overlay) / 255.;
889        float a = SkColorGetA(overlay) / 255.;
890        cairo_set_source_rgba(cr, r, g, b, a);
891        cairo_fill(cr);
892        cairo_destroy(cr);
893
894        gdk_window_end_paint(window);
895      }
896    }
897    if (!whiteout_start_time_.is_null()) {
898      base::TimeDelta whiteout_duration = base::TimeTicks::Now() -
899          whiteout_start_time_;
900      UMA_HISTOGRAM_TIMES("MPArch.RWHH_WhiteoutDuration", whiteout_duration);
901
902      // Reset the start time to 0 so that we start recording again the next
903      // time the backing store is NULL...
904      whiteout_start_time_ = base::TimeTicks();
905    }
906    if (!tab_switch_paint_time_.is_null()) {
907      base::TimeDelta tab_switch_paint_duration = base::TimeTicks::Now() -
908          tab_switch_paint_time_;
909      UMA_HISTOGRAM_TIMES("MPArch.RWH_TabSwitchPaintDuration",
910          tab_switch_paint_duration);
911      // Reset tab_switch_paint_time_ to 0 so future tab selections are
912      // recorded.
913      tab_switch_paint_time_ = base::TimeTicks();
914    }
915  } else {
916    if (window)
917      gdk_window_clear(window);
918    if (whiteout_start_time_.is_null())
919      whiteout_start_time_ = base::TimeTicks::Now();
920  }
921}
922
923void RenderWidgetHostViewGtk::ShowCurrentCursor() {
924  // The widget may not have a window. If that's the case, abort mission. This
925  // is the same issue as that explained above in Paint().
926  if (!view_.get()->window)
927    return;
928
929  // TODO(port): WebKit bug https://bugs.webkit.org/show_bug.cgi?id=16388 is
930  // that calling gdk_window_set_cursor repeatedly is expensive.  We should
931  // avoid it here where possible.
932  GdkCursor* gdk_cursor;
933  bool should_unref = false;
934  if (current_cursor_.GetCursorType() == GDK_LAST_CURSOR) {
935    if (is_loading_) {
936      // Use MOZ_CURSOR_SPINNING if we are showing the default cursor and
937      // the page is loading.
938      static const GdkColor fg = { 0, 0, 0, 0 };
939      static const GdkColor bg = { 65535, 65535, 65535, 65535 };
940      GdkPixmap* source =
941        gdk_bitmap_create_from_data(NULL, moz_spinning_bits, 32, 32);
942      GdkPixmap* mask =
943        gdk_bitmap_create_from_data(NULL, moz_spinning_mask_bits, 32, 32);
944      gdk_cursor = gdk_cursor_new_from_pixmap(source, mask, &fg, &bg, 2, 2);
945      should_unref = true;
946      g_object_unref(source);
947      g_object_unref(mask);
948    } else {
949      gdk_cursor = NULL;
950    }
951  } else {
952    gdk_cursor = current_cursor_.GetNativeCursor();
953  }
954  gdk_window_set_cursor(view_.get()->window, gdk_cursor);
955  // The window now owns the cursor.
956  if (should_unref && gdk_cursor)
957    gdk_cursor_unref(gdk_cursor);
958}
959
960void RenderWidgetHostViewGtk::DoInitAsPopup(
961    RenderWidgetHostView* parent_host_view,
962    GtkWindowType window_type,
963    const gfx::Rect& pos,
964    bool is_fullscreen) {
965  // If we are not a popup, then popup will be leaked.
966  DCHECK(IsPopup());
967
968  parent_host_view_ = parent_host_view;
969  parent_ = parent_host_view->GetNativeView();
970  GtkWidget* popup = gtk_window_new(window_type);
971  gtk_window_set_decorated(GTK_WINDOW(popup), FALSE);
972  view_.Own(RenderWidgetHostViewGtkWidget::CreateNewWidget(this));
973  // |im_context_| must be created after creating |view_| widget.
974  im_context_.reset(new GtkIMContextWrapper(this));
975  // |key_bindings_handler_| must be created after creating |view_| widget.
976  key_bindings_handler_.reset(new GtkKeyBindingsHandler(view_.get()));
977  plugin_container_manager_.set_host_widget(view_.get());
978
979#if defined(OS_CHROMEOS)
980  tooltip_window_.reset(new views::TooltipWindowGtk(view_.get()));
981#endif  // defined(OS_CHROMEOS)
982
983  gtk_container_add(GTK_CONTAINER(popup), view_.get());
984
985  if (is_fullscreen) {
986    // Set the request size to the size of the screen.
987    // TODO(boliu): Make sure this works for multi-monitor set ups and move this
988    // to some utility function.
989    GdkScreen* screen = gtk_window_get_screen(GTK_WINDOW(popup));
990    requested_size_ = gfx::Size(
991        std::min(gdk_screen_get_width(screen), kMaxWindowWidth),
992        std::min(gdk_screen_get_height(screen), kMaxWindowHeight));
993  } else {
994    requested_size_ = gfx::Size(std::min(pos.width(), kMaxWindowWidth),
995                                std::min(pos.height(), kMaxWindowHeight));
996  }
997  host_->WasResized();
998
999  gtk_widget_set_size_request(view_.get(), requested_size_.width(),
1000                              requested_size_.height());
1001  // Don't allow the window to be resized. This also forces the window to
1002  // shrink down to the size of its child contents.
1003  gtk_window_set_resizable(GTK_WINDOW(popup), FALSE);
1004  gtk_window_set_default_size(GTK_WINDOW(popup), -1, -1);
1005  gtk_window_move(GTK_WINDOW(popup), pos.x(), pos.y());
1006  if (is_fullscreen) {
1007    gtk_window_fullscreen(GTK_WINDOW(popup));
1008  }
1009
1010  gtk_widget_show_all(popup);
1011
1012  // If we are not activatable, we don't want to grab keyboard input,
1013  // and webkit will manage our destruction.
1014  // For unknown reason, calling gtk_grab_add() before realizing the widget may
1015  // cause an assertion failure. See http://crbug.com/51834. So we do it after
1016  // showing the popup.
1017  if (NeedsInputGrab()) {
1018    // Grab all input for the app. If a click lands outside the bounds of the
1019    // popup, WebKit will notice and destroy us. Before doing this we need
1020    // to ensure that the the popup is added to the browser's window group,
1021    // to allow for the grabs to work correctly.
1022    gtk_window_group_add_window(gtk_window_get_group(
1023        GTK_WINDOW(gtk_widget_get_toplevel(parent_))), GTK_WINDOW(popup));
1024    gtk_grab_add(view_.get());
1025
1026    // We need for the application to do an X grab as well. However if the app
1027    // already has an X grab (as in the case of extension popup), an app grab
1028    // will suffice.
1029    do_x_grab_ = !gdk_pointer_is_grabbed();
1030
1031    // Now grab all of X's input.
1032    if (do_x_grab_) {
1033      gdk_pointer_grab(
1034          parent_->window,
1035          TRUE,  // Only events outside of the window are reported with respect
1036                 // to |parent_->window|.
1037          static_cast<GdkEventMask>(GDK_BUTTON_PRESS_MASK |
1038              GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK),
1039          NULL,
1040          NULL,
1041          GDK_CURRENT_TIME);
1042      // We grab keyboard events too so things like alt+tab are eaten.
1043      gdk_keyboard_grab(parent_->window, TRUE, GDK_CURRENT_TIME);
1044    }
1045  }
1046}
1047
1048void RenderWidgetHostViewGtk::CreatePluginContainer(
1049    gfx::PluginWindowHandle id) {
1050  plugin_container_manager_.CreatePluginContainer(id);
1051}
1052
1053void RenderWidgetHostViewGtk::DestroyPluginContainer(
1054    gfx::PluginWindowHandle id) {
1055  plugin_container_manager_.DestroyPluginContainer(id);
1056}
1057
1058void RenderWidgetHostViewGtk::SetVisuallyDeemphasized(
1059    const SkColor* color, bool animate) {
1060  // Do nothing unless |color| has changed, meaning |animate| is only
1061  // respected for the first call.
1062  if (color && (*color == overlay_color_))
1063    return;
1064
1065  overlay_color_ = color ? *color : 0;
1066
1067  if (animate) {
1068    overlay_animation_.Reset();
1069    overlay_animation_.Show();
1070  } else {
1071    overlay_animation_.Reset(1.0);
1072    gtk_widget_queue_draw(view_.get());
1073  }
1074}
1075
1076bool RenderWidgetHostViewGtk::ContainsNativeView(
1077    gfx::NativeView native_view) const {
1078  // TODO(port)
1079  NOTREACHED() <<
1080    "RenderWidgetHostViewGtk::ContainsNativeView not implemented.";
1081  return false;
1082}
1083
1084void RenderWidgetHostViewGtk::AcceleratedCompositingActivated(bool activated) {
1085  GtkPreserveWindow* widget =
1086    reinterpret_cast<GtkPreserveWindow*>(view_.get());
1087
1088  gtk_preserve_window_delegate_resize(widget, activated);
1089}
1090
1091void RenderWidgetHostViewGtk::ForwardKeyboardEvent(
1092    const NativeWebKeyboardEvent& event) {
1093  if (!host_)
1094    return;
1095
1096  EditCommands edit_commands;
1097  if (!event.skip_in_browser &&
1098      key_bindings_handler_->Match(event, &edit_commands)) {
1099    host_->ForwardEditCommandsForNextKeyEvent(edit_commands);
1100    NativeWebKeyboardEvent copy_event(event);
1101    copy_event.match_edit_command = true;
1102    host_->ForwardKeyboardEvent(copy_event);
1103    return;
1104  }
1105
1106  host_->ForwardKeyboardEvent(event);
1107}
1108
1109void RenderWidgetHostViewGtk::AnimationEnded(const ui::Animation* animation) {
1110  gtk_widget_queue_draw(view_.get());
1111}
1112
1113void RenderWidgetHostViewGtk::AnimationProgressed(
1114    const ui::Animation* animation) {
1115  gtk_widget_queue_draw(view_.get());
1116}
1117
1118void RenderWidgetHostViewGtk::AnimationCanceled(
1119    const ui::Animation* animation) {
1120  gtk_widget_queue_draw(view_.get());
1121}
1122
1123// static
1124RenderWidgetHostView*
1125    RenderWidgetHostView::GetRenderWidgetHostViewFromNativeView(
1126        gfx::NativeView widget) {
1127  gpointer user_data = g_object_get_data(G_OBJECT(widget),
1128                                         kRenderWidgetHostViewKey);
1129  return reinterpret_cast<RenderWidgetHostView*>(user_data);
1130}
1131