/*
 * Copyright (C) 2007, 2008 Holger Hans Peter Freyther
 * Copyright (C) 2007, 2008 Christian Dywan <christian@imendio.com>
 * Copyright (C) 2008 Nuanti Ltd.
 * Copyright (C) 2008 Alp Toker <alp@atoker.com>
 * Copyright (C) 2008 Gustavo Noronha Silva <gns@gnome.org>
 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
 * Copyright (C) 2012 Igalia S. L.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "config.h"
#include "ChromeClientGtk.h"

#include "Chrome.h"
#include "Console.h"
#include "DumpRenderTreeSupportGtk.h"
#include "Element.h"
#include "FileChooser.h"
#include "FileIconLoader.h"
#include "FileSystem.h"
#include "FloatRect.h"
#include "FocusController.h"
#include "FrameLoadRequest.h"
#include "FrameView.h"
#include "GtkUtilities.h"
#include "GtkVersioning.h"
#include "HTMLNames.h"
#include "HitTestResult.h"
#include "Icon.h"
#include "InspectorController.h"
#include "IntRect.h"
#include "KURL.h"
#include "NavigationAction.h"
#include "NotImplemented.h"
#include "PopupMenuClient.h"
#include "PopupMenuGtk.h"
#include "RefPtrCairo.h"
#include "SearchPopupMenuGtk.h"
#include "SecurityOrigin.h"
#include "WebKitDOMBinding.h"
#include "WebKitDOMHTMLElementPrivate.h"
#include "WindowFeatures.h"
#include "webkitfilechooserrequestprivate.h"
#include "webkitgeolocationpolicydecision.h"
#include "webkitgeolocationpolicydecisionprivate.h"
#include "webkitnetworkrequest.h"
#include "webkitsecurityoriginprivate.h"
#include "webkitviewportattributesprivate.h"
#include "webkitwebframeprivate.h"
#include "webkitwebview.h"
#include "webkitwebviewprivate.h"
#include "webkitwebwindowfeaturesprivate.h"
#include <gdk/gdk.h>
#include <gdk/gdkkeysyms.h>
#include <glib.h>
#include <glib/gi18n-lib.h>
#include <gtk/gtk.h>
#include <wtf/MathExtras.h>
#include <wtf/text/CString.h>
#include <wtf/text/WTFString.h>

#if ENABLE(SQL_DATABASE)
#include "DatabaseManager.h"
#endif

#if ENABLE(VIDEO) && USE(NATIVE_FULLSCREEN_VIDEO)
#include "HTMLMediaElement.h"
#endif

using namespace WebCore;

namespace WebKit {

ChromeClient::ChromeClient(WebKitWebView* webView)
    : m_webView(webView)
    , m_adjustmentWatcher(webView)
    , m_closeSoonTimer(0)
    , m_displayTimer(this, &ChromeClient::paint)
    , m_forcePaint(false)
    , m_lastDisplayTime(0)
    , m_repaintSoonSourceId(0)
{
    ASSERT(m_webView);
}

void ChromeClient::chromeDestroyed()
{
    if (m_closeSoonTimer)
        g_source_remove(m_closeSoonTimer);

    if (m_repaintSoonSourceId)
        g_source_remove(m_repaintSoonSourceId);

    delete this;
}

FloatRect ChromeClient::windowRect()
{
    GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(m_webView));
    if (widgetIsOnscreenToplevelWindow(window)) {
        gint left, top, width, height;
        gtk_window_get_position(GTK_WINDOW(window), &left, &top);
        gtk_window_get_size(GTK_WINDOW(window), &width, &height);
        return IntRect(left, top, width, height);
    }
    return FloatRect();
}

void ChromeClient::setWindowRect(const FloatRect& rect)
{
    IntRect intrect = IntRect(rect);
    WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);

    g_object_set(webWindowFeatures,
                 "x", intrect.x(),
                 "y", intrect.y(),
                 "width", intrect.width(),
                 "height", intrect.height(),
                 NULL);

    gboolean autoResizeWindow;
    WebKitWebSettings* settings = webkit_web_view_get_settings(m_webView);
    g_object_get(settings, "auto-resize-window", &autoResizeWindow, NULL);

    if (!autoResizeWindow)
        return;

    GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(m_webView));
    if (widgetIsOnscreenToplevelWindow(window)) {
        gtk_window_move(GTK_WINDOW(window), intrect.x(), intrect.y());
        if (!intrect.isEmpty())
            gtk_window_resize(GTK_WINDOW(window), intrect.width(), intrect.height());
    }
}

static IntRect getWebViewRect(WebKitWebView* webView)
{
    GtkAllocation allocation;
    gtk_widget_get_allocation(GTK_WIDGET(webView), &allocation);
    return IntRect(allocation.x, allocation.y, allocation.width, allocation.height);
}

FloatRect ChromeClient::pageRect()
{
    return getWebViewRect(m_webView);
}

void ChromeClient::focus()
{
    gtk_widget_grab_focus(GTK_WIDGET(m_webView));
}

void ChromeClient::unfocus()
{
    GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(m_webView));
    if (widgetIsOnscreenToplevelWindow(window))
        gtk_window_set_focus(GTK_WINDOW(window), NULL);
}

Page* ChromeClient::createWindow(Frame* frame, const FrameLoadRequest& frameLoadRequest, const WindowFeatures& coreFeatures, const NavigationAction&)
{
    WebKitWebView* webView = 0;

    g_signal_emit_by_name(m_webView, "create-web-view", kit(frame), &webView);

    if (!webView)
        return 0;

    GRefPtr<WebKitWebWindowFeatures> webWindowFeatures(adoptGRef(kitNew(coreFeatures)));
    g_object_set(webView, "window-features", webWindowFeatures.get(), NULL);

    return core(webView);
}

void ChromeClient::show()
{
    webkit_web_view_notify_ready(m_webView);
}

bool ChromeClient::canRunModal()
{
    notImplemented();
    return false;
}

void ChromeClient::runModal()
{
    notImplemented();
}

void ChromeClient::setToolbarsVisible(bool visible)
{
    WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);

    g_object_set(webWindowFeatures, "toolbar-visible", visible, NULL);
}

bool ChromeClient::toolbarsVisible()
{
    WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
    gboolean visible;

    g_object_get(webWindowFeatures, "toolbar-visible", &visible, NULL);
    return visible;
}

void ChromeClient::setStatusbarVisible(bool visible)
{
    WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);

    g_object_set(webWindowFeatures, "statusbar-visible", visible, NULL);
}

bool ChromeClient::statusbarVisible()
{
    WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
    gboolean visible;

    g_object_get(webWindowFeatures, "statusbar-visible", &visible, NULL);
    return visible;
}

void ChromeClient::setScrollbarsVisible(bool visible)
{
    WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);

    g_object_set(webWindowFeatures, "scrollbar-visible", visible, NULL);
}

bool ChromeClient::scrollbarsVisible()
{
    WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
    gboolean visible;

    g_object_get(webWindowFeatures, "scrollbar-visible", &visible, NULL);
    return visible;
}

void ChromeClient::setMenubarVisible(bool visible)
{
    WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);

    g_object_set(webWindowFeatures, "menubar-visible", visible, NULL);
}

bool ChromeClient::menubarVisible()
{
    WebKitWebWindowFeatures* webWindowFeatures = webkit_web_view_get_window_features(m_webView);
    gboolean visible;

    g_object_get(webWindowFeatures, "menubar-visible", &visible, NULL);
    return visible;
}

void ChromeClient::setResizable(bool)
{
    // Ignored for now
}

static gboolean emitCloseWebViewSignalLater(WebKitWebView* view)
{
    gboolean isHandled;
    g_signal_emit_by_name(view, "close-web-view", &isHandled);
    return FALSE;
}

void ChromeClient::closeWindowSoon()
{
    // We may not have a WebView as create-web-view can return NULL.
    if (!m_webView)
        return;
    if (m_closeSoonTimer) // Don't call close-web-view more than once.
        return;

    // We need to remove the parent WebView from WebViewSets here, before it actually
    // closes, to make sure that JavaScript code that executes before it closes
    // can't find it. Otherwise, window.open will select a closed WebView instead of 
    // opening a new one <rdar://problem/3572585>.
    m_webView->priv->corePage->setGroupName("");

    // We also need to stop the load to prevent further parsing or JavaScript execution
    // after the window has torn down <rdar://problem/4161660>.
    webkit_web_view_stop_loading(m_webView);

    // Clients commonly destroy the web view during the close-web-view signal, but our caller
    // may need to send more signals to the web view. For instance, if this happened in the
    // onload handler, it will need to call FrameLoaderClient::dispatchDidHandleOnloadEvents.
    // Instead of firing the close-web-view signal now, fire it after the caller finishes.
    // This seems to match the Mac/Windows port behavior.
    m_closeSoonTimer = g_timeout_add(0, reinterpret_cast<GSourceFunc>(emitCloseWebViewSignalLater), m_webView);
}

bool ChromeClient::canTakeFocus(FocusDirection)
{
    return gtk_widget_get_can_focus(GTK_WIDGET(m_webView));
}

void ChromeClient::takeFocus(FocusDirection)
{
    unfocus();
}

void ChromeClient::focusedNodeChanged(Node*)
{
}

void ChromeClient::focusedFrameChanged(Frame*)
{
}

bool ChromeClient::canRunBeforeUnloadConfirmPanel()
{
    return true;
}

bool ChromeClient::runBeforeUnloadConfirmPanel(const WTF::String& message, WebCore::Frame* frame)
{
    return runJavaScriptConfirm(frame, message);
}

void ChromeClient::addMessageToConsole(WebCore::MessageSource source, WebCore::MessageLevel level, const WTF::String& message, unsigned lineNumber, const WTF::String& sourceId)
{
    gboolean retval;
    g_signal_emit_by_name(m_webView, "console-message", message.utf8().data(), lineNumber, sourceId.utf8().data(), &retval);
}

void ChromeClient::runJavaScriptAlert(Frame* frame, const String& message)
{
    gboolean retval;
    g_signal_emit_by_name(m_webView, "script-alert", kit(frame), message.utf8().data(), &retval);
}

bool ChromeClient::runJavaScriptConfirm(Frame* frame, const String& message)
{
    gboolean retval;
    gboolean didConfirm;
    g_signal_emit_by_name(m_webView, "script-confirm", kit(frame), message.utf8().data(), &didConfirm, &retval);
    return didConfirm == TRUE;
}

bool ChromeClient::runJavaScriptPrompt(Frame* frame, const String& message, const String& defaultValue, String& result)
{
    gboolean retval;
    gchar* value = 0;
    g_signal_emit_by_name(m_webView, "script-prompt", kit(frame), message.utf8().data(), defaultValue.utf8().data(), &value, &retval);
    if (value) {
        result = String::fromUTF8(value);
        g_free(value);
        return true;
    }
    return false;
}

void ChromeClient::setStatusbarText(const String& string)
{
    CString stringMessage = string.utf8();
    g_signal_emit_by_name(m_webView, "status-bar-text-changed", stringMessage.data());
}

bool ChromeClient::shouldInterruptJavaScript()
{
    notImplemented();
    return false;
}

KeyboardUIMode ChromeClient::keyboardUIMode()
{
    bool tabsToLinks = true;
    if (DumpRenderTreeSupportGtk::dumpRenderTreeModeEnabled())
        tabsToLinks = DumpRenderTreeSupportGtk::linksIncludedInFocusChain();

    return tabsToLinks ? KeyboardAccessTabsToLinks : KeyboardAccessDefault;
}

IntRect ChromeClient::windowResizerRect() const
{
    notImplemented();
    return IntRect();
}

static gboolean repaintEverythingSoonTimeout(ChromeClient* client)
{
    client->paint(0);
    return FALSE;
}

static void clipOutOldWidgetArea(cairo_t* cr, const IntSize& oldSize, const IntSize& newSize)
{
    cairo_move_to(cr, oldSize.width(), 0);
    cairo_line_to(cr, newSize.width(), 0);
    cairo_line_to(cr, newSize.width(), newSize.height());
    cairo_line_to(cr, 0, newSize.height());
    cairo_line_to(cr, 0, oldSize.height());
    cairo_line_to(cr, oldSize.width(), oldSize.height());
    cairo_close_path(cr);
    cairo_clip(cr);
}

static void clearEverywhereInBackingStore(WebKitWebView* webView, cairo_t* cr)
{
    // The strategy here is to quickly draw white into this new canvas, so that
    // when a user quickly resizes the WebView in an environment that has opaque
    // resizing (like Gnome Shell), there are no drawing artifacts.
    if (!webView->priv->transparent) {
        cairo_set_source_rgb(cr, 1, 1, 1);
        cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
    } else
        cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
    cairo_paint(cr);
}

void ChromeClient::widgetSizeChanged(const IntSize& oldWidgetSize, IntSize newSize)
{
#if USE(ACCELERATED_COMPOSITING)
    AcceleratedCompositingContext* compositingContext = m_webView->priv->acceleratedCompositingContext.get();
    if (compositingContext->enabled()) {
        m_webView->priv->acceleratedCompositingContext->resizeRootLayer(newSize);
        return;
    }
#endif

    // Grow the backing store by at least 1.5 times the current size. This prevents
    // lots of unnecessary allocations during an opaque resize.
    WidgetBackingStore* backingStore = m_webView->priv->backingStore.get();
    if (backingStore && oldWidgetSize == newSize)
        return;

    if (backingStore) {
        const IntSize& oldSize = backingStore->size();
        if (newSize.width() > oldSize.width())
            newSize.setWidth(std::max(newSize.width(), static_cast<int>(oldSize.width() * 1.5)));
        if (newSize.height() > oldSize.height())
            newSize.setHeight(std::max(newSize.height(), static_cast<int>(oldSize.height() * 1.5)));
    }

    // If we did not have a backing store before or if the backing store is growing, we need
    // to reallocate a new one and set it up so that we don't see artifacts while resizing.
    if (!backingStore
        || newSize.width() > backingStore->size().width()
        || newSize.height() > backingStore->size().height()) {

        OwnPtr<WidgetBackingStore> newBackingStore =
            WebCore::WidgetBackingStore::create(GTK_WIDGET(m_webView), newSize);
        RefPtr<cairo_t> cr = adoptRef(cairo_create(newBackingStore->cairoSurface()));

        clearEverywhereInBackingStore(m_webView, cr.get());

        // Now we copy the old backing store image over the new cleared surface to prevent
        // annoying flashing as the widget grows. We do the "real" paint in a timeout
        // since we don't want to block resizing too long.
        if (backingStore) {
            cairo_set_source_surface(cr.get(), backingStore->cairoSurface(), 0, 0);
            cairo_rectangle(cr.get(), 0, 0, backingStore->size().width(), backingStore->size().height());
            cairo_fill(cr.get());
        }

        m_webView->priv->backingStore = newBackingStore.release();
        backingStore = m_webView->priv->backingStore.get();

    } else if (oldWidgetSize.width() < newSize.width() || oldWidgetSize.height() < newSize.height()) {
        // The widget is growing, but we did not need to create a new backing store.
        // We should clear any old data outside of the old widget region.
        RefPtr<cairo_t> cr = adoptRef(cairo_create(backingStore->cairoSurface()));
        clipOutOldWidgetArea(cr.get(), oldWidgetSize, newSize);
        clearEverywhereInBackingStore(m_webView, cr.get());
    }

    // We need to force a redraw and ignore the framerate cap.
    m_lastDisplayTime = 0;
    m_dirtyRegion.unite(IntRect(IntPoint(), backingStore->size()));

    // WebCore timers by default have a lower priority which leads to more artifacts when opaque
    // resize is on, thus we use g_timeout_add here to force a higher timeout priority.
    if (!m_repaintSoonSourceId)
        m_repaintSoonSourceId = g_timeout_add(0, reinterpret_cast<GSourceFunc>(repaintEverythingSoonTimeout), this);
}

static void coalesceRectsIfPossible(const IntRect& clipRect, Vector<IntRect>& rects)
{
    const unsigned int cRectThreshold = 10;
    const float cWastedSpaceThreshold = 0.75f;
    bool useUnionedRect = (rects.size() <= 1) || (rects.size() > cRectThreshold);
    if (!useUnionedRect) {
        // Attempt to guess whether or not we should use the unioned rect or the individual rects.
        // We do this by computing the percentage of "wasted space" in the union. If that wasted space
        // is too large, then we will do individual rect painting instead.
        float unionPixels = (clipRect.width() * clipRect.height());
        float singlePixels = 0;
        for (size_t i = 0; i < rects.size(); ++i)
            singlePixels += rects[i].width() * rects[i].height();
        float wastedSpace = 1 - (singlePixels / unionPixels);
        if (wastedSpace <= cWastedSpaceThreshold)
            useUnionedRect = true;
    }

    if (!useUnionedRect)
        return;

    rects.clear();
    rects.append(clipRect);
}

static void paintWebView(WebKitWebView* webView, Frame* frame, Region dirtyRegion)
{
    if (!webView->priv->backingStore)
        return;

    Vector<IntRect> rects = dirtyRegion.rects();
    coalesceRectsIfPossible(dirtyRegion.bounds(), rects);

    RefPtr<cairo_t> backingStoreContext = adoptRef(cairo_create(webView->priv->backingStore->cairoSurface()));
    GraphicsContext gc(backingStoreContext.get());
    gc.applyDeviceScaleFactor(frame->page()->deviceScaleFactor());
    for (size_t i = 0; i < rects.size(); i++) {
        const IntRect& rect = rects[i];

        gc.save();
        gc.clip(rect);
        if (webView->priv->transparent)
            gc.clearRect(rect);
        frame->view()->paint(&gc, rect);
        gc.restore();
    }

    gc.save();
    gc.clip(dirtyRegion.bounds());
    frame->page()->inspectorController()->drawHighlight(gc);
    gc.restore();
}

void ChromeClient::performAllPendingScrolls()
{
    if (!m_webView->priv->backingStore)
        return;

    // Scroll all pending scroll rects and invalidate those parts of the widget.
    for (size_t i = 0; i < m_rectsToScroll.size(); i++) {
        IntRect& scrollRect = m_rectsToScroll[i];
        m_webView->priv->backingStore->scroll(scrollRect, m_scrollOffsets[i]);
        gtk_widget_queue_draw_area(GTK_WIDGET(m_webView), scrollRect.x(), scrollRect.y(), scrollRect.width(), scrollRect.height());
    }

    m_rectsToScroll.clear();
    m_scrollOffsets.clear();
}

void ChromeClient::paint(WebCore::Timer<ChromeClient>*)
{
    static const double minimumFrameInterval = 1.0 / 60.0; // No more than 60 frames a second.
    double timeSinceLastDisplay = currentTime() - m_lastDisplayTime;
    double timeUntilNextDisplay = minimumFrameInterval - timeSinceLastDisplay;

    if (timeUntilNextDisplay > 0 && !m_forcePaint) {
        m_displayTimer.startOneShot(timeUntilNextDisplay);
        return;
    }

    Frame* frame = core(m_webView)->mainFrame();
    if (!frame || !frame->contentRenderer() || !frame->view())
        return;

    frame->view()->updateLayoutAndStyleIfNeededRecursive();
    performAllPendingScrolls();
    paintWebView(m_webView, frame, m_dirtyRegion);

    HashSet<GtkWidget*> children = m_webView->priv->children;
    HashSet<GtkWidget*>::const_iterator end = children.end();
    for (HashSet<GtkWidget*>::const_iterator current = children.begin(); current != end; ++current) {
        if (static_cast<GtkAllocation*>(g_object_get_data(G_OBJECT(*current), "delayed-allocation"))) {
            gtk_widget_queue_resize_no_redraw(GTK_WIDGET(m_webView));
            break;
        }
    }

    const IntRect& rect = m_dirtyRegion.bounds();
    gtk_widget_queue_draw_area(GTK_WIDGET(m_webView), rect.x(), rect.y(), rect.width(), rect.height());

    m_dirtyRegion = Region();
    m_lastDisplayTime = currentTime();
    m_repaintSoonSourceId = 0;

    // We update the IM context window location here, because we want it to be
    // synced with cursor movement. For instance, a text field can move without
    // the selection changing.
    Frame* focusedFrame = core(m_webView)->focusController()->focusedOrMainFrame();
    if (focusedFrame && focusedFrame->editor()->canEdit())
        m_webView->priv->imFilter.setCursorRect(frame->selection()->absoluteCaretBounds());
}

void ChromeClient::forcePaint()
{
#if USE(ACCELERATED_COMPOSITING)
    if (m_webView->priv->acceleratedCompositingContext->enabled())
        return;
#endif

    m_forcePaint = true;
    paint(0);
    m_forcePaint = false;
}

void ChromeClient::invalidateRootView(const IntRect&, bool immediate)
{
}

void ChromeClient::invalidateContentsAndRootView(const IntRect& updateRect, bool immediate)
{
#if USE(ACCELERATED_COMPOSITING)
    AcceleratedCompositingContext* acContext = m_webView->priv->acceleratedCompositingContext.get();
    if (acContext->enabled()) {
        acContext->setNonCompositedContentsNeedDisplay(updateRect);
        return;
    }
#endif

    if (updateRect.isEmpty())
        return;
    m_dirtyRegion.unite(updateRect);
    m_displayTimer.startOneShot(0);
}

void ChromeClient::invalidateContentsForSlowScroll(const IntRect& updateRect, bool immediate)
{
    m_adjustmentWatcher.updateAdjustmentsFromScrollbarsLater();

#if USE(ACCELERATED_COMPOSITING)
    AcceleratedCompositingContext* acContext = m_webView->priv->acceleratedCompositingContext.get();
    if (acContext->enabled()) {
        acContext->setNonCompositedContentsNeedDisplay(updateRect);
        return;
    }
#endif

    invalidateContentsAndRootView(updateRect, immediate);
}

void ChromeClient::scroll(const IntSize& delta, const IntRect& rectToScroll, const IntRect& clipRect)
{
    m_adjustmentWatcher.updateAdjustmentsFromScrollbarsLater();

#if USE(ACCELERATED_COMPOSITING)
    AcceleratedCompositingContext* compositingContext = m_webView->priv->acceleratedCompositingContext.get();
    if (compositingContext->enabled()) {
        ASSERT(!rectToScroll.isEmpty());
        ASSERT(delta.width() || delta.height());

        compositingContext->scrollNonCompositedContents(rectToScroll, delta);
        return;
    }
#endif

    m_rectsToScroll.append(rectToScroll);
    m_scrollOffsets.append(delta);

    // The code to calculate the scroll repaint region is originally from WebKit2.
    // Get the part of the dirty region that is in the scroll rect.
    Region dirtyRegionInScrollRect = intersect(rectToScroll, m_dirtyRegion);
    if (!dirtyRegionInScrollRect.isEmpty()) {
        // There are parts of the dirty region that are inside the scroll rect.
        // We need to subtract them from the region, move them and re-add them.
        m_dirtyRegion.subtract(rectToScroll);

        // Move the dirty parts.
        Region movedDirtyRegionInScrollRect = intersect(translate(dirtyRegionInScrollRect, delta), rectToScroll);

        // And add them back.
        m_dirtyRegion.unite(movedDirtyRegionInScrollRect);
    }

    // Compute the scroll repaint region. We ensure that we are not subtracting areas
    // that we've scrolled from outside the viewport from the repaint region.
    IntRect onScreenScrollRect = rectToScroll;
    onScreenScrollRect.intersect(IntRect(IntPoint(), enclosingIntRect(pageRect()).size()));
    Region scrollRepaintRegion = subtract(rectToScroll, translate(onScreenScrollRect, delta));

    m_dirtyRegion.unite(scrollRepaintRegion);
    m_displayTimer.startOneShot(0);
}

IntRect ChromeClient::rootViewToScreen(const IntRect& rect) const
{
    return IntRect(convertWidgetPointToScreenPoint(GTK_WIDGET(m_webView), rect.location()), rect.size());
}

IntPoint ChromeClient::screenToRootView(const IntPoint& point) const
{
    IntPoint widgetPositionOnScreen = convertWidgetPointToScreenPoint(GTK_WIDGET(m_webView), IntPoint());
    IntPoint result(point);
    result.move(-widgetPositionOnScreen.x(), -widgetPositionOnScreen.y());
    return result;
}

PlatformPageClient ChromeClient::platformPageClient() const
{
    return GTK_WIDGET(m_webView);
}

void ChromeClient::contentsSizeChanged(Frame* frame, const IntSize& size) const
{
    if (m_adjustmentWatcher.scrollbarsDisabled())
        return;

    // We need to queue a resize request only if the size changed,
    // otherwise we get into an infinite loop!
    GtkWidget* widget = GTK_WIDGET(m_webView);
    GtkRequisition requisition;
#if GTK_CHECK_VERSION(2, 20, 0)
    gtk_widget_get_requisition(widget, &requisition);
#else
    requisition = widget->requisition;
#endif
    if (gtk_widget_get_realized(widget)
        && (requisition.height != size.height())
        || (requisition.width != size.width()))
        gtk_widget_queue_resize_no_redraw(widget);

    // If this was a main frame size change, update the scrollbars.
    if (frame != frame->page()->mainFrame())
        return;
    m_adjustmentWatcher.updateAdjustmentsFromScrollbarsLater();
}

void ChromeClient::scrollbarsModeDidChange() const
{
    WebKitWebFrame* webFrame = webkit_web_view_get_main_frame(m_webView);
    if (!webFrame)
        return;

    g_object_notify(G_OBJECT(webFrame), "horizontal-scrollbar-policy");
    g_object_notify(G_OBJECT(webFrame), "vertical-scrollbar-policy");

    gboolean isHandled;
    g_signal_emit_by_name(webFrame, "scrollbars-policy-changed", &isHandled);

    if (isHandled)
        return;

    GtkWidget* parent = gtk_widget_get_parent(GTK_WIDGET(m_webView));
    if (!parent || !GTK_IS_SCROLLED_WINDOW(parent))
        return;

    GtkPolicyType horizontalPolicy = webkit_web_frame_get_horizontal_scrollbar_policy(webFrame);
    GtkPolicyType verticalPolicy = webkit_web_frame_get_vertical_scrollbar_policy(webFrame);

    // ScrolledWindow doesn't like to display only part of a widget if
    // the scrollbars are completely disabled; We have a disparity
    // here on what the policy requested by the web app is and what we
    // can represent; the idea is not to show scrollbars, only.
    if (horizontalPolicy == GTK_POLICY_NEVER)
        horizontalPolicy = GTK_POLICY_AUTOMATIC;

    if (verticalPolicy == GTK_POLICY_NEVER)
        verticalPolicy = GTK_POLICY_AUTOMATIC;

    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(parent),
                                   horizontalPolicy, verticalPolicy);
}

void ChromeClient::mouseDidMoveOverElement(const HitTestResult& hit, unsigned modifierFlags)
{
    // check if the element is a link...
    bool isLink = hit.isLiveLink();
    if (isLink) {
        KURL url = hit.absoluteLinkURL();
        if (!url.isEmpty() && url != m_hoveredLinkURL) {
            TextDirection dir;
            CString titleString = hit.title(dir).utf8();
            CString urlString = url.string().utf8();
            g_signal_emit_by_name(m_webView, "hovering-over-link", titleString.data(), urlString.data());
            m_hoveredLinkURL = url;
        }
    } else if (!isLink && !m_hoveredLinkURL.isEmpty()) {
        g_signal_emit_by_name(m_webView, "hovering-over-link", 0, 0);
        m_hoveredLinkURL = KURL();
    }

    if (Node* node = hit.innerNonSharedNode()) {
        Frame* frame = node->document()->frame();
        FrameView* view = frame ? frame->view() : 0;
        m_webView->priv->tooltipArea = view ? view->contentsToWindow(node->pixelSnappedBoundingBox()) : IntRect();
    } else
        m_webView->priv->tooltipArea = IntRect();
}

void ChromeClient::setToolTip(const String& toolTip, TextDirection)
{
    webkit_web_view_set_tooltip_text(m_webView, toolTip.utf8().data());
}

void ChromeClient::print(Frame* frame)
{
    WebKitWebFrame* webFrame = kit(frame);
    gboolean isHandled = false;
    g_signal_emit_by_name(m_webView, "print-requested", webFrame, &isHandled);

    if (isHandled)
        return;

    webkit_web_frame_print(webFrame);
}

#if ENABLE(SQL_DATABASE)
void ChromeClient::exceededDatabaseQuota(Frame* frame, const String& databaseName, DatabaseDetails)
{
    guint64 defaultQuota = webkit_get_default_web_database_quota();
    DatabaseManager::manager().setQuota(frame->document()->securityOrigin(), defaultQuota);

    WebKitWebFrame* webFrame = kit(frame);
    WebKitSecurityOrigin* origin = webkit_web_frame_get_security_origin(webFrame);
    WebKitWebDatabase* webDatabase = webkit_security_origin_get_web_database(origin, databaseName.utf8().data());
    g_signal_emit_by_name(m_webView, "database-quota-exceeded", webFrame, webDatabase);
}
#endif

void ChromeClient::reachedMaxAppCacheSize(int64_t spaceNeeded)
{
    // FIXME: Free some space.
    notImplemented();
}

void ChromeClient::reachedApplicationCacheOriginQuota(SecurityOrigin*, int64_t)
{
    notImplemented();
}

void ChromeClient::runOpenPanel(Frame*, PassRefPtr<FileChooser> prpFileChooser)
{
    GRefPtr<WebKitFileChooserRequest> request = adoptGRef(webkit_file_chooser_request_create(prpFileChooser));
    webkitWebViewRunFileChooserRequest(m_webView, request.get());
}

void ChromeClient::loadIconForFiles(const Vector<WTF::String>& filenames, WebCore::FileIconLoader* loader)
{
    loader->notifyFinished(Icon::createIconForFiles(filenames));
}

void ChromeClient::dispatchViewportPropertiesDidChange(const ViewportArguments& arguments) const
{
    // Recompute the viewport attributes making it valid.
    webkitViewportAttributesRecompute(webkit_web_view_get_viewport_attributes(m_webView));
}

void ChromeClient::setCursor(const Cursor& cursor)
{
    // [GTK] Widget::setCursor() gets called frequently
    // http://bugs.webkit.org/show_bug.cgi?id=16388
    // Setting the cursor may be an expensive operation in some backends,
    // so don't re-set the cursor if it's already set to the target value.
    GdkWindow* window = gtk_widget_get_window(platformPageClient());
    if (!window)
        return;

    GdkCursor* currentCursor = gdk_window_get_cursor(window);
    GdkCursor* newCursor = cursor.platformCursor().get();
    if (currentCursor != newCursor)
        gdk_window_set_cursor(window, newCursor);
}

void ChromeClient::setCursorHiddenUntilMouseMoves(bool)
{
    notImplemented();
}

bool ChromeClient::selectItemWritingDirectionIsNatural()
{
    return false;
}

bool ChromeClient::selectItemAlignmentFollowsMenuWritingDirection()
{
    return true;
}

bool ChromeClient::hasOpenedPopup() const
{
    notImplemented();
    return false;
}

PassRefPtr<WebCore::PopupMenu> ChromeClient::createPopupMenu(WebCore::PopupMenuClient* client) const
{
    return adoptRef(new PopupMenuGtk(client));
}

PassRefPtr<WebCore::SearchPopupMenu> ChromeClient::createSearchPopupMenu(WebCore::PopupMenuClient* client) const
{
    return adoptRef(new SearchPopupMenuGtk(client));
}

#if ENABLE(VIDEO) && USE(NATIVE_FULLSCREEN_VIDEO)
bool ChromeClient::supportsFullscreenForNode(const Node* node)
{
    return node->hasTagName(HTMLNames::videoTag);
}

void ChromeClient::enterFullscreenForNode(Node* node)
{
    if (!node)
        return;

    HTMLElement* element = static_cast<HTMLElement*>(node);
    if (element && element->isMediaElement()) {
        HTMLMediaElement* mediaElement = static_cast<HTMLMediaElement*>(element);
        if (mediaElement->player() && mediaElement->player()->canEnterFullscreen())
            mediaElement->player()->enterFullscreen();
    }
}

void ChromeClient::exitFullscreenForNode(Node* node)
{
    if (!node)
        return;

    HTMLElement* element = static_cast<HTMLElement*>(node);
    if (element && element->isMediaElement()) {
        HTMLMediaElement* mediaElement = static_cast<HTMLMediaElement*>(element);
        if (mediaElement->player())
            mediaElement->player()->exitFullscreen();
    }
}
#endif

#if ENABLE(FULLSCREEN_API)
bool ChromeClient::supportsFullScreenForElement(const WebCore::Element* element, bool withKeyboard)
{
    return !withKeyboard;
}

static gboolean onFullscreenGtkKeyPressEvent(GtkWidget* widget, GdkEventKey* event, ChromeClient* chromeClient)
{
    switch (event->keyval) {
    case GDK_KEY_Escape:
    case GDK_KEY_f:
    case GDK_KEY_F:
        chromeClient->cancelFullScreen();
        return TRUE;
    default:
        break;
    }

    return FALSE;
}

void ChromeClient::cancelFullScreen()
{
    ASSERT(m_fullScreenElement);
    m_fullScreenElement->document()->webkitCancelFullScreen();
}

void ChromeClient::enterFullScreenForElement(WebCore::Element* element)
{
    gboolean returnValue;
    GRefPtr<WebKitDOMHTMLElement> kitElement(adoptGRef(kit(reinterpret_cast<HTMLElement*>(element))));
    g_signal_emit_by_name(m_webView, "entering-fullscreen", kitElement.get(), &returnValue);
    if (returnValue)
        return;

#if ENABLE(VIDEO) && USE(NATIVE_FULLSCREEN_VIDEO)
    if (element && element->isMediaElement()) {
        HTMLMediaElement* mediaElement = static_cast<HTMLMediaElement*>(element);
        if (mediaElement->player() && mediaElement->player()->canEnterFullscreen()) {
            element->document()->webkitWillEnterFullScreenForElement(element);
            mediaElement->player()->enterFullscreen();
            m_fullScreenElement = element;
            element->document()->webkitDidEnterFullScreenForElement(element);
        }
        return;
    }
#endif

    GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(m_webView));
    if (!widgetIsOnscreenToplevelWindow(window))
        return;

    g_signal_connect(window, "key-press-event", G_CALLBACK(onFullscreenGtkKeyPressEvent), this);

    m_fullScreenElement = element;

    element->document()->webkitWillEnterFullScreenForElement(element);
    m_adjustmentWatcher.disableAllScrollbars();
    gtk_window_fullscreen(GTK_WINDOW(window));
    element->document()->webkitDidEnterFullScreenForElement(element);
}

void ChromeClient::exitFullScreenForElement(WebCore::Element*)
{
    // The element passed into this function is not reliable, i.e. it could
    // be null. In addition the parameter may be disappearing in the future.
    // So we use the reference to the element we saved above.
    ASSERT(m_fullScreenElement);

    gboolean returnValue;
    GRefPtr<WebKitDOMHTMLElement> kitElement(adoptGRef(kit(reinterpret_cast<HTMLElement*>(m_fullScreenElement.get()))));
    g_signal_emit_by_name(m_webView, "leaving-fullscreen", kitElement.get(), &returnValue);
    if (returnValue)
        return;

#if ENABLE(VIDEO) && USE(NATIVE_FULLSCREEN_VIDEO)
    if (m_fullScreenElement && m_fullScreenElement->isMediaElement()) {
        m_fullScreenElement->document()->webkitWillExitFullScreenForElement(m_fullScreenElement.get());
        HTMLMediaElement* mediaElement = static_cast<HTMLMediaElement*>(m_fullScreenElement.get());
        if (mediaElement->player()) {
            mediaElement->player()->exitFullscreen();
            m_fullScreenElement->document()->webkitDidExitFullScreenForElement(m_fullScreenElement.get());
            m_fullScreenElement.clear();
        }
        return;
    }
#endif

    GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(m_webView));
    ASSERT(widgetIsOnscreenToplevelWindow(window));
    g_signal_handlers_disconnect_by_func(window, reinterpret_cast<void*>(onFullscreenGtkKeyPressEvent), this);

    m_fullScreenElement->document()->webkitWillExitFullScreenForElement(m_fullScreenElement.get());
    gtk_window_unfullscreen(GTK_WINDOW(window));
    m_adjustmentWatcher.enableAllScrollbars();
    m_fullScreenElement->document()->webkitDidExitFullScreenForElement(m_fullScreenElement.get());
    m_fullScreenElement.clear();
}
#endif

#if USE(ACCELERATED_COMPOSITING)
void ChromeClient::attachRootGraphicsLayer(Frame* frame, GraphicsLayer* rootLayer)
{
    AcceleratedCompositingContext* context = m_webView->priv->acceleratedCompositingContext.get();
    bool turningOffCompositing = !rootLayer && context->enabled();
    bool turningOnCompositing = rootLayer && !context->enabled();

    context->setRootCompositingLayer(rootLayer);

    if (turningOnCompositing) {
        m_displayTimer.stop();
        m_webView->priv->backingStore = WebCore::WidgetBackingStore::create(GTK_WIDGET(m_webView), IntSize(1, 1));
    }

    if (turningOffCompositing) {
        m_webView->priv->backingStore = WebCore::WidgetBackingStore::create(GTK_WIDGET(m_webView), getWebViewRect(m_webView).size());
        RefPtr<cairo_t> cr = adoptRef(cairo_create(m_webView->priv->backingStore->cairoSurface()));
        clearEverywhereInBackingStore(m_webView, cr.get());
    }
}

void ChromeClient::setNeedsOneShotDrawingSynchronization()
{
    m_webView->priv->acceleratedCompositingContext->scheduleLayerFlush();
}

void ChromeClient::scheduleCompositingLayerFlush()
{
    m_webView->priv->acceleratedCompositingContext->scheduleLayerFlush();
}

ChromeClient::CompositingTriggerFlags ChromeClient::allowedCompositingTriggers() const
{
     if (!platformPageClient())
        return false;
#if USE(CLUTTER)
    // Currently, we only support CSS 3D Transforms.
    return ThreeDTransformTrigger | AnimationTrigger;
#else
    return AllTriggers;
#endif
}
#endif

}
