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