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