/*
 *  Copyright (C) 2010 Collabora Ltd.
 *  Copyright (C) 2010 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 "webkitiwebview.h"

#include "GraphicsLayerActor.h"
#include "FocusController.h"
#include "Frame.h"
#include "FrameView.h"
#include "Logging.h"
#include "PlatformGestureEvent.h"
#include "PlatformKeyboardEvent.h"
#include "PlatformMouseEvent.h"
#include "PlatformWheelEvent.h"
#include "PlatformTouchEvent.h"
#include "UtilClutter.h"
#include "webkitiwebviewprivate.h"
#include "webkitmarshal.h"
#include "webkitgwebframeprivate.h"
#include "webkitwebpageprivate.h"

#if ENABLE(VIDEO)
#include "WebKitDOMHTMLVideoElement.h"
#endif

#if ENABLE(WEBGL)
#include "GraphicsContext3DPrivate.h"
#endif

using namespace WebKit;
using namespace WebCore;

static void webkit_iweb_view_default_init(WebKitIWebViewInterface* iface);

G_DEFINE_INTERFACE(WebKitIWebView, webkit_iweb_view, G_TYPE_OBJECT)

static const int default_touch_radius = 30;

enum {
    MEDIA_FULLSCREEN = 1,
    MEDIA_EXIT_FULLSCREEN,
    LAST_SIGNAL
};
static guint webkit_iweb_view_signals[LAST_SIGNAL] = { 0, };

/* Default implementations. */
#if ENABLE(VIDEO)
static guint controlsBoxTimeoutId = 0;
static guint pausedNotifyId = 0;
static guint currentTimeNotifyId = 0;
static ClutterActor* stage = 0;
static ClutterActor* controlsBox = 0;
static ClutterActor* videoTexture = 0;
#endif

WebKitWebPage* webkit_iweb_view_base_get_page(WebKitIWebView* view)
{
    return 0;
}

WebKitIRenderTheme* webkit_iweb_view_base_get_render_theme(WebKitIWebView* view)
{
    return webkit_irender_theme_get_default();
}

WebKitIPopupMenu* webkit_iweb_view_base_create_popup_menu(WebKitIWebView* view)
{
    /* FIXME: If the view doesn't implement this, using any menu segfaults.
     */
    return NULL;
}

ClutterContainer* webkit_iweb_view_base_get_container(WebKitIWebView* view)
{
    ClutterActor* actor = CLUTTER_ACTOR(view);
    ClutterStage* stage = CLUTTER_STAGE(clutter_actor_get_stage(actor));
    return CLUTTER_CONTAINER(stage);
}

void webkit_iweb_view_base_invalidate(WebKitIWebView* view)
{
    if (!webkit_iweb_view_get_page(view))
        return;

    ClutterActor* actor = CLUTTER_ACTOR(view);
    ClutterGeometry geometry;
    clutter_actor_get_geometry(actor, &geometry);
    webkit_iweb_view_invalidate_rect(view, &geometry);
}

void webkit_iweb_view_base_invalidate_rect(WebKitIWebView* view,
                                           ClutterGeometry* updateRect)
{
    return;
}

void webkit_iweb_view_base_get_window_rect(WebKitIWebView* view,
                                           ClutterGeometry* rect)
{
    // NOTE: Assume toplevel window == stage by default
    ClutterActor *stage = clutter_actor_get_stage(CLUTTER_ACTOR(view));
    clutter_actor_get_geometry(stage, rect);
}

void webkit_iweb_view_base_set_window_rect(WebKitIWebView* view,
                                           ClutterGeometry* rect)
{
    // NOTE: Not implemented by default
}

static void webkit_iweb_view_base_post_commit_setup(WebKitIWebView* view,
                                                    gboolean resetScrolling)
{
}

void webkit_iweb_view_base_add_tile(WebKitIWebView* view, ClutterActor* tile)
{
    clutter_actor_add_child(CLUTTER_ACTOR(view), tile);
}

#if ENABLE(VIDEO)
static void exitFullscreen(WebKitIWebView* view)
{
    if (controlsBoxTimeoutId) {
        g_source_remove(controlsBoxTimeoutId);
        controlsBoxTimeoutId = 0;
    }

    webkit_web_page_request_exit_fullscreen_media(webkit_iweb_view_get_page(view));
}

static gboolean keyReleaseEvent(ClutterActor* actor, ClutterKeyEvent* event, WebKitIWebView* view)
{
    switch (event->keyval) {
    case CLUTTER_KEY_Escape:
        exitFullscreen(view);
        break;
    default:
        return FALSE;
    }

    return TRUE;
}

static void placeControlsBox()
{
    float width, height;
    clutter_actor_get_size(stage, &width, &height);

    float x, y;

    x = width / 2 - (clutter_actor_get_width(controlsBox) / 2);
    y = height - clutter_actor_get_height(controlsBox) - 40;

    clutter_actor_set_position(controlsBox, x, y);
}

