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 |