41c840924189d7c8c7028d0247ef8f92ab8b19fd
[WebKit-https.git] / Source / WebKit / gtk / WebCoreSupport / ChromeClientGtk.cpp
1 /*
2  * Copyright (C) 2007, 2008 Holger Hans Peter Freyther
3  * Copyright (C) 2007, 2008 Christian Dywan <christian@imendio.com>
4  * Copyright (C) 2008 Nuanti Ltd.
5  * Copyright (C) 2008 Alp Toker <alp@atoker.com>
6  * Copyright (C) 2008 Gustavo Noronha Silva <gns@gnome.org>
7  * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
8  * Copyright (C) 2012 Igalia S. L.
9  *
10  *  This library is free software; you can redistribute it and/or
11  *  modify it under the terms of the GNU Lesser General Public
12  *  License as published by the Free Software Foundation; either
13  *  version 2 of the License, or (at your option) any later version.
14  *
15  *  This library is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  *  Lesser General Public License for more details.
19  *
20  *  You should have received a copy of the GNU Lesser General Public
21  *  License along with this library; if not, write to the Free Software
22  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23  */
24
25 #include "config.h"
26 #include "ChromeClientGtk.h"
27
28 #include "Chrome.h"
29 #include "Console.h"
30 #include "DumpRenderTreeSupportGtk.h"
31 #include "Element.h"
32 #include "FileChooser.h"
33 #include "FileIconLoader.h"
34 #include "FileSystem.h"
35 #include "FloatRect.h"
36 #include "FocusController.h"
37 #include "FrameLoadRequest.h"
38 #include "FrameView.h"
39 #include "GtkUtilities.h"
40 #include "GtkVersioning.h"
41 #include "HTMLNames.h"
42 #include "HitTestResult.h"
43 #include "Icon.h"
44 #include "InspectorController.h"
45 #include "IntRect.h"
46 #include "KURL.h"
47 #include "NavigationAction.h"
48 #include "NotImplemented.h"
49 #include "PlatformString.h"
50 #include "PopupMenuClient.h"
51 #include "PopupMenuGtk.h"
52 #include "RefPtrCairo.h"
53 #include "SearchPopupMenuGtk.h"
54 #include "SecurityOrigin.h"
55 #include "WebKitDOMBinding.h"
56 #include "WebKitDOMHTMLElementPrivate.h"
57 #include "WindowFeatures.h"
58 #include "webkitfilechooserrequestprivate.h"
59 #include "webkitgeolocationpolicydecision.h"
60 #include "webkitgeolocationpolicydecisionprivate.h"
61 #include "webkitnetworkrequest.h"
62 #include "webkitsecurityoriginprivate.h"
63 #include "webkitviewportattributesprivate.h"
64 #include "webkitwebframeprivate.h"
65 #include "webkitwebview.h"
66 #include "webkitwebviewprivate.h"
67 #include "webkitwebwindowfeaturesprivate.h"
68 #include <gdk/gdk.h>
69 #include <gdk/gdkkeysyms.h>
70 #include <glib.h>
71 #include <glib/gi18n-lib.h>
72 #include <gtk/gtk.h>
73 #include <wtf/MathExtras.h>
74 #include <wtf/text/CString.h>
75
76 #if ENABLE(SQL_DATABASE)
77 #include "DatabaseTracker.h"
78 #endif
79
80 using namespace WebCore;
81
82 namespace WebKit {
83
84 ChromeClient::ChromeClient(WebKitWebView* webView)
85     : m_webView(webView)
86     , m_adjustmentWatcher(webView)
87     , m_closeSoonTimer(0)
88     , m_displayTimer(this, &ChromeClient::paint)
89     , m_forcePaint(false)
90     , m_lastDisplayTime(0)
91     , m_repaintSoonSourceId(0)
92 {
93     ASSERT(m_webView);
94 }
95
96 void ChromeClient::chromeDestroyed()
97 {
98     if (m_closeSoonTimer)
99         g_source_remove(m_closeSoonTimer);
100
101     if (m_repaintSoonSourceId)
102         g_source_remove(m_repaintSoonSourceId);
103
104     delete this;
105 }
106
107 FloatRect ChromeClient::windowRect()
108 {
109     GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(m_webView));
110     if (widgetIsOnscreenToplevelWindow(window)) {
111         gint left, top, width, height;
112         gtk_window_get_position(GTK_WINDOW(window), &left, &top);
113         gtk_window_get_size(GTK_WINDOW(window), &width, &height);
114         return IntRect(left, top, width, height);
115     }
116     return FloatRect();
117 }
118
119 void ChromeClient::setWindowRect(const FloatRect& rect)
120 {
121     IntRect intrect = IntRect(rect);
122     WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
123
124     g_object_set(webWindowFeatures,
125                  "x", intrect.x(),
126                  "y", intrect.y(),
127                  "width", intrect.width(),
128                  "height", intrect.height(),
129                  NULL);
130
131     gboolean autoResizeWindow;
132     WebKitWebSettings* settings = webkit_web_view_get_settings(m_webView);
133     g_object_get(settings, "auto-resize-window", &autoResizeWindow, NULL);
134
135     if (!autoResizeWindow)
136         return;
137
138     GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(m_webView));
139     if (widgetIsOnscreenToplevelWindow(window)) {
140         gtk_window_move(GTK_WINDOW(window), intrect.x(), intrect.y());
141         gtk_window_resize(GTK_WINDOW(window), intrect.width(), intrect.height());
142     }
143 }
144
145 FloatRect ChromeClient::pageRect()
146 {
147     GtkAllocation allocation;
148 #if GTK_CHECK_VERSION(2, 18, 0)
149     gtk_widget_get_allocation(GTK_WIDGET(m_webView), &allocation);
150 #else
151     allocation = GTK_WIDGET(m_webView)->allocation;
152 #endif
153     return IntRect(allocation.x, allocation.y, allocation.width, allocation.height);
154 }
155
156 void ChromeClient::focus()
157 {
158     gtk_widget_grab_focus(GTK_WIDGET(m_webView));
159 }
160
161 void ChromeClient::unfocus()
162 {
163     GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(m_webView));
164     if (widgetIsOnscreenToplevelWindow(window))
165         gtk_window_set_focus(GTK_WINDOW(window), NULL);
166 }
167
168 Page* ChromeClient::createWindow(Frame* frame, const FrameLoadRequest& frameLoadRequest, const WindowFeatures& coreFeatures, const NavigationAction&)
169 {
170     WebKitWebView* webView = 0;
171
172     g_signal_emit_by_name(m_webView, "create-web-view", kit(frame), &webView);
173
174     if (!webView)
175         return 0;
176
177     GRefPtr<WebKitWebWindowFeatures> webWindowFeatures(adoptGRef(kitNew(coreFeatures)));
178     g_object_set(webView, "window-features", webWindowFeatures.get(), NULL);
179
180     return core(webView);
181 }
182
183 void ChromeClient::show()
184 {
185     webkit_web_view_notify_ready(m_webView);
186 }
187
188 bool ChromeClient::canRunModal()
189 {
190     notImplemented();
191     return false;
192 }
193
194 void ChromeClient::runModal()
195 {
196     notImplemented();
197 }
198
199 void ChromeClient::setToolbarsVisible(bool visible)
200 {
201     WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
202
203     g_object_set(webWindowFeatures, "toolbar-visible", visible, NULL);
204 }
205
206 bool ChromeClient::toolbarsVisible()
207 {
208     WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
209     gboolean visible;
210
211     g_object_get(webWindowFeatures, "toolbar-visible", &visible, NULL);
212     return visible;
213 }
214
215 void ChromeClient::setStatusbarVisible(bool visible)
216 {
217     WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
218
219     g_object_set(webWindowFeatures, "statusbar-visible", visible, NULL);
220 }
221
222 bool ChromeClient::statusbarVisible()
223 {
224     WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
225     gboolean visible;
226
227     g_object_get(webWindowFeatures, "statusbar-visible", &visible, NULL);
228     return visible;
229 }
230
231 void ChromeClient::setScrollbarsVisible(bool visible)
232 {
233     WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
234
235     g_object_set(webWindowFeatures, "scrollbar-visible", visible, NULL);
236 }
237
238 bool ChromeClient::scrollbarsVisible()
239 {
240     WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
241     gboolean visible;
242
243     g_object_get(webWindowFeatures, "scrollbar-visible", &visible, NULL);
244     return visible;
245 }
246
247 void ChromeClient::setMenubarVisible(bool visible)
248 {
249     WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
250
251     g_object_set(webWindowFeatures, "menubar-visible", visible, NULL);
252 }
253
254 bool ChromeClient::menubarVisible()
255 {
256     WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
257     gboolean visible;
258
259     g_object_get(webWindowFeatures, "menubar-visible", &visible, NULL);
260     return visible;
261 }
262
263 void ChromeClient::setResizable(bool)
264 {
265     // Ignored for now
266 }
267
268 static gboolean emitCloseWebViewSignalLater(WebKitWebView* view)
269 {
270     gboolean isHandled;
271     g_signal_emit_by_name(view, "close-web-view", &isHandled);
272     return FALSE;
273 }
274
275 void ChromeClient::closeWindowSoon()
276 {
277     // We may not have a WebView as create-web-view can return NULL.
278     if (!m_webView)
279         return;
280     if (m_closeSoonTimer) // Don't call close-web-view more than once.
281         return;
282
283     // We need to remove the parent WebView from WebViewSets here, before it actually
284     // closes, to make sure that JavaScript code that executes before it closes
285     // can't find it. Otherwise, window.open will select a closed WebView instead of 
286     // opening a new one <rdar://problem/3572585>.
287     m_webView->priv->corePage->setGroupName("");
288
289     // We also need to stop the load to prevent further parsing or JavaScript execution
290     // after the window has torn down <rdar://problem/4161660>.
291     webkit_web_view_stop_loading(m_webView);
292
293     // Clients commonly destroy the web view during the close-web-view signal, but our caller
294     // may need to send more signals to the web view. For instance, if this happened in the
295     // onload handler, it will need to call FrameLoaderClient::dispatchDidHandleOnloadEvents.
296     // Instead of firing the close-web-view signal now, fire it after the caller finishes.
297     // This seems to match the Mac/Windows port behavior.
298     m_closeSoonTimer = g_timeout_add(0, reinterpret_cast<GSourceFunc>(emitCloseWebViewSignalLater), m_webView);
299 }
300
301 bool ChromeClient::canTakeFocus(FocusDirection)
302 {
303     return gtk_widget_get_can_focus(GTK_WIDGET(m_webView));
304 }
305
306 void ChromeClient::takeFocus(FocusDirection)
307 {
308     unfocus();
309 }
310
311 void ChromeClient::focusedNodeChanged(Node*)
312 {
313 }
314
315 void ChromeClient::focusedFrameChanged(Frame*)
316 {
317 }
318
319 bool ChromeClient::canRunBeforeUnloadConfirmPanel()
320 {
321     return true;
322 }
323
324 bool ChromeClient::runBeforeUnloadConfirmPanel(const WTF::String& message, WebCore::Frame* frame)
325 {
326     return runJavaScriptConfirm(frame, message);
327 }
328
329 void ChromeClient::addMessageToConsole(WebCore::MessageSource source, WebCore::MessageType type, WebCore::MessageLevel level, const WTF::String& message, unsigned int lineNumber, const WTF::String& sourceId)
330 {
331     gboolean retval;
332     g_signal_emit_by_name(m_webView, "console-message", message.utf8().data(), lineNumber, sourceId.utf8().data(), &retval);
333 }
334
335 void ChromeClient::runJavaScriptAlert(Frame* frame, const String& message)
336 {
337     gboolean retval;
338     g_signal_emit_by_name(m_webView, "script-alert", kit(frame), message.utf8().data(), &retval);
339 }
340
341 bool ChromeClient::runJavaScriptConfirm(Frame* frame, const String& message)
342 {
343     gboolean retval;
344     gboolean didConfirm;
345     g_signal_emit_by_name(m_webView, "script-confirm", kit(frame), message.utf8().data(), &didConfirm, &retval);
346     return didConfirm == TRUE;
347 }
348
349 bool ChromeClient::runJavaScriptPrompt(Frame* frame, const String& message, const String& defaultValue, String& result)
350 {
351     gboolean retval;
352     gchar* value = 0;
353     g_signal_emit_by_name(m_webView, "script-prompt", kit(frame), message.utf8().data(), defaultValue.utf8().data(), &value, &retval);
354     if (value) {
355         result = String::fromUTF8(value);
356         g_free(value);
357         return true;
358     }
359     return false;
360 }
361
362 void ChromeClient::setStatusbarText(const String& string)
363 {
364     CString stringMessage = string.utf8();
365     g_signal_emit_by_name(m_webView, "status-bar-text-changed", stringMessage.data());
366 }
367
368 bool ChromeClient::shouldInterruptJavaScript()
369 {
370     notImplemented();
371     return false;
372 }
373
374 KeyboardUIMode ChromeClient::keyboardUIMode()
375 {
376     bool tabsToLinks = true;
377     if (DumpRenderTreeSupportGtk::dumpRenderTreeModeEnabled())
378         tabsToLinks = DumpRenderTreeSupportGtk::linksIncludedInFocusChain();
379
380     return tabsToLinks ? KeyboardAccessTabsToLinks : KeyboardAccessDefault;
381 }
382
383 IntRect ChromeClient::windowResizerRect() const
384 {
385     notImplemented();
386     return IntRect();
387 }
388
389 static gboolean repaintEverythingSoonTimeout(ChromeClient* client)
390 {
391     client->paint(0);
392     return FALSE;
393 }
394
395 static void clipOutOldWidgetArea(cairo_t* cr, const IntSize& oldSize, const IntSize& newSize)
396 {
397     cairo_move_to(cr, oldSize.width(), 0);
398     cairo_line_to(cr, newSize.width(), 0);
399     cairo_line_to(cr, newSize.width(), newSize.height());
400     cairo_line_to(cr, 0, newSize.height());
401     cairo_line_to(cr, 0, oldSize.height());
402     cairo_line_to(cr, oldSize.width(), oldSize.height());
403     cairo_close_path(cr);
404     cairo_clip(cr);
405 }
406
407 static void clearEverywhereInBackingStore(WebKitWebView* webView, cairo_t* cr)
408 {
409     // The strategy here is to quickly draw white into this new canvas, so that
410     // when a user quickly resizes the WebView in an environment that has opaque
411     // resizing (like Gnome Shell), there are no drawing artifacts.
412     if (!webView->priv->transparent) {
413         cairo_set_source_rgb(cr, 1, 1, 1);
414         cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
415     } else
416         cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
417     cairo_paint(cr);
418 }
419
420 void ChromeClient::widgetSizeChanged(const IntSize& oldWidgetSize, IntSize newSize)
421 {
422     WidgetBackingStore* backingStore = m_webView->priv->backingStore.get();
423
424     // Grow the backing store by at least 1.5 times the current size. This prevents
425     // lots of unnecessary allocations during an opaque resize.
426     if (backingStore) {
427         const IntSize& oldSize = backingStore->size();
428         if (newSize.width() > oldSize.width())
429             newSize.setWidth(std::max(newSize.width(), static_cast<int>(oldSize.width() * 1.5)));
430         if (newSize.height() > oldSize.height())
431             newSize.setHeight(std::max(newSize.height(), static_cast<int>(oldSize.height() * 1.5)));
432     }
433
434     // If we did not have a backing store before or if the backing store is growing, we need
435     // to reallocate a new one and set it up so that we don't see artifacts while resizing.
436     if (!backingStore
437         || newSize.width() > backingStore->size().width()
438         || newSize.height() > backingStore->size().height()) {
439
440         OwnPtr<WidgetBackingStore> newBackingStore =
441             WebCore::WidgetBackingStore::create(GTK_WIDGET(m_webView), newSize);
442         RefPtr<cairo_t> cr = adoptRef(cairo_create(newBackingStore->cairoSurface()));
443
444         clearEverywhereInBackingStore(m_webView, cr.get());
445
446         // Now we copy the old backing store image over the new cleared surface to prevent
447         // annoying flashing as the widget grows. We do the "real" paint in a timeout
448         // since we don't want to block resizing too long.
449         if (backingStore) {
450             cairo_set_source_surface(cr.get(), backingStore->cairoSurface(), 0, 0);
451             cairo_rectangle(cr.get(), 0, 0, backingStore->size().width(), backingStore->size().height());
452             cairo_fill(cr.get());
453         }
454
455         m_webView->priv->backingStore = newBackingStore.release();
456         backingStore = m_webView->priv->backingStore.get();
457
458     } else if (oldWidgetSize.width() < newSize.width() || oldWidgetSize.height() < newSize.height()) {
459         // The widget is growing, but we did not need to create a new backing store.
460         // We should clear any old data outside of the old widget region.
461         RefPtr<cairo_t> cr = adoptRef(cairo_create(backingStore->cairoSurface()));
462         clipOutOldWidgetArea(cr.get(), oldWidgetSize, newSize);
463         clearEverywhereInBackingStore(m_webView, cr.get());
464     }
465
466     // We need to force a redraw and ignore the framerate cap.
467     m_lastDisplayTime = 0;
468     m_dirtyRegion.unite(IntRect(IntPoint(), backingStore->size()));
469
470     // WebCore timers by default have a lower priority which leads to more artifacts when opaque
471     // resize is on, thus we use g_timeout_add here to force a higher timeout priority.
472     if (!m_repaintSoonSourceId)
473         m_repaintSoonSourceId = g_timeout_add(0, reinterpret_cast<GSourceFunc>(repaintEverythingSoonTimeout), this);
474 }
475
476 static void coalesceRectsIfPossible(const IntRect& clipRect, Vector<IntRect>& rects)
477 {
478     const unsigned int cRectThreshold = 10;
479     const float cWastedSpaceThreshold = 0.75f;
480     bool useUnionedRect = (rects.size() <= 1) || (rects.size() > cRectThreshold);
481     if (!useUnionedRect) {
482         // Attempt to guess whether or not we should use the unioned rect or the individual rects.
483         // We do this by computing the percentage of "wasted space" in the union. If that wasted space
484         // is too large, then we will do individual rect painting instead.
485         float unionPixels = (clipRect.width() * clipRect.height());
486         float singlePixels = 0;
487         for (size_t i = 0; i < rects.size(); ++i)
488             singlePixels += rects[i].width() * rects[i].height();
489         float wastedSpace = 1 - (singlePixels / unionPixels);
490         if (wastedSpace <= cWastedSpaceThreshold)
491             useUnionedRect = true;
492     }
493
494     if (!useUnionedRect)
495         return;
496
497     rects.clear();
498     rects.append(clipRect);
499 }
500
501 static void paintWebView(WebKitWebView* webView, Frame* frame, Region dirtyRegion)
502 {
503     if (!webView->priv->backingStore)
504         return;
505
506     Vector<IntRect> rects = dirtyRegion.rects();
507     coalesceRectsIfPossible(dirtyRegion.bounds(), rects);
508
509     RefPtr<cairo_t> backingStoreContext = adoptRef(cairo_create(webView->priv->backingStore->cairoSurface()));
510     GraphicsContext gc(backingStoreContext.get());
511     gc.applyDeviceScaleFactor(frame->page()->deviceScaleFactor());
512     for (size_t i = 0; i < rects.size(); i++) {
513         const IntRect& rect = rects[i];
514
515         gc.save();
516         gc.clip(rect);
517         if (webView->priv->transparent)
518             gc.clearRect(rect);
519         frame->view()->paint(&gc, rect);
520         gc.restore();
521     }
522
523     gc.save();
524     gc.clip(dirtyRegion.bounds());
525     frame->page()->inspectorController()->drawHighlight(gc);
526     gc.restore();
527 }
528
529 void ChromeClient::invalidateWidgetRect(const IntRect& rect)
530 {
531 #if USE(ACCELERATED_COMPOSITING)
532     AcceleratedCompositingContext* acContext = m_webView->priv->acceleratedCompositingContext.get();
533     if (acContext->enabled()) {
534         acContext->scheduleRootLayerRepaint(rect);
535         return;
536     }
537 #endif
538     gtk_widget_queue_draw_area(GTK_WIDGET(m_webView),
539                                rect.x(), rect.y(),
540                                rect.width(), rect.height());
541 }
542
543 void ChromeClient::performAllPendingScrolls()
544 {
545     if (!m_webView->priv->backingStore)
546         return;
547
548     // Scroll all pending scroll rects and invalidate those parts of the widget.
549     for (size_t i = 0; i < m_rectsToScroll.size(); i++) {
550         IntRect& scrollRect = m_rectsToScroll[i];
551         m_webView->priv->backingStore->scroll(scrollRect, m_scrollOffsets[i]);
552         invalidateWidgetRect(scrollRect);
553     }
554
555     m_rectsToScroll.clear();
556     m_scrollOffsets.clear();
557 }
558
559 void ChromeClient::paint(WebCore::Timer<ChromeClient>*)
560 {
561     static const double minimumFrameInterval = 1.0 / 60.0; // No more than 60 frames a second.
562     double timeSinceLastDisplay = currentTime() - m_lastDisplayTime;
563     double timeUntilNextDisplay = minimumFrameInterval - timeSinceLastDisplay;
564
565     if (timeUntilNextDisplay > 0 && !m_forcePaint) {
566         m_displayTimer.startOneShot(timeUntilNextDisplay);
567         return;
568     }
569
570     Frame* frame = core(m_webView)->mainFrame();
571     if (!frame || !frame->contentRenderer() || !frame->view())
572         return;
573
574     frame->view()->updateLayoutAndStyleIfNeededRecursive();
575     performAllPendingScrolls();
576     paintWebView(m_webView, frame, m_dirtyRegion);
577
578     HashSet<GtkWidget*> children = m_webView->priv->children;
579     HashSet<GtkWidget*>::const_iterator end = children.end();
580     for (HashSet<GtkWidget*>::const_iterator current = children.begin(); current != end; ++current) {
581         if (static_cast<GtkAllocation*>(g_object_get_data(G_OBJECT(*current), "delayed-allocation"))) {
582             gtk_widget_queue_resize_no_redraw(GTK_WIDGET(m_webView));
583             break;
584         }
585     }
586
587     const IntRect& rect = m_dirtyRegion.bounds();
588     invalidateWidgetRect(rect);
589
590 #if USE(ACCELERATED_COMPOSITING)
591     m_webView->priv->acceleratedCompositingContext->syncLayersNow();
592     m_webView->priv->acceleratedCompositingContext->renderLayersToWindow(0, rect);
593 #endif
594
595     m_dirtyRegion = Region();
596     m_lastDisplayTime = currentTime();
597     m_repaintSoonSourceId = 0;
598
599     // We update the IM context window location here, because we want it to be
600     // synced with cursor movement. For instance, a text field can move without
601     // the selection changing.
602     Frame* focusedFrame = core(m_webView)->focusController()->focusedOrMainFrame();
603     if (focusedFrame && focusedFrame->editor()->canEdit())
604         m_webView->priv->imFilter.setCursorRect(frame->selection()->absoluteCaretBounds());
605 }
606
607 void ChromeClient::forcePaint()
608 {
609     m_forcePaint = true;
610     paint(0);
611     m_forcePaint = false;
612 }
613
614 void ChromeClient::invalidateRootView(const IntRect&, bool immediate)
615 {
616 }
617
618 void ChromeClient::invalidateContentsAndRootView(const IntRect& updateRect, bool immediate)
619 {
620     if (updateRect.isEmpty())
621         return;
622     m_dirtyRegion.unite(updateRect);
623     m_displayTimer.startOneShot(0);
624 }
625
626 void ChromeClient::invalidateContentsForSlowScroll(const IntRect& updateRect, bool immediate)
627 {
628     invalidateContentsAndRootView(updateRect, immediate);
629     m_adjustmentWatcher.updateAdjustmentsFromScrollbarsLater();
630 }
631
632 void ChromeClient::scroll(const IntSize& delta, const IntRect& rectToScroll, const IntRect& clipRect)
633 {
634     m_rectsToScroll.append(rectToScroll);
635     m_scrollOffsets.append(delta);
636
637     // The code to calculate the scroll repaint region is originally from WebKit2.
638     // Get the part of the dirty region that is in the scroll rect.
639     Region dirtyRegionInScrollRect = intersect(rectToScroll, m_dirtyRegion);
640     if (!dirtyRegionInScrollRect.isEmpty()) {
641         // There are parts of the dirty region that are inside the scroll rect.
642         // We need to subtract them from the region, move them and re-add them.
643         m_dirtyRegion.subtract(rectToScroll);
644
645         // Move the dirty parts.
646         Region movedDirtyRegionInScrollRect = intersect(translate(dirtyRegionInScrollRect, delta), rectToScroll);
647
648         // And add them back.
649         m_dirtyRegion.unite(movedDirtyRegionInScrollRect);
650     }
651
652     // Compute the scroll repaint region. We ensure that we are not subtracting areas
653     // that we've scrolled from outside the viewport from the repaint region.
654     IntRect onScreenScrollRect = rectToScroll;
655     onScreenScrollRect.intersect(IntRect(IntPoint(), enclosingIntRect(pageRect()).size()));
656     Region scrollRepaintRegion = subtract(rectToScroll, translate(onScreenScrollRect, delta));
657
658     m_dirtyRegion.unite(scrollRepaintRegion);
659     m_displayTimer.startOneShot(0);
660
661     m_adjustmentWatcher.updateAdjustmentsFromScrollbarsLater();
662 }
663
664 IntRect ChromeClient::rootViewToScreen(const IntRect& rect) const
665 {
666     return IntRect(convertWidgetPointToScreenPoint(GTK_WIDGET(m_webView), rect.location()), rect.size());
667 }
668
669 IntPoint ChromeClient::screenToRootView(const IntPoint& point) const
670 {
671     IntPoint widgetPositionOnScreen = convertWidgetPointToScreenPoint(GTK_WIDGET(m_webView), IntPoint());
672     IntPoint result(point);
673     result.move(-widgetPositionOnScreen.x(), -widgetPositionOnScreen.y());
674     return result;
675 }
676
677 PlatformPageClient ChromeClient::platformPageClient() const
678 {
679     return GTK_WIDGET(m_webView);
680 }
681
682 void ChromeClient::contentsSizeChanged(Frame* frame, const IntSize& size) const
683 {
684     if (m_adjustmentWatcher.scrollbarsDisabled())
685         return;
686
687     // We need to queue a resize request only if the size changed,
688     // otherwise we get into an infinite loop!
689     GtkWidget* widget = GTK_WIDGET(m_webView);
690     GtkRequisition requisition;
691 #if GTK_CHECK_VERSION(2, 20, 0)
692     gtk_widget_get_requisition(widget, &requisition);
693 #else
694     requisition = widget->requisition;
695 #endif
696     if (gtk_widget_get_realized(widget)
697         && (requisition.height != size.height())
698         || (requisition.width != size.width()))
699         gtk_widget_queue_resize_no_redraw(widget);
700
701     // If this was a main frame size change, update the scrollbars.
702     if (frame != frame->page()->mainFrame())
703         return;
704     m_adjustmentWatcher.updateAdjustmentsFromScrollbarsLater();
705 }
706
707 void ChromeClient::scrollbarsModeDidChange() const
708 {
709     WebKitWebFrame* webFrame = webkit_web_view_get_main_frame(m_webView);
710     if (!webFrame)
711         return;
712
713     g_object_notify(G_OBJECT(webFrame), "horizontal-scrollbar-policy");
714     g_object_notify(G_OBJECT(webFrame), "vertical-scrollbar-policy");
715
716     gboolean isHandled;
717     g_signal_emit_by_name(webFrame, "scrollbars-policy-changed", &isHandled);
718
719     if (isHandled)
720         return;
721
722     GtkWidget* parent = gtk_widget_get_parent(GTK_WIDGET(m_webView));
723     if (!parent || !GTK_IS_SCROLLED_WINDOW(parent))
724         return;
725
726     GtkPolicyType horizontalPolicy = webkit_web_frame_get_horizontal_scrollbar_policy(webFrame);
727     GtkPolicyType verticalPolicy = webkit_web_frame_get_vertical_scrollbar_policy(webFrame);
728
729     // ScrolledWindow doesn't like to display only part of a widget if
730     // the scrollbars are completely disabled; We have a disparity
731     // here on what the policy requested by the web app is and what we
732     // can represent; the idea is not to show scrollbars, only.
733     if (horizontalPolicy == GTK_POLICY_NEVER)
734         horizontalPolicy = GTK_POLICY_AUTOMATIC;
735
736     if (verticalPolicy == GTK_POLICY_NEVER)
737         verticalPolicy = GTK_POLICY_AUTOMATIC;
738
739     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(parent),
740                                    horizontalPolicy, verticalPolicy);
741 }
742
743 void ChromeClient::mouseDidMoveOverElement(const HitTestResult& hit, unsigned modifierFlags)
744 {
745     // check if the element is a link...
746     bool isLink = hit.isLiveLink();
747     if (isLink) {
748         KURL url = hit.absoluteLinkURL();
749         if (!url.isEmpty() && url != m_hoveredLinkURL) {
750             TextDirection dir;
751             CString titleString = hit.title(dir).utf8();
752             CString urlString = url.string().utf8();
753             g_signal_emit_by_name(m_webView, "hovering-over-link", titleString.data(), urlString.data());
754             m_hoveredLinkURL = url;
755         }
756     } else if (!isLink && !m_hoveredLinkURL.isEmpty()) {
757         g_signal_emit_by_name(m_webView, "hovering-over-link", 0, 0);
758         m_hoveredLinkURL = KURL();
759     }
760
761     if (Node* node = hit.innerNonSharedNode()) {
762         Frame* frame = node->document()->frame();
763         FrameView* view = frame ? frame->view() : 0;
764         m_webView->priv->tooltipArea = view ? view->contentsToWindow(node->getPixelSnappedRect()) : IntRect();
765     } else
766         m_webView->priv->tooltipArea = IntRect();
767 }
768
769 void ChromeClient::setToolTip(const String& toolTip, TextDirection)
770 {
771     webkit_web_view_set_tooltip_text(m_webView, toolTip.utf8().data());
772 }
773
774 void ChromeClient::print(Frame* frame)
775 {
776     WebKitWebFrame* webFrame = kit(frame);
777     gboolean isHandled = false;
778     g_signal_emit_by_name(m_webView, "print-requested", webFrame, &isHandled);
779
780     if (isHandled)
781         return;
782
783     webkit_web_frame_print(webFrame);
784 }
785
786 #if ENABLE(SQL_DATABASE)
787 void ChromeClient::exceededDatabaseQuota(Frame* frame, const String& databaseName)
788 {
789     guint64 defaultQuota = webkit_get_default_web_database_quota();
790     DatabaseTracker::tracker().setQuota(frame->document()->securityOrigin(), defaultQuota);
791
792     WebKitWebFrame* webFrame = kit(frame);
793     WebKitSecurityOrigin* origin = webkit_web_frame_get_security_origin(webFrame);
794     WebKitWebDatabase* webDatabase = webkit_security_origin_get_web_database(origin, databaseName.utf8().data());
795     g_signal_emit_by_name(m_webView, "database-quota-exceeded", webFrame, webDatabase);
796 }
797 #endif
798
799 void ChromeClient::reachedMaxAppCacheSize(int64_t spaceNeeded)
800 {
801     // FIXME: Free some space.
802     notImplemented();
803 }
804
805 void ChromeClient::reachedApplicationCacheOriginQuota(SecurityOrigin*, int64_t)
806 {
807     notImplemented();
808 }
809
810 void ChromeClient::runOpenPanel(Frame*, PassRefPtr<FileChooser> prpFileChooser)
811 {
812     GRefPtr<WebKitFileChooserRequest> request = adoptGRef(webkit_file_chooser_request_create(prpFileChooser));
813     webkitWebViewRunFileChooserRequest(m_webView, request.get());
814 }
815
816 void ChromeClient::loadIconForFiles(const Vector<WTF::String>& filenames, WebCore::FileIconLoader* loader)
817 {
818     loader->notifyFinished(Icon::createIconForFiles(filenames));
819 }
820
821 void ChromeClient::dispatchViewportPropertiesDidChange(const ViewportArguments& arguments) const
822 {
823     // Recompute the viewport attributes making it valid.
824     webkitViewportAttributesRecompute(webkit_web_view_get_viewport_attributes(m_webView));
825 }
826
827 void ChromeClient::setCursor(const Cursor& cursor)
828 {
829     // [GTK] Widget::setCursor() gets called frequently
830     // http://bugs.webkit.org/show_bug.cgi?id=16388
831     // Setting the cursor may be an expensive operation in some backends,
832     // so don't re-set the cursor if it's already set to the target value.
833     GdkWindow* window = gtk_widget_get_window(platformPageClient());
834     if (!window)
835         return;
836
837     GdkCursor* currentCursor = gdk_window_get_cursor(window);
838     GdkCursor* newCursor = cursor.platformCursor().get();
839     if (currentCursor != newCursor)
840         gdk_window_set_cursor(window, newCursor);
841 }
842
843 void ChromeClient::setCursorHiddenUntilMouseMoves(bool)
844 {
845     notImplemented();
846 }
847
848 bool ChromeClient::selectItemWritingDirectionIsNatural()
849 {
850     return false;
851 }
852
853 bool ChromeClient::selectItemAlignmentFollowsMenuWritingDirection()
854 {
855     return true;
856 }
857
858 bool ChromeClient::hasOpenedPopup() const
859 {
860     notImplemented();
861     return false;
862 }
863
864 PassRefPtr<WebCore::PopupMenu> ChromeClient::createPopupMenu(WebCore::PopupMenuClient* client) const
865 {
866     return adoptRef(new PopupMenuGtk(client));
867 }
868
869 PassRefPtr<WebCore::SearchPopupMenu> ChromeClient::createSearchPopupMenu(WebCore::PopupMenuClient* client) const
870 {
871     return adoptRef(new SearchPopupMenuGtk(client));
872 }
873
874 #if ENABLE(VIDEO)
875
876 bool ChromeClient::supportsFullscreenForNode(const Node* node)
877 {
878     return node->hasTagName(HTMLNames::videoTag);
879 }
880
881 void ChromeClient::enterFullscreenForNode(Node* node)
882 {
883     webViewEnterFullscreen(m_webView, node);
884 }
885
886 void ChromeClient::exitFullscreenForNode(Node* node)
887 {
888     webViewExitFullscreen(m_webView);
889 }
890 #endif
891
892 #if ENABLE(FULLSCREEN_API)
893 bool ChromeClient::supportsFullScreenForElement(const WebCore::Element* element, bool withKeyboard)
894 {
895     return !withKeyboard;
896 }
897
898 static gboolean onFullscreenGtkKeyPressEvent(GtkWidget* widget, GdkEventKey* event, ChromeClient* chromeClient)
899 {
900     switch (event->keyval) {
901     case GDK_KEY_Escape:
902     case GDK_KEY_f:
903     case GDK_KEY_F:
904         chromeClient->cancelFullScreen();
905         return TRUE;
906     default:
907         break;
908     }
909
910     return FALSE;
911 }
912
913 void ChromeClient::cancelFullScreen()
914 {
915     ASSERT(m_fullScreenElement);
916     m_fullScreenElement->document()->webkitCancelFullScreen();
917 }
918
919 void ChromeClient::enterFullScreenForElement(WebCore::Element* element)
920 {
921     gboolean returnValue;
922     GRefPtr<WebKitDOMHTMLElement> kitElement(adoptGRef(kit(reinterpret_cast<HTMLElement*>(element))));
923     g_signal_emit_by_name(m_webView, "entering-fullscreen", kitElement.get(), &returnValue);
924     if (returnValue)
925         return;
926
927     GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(m_webView));
928     if (!widgetIsOnscreenToplevelWindow(window))
929         return;
930
931     g_signal_connect(window, "key-press-event", G_CALLBACK(onFullscreenGtkKeyPressEvent), this);
932
933     m_fullScreenElement = element;
934
935     element->document()->webkitWillEnterFullScreenForElement(element);
936     m_adjustmentWatcher.disableAllScrollbars();
937     gtk_window_fullscreen(GTK_WINDOW(window));
938     element->document()->webkitDidEnterFullScreenForElement(element);
939 }
940
941 void ChromeClient::exitFullScreenForElement(WebCore::Element*)
942 {
943     // The element passed into this function is not reliable, i.e. it could
944     // be null. In addition the parameter may be disappearing in the future.
945     // So we use the reference to the element we saved above.
946     ASSERT(m_fullScreenElement);
947
948     gboolean returnValue;
949     GRefPtr<WebKitDOMHTMLElement> kitElement(adoptGRef(kit(reinterpret_cast<HTMLElement*>(m_fullScreenElement.get()))));
950     g_signal_emit_by_name(m_webView, "leaving-fullscreen", kitElement.get(), &returnValue);
951     if (returnValue)
952         return;
953
954     GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(m_webView));
955     ASSERT(widgetIsOnscreenToplevelWindow(window));
956     g_signal_handlers_disconnect_by_func(window, reinterpret_cast<void*>(onFullscreenGtkKeyPressEvent), this);
957
958     m_fullScreenElement->document()->webkitWillExitFullScreenForElement(m_fullScreenElement.get());
959     gtk_window_unfullscreen(GTK_WINDOW(window));
960     m_adjustmentWatcher.enableAllScrollbars();
961     m_fullScreenElement->document()->webkitDidExitFullScreenForElement(m_fullScreenElement.get());
962     m_fullScreenElement.clear();
963 }
964 #endif
965
966 #if USE(ACCELERATED_COMPOSITING)
967 void ChromeClient::attachRootGraphicsLayer(Frame* frame, GraphicsLayer* rootLayer)
968 {
969     m_webView->priv->acceleratedCompositingContext->attachRootGraphicsLayer(rootLayer);
970 }
971
972 void ChromeClient::setNeedsOneShotDrawingSynchronization()
973 {
974     m_webView->priv->acceleratedCompositingContext->markForSync();
975 }
976
977 void ChromeClient::scheduleCompositingLayerSync()
978 {
979     m_webView->priv->acceleratedCompositingContext->markForSync();
980 }
981
982 ChromeClient::CompositingTriggerFlags ChromeClient::allowedCompositingTriggers() const
983 {
984      if (!platformPageClient())
985         return false;
986 #if USE(CLUTTER)
987     // Currently, we only support CSS 3D Transforms.
988     return ThreeDTransformTrigger;
989 #else
990     return AllTriggers;
991 #endif
992 }
993 #endif
994
995 }