static void placeTexture()
{
    float width, height;
    clutter_actor_get_size(stage, &width, &height);
    clutter_actor_set_size(videoTexture, width, height);
}

static void allocationChanged(ClutterActor* stage, GParamSpec* pspec, gpointer data)
{
    placeControlsBox();
    placeTexture();
}

static gboolean hideControlsBox(gpointer data)
{
    ClutterActor* controlsBox = CLUTTER_ACTOR(data);
    clutter_actor_animate(controlsBox, CLUTTER_LINEAR, 500,
                          "opacity", 0, NULL);

    controlsBoxTimeoutId = 0;

    return FALSE;
}

static void motionEvent(ClutterActor* stage, ClutterEvent* event, ClutterActor* controlsBox)
{
    if (controlsBoxTimeoutId)
        g_source_remove(controlsBoxTimeoutId);
    else
        clutter_actor_animate(controlsBox, CLUTTER_LINEAR, 500,
                              "opacity", 255, NULL);

    controlsBoxTimeoutId = g_timeout_add_seconds(3, hideControlsBox, controlsBox);
}

static void paintMediaWidget(ClutterCairoTexture* texture, WebKitIRenderThemeWidgetType type, WebKitIRenderThemeWidgetState* state, int flags)
{
    cairo_t* cr = clutter_cairo_texture_create(texture);
    cairo_rectangle_int_t rect = { 0, 0, 20, 20 };
    webkit_irender_theme_paint_widget(webkit_irender_theme_get_default(), type, state, flags, cr, &rect);
    cairo_destroy(cr);
}

static int playingFlag(WebKitDOMHTMLMediaElement* element)
{
    if (!webkit_dom_html_media_element_get_paused(element))
        return mediaPlaying;
    return 0;
}

static void repaintMediaButton(ClutterCairoTexture* texture, WebKitDOMHTMLMediaElement* element)
{
    WebKitIRenderThemeWidgetState state;
    state.active = 1;
    state.focused = 0;
    state.disabled = state.isDefault = state.canDefault = state.depressed = state.curpos = state.maxpos = 0;
    state.inHover = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(texture), "webkit-clutter-media-button-hovered"));

    WebKitIRenderThemeWidgetType type = static_cast<WebKitIRenderThemeWidgetType>(GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(texture), "webkit-clutter-media-button-type")));
    paintMediaWidget(texture, type, &state, playingFlag(element));
}

static gboolean mediaMediaButtonEnterEvent(ClutterActor* button, ClutterEvent* event, WebKitDOMHTMLMediaElement* element)
{
    g_object_set_data(G_OBJECT(button), "webkit-clutter-media-button-hovered", GINT_TO_POINTER(1));
    repaintMediaButton(CLUTTER_CAIRO_TEXTURE(button), element);
    return TRUE;
}

static gboolean mediaMediaButtonLeaveEvent(ClutterActor* button, ClutterEvent* event, WebKitDOMHTMLMediaElement* element)
{
    g_object_set_data(G_OBJECT(button), "webkit-clutter-media-button-hovered", GINT_TO_POINTER(0));
    repaintMediaButton(CLUTTER_CAIRO_TEXTURE(button), element);
    return TRUE;
}

static gboolean mediaPlayButtonReleaseEvent(ClutterActor* button, ClutterEvent* event, WebKitDOMHTMLMediaElement* element)
{
    if (webkit_dom_html_media_element_get_paused(element))
        webkit_dom_html_media_element_play(element);
    else
        webkit_dom_html_media_element_pause(element);
    return TRUE;
}

static gboolean mediaExitFullscreenButtonReleaseEvent(ClutterActor* button, ClutterEvent* event, WebKitIWebView* view)
{
    exitFullscreen(view);
    return TRUE;
}

static void mediaPlayingChanged(WebKitDOMHTMLMediaElement* element, GParamSpec* pspec, ClutterCairoTexture* texture)
{
    repaintMediaButton(texture, element);
}


