1 // Copyright (c) 2012 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 "content/browser/renderer_host/render_widget_host_view_gtk.h" 6 7 #include <cairo/cairo.h> 8 #include <gdk/gdk.h> 9 #include <gdk/gdkkeysyms.h> 10 #include <gdk/gdkx.h> 11 #include <gtk/gtk.h> 12 13 #include <algorithm> 14 #include <string> 15 16 #include "base/bind_helpers.h" 17 #include "base/command_line.h" 18 #include "base/debug/trace_event.h" 19 #include "base/logging.h" 20 #include "base/message_loop/message_loop.h" 21 #include "base/metrics/histogram.h" 22 #include "base/strings/string_number_conversions.h" 23 #include "base/strings/utf_offset_string_conversions.h" 24 #include "base/strings/utf_string_conversions.h" 25 #include "base/time/time.h" 26 #include "content/browser/accessibility/browser_accessibility_gtk.h" 27 #include "content/browser/accessibility/browser_accessibility_manager_gtk.h" 28 #include "content/browser/renderer_host/backing_store_gtk.h" 29 #include "content/browser/renderer_host/gtk_im_context_wrapper.h" 30 #include "content/browser/renderer_host/gtk_key_bindings_handler.h" 31 #include "content/browser/renderer_host/gtk_window_utils.h" 32 #include "content/browser/renderer_host/render_view_host_delegate.h" 33 #include "content/browser/renderer_host/render_view_host_impl.h" 34 #include "content/common/gpu/gpu_messages.h" 35 #include "content/common/input_messages.h" 36 #include "content/common/view_messages.h" 37 #include "content/common/webplugin_geometry.h" 38 #include "content/public/browser/native_web_keyboard_event.h" 39 #include "content/public/common/content_switches.h" 40 #include "skia/ext/platform_canvas.h" 41 #include "third_party/WebKit/public/web/WebInputEvent.h" 42 #include "third_party/WebKit/public/web/WebScreenInfo.h" 43 #include "third_party/WebKit/public/web/gtk/WebInputEventFactory.h" 44 #include "ui/base/clipboard/scoped_clipboard_writer.h" 45 #include "ui/base/gtk/gtk_compat.h" 46 #include "ui/base/text/text_elider.h" 47 #include "ui/base/x/active_window_watcher_x.h" 48 #include "ui/base/x/x11_util.h" 49 #include "ui/gfx/gtk_native_view_id_manager.h" 50 #include "ui/gfx/gtk_preserve_window.h" 51 #include "webkit/common/cursors/webcursor_gtk_data.h" 52 53 using WebKit::WebInputEventFactory; 54 using WebKit::WebMouseWheelEvent; 55 using WebKit::WebScreenInfo; 56 57 namespace content { 58 namespace { 59 60 // Paint rects on Linux are bounded by the maximum size of a shared memory 61 // region. By default that's 32MB, but many distros increase it significantly 62 // (i.e. to 256MB). 63 // 64 // We fetch the maximum value from /proc/sys/kernel/shmmax at runtime and, if 65 // we exceed that, then we limit the height of the paint rect in the renderer. 66 // 67 // These constants are here to ensure that, in the event that we exceed it, we 68 // end up with something a little more square. Previously we had 4000x4000, but 69 // people's monitor setups are actually exceeding that these days. 70 const int kMaxWindowWidth = 10000; 71 const int kMaxWindowHeight = 10000; 72 73 // See WebInputEventFactor.cpp for a reason for this being the default 74 // scroll size for linux. 75 const float kDefaultScrollPixelsPerTick = 160.0f / 3.0f; 76 77 const GdkColor kBGColor = 78 #if defined(NDEBUG) 79 { 0, 0xff * 257, 0xff * 257, 0xff * 257 }; 80 #else 81 { 0, 0x00 * 257, 0xff * 257, 0x00 * 257 }; 82 #endif 83 84 // Returns the spinning cursor used for loading state. 85 GdkCursor* GetMozSpinningCursor() { 86 static GdkCursor* moz_spinning_cursor = NULL; 87 if (!moz_spinning_cursor) { 88 const GdkColor fg = { 0, 0, 0, 0 }; 89 const GdkColor bg = { 65535, 65535, 65535, 65535 }; 90 GdkPixmap* source = gdk_bitmap_create_from_data( 91 NULL, reinterpret_cast<const gchar*>(moz_spinning_bits), 32, 32); 92 GdkPixmap* mask = gdk_bitmap_create_from_data( 93 NULL, reinterpret_cast<const gchar*>(moz_spinning_mask_bits), 32, 32); 94 moz_spinning_cursor = 95 gdk_cursor_new_from_pixmap(source, mask, &fg, &bg, 2, 2); 96 g_object_unref(source); 97 g_object_unref(mask); 98 } 99 return moz_spinning_cursor; 100 } 101 102 bool MovedToPoint(const WebKit::WebMouseEvent& mouse_event, 103 const gfx::Point& center) { 104 return mouse_event.globalX == center.x() && 105 mouse_event.globalY == center.y(); 106 } 107 108 } // namespace 109 110 // This class is a simple convenience wrapper for Gtk functions. It has only 111 // static methods. 112 class RenderWidgetHostViewGtkWidget { 113 public: 114 static AtkObject* GetAccessible(void* userdata) { 115 return (static_cast<RenderWidgetHostViewGtk*>(userdata))-> 116 GetAccessible(); 117 } 118 119 static GtkWidget* CreateNewWidget(RenderWidgetHostViewGtk* host_view) { 120 GtkWidget* widget = gtk_preserve_window_new(); 121 gtk_widget_set_name(widget, "chrome-render-widget-host-view"); 122 // We manually double-buffer in Paint() because Paint() may or may not be 123 // called in repsonse to an "expose-event" signal. 124 gtk_widget_set_double_buffered(widget, FALSE); 125 gtk_widget_set_redraw_on_allocate(widget, FALSE); 126 gtk_widget_modify_bg(widget, GTK_STATE_NORMAL, &kBGColor); 127 // Allow the browser window to be resized freely. 128 gtk_widget_set_size_request(widget, 0, 0); 129 130 gtk_widget_add_events(widget, GDK_EXPOSURE_MASK | 131 GDK_STRUCTURE_MASK | 132 GDK_POINTER_MOTION_MASK | 133 GDK_BUTTON_PRESS_MASK | 134 GDK_BUTTON_RELEASE_MASK | 135 GDK_KEY_PRESS_MASK | 136 GDK_KEY_RELEASE_MASK | 137 GDK_FOCUS_CHANGE_MASK | 138 GDK_ENTER_NOTIFY_MASK | 139 GDK_LEAVE_NOTIFY_MASK); 140 gtk_widget_set_can_focus(widget, TRUE); 141 142 g_signal_connect(widget, "expose-event", 143 G_CALLBACK(OnExposeEvent), host_view); 144 g_signal_connect(widget, "realize", 145 G_CALLBACK(OnRealize), host_view); 146 g_signal_connect(widget, "configure-event", 147 G_CALLBACK(OnConfigureEvent), host_view); 148 g_signal_connect(widget, "key-press-event", 149 G_CALLBACK(OnKeyPressReleaseEvent), host_view); 150 g_signal_connect(widget, "key-release-event", 151 G_CALLBACK(OnKeyPressReleaseEvent), host_view); 152 g_signal_connect(widget, "focus-in-event", 153 G_CALLBACK(OnFocusIn), host_view); 154 g_signal_connect(widget, "focus-out-event", 155 G_CALLBACK(OnFocusOut), host_view); 156 g_signal_connect(widget, "grab-notify", 157 G_CALLBACK(OnGrabNotify), host_view); 158 g_signal_connect(widget, "button-press-event", 159 G_CALLBACK(OnButtonPressReleaseEvent), host_view); 160 g_signal_connect(widget, "button-release-event", 161 G_CALLBACK(OnButtonPressReleaseEvent), host_view); 162 g_signal_connect(widget, "motion-notify-event", 163 G_CALLBACK(OnMouseMoveEvent), host_view); 164 g_signal_connect(widget, "enter-notify-event", 165 G_CALLBACK(OnCrossingEvent), host_view); 166 g_signal_connect(widget, "leave-notify-event", 167 G_CALLBACK(OnCrossingEvent), host_view); 168 g_signal_connect(widget, "client-event", 169 G_CALLBACK(OnClientEvent), host_view); 170 171 172 // Connect after so that we are called after the handler installed by the 173 // WebContentsView which handles zoom events. 174 g_signal_connect_after(widget, "scroll-event", 175 G_CALLBACK(OnMouseScrollEvent), host_view); 176 177 // Route calls to get_accessible to the view. 178 gtk_preserve_window_set_accessible_factory( 179 GTK_PRESERVE_WINDOW(widget), GetAccessible, host_view); 180 181 return widget; 182 } 183 184 private: 185 static gboolean OnExposeEvent(GtkWidget* widget, 186 GdkEventExpose* expose, 187 RenderWidgetHostViewGtk* host_view) { 188 if (host_view->is_hidden_) 189 return FALSE; 190 const gfx::Rect damage_rect(expose->area); 191 host_view->Paint(damage_rect); 192 return FALSE; 193 } 194 195 static gboolean OnRealize(GtkWidget* widget, 196 RenderWidgetHostViewGtk* host_view) { 197 // Use GtkSignalRegistrar to register events on a widget we don't 198 // control the lifetime of, auto disconnecting at our end of our life. 199 host_view->signals_.Connect(gtk_widget_get_toplevel(widget), 200 "configure-event", 201 G_CALLBACK(OnConfigureEvent), host_view); 202 return FALSE; 203 } 204 205 static gboolean OnConfigureEvent(GtkWidget* widget, 206 GdkEventConfigure* event, 207 RenderWidgetHostViewGtk* host_view) { 208 host_view->MarkCachedWidgetCenterStale(); 209 host_view->UpdateScreenInfo(host_view->GetNativeView()); 210 return FALSE; 211 } 212 213 static gboolean OnKeyPressReleaseEvent(GtkWidget* widget, 214 GdkEventKey* event, 215 RenderWidgetHostViewGtk* host_view) { 216 TRACE_EVENT0("browser", 217 "RenderWidgetHostViewGtkWidget::OnKeyPressReleaseEvent"); 218 // Force popups or fullscreen windows to close on Escape so they won't keep 219 // the keyboard grabbed or be stuck onscreen if the renderer is hanging. 220 bool should_close_on_escape = 221 (host_view->IsPopup() && host_view->NeedsInputGrab()) || 222 host_view->is_fullscreen_; 223 if (should_close_on_escape && GDK_Escape == event->keyval) { 224 host_view->host_->Shutdown(); 225 } else { 226 // Send key event to input method. 227 host_view->im_context_->ProcessKeyEvent(event); 228 } 229 230 // We return TRUE because we did handle the event. If it turns out webkit 231 // can't handle the event, we'll deal with it in 232 // RenderView::UnhandledKeyboardEvent(). 233 return TRUE; 234 } 235 236 static gboolean OnFocusIn(GtkWidget* widget, 237 GdkEventFocus* focus, 238 RenderWidgetHostViewGtk* host_view) { 239 host_view->ShowCurrentCursor(); 240 RenderWidgetHostImpl* host = 241 RenderWidgetHostImpl::From(host_view->GetRenderWidgetHost()); 242 host->GotFocus(); 243 host->SetActive(true); 244 245 // The only way to enable a GtkIMContext object is to call its focus in 246 // handler. 247 host_view->im_context_->OnFocusIn(); 248 249 return TRUE; 250 } 251 252 static gboolean OnFocusOut(GtkWidget* widget, 253 GdkEventFocus* focus, 254 RenderWidgetHostViewGtk* host_view) { 255 // Whenever we lose focus, set the cursor back to that of our parent window, 256 // which should be the default arrow. 257 gdk_window_set_cursor(gtk_widget_get_window(widget), NULL); 258 // If we are showing a context menu, maintain the illusion that webkit has 259 // focus. 260 if (!host_view->IsShowingContextMenu()) { 261 RenderWidgetHostImpl* host = 262 RenderWidgetHostImpl::From(host_view->GetRenderWidgetHost()); 263 host->SetActive(false); 264 host->Blur(); 265 } 266 267 // Prevents us from stealing input context focus in OnGrabNotify() handler. 268 host_view->was_imcontext_focused_before_grab_ = false; 269 270 // Disable the GtkIMContext object. 271 host_view->im_context_->OnFocusOut(); 272 273 host_view->set_last_mouse_down(NULL); 274 275 return TRUE; 276 } 277 278 // Called when we are shadowed or unshadowed by a keyboard grab (which will 279 // occur for activatable popups, such as dropdown menus). Popup windows do not 280 // take focus, so we never get a focus out or focus in event when they are 281 // shown, and must rely on this signal instead. 282 static void OnGrabNotify(GtkWidget* widget, gboolean was_grabbed, 283 RenderWidgetHostViewGtk* host_view) { 284 if (was_grabbed) { 285 if (host_view->was_imcontext_focused_before_grab_) 286 host_view->im_context_->OnFocusIn(); 287 } else { 288 host_view->was_imcontext_focused_before_grab_ = 289 host_view->im_context_->is_focused(); 290 if (host_view->was_imcontext_focused_before_grab_) { 291 gdk_window_set_cursor(gtk_widget_get_window(widget), NULL); 292 host_view->im_context_->OnFocusOut(); 293 } 294 } 295 } 296 297 static gboolean OnButtonPressReleaseEvent( 298 GtkWidget* widget, 299 GdkEventButton* event, 300 RenderWidgetHostViewGtk* host_view) { 301 TRACE_EVENT0("browser", 302 "RenderWidgetHostViewGtkWidget::OnButtonPressReleaseEvent"); 303 304 if (event->type != GDK_BUTTON_RELEASE) 305 host_view->set_last_mouse_down(event); 306 307 if (!(event->button == 1 || event->button == 2 || event->button == 3)) 308 return FALSE; // We do not forward any other buttons to the renderer. 309 if (event->type == GDK_2BUTTON_PRESS || event->type == GDK_3BUTTON_PRESS) 310 return FALSE; 311 312 // If we don't have focus already, this mouse click will focus us. 313 if (!gtk_widget_is_focus(widget)) 314 host_view->host_->OnPointerEventActivate(); 315 316 // Confirm existing composition text on mouse click events, to make sure 317 // the input caret won't be moved with an ongoing composition session. 318 if (event->type != GDK_BUTTON_RELEASE) 319 host_view->im_context_->ConfirmComposition(); 320 321 // We want to translate the coordinates of events that do not originate 322 // from this widget to be relative to the top left of the widget. 323 GtkWidget* event_widget = gtk_get_event_widget( 324 reinterpret_cast<GdkEvent*>(event)); 325 if (event_widget != widget) { 326 int x = 0; 327 int y = 0; 328 gtk_widget_get_pointer(widget, &x, &y); 329 // If the mouse event happens outside our popup, force the popup to 330 // close. We do this so a hung renderer doesn't prevent us from 331 // releasing the x pointer grab. 332 GtkAllocation allocation; 333 gtk_widget_get_allocation(widget, &allocation); 334 bool click_in_popup = x >= 0 && y >= 0 && x < allocation.width && 335 y < allocation.height; 336 // Only Shutdown on mouse downs. Mouse ups can occur outside the render 337 // view if the user drags for DnD or while using the scrollbar on a select 338 // dropdown. Don't shutdown if we are not a popup. 339 if (event->type != GDK_BUTTON_RELEASE && host_view->IsPopup() && 340 !host_view->is_popup_first_mouse_release_ && !click_in_popup) { 341 host_view->host_->Shutdown(); 342 return FALSE; 343 } 344 event->x = x; 345 event->y = y; 346 } 347 348 // TODO(evanm): why is this necessary here but not in test shell? 349 // This logic is the same as GtkButton. 350 if (event->type == GDK_BUTTON_PRESS && !gtk_widget_has_focus(widget)) 351 gtk_widget_grab_focus(widget); 352 353 host_view->is_popup_first_mouse_release_ = false; 354 RenderWidgetHostImpl* widget_host = 355 RenderWidgetHostImpl::From(host_view->GetRenderWidgetHost()); 356 if (widget_host) 357 widget_host->ForwardMouseEvent(WebInputEventFactory::mouseEvent(event)); 358 359 // Although we did handle the mouse event, we need to let other handlers 360 // run (in particular the one installed by WebContentsViewGtk). 361 return FALSE; 362 } 363 364 static gboolean OnMouseMoveEvent(GtkWidget* widget, 365 GdkEventMotion* event, 366 RenderWidgetHostViewGtk* host_view) { 367 TRACE_EVENT0("browser", 368 "RenderWidgetHostViewGtkWidget::OnMouseMoveEvent"); 369 // We want to translate the coordinates of events that do not originate 370 // from this widget to be relative to the top left of the widget. 371 GtkWidget* event_widget = gtk_get_event_widget( 372 reinterpret_cast<GdkEvent*>(event)); 373 if (event_widget != widget) { 374 int x = 0; 375 int y = 0; 376 gtk_widget_get_pointer(widget, &x, &y); 377 event->x = x; 378 event->y = y; 379 } 380 381 host_view->ModifyEventForEdgeDragging(widget, event); 382 383 WebKit::WebMouseEvent mouse_event = 384 WebInputEventFactory::mouseEvent(event); 385 386 if (host_view->mouse_locked_) { 387 gfx::Point center = host_view->GetWidgetCenter(); 388 389 bool moved_to_center = MovedToPoint(mouse_event, center); 390 if (moved_to_center) 391 host_view->mouse_has_been_warped_to_new_center_ = true; 392 393 host_view->ModifyEventMovementAndCoords(&mouse_event); 394 395 if (!moved_to_center && 396 (mouse_event.movementX || mouse_event.movementY)) { 397 GdkDisplay* display = gtk_widget_get_display(widget); 398 GdkScreen* screen = gtk_widget_get_screen(widget); 399 gdk_display_warp_pointer(display, screen, center.x(), center.y()); 400 if (host_view->mouse_has_been_warped_to_new_center_) 401 RenderWidgetHostImpl::From( 402 host_view->GetRenderWidgetHost())->ForwardMouseEvent(mouse_event); 403 } 404 } else { // Mouse is not locked. 405 host_view->ModifyEventMovementAndCoords(&mouse_event); 406 // Do not send mouse events while the mouse cursor is being warped back 407 // to the unlocked location. 408 if (!host_view->mouse_is_being_warped_to_unlocked_position_) { 409 RenderWidgetHostImpl::From( 410 host_view->GetRenderWidgetHost())->ForwardMouseEvent(mouse_event); 411 } 412 } 413 return FALSE; 414 } 415 416 static gboolean OnCrossingEvent(GtkWidget* widget, 417 GdkEventCrossing* event, 418 RenderWidgetHostViewGtk* host_view) { 419 TRACE_EVENT0("browser", 420 "RenderWidgetHostViewGtkWidget::OnCrossingEvent"); 421 const int any_button_mask = 422 GDK_BUTTON1_MASK | 423 GDK_BUTTON2_MASK | 424 GDK_BUTTON3_MASK | 425 GDK_BUTTON4_MASK | 426 GDK_BUTTON5_MASK; 427 428 // Only forward crossing events if the mouse button is not down. 429 // (When the mouse button is down, the proper events are already being 430 // sent by ButtonPressReleaseEvent and MouseMoveEvent, above, and if we 431 // additionally send this crossing event with the state indicating the 432 // button is down, it causes problems with drag and drop in WebKit.) 433 if (!(event->state & any_button_mask)) { 434 WebKit::WebMouseEvent mouse_event = 435 WebInputEventFactory::mouseEvent(event); 436 host_view->ModifyEventMovementAndCoords(&mouse_event); 437 // When crossing out and back into a render view the movement values 438 // must represent the instantaneous movement of the mouse, not the jump 439 // from the exit to re-entry point. 440 mouse_event.movementX = 0; 441 mouse_event.movementY = 0; 442 RenderWidgetHostImpl::From( 443 host_view->GetRenderWidgetHost())->ForwardMouseEvent(mouse_event); 444 } 445 446 return FALSE; 447 } 448 449 static gboolean OnClientEvent(GtkWidget* widget, 450 GdkEventClient* event, 451 RenderWidgetHostViewGtk* host_view) { 452 VLOG(1) << "client event type: " << event->message_type 453 << " data_format: " << event->data_format 454 << " data: " << event->data.l; 455 return TRUE; 456 } 457 458 // Return the net up / down (or left / right) distance represented by events 459 // in the events will be removed from the queue. We only look at the top of 460 // queue...any other type of event will cause us not to look farther. 461 // If there is a change to the set of modifier keys or scroll axis 462 // in the events we will stop looking as well. 463 static int GetPendingScrollDelta(bool vert, guint current_event_state) { 464 int num_clicks = 0; 465 GdkEvent* event; 466 bool event_coalesced = true; 467 while ((event = gdk_event_get()) && event_coalesced) { 468 event_coalesced = false; 469 if (event->type == GDK_SCROLL) { 470 GdkEventScroll scroll = event->scroll; 471 if (scroll.state & GDK_SHIFT_MASK) { 472 if (scroll.direction == GDK_SCROLL_UP) 473 scroll.direction = GDK_SCROLL_LEFT; 474 else if (scroll.direction == GDK_SCROLL_DOWN) 475 scroll.direction = GDK_SCROLL_RIGHT; 476 } 477 if (vert) { 478 if (scroll.direction == GDK_SCROLL_UP || 479 scroll.direction == GDK_SCROLL_DOWN) { 480 if (scroll.state == current_event_state) { 481 num_clicks += (scroll.direction == GDK_SCROLL_UP ? 1 : -1); 482 gdk_event_free(event); 483 event_coalesced = true; 484 } 485 } 486 } else { 487 if (scroll.direction == GDK_SCROLL_LEFT || 488 scroll.direction == GDK_SCROLL_RIGHT) { 489 if (scroll.state == current_event_state) { 490 num_clicks += (scroll.direction == GDK_SCROLL_LEFT ? 1 : -1); 491 gdk_event_free(event); 492 event_coalesced = true; 493 } 494 } 495 } 496 } 497 } 498 // If we have an event left we put it back on the queue. 499 if (event) { 500 gdk_event_put(event); 501 gdk_event_free(event); 502 } 503 return num_clicks * kDefaultScrollPixelsPerTick; 504 } 505 506 static gboolean OnMouseScrollEvent(GtkWidget* widget, 507 GdkEventScroll* event, 508 RenderWidgetHostViewGtk* host_view) { 509 TRACE_EVENT0("browser", 510 "RenderWidgetHostViewGtkWidget::OnMouseScrollEvent"); 511 // If the user is holding shift, translate it into a horizontal scroll. We 512 // don't care what other modifiers the user may be holding (zooming is 513 // handled at the WebContentsView level). 514 if (event->state & GDK_SHIFT_MASK) { 515 if (event->direction == GDK_SCROLL_UP) 516 event->direction = GDK_SCROLL_LEFT; 517 else if (event->direction == GDK_SCROLL_DOWN) 518 event->direction = GDK_SCROLL_RIGHT; 519 } 520 521 WebMouseWheelEvent web_event = WebInputEventFactory::mouseWheelEvent(event); 522 // We peek ahead at the top of the queue to look for additional pending 523 // scroll events. 524 if (event->direction == GDK_SCROLL_UP || 525 event->direction == GDK_SCROLL_DOWN) { 526 if (event->direction == GDK_SCROLL_UP) 527 web_event.deltaY = kDefaultScrollPixelsPerTick; 528 else 529 web_event.deltaY = -kDefaultScrollPixelsPerTick; 530 web_event.deltaY += GetPendingScrollDelta(true, event->state); 531 } else { 532 if (event->direction == GDK_SCROLL_LEFT) 533 web_event.deltaX = kDefaultScrollPixelsPerTick; 534 else 535 web_event.deltaX = -kDefaultScrollPixelsPerTick; 536 web_event.deltaX += GetPendingScrollDelta(false, event->state); 537 } 538 RenderWidgetHostImpl::From( 539 host_view->GetRenderWidgetHost())->ForwardWheelEvent(web_event); 540 return FALSE; 541 } 542 543 DISALLOW_IMPLICIT_CONSTRUCTORS(RenderWidgetHostViewGtkWidget); 544 }; 545 546 RenderWidgetHostViewGtk::RenderWidgetHostViewGtk(RenderWidgetHost* widget_host) 547 : host_(RenderWidgetHostImpl::From(widget_host)), 548 about_to_validate_and_paint_(false), 549 is_hidden_(false), 550 is_loading_(false), 551 parent_(NULL), 552 is_popup_first_mouse_release_(true), 553 was_imcontext_focused_before_grab_(false), 554 do_x_grab_(false), 555 is_fullscreen_(false), 556 made_active_(false), 557 mouse_is_being_warped_to_unlocked_position_(false), 558 destroy_handler_id_(0), 559 dragged_at_horizontal_edge_(0), 560 dragged_at_vertical_edge_(0), 561 compositing_surface_(gfx::kNullPluginWindow), 562 last_mouse_down_(NULL) { 563 host_->SetView(this); 564 } 565 566 RenderWidgetHostViewGtk::~RenderWidgetHostViewGtk() { 567 UnlockMouse(); 568 set_last_mouse_down(NULL); 569 view_.Destroy(); 570 } 571 572 bool RenderWidgetHostViewGtk::OnMessageReceived(const IPC::Message& message) { 573 bool handled = true; 574 IPC_BEGIN_MESSAGE_MAP(RenderWidgetHostViewGtk, message) 575 IPC_MESSAGE_HANDLER(ViewHostMsg_CreatePluginContainer, 576 OnCreatePluginContainer) 577 IPC_MESSAGE_HANDLER(ViewHostMsg_DestroyPluginContainer, 578 OnDestroyPluginContainer) 579 IPC_MESSAGE_UNHANDLED(handled = false) 580 IPC_END_MESSAGE_MAP() 581 return handled; 582 } 583 584 void RenderWidgetHostViewGtk::InitAsChild( 585 gfx::NativeView parent_view) { 586 DoSharedInit(); 587 gtk_widget_show(view_.get()); 588 } 589 590 void RenderWidgetHostViewGtk::InitAsPopup( 591 RenderWidgetHostView* parent_host_view, const gfx::Rect& pos) { 592 // If we aren't a popup, then |window| will be leaked. 593 DCHECK(IsPopup()); 594 595 DoSharedInit(); 596 parent_ = parent_host_view->GetNativeView(); 597 GtkWindow* window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_POPUP)); 598 gtk_container_add(GTK_CONTAINER(window), view_.get()); 599 DoPopupOrFullscreenInit(window, pos); 600 601 // Grab all input for the app. If a click lands outside the bounds of the 602 // popup, WebKit will notice and destroy us. The underlying X window needs to 603 // be created and mapped by the above code before we can grab the input 604 // devices. 605 if (NeedsInputGrab()) { 606 // If our parent is in a widget hierarchy that ends with a window, add 607 // ourselves to the same window group to make sure that our GTK grab 608 // covers it. 609 GtkWidget* toplevel = gtk_widget_get_toplevel(parent_); 610 if (toplevel && 611 GTK_WIDGET_TOPLEVEL(toplevel) && 612 GTK_IS_WINDOW(toplevel)) { 613 gtk_window_group_add_window( 614 gtk_window_get_group(GTK_WINDOW(toplevel)), window); 615 } 616 617 // Install an application-level GTK grab to make sure that we receive all of 618 // the app's input. 619 gtk_grab_add(view_.get()); 620 621 // We need to install an X grab as well. However if the app already has an X 622 // grab (as in the case of extension popup), an app grab will suffice. 623 do_x_grab_ = !gdk_pointer_is_grabbed(); 624 if (do_x_grab_) { 625 // Install the grab on behalf our parent window if it and all of its 626 // ancestors are mapped; otherwise, just use ourselves (maybe we're being 627 // shown on behalf of an inactive tab). 628 GdkWindow* grab_window = gtk_widget_get_window(parent_); 629 if (!grab_window || !gdk_window_is_viewable(grab_window)) 630 grab_window = gtk_widget_get_window(view_.get()); 631 632 gdk_pointer_grab( 633 grab_window, 634 TRUE, // Only events outside of the window are reported with 635 // respect to |parent_->window|. 636 static_cast<GdkEventMask>(GDK_BUTTON_PRESS_MASK | 637 GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK), 638 NULL, 639 NULL, 640 GDK_CURRENT_TIME); 641 // We grab keyboard events too so things like alt+tab are eaten. 642 gdk_keyboard_grab(grab_window, TRUE, GDK_CURRENT_TIME); 643 } 644 } 645 } 646 647 void RenderWidgetHostViewGtk::InitAsFullscreen( 648 RenderWidgetHostView* reference_host_view) { 649 DCHECK(reference_host_view); 650 DoSharedInit(); 651 652 is_fullscreen_ = true; 653 GtkWindow* window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); 654 gtk_window_set_decorated(window, FALSE); 655 destroy_handler_id_ = g_signal_connect(GTK_WIDGET(window), 656 "destroy", 657 G_CALLBACK(OnDestroyThunk), 658 this); 659 gtk_container_add(GTK_CONTAINER(window), view_.get()); 660 661 // Try to move and resize the window to cover the screen in case the window 662 // manager doesn't support _NET_WM_STATE_FULLSCREEN. 663 GdkScreen* screen = gtk_window_get_screen(window); 664 GdkWindow* ref_gdk_window = gtk_widget_get_window( 665 reference_host_view->GetNativeView()); 666 667 gfx::Rect bounds; 668 if (ref_gdk_window) { 669 const int monitor_id = gdk_screen_get_monitor_at_window(screen, 670 ref_gdk_window); 671 GdkRectangle monitor_rect; 672 gdk_screen_get_monitor_geometry(screen, monitor_id, &monitor_rect); 673 bounds = gfx::Rect(monitor_rect); 674 } else { 675 bounds = gfx::Rect( 676 0, 0, gdk_screen_get_width(screen), gdk_screen_get_height(screen)); 677 } 678 gtk_window_move(window, bounds.x(), bounds.y()); 679 gtk_window_resize(window, bounds.width(), bounds.height()); 680 gtk_window_fullscreen(window); 681 DoPopupOrFullscreenInit(window, bounds); 682 } 683 684 RenderWidgetHost* RenderWidgetHostViewGtk::GetRenderWidgetHost() const { 685 return host_; 686 } 687 688 void RenderWidgetHostViewGtk::WasShown() { 689 if (!is_hidden_) 690 return; 691 692 if (web_contents_switch_paint_time_.is_null()) 693 web_contents_switch_paint_time_ = base::TimeTicks::Now(); 694 is_hidden_ = false; 695 host_->WasShown(); 696 } 697 698 void RenderWidgetHostViewGtk::WasHidden() { 699 if (is_hidden_) 700 return; 701 702 // If we receive any more paint messages while we are hidden, we want to 703 // ignore them so we don't re-allocate the backing store. We will paint 704 // everything again when we become selected again. 705 is_hidden_ = true; 706 707 // If we have a renderer, then inform it that we are being hidden so it can 708 // reduce its resource utilization. 709 host_->WasHidden(); 710 711 web_contents_switch_paint_time_ = base::TimeTicks(); 712 } 713 714 void RenderWidgetHostViewGtk::SetSize(const gfx::Size& size) { 715 int width = std::min(size.width(), kMaxWindowWidth); 716 int height = std::min(size.height(), kMaxWindowHeight); 717 if (IsPopup()) { 718 // We're a popup, honor the size request. 719 gtk_widget_set_size_request(view_.get(), width, height); 720 } 721 722 // Update the size of the RWH. 723 if (requested_size_.width() != width || 724 requested_size_.height() != height) { 725 requested_size_ = gfx::Size(width, height); 726 host_->SendScreenRects(); 727 host_->WasResized(); 728 } 729 } 730 731 void RenderWidgetHostViewGtk::SetBounds(const gfx::Rect& rect) { 732 // This is called when webkit has sent us a Move message. 733 if (IsPopup()) { 734 gtk_window_move(GTK_WINDOW(gtk_widget_get_toplevel(view_.get())), 735 rect.x(), rect.y()); 736 } 737 738 SetSize(rect.size()); 739 } 740 741 gfx::NativeView RenderWidgetHostViewGtk::GetNativeView() const { 742 return view_.get(); 743 } 744 745 gfx::NativeViewId RenderWidgetHostViewGtk::GetNativeViewId() const { 746 return GtkNativeViewManager::GetInstance()->GetIdForWidget(view_.get()); 747 } 748 749 gfx::NativeViewAccessible RenderWidgetHostViewGtk::GetNativeViewAccessible() { 750 NOTIMPLEMENTED(); 751 return NULL; 752 } 753 754 void RenderWidgetHostViewGtk::MovePluginWindows( 755 const gfx::Vector2d& scroll_offset, 756 const std::vector<WebPluginGeometry>& moves) { 757 for (size_t i = 0; i < moves.size(); ++i) { 758 plugin_container_manager_.MovePluginContainer(moves[i]); 759 } 760 } 761 762 void RenderWidgetHostViewGtk::Focus() { 763 gtk_widget_grab_focus(view_.get()); 764 } 765 766 void RenderWidgetHostViewGtk::Blur() { 767 // TODO(estade): We should be clearing native focus as well, but I know of no 768 // way to do that without focusing another widget. 769 host_->Blur(); 770 } 771 772 bool RenderWidgetHostViewGtk::HasFocus() const { 773 return gtk_widget_has_focus(view_.get()); 774 } 775 776 void RenderWidgetHostViewGtk::ActiveWindowChanged(GdkWindow* window) { 777 GdkWindow* our_window = gtk_widget_get_parent_window(view_.get()); 778 779 if (our_window == window) 780 made_active_ = true; 781 782 // If the window was previously active, but isn't active anymore, shut it 783 // down. 784 if (is_fullscreen_ && our_window != window && made_active_) 785 host_->Shutdown(); 786 } 787 788 bool RenderWidgetHostViewGtk::Send(IPC::Message* message) { 789 return host_->Send(message); 790 } 791 792 bool RenderWidgetHostViewGtk::IsSurfaceAvailableForCopy() const { 793 return true; 794 } 795 796 void RenderWidgetHostViewGtk::Show() { 797 gtk_widget_show(view_.get()); 798 } 799 800 void RenderWidgetHostViewGtk::Hide() { 801 gtk_widget_hide(view_.get()); 802 } 803 804 bool RenderWidgetHostViewGtk::IsShowing() { 805 return gtk_widget_get_visible(view_.get()); 806 } 807 808 gfx::Rect RenderWidgetHostViewGtk::GetViewBounds() const { 809 GdkWindow* gdk_window = gtk_widget_get_window(view_.get()); 810 if (!gdk_window) 811 return gfx::Rect(requested_size_); 812 GdkRectangle window_rect; 813 gdk_window_get_origin(gdk_window, &window_rect.x, &window_rect.y); 814 return gfx::Rect(window_rect.x, window_rect.y, 815 requested_size_.width(), requested_size_.height()); 816 } 817 818 void RenderWidgetHostViewGtk::UpdateCursor(const WebCursor& cursor) { 819 // Optimize the common case, where the cursor hasn't changed. 820 // However, we can switch between different pixmaps, so only on the 821 // non-pixmap branch. 822 if (current_cursor_.GetCursorType() != GDK_CURSOR_IS_PIXMAP && 823 current_cursor_.GetCursorType() == cursor.GetCursorType()) { 824 return; 825 } 826 827 current_cursor_ = cursor; 828 ShowCurrentCursor(); 829 } 830 831 void RenderWidgetHostViewGtk::SetIsLoading(bool is_loading) { 832 is_loading_ = is_loading; 833 // Only call ShowCurrentCursor() when it will actually change the cursor. 834 if (current_cursor_.GetCursorType() == GDK_LAST_CURSOR) 835 ShowCurrentCursor(); 836 } 837 838 void RenderWidgetHostViewGtk::TextInputTypeChanged( 839 ui::TextInputType type, 840 bool can_compose_inline, 841 ui::TextInputMode input_mode) { 842 im_context_->UpdateInputMethodState(type, can_compose_inline); 843 } 844 845 void RenderWidgetHostViewGtk::ImeCancelComposition() { 846 im_context_->CancelComposition(); 847 } 848 849 void RenderWidgetHostViewGtk::DidUpdateBackingStore( 850 const gfx::Rect& scroll_rect, 851 const gfx::Vector2d& scroll_delta, 852 const std::vector<gfx::Rect>& copy_rects, 853 const ui::LatencyInfo& latency_info) { 854 TRACE_EVENT0("ui::gtk", "RenderWidgetHostViewGtk::DidUpdateBackingStore"); 855 software_latency_info_.MergeWith(latency_info); 856 857 if (is_hidden_) 858 return; 859 860 // TODO(darin): Implement the equivalent of Win32's ScrollWindowEX. Can that 861 // be done using XCopyArea? Perhaps similar to 862 // BackingStore::ScrollBackingStore? 863 if (about_to_validate_and_paint_) 864 invalid_rect_.Union(scroll_rect); 865 else 866 Paint(scroll_rect); 867 868 for (size_t i = 0; i < copy_rects.size(); ++i) { 869 // Avoid double painting. NOTE: This is only relevant given the call to 870 // Paint(scroll_rect) above. 871 gfx::Rect rect = gfx::SubtractRects(copy_rects[i], scroll_rect); 872 if (rect.IsEmpty()) 873 continue; 874 875 if (about_to_validate_and_paint_) 876 invalid_rect_.Union(rect); 877 else 878 Paint(rect); 879 } 880 } 881 882 void RenderWidgetHostViewGtk::RenderProcessGone(base::TerminationStatus status, 883 int error_code) { 884 Destroy(); 885 plugin_container_manager_.set_host_widget(NULL); 886 } 887 888 void RenderWidgetHostViewGtk::Destroy() { 889 if (compositing_surface_ != gfx::kNullPluginWindow) { 890 GtkNativeViewManager* manager = GtkNativeViewManager::GetInstance(); 891 manager->ReleasePermanentXID(compositing_surface_); 892 } 893 894 if (do_x_grab_) { 895 // Undo the X grab. 896 GdkDisplay* display = gtk_widget_get_display(parent_); 897 gdk_display_pointer_ungrab(display, GDK_CURRENT_TIME); 898 gdk_display_keyboard_ungrab(display, GDK_CURRENT_TIME); 899 } 900 901 if (view_.get()) { 902 // If this is a popup or fullscreen widget, then we need to destroy the 903 // window that we created to hold it. 904 if (IsPopup() || is_fullscreen_) { 905 GtkWidget* window = gtk_widget_get_parent(view_.get()); 906 907 ui::ActiveWindowWatcherX::RemoveObserver(this); 908 909 // Disconnect the destroy handler so that we don't try to shutdown twice. 910 if (is_fullscreen_) 911 g_signal_handler_disconnect(window, destroy_handler_id_); 912 913 gtk_widget_destroy(window); 914 } 915 916 // Remove |view_| from all containers now, so nothing else can hold a 917 // reference to |view_|'s widget except possibly a gtk signal handler if 918 // this code is currently executing within the context of a gtk signal 919 // handler. Note that |view_| is still alive after this call. It will be 920 // deallocated in the destructor. 921 // See http://crbug.com/11847 for details. 922 gtk_widget_destroy(view_.get()); 923 } 924 925 // The RenderWidgetHost's destruction led here, so don't call it. 926 host_ = NULL; 927 928 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); 929 } 930 931 void RenderWidgetHostViewGtk::SetTooltipText(const string16& tooltip_text) { 932 // Maximum number of characters we allow in a tooltip. 933 const int kMaxTooltipLength = 8 << 10; 934 // Clamp the tooltip length to kMaxTooltipLength so that we don't 935 // accidentally DOS the user with a mega tooltip (since GTK doesn't do 936 // this itself). 937 // I filed https://bugzilla.gnome.org/show_bug.cgi?id=604641 upstream. 938 const string16 clamped_tooltip = 939 ui::TruncateString(tooltip_text, kMaxTooltipLength); 940 941 if (clamped_tooltip.empty()) { 942 gtk_widget_set_has_tooltip(view_.get(), FALSE); 943 } else { 944 gtk_widget_set_tooltip_text(view_.get(), 945 UTF16ToUTF8(clamped_tooltip).c_str()); 946 } 947 } 948 949 void RenderWidgetHostViewGtk::SelectionChanged(const string16& text, 950 size_t offset, 951 const ui::Range& range) { 952 RenderWidgetHostViewBase::SelectionChanged(text, offset, range); 953 954 if (text.empty() || range.is_empty()) 955 return; 956 size_t pos = range.GetMin() - offset; 957 size_t n = range.length(); 958 959 DCHECK(pos + n <= text.length()) << "The text can not fully cover range."; 960 if (pos >= text.length()) { 961 NOTREACHED() << "The text can not cover range."; 962 return; 963 } 964 965 // Set the BUFFER_SELECTION to the ui::Clipboard. 966 ui::ScopedClipboardWriter clipboard_writer( 967 ui::Clipboard::GetForCurrentThread(), 968 ui::Clipboard::BUFFER_SELECTION); 969 clipboard_writer.WriteText(text.substr(pos, n)); 970 } 971 972 void RenderWidgetHostViewGtk::SelectionBoundsChanged( 973 const ViewHostMsg_SelectionBounds_Params& params) { 974 im_context_->UpdateCaretBounds( 975 gfx::UnionRects(params.anchor_rect, params.focus_rect)); 976 } 977 978 void RenderWidgetHostViewGtk::ScrollOffsetChanged() { 979 } 980 981 GdkEventButton* RenderWidgetHostViewGtk::GetLastMouseDown() { 982 return last_mouse_down_; 983 } 984 985 gfx::NativeView RenderWidgetHostViewGtk::BuildInputMethodsGtkMenu() { 986 return im_context_->BuildInputMethodsGtkMenu(); 987 } 988 989 void RenderWidgetHostViewGtk::OnDestroy(GtkWidget* widget) { 990 DCHECK(is_fullscreen_); 991 host_->Shutdown(); 992 } 993 994 bool RenderWidgetHostViewGtk::NeedsInputGrab() { 995 return popup_type_ == WebKit::WebPopupTypeSelect; 996 } 997 998 bool RenderWidgetHostViewGtk::IsPopup() const { 999 return popup_type_ != WebKit::WebPopupTypeNone; 1000 } 1001 1002 void RenderWidgetHostViewGtk::DoSharedInit() { 1003 view_.Own(RenderWidgetHostViewGtkWidget::CreateNewWidget(this)); 1004 im_context_.reset(new GtkIMContextWrapper(this)); 1005 plugin_container_manager_.set_host_widget(view_.get()); 1006 key_bindings_handler_.reset(new GtkKeyBindingsHandler(view_.get())); 1007 } 1008 1009 void RenderWidgetHostViewGtk::DoPopupOrFullscreenInit(GtkWindow* window, 1010 const gfx::Rect& bounds) { 1011 requested_size_.SetSize(std::min(bounds.width(), kMaxWindowWidth), 1012 std::min(bounds.height(), kMaxWindowHeight)); 1013 host_->WasResized(); 1014 1015 ui::ActiveWindowWatcherX::AddObserver(this); 1016 1017 // Don't set the size when we're going fullscreen. This can confuse the 1018 // window manager into thinking we're resizing a fullscreen window and 1019 // therefore not fullscreen anymore. 1020 if (!is_fullscreen_) { 1021 gtk_widget_set_size_request( 1022 view_.get(), requested_size_.width(), requested_size_.height()); 1023 1024 // Don't allow the window to be resized. This also forces the window to 1025 // shrink down to the size of its child contents. 1026 gtk_window_set_resizable(window, FALSE); 1027 gtk_window_set_default_size(window, -1, -1); 1028 gtk_window_move(window, bounds.x(), bounds.y()); 1029 } 1030 1031 gtk_widget_show_all(GTK_WIDGET(window)); 1032 } 1033 1034 BackingStore* RenderWidgetHostViewGtk::AllocBackingStore( 1035 const gfx::Size& size) { 1036 gint depth = gdk_visual_get_depth(gtk_widget_get_visual(view_.get())); 1037 return new BackingStoreGtk(host_, size, 1038 ui::GetVisualFromGtkWidget(view_.get()), 1039 depth); 1040 } 1041 1042 // NOTE: |output| is initialized with the size of |src_subrect|, and |dst_size| 1043 // is ignored on GTK. 1044 void RenderWidgetHostViewGtk::CopyFromCompositingSurface( 1045 const gfx::Rect& src_subrect, 1046 const gfx::Size& /* dst_size */, 1047 const base::Callback<void(bool, const SkBitmap&)>& callback) { 1048 // Grab the snapshot from the renderer as that's the only reliable way to 1049 // readback from the GPU for this platform right now. 1050 GetRenderWidgetHost()->GetSnapshotFromRenderer(src_subrect, callback); 1051 } 1052 1053 void RenderWidgetHostViewGtk::CopyFromCompositingSurfaceToVideoFrame( 1054 const gfx::Rect& src_subrect, 1055 const scoped_refptr<media::VideoFrame>& target, 1056 const base::Callback<void(bool)>& callback) { 1057 NOTIMPLEMENTED(); 1058 callback.Run(false); 1059 } 1060 1061 bool RenderWidgetHostViewGtk::CanCopyToVideoFrame() const { 1062 return false; 1063 } 1064 1065 void RenderWidgetHostViewGtk::AcceleratedSurfaceBuffersSwapped( 1066 const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params, 1067 int gpu_host_id) { 1068 AcceleratedSurfaceMsg_BufferPresented_Params ack_params; 1069 ack_params.sync_point = 0; 1070 RenderWidgetHostImpl::AcknowledgeBufferPresent( 1071 params.route_id, gpu_host_id, ack_params); 1072 host_->FrameSwapped(params.latency_info); 1073 } 1074 1075 void RenderWidgetHostViewGtk::AcceleratedSurfacePostSubBuffer( 1076 const GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params& params, 1077 int gpu_host_id) { 1078 AcceleratedSurfaceMsg_BufferPresented_Params ack_params; 1079 ack_params.sync_point = 0; 1080 RenderWidgetHostImpl::AcknowledgeBufferPresent( 1081 params.route_id, gpu_host_id, ack_params); 1082 host_->FrameSwapped(params.latency_info); 1083 } 1084 1085 void RenderWidgetHostViewGtk::AcceleratedSurfaceSuspend() { 1086 } 1087 1088 void RenderWidgetHostViewGtk::AcceleratedSurfaceRelease() { 1089 } 1090 1091 bool RenderWidgetHostViewGtk::HasAcceleratedSurface( 1092 const gfx::Size& desired_size) { 1093 // TODO(jbates) Implement this so this view can use GetBackingStore for both 1094 // software and GPU frames. Defaulting to false just makes GetBackingStore 1095 // only useable for software frames. 1096 return false; 1097 } 1098 1099 void RenderWidgetHostViewGtk::SetBackground(const SkBitmap& background) { 1100 RenderWidgetHostViewBase::SetBackground(background); 1101 Send(new ViewMsg_SetBackground(host_->GetRoutingID(), background)); 1102 } 1103 1104 void RenderWidgetHostViewGtk::ModifyEventForEdgeDragging( 1105 GtkWidget* widget, GdkEventMotion* event) { 1106 // If the widget is aligned with an edge of the monitor its on and the user 1107 // attempts to drag past that edge we track the number of times it has 1108 // occurred, so that we can force the widget to scroll when it otherwise 1109 // would be unable to, by modifying the (x,y) position in the drag 1110 // event that we forward on to webkit. If we get a move that's no longer a 1111 // drag or a drag indicating the user is no longer at that edge we stop 1112 // altering the drag events. 1113 int new_dragged_at_horizontal_edge = 0; 1114 int new_dragged_at_vertical_edge = 0; 1115 // Used for checking the edges of the monitor. We cache the values to save 1116 // roundtrips to the X server. 1117 CR_DEFINE_STATIC_LOCAL(gfx::Size, drag_monitor_size, ()); 1118 if (event->state & GDK_BUTTON1_MASK) { 1119 if (drag_monitor_size.IsEmpty()) { 1120 // We can safely cache the monitor size for the duration of a drag. 1121 GdkScreen* screen = gtk_widget_get_screen(widget); 1122 int monitor = 1123 gdk_screen_get_monitor_at_point(screen, event->x_root, event->y_root); 1124 GdkRectangle geometry; 1125 gdk_screen_get_monitor_geometry(screen, monitor, &geometry); 1126 drag_monitor_size.SetSize(geometry.width, geometry.height); 1127 } 1128 GtkAllocation allocation; 1129 gtk_widget_get_allocation(widget, &allocation); 1130 // Check X and Y independently, as the user could be dragging into a corner. 1131 if (event->x == 0 && event->x_root == 0) { 1132 new_dragged_at_horizontal_edge = dragged_at_horizontal_edge_ - 1; 1133 } else if (allocation.width - 1 == static_cast<gint>(event->x) && 1134 drag_monitor_size.width() - 1 == static_cast<gint>(event->x_root)) { 1135 new_dragged_at_horizontal_edge = dragged_at_horizontal_edge_ + 1; 1136 } 1137 1138 if (event->y == 0 && event->y_root == 0) { 1139 new_dragged_at_vertical_edge = dragged_at_vertical_edge_ - 1; 1140 } else if (allocation.height - 1 == static_cast<gint>(event->y) && 1141 drag_monitor_size.height() - 1 == static_cast<gint>(event->y_root)) { 1142 new_dragged_at_vertical_edge = dragged_at_vertical_edge_ + 1; 1143 } 1144 1145 event->x_root += new_dragged_at_horizontal_edge; 1146 event->x += new_dragged_at_horizontal_edge; 1147 event->y_root += new_dragged_at_vertical_edge; 1148 event->y += new_dragged_at_vertical_edge; 1149 } else { 1150 // Clear whenever we get a non-drag mouse move. 1151 drag_monitor_size.SetSize(0, 0); 1152 } 1153 dragged_at_horizontal_edge_ = new_dragged_at_horizontal_edge; 1154 dragged_at_vertical_edge_ = new_dragged_at_vertical_edge; 1155 } 1156 1157 void RenderWidgetHostViewGtk::Paint(const gfx::Rect& damage_rect) { 1158 TRACE_EVENT0("ui::gtk", "RenderWidgetHostViewGtk::Paint"); 1159 1160 // If the GPU process is rendering directly into the View, 1161 // call the compositor directly. 1162 RenderWidgetHostImpl* render_widget_host = 1163 RenderWidgetHostImpl::From(GetRenderWidgetHost()); 1164 if (render_widget_host->is_accelerated_compositing_active()) { 1165 host_->ScheduleComposite(); 1166 return; 1167 } 1168 1169 GdkWindow* window = gtk_widget_get_window(view_.get()); 1170 DCHECK(!about_to_validate_and_paint_); 1171 1172 invalid_rect_ = damage_rect; 1173 about_to_validate_and_paint_ = true; 1174 1175 // If the size of our canvas is (0,0), then we don't want to block here. We 1176 // are doing one of our first paints and probably have animations going on. 1177 bool force_create = !host_->empty(); 1178 BackingStoreGtk* backing_store = static_cast<BackingStoreGtk*>( 1179 host_->GetBackingStore(force_create)); 1180 // Calling GetBackingStore maybe have changed |invalid_rect_|... 1181 about_to_validate_and_paint_ = false; 1182 1183 gfx::Rect paint_rect = gfx::Rect(0, 0, kMaxWindowWidth, kMaxWindowHeight); 1184 paint_rect.Intersect(invalid_rect_); 1185 1186 if (backing_store) { 1187 // Only render the widget if it is attached to a window; there's a short 1188 // period where this object isn't attached to a window but hasn't been 1189 // Destroy()ed yet and it receives paint messages... 1190 if (window) { 1191 backing_store->XShowRect(gfx::Point(0, 0), 1192 paint_rect, ui::GetX11WindowFromGtkWidget(view_.get())); 1193 } 1194 if (!whiteout_start_time_.is_null()) { 1195 base::TimeDelta whiteout_duration = base::TimeTicks::Now() - 1196 whiteout_start_time_; 1197 UMA_HISTOGRAM_TIMES("MPArch.RWHH_WhiteoutDuration", whiteout_duration); 1198 1199 // Reset the start time to 0 so that we start recording again the next 1200 // time the backing store is NULL... 1201 whiteout_start_time_ = base::TimeTicks(); 1202 } 1203 if (!web_contents_switch_paint_time_.is_null()) { 1204 base::TimeDelta web_contents_switch_paint_duration = 1205 base::TimeTicks::Now() - web_contents_switch_paint_time_; 1206 UMA_HISTOGRAM_TIMES("MPArch.RWH_TabSwitchPaintDuration", 1207 web_contents_switch_paint_duration); 1208 // Reset web_contents_switch_paint_time_ to 0 so future tab selections are 1209 // recorded. 1210 web_contents_switch_paint_time_ = base::TimeTicks(); 1211 } 1212 software_latency_info_.swap_timestamp = base::TimeTicks::HighResNow(); 1213 render_widget_host->FrameSwapped(software_latency_info_); 1214 software_latency_info_.Clear(); 1215 } else { 1216 if (window) 1217 gdk_window_clear(window); 1218 if (whiteout_start_time_.is_null()) 1219 whiteout_start_time_ = base::TimeTicks::Now(); 1220 } 1221 } 1222 1223 void RenderWidgetHostViewGtk::ShowCurrentCursor() { 1224 // The widget may not have a window. If that's the case, abort mission. This 1225 // is the same issue as that explained above in Paint(). 1226 if (!gtk_widget_get_window(view_.get())) 1227 return; 1228 1229 // TODO(port): WebKit bug https://bugs.webkit.org/show_bug.cgi?id=16388 is 1230 // that calling gdk_window_set_cursor repeatedly is expensive. We should 1231 // avoid it here where possible. 1232 GdkCursor* gdk_cursor; 1233 if (current_cursor_.GetCursorType() == GDK_LAST_CURSOR) { 1234 // Use MOZ_CURSOR_SPINNING if we are showing the default cursor and 1235 // the page is loading. 1236 gdk_cursor = is_loading_ ? GetMozSpinningCursor() : NULL; 1237 } else { 1238 gdk_cursor = current_cursor_.GetNativeCursor(); 1239 } 1240 gdk_window_set_cursor(gtk_widget_get_window(view_.get()), gdk_cursor); 1241 } 1242 1243 void RenderWidgetHostViewGtk::SetHasHorizontalScrollbar( 1244 bool has_horizontal_scrollbar) { 1245 } 1246 1247 void RenderWidgetHostViewGtk::SetScrollOffsetPinning( 1248 bool is_pinned_to_left, bool is_pinned_to_right) { 1249 } 1250 1251 1252 void RenderWidgetHostViewGtk::OnAcceleratedCompositingStateChange() { 1253 bool activated = host_->is_accelerated_compositing_active(); 1254 GtkPreserveWindow* widget = reinterpret_cast<GtkPreserveWindow*>(view_.get()); 1255 1256 gtk_preserve_window_delegate_resize(widget, activated); 1257 } 1258 1259 void RenderWidgetHostViewGtk::GetScreenInfo(WebScreenInfo* results) { 1260 GdkWindow* gdk_window = gtk_widget_get_window(view_.get()); 1261 if (!gdk_window) { 1262 GdkDisplay* display = gdk_display_get_default(); 1263 gdk_window = gdk_display_get_default_group(display); 1264 } 1265 if (!gdk_window) 1266 return; 1267 GetScreenInfoFromNativeWindow(gdk_window, results); 1268 } 1269 1270 gfx::Rect RenderWidgetHostViewGtk::GetBoundsInRootWindow() { 1271 GtkWidget* toplevel = gtk_widget_get_toplevel(view_.get()); 1272 if (!toplevel) 1273 return GetViewBounds(); 1274 1275 GdkRectangle frame_extents; 1276 GdkWindow* gdk_window = gtk_widget_get_window(toplevel); 1277 if (!gdk_window) 1278 return GetViewBounds(); 1279 1280 gdk_window_get_frame_extents(gdk_window, &frame_extents); 1281 return gfx::Rect(frame_extents.x, frame_extents.y, 1282 frame_extents.width, frame_extents.height); 1283 } 1284 1285 gfx::GLSurfaceHandle RenderWidgetHostViewGtk::GetCompositingSurface() { 1286 if (compositing_surface_ == gfx::kNullPluginWindow) { 1287 GtkNativeViewManager* manager = GtkNativeViewManager::GetInstance(); 1288 gfx::NativeViewId view_id = GetNativeViewId(); 1289 1290 if (!manager->GetPermanentXIDForId(&compositing_surface_, view_id)) { 1291 DLOG(ERROR) << "Can't find XID for view id " << view_id; 1292 } 1293 } 1294 return gfx::GLSurfaceHandle(compositing_surface_, gfx::NATIVE_TRANSPORT); 1295 } 1296 1297 bool RenderWidgetHostViewGtk::LockMouse() { 1298 if (mouse_locked_) 1299 return true; 1300 1301 mouse_locked_ = true; 1302 1303 // Release any current grab. 1304 GtkWidget* current_grab_window = gtk_grab_get_current(); 1305 if (current_grab_window) { 1306 gtk_grab_remove(current_grab_window); 1307 LOG(WARNING) << "Locking Mouse with gdk_pointer_grab, " 1308 << "but had to steal grab from another window"; 1309 } 1310 1311 GtkWidget* widget = view_.get(); 1312 GdkWindow* window = gtk_widget_get_window(widget); 1313 GdkCursor* cursor = gdk_cursor_new(GDK_BLANK_CURSOR); 1314 1315 GdkGrabStatus grab_status = 1316 gdk_pointer_grab(window, 1317 FALSE, // owner_events 1318 static_cast<GdkEventMask>( 1319 GDK_POINTER_MOTION_MASK | 1320 GDK_BUTTON_PRESS_MASK | 1321 GDK_BUTTON_RELEASE_MASK), 1322 window, // confine_to 1323 cursor, 1324 GDK_CURRENT_TIME); 1325 1326 if (grab_status != GDK_GRAB_SUCCESS) { 1327 LOG(WARNING) << "Failed to grab pointer for LockMouse. " 1328 << "gdk_pointer_grab returned: " << grab_status; 1329 mouse_locked_ = false; 1330 return false; 1331 } 1332 1333 // Clear the tooltip window. 1334 SetTooltipText(string16()); 1335 1336 // Ensure that the widget center location will be relevant for this mouse 1337 // lock session. It is updated whenever the window geometry moves 1338 // but may be out of date due to switching tabs. 1339 MarkCachedWidgetCenterStale(); 1340 1341 return true; 1342 } 1343 1344 void RenderWidgetHostViewGtk::UnlockMouse() { 1345 if (!mouse_locked_) 1346 return; 1347 1348 mouse_locked_ = false; 1349 1350 GtkWidget* widget = view_.get(); 1351 GdkDisplay* display = gtk_widget_get_display(widget); 1352 GdkScreen* screen = gtk_widget_get_screen(widget); 1353 gdk_display_pointer_ungrab(display, GDK_CURRENT_TIME); 1354 gdk_display_warp_pointer(display, screen, 1355 unlocked_global_mouse_position_.x(), 1356 unlocked_global_mouse_position_.y()); 1357 mouse_is_being_warped_to_unlocked_position_ = true; 1358 1359 if (host_) 1360 host_->LostMouseLock(); 1361 } 1362 1363 void RenderWidgetHostViewGtk::ForwardKeyboardEvent( 1364 const NativeWebKeyboardEvent& event) { 1365 if (!host_) 1366 return; 1367 1368 EditCommands edit_commands; 1369 if (!event.skip_in_browser && 1370 key_bindings_handler_->Match(event, &edit_commands)) { 1371 Send(new InputMsg_SetEditCommandsForNextKeyEvent( 1372 host_->GetRoutingID(), edit_commands)); 1373 NativeWebKeyboardEvent copy_event(event); 1374 copy_event.match_edit_command = true; 1375 host_->ForwardKeyboardEvent(copy_event); 1376 return; 1377 } 1378 1379 host_->ForwardKeyboardEvent(event); 1380 } 1381 1382 bool RenderWidgetHostViewGtk::RetrieveSurrounding(std::string* text, 1383 size_t* cursor_index) { 1384 if (!selection_range_.IsValid()) 1385 return false; 1386 1387 size_t offset = selection_range_.GetMin() - selection_text_offset_; 1388 DCHECK(offset <= selection_text_.length()); 1389 1390 if (offset == selection_text_.length()) { 1391 *text = UTF16ToUTF8(selection_text_); 1392 *cursor_index = text->length(); 1393 return true; 1394 } 1395 1396 *text = base::UTF16ToUTF8AndAdjustOffset( 1397 base::StringPiece16(selection_text_), &offset); 1398 if (offset == string16::npos) { 1399 NOTREACHED() << "Invalid offset in UTF16 string."; 1400 return false; 1401 } 1402 *cursor_index = offset; 1403 return true; 1404 } 1405 1406 void RenderWidgetHostViewGtk::set_last_mouse_down(GdkEventButton* event) { 1407 GdkEventButton* temp = NULL; 1408 if (event) { 1409 temp = reinterpret_cast<GdkEventButton*>( 1410 gdk_event_copy(reinterpret_cast<GdkEvent*>(event))); 1411 } 1412 1413 if (last_mouse_down_) 1414 gdk_event_free(reinterpret_cast<GdkEvent*>(last_mouse_down_)); 1415 1416 last_mouse_down_ = temp; 1417 } 1418 1419 void RenderWidgetHostViewGtk::MarkCachedWidgetCenterStale() { 1420 widget_center_valid_ = false; 1421 mouse_has_been_warped_to_new_center_ = false; 1422 } 1423 1424 gfx::Point RenderWidgetHostViewGtk::GetWidgetCenter() { 1425 if (widget_center_valid_) 1426 return widget_center_; 1427 1428 GdkWindow* window = gtk_widget_get_window(view_.get()); 1429 gint window_x = 0; 1430 gint window_y = 0; 1431 gdk_window_get_origin(window, &window_x, &window_y); 1432 gint window_w = gdk_window_get_width(window); 1433 gint window_h = gdk_window_get_height(window); 1434 widget_center_.SetPoint(window_x + window_w / 2, 1435 window_y + window_h / 2); 1436 widget_center_valid_ = true; 1437 return widget_center_; 1438 } 1439 1440 void RenderWidgetHostViewGtk::ModifyEventMovementAndCoords( 1441 WebKit::WebMouseEvent* event) { 1442 // Movement is computed by taking the difference of the new cursor position 1443 // and the previous. Under mouse lock the cursor will be warped back to the 1444 // center so that we are not limited by clipping boundaries. 1445 // We do not measure movement as the delta from cursor to center because 1446 // we may receive more mouse movement events before our warp has taken 1447 // effect. 1448 event->movementX = event->globalX - global_mouse_position_.x(); 1449 event->movementY = event->globalY - global_mouse_position_.y(); 1450 1451 // While the cursor is being warped back to the unlocked position, suppress 1452 // the movement member data. 1453 if (mouse_is_being_warped_to_unlocked_position_) { 1454 event->movementX = 0; 1455 event->movementY = 0; 1456 if (MovedToPoint(*event, unlocked_global_mouse_position_)) 1457 mouse_is_being_warped_to_unlocked_position_ = false; 1458 } 1459 1460 global_mouse_position_.SetPoint(event->globalX, event->globalY); 1461 1462 // Under mouse lock, coordinates of mouse are locked to what they were when 1463 // mouse lock was entered. 1464 if (mouse_locked_) { 1465 event->x = unlocked_mouse_position_.x(); 1466 event->y = unlocked_mouse_position_.y(); 1467 event->windowX = unlocked_mouse_position_.x(); 1468 event->windowY = unlocked_mouse_position_.y(); 1469 event->globalX = unlocked_global_mouse_position_.x(); 1470 event->globalY = unlocked_global_mouse_position_.y(); 1471 } else { 1472 unlocked_mouse_position_.SetPoint(event->windowX, event->windowY); 1473 unlocked_global_mouse_position_.SetPoint(event->globalX, event->globalY); 1474 } 1475 } 1476 1477 //////////////////////////////////////////////////////////////////////////////// 1478 // RenderWidgetHostView, public: 1479 1480 // static 1481 RenderWidgetHostView* RenderWidgetHostView::CreateViewForWidget( 1482 RenderWidgetHost* widget) { 1483 return new RenderWidgetHostViewGtk(widget); 1484 } 1485 1486 // static 1487 void RenderWidgetHostViewPort::GetDefaultScreenInfo(WebScreenInfo* results) { 1488 GdkWindow* gdk_window = 1489 gdk_display_get_default_group(gdk_display_get_default()); 1490 GetScreenInfoFromNativeWindow(gdk_window, results); 1491 } 1492 1493 void RenderWidgetHostViewGtk::SetAccessibilityFocus(int acc_obj_id) { 1494 if (!host_) 1495 return; 1496 1497 host_->AccessibilitySetFocus(acc_obj_id); 1498 } 1499 1500 void RenderWidgetHostViewGtk::AccessibilityDoDefaultAction(int acc_obj_id) { 1501 if (!host_) 1502 return; 1503 1504 host_->AccessibilityDoDefaultAction(acc_obj_id); 1505 } 1506 1507 void RenderWidgetHostViewGtk::AccessibilityScrollToMakeVisible( 1508 int acc_obj_id, gfx::Rect subfocus) { 1509 if (!host_) 1510 return; 1511 1512 host_->AccessibilityScrollToMakeVisible(acc_obj_id, subfocus); 1513 } 1514 1515 void RenderWidgetHostViewGtk::AccessibilityScrollToPoint( 1516 int acc_obj_id, gfx::Point point) { 1517 if (!host_) 1518 return; 1519 1520 host_->AccessibilityScrollToPoint(acc_obj_id, point); 1521 } 1522 1523 void RenderWidgetHostViewGtk::AccessibilitySetTextSelection( 1524 int acc_obj_id, int start_offset, int end_offset) { 1525 if (!host_) 1526 return; 1527 1528 host_->AccessibilitySetTextSelection(acc_obj_id, start_offset, end_offset); 1529 } 1530 1531 gfx::Point RenderWidgetHostViewGtk::GetLastTouchEventLocation() const { 1532 // Not needed on Linux. 1533 return gfx::Point(); 1534 } 1535 1536 void RenderWidgetHostViewGtk::FatalAccessibilityTreeError() { 1537 if (host_) { 1538 host_->FatalAccessibilityTreeError(); 1539 SetBrowserAccessibilityManager(NULL); 1540 } else { 1541 CHECK(FALSE); 1542 } 1543 } 1544 1545 void RenderWidgetHostViewGtk::OnAccessibilityNotifications( 1546 const std::vector<AccessibilityHostMsg_NotificationParams>& params) { 1547 if (!GetBrowserAccessibilityManager()) { 1548 GtkWidget* parent = gtk_widget_get_parent(view_.get()); 1549 SetBrowserAccessibilityManager( 1550 new BrowserAccessibilityManagerGtk( 1551 parent, 1552 BrowserAccessibilityManagerGtk::GetEmptyDocument(), 1553 this)); 1554 } 1555 GetBrowserAccessibilityManager()->OnAccessibilityNotifications(params); 1556 } 1557 1558 AtkObject* RenderWidgetHostViewGtk::GetAccessible() { 1559 if (!GetBrowserAccessibilityManager()) { 1560 GtkWidget* parent = gtk_widget_get_parent(view_.get()); 1561 SetBrowserAccessibilityManager( 1562 new BrowserAccessibilityManagerGtk( 1563 parent, 1564 BrowserAccessibilityManagerGtk::GetEmptyDocument(), 1565 this)); 1566 } 1567 BrowserAccessibilityGtk* root = 1568 GetBrowserAccessibilityManager()->GetRoot()->ToBrowserAccessibilityGtk(); 1569 1570 atk_object_set_role(root->GetAtkObject(), ATK_ROLE_HTML_CONTAINER); 1571 return root->GetAtkObject(); 1572 } 1573 1574 void RenderWidgetHostViewGtk::OnCreatePluginContainer( 1575 gfx::PluginWindowHandle id) { 1576 plugin_container_manager_.CreatePluginContainer(id); 1577 } 1578 1579 void RenderWidgetHostViewGtk::OnDestroyPluginContainer( 1580 gfx::PluginWindowHandle id) { 1581 plugin_container_manager_.DestroyPluginContainer(id); 1582 } 1583 1584 } // namespace content 1585