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