static String timeToString(float time)
{
    if (!std::isfinite(time))
        time = 0;
    int seconds = fabsf(time);
    int hours = seconds / (60 * 60);
    int minutes = (seconds / 60) % 60;
    seconds %= 60;

    if (hours) {
        if (hours > 9)
            return String::format("%s%02d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds);
        return String::format("%s%01d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds);
    }

    return String::format("%s%02d:%02d", (time < 0 ? "-" : ""), minutes, seconds);
}

static void mediaProgress(WebKitDOMHTMLMediaElement* element, GParamSpec* pspec, ClutterText* timeDisplay)
{
    float duration, current;
    g_object_get(element, "duration", &duration, "current-time", &current, NULL);

    char* timeString = g_strdup_printf("%s / %s", timeToString(current).utf8().data(), timeToString(duration).utf8().data());
    clutter_text_set_text(CLUTTER_TEXT(timeDisplay), timeString);
    g_free(timeString);
}

gboolean webkit_iweb_view_base_handle_fullscreen_media(WebKitIWebView* view,
                                                       WebKitDOMHTMLVideoElement* element,
                                                       ClutterActor* texture)
{
    g_assert(!stage);
    g_assert(!videoTexture);
    g_assert(!controlsBox);

    videoTexture = texture;

    stage = clutter_stage_new();
    clutter_container_add_actor(CLUTTER_CONTAINER(stage), texture);

    ClutterLayoutManager* layoutManager = clutter_box_layout_new();
    g_object_set(layoutManager, "spacing", 5, NULL);

    controlsBox = clutter_box_new(layoutManager);
    clutter_container_add_actor(CLUTTER_CONTAINER(stage), controlsBox);

    // Play/pause button.
    ClutterActor* button = clutter_cairo_texture_new(20, 20);
    clutter_actor_set_reactive(button, TRUE);
    clutter_container_add_actor(CLUTTER_CONTAINER(controlsBox), button);

    // Paint the button.
    g_object_set_data(G_OBJECT(button), "webkit-clutter-media-button-type", GUINT_TO_POINTER(widgetMediaPlayButton));
    repaintMediaButton(CLUTTER_CAIRO_TEXTURE(button), WEBKIT_DOM_HTML_MEDIA_ELEMENT(element));

    // Event handling.
    g_signal_connect(button, "enter-event",
                     G_CALLBACK(mediaMediaButtonEnterEvent), element);
    g_signal_connect(button, "leave-event",
                     G_CALLBACK(mediaMediaButtonLeaveEvent), element);
    g_signal_connect(button, "button-release-event",
                     G_CALLBACK(mediaPlayButtonReleaseEvent), view);

    // Media events.
    pausedNotifyId = g_signal_connect(element, "notify::paused",
                                      G_CALLBACK(mediaPlayingChanged), button);

    // Time display.
    ClutterActor* timeDisplay = clutter_text_new();
    clutter_container_add_actor(CLUTTER_CONTAINER(controlsBox), timeDisplay);
    clutter_text_set_text(CLUTTER_TEXT(timeDisplay), "0:00:00 / 0:00:00");

    ClutterColor color;
    clutter_color_from_string(&color, "white");
    clutter_text_set_color(CLUTTER_TEXT(timeDisplay), &color);

    currentTimeNotifyId = g_signal_connect(element, "notify::current-time",
                                           G_CALLBACK(mediaProgress), timeDisplay);

    // Exit fullscreen button.
    button = clutter_cairo_texture_new(20, 20);
    clutter_actor_set_reactive(button, TRUE);
    clutter_container_add_actor(CLUTTER_CONTAINER(controlsBox), button);

    // Paint the button.
    g_object_set_data(G_OBJECT(button), "webkit-clutter-media-button-type", GUINT_TO_POINTER(widgetMediaFullscreenButton));
    repaintMediaButton(CLUTTER_CAIRO_TEXTURE(button), WEBKIT_DOM_HTML_MEDIA_ELEMENT(element));

    // Event handling.
    g_signal_connect(button, "enter-event",
                     G_CALLBACK(mediaMediaButtonEnterEvent), element);
    g_signal_connect(button, "leave-event",
                     G_CALLBACK(mediaMediaButtonLeaveEvent), element);
    g_signal_connect(button, "button-release-event",
                     G_CALLBACK(mediaExitFullscreenButtonReleaseEvent), view);

    placeControlsBox();
    clutter_actor_set_opacity(controlsBox, 255);

    clutter_actor_show_all(stage);
    clutter_stage_set_fullscreen(CLUTTER_STAGE(stage), TRUE);

    g_signal_connect(stage, "notify::allocation",
                     G_CALLBACK(allocationChanged), 0);

    g_signal_connect(stage, "key-release-event",
                     G_CALLBACK(keyReleaseEvent), view);

    g_signal_connect(stage, "motion-event",
                     G_CALLBACK(motionEvent), controlsBox);

    return TRUE;
}

gboolean webkit_iweb_view_base_handle_exit_fullscreen_media(WebKitIWebView* view,
                                                        ClutterActor* texture)
{
    if (pausedNotifyId) {
        g_source_remove(pausedNotifyId);
        pausedNotifyId = 0;
    }

    if (currentTimeNotifyId) {
        g_source_remove(currentTimeNotifyId);
        currentTimeNotifyId = 0;
    }

    clutter_container_remove_actor(CLUTTER_CONTAINER(stage), texture);
    clutter_actor_destroy(stage);

    stage = videoTexture = controlsBox = 0;

    return TRUE;
}
#endif // ENABLE(VIDEO)

void
webkit_iweb_view_base_add_plugin_actor(WebKitIWebView* view,
                                       ClutterActor* plugin)
{
    g_return_if_fail(WEBKIT_IS_IWEB_VIEW(view));
    g_return_if_fail(CLUTTER_IS_ACTOR(plugin));

#if ENABLE(NETSCAPE_PLUGIN_API)
    ClutterActor* stage = clutter_actor_get_stage(CLUTTER_ACTOR(view));
    if (stage)
        clutter_actor_add_child(stage, plugin);
#endif
}

void
webkit_iweb_view_base_remove_plugin_actor(WebKitIWebView* view,
                                          ClutterActor* plugin)
{
    g_return_if_fail(WEBKIT_IS_IWEB_VIEW(view));
    g_return_if_fail(CLUTTER_IS_ACTOR(plugin));

#if ENABLE(NETSCAPE_PLUGIN_API)
    ClutterActor* parent = clutter_actor_get_parent(plugin);
    if (plugin)
        clutter_actor_remove_child(parent, plugin);
#endif
}

static void webkit_iweb_view_default_init(WebKitIWebViewInterface* iface)
{
    iface->get_page = webkit_iweb_view_base_get_page;
    iface->get_render_theme = webkit_iweb_view_base_get_render_theme;
    iface->invalidate = webkit_iweb_view_base_invalidate;
    iface->invalidate_rect = webkit_iweb_view_base_invalidate_rect;
    iface->get_window_rect = webkit_iweb_view_base_get_window_rect;
    iface->set_window_rect = webkit_iweb_view_base_set_window_rect;
    iface->post_commit_setup = webkit_iweb_view_base_post_commit_setup;

    iface->add_tile = webkit_iweb_view_base_add_tile;

    iface->add_plugin_actor = webkit_iweb_view_base_add_plugin_actor;
    iface->remove_plugin_actor = webkit_iweb_view_base_remove_plugin_actor;

#if ENABLE(VIDEO)
    iface->handle_fullscreen_media = webkit_iweb_view_base_handle_fullscreen_media;
    iface->handle_exit_fullscreen_media = webkit_iweb_view_base_handle_exit_fullscreen_media;

    webkit_iweb_view_signals[MEDIA_FULLSCREEN] = g_signal_new("media-fullscreen",
            WEBKIT_TYPE_IWEB_VIEW,
            (GSignalFlags)G_SIGNAL_RUN_LAST,
            0,
            g_signal_accumulator_true_handled,
            NULL,
            webkit_marshal_BOOLEAN__OBJECT_OBJECT,
            G_TYPE_BOOLEAN, 2,
            WEBKIT_TYPE_DOM_HTML_VIDEO_ELEMENT,
            CLUTTER_TYPE_ACTOR);

    webkit_iweb_view_signals[MEDIA_EXIT_FULLSCREEN] = g_signal_new("media-exit-fullscreen",
            WEBKIT_TYPE_IWEB_VIEW,
            (GSignalFlags)G_SIGNAL_RUN_LAST,
            0,
            g_signal_accumulator_true_handled,
            NULL,
            webkit_marshal_BOOLEAN__OBJECT,
            G_TYPE_BOOLEAN, 1,
            CLUTTER_TYPE_ACTOR);
#endif
}

#if USE(ACCELERATED_COMPOSITING)
void webkit_iweb_view_set_root_graphics_layer(WebKitIWebView* view, GraphicsLayerClutter* layer)
{
    g_return_if_fail(WEBKIT_IS_IWEB_VIEW(view));

    WebKitWebPage* webPage = webkit_iweb_view_get_page(view);
    GraphicsLayerClutter* currentLayer = webkit_web_page_get_root_layer(webPage);
    if (currentLayer) {
        LOG(AcceleratedCompositing, "The root GraphicsLayer is not NULL\n");
        webkit_web_page_set_root_layer(webPage, NULL);
    }

    if (layer) {
        graphicsLayerActorSetLayerType(GRAPHICS_LAYER_ACTOR(layer->platformLayer()), GraphicsLayerClutter::LayerTypeRootLayer);
        webkit_web_page_set_root_layer(webPage, layer);
        LOG(AcceleratedCompositing, "The root GraphicsLayer has been added to the view container\n");
    }

    Frame* frame = core(webkit_iweb_view_get_page(view))->mainFrame();
    frame->view()->flushCompositingStateIncludingSubframes();
}

static gboolean sync_layers(gpointer data)
{
    WebKitIWebView* view = (WebKitIWebView*)data;
    Frame* frame = core(webkit_iweb_view_get_page(view))->mainFrame();

    bool synced = frame->view()->flushCompositingStateIncludingSubframes();

    // If this returns false we haven't been able to sync yet, so we'll retry.
    if (!synced)
        return TRUE;

    g_object_set_data(G_OBJECT(view), "webkit-iweb-view-sync-request-id", 0);
    return FALSE;
}

static void cancelPendingLayersSync(gpointer data)
{
    guint id = GPOINTER_TO_UINT(data);
    g_source_remove(id);
}

void webkit_iweb_view_mark_for_sync(WebKitIWebView* view)
{
    gpointer data = g_object_get_data(G_OBJECT(view), "webkit-iweb-view-sync-request-id");
    if (data && GPOINTER_TO_UINT(data))
        return;

    guint id = g_idle_add_full(CLUTTER_PRIORITY_REDRAW, sync_layers, view, 0);

    GObject* object = G_OBJECT(view);
    g_object_set_data(object, "webkit-iweb-view-sync-request-id", GUINT_TO_POINTER(id));
    g_object_weak_ref(object, reinterpret_cast<GWeakNotify>(cancelPendingLayersSync), GUINT_TO_POINTER(id));
}

gboolean webkit_iweb_view_allows_accelerated_compositing(WebKitIWebView* view)
{
    return TRUE;
}

#endif

/* Default method implementations, to be used by any toolkit library. */
gboolean webkit_iweb_view_button_press_event(ClutterActor* actor,
                                             ClutterButtonEvent* event)
{
    clutter_actor_grab_key_focus(actor);

    PlatformMouseEvent platformEvent(actor, event);
    Frame* frame = core(webkit_iweb_view_get_page(WEBKIT_IWEB_VIEW(actor)))->mainFrame();
    if (!frame->view())
        return FALSE;

    if (clutter_event_is_pointer_emulated((ClutterEvent*)event))
        return FALSE;

    return frame->eventHandler()->handleMousePressEvent(platformEvent);
}

gboolean webkit_iweb_view_button_release_event(ClutterActor* actor,
                                               ClutterButtonEvent* event)
{
    PlatformMouseEvent platformEvent(actor, event);
    Frame* frame = core(webkit_iweb_view_get_page(WEBKIT_IWEB_VIEW(actor)))->mainFrame();
    if (!frame->view())
        return FALSE;

    if (clutter_event_is_pointer_emulated((ClutterEvent*)event))
        return FALSE;

    return frame->eventHandler()->handleMouseReleaseEvent(platformEvent);
}

gboolean webkit_iweb_view_motion_event(ClutterActor* actor,
                                       ClutterMotionEvent* event)
{
    Frame* frame = core(webkit_iweb_view_get_page(WEBKIT_IWEB_VIEW(actor)))->mainFrame();
    if (!frame->view())
        return FALSE;

    if (clutter_event_is_pointer_emulated((ClutterEvent*)event))
        return FALSE;

    return frame->eventHandler()->mouseMoved(PlatformMouseEvent(actor, event));
}

gboolean webkit_iweb_view_scroll_event(ClutterActor* actor,
                                       ClutterScrollEvent* event)
{
    Frame* frame = core(webkit_iweb_view_get_page(WEBKIT_IWEB_VIEW(actor)))->mainFrame();
    if (!frame->view())
        return FALSE;

    // Ignore emulated scroll events, we handle the SMOOTH ones if available
    if (clutter_event_is_pointer_emulated((ClutterEvent*)event))
        return FALSE;

    PlatformWheelEvent wheelEvent(event);
    return frame->eventHandler()->handleWheelEvent(wheelEvent);
}

gboolean webkit_iweb_view_key_event(ClutterActor* actor, ClutterKeyEvent* event)
{
    Frame* frame = core(webkit_iweb_view_get_page(WEBKIT_IWEB_VIEW(actor)))->focusController()->focusedOrMainFrame();
    if (!frame->view())
        return FALSE;

    return frame->eventHandler()->keyEvent(PlatformKeyboardEvent(event));
}

gboolean webkit_iweb_view_touch_event(ClutterActor* actor, ClutterTouchEvent* event)
{
#if ENABLE(TOUCH_EVENTS)
    Frame* frame = core(webkit_iweb_view_get_page(WEBKIT_IWEB_VIEW(actor)))->focusController()->focusedOrMainFrame();
    if (!frame->view())
        return FALSE;

    return frame->eventHandler()->handleTouchEvent(PlatformTouchEvent(actor, event));
#else
    return FALSE;
#endif
}

void webkit_iweb_view_key_focus_in(ClutterActor* actor)
{
    // FIXME - WebKitGTK+ handles this differently depending on whether the
    // toplevel has focus - should we?
    Page* page = core(webkit_iweb_view_get_page(WEBKIT_IWEB_VIEW(actor)));
    FocusController* focusController = page->focusController();

    focusController->setActive(true);
    if (!focusController->focusedFrame())
        focusController->setFocusedFrame(page->mainFrame());

    focusController->setFocused(true);
}

void webkit_iweb_view_key_focus_out(ClutterActor* actor)
{
    Page* page = core(webkit_iweb_view_get_page(WEBKIT_IWEB_VIEW(actor)));
    if (page) {
        page->focusController()->setActive(false);
        page->focusController()->setFocused(false);
    }
}

#if ENABLE(GESTURE_EVENTS)
gboolean webkit_iweb_view_gesture_scroll_begin(ClutterActor*       actor,
                                               gint64              timestamp,
                                               gfloat              event_x,
                                               gfloat              event_y)
{
    Frame* frame = core(webkit_iweb_view_get_page(WEBKIT_IWEB_VIEW(actor)))->mainFrame();
    if (!frame->view())
        return FALSE;

    IntPoint position;
    IntPoint globalPosition;
    UtilClutter::convertEventCoords(actor, event_x, event_y, position, globalPosition);

    PlatformGestureEvent scrollEvent(PlatformEvent::GestureScrollBegin,
            position,
            globalPosition,
            timestamp/1000.0,
            0,
            0,
            false,
            false,
            false,
            false);
    return frame->eventHandler()->handleGestureEvent(scrollEvent);
}

gboolean webkit_iweb_view_gesture_scroll_update(ClutterActor*       actor,
                                                gint64              timestamp,
                                                gfloat              event_x,
                                                gfloat              event_y,
                                                gfloat              delta_x,
                                                gfloat              delta_y)
{
    Frame* frame = core(webkit_iweb_view_get_page(WEBKIT_IWEB_VIEW(actor)))->mainFrame();
    if (!frame->view())
        return FALSE;

    IntPoint position;
    IntPoint globalPosition;
    UtilClutter::convertEventCoords(actor, event_x, event_y, position, globalPosition);

    PlatformGestureEvent scrollEvent(PlatformEvent::GestureScrollUpdate,
            position,
            globalPosition,
            timestamp/1000.0,
            delta_x,
            delta_y,
            false,
            false,
            false,
            false);
    return frame->eventHandler()->handleGestureEvent(scrollEvent);
}

gboolean webkit_iweb_view_gesture_scroll_end(ClutterActor*       actor,
                                             gint64              timestamp,
                                             gfloat              event_x,
                                             gfloat              event_y)
{
    Frame* frame = core(webkit_iweb_view_get_page(WEBKIT_IWEB_VIEW(actor)))->mainFrame();
    if (!frame->view())
        return FALSE;

    IntPoint position;
    IntPoint globalPosition;
    UtilClutter::convertEventCoords(actor, event_x, event_y, position, globalPosition);

    PlatformGestureEvent scrollEvent(PlatformEvent::GestureScrollEnd,
            position,
            globalPosition,
            timestamp/1000.0,
            0,
            0,
            false,
            false,
            false,
            false);
    return frame->eventHandler()->handleGestureEvent(scrollEvent);
}

gboolean webkit_iweb_view_gesture_scroll_momentum_begin(ClutterActor*       actor,
                                                        gint64              timestamp,
                                                        gfloat              event_x,
                                                        gfloat              event_y)
{
    Frame* frame = core(webkit_iweb_view_get_page(WEBKIT_IWEB_VIEW(actor)))->mainFrame();
    if (!frame->view())
        return FALSE;

    IntPoint position;
    IntPoint globalPosition;
    UtilClutter::convertEventCoords(actor, event_x, event_y, position, globalPosition);

    PlatformGestureEvent scrollEvent(PlatformEvent::GestureScrollMomentumBegin,
            position,
            globalPosition,
            timestamp/1000.0,
            0,
            0,
            false,
            false,
            false,
            false);
    return frame->eventHandler()->handleGestureEvent(scrollEvent);
}

gboolean webkit_iweb_view_gesture_scroll_momentum_update(ClutterActor*       actor,
                                                         gint64              timestamp,
                                                         gfloat              event_x,
                                                         gfloat              event_y,
                                                         gfloat              delta_x,
                                                         gfloat              delta_y)
{
    Frame* frame = core(webkit_iweb_view_get_page(WEBKIT_IWEB_VIEW(actor)))->mainFrame();
    if (!frame->view())
        return FALSE;

    IntPoint position;
    IntPoint globalPosition;
    UtilClutter::convertEventCoords(actor, event_x, event_y, position, globalPosition);

    PlatformGestureEvent scrollEvent(PlatformEvent::GestureScrollMomentumUpdate,
            position,
            globalPosition,
            timestamp/1000.0,
            delta_x,
            delta_y,
            false,
            false,
            false,
            false);
    return frame->eventHandler()->handleGestureEvent(scrollEvent);
}

gboolean webkit_iweb_view_gesture_scroll_momentum_end(ClutterActor*       actor,
                                                      gint64              timestamp,
                                                      gfloat              event_x,
                                                      gfloat              event_y)
{
    Frame* frame = core(webkit_iweb_view_get_page(WEBKIT_IWEB_VIEW(actor)))->mainFrame();
    if (!frame->view())
        return FALSE;

    IntPoint position;
    IntPoint globalPosition;
    UtilClutter::convertEventCoords(actor, event_x, event_y, position, globalPosition);

    PlatformGestureEvent scrollEvent(PlatformEvent::GestureScrollMomentumEnd,
            position,
            globalPosition,
            timestamp/1000.0,
            0,
            0,
            false,
            false,
            false,
            false);
    return frame->eventHandler()->handleGestureEvent(scrollEvent);
}

gboolean webkit_iweb_view_gesture_tap_begin(ClutterActor*       actor,
                                            gint64              timestamp,
                                            gfloat              event_x,
                                            gfloat              event_y,
                                            ClutterModifierType state)
{
    Frame* frame = core(webkit_iweb_view_get_page(WEBKIT_IWEB_VIEW(actor)))->mainFrame();
    if (!frame->view())
        return FALSE;

    IntPoint position;
    IntPoint globalPosition;
    UtilClutter::convertEventCoords(actor, event_x, event_y, position, globalPosition);

    PlatformGestureEvent tapEvent(PlatformEvent::GestureTapDown,
            position,
            globalPosition,
            timestamp/1000.0,
            IntSize(default_touch_radius, default_touch_radius),
            FloatPoint(),
            state & CLUTTER_SHIFT_MASK,
            state & CLUTTER_CONTROL_MASK,
            state & CLUTTER_MOD1_MASK,
            state & CLUTTER_META_MASK);
    return frame->eventHandler()->handleGestureEvent(tapEvent);
}

gboolean webkit_iweb_view_gesture_tap(ClutterActor*       actor,
                                      gint64              timestamp,
                                      gfloat              event_x,
                                      gfloat              event_y,
                                      ClutterModifierType state)
{
    Frame* frame = core(webkit_iweb_view_get_page(WEBKIT_IWEB_VIEW(actor)))->mainFrame();
    if (!frame->view())
        return FALSE;

    IntPoint position;
    IntPoint globalPosition;
    UtilClutter::convertEventCoords(actor, event_x, event_y, position, globalPosition);

    PlatformGestureEvent tapEvent(PlatformEvent::GestureTap,
            position,
            globalPosition,
            timestamp/1000.0,
            IntSize(default_touch_radius, default_touch_radius),
            FloatPoint(),
            state & CLUTTER_SHIFT_MASK,
            state & CLUTTER_CONTROL_MASK,
            state & CLUTTER_MOD1_MASK,
            state & CLUTTER_META_MASK);
    return frame->eventHandler()->handleGestureEvent(tapEvent);
}

gboolean webkit_iweb_view_gesture_tap_cancel(ClutterActor*       actor,
                                             gint64              timestamp,
                                             gfloat              event_x,
                                             gfloat              event_y,
                                             ClutterModifierType state)
{
    Frame* frame = core(webkit_iweb_view_get_page(WEBKIT_IWEB_VIEW(actor)))->mainFrame();
    if (!frame->view())
        return FALSE;

    IntPoint position;
    IntPoint globalPosition;
    UtilClutter::convertEventCoords(actor, event_x, event_y, position, globalPosition);

    PlatformGestureEvent tapEvent(PlatformEvent::GestureTapDownCancel,
            position,
            globalPosition,
            timestamp/1000.0,
            IntSize(default_touch_radius, default_touch_radius),
            FloatPoint(),
            state & CLUTTER_SHIFT_MASK,
            state & CLUTTER_CONTROL_MASK,
            state & CLUTTER_MOD1_MASK,
            state & CLUTTER_META_MASK);
    return frame->eventHandler()->handleGestureEvent(tapEvent);
}
#endif

/* Generic functions. */
void webkit_iweb_view_load_uri(WebKitIWebView* view,
                               const gchar* uri)
{
    WebKitWebPage* page = WEBKIT_IWEB_VIEW_GET_INTERFACE(view)->get_page(view);
    if (page)
        webkit_web_page_load_uri(page, uri);
}

/**
 * webkit_iweb_view_load_string:
 * @view: a #WebKitIWebView
 * @content: an URI string
 * @mime_type: the MIME type, or %NULL
 * @encoding: the encoding, or %NULL
 * @base_uri: the base URI for relative locations
 *
 * Requests loading of the given @content with the specified @mime_type,
 * @encoding and @base_uri.
 *
 * If @mime_type is %NULL, "text/html" is assumed.
 *
 * If @encoding is %NULL, "UTF-8" is assumed.
 */
void webkit_iweb_view_load_string(WebKitIWebView* view, const gchar* content, const gchar* mimeType, const gchar* encoding, const gchar* baseUri)
{
    WebKitWebPage* page = WEBKIT_IWEB_VIEW_GET_INTERFACE(view)->get_page(view);
    if (page)
        webkit_web_page_load_string(page, content, mimeType, encoding, baseUri);
}

WebKitWebPage* webkit_iweb_view_get_page(WebKitIWebView* view)
{
    return WEBKIT_IWEB_VIEW_GET_INTERFACE(view)->get_page(view);
}

WebKitIRenderTheme* webkit_iweb_view_get_render_theme(WebKitIWebView* view)
{
    return WEBKIT_IWEB_VIEW_GET_INTERFACE(view)->get_render_theme(view);
}

WebKitIPopupMenu* webkit_iweb_view_create_popup_menu(WebKitIWebView* view)
{
    return WEBKIT_IWEB_VIEW_GET_INTERFACE(view)->create_popup_menu(view);
}

void webkit_iweb_view_invalidate(WebKitIWebView* view)
{
    WEBKIT_IWEB_VIEW_GET_INTERFACE(view)->invalidate(view);
}

void webkit_iweb_view_invalidate_rect(WebKitIWebView* view,
                                      ClutterGeometry* updateRect)
{
    WEBKIT_IWEB_VIEW_GET_INTERFACE(view)->invalidate_rect(view, updateRect);
}

void webkit_iweb_view_get_window_rect(WebKitIWebView* view,
                                      ClutterGeometry* rect)
{
    WEBKIT_IWEB_VIEW_GET_INTERFACE(view)->get_window_rect(view, rect);
}

void webkit_iweb_view_set_window_rect(WebKitIWebView* view,
                                      ClutterGeometry* rect)
{
    WEBKIT_IWEB_VIEW_GET_INTERFACE(view)->set_window_rect(view, rect);
}

void webkit_iweb_view_post_commit_setup(WebKitIWebView* view,
                                        gboolean resetScrolling)
{
    WEBKIT_IWEB_VIEW_GET_INTERFACE(view)->post_commit_setup(view, resetScrolling);
}

#if ENABLE(GESTURE_EVENTS)
void webkit_iweb_view_cancel_gestures(WebKitIWebView* view)
{
    if (WEBKIT_IWEB_VIEW_GET_INTERFACE(view)->cancel_gestures != NULL)
        WEBKIT_IWEB_VIEW_GET_INTERFACE(view)->cancel_gestures(view);
}
#endif

gboolean webkit_iweb_view_handle_fullscreen_media(WebKitIWebView* view,
                                                  WebKitDOMHTMLVideoElement* element,
                                                  ClutterActor* texture)
{
#if ENABLE(VIDEO)
    WebKitIWebViewInterface* iface = WEBKIT_IWEB_VIEW_GET_INTERFACE(view);

    if (iface->handle_fullscreen_media(view, element, texture))
        return TRUE;

    if (iface->handle_fullscreen_media == webkit_iweb_view_base_handle_fullscreen_media)
        return FALSE;

    return webkit_iweb_view_base_handle_fullscreen_media(view, element, texture);
#else
    return FALSE;
#endif
}

gboolean webkit_iweb_view_handle_exit_fullscreen_media(WebKitIWebView* view,
                                                       ClutterActor* texture)
{
#if ENABLE(VIDEO)
    WebKitIWebViewInterface* iface = WEBKIT_IWEB_VIEW_GET_INTERFACE(view);

    if (iface->handle_exit_fullscreen_media(view, texture))
        return TRUE;

    if (iface->handle_exit_fullscreen_media == webkit_iweb_view_base_handle_exit_fullscreen_media)
        return FALSE;

    return webkit_iweb_view_base_handle_exit_fullscreen_media(view, texture);
#else
    return FALSE;
#endif
}

void webkit_iweb_view_add_plugin_actor(WebKitIWebView* view,
                                       ClutterActor* actor)
{
    WEBKIT_IWEB_VIEW_GET_INTERFACE(view)->add_plugin_actor(view, actor);
}

void webkit_iweb_view_remove_plugin_actor(WebKitIWebView* view,
                                          ClutterActor* actor)
{
    WEBKIT_IWEB_VIEW_GET_INTERFACE(view)->remove_plugin_actor(view, actor);
}

void webkit_iweb_view_add_tile(WebKitIWebView* view, ClutterActor* actor)
{
    WEBKIT_IWEB_VIEW_GET_INTERFACE(view)->add_tile(view, actor);
}

void webkit_iweb_view_delegated_scroll(WebKitIWebView* view,
                                       int x, int y)
{
    g_return_if_fail(WEBKIT_IWEB_VIEW_GET_INTERFACE(view)->delegated_scroll);

    WEBKIT_IWEB_VIEW_GET_INTERFACE(view)->delegated_scroll(view, x, y);
}

#if ENABLE(WEBGL)
void webkit_iweb_view_pop_gl_context (ClutterActor*)
{
    GraphicsContext3DPrivate::popGraphicsContext();
}
#endif
