| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "webkit/tools/test_shell/webwidget_host.h" | |
| 6 | |
| 7 #include <cairo/cairo.h> | |
| 8 #include <gtk/gtk.h> | |
| 9 | |
| 10 #include "base/basictypes.h" | |
| 11 #include "base/logging.h" | |
| 12 #include "skia/ext/bitmap_platform_device.h" | |
| 13 #include "skia/ext/platform_canvas.h" | |
| 14 #include "skia/ext/platform_device.h" | |
| 15 #include "third_party/WebKit/Source/Platform/chromium/public/WebSize.h" | |
| 16 #include "third_party/WebKit/Source/WebKit/chromium/public/gtk/WebInputEventFact
ory.h" | |
| 17 #include "third_party/WebKit/Source/WebKit/chromium/public/x11/WebScreenInfoFact
ory.h" | |
| 18 #include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h" | |
| 19 #include "third_party/WebKit/Source/WebKit/chromium/public/WebPopupMenu.h" | |
| 20 #include "third_party/WebKit/Source/WebKit/chromium/public/WebScreenInfo.h" | |
| 21 #include "webkit/tools/test_shell/test_shell.h" | |
| 22 #include "webkit/tools/test_shell/test_shell_x11.h" | |
| 23 | |
| 24 using WebKit::WebInputEventFactory; | |
| 25 using WebKit::WebKeyboardEvent; | |
| 26 using WebKit::WebMouseEvent; | |
| 27 using WebKit::WebMouseWheelEvent; | |
| 28 using WebKit::WebPopupMenu; | |
| 29 using WebKit::WebScreenInfo; | |
| 30 using WebKit::WebScreenInfoFactory; | |
| 31 using WebKit::WebSize; | |
| 32 using WebKit::WebWidgetClient; | |
| 33 | |
| 34 namespace { | |
| 35 | |
| 36 // Used to store a backpointer to WebWidgetHost from our GtkWidget. | |
| 37 const char kWebWidgetHostKey[] = "webwidgethost"; | |
| 38 | |
| 39 // In response to an invalidation, we call into WebKit to do layout. On | |
| 40 // Windows, WM_PAINT is a virtual message so any extra invalidates that come up | |
| 41 // while it's doing layout are implicitly swallowed as soon as we actually do | |
| 42 // drawing via BeginPaint. | |
| 43 // | |
| 44 // Though GTK does know how to collapse multiple paint requests, it won't erase | |
| 45 // paint requests from the future when we start drawing. To avoid an infinite | |
| 46 // cycle of repaints, we track whether we're currently handling a redraw, and | |
| 47 // during that if we get told by WebKit that a region has become invalid, we | |
| 48 // still add that region to the local dirty rect but *don't* enqueue yet | |
| 49 // another "do a paint" message. | |
| 50 bool g_handling_expose = false; | |
| 51 | |
| 52 // ----------------------------------------------------------------------------- | |
| 53 // Callback functions to proxy to host... | |
| 54 | |
| 55 // The web contents are completely drawn and handled by WebKit, except that | |
| 56 // windowed plugins are GtkSockets on top of it. We need to place the | |
| 57 // GtkSockets inside a GtkContainer. We use a GtkFixed container, and the | |
| 58 // GtkSocket objects override a little bit to manage their size (see the code | |
| 59 // in webplugin_delegate_impl_gtk.cc). We listen on a the events we're | |
| 60 // interested in and forward them on to the WebWidgetHost. This class is a | |
| 61 // collection of static methods, implementing the widget related code. | |
| 62 class WebWidgetHostGtkWidget { | |
| 63 public: | |
| 64 // This will create a new widget used for hosting the web contents. We use | |
| 65 // our GtkDrawingAreaContainer here, for the reasons mentioned above. | |
| 66 static GtkWidget* CreateNewWidget(GtkWidget* parent_view, | |
| 67 WebWidgetHost* host) { | |
| 68 GtkWidget* widget = gtk_fixed_new(); | |
| 69 gtk_fixed_set_has_window(GTK_FIXED(widget), true); | |
| 70 | |
| 71 gtk_box_pack_start(GTK_BOX(parent_view), widget, TRUE, TRUE, 0); | |
| 72 | |
| 73 gtk_widget_add_events(widget, GDK_EXPOSURE_MASK | | |
| 74 GDK_POINTER_MOTION_MASK | | |
| 75 GDK_BUTTON_PRESS_MASK | | |
| 76 GDK_BUTTON_RELEASE_MASK | | |
| 77 GDK_KEY_PRESS_MASK | | |
| 78 GDK_KEY_RELEASE_MASK); | |
| 79 gtk_widget_set_can_focus(widget, TRUE); | |
| 80 g_signal_connect(widget, "size-request", | |
| 81 G_CALLBACK(&HandleSizeRequest), host); | |
| 82 g_signal_connect(widget, "size-allocate", | |
| 83 G_CALLBACK(&HandleSizeAllocate), host); | |
| 84 g_signal_connect(widget, "configure-event", | |
| 85 G_CALLBACK(&HandleConfigure), host); | |
| 86 g_signal_connect(widget, "expose-event", | |
| 87 G_CALLBACK(&HandleExpose), host); | |
| 88 g_signal_connect(widget, "destroy", | |
| 89 G_CALLBACK(&HandleDestroy), host); | |
| 90 g_signal_connect(widget, "key-press-event", | |
| 91 G_CALLBACK(&HandleKeyPress), host); | |
| 92 g_signal_connect(widget, "key-release-event", | |
| 93 G_CALLBACK(&HandleKeyRelease), host); | |
| 94 g_signal_connect(widget, "focus", | |
| 95 G_CALLBACK(&HandleFocus), host); | |
| 96 g_signal_connect(widget, "focus-in-event", | |
| 97 G_CALLBACK(&HandleFocusIn), host); | |
| 98 g_signal_connect(widget, "focus-out-event", | |
| 99 G_CALLBACK(&HandleFocusOut), host); | |
| 100 g_signal_connect(widget, "button-press-event", | |
| 101 G_CALLBACK(&HandleButtonPress), host); | |
| 102 g_signal_connect(widget, "button-release-event", | |
| 103 G_CALLBACK(&HandleButtonRelease), host); | |
| 104 g_signal_connect(widget, "motion-notify-event", | |
| 105 G_CALLBACK(&HandleMotionNotify), host); | |
| 106 g_signal_connect(widget, "scroll-event", | |
| 107 G_CALLBACK(&HandleScroll), host); | |
| 108 | |
| 109 g_object_set_data(G_OBJECT(widget), kWebWidgetHostKey, host); | |
| 110 return widget; | |
| 111 } | |
| 112 | |
| 113 private: | |
| 114 // Our size was requested. We let the GtkFixed do its normal calculation, | |
| 115 // after which this callback is called. The GtkFixed will come up with a | |
| 116 // requisition based on its children, which include plugin windows. Since | |
| 117 // we don't want to prevent resizing smaller than a plugin window, we need to | |
| 118 // control the size ourself. | |
| 119 static void HandleSizeRequest(GtkWidget* widget, | |
| 120 GtkRequisition* req, | |
| 121 WebWidgetHost* host) { | |
| 122 // This is arbitrary, but the WebKit scrollbars try to shrink themselves | |
| 123 // if the browser window is too small. Give them some space. | |
| 124 static const int kMinWidthHeight = 64; | |
| 125 | |
| 126 req->width = kMinWidthHeight; | |
| 127 req->height = kMinWidthHeight; | |
| 128 } | |
| 129 | |
| 130 // Our size has changed. | |
| 131 static void HandleSizeAllocate(GtkWidget* widget, | |
| 132 GtkAllocation* allocation, | |
| 133 WebWidgetHost* host) { | |
| 134 host->Resize(WebSize(allocation->width, allocation->height)); | |
| 135 } | |
| 136 | |
| 137 // Size, position, or stacking of the GdkWindow changed. | |
| 138 static gboolean HandleConfigure(GtkWidget* widget, | |
| 139 GdkEventConfigure* config, | |
| 140 WebWidgetHost* host) { | |
| 141 host->Resize(WebSize(config->width, config->height)); | |
| 142 return FALSE; | |
| 143 } | |
| 144 | |
| 145 // A portion of the GdkWindow needs to be redraw. | |
| 146 static gboolean HandleExpose(GtkWidget* widget, | |
| 147 GdkEventExpose* expose, | |
| 148 WebWidgetHost* host) { | |
| 149 // See comments above about what g_handling_expose is for. | |
| 150 g_handling_expose = true; | |
| 151 gfx::Rect rect(expose->area); | |
| 152 host->UpdatePaintRect(rect); | |
| 153 host->Paint(); | |
| 154 g_handling_expose = false; | |
| 155 return FALSE; | |
| 156 } | |
| 157 | |
| 158 // The GdkWindow was destroyed. | |
| 159 static gboolean HandleDestroy(GtkWidget* widget, void* unused) { | |
| 160 // The associated WebWidgetHost instance may have already been destroyed. | |
| 161 WebWidgetHost* host = static_cast<WebWidgetHost*>( | |
| 162 g_object_get_data(G_OBJECT(widget), kWebWidgetHostKey)); | |
| 163 if (host) | |
| 164 host->WindowDestroyed(); | |
| 165 return FALSE; | |
| 166 } | |
| 167 | |
| 168 // Keyboard key pressed. | |
| 169 static gboolean HandleKeyPress(GtkWidget* widget, | |
| 170 GdkEventKey* event, | |
| 171 WebWidgetHost* host) { | |
| 172 host->webwidget()->handleInputEvent( | |
| 173 WebInputEventFactory::keyboardEvent(event)); | |
| 174 | |
| 175 // In the browser we do a ton of work with IMEs. This is some minimal | |
| 176 // code to make basic text work in test_shell, but doesn't cover IME. | |
| 177 // This is a copy of the logic in ProcessUnfilteredKeyPressEvent in | |
| 178 // render_widget_host_view_gtk.cc . | |
| 179 if (event->type == GDK_KEY_PRESS) { | |
| 180 WebKeyboardEvent wke = WebInputEventFactory::keyboardEvent(event); | |
| 181 if (wke.text[0]) { | |
| 182 wke.type = WebKit::WebInputEvent::Char; | |
| 183 host->webwidget()->handleInputEvent(wke); | |
| 184 } | |
| 185 } | |
| 186 | |
| 187 return FALSE; | |
| 188 } | |
| 189 | |
| 190 // Keyboard key released. | |
| 191 static gboolean HandleKeyRelease(GtkWidget* widget, | |
| 192 GdkEventKey* event, | |
| 193 WebWidgetHost* host) { | |
| 194 return HandleKeyPress(widget, event, host); | |
| 195 } | |
| 196 | |
| 197 // This signal is called when arrow keys or tab is pressed. If we return | |
| 198 // true, we prevent focus from being moved to another widget. If we want to | |
| 199 // allow focus to be moved outside of web contents, we need to implement | |
| 200 // WebViewDelegate::TakeFocus in the test webview delegate. | |
| 201 static gboolean HandleFocus(GtkWidget* widget, | |
| 202 GdkEventFocus* focus, | |
| 203 WebWidgetHost* host) { | |
| 204 return TRUE; | |
| 205 } | |
| 206 | |
| 207 // Keyboard focus entered. | |
| 208 static gboolean HandleFocusIn(GtkWidget* widget, | |
| 209 GdkEventFocus* focus, | |
| 210 WebWidgetHost* host) { | |
| 211 // Ignore focus calls in layout test mode so that tests don't mess with each | |
| 212 // other's focus when running in parallel. | |
| 213 if (!TestShell::layout_test_mode()) | |
| 214 host->webwidget()->setFocus(true); | |
| 215 return TRUE; | |
| 216 } | |
| 217 | |
| 218 // Keyboard focus left. | |
| 219 static gboolean HandleFocusOut(GtkWidget* widget, | |
| 220 GdkEventFocus* focus, | |
| 221 WebWidgetHost* host) { | |
| 222 // Ignore focus calls in layout test mode so that tests don't mess with each | |
| 223 // other's focus when running in parallel. | |
| 224 if (!TestShell::layout_test_mode()) | |
| 225 host->webwidget()->setFocus(false); | |
| 226 return TRUE; | |
| 227 } | |
| 228 | |
| 229 // Mouse button down. | |
| 230 static gboolean HandleButtonPress(GtkWidget* widget, | |
| 231 GdkEventButton* event, | |
| 232 WebWidgetHost* host) { | |
| 233 if (!(event->button == 1 || event->button == 2 || event->button == 3)) | |
| 234 return FALSE; // We do not forward any other buttons to the renderer. | |
| 235 if (event->type == GDK_2BUTTON_PRESS || event->type == GDK_3BUTTON_PRESS) | |
| 236 return FALSE; | |
| 237 host->webwidget()->handleInputEvent( | |
| 238 WebInputEventFactory::mouseEvent(event)); | |
| 239 return FALSE; | |
| 240 } | |
| 241 | |
| 242 // Mouse button up. | |
| 243 static gboolean HandleButtonRelease(GtkWidget* widget, | |
| 244 GdkEventButton* event, | |
| 245 WebWidgetHost* host) { | |
| 246 return HandleButtonPress(widget, event, host); | |
| 247 } | |
| 248 | |
| 249 // Mouse pointer movements. | |
| 250 static gboolean HandleMotionNotify(GtkWidget* widget, | |
| 251 GdkEventMotion* event, | |
| 252 WebWidgetHost* host) { | |
| 253 host->webwidget()->handleInputEvent( | |
| 254 WebInputEventFactory::mouseEvent(event)); | |
| 255 return FALSE; | |
| 256 } | |
| 257 | |
| 258 // Mouse scroll wheel. | |
| 259 static gboolean HandleScroll(GtkWidget* widget, | |
| 260 GdkEventScroll* event, | |
| 261 WebWidgetHost* host) { | |
| 262 host->webwidget()->handleInputEvent( | |
| 263 WebInputEventFactory::mouseWheelEvent(event)); | |
| 264 return FALSE; | |
| 265 } | |
| 266 | |
| 267 DISALLOW_IMPLICIT_CONSTRUCTORS(WebWidgetHostGtkWidget); | |
| 268 }; | |
| 269 | |
| 270 } // namespace | |
| 271 | |
| 272 // This is provided so that the webview can reuse the custom GTK window code. | |
| 273 // static | |
| 274 gfx::NativeView WebWidgetHost::CreateWidget( | |
| 275 gfx::NativeView parent_view, WebWidgetHost* host) { | |
| 276 return WebWidgetHostGtkWidget::CreateNewWidget(parent_view, host); | |
| 277 } | |
| 278 | |
| 279 // static | |
| 280 WebWidgetHost* WebWidgetHost::Create(GtkWidget* parent_view, | |
| 281 WebWidgetClient* client) { | |
| 282 WebWidgetHost* host = new WebWidgetHost(); | |
| 283 host->view_ = CreateWidget(parent_view, host); | |
| 284 host->webwidget_ = WebPopupMenu::create(client); | |
| 285 // We manage our own double buffering because we need to be able to update | |
| 286 // the expose area in an ExposeEvent within the lifetime of the event handler. | |
| 287 gtk_widget_set_double_buffered(GTK_WIDGET(host->view_), false); | |
| 288 | |
| 289 return host; | |
| 290 } | |
| 291 | |
| 292 void WebWidgetHost::UpdatePaintRect(const gfx::Rect& rect) { | |
| 293 paint_rect_.Union(rect); | |
| 294 } | |
| 295 | |
| 296 void WebWidgetHost::DidInvalidateRect(const gfx::Rect& damaged_rect) { | |
| 297 DLOG_IF(WARNING, painting_) << "unexpected invalidation while painting"; | |
| 298 | |
| 299 UpdatePaintRect(damaged_rect); | |
| 300 | |
| 301 if (!g_handling_expose) { | |
| 302 gtk_widget_queue_draw_area(GTK_WIDGET(view_), damaged_rect.x(), | |
| 303 damaged_rect.y(), damaged_rect.width(), damaged_rect.height()); | |
| 304 } | |
| 305 } | |
| 306 | |
| 307 void WebWidgetHost::DidScrollRect(int dx, int dy, const gfx::Rect& clip_rect) { | |
| 308 // This is used for optimizing painting when the renderer is scrolled. We're | |
| 309 // currently not doing any optimizations so just invalidate the region. | |
| 310 DidInvalidateRect(clip_rect); | |
| 311 } | |
| 312 | |
| 313 void WebWidgetHost::ScheduleComposite() { | |
| 314 int width = logical_size_.width(); | |
| 315 int height = logical_size_.height(); | |
| 316 GdkRectangle grect = { | |
| 317 0, | |
| 318 0, | |
| 319 width, | |
| 320 height | |
| 321 }; | |
| 322 GdkWindow* window = view_->window; | |
| 323 gdk_window_invalidate_rect(window, &grect, 0); | |
| 324 } | |
| 325 | |
| 326 WebWidgetHost::WebWidgetHost() | |
| 327 : view_(NULL), | |
| 328 webwidget_(NULL), | |
| 329 scroll_dx_(0), | |
| 330 scroll_dy_(0), | |
| 331 weak_factory_(this) { | |
| 332 set_painting(false); | |
| 333 } | |
| 334 | |
| 335 WebWidgetHost::~WebWidgetHost() { | |
| 336 // We may be deleted before the view_. Clear out the signals so that we don't | |
| 337 // attempt to invoke something on a deleted object. | |
| 338 g_object_set_data(G_OBJECT(view_), kWebWidgetHostKey, NULL); | |
| 339 g_signal_handlers_disconnect_matched(view_, | |
| 340 G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, this); | |
| 341 webwidget_->close(); | |
| 342 } | |
| 343 | |
| 344 void WebWidgetHost::Resize(const gfx::Size &newsize) { | |
| 345 // The pixel buffer backing us is now the wrong size | |
| 346 canvas_.reset(); | |
| 347 logical_size_ = newsize; | |
| 348 webwidget_->resize(newsize); | |
| 349 } | |
| 350 | |
| 351 void WebWidgetHost::Paint() { | |
| 352 int width = logical_size_.width(); | |
| 353 int height = logical_size_.height(); | |
| 354 gfx::Rect client_rect(width, height); | |
| 355 | |
| 356 // Allocate a canvas if necessary | |
| 357 if (!canvas_.get()) { | |
| 358 ResetScrollRect(); | |
| 359 paint_rect_ = client_rect; | |
| 360 canvas_.reset(skia::CreatePlatformCanvas(width, height, true, 0, | |
| 361 skia::RETURN_NULL_ON_FAILURE)); | |
| 362 if (!canvas_.get()) { | |
| 363 // memory allocation failed, we can't paint. | |
| 364 LOG(ERROR) << "Failed to allocate memory for " << width << "x" << height; | |
| 365 return; | |
| 366 } | |
| 367 } | |
| 368 | |
| 369 webwidget_->animate(0.0); | |
| 370 | |
| 371 // This may result in more invalidation | |
| 372 webwidget_->layout(); | |
| 373 | |
| 374 // Paint the canvas if necessary. Allow painting to generate extra rects the | |
| 375 // first time we call it. This is necessary because some WebCore rendering | |
| 376 // objects update their layout only when painted. | |
| 377 // Store the total area painted in total_paint. Then tell the gdk window | |
| 378 // to update that area after we're done painting it. | |
| 379 gfx::Rect total_paint; | |
| 380 for (int i = 0; i < 2; ++i) { | |
| 381 paint_rect_.Intersect(client_rect); | |
| 382 if (!paint_rect_.IsEmpty()) { | |
| 383 gfx::Rect rect(paint_rect_); | |
| 384 paint_rect_ = gfx::Rect(); | |
| 385 | |
| 386 DLOG_IF(WARNING, i == 1) << "painting caused additional invalidations"; | |
| 387 PaintRect(rect); | |
| 388 total_paint.Union(rect); | |
| 389 } | |
| 390 } | |
| 391 //DCHECK(paint_rect_.IsEmpty()); | |
| 392 | |
| 393 // Invalidate the paint region on the widget's underlying gdk window. Note | |
| 394 // that gdk_window_invalidate_* will generate extra expose events, which | |
| 395 // we wish to avoid. So instead we use calls to begin_paint/end_paint. | |
| 396 GdkRectangle grect = { | |
| 397 total_paint.x(), | |
| 398 total_paint.y(), | |
| 399 total_paint.width(), | |
| 400 total_paint.height(), | |
| 401 }; | |
| 402 GdkWindow* window = view_->window; | |
| 403 gdk_window_begin_paint_rect(window, &grect); | |
| 404 | |
| 405 // BitBlit to the gdk window. | |
| 406 skia::ScopedPlatformPaint scoped_platform_paint(canvas_.get()); | |
| 407 cairo_t* source_surface = scoped_platform_paint.GetPlatformSurface(); | |
| 408 cairo_t* cairo_drawable = gdk_cairo_create(window); | |
| 409 cairo_set_source_surface(cairo_drawable, cairo_get_target(source_surface), | |
| 410 0, 0); | |
| 411 cairo_paint(cairo_drawable); | |
| 412 cairo_destroy(cairo_drawable); | |
| 413 | |
| 414 gdk_window_end_paint(window); | |
| 415 } | |
| 416 | |
| 417 WebScreenInfo WebWidgetHost::GetScreenInfo() { | |
| 418 Display* display = test_shell_x11::GtkWidgetGetDisplay(view_); | |
| 419 int screen_num = test_shell_x11::GtkWidgetGetScreenNum(view_); | |
| 420 return WebScreenInfoFactory::screenInfo(display, screen_num); | |
| 421 } | |
| 422 | |
| 423 void WebWidgetHost::ResetScrollRect() { | |
| 424 // This method is only needed for optimized scroll painting, which we don't | |
| 425 // care about in the test shell, yet. | |
| 426 } | |
| 427 | |
| 428 void WebWidgetHost::PaintRect(const gfx::Rect& rect) { | |
| 429 set_painting(true); | |
| 430 webwidget_->paint(canvas_.get(), rect); | |
| 431 set_painting(false); | |
| 432 } | |
| 433 | |
| 434 void WebWidgetHost::WindowDestroyed() { | |
| 435 delete this; | |
| 436 } | |
| OLD | NEW |