blob: 12b0abbe38e9abe378dd4aeb09a0d44b7cc00658 [file] [log] [blame]
/*
* Copyright (C) 2010 Apple Inc. All rights reserved.
* Portions Copyright (c) 2010 Motorola Mobility, Inc. All rights reserved.
* Copyright (C) 2013 Gustavo Noronha Silva <gns@gnome.org>.
* Copyright (C) 2011, 2020 Igalia S.L.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "WebKitWebViewBase.h"
#include "APIPageConfiguration.h"
#include "AcceleratedBackingStore.h"
#include "DragSource.h"
#include "DrawingAreaProxyCoordinatedGraphics.h"
#include "DropTarget.h"
#include "InputMethodFilter.h"
#include "KeyBindingTranslator.h"
#include "NativeWebKeyboardEvent.h"
#include "NativeWebMouseEvent.h"
#include "NativeWebWheelEvent.h"
#include "PageClientImpl.h"
#include "PointerLockManager.h"
#include "ViewGestureController.h"
#include "WebEventFactory.h"
#include "WebInspectorProxy.h"
#include "WebKit2Initialize.h"
#include "WebKitEmojiChooser.h"
#include "WebKitInputMethodContextImplGtk.h"
#include "WebKitWebViewAccessible.h"
#include "WebKitWebViewBaseInternal.h"
#include "WebKitWebViewBasePrivate.h"
#include "WebPageGroup.h"
#include "WebPageProxy.h"
#include "WebPopupMenuProxyGtk.h"
#include "WebPreferences.h"
#include "WebProcessPool.h"
#include "WebUserContentControllerProxy.h"
#include <WebCore/ActivityState.h>
#include <WebCore/CairoUtilities.h>
#include <WebCore/GUniquePtrGtk.h>
#include <WebCore/GtkUtilities.h>
#include <WebCore/GtkVersioning.h>
#include <WebCore/NotImplemented.h>
#include <WebCore/PlatformDisplay.h>
#include <WebCore/PlatformKeyboardEvent.h>
#include <WebCore/RefPtrCairo.h>
#include <WebCore/Region.h>
#include <gdk/gdk.h>
#include <gdk/gdkkeysyms.h>
#include <glib/gi18n-lib.h>
#include <memory>
#include <pal/system/SleepDisabler.h>
#include <wtf/Compiler.h>
#include <wtf/HashMap.h>
#include <wtf/MathExtras.h>
#include <wtf/glib/GRefPtr.h>
#include <wtf/glib/RunLoopSourcePriority.h>
#include <wtf/glib/WTFGType.h>
#include <wtf/text/CString.h>
#if ENABLE(FULLSCREEN_API)
#include "WebFullScreenManagerProxy.h"
#endif
using namespace WebKit;
using namespace WebCore;
#if !USE(GTK4)
struct ClickCounter {
public:
void reset()
{
currentClickCount = 0;
previousClickPoint = IntPoint();
previousClickTime = 0;
previousClickButton = 0;
}
int currentClickCountForGdkButtonEvent(GdkEvent* event)
{
int doubleClickDistance = 250;
int doubleClickTime = 5;
g_object_get(gtk_settings_get_for_screen(gdk_event_get_screen(event)),
"gtk-double-click-distance", &doubleClickDistance, "gtk-double-click-time", &doubleClickTime, nullptr);
// GTK+ only counts up to triple clicks, but WebCore wants to know about
// quadruple clicks, quintuple clicks, ad infinitum. Here, we replicate the
// GDK logic for counting clicks.
guint32 eventTime = gdk_event_get_time(event);
if (!eventTime) {
// Real events always have a non-zero time, but events synthesized
// by the WTR do not and we must calculate a time manually. This time
// is not calculated in the WTR, because GTK+ does not work well with
// anything other than GDK_CURRENT_TIME on synthesized events.
eventTime = g_get_monotonic_time() / 1000;
}
GdkEventType type;
guint button;
double x, y;
gdk_event_get_coords(event, &x, &y);
gdk_event_get_button(event, &button);
type = gdk_event_get_event_type(event);
if ((type == GDK_2BUTTON_PRESS || type == GDK_3BUTTON_PRESS)
|| ((std::abs(x - previousClickPoint.x()) < doubleClickDistance)
&& (std::abs(y - previousClickPoint.y()) < doubleClickDistance)
&& (eventTime - previousClickTime < static_cast<unsigned>(doubleClickTime))
&& (button == previousClickButton)))
currentClickCount++;
else
currentClickCount = 1;
previousClickPoint = IntPoint(x, y);
previousClickButton = button;
previousClickTime = eventTime;
return currentClickCount;
}
private:
int currentClickCount;
IntPoint previousClickPoint;
unsigned previousClickButton;
int previousClickTime;
};
#endif
struct MotionEvent {
MotionEvent(const FloatPoint& position, const FloatPoint& globalPosition, WebMouseEvent::Button button, unsigned short buttons, OptionSet<WebEvent::Modifier> modifiers)
: position(position)
, globalPosition(globalPosition)
, button(button)
, buttons(buttons)
, modifiers(modifiers)
{
}
MotionEvent(GtkWidget* widget, GdkEvent* event)
{
double x, y, xRoot, yRoot;
GdkModifierType state;
if (event) {
gdk_event_get_coords(event, &x, &y);
gdk_event_get_root_coords(event, &xRoot, &yRoot);
gdk_event_get_state(event, &state);
} else {
auto* device = gdk_seat_get_pointer(gdk_display_get_default_seat(gtk_widget_get_display(widget)));
widgetDevicePosition(widget, device, &x, &y, &state);
auto rootPoint = widgetRootCoords(widget, x, y);
xRoot = rootPoint.x();
yRoot = rootPoint.y();
}
MotionEvent(FloatPoint(x, y), FloatPoint(xRoot, yRoot), state);
}
MotionEvent(FloatPoint&& position, FloatPoint&& globalPosition, GdkModifierType state)
: position(WTFMove(position))
, globalPosition(WTFMove(globalPosition))
{
if (state & GDK_CONTROL_MASK)
modifiers.add(WebEvent::Modifier::ControlKey);
if (state & GDK_SHIFT_MASK)
modifiers.add(WebEvent::Modifier::ShiftKey);
if (state & GDK_MOD1_MASK)
modifiers.add(WebEvent::Modifier::AltKey);
if (state & GDK_META_MASK)
modifiers.add(WebEvent::Modifier::MetaKey);
if (state & GDK_BUTTON1_MASK) {
button = WebMouseEvent::LeftButton;
buttons |= 1;
}
if (state & GDK_BUTTON2_MASK) {
button = WebMouseEvent::MiddleButton;
buttons |= 4;
}
if (state & GDK_BUTTON3_MASK) {
button = WebMouseEvent::RightButton;
buttons |= 2;
}
}
FloatSize delta(GdkEvent* event)
{
double x, y;
gdk_event_get_root_coords(event, &x, &y);
return FloatPoint(x, y) - globalPosition;
}
FloatPoint position;
FloatPoint globalPosition;
WebMouseEvent::Button button { WebMouseEvent::NoButton };
unsigned short buttons { 0 };
OptionSet<WebEvent::Modifier> modifiers;
};
#if !USE(GTK4)
typedef HashMap<GtkWidget*, IntRect> WebKitWebViewChildrenMap;
#endif
typedef HashMap<uint32_t, GUniquePtr<GdkEvent>> TouchEventsMap;
struct _WebKitWebViewBasePrivate {
_WebKitWebViewBasePrivate()
: updateActivityStateTimer(RunLoop::main(), this, &_WebKitWebViewBasePrivate::updateActivityStateTimerFired)
#if GTK_CHECK_VERSION(3, 24, 0)
, releaseEmojiChooserTimer(RunLoop::main(), this, &_WebKitWebViewBasePrivate::releaseEmojiChooserTimerFired)
#endif
{
#if GTK_CHECK_VERSION(3, 24, 0)
releaseEmojiChooserTimer.setPriority(RunLoopSourcePriority::ReleaseUnusedResourcesTimer);
#endif
}
void updateActivityStateTimerFired()
{
if (!pageProxy)
return;
pageProxy->activityStateDidChange(activityStateFlagsToUpdate);
activityStateFlagsToUpdate = { };
}
#if GTK_CHECK_VERSION(3, 24, 0)
void releaseEmojiChooserTimerFired()
{
#if USE(GTK4)
g_clear_pointer(&emojiChooser, gtk_widget_unparent);
#else
if (emojiChooser) {
gtk_widget_destroy(emojiChooser);
emojiChooser = nullptr;
}
#endif
}
#endif
#if !USE(GTK4)
WebKitWebViewChildrenMap children;
#endif
std::unique_ptr<PageClientImpl> pageClient;
RefPtr<WebPageProxy> pageProxy;
bool shouldForwardNextKeyEvent { false };
bool shouldForwardNextWheelEvent { false };
#if !USE(GTK4)
ClickCounter clickCounter;
#endif
CString tooltipText;
IntRect tooltipArea;
GRefPtr<AtkObject> accessible;
GtkWidget* dialog { nullptr };
GtkWidget* inspectorView { nullptr };
AttachmentSide inspectorAttachmentSide { AttachmentSide::Bottom };
unsigned inspectorViewSize { 0 };
GUniquePtr<GdkEvent> contextMenuEvent;
WebContextMenuProxyGtk* activeContextMenuProxy { nullptr };
InputMethodFilter inputMethodFilter;
KeyBindingTranslator keyBindingTranslator;
TouchEventsMap touchEvents;
IntSize contentsSize;
Optional<MotionEvent> lastMotionEvent;
GtkWindow* toplevelOnScreenWindow { nullptr };
#if USE(GTK4)
unsigned long toplevelIsActiveID { 0 };
unsigned long toplevelWindowStateChangedID { 0 };
unsigned long toplevelWindowUnrealizedID { 0 };
#else
unsigned long toplevelFocusInEventID { 0 };
unsigned long toplevelFocusOutEventID { 0 };
unsigned long toplevelWindowStateEventID { 0 };
#endif
unsigned long toplevelWindowRealizedID { 0 };
unsigned long themeChangedID { 0 };
unsigned long applicationPreferDarkThemeID { 0 };
// View State.
OptionSet<ActivityState::Flag> activityState;
OptionSet<ActivityState::Flag> activityStateFlagsToUpdate;
RunLoop::Timer<WebKitWebViewBasePrivate> updateActivityStateTimer;
#if ENABLE(FULLSCREEN_API)
bool fullScreenModeActive { false };
std::unique_ptr<PAL::SleepDisabler> sleepDisabler;
#endif
std::unique_ptr<AcceleratedBackingStore> acceleratedBackingStore;
#if ENABLE(DRAG_SUPPORT)
std::unique_ptr<DragSource> dragSource;
std::unique_ptr<DropTarget> dropTarget;
#endif
#if !USE(GTK4)
std::unique_ptr<GestureController> gestureController;
std::unique_ptr<ViewGestureController> viewGestureController;
bool isBackForwardNavigationGestureEnabled { false };
#endif
#if GTK_CHECK_VERSION(3, 24, 0)
GtkWidget* emojiChooser;
CompletionHandler<void(String)> emojiChooserCompletionHandler;
RunLoop::Timer<WebKitWebViewBasePrivate> releaseEmojiChooserTimer;
#endif
std::unique_ptr<PointerLockManager> pointerLockManager;
};
#if USE(GTK4)
WEBKIT_DEFINE_TYPE(WebKitWebViewBase, webkit_web_view_base, GTK_TYPE_WIDGET)
#else
WEBKIT_DEFINE_TYPE(WebKitWebViewBase, webkit_web_view_base, GTK_TYPE_CONTAINER)
#endif
static void webkitWebViewBaseScheduleUpdateActivityState(WebKitWebViewBase* webViewBase, OptionSet<ActivityState::Flag> flagsToUpdate)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
priv->activityStateFlagsToUpdate.add(flagsToUpdate);
if (priv->updateActivityStateTimer.isActive())
return;
priv->updateActivityStateTimer.startOneShot(0_s);
}
#if !USE(GTK4)
static gboolean toplevelWindowFocusInEvent(GtkWidget* widget, GdkEventFocus*, WebKitWebViewBase* webViewBase)
{
// Spurious focus in events can occur when the window is hidden.
if (!gtk_widget_get_visible(widget))
return FALSE;
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (priv->activityState & ActivityState::WindowIsActive)
return FALSE;
priv->activityState.add(ActivityState::WindowIsActive);
webkitWebViewBaseScheduleUpdateActivityState(webViewBase, ActivityState::WindowIsActive);
return FALSE;
}
static gboolean toplevelWindowFocusOutEvent(GtkWidget*, GdkEventFocus*, WebKitWebViewBase* webViewBase)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (!(priv->activityState & ActivityState::WindowIsActive))
return FALSE;
priv->activityState.remove(ActivityState::WindowIsActive);
webkitWebViewBaseScheduleUpdateActivityState(webViewBase, ActivityState::WindowIsActive);
return FALSE;
}
static gboolean toplevelWindowStateEvent(GtkWidget*, GdkEventWindowState* event, WebKitWebViewBase* webViewBase)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (!(event->changed_mask & GDK_WINDOW_STATE_ICONIFIED))
return FALSE;
bool visible = !(event->new_window_state & GDK_WINDOW_STATE_ICONIFIED);
if ((visible && priv->activityState & ActivityState::IsVisible) || (!visible && !(priv->activityState & ActivityState::IsVisible)))
return FALSE;
if (visible)
priv->activityState.add(ActivityState::IsVisible);
else
priv->activityState.remove(ActivityState::IsVisible);
webkitWebViewBaseScheduleUpdateActivityState(webViewBase, ActivityState::IsVisible);
return FALSE;
}
static void themeChanged(WebKitWebViewBase* webViewBase)
{
webViewBase->priv->pageProxy->themeDidChange();
}
static void applicationPreferDarkThemeChanged(WebKitWebViewBase* webViewBase)
{
webViewBase->priv->pageProxy->effectiveAppearanceDidChange();
}
static void toplevelWindowRealized(WebKitWebViewBase* webViewBase)
{
gtk_widget_realize(GTK_WIDGET(webViewBase));
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (priv->toplevelWindowRealizedID) {
g_signal_handler_disconnect(priv->toplevelOnScreenWindow, priv->toplevelWindowRealizedID);
priv->toplevelWindowRealizedID = 0;
}
}
static void webkitWebViewBaseSetToplevelOnScreenWindow(WebKitWebViewBase* webViewBase, GtkWindow* window)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (priv->toplevelOnScreenWindow == window)
return;
if (priv->toplevelFocusInEventID) {
g_signal_handler_disconnect(priv->toplevelOnScreenWindow, priv->toplevelFocusInEventID);
priv->toplevelFocusInEventID = 0;
}
if (priv->toplevelFocusOutEventID) {
g_signal_handler_disconnect(priv->toplevelOnScreenWindow, priv->toplevelFocusOutEventID);
priv->toplevelFocusOutEventID = 0;
}
if (priv->toplevelWindowStateEventID) {
g_signal_handler_disconnect(priv->toplevelOnScreenWindow, priv->toplevelWindowStateEventID);
priv->toplevelWindowStateEventID = 0;
}
if (priv->toplevelWindowRealizedID) {
g_signal_handler_disconnect(priv->toplevelOnScreenWindow, priv->toplevelWindowRealizedID);
priv->toplevelWindowRealizedID = 0;
}
if (priv->themeChangedID || priv->applicationPreferDarkThemeID) {
auto* settings = gtk_widget_get_settings(GTK_WIDGET(priv->toplevelOnScreenWindow));
if (priv->themeChangedID) {
g_signal_handler_disconnect(settings, priv->themeChangedID);
priv->themeChangedID = 0;
}
if (priv->applicationPreferDarkThemeID) {
g_signal_handler_disconnect(settings, priv->applicationPreferDarkThemeID);
priv->applicationPreferDarkThemeID = 0;
}
}
priv->toplevelOnScreenWindow = window;
if (!priv->toplevelOnScreenWindow) {
OptionSet<ActivityState::Flag> flagsToUpdate;
if (priv->activityState & ActivityState::IsInWindow) {
priv->activityState.remove(ActivityState::IsInWindow);
flagsToUpdate.add(ActivityState::IsInWindow);
}
if (priv->activityState & ActivityState::WindowIsActive) {
priv->activityState.remove(ActivityState::WindowIsActive);
flagsToUpdate.add(ActivityState::IsInWindow);
}
if (flagsToUpdate)
webkitWebViewBaseScheduleUpdateActivityState(webViewBase, flagsToUpdate);
return;
}
priv->toplevelFocusInEventID =
g_signal_connect(priv->toplevelOnScreenWindow, "focus-in-event",
G_CALLBACK(toplevelWindowFocusInEvent), webViewBase);
priv->toplevelFocusOutEventID =
g_signal_connect(priv->toplevelOnScreenWindow, "focus-out-event",
G_CALLBACK(toplevelWindowFocusOutEvent), webViewBase);
priv->toplevelWindowStateEventID =
g_signal_connect(priv->toplevelOnScreenWindow, "window-state-event", G_CALLBACK(toplevelWindowStateEvent), webViewBase);
auto* settings = gtk_widget_get_settings(GTK_WIDGET(priv->toplevelOnScreenWindow));
priv->themeChangedID =
g_signal_connect_swapped(settings, "notify::gtk-theme-name", G_CALLBACK(themeChanged), webViewBase);
priv->applicationPreferDarkThemeID =
g_signal_connect_swapped(settings, "notify::gtk-application-prefer-dark-theme", G_CALLBACK(applicationPreferDarkThemeChanged), webViewBase);
if (gtk_widget_get_realized(GTK_WIDGET(window)))
gtk_widget_realize(GTK_WIDGET(webViewBase));
else
priv->toplevelWindowRealizedID = g_signal_connect_swapped(window, "realize", G_CALLBACK(toplevelWindowRealized), webViewBase);
}
#endif
static void webkitWebViewBaseRealize(GtkWidget* widget)
{
WebKitWebViewBase* webView = WEBKIT_WEB_VIEW_BASE(widget);
WebKitWebViewBasePrivate* priv = webView->priv;
#if USE(GTK4)
GTK_WIDGET_CLASS(webkit_web_view_base_parent_class)->realize(widget);
#else
gtk_widget_set_realized(widget, TRUE);
GtkAllocation allocation;
gtk_widget_get_allocation(widget, &allocation);
GdkWindowAttr attributes;
attributes.window_type = GDK_WINDOW_CHILD;
attributes.x = allocation.x;
attributes.y = allocation.y;
attributes.width = allocation.width;
attributes.height = allocation.height;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.visual = gtk_widget_get_visual(widget);
attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK
| GDK_EXPOSURE_MASK
| GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
| GDK_SCROLL_MASK
| GDK_SMOOTH_SCROLL_MASK
| GDK_POINTER_MOTION_MASK
| GDK_ENTER_NOTIFY_MASK
| GDK_LEAVE_NOTIFY_MASK
| GDK_KEY_PRESS_MASK
| GDK_KEY_RELEASE_MASK
| GDK_BUTTON_MOTION_MASK
| GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON2_MOTION_MASK
| GDK_BUTTON3_MOTION_MASK
| GDK_TOUCH_MASK;
attributes.event_mask |= GDK_TOUCHPAD_GESTURE_MASK;
gint attributesMask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
GdkWindow* window = gdk_window_new(gtk_widget_get_parent_window(widget), &attributes, attributesMask);
gtk_widget_set_window(widget, window);
gdk_window_set_user_data(window, widget);
#endif
auto* imContext = priv->inputMethodFilter.context();
if (WEBKIT_IS_INPUT_METHOD_CONTEXT_IMPL_GTK(imContext))
webkitInputMethodContextImplGtkSetClientWidget(WEBKIT_INPUT_METHOD_CONTEXT_IMPL_GTK(imContext), widget);
if (priv->acceleratedBackingStore)
priv->acceleratedBackingStore->realize();
}
static void webkitWebViewBaseUnrealize(GtkWidget* widget)
{
WebKitWebViewBase* webView = WEBKIT_WEB_VIEW_BASE(widget);
auto* imContext = webView->priv->inputMethodFilter.context();
if (WEBKIT_IS_INPUT_METHOD_CONTEXT_IMPL_GTK(imContext))
webkitInputMethodContextImplGtkSetClientWidget(WEBKIT_INPUT_METHOD_CONTEXT_IMPL_GTK(imContext), nullptr);
if (webView->priv->acceleratedBackingStore)
webView->priv->acceleratedBackingStore->unrealize();
GTK_WIDGET_CLASS(webkit_web_view_base_parent_class)->unrealize(widget);
}
#if !USE(GTK4)
static bool webkitWebViewChildIsInternalWidget(WebKitWebViewBase* webViewBase, GtkWidget* widget)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
return widget == priv->inspectorView || widget == priv->dialog || widget == priv->keyBindingTranslator.widget();
}
static void webkitWebViewBaseContainerAdd(GtkContainer* container, GtkWidget* widget)
{
WebKitWebViewBase* webView = WEBKIT_WEB_VIEW_BASE(container);
WebKitWebViewBasePrivate* priv = webView->priv;
// Internal widgets like the web inspector and authentication dialog have custom
// allocations so we don't need to add them to our list of children.
if (!webkitWebViewChildIsInternalWidget(webView, widget)) {
GtkAllocation childAllocation;
gtk_widget_get_allocation(widget, &childAllocation);
priv->children.set(widget, childAllocation);
}
gtk_widget_set_parent(widget, GTK_WIDGET(container));
}
#endif
void webkitWebViewBaseAddDialog(WebKitWebViewBase* webViewBase, GtkWidget* dialog)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
priv->dialog = dialog;
#if USE(GTK4)
g_object_add_weak_pointer(G_OBJECT(dialog), reinterpret_cast<void**>(&priv->dialog));
#endif
gtk_widget_set_parent(dialog, GTK_WIDGET(webViewBase));
gtk_widget_show(dialog);
// We need to draw the shadow over the widget.
gtk_widget_queue_draw(GTK_WIDGET(webViewBase));
}
#if USE(GTK4)
static void webkitWebViewBaseRemoveDialog(WebKitWebViewBase* webViewBase, GtkWidget* dialog)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (!priv->dialog)
return;
g_object_remove_weak_pointer(G_OBJECT(dialog), reinterpret_cast<void**>(&priv->dialog));
g_clear_pointer(&priv->dialog, gtk_widget_unparent);
}
#endif
#if !USE(GTK4)
static void webkitWebViewBaseContainerRemove(GtkContainer* container, GtkWidget* widget)
{
WebKitWebViewBase* webView = WEBKIT_WEB_VIEW_BASE(container);
WebKitWebViewBasePrivate* priv = webView->priv;
GtkWidget* widgetContainer = GTK_WIDGET(container);
gboolean wasVisible = gtk_widget_get_visible(widget);
gtk_widget_unparent(widget);
if (priv->inspectorView == widget) {
priv->inspectorView = 0;
priv->inspectorViewSize = 0;
} else if (priv->dialog == widget) {
priv->dialog = nullptr;
if (gtk_widget_get_visible(widgetContainer))
gtk_widget_grab_focus(widgetContainer);
} else if (priv->keyBindingTranslator.widget() == widget)
priv->keyBindingTranslator.invalidate();
else {
ASSERT(priv->children.contains(widget));
priv->children.remove(widget);
}
if (wasVisible && gtk_widget_get_visible(widgetContainer))
gtk_widget_queue_resize(widgetContainer);
}
static void webkitWebViewBaseContainerForall(GtkContainer* container, gboolean includeInternals, GtkCallback callback, gpointer callbackData)
{
WebKitWebViewBase* webView = WEBKIT_WEB_VIEW_BASE(container);
WebKitWebViewBasePrivate* priv = webView->priv;
for (const auto& child : copyToVector(priv->children.keys())) {
if (priv->children.contains(child))
(*callback)(child, callbackData);
}
if (includeInternals && priv->keyBindingTranslator.widget())
(*callback)(priv->keyBindingTranslator.widget(), callbackData);
if (includeInternals && priv->inspectorView)
(*callback)(priv->inspectorView, callbackData);
if (includeInternals && priv->dialog)
(*callback)(priv->dialog, callbackData);
}
void webkitWebViewBaseChildMoveResize(WebKitWebViewBase* webView, GtkWidget* child, const IntRect& childRect)
{
const IntRect& geometry = webView->priv->children.get(child);
if (geometry == childRect)
return;
webView->priv->children.set(child, childRect);
gtk_widget_queue_resize_no_redraw(GTK_WIDGET(webView));
}
#endif
void webkitWebViewBaseAddWebInspector(WebKitWebViewBase* webViewBase, GtkWidget* inspector, AttachmentSide attachmentSide)
{
if (webViewBase->priv->inspectorView == inspector && webViewBase->priv->inspectorAttachmentSide == attachmentSide)
return;
webViewBase->priv->inspectorAttachmentSide = attachmentSide;
if (webViewBase->priv->inspectorView == inspector) {
gtk_widget_queue_resize(GTK_WIDGET(webViewBase));
return;
}
webViewBase->priv->inspectorView = inspector;
gtk_widget_set_parent(inspector, GTK_WIDGET(webViewBase));
}
void webkitWebViewBaseRemoveWebInspector(WebKitWebViewBase* webViewBase, GtkWidget* inspector)
{
if (webViewBase->priv->inspectorView != inspector)
return;
#if USE(GTK4)
g_clear_pointer(&webViewBase->priv->inspectorView, gtk_widget_unparent);
#else
gtk_container_remove(GTK_CONTAINER(webViewBase), inspector);
#endif
}
#if GTK_CHECK_VERSION(3, 24, 0)
static void webkitWebViewBaseCompleteEmojiChooserRequest(WebKitWebViewBase* webView, const String& text)
{
if (auto completionHandler = std::exchange(webView->priv->emojiChooserCompletionHandler, nullptr))
completionHandler(text);
}
#endif
static void webkitWebViewBaseDispose(GObject* gobject)
{
WebKitWebViewBase* webView = WEBKIT_WEB_VIEW_BASE(gobject);
#if USE(GTK4)
webkitWebViewBaseRemoveDialog(webView, webView->priv->dialog);
webkitWebViewBaseRemoveWebInspector(webView, webView->priv->inspectorView);
if (auto* widget = webView->priv->keyBindingTranslator.widget())
gtk_widget_unparent(widget);
g_clear_pointer(&webView->priv->emojiChooser, gtk_widget_unparent);
#else
g_clear_pointer(&webView->priv->dialog, gtk_widget_destroy);
webkitWebViewBaseSetToplevelOnScreenWindow(webView, nullptr);
#endif
if (webView->priv->accessible)
webkitWebViewAccessibleSetWebView(WEBKIT_WEB_VIEW_ACCESSIBLE(webView->priv->accessible.get()), nullptr);
#if GTK_CHECK_VERSION(3, 24, 0)
webkitWebViewBaseCompleteEmojiChooserRequest(webView, emptyString());
#endif
if (webView->priv->pointerLockManager) {
webView->priv->pointerLockManager->unlock();
webView->priv->pointerLockManager = nullptr;
}
webView->priv->inputMethodFilter.setContext(nullptr);
webView->priv->pageProxy->close();
webView->priv->acceleratedBackingStore = nullptr;
webView->priv->sleepDisabler = nullptr;
webView->priv->keyBindingTranslator.invalidate();
G_OBJECT_CLASS(webkit_web_view_base_parent_class)->dispose(gobject);
}
#if USE(GTK4)
static void webkitWebViewBaseSnapshot(GtkWidget* widget, GtkSnapshot* snapshot)
{
int scaleFactor = gtk_widget_get_scale_factor(widget);
int width = gtk_widget_get_width(widget) * scaleFactor;
int height = gtk_widget_get_height(widget) * scaleFactor;
if (!width || !height)
return;
WebKitWebViewBase* webViewBase = WEBKIT_WEB_VIEW_BASE(widget);
auto* drawingArea = static_cast<DrawingAreaProxyCoordinatedGraphics*>(webViewBase->priv->pageProxy->drawingArea());
if (!drawingArea)
return;
ASSERT(drawingArea->isInAcceleratedCompositingMode());
webViewBase->priv->acceleratedBackingStore->snapshot(snapshot);
if (webViewBase->priv->inspectorView)
gtk_widget_snapshot_child(widget, webViewBase->priv->inspectorView, snapshot);
if (webViewBase->priv->dialog)
gtk_widget_snapshot_child(widget, webViewBase->priv->dialog, snapshot);
}
#else
static gboolean webkitWebViewBaseDraw(GtkWidget* widget, cairo_t* cr)
{
WebKitWebViewBase* webViewBase = WEBKIT_WEB_VIEW_BASE(widget);
auto* drawingArea = static_cast<DrawingAreaProxyCoordinatedGraphics*>(webViewBase->priv->pageProxy->drawingArea());
if (!drawingArea)
return FALSE;
GdkRectangle clipRect;
if (!gdk_cairo_get_clip_rectangle(cr, &clipRect))
return FALSE;
bool showingNavigationSnapshot = webViewBase->priv->pageProxy->isShowingNavigationGestureSnapshot();
if (showingNavigationSnapshot)
cairo_push_group(cr);
if (drawingArea->isInAcceleratedCompositingMode()) {
ASSERT(webViewBase->priv->acceleratedBackingStore);
webViewBase->priv->acceleratedBackingStore->paint(cr, clipRect);
} else {
WebCore::Region unpaintedRegion; // This is simply unused.
drawingArea->paint(cr, clipRect, unpaintedRegion);
}
if (showingNavigationSnapshot) {
RefPtr<cairo_pattern_t> group = adoptRef(cairo_pop_group(cr));
if (auto* controller = webkitWebViewBaseViewGestureController(webViewBase))
controller->draw(cr, group.get());
}
GTK_WIDGET_CLASS(webkit_web_view_base_parent_class)->draw(widget, cr);
return FALSE;
}
#endif
#if !USE(GTK4)
static void webkitWebViewBaseChildAllocate(GtkWidget* child, gpointer userData)
{
if (!gtk_widget_get_visible(child))
return;
WebKitWebViewBase* webViewBase = WEBKIT_WEB_VIEW_BASE(userData);
WebKitWebViewBasePrivate* priv = webViewBase->priv;
const IntRect& geometry = priv->children.get(child);
if (geometry.isEmpty())
return;
GtkAllocation childAllocation = geometry;
gtk_widget_size_allocate(child, &childAllocation);
priv->children.set(child, IntRect());
}
#endif
#if USE(GTK4)
static void webkitWebViewBaseSizeAllocate(GtkWidget* widget, int width, int height, int baseline)
#else
static void webkitWebViewBaseSizeAllocate(GtkWidget* widget, GtkAllocation* allocation)
#endif
{
#if USE(GTK4)
GTK_WIDGET_CLASS(webkit_web_view_base_parent_class)->size_allocate(widget, width, height, baseline);
GtkAllocation allocationStack = { 0, 0, width, height };
GtkAllocation* allocation = &allocationStack;
#else
GTK_WIDGET_CLASS(webkit_web_view_base_parent_class)->size_allocate(widget, allocation);
#endif
WebKitWebViewBase* webViewBase = WEBKIT_WEB_VIEW_BASE(widget);
#if !USE(GTK4)
gtk_container_foreach(GTK_CONTAINER(webViewBase), webkitWebViewBaseChildAllocate, webViewBase);
#endif
IntRect viewRect(allocation->x, allocation->y, allocation->width, allocation->height);
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (priv->inspectorView) {
GtkAllocation childAllocation = viewRect;
if (priv->inspectorAttachmentSide == AttachmentSide::Bottom) {
int inspectorViewHeight = std::min(static_cast<int>(priv->inspectorViewSize), allocation->height);
childAllocation.x = 0;
childAllocation.y = allocation->height - inspectorViewHeight;
childAllocation.height = inspectorViewHeight;
viewRect.setHeight(std::max(allocation->height - inspectorViewHeight, 1));
} else {
int inspectorViewWidth = std::min(static_cast<int>(priv->inspectorViewSize), allocation->width);
childAllocation.y = 0;
childAllocation.x = allocation->width - inspectorViewWidth;
childAllocation.width = inspectorViewWidth;
viewRect.setWidth(std::max(allocation->width - inspectorViewWidth, 1));
}
gtk_widget_size_allocate(priv->inspectorView, &childAllocation);
}
// The dialogs are centered in the view rect, which means that it
// never overlaps the web inspector. Thus, we need to calculate the allocation here
// after calculating the inspector allocation.
if (priv->dialog) {
GtkRequisition minimumSize;
gtk_widget_get_preferred_size(priv->dialog, &minimumSize, nullptr);
GtkAllocation childAllocation = { 0, 0, std::max(minimumSize.width, viewRect.width()), std::max(minimumSize.height, viewRect.height()) };
gtk_widget_size_allocate(priv->dialog, &childAllocation);
}
#if USE(GTK4)
for (auto* child = gtk_widget_get_first_child(widget); child; child = gtk_widget_get_next_sibling(child)) {
if (GTK_IS_NATIVE(child))
gtk_native_check_resize(GTK_NATIVE(child));
}
#endif
if (auto* drawingArea = static_cast<DrawingAreaProxyCoordinatedGraphics*>(priv->pageProxy->drawingArea()))
drawingArea->setSize(viewRect.size());
}
#if USE(GTK4)
static void webkitWebViewBaseMeasure(GtkWidget* widget, GtkOrientation orientation, int, int* minimumSize, int* naturalSize, int*, int*)
{
WebKitWebViewBasePrivate* priv = WEBKIT_WEB_VIEW_BASE(widget)->priv;
switch (orientation) {
case GTK_ORIENTATION_HORIZONTAL:
*naturalSize = priv->contentsSize.width();
break;
case GTK_ORIENTATION_VERTICAL:
*naturalSize = priv->contentsSize.height();
break;
}
*minimumSize = 0;
}
#else
static void webkitWebViewBaseGetPreferredWidth(GtkWidget* widget, gint* minimumSize, gint* naturalSize)
{
WebKitWebViewBasePrivate* priv = WEBKIT_WEB_VIEW_BASE(widget)->priv;
*minimumSize = 0;
*naturalSize = priv->contentsSize.width();
}
static void webkitWebViewBaseGetPreferredHeight(GtkWidget* widget, gint* minimumSize, gint* naturalSize)
{
WebKitWebViewBasePrivate* priv = WEBKIT_WEB_VIEW_BASE(widget)->priv;
*minimumSize = 0;
*naturalSize = priv->contentsSize.height();
}
#endif
static void webkitWebViewBaseMap(GtkWidget* widget)
{
GTK_WIDGET_CLASS(webkit_web_view_base_parent_class)->map(widget);
WebKitWebViewBase* webViewBase = WEBKIT_WEB_VIEW_BASE(widget);
WebKitWebViewBasePrivate* priv = webViewBase->priv;
OptionSet<ActivityState::Flag> flagsToUpdate;
if (!(priv->activityState & ActivityState::IsVisible))
flagsToUpdate.add(ActivityState::IsVisible);
if (priv->toplevelOnScreenWindow) {
if (!(priv->activityState & ActivityState::IsInWindow))
flagsToUpdate.add(ActivityState::IsInWindow);
if (gtk_window_is_active(GTK_WINDOW(priv->toplevelOnScreenWindow)) && !(priv->activityState & ActivityState::WindowIsActive))
flagsToUpdate.add(ActivityState::WindowIsActive);
}
if (!flagsToUpdate)
return;
priv->activityState.add(flagsToUpdate);
webkitWebViewBaseScheduleUpdateActivityState(webViewBase, flagsToUpdate);
}
static void webkitWebViewBaseUnmap(GtkWidget* widget)
{
GTK_WIDGET_CLASS(webkit_web_view_base_parent_class)->unmap(widget);
WebKitWebViewBase* webViewBase = WEBKIT_WEB_VIEW_BASE(widget);
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (!(priv->activityState & ActivityState::IsVisible))
return;
priv->activityState.remove(ActivityState::IsVisible);
webkitWebViewBaseScheduleUpdateActivityState(webViewBase, ActivityState::IsVisible);
}
#if !USE(GTK4)
static gboolean webkitWebViewBaseFocusInEvent(GtkWidget* widget, GdkEventFocus* event)
{
WebKitWebViewBase* webViewBase = WEBKIT_WEB_VIEW_BASE(widget);
webkitWebViewBaseSetFocus(webViewBase, true);
webViewBase->priv->inputMethodFilter.notifyFocusedIn();
return GTK_WIDGET_CLASS(webkit_web_view_base_parent_class)->focus_in_event(widget, event);
}
static gboolean webkitWebViewBaseFocusOutEvent(GtkWidget* widget, GdkEventFocus* event)
{
WebKitWebViewBase* webViewBase = WEBKIT_WEB_VIEW_BASE(widget);
webkitWebViewBaseSetFocus(webViewBase, false);
webViewBase->priv->inputMethodFilter.notifyFocusedOut();
return GTK_WIDGET_CLASS(webkit_web_view_base_parent_class)->focus_out_event(widget, event);
}
static gboolean webkitWebViewBaseKeyPressEvent(GtkWidget* widget, GdkEventKey* keyEvent)
{
WebKitWebViewBase* webViewBase = WEBKIT_WEB_VIEW_BASE(widget);
WebKitWebViewBasePrivate* priv = webViewBase->priv;
// Since WebProcess key event handling is not synchronous, handle the event in two passes.
// When WebProcess processes the input event, it will call PageClientImpl::doneWithKeyEvent
// with event handled status which determines whether to pass the input event to parent or not
// using gtk_main_do_event().
if (priv->shouldForwardNextKeyEvent) {
priv->shouldForwardNextKeyEvent = false;
return GTK_WIDGET_CLASS(webkit_web_view_base_parent_class)->key_press_event(widget, keyEvent);
}
GdkModifierType state;
guint keyval;
gdk_event_get_state(reinterpret_cast<GdkEvent*>(keyEvent), &state);
gdk_event_get_keyval(reinterpret_cast<GdkEvent*>(keyEvent), &keyval);
#if ENABLE(DEVELOPER_MODE) && OS(LINUX)
if ((state & GDK_CONTROL_MASK) && (state & GDK_SHIFT_MASK) && keyval == GDK_KEY_G) {
auto& preferences = priv->pageProxy->preferences();
preferences.setResourceUsageOverlayVisible(!preferences.resourceUsageOverlayVisible());
return GDK_EVENT_STOP;
}
#endif
if (priv->dialog)
return GTK_WIDGET_CLASS(webkit_web_view_base_parent_class)->key_press_event(widget, keyEvent);
#if ENABLE(FULLSCREEN_API)
if (priv->fullScreenModeActive) {
switch (keyval) {
case GDK_KEY_Escape:
case GDK_KEY_f:
case GDK_KEY_F:
priv->pageProxy->fullScreenManager()->requestExitFullScreen();
return GDK_EVENT_STOP;
default:
break;
}
}
#endif
auto filterResult = priv->inputMethodFilter.filterKeyEvent(reinterpret_cast<GdkEvent*>(keyEvent));
if (!filterResult.handled) {
priv->pageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(reinterpret_cast<GdkEvent*>(keyEvent), filterResult.keyText,
priv->keyBindingTranslator.commandsForKeyEvent(keyEvent)));
}
return GDK_EVENT_STOP;
}
static gboolean webkitWebViewBaseKeyReleaseEvent(GtkWidget* widget, GdkEventKey* keyEvent)
{
WebKitWebViewBase* webViewBase = WEBKIT_WEB_VIEW_BASE(widget);
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (!priv->inputMethodFilter.filterKeyEvent(reinterpret_cast<GdkEvent*>(keyEvent)).handled)
priv->pageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(reinterpret_cast<GdkEvent*>(keyEvent), { }, { }));
return GDK_EVENT_STOP;
}
#endif
#if USE(GTK4)
static void webkitWebViewBaseFocusEnter(WebKitWebViewBase* webViewBase, GtkEventController*)
{
webkitWebViewBaseSetFocus(webViewBase, true);
webViewBase->priv->inputMethodFilter.notifyFocusedIn();
}
static void webkitWebViewBaseFocusLeave(WebKitWebViewBase* webViewBase, GtkEventController*)
{
webkitWebViewBaseSetFocus(webViewBase, false);
webViewBase->priv->inputMethodFilter.notifyFocusedOut();
}
static gboolean webkitWebViewBaseKeyPressed(WebKitWebViewBase* webViewBase, unsigned keyval, unsigned, GdkModifierType state, GtkEventController* controller)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
// Since WebProcess key event handling is not synchronous, handle the event in two passes.
// When WebProcess processes the input event, it will call PageClientImpl::doneWithKeyEvent
// with event handled status which determines whether to pass the input event to parent or not
// using gdk_display_put_event().
if (priv->shouldForwardNextKeyEvent) {
priv->shouldForwardNextKeyEvent = false;
return GDK_EVENT_PROPAGATE;
}
#if ENABLE(DEVELOPER_MODE) && OS(LINUX)
if ((state & GDK_CONTROL_MASK) && (state & GDK_SHIFT_MASK) && keyval == GDK_KEY_G) {
auto& preferences = priv->pageProxy->preferences();
preferences.setResourceUsageOverlayVisible(!preferences.resourceUsageOverlayVisible());
return GDK_EVENT_STOP;
}
#endif
if (priv->dialog)
return gtk_event_controller_key_forward(GTK_EVENT_CONTROLLER_KEY(controller), priv->dialog);
#if ENABLE(FULLSCREEN_API)
if (priv->fullScreenModeActive) {
switch (keyval) {
case GDK_KEY_Escape:
case GDK_KEY_f:
case GDK_KEY_F:
priv->pageProxy->fullScreenManager()->requestExitFullScreen();
return GDK_EVENT_STOP;
default:
break;
}
}
#endif
auto* event = gtk_event_controller_get_current_event(controller);
auto filterResult = priv->inputMethodFilter.filterKeyEvent(event);
if (!filterResult.handled) {
priv->pageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(event, filterResult.keyText,
priv->keyBindingTranslator.commandsForKeyEvent(GTK_EVENT_CONTROLLER_KEY(controller))));
}
return GDK_EVENT_STOP;
}
static void webkitWebViewBaseKeyReleased(WebKitWebViewBase* webViewBase, unsigned, unsigned, GdkModifierType, GtkEventController* controller)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
auto* event = gtk_event_controller_get_current_event(controller);
if (!priv->inputMethodFilter.filterKeyEvent(event).handled)
priv->pageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(event, { }, { }));
}
#endif
#if !USE(GTK4)
static void webkitWebViewBaseHandleMouseEvent(WebKitWebViewBase* webViewBase, GdkEvent* event)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
ASSERT(!priv->dialog);
int clickCount = 0;
Optional<FloatSize> movementDelta;
GdkEventType eventType = gdk_event_get_event_type(event);
switch (eventType) {
case GDK_BUTTON_PRESS:
case GDK_2BUTTON_PRESS:
case GDK_3BUTTON_PRESS: {
// For double and triple clicks GDK sends both a normal button press event
// and a specific type (like GDK_2BUTTON_PRESS). If we detect a special press
// coming up, ignore this event as it certainly generated the double or triple
// click. The consequence of not eating this event is two DOM button press events
// are generated.
GUniquePtr<GdkEvent> nextEvent(gdk_event_peek());
if (nextEvent && (nextEvent->any.type == GDK_2BUTTON_PRESS || nextEvent->any.type == GDK_3BUTTON_PRESS))
return;
priv->inputMethodFilter.cancelComposition();
guint button;
gdk_event_get_button(event, &button);
// If it's a right click event save it as a possible context menu event.
if (button == GDK_BUTTON_SECONDARY)
priv->contextMenuEvent.reset(gdk_event_copy(event));
clickCount = priv->clickCounter.currentClickCountForGdkButtonEvent(event);
}
FALLTHROUGH;
case GDK_BUTTON_RELEASE:
gtk_widget_grab_focus(GTK_WIDGET(webViewBase));
break;
case GDK_MOTION_NOTIFY:
// Pointer Lock. 7.1 Attributes: movementX/Y must be updated regardless of pointer lock state.
if (priv->lastMotionEvent)
movementDelta = priv->lastMotionEvent->delta(event);
priv->lastMotionEvent = MotionEvent(GTK_WIDGET(webViewBase), event);
break;
case GDK_ENTER_NOTIFY:
case GDK_LEAVE_NOTIFY:
break;
default:
ASSERT_NOT_REACHED();
}
priv->pageProxy->handleMouseEvent(NativeWebMouseEvent(event, clickCount, movementDelta));
}
static gboolean webkitWebViewBaseButtonPressEvent(GtkWidget* widget, GdkEventButton* event)
{
WebKitWebViewBase* webViewBase = WEBKIT_WEB_VIEW_BASE(widget);
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (priv->dialog)
return GDK_EVENT_STOP;
webkitWebViewBaseHandleMouseEvent(webViewBase, reinterpret_cast<GdkEvent*>(event));
return GDK_EVENT_STOP;
}
static gboolean webkitWebViewBaseButtonReleaseEvent(GtkWidget* widget, GdkEventButton* event)
{
WebKitWebViewBase* webViewBase = WEBKIT_WEB_VIEW_BASE(widget);
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (priv->dialog)
return GDK_EVENT_STOP;
webkitWebViewBaseHandleMouseEvent(webViewBase, reinterpret_cast<GdkEvent*>(event));
return GDK_EVENT_STOP;
}
#endif
#if USE(GTK4)
static void webkitWebViewBaseButtonPressed(WebKitWebViewBase* webViewBase, int clickCount, double x, double y, GtkGesture* gesture)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (priv->dialog)
return;
if (clickCount == 1)
priv->inputMethodFilter.cancelComposition();
gtk_widget_grab_focus(GTK_WIDGET(webViewBase));
auto* sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
gtk_gesture_set_sequence_state(gesture, sequence, GTK_EVENT_SEQUENCE_CLAIMED);
auto button = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(gesture));
auto* event = gtk_gesture_get_last_event(gesture, sequence);
// If it's a right click event save it as a possible context menu event.
if (button == GDK_BUTTON_SECONDARY)
priv->contextMenuEvent.reset(gdk_event_copy(event));
priv->pageProxy->handleMouseEvent(NativeWebMouseEvent(event, { clampToInteger(x), clampToInteger(y) }, clickCount, WTF::nullopt));
}
static void webkitWebViewBaseButtonReleased(WebKitWebViewBase* webViewBase, int clickCount, double x, double y, GtkGesture* gesture)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (priv->dialog)
return;
gtk_widget_grab_focus(GTK_WIDGET(webViewBase));
auto* sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
gtk_gesture_set_sequence_state(gesture, sequence, GTK_EVENT_SEQUENCE_CLAIMED);
priv->pageProxy->handleMouseEvent(NativeWebMouseEvent(gtk_gesture_get_last_event(gesture, sequence), { clampToInteger(x), clampToInteger(y) }, clickCount, WTF::nullopt));
}
#endif
#if !USE(GTK4)
static void webkitWebViewBaseHandleWheelEvent(WebKitWebViewBase* webViewBase, GdkEvent* event, Optional<WebWheelEvent::Phase> phase = WTF::nullopt, Optional<WebWheelEvent::Phase> momentum = WTF::nullopt)
{
ViewGestureController* controller = webkitWebViewBaseViewGestureController(webViewBase);
if (controller && controller->isSwipeGestureEnabled() && controller->handleScrollWheelEvent(reinterpret_cast<GdkEventScroll*>(event)))
return;
WebKitWebViewBasePrivate* priv = webViewBase->priv;
ASSERT(!priv->dialog);
if (phase)
priv->pageProxy->handleWheelEvent(NativeWebWheelEvent(event, phase.value(), momentum.valueOr(WebWheelEvent::Phase::PhaseNone)));
else
priv->pageProxy->handleWheelEvent(NativeWebWheelEvent(event));
}
static gboolean webkitWebViewBaseScrollEvent(GtkWidget* widget, GdkEventScroll* event)
{
WebKitWebViewBase* webViewBase = WEBKIT_WEB_VIEW_BASE(widget);
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (std::exchange(priv->shouldForwardNextWheelEvent, false))
return GDK_EVENT_PROPAGATE;
if (priv->dialog)
return GDK_EVENT_PROPAGATE;
// Shift+Wheel scrolls in the perpendicular direction.
if (event->state & GDK_SHIFT_MASK) {
switch (event->direction) {
case GDK_SCROLL_UP:
event->direction = GDK_SCROLL_LEFT;
break;
case GDK_SCROLL_LEFT:
event->direction = GDK_SCROLL_UP;
break;
case GDK_SCROLL_DOWN:
event->direction = GDK_SCROLL_RIGHT;
break;
case GDK_SCROLL_RIGHT:
event->direction = GDK_SCROLL_DOWN;
break;
case GDK_SCROLL_SMOOTH:
std::swap(event->delta_x, event->delta_y);
break;
}
}
webkitWebViewBaseHandleWheelEvent(webViewBase, reinterpret_cast<GdkEvent*>(event));
return GDK_EVENT_STOP;
}
#endif
#if USE(GTK4)
static gboolean webkitWebViewBaseScroll(WebKitWebViewBase* webViewBase, double deltaX, double deltaY, GtkEventController* controller)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (priv->dialog)
return GDK_EVENT_PROPAGATE;
auto* event = gtk_event_controller_get_current_event(controller);
// Shift+Wheel scrolls in the perpendicular direction.
if (gdk_event_get_modifier_state(event) & GDK_SHIFT_MASK)
std::swap(deltaX, deltaY);
priv->pageProxy->handleWheelEvent(NativeWebWheelEvent(event, priv->lastMotionEvent ? IntPoint(priv->lastMotionEvent->position) : IntPoint(), FloatSize(-deltaX, -deltaY)));
return GDK_EVENT_STOP;
}
#endif
#if !USE(GTK4)
static gboolean webkitWebViewBasePopupMenu(GtkWidget* widget)
{
WebKitWebViewBase* webViewBase = WEBKIT_WEB_VIEW_BASE(widget);
WebKitWebViewBasePrivate* priv = webViewBase->priv;
GdkEvent* currentEvent = gtk_get_current_event();
if (!currentEvent)
currentEvent = gdk_event_new(GDK_NOTHING);
priv->contextMenuEvent.reset(currentEvent);
priv->pageProxy->handleContextMenuKeyEvent();
return TRUE;
}
static gboolean webkitWebViewBaseMotionNotifyEvent(GtkWidget* widget, GdkEventMotion* event)
{
WebKitWebViewBase* webViewBase = WEBKIT_WEB_VIEW_BASE(widget);
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (priv->dialog) {
auto* widgetClass = GTK_WIDGET_CLASS(webkit_web_view_base_parent_class);
return widgetClass->motion_notify_event ? widgetClass->motion_notify_event(widget, event) : GDK_EVENT_PROPAGATE;
}
if (priv->pointerLockManager) {
double x, y;
gdk_event_get_root_coords(reinterpret_cast<GdkEvent*>(event), &x, &y);
priv->pointerLockManager->didReceiveMotionEvent(FloatPoint(x, y));
return GDK_EVENT_STOP;
}
webkitWebViewBaseHandleMouseEvent(webViewBase, reinterpret_cast<GdkEvent*>(event));
return GDK_EVENT_PROPAGATE;
}
static gboolean webkitWebViewBaseCrossingNotifyEvent(GtkWidget* widget, GdkEventCrossing* crossingEvent)
{
WebKitWebViewBase* webViewBase = WEBKIT_WEB_VIEW_BASE(widget);
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (priv->dialog)
return GDK_EVENT_PROPAGATE;
#if ENABLE(DEVELOPER_MODE)
// Do not send mouse move events to the WebProcess for crossing events during testing.
// WTR never generates crossing events and they can confuse tests.
// https://bugs.webkit.org/show_bug.cgi?id=185072.
if (UNLIKELY(priv->pageProxy->process().processPool().configuration().fullySynchronousModeIsAllowedForTesting()))
return GDK_EVENT_PROPAGATE;
#endif
// In the case of crossing events, it's very important the actual coordinates the WebProcess receives, because once the mouse leaves
// the web view, the WebProcess won't receive more events until the mouse enters again in the web view. So, if the coordinates of the leave
// event are not accurate, the WebProcess might not know the mouse left the view. This can happen because of double to integer conversion,
// if the coordinates of the leave event are for example (25.2, -0.9), the WebProcess will receive (25, 0) and any hit test will succeed
// because those coordinates are inside the web view.
GtkAllocation allocation;
gtk_widget_get_allocation(widget, &allocation);
double xEvent, yEvent;
gdk_event_get_coords(reinterpret_cast<GdkEvent*>(crossingEvent), &xEvent, &yEvent);
double width = allocation.width;
double height = allocation.height;
double x = xEvent;
double y = yEvent;
if (x < 0 && x > -1)
x = -1;
else if (x >= width && x < width + 1)
x = width + 1;
if (y < 0 && y > -1)
y = -1;
else if (y >= height && y < height + 1)
y = height + 1;
GdkEvent* event = reinterpret_cast<GdkEvent*>(crossingEvent);
GUniquePtr<GdkEvent> copiedEvent;
if (x != xEvent || y != yEvent) {
copiedEvent.reset(gdk_event_copy(event));
copiedEvent->crossing.x = x;
copiedEvent->crossing.y = y;
}
webkitWebViewBaseHandleMouseEvent(webViewBase, copiedEvent ? copiedEvent.get() : event);
return GDK_EVENT_PROPAGATE;
}
#endif
#if USE(GTK4)
static void webkitWebViewBaseEnter(WebKitWebViewBase* webViewBase, double x, double y, GdkCrossingMode, GtkEventController*)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (priv->dialog)
return;
#if ENABLE(DEVELOPER_MODE)
// Do not send mouse move events to the WebProcess for crossing events during testing.
// WTR never generates crossing events and they can confuse tests.
// https://bugs.webkit.org/show_bug.cgi?id=185072.
if (UNLIKELY(priv->pageProxy->process().processPool().configuration().fullySynchronousModeIsAllowedForTesting()))
return;
#endif
priv->pageProxy->handleMouseEvent(NativeWebMouseEvent({ clampToInteger(x), clampToInteger(y) }));
}
static gboolean webkitWebViewBaseMotion(WebKitWebViewBase* webViewBase, double x, double y, GtkEventController* controller)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (priv->dialog)
return GDK_EVENT_PROPAGATE;
if (priv->pointerLockManager) {
priv->pointerLockManager->didReceiveMotionEvent(FloatPoint(x, y));
return GDK_EVENT_STOP;
}
auto* event = gtk_event_controller_get_current_event(controller);
Optional<FloatSize> movementDelta;
MotionEvent motionEvent(FloatPoint(x, y), FloatPoint(x, y), gdk_event_get_modifier_state(event));
if (priv->lastMotionEvent)
movementDelta = motionEvent.position - priv->lastMotionEvent->position;
priv->lastMotionEvent = WTFMove(motionEvent);
webViewBase->priv->pageProxy->handleMouseEvent(NativeWebMouseEvent(event, { clampToInteger(x), clampToInteger(y) }, 0, movementDelta));
return GDK_EVENT_PROPAGATE;
}
static void webkitWebViewBaseLeave(WebKitWebViewBase* webViewBase, GdkCrossingMode, GtkEventController*)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (priv->dialog)
return;
#if ENABLE(DEVELOPER_MODE)
// Do not send mouse move events to the WebProcess for crossing events during testing.
// WTR never generates crossing events and they can confuse tests.
// https://bugs.webkit.org/show_bug.cgi?id=185072.
if (UNLIKELY(priv->pageProxy->process().processPool().configuration().fullySynchronousModeIsAllowedForTesting()))
return;
#endif
priv->pageProxy->handleMouseEvent(NativeWebMouseEvent({ -1, -1 }));
}
#endif
#if ENABLE(TOUCH_EVENTS) && !USE(GTK4)
static void appendTouchEvent(Vector<WebPlatformTouchPoint>& touchPoints, const GdkEvent* event, WebPlatformTouchPoint::TouchPointState state)
{
gdouble x, y;
gdk_event_get_coords(event, &x, &y);
gdouble xRoot, yRoot;
gdk_event_get_root_coords(event, &xRoot, &yRoot);
uint32_t identifier = GPOINTER_TO_UINT(gdk_event_get_event_sequence(event));
touchPoints.uncheckedAppend(WebPlatformTouchPoint(identifier, state, IntPoint(xRoot, yRoot), IntPoint(x, y)));
}
static inline WebPlatformTouchPoint::TouchPointState touchPointStateForEvents(const GdkEvent* current, const GdkEvent* event)
{
if (gdk_event_get_event_sequence(current) != gdk_event_get_event_sequence(event))
return WebPlatformTouchPoint::TouchStationary;
switch (current->type) {
case GDK_TOUCH_UPDATE:
return WebPlatformTouchPoint::TouchMoved;
case GDK_TOUCH_BEGIN:
return WebPlatformTouchPoint::TouchPressed;
case GDK_TOUCH_END:
return WebPlatformTouchPoint::TouchReleased;
case GDK_TOUCH_CANCEL:
return WebPlatformTouchPoint::TouchCancelled;
default:
return WebPlatformTouchPoint::TouchStationary;
}
}
static void webkitWebViewBaseGetTouchPointsForEvent(WebKitWebViewBase* webViewBase, GdkEvent* event, Vector<WebPlatformTouchPoint>& touchPoints)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
GdkEventType type = gdk_event_get_event_type(event);
bool touchEnd = (type == GDK_TOUCH_END) || (type == GDK_TOUCH_CANCEL);
touchPoints.reserveInitialCapacity(touchEnd ? priv->touchEvents.size() + 1 : priv->touchEvents.size());
for (const auto& it : priv->touchEvents)
appendTouchEvent(touchPoints, it.value.get(), touchPointStateForEvents(it.value.get(), event));
// Touch was already removed from the TouchEventsMap, add it here.
if (touchEnd)
appendTouchEvent(touchPoints, event, WebPlatformTouchPoint::TouchReleased);
}
static gboolean webkitWebViewBaseTouchEvent(GtkWidget* widget, GdkEventTouch* event)
{
WebKitWebViewBase* webViewBase = WEBKIT_WEB_VIEW_BASE(widget);
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (priv->dialog)
return GDK_EVENT_STOP;
GdkEvent* touchEvent = reinterpret_cast<GdkEvent*>(event);
uint32_t sequence = GPOINTER_TO_UINT(gdk_event_get_event_sequence(touchEvent));
GdkEventType type = gdk_event_get_event_type(touchEvent);
switch (type) {
case GDK_TOUCH_BEGIN: {
ASSERT(!priv->touchEvents.contains(sequence));
GUniquePtr<GdkEvent> event(gdk_event_copy(touchEvent));
priv->touchEvents.add(sequence, WTFMove(event));
break;
}
case GDK_TOUCH_UPDATE: {
auto it = priv->touchEvents.find(sequence);
ASSERT(it != priv->touchEvents.end());
it->value.reset(gdk_event_copy(touchEvent));
break;
}
case GDK_TOUCH_CANCEL:
FALLTHROUGH;
case GDK_TOUCH_END:
ASSERT(priv->touchEvents.contains(sequence));
priv->touchEvents.remove(sequence);
break;
default:
break;
}
Vector<WebPlatformTouchPoint> touchPoints;
webkitWebViewBaseGetTouchPointsForEvent(webViewBase, touchEvent, touchPoints);
priv->pageProxy->handleTouchEvent(NativeWebTouchEvent(reinterpret_cast<GdkEvent*>(event), WTFMove(touchPoints)));
return GDK_EVENT_STOP;
}
#endif // ENABLE(TOUCH_EVENTS)
#if !USE(GTK4)
class TouchGestureController final : public GestureControllerClient {
WTF_MAKE_FAST_ALLOCATED;
public:
explicit TouchGestureController(WebKitWebViewBase* webViewBase)
: m_webView(webViewBase)
{
}
private:
static GUniquePtr<GdkEvent> createScrollEvent(GdkEventTouch* event, const FloatPoint& point, const FloatPoint& delta, bool isStop = false)
{
GUniquePtr<GdkEvent> scrollEvent(gdk_event_new(GDK_SCROLL));
scrollEvent->scroll.time = event->time;
scrollEvent->scroll.x = point.x();
scrollEvent->scroll.y = point.y();
scrollEvent->scroll.x_root = event->x_root;
scrollEvent->scroll.y_root = event->y_root;
scrollEvent->scroll.direction = GDK_SCROLL_SMOOTH;
scrollEvent->scroll.delta_x = delta.x();
scrollEvent->scroll.delta_y = delta.y();
scrollEvent->scroll.state = event->state;
scrollEvent->scroll.is_stop = isStop;
scrollEvent->scroll.window = event->window ? GDK_WINDOW(g_object_ref(event->window)) : nullptr;
auto* touchEvent = reinterpret_cast<GdkEvent*>(event);
gdk_event_set_screen(scrollEvent.get(), gdk_event_get_screen(touchEvent));
gdk_event_set_device(scrollEvent.get(), gdk_event_get_device(touchEvent));
gdk_event_set_source_device(scrollEvent.get(), gdk_event_get_source_device(touchEvent));
return scrollEvent;
}
void simulateMouseClick(GdkEventTouch* event, unsigned button)
{
GUniquePtr<GdkEvent> pointerEvent(gdk_event_new(GDK_MOTION_NOTIFY));
pointerEvent->motion.time = event->time;
pointerEvent->motion.x = event->x;
pointerEvent->motion.y = event->y;
pointerEvent->motion.x_root = event->x_root;
pointerEvent->motion.y_root = event->y_root;
pointerEvent->motion.state = event->state;
pointerEvent->motion.window = event->window ? GDK_WINDOW(g_object_ref(event->window)) : nullptr;
auto* touchEvent = reinterpret_cast<GdkEvent*>(event);
gdk_event_set_screen(pointerEvent.get(), gdk_event_get_screen(touchEvent));
gdk_event_set_device(pointerEvent.get(), gdk_event_get_device(touchEvent));
gdk_event_set_source_device(pointerEvent.get(), gdk_event_get_source_device(touchEvent));
webkitWebViewBaseHandleMouseEvent(m_webView, pointerEvent.get());
pointerEvent.reset(gdk_event_new(GDK_BUTTON_PRESS));
pointerEvent->button.button = button;
pointerEvent->button.time = event->time;
pointerEvent->button.x = event->x;
pointerEvent->button.y = event->y;
pointerEvent->button.x_root = event->x_root;
pointerEvent->button.y_root = event->y_root;
pointerEvent->button.window = event->window ? GDK_WINDOW(g_object_ref(event->window)) : nullptr;
gdk_event_set_screen(pointerEvent.get(), gdk_event_get_screen(touchEvent));
gdk_event_set_device(pointerEvent.get(), gdk_event_get_device(touchEvent));
gdk_event_set_source_device(pointerEvent.get(), gdk_event_get_source_device(touchEvent));
webkitWebViewBaseHandleMouseEvent(m_webView, pointerEvent.get());
pointerEvent->type = GDK_BUTTON_RELEASE;
webkitWebViewBaseHandleMouseEvent(m_webView, pointerEvent.get());
}
void tap(GdkEventTouch* event) final
{
simulateMouseClick(event, GDK_BUTTON_PRIMARY);
}
void startDrag(GdkEventTouch* event, const FloatPoint& startPoint) final
{
GUniquePtr<GdkEvent> scrollEvent = createScrollEvent(event, startPoint, { });
webkitWebViewBaseHandleWheelEvent(m_webView, scrollEvent.get(), WebWheelEvent::Phase::PhaseBegan);
}
void drag(GdkEventTouch* event, const FloatPoint& point, const FloatPoint& delta) final
{
GUniquePtr<GdkEvent> scrollEvent = createScrollEvent(event, point, delta);
webkitWebViewBaseHandleWheelEvent(m_webView, scrollEvent.get(), WebWheelEvent::Phase::PhaseChanged);
}
void cancelDrag() final
{
if (auto* controller = webkitWebViewBaseViewGestureController(m_webView))
controller->cancelSwipe();
}
void swipe(GdkEventTouch* event, const FloatPoint& velocity) final
{
double x, y;
gdk_event_get_coords(reinterpret_cast<GdkEvent*>(event), &x, &y);
GUniquePtr<GdkEvent> scrollEvent = createScrollEvent(event, FloatPoint::narrowPrecision(x, y), velocity, true);
webkitWebViewBaseHandleWheelEvent(m_webView, scrollEvent.get(), WebWheelEvent::Phase::PhaseNone, WebWheelEvent::Phase::PhaseBegan);
}
void startZoom(const IntPoint& center, double& initialScale, IntPoint& initialPoint) final
{
auto* page = m_webView->priv->pageProxy.get();
ASSERT(page);
initialScale = page->pageScaleFactor();
page->getCenterForZoomGesture(center, initialPoint);
}
void zoom(double scale, const IntPoint& origin) final
{
auto* page = m_webView->priv->pageProxy.get();
ASSERT(page);
page->scalePage(scale, origin);
}
void longPress(GdkEventTouch* event) final
{
simulateMouseClick(event, GDK_BUTTON_SECONDARY);
}
WebKitWebViewBase* m_webView;
};
GestureController& webkitWebViewBaseGestureController(WebKitWebViewBase* webViewBase)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (!priv->gestureController)
priv->gestureController = makeUnique<GestureController>(GTK_WIDGET(webViewBase), makeUnique<TouchGestureController>(webViewBase));
return *priv->gestureController;
}
#endif
void webkitWebViewBaseSetEnableBackForwardNavigationGesture(WebKitWebViewBase* webViewBase, bool enabled)
{
#if !USE(GTK4)
WebKitWebViewBasePrivate* priv = webViewBase->priv;
priv->isBackForwardNavigationGestureEnabled = enabled;
if (auto* controller = webkitWebViewBaseViewGestureController(webViewBase))
controller->setSwipeGestureEnabled(enabled);
priv->pageProxy->setShouldRecordNavigationSnapshots(enabled);
#endif
}
#if !USE(GTK4)
ViewGestureController* webkitWebViewBaseViewGestureController(WebKitWebViewBase* webViewBase)
{
return webViewBase->priv->viewGestureController.get();
}
#endif
bool webkitWebViewBaseBeginBackSwipeForTesting(WebKitWebViewBase* webViewBase)
{
#if !USE(GTK4)
if (auto* gestureController = webkitWebViewBaseViewGestureController(webViewBase))
return gestureController->beginSimulatedSwipeInDirectionForTesting(ViewGestureController::SwipeDirection::Back);
#endif
return FALSE;
}
bool webkitWebViewBaseCompleteBackSwipeForTesting(WebKitWebViewBase* webViewBase)
{
#if !USE(GTK4)
if (auto* gestureController = webkitWebViewBaseViewGestureController(webViewBase))
return gestureController->completeSimulatedSwipeInDirectionForTesting(ViewGestureController::SwipeDirection::Back);
#endif
return FALSE;
}
static gboolean webkitWebViewBaseQueryTooltip(GtkWidget* widget, gint /* x */, gint /* y */, gboolean keyboardMode, GtkTooltip* tooltip)
{
WebKitWebViewBasePrivate* priv = WEBKIT_WEB_VIEW_BASE(widget)->priv;
if (keyboardMode) {
// TODO: https://bugs.webkit.org/show_bug.cgi?id=61732.
notImplemented();
return FALSE;
}
if (priv->tooltipText.length() <= 0)
return FALSE;
if (!priv->tooltipArea.isEmpty()) {
GdkRectangle area = priv->tooltipArea;
gtk_tooltip_set_tip_area(tooltip, &area);
} else
gtk_tooltip_set_tip_area(tooltip, 0);
gtk_tooltip_set_text(tooltip, priv->tooltipText.data());
return TRUE;
}
#if !USE(GTK4)
static gboolean webkitWebViewBaseEvent(GtkWidget* widget, GdkEvent* event)
{
if (gdk_event_get_event_type(event) == GDK_TOUCHPAD_PINCH)
webkitWebViewBaseGestureController(WEBKIT_WEB_VIEW_BASE(widget)).handleEvent(event);
return GDK_EVENT_PROPAGATE;
}
#endif
static AtkObject* webkitWebViewBaseGetAccessible(GtkWidget* widget)
{
WebKitWebViewBasePrivate* priv = WEBKIT_WEB_VIEW_BASE(widget)->priv;
if (!priv->accessible) {
// Create the accessible object and associate it to the widget.
priv->accessible = adoptGRef(ATK_OBJECT(webkitWebViewAccessibleNew(widget)));
// Set the parent to not break bottom-up navigation.
if (auto* parentWidget = gtk_widget_get_parent(widget)) {
if (auto* axParent = gtk_widget_get_accessible(parentWidget))
atk_object_set_parent(priv->accessible.get(), axParent);
}
}
return priv->accessible.get();
}
#if USE(GTK4)
static void toplevelWindowIsActiveChanged(GtkWindow* window, GParamSpec*, WebKitWebViewBase* webViewBase)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (gtk_window_is_active(window)) {
if (priv->activityState & ActivityState::WindowIsActive)
return;
priv->activityState.add(ActivityState::WindowIsActive);
if (priv->activityState & ActivityState::IsFocused)
priv->inputMethodFilter.notifyFocusedIn();
} else {
if (!(priv->activityState & ActivityState::WindowIsActive))
return;
priv->activityState.remove(ActivityState::WindowIsActive);
if (priv->activityState & ActivityState::IsFocused)
priv->inputMethodFilter.notifyFocusedOut();
}
webkitWebViewBaseScheduleUpdateActivityState(webViewBase, ActivityState::WindowIsActive);
}
static void toplevelWindowStateChanged(GdkSurface* surface, GParamSpec*, WebKitWebViewBase* webViewBase)
{
auto state = gdk_toplevel_get_state(GDK_TOPLEVEL(surface));
bool visible = !(state & GDK_SURFACE_STATE_MINIMIZED);
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (visible) {
if (priv->activityState & ActivityState::IsVisible)
return;
priv->activityState.add(ActivityState::IsVisible);
} else {
if (!(priv->activityState & ActivityState::IsVisible))
return;
priv->activityState.remove(ActivityState::IsVisible);
}
webkitWebViewBaseScheduleUpdateActivityState(webViewBase, ActivityState::IsVisible);
}
static void toplevelWindowRealized(WebKitWebViewBase* webViewBase)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
g_clear_signal_handler(&priv->toplevelWindowRealizedID, priv->toplevelOnScreenWindow);
priv->toplevelWindowStateChangedID =
g_signal_connect(gtk_native_get_surface(GTK_NATIVE(priv->toplevelOnScreenWindow)), "notify::state", G_CALLBACK(toplevelWindowStateChanged), webViewBase);
}
static void toplevelWindowUnrealized(WebKitWebViewBase* webViewBase)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
g_clear_signal_handler(&priv->toplevelWindowUnrealizedID, priv->toplevelOnScreenWindow);
g_clear_signal_handler(&priv->toplevelWindowStateChangedID, gtk_native_get_surface(GTK_NATIVE(priv->toplevelOnScreenWindow)));
}
static void webkitWebViewBaseRoot(GtkWidget* widget)
{
GTK_WIDGET_CLASS(webkit_web_view_base_parent_class)->root(widget);
WebKitWebViewBase* webViewBase = WEBKIT_WEB_VIEW_BASE(widget);
WebKitWebViewBasePrivate* priv = webViewBase->priv;
priv->toplevelOnScreenWindow = GTK_WINDOW(gtk_widget_get_root(widget));
OptionSet<ActivityState::Flag> flagsToUpdate;
if (!(priv->activityState & ActivityState::IsInWindow)) {
priv->activityState.add(ActivityState::IsInWindow);
flagsToUpdate.add(ActivityState::IsInWindow);
}
if (gtk_widget_get_visible(GTK_WIDGET(priv->toplevelOnScreenWindow)) && gtk_window_is_active(priv->toplevelOnScreenWindow)) {
priv->activityState.add(ActivityState::WindowIsActive);
flagsToUpdate.add(ActivityState::IsInWindow);
}
if (flagsToUpdate)
webkitWebViewBaseScheduleUpdateActivityState(webViewBase, flagsToUpdate);
priv->toplevelIsActiveID =
g_signal_connect(priv->toplevelOnScreenWindow, "notify::is-active", G_CALLBACK(toplevelWindowIsActiveChanged), widget);
if (gtk_widget_get_realized(GTK_WIDGET(priv->toplevelOnScreenWindow))) {
priv->toplevelWindowStateChangedID =
g_signal_connect(gtk_native_get_surface(GTK_NATIVE(priv->toplevelOnScreenWindow)), "notify::state", G_CALLBACK(toplevelWindowStateChanged), widget);
} else {
priv->toplevelWindowRealizedID =
g_signal_connect_swapped(priv->toplevelOnScreenWindow, "realize", G_CALLBACK(toplevelWindowRealized), widget);
}
priv->toplevelWindowUnrealizedID =
g_signal_connect_swapped(priv->toplevelOnScreenWindow, "unrealize", G_CALLBACK(toplevelWindowUnrealized), widget);
auto* settings = gtk_widget_get_settings(GTK_WIDGET(priv->toplevelOnScreenWindow));
priv->themeChangedID =
g_signal_connect_swapped(settings, "notify::gtk-theme-name", G_CALLBACK(+[](WebKitWebViewBase* webViewBase) {
webViewBase->priv->pageProxy->themeDidChange();
}), widget);
priv->applicationPreferDarkThemeID =
g_signal_connect_swapped(settings, "notify::gtk-application-prefer-dark-theme", G_CALLBACK(+[](WebKitWebViewBase* webViewBase) {
webViewBase->priv->pageProxy->effectiveAppearanceDidChange();
}), widget);
}
static void webkitWebViewBaseUnroot(GtkWidget* widget)
{
GTK_WIDGET_CLASS(webkit_web_view_base_parent_class)->unroot(widget);
WebKitWebViewBase* webViewBase = WEBKIT_WEB_VIEW_BASE(widget);
WebKitWebViewBasePrivate* priv = webViewBase->priv;
g_clear_signal_handler(&priv->toplevelIsActiveID, priv->toplevelOnScreenWindow);
g_clear_signal_handler(&priv->toplevelWindowRealizedID, priv->toplevelOnScreenWindow);
g_clear_signal_handler(&priv->toplevelWindowUnrealizedID, priv->toplevelOnScreenWindow);
if (gtk_widget_get_realized(GTK_WIDGET(priv->toplevelOnScreenWindow)))
g_clear_signal_handler(&priv->toplevelWindowStateChangedID, gtk_native_get_surface(GTK_NATIVE(priv->toplevelOnScreenWindow)));
auto* settings = gtk_widget_get_settings(GTK_WIDGET(priv->toplevelOnScreenWindow));
g_clear_signal_handler(&priv->themeChangedID, settings);
g_clear_signal_handler(&priv->applicationPreferDarkThemeID, settings);
priv->toplevelOnScreenWindow = nullptr;
OptionSet<ActivityState::Flag> flagsToUpdate;
if (priv->activityState & ActivityState::IsInWindow) {
priv->activityState.remove(ActivityState::IsInWindow);
flagsToUpdate.add(ActivityState::IsInWindow);
}
if (priv->activityState & ActivityState::WindowIsActive) {
priv->activityState.remove(ActivityState::WindowIsActive);
flagsToUpdate.add(ActivityState::IsInWindow);
}
if (flagsToUpdate)
webkitWebViewBaseScheduleUpdateActivityState(webViewBase, flagsToUpdate);
}
#else
static void webkitWebViewBaseHierarchyChanged(GtkWidget* widget, GtkWidget* oldToplevel)
{
WebKitWebViewBasePrivate* priv = WEBKIT_WEB_VIEW_BASE(widget)->priv;
if (widgetIsOnscreenToplevelWindow(oldToplevel) && GTK_WINDOW(oldToplevel) == priv->toplevelOnScreenWindow) {
webkitWebViewBaseSetToplevelOnScreenWindow(WEBKIT_WEB_VIEW_BASE(widget), nullptr);
return;
}
if (!oldToplevel) {
GtkWidget* toplevel = gtk_widget_get_toplevel(widget);
if (widgetIsOnscreenToplevelWindow(toplevel))
webkitWebViewBaseSetToplevelOnScreenWindow(WEBKIT_WEB_VIEW_BASE(widget), GTK_WINDOW(toplevel));
}
}
#endif
#if USE(GTK4)
static gboolean webkitWebViewBaseGrabFocus(GtkWidget* widget)
{
gtk_root_set_focus(gtk_widget_get_root(widget), widget);
return TRUE;
}
#endif
static gboolean webkitWebViewBaseFocus(GtkWidget* widget, GtkDirectionType direction)
{
// If a dialog is active, we need to forward focus events there. This
// ensures that you can tab between elements in the box.
WebKitWebViewBasePrivate* priv = WEBKIT_WEB_VIEW_BASE(widget)->priv;
if (priv->dialog) {
gboolean returnValue;
g_signal_emit_by_name(priv->dialog, "focus", direction, &returnValue);
return returnValue;
}
return GTK_WIDGET_CLASS(webkit_web_view_base_parent_class)->focus(widget, direction);
}
static void webkitWebViewBaseConstructed(GObject* object)
{
G_OBJECT_CLASS(webkit_web_view_base_parent_class)->constructed(object);
GtkWidget* viewWidget = GTK_WIDGET(object);
gtk_widget_set_can_focus(viewWidget, TRUE);
WebKitWebViewBasePrivate* priv = WEBKIT_WEB_VIEW_BASE(object)->priv;
priv->pageClient = makeUnique<PageClientImpl>(viewWidget);
gtk_widget_set_parent(priv->keyBindingTranslator.widget(), viewWidget);
#if ENABLE(DRAG_SUPPORT)
priv->dropTarget = makeUnique<DropTarget>(viewWidget);
#endif
#if USE(GTK4)
auto* controller = gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES);
g_signal_connect_object(controller, "scroll", G_CALLBACK(webkitWebViewBaseScroll), viewWidget, G_CONNECT_SWAPPED);
gtk_widget_add_controller(viewWidget, controller);
controller = gtk_event_controller_motion_new();
g_signal_connect_object(controller, "enter", G_CALLBACK(webkitWebViewBaseEnter), viewWidget, G_CONNECT_SWAPPED);
g_signal_connect_object(controller, "motion", G_CALLBACK(webkitWebViewBaseMotion), viewWidget, G_CONNECT_SWAPPED);
g_signal_connect_object(controller, "leave", G_CALLBACK(webkitWebViewBaseLeave), viewWidget, G_CONNECT_SWAPPED);
gtk_widget_add_controller(viewWidget, controller);
controller = gtk_event_controller_focus_new();
g_signal_connect_object(controller, "enter", G_CALLBACK(webkitWebViewBaseFocusEnter), viewWidget, G_CONNECT_SWAPPED);
g_signal_connect_object(controller, "leave", G_CALLBACK(webkitWebViewBaseFocusLeave), viewWidget, G_CONNECT_SWAPPED);
gtk_widget_add_controller(viewWidget, controller);
controller = gtk_event_controller_key_new();
g_signal_connect_object(controller, "key-pressed", G_CALLBACK(webkitWebViewBaseKeyPressed), viewWidget, G_CONNECT_SWAPPED);
g_signal_connect_object(controller, "key-released", G_CALLBACK(webkitWebViewBaseKeyReleased), viewWidget, G_CONNECT_SWAPPED);
gtk_widget_add_controller(viewWidget, controller);
auto* gesture = gtk_gesture_click_new();
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(gesture), 0);
g_signal_connect_object(gesture, "pressed", G_CALLBACK(webkitWebViewBaseButtonPressed), viewWidget, G_CONNECT_SWAPPED);
g_signal_connect_object(gesture, "released", G_CALLBACK(webkitWebViewBaseButtonReleased), viewWidget, G_CONNECT_SWAPPED);
gtk_widget_add_controller(viewWidget, GTK_EVENT_CONTROLLER(gesture));
#endif
}
static void webkit_web_view_base_class_init(WebKitWebViewBaseClass* webkitWebViewBaseClass)
{
GtkWidgetClass* widgetClass = GTK_WIDGET_CLASS(webkitWebViewBaseClass);
widgetClass->realize = webkitWebViewBaseRealize;
widgetClass->unrealize = webkitWebViewBaseUnrealize;
#if USE(GTK4)
widgetClass->snapshot = webkitWebViewBaseSnapshot;
#else
widgetClass->draw = webkitWebViewBaseDraw;
#endif
widgetClass->size_allocate = webkitWebViewBaseSizeAllocate;
#if USE(GTK4)
widgetClass->measure = webkitWebViewBaseMeasure;
#else
widgetClass->get_preferred_width = webkitWebViewBaseGetPreferredWidth;
widgetClass->get_preferred_height = webkitWebViewBaseGetPreferredHeight;
#endif
widgetClass->map = webkitWebViewBaseMap;
widgetClass->unmap = webkitWebViewBaseUnmap;
widgetClass->focus = webkitWebViewBaseFocus;
#if USE(GTK4)
widgetClass->grab_focus = webkitWebViewBaseGrabFocus;
#else
widgetClass->focus_in_event = webkitWebViewBaseFocusInEvent;
widgetClass->focus_out_event = webkitWebViewBaseFocusOutEvent;
widgetClass->key_press_event = webkitWebViewBaseKeyPressEvent;
widgetClass->key_release_event = webkitWebViewBaseKeyReleaseEvent;
widgetClass->button_press_event = webkitWebViewBaseButtonPressEvent;
widgetClass->button_release_event = webkitWebViewBaseButtonReleaseEvent;
widgetClass->scroll_event = webkitWebViewBaseScrollEvent;
widgetClass->popup_menu = webkitWebViewBasePopupMenu;
widgetClass->motion_notify_event = webkitWebViewBaseMotionNotifyEvent;
widgetClass->enter_notify_event = webkitWebViewBaseCrossingNotifyEvent;
widgetClass->leave_notify_event = webkitWebViewBaseCrossingNotifyEvent;
#endif
#if ENABLE(TOUCH_EVENTS) && !USE(GTK4)
widgetClass->touch_event = webkitWebViewBaseTouchEvent;
#endif
widgetClass->query_tooltip = webkitWebViewBaseQueryTooltip;
#if !USE(GTK4)
widgetClass->event = webkitWebViewBaseEvent;
#endif
widgetClass->get_accessible = webkitWebViewBaseGetAccessible;
#if USE(GTK4)
widgetClass->root = webkitWebViewBaseRoot;
widgetClass->unroot = webkitWebViewBaseUnroot;
#else
widgetClass->hierarchy_changed = webkitWebViewBaseHierarchyChanged;
#endif
GObjectClass* gobjectClass = G_OBJECT_CLASS(webkitWebViewBaseClass);
gobjectClass->constructed = webkitWebViewBaseConstructed;
gobjectClass->dispose = webkitWebViewBaseDispose;
#if !USE(GTK4)
GtkContainerClass* containerClass = GTK_CONTAINER_CLASS(webkitWebViewBaseClass);
containerClass->add = webkitWebViewBaseContainerAdd;
containerClass->remove = webkitWebViewBaseContainerRemove;
containerClass->forall = webkitWebViewBaseContainerForall;
#endif
// Before creating a WebKitWebViewBasePriv we need to be sure that WebKit is started.
// Usually starting a context triggers InitializeWebKit2, but in case
// we create a view without asking before for a default_context we get a crash.
WebKit::InitializeWebKit2();
gtk_widget_class_set_css_name(widgetClass, "webkitwebview");
}
WebKitWebViewBase* webkitWebViewBaseCreate(const API::PageConfiguration& configuration)
{
WebKitWebViewBase* webkitWebViewBase = WEBKIT_WEB_VIEW_BASE(g_object_new(WEBKIT_TYPE_WEB_VIEW_BASE, nullptr));
webkitWebViewBaseCreateWebPage(webkitWebViewBase, configuration.copy());
return webkitWebViewBase;
}
WebPageProxy* webkitWebViewBaseGetPage(WebKitWebViewBase* webkitWebViewBase)
{
return webkitWebViewBase->priv->pageProxy.get();
}
static void deviceScaleFactorChanged(WebKitWebViewBase* webkitWebViewBase)
{
webkitWebViewBase->priv->pageProxy->setIntrinsicDeviceScaleFactor(gtk_widget_get_scale_factor(GTK_WIDGET(webkitWebViewBase)));
}
void webkitWebViewBaseCreateWebPage(WebKitWebViewBase* webkitWebViewBase, Ref<API::PageConfiguration>&& configuration)
{
WebKitWebViewBasePrivate* priv = webkitWebViewBase->priv;
WebProcessPool* processPool = configuration->processPool();
priv->pageProxy = processPool->createWebPage(*priv->pageClient, WTFMove(configuration));
priv->acceleratedBackingStore = AcceleratedBackingStore::create(*priv->pageProxy);
priv->pageProxy->initializeWebPage();
// We attach this here, because changes in scale factor are passed directly to the page proxy.
priv->pageProxy->setIntrinsicDeviceScaleFactor(gtk_widget_get_scale_factor(GTK_WIDGET(webkitWebViewBase)));
g_signal_connect(webkitWebViewBase, "notify::scale-factor", G_CALLBACK(deviceScaleFactorChanged), nullptr);
}
void webkitWebViewBaseSetTooltipText(WebKitWebViewBase* webViewBase, const char* tooltip)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (tooltip && tooltip[0] != '\0') {
priv->tooltipText = tooltip;
gtk_widget_set_has_tooltip(GTK_WIDGET(webViewBase), TRUE);
} else {
priv->tooltipText = "";
gtk_widget_set_has_tooltip(GTK_WIDGET(webViewBase), FALSE);
}
gtk_widget_trigger_tooltip_query(GTK_WIDGET(webViewBase));
}
void webkitWebViewBaseSetTooltipArea(WebKitWebViewBase* webViewBase, const IntRect& tooltipArea)
{
webViewBase->priv->tooltipArea = tooltipArea;
}
#if ENABLE(DRAG_SUPPORT)
void webkitWebViewBaseStartDrag(WebKitWebViewBase* webViewBase, SelectionData&& selectionData, OptionSet<DragOperation> dragOperationMask, RefPtr<ShareableBitmap>&& image)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (!priv->dragSource)
priv->dragSource = makeUnique<DragSource>(GTK_WIDGET(webViewBase));
priv->dragSource->begin(WTFMove(selectionData), dragOperationMask, WTFMove(image));
#if !USE(GTK4)
// A drag starting should prevent a double-click from happening. This might
// happen if a drag is followed very quickly by another click (like in the WTR).
priv->clickCounter.reset();
#endif
}
void webkitWebViewBaseDidPerformDragControllerAction(WebKitWebViewBase* webViewBase)
{
webViewBase->priv->dropTarget->didPerformAction();
}
#endif // ENABLE(DRAG_SUPPORT)
void webkitWebViewBaseForwardNextKeyEvent(WebKitWebViewBase* webkitWebViewBase)
{
webkitWebViewBase->priv->shouldForwardNextKeyEvent = TRUE;
}
void webkitWebViewBaseForwardNextWheelEvent(WebKitWebViewBase* webkitWebViewBase)
{
webkitWebViewBase->priv->shouldForwardNextWheelEvent = true;
}
void webkitWebViewBaseEnterFullScreen(WebKitWebViewBase* webkitWebViewBase)
{
#if ENABLE(FULLSCREEN_API)
WebKitWebViewBasePrivate* priv = webkitWebViewBase->priv;
ASSERT(!priv->fullScreenModeActive);
WebFullScreenManagerProxy* fullScreenManagerProxy = priv->pageProxy->fullScreenManager();
fullScreenManagerProxy->willEnterFullScreen();
GtkWidget* topLevelWindow = gtk_widget_get_toplevel(GTK_WIDGET(webkitWebViewBase));
if (gtk_widget_is_toplevel(topLevelWindow))
gtk_window_fullscreen(GTK_WINDOW(topLevelWindow));
fullScreenManagerProxy->didEnterFullScreen();
priv->fullScreenModeActive = true;
priv->sleepDisabler = PAL::SleepDisabler::create(_("Website running in fullscreen mode"), PAL::SleepDisabler::Type::Display);
#endif
}
void webkitWebViewBaseExitFullScreen(WebKitWebViewBase* webkitWebViewBase)
{
#if ENABLE(FULLSCREEN_API)
WebKitWebViewBasePrivate* priv = webkitWebViewBase->priv;
ASSERT(priv->fullScreenModeActive);
WebFullScreenManagerProxy* fullScreenManagerProxy = priv->pageProxy->fullScreenManager();
fullScreenManagerProxy->willExitFullScreen();
GtkWidget* topLevelWindow = gtk_widget_get_toplevel(GTK_WIDGET(webkitWebViewBase));
if (gtk_widget_is_toplevel(topLevelWindow))
gtk_window_unfullscreen(GTK_WINDOW(topLevelWindow));
fullScreenManagerProxy->didExitFullScreen();
priv->fullScreenModeActive = false;
priv->sleepDisabler = nullptr;
#endif
}
bool webkitWebViewBaseIsFullScreen(WebKitWebViewBase* webkitWebViewBase)
{
#if ENABLE(FULLSCREEN_API)
return webkitWebViewBase->priv->fullScreenModeActive;
#else
return false;
#endif
}
void webkitWebViewBaseSetInspectorViewSize(WebKitWebViewBase* webkitWebViewBase, unsigned size)
{
if (webkitWebViewBase->priv->inspectorViewSize == size)
return;
webkitWebViewBase->priv->inspectorViewSize = size;
if (webkitWebViewBase->priv->inspectorView)
gtk_widget_queue_resize_no_redraw(GTK_WIDGET(webkitWebViewBase));
}
void webkitWebViewBaseSetActiveContextMenuProxy(WebKitWebViewBase* webkitWebViewBase, WebContextMenuProxyGtk* contextMenuProxy)
{
webkitWebViewBase->priv->activeContextMenuProxy = contextMenuProxy;
g_signal_connect(contextMenuProxy->gtkWidget(), WebContextMenuProxyGtk::widgetDismissedSignal, G_CALLBACK(+[](GtkWidget* widget, WebKitWebViewBase* webViewBase) {
if (webViewBase->priv->activeContextMenuProxy && webViewBase->priv->activeContextMenuProxy->gtkWidget() == widget)
webViewBase->priv->activeContextMenuProxy = nullptr;
}), webkitWebViewBase);
}
WebContextMenuProxyGtk* webkitWebViewBaseGetActiveContextMenuProxy(WebKitWebViewBase* webkitWebViewBase)
{
return webkitWebViewBase->priv->activeContextMenuProxy;
}
GdkEvent* webkitWebViewBaseTakeContextMenuEvent(WebKitWebViewBase* webkitWebViewBase)
{
return webkitWebViewBase->priv->contextMenuEvent.release();
}
void webkitWebViewBaseSetFocus(WebKitWebViewBase* webViewBase, bool focused)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if ((focused && priv->activityState & ActivityState::IsFocused) || (!focused && !(priv->activityState & ActivityState::IsFocused)))
return;
OptionSet<ActivityState::Flag> flagsToUpdate { ActivityState::IsFocused };
if (focused) {
priv->activityState.add(ActivityState::IsFocused);
// If the view has received the focus and the window is not active
// mark the current window as active now. This can happen if the
// toplevel window is a GTK_WINDOW_POPUP and the focus has been
// set programatically like WebKitTestRunner does, because POPUP
// can't be focused.
if (!(priv->activityState & ActivityState::WindowIsActive)) {
priv->activityState.add(ActivityState::WindowIsActive);
flagsToUpdate.add(ActivityState::WindowIsActive);
}
} else
priv->activityState.remove(ActivityState::IsFocused);
webkitWebViewBaseScheduleUpdateActivityState(webViewBase, flagsToUpdate);
}
bool webkitWebViewBaseIsInWindowActive(WebKitWebViewBase* webViewBase)
{
return webViewBase->priv->activityState.contains(ActivityState::WindowIsActive);
}
bool webkitWebViewBaseIsFocused(WebKitWebViewBase* webViewBase)
{
return webViewBase->priv->activityState.contains(ActivityState::IsFocused);
}
bool webkitWebViewBaseIsVisible(WebKitWebViewBase* webViewBase)
{
return webViewBase->priv->activityState.contains(ActivityState::IsVisible);
}
bool webkitWebViewBaseIsInWindow(WebKitWebViewBase* webViewBase)
{
return webViewBase->priv->activityState.contains(ActivityState::IsInWindow);
}
void webkitWebViewBaseSetInputMethodState(WebKitWebViewBase* webkitWebViewBase, Optional<InputMethodState>&& state)
{
webkitWebViewBase->priv->inputMethodFilter.setState(WTFMove(state));
}
void webkitWebViewBaseUpdateTextInputState(WebKitWebViewBase* webkitWebViewBase)
{
const auto& editorState = webkitWebViewBase->priv->pageProxy->editorState();
if (!editorState.isMissingPostLayoutData) {
webkitWebViewBase->priv->inputMethodFilter.notifyCursorRect(editorState.postLayoutData().caretRectAtStart);
webkitWebViewBase->priv->inputMethodFilter.notifySurrounding(editorState.postLayoutData().surroundingContext, editorState.postLayoutData().surroundingContextCursorPosition,
editorState.postLayoutData().surroundingContextSelectionPosition);
}
}
void webkitWebViewBaseSetContentsSize(WebKitWebViewBase* webkitWebViewBase, const IntSize& contentsSize)
{
WebKitWebViewBasePrivate* priv = webkitWebViewBase->priv;
if (priv->contentsSize == contentsSize)
return;
priv->contentsSize = contentsSize;
}
void webkitWebViewBaseResetClickCounter(WebKitWebViewBase* webkitWebViewBase)
{
#if !USE(GTK4)
webkitWebViewBase->priv->clickCounter.reset();
#endif
}
void webkitWebViewBaseEnterAcceleratedCompositingMode(WebKitWebViewBase* webkitWebViewBase, const LayerTreeContext& layerTreeContext)
{
ASSERT(webkitWebViewBase->priv->acceleratedBackingStore);
webkitWebViewBase->priv->acceleratedBackingStore->update(layerTreeContext);
}
void webkitWebViewBaseUpdateAcceleratedCompositingMode(WebKitWebViewBase* webkitWebViewBase, const LayerTreeContext& layerTreeContext)
{
ASSERT(webkitWebViewBase->priv->acceleratedBackingStore);
webkitWebViewBase->priv->acceleratedBackingStore->update(layerTreeContext);
}
void webkitWebViewBaseExitAcceleratedCompositingMode(WebKitWebViewBase* webkitWebViewBase)
{
ASSERT(webkitWebViewBase->priv->acceleratedBackingStore);
webkitWebViewBase->priv->acceleratedBackingStore->update(LayerTreeContext());
}
bool webkitWebViewBaseMakeGLContextCurrent(WebKitWebViewBase* webkitWebViewBase)
{
ASSERT(webkitWebViewBase->priv->acceleratedBackingStore);
return webkitWebViewBase->priv->acceleratedBackingStore->makeContextCurrent();
}
void webkitWebViewBaseWillSwapWebProcess(WebKitWebViewBase* webkitWebViewBase)
{
#if !USE(GTK4)
WebKitWebViewBasePrivate* priv = webkitWebViewBase->priv;
if (priv->viewGestureController)
priv->viewGestureController->disconnectFromProcess();
#endif
}
void webkitWebViewBaseDidExitWebProcess(WebKitWebViewBase* webkitWebViewBase)
{
#if !USE(GTK4)
webkitWebViewBase->priv->viewGestureController = nullptr;
#endif
}
void webkitWebViewBaseDidRelaunchWebProcess(WebKitWebViewBase* webkitWebViewBase)
{
// Queue a resize to ensure the new DrawingAreaProxy is resized.
gtk_widget_queue_resize_no_redraw(GTK_WIDGET(webkitWebViewBase));
WebKitWebViewBasePrivate* priv = webkitWebViewBase->priv;
if (priv->acceleratedBackingStore) {
auto* drawingArea = static_cast<DrawingAreaProxyCoordinatedGraphics*>(priv->pageProxy->drawingArea());
priv->acceleratedBackingStore->update(drawingArea->layerTreeContext());
}
#if !USE(GTK4)
if (priv->viewGestureController)
priv->viewGestureController->connectToProcess();
else {
priv->viewGestureController = makeUnique<WebKit::ViewGestureController>(*priv->pageProxy);
priv->viewGestureController->setSwipeGestureEnabled(priv->isBackForwardNavigationGestureEnabled);
}
#endif
}
void webkitWebViewBasePageClosed(WebKitWebViewBase* webkitWebViewBase)
{
if (webkitWebViewBase->priv->acceleratedBackingStore)
webkitWebViewBase->priv->acceleratedBackingStore->update({ });
}
RefPtr<WebKit::ViewSnapshot> webkitWebViewBaseTakeViewSnapshot(WebKitWebViewBase* webkitWebViewBase, Optional<IntRect>&& clipRect)
{
WebPageProxy* page = webkitWebViewBase->priv->pageProxy.get();
IntSize size = clipRect ? clipRect->size() : page->viewSize();
float deviceScale = page->deviceScaleFactor();
size.scale(deviceScale);
RefPtr<cairo_surface_t> surface = adoptRef(cairo_image_surface_create(CAIRO_FORMAT_RGB24, size.width(), size.height()));
cairoSurfaceSetDeviceScale(surface.get(), deviceScale, deviceScale);
RefPtr<cairo_t> cr = adoptRef(cairo_create(surface.get()));
if (clipRect) {
cairo_translate(cr.get(), -clipRect->x(), -clipRect->y());
cairo_rectangle(cr.get(), clipRect->x(), clipRect->y(), clipRect->width(), clipRect->height());
cairo_clip(cr.get());
}
#if !USE(GTK4)
webkitWebViewBaseDraw(GTK_WIDGET(webkitWebViewBase), cr.get());
#endif
return ViewSnapshot::create(WTFMove(surface));
}
void webkitWebViewBaseDidStartProvisionalLoadForMainFrame(WebKitWebViewBase* webkitWebViewBase)
{
#if !USE(GTK4)
ViewGestureController* controller = webkitWebViewBaseViewGestureController(webkitWebViewBase);
if (controller && controller->isSwipeGestureEnabled())
controller->didStartProvisionalLoadForMainFrame();
#endif
}
void webkitWebViewBaseDidFirstVisuallyNonEmptyLayoutForMainFrame(WebKitWebViewBase* webkitWebViewBase)
{
#if !USE(GTK4)
ViewGestureController* controller = webkitWebViewBaseViewGestureController(webkitWebViewBase);
if (controller && controller->isSwipeGestureEnabled())
controller->didFirstVisuallyNonEmptyLayoutForMainFrame();
#endif
}
void webkitWebViewBaseDidFinishNavigation(WebKitWebViewBase* webkitWebViewBase, API::Navigation* navigation)
{
#if !USE(GTK4)
ViewGestureController* controller = webkitWebViewBaseViewGestureController(webkitWebViewBase);
if (controller && controller->isSwipeGestureEnabled())
controller->didFinishNavigation(navigation);
#endif
}
void webkitWebViewBaseDidFailNavigation(WebKitWebViewBase* webkitWebViewBase, API::Navigation* navigation)
{
#if !USE(GTK4)
ViewGestureController* controller = webkitWebViewBaseViewGestureController(webkitWebViewBase);
if (controller && controller->isSwipeGestureEnabled())
controller->didFailNavigation(navigation);
#endif
}
void webkitWebViewBaseDidSameDocumentNavigationForMainFrame(WebKitWebViewBase* webkitWebViewBase, SameDocumentNavigationType type)
{
#if !USE(GTK4)
ViewGestureController* controller = webkitWebViewBaseViewGestureController(webkitWebViewBase);
if (controller && controller->isSwipeGestureEnabled())
controller->didSameDocumentNavigationForMainFrame(type);
#endif
}
void webkitWebViewBaseDidRestoreScrollPosition(WebKitWebViewBase* webkitWebViewBase)
{
#if !USE(GTK4)
ViewGestureController* controller = webkitWebViewBaseViewGestureController(webkitWebViewBase);
if (controller && controller->isSwipeGestureEnabled())
webkitWebViewBase->priv->viewGestureController->didRestoreScrollPosition();
#endif
}
#if GTK_CHECK_VERSION(3, 24, 0)
static void emojiChooserEmojiPicked(WebKitWebViewBase* webkitWebViewBase, const char* text)
{
webkitWebViewBaseCompleteEmojiChooserRequest(webkitWebViewBase, String::fromUTF8(text));
}
static void emojiChooserClosed(WebKitWebViewBase* webkitWebViewBase)
{
// The emoji chooser first closes the popover and then emits emoji-picked signal, so complete
// the request if the emoji isn't picked before the next run loop iteration.
RunLoop::main().dispatch([webViewBase = GRefPtr<WebKitWebViewBase>(webkitWebViewBase)] {
webkitWebViewBaseCompleteEmojiChooserRequest(webViewBase.get(), emptyString());
});
webkitWebViewBase->priv->releaseEmojiChooserTimer.startOneShot(2_min);
}
#endif
void webkitWebViewBaseShowEmojiChooser(WebKitWebViewBase* webkitWebViewBase, const IntRect& caretRect, CompletionHandler<void(String)>&& completionHandler)
{
#if GTK_CHECK_VERSION(3, 24, 0)
WebKitWebViewBasePrivate* priv = webkitWebViewBase->priv;
priv->releaseEmojiChooserTimer.stop();
if (!priv->emojiChooser) {
#if USE(GTK4)
priv->emojiChooser = gtk_emoji_chooser_new();
gtk_widget_set_parent(priv->emojiChooser, GTK_WIDGET(webkitWebViewBase));
#else
priv->emojiChooser = webkitEmojiChooserNew();
gtk_popover_set_relative_to(GTK_POPOVER(priv->emojiChooser), GTK_WIDGET(webkitWebViewBase));
#endif
g_signal_connect_swapped(priv->emojiChooser, "emoji-picked", G_CALLBACK(emojiChooserEmojiPicked), webkitWebViewBase);
g_signal_connect_swapped(priv->emojiChooser, "closed", G_CALLBACK(emojiChooserClosed), webkitWebViewBase);
}
priv->emojiChooserCompletionHandler = WTFMove(completionHandler);
GdkRectangle gdkCaretRect = caretRect;
gtk_popover_set_pointing_to(GTK_POPOVER(priv->emojiChooser), &gdkCaretRect);
gtk_popover_popup(GTK_POPOVER(priv->emojiChooser));
#else
UNUSED_PARAM(webkitWebViewBase);
UNUSED_PARAM(caretRect);
completionHandler(emptyString());
#endif
}
#if USE(WPE_RENDERER)
int webkitWebViewBaseRenderHostFileDescriptor(WebKitWebViewBase* webkitWebViewBase)
{
if (!webkitWebViewBase->priv->acceleratedBackingStore)
return -1;
return webkitWebViewBase->priv->acceleratedBackingStore->renderHostFileDescriptor();
}
#endif
void webkitWebViewBaseRequestPointerLock(WebKitWebViewBase* webViewBase)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
ASSERT(!priv->pointerLockManager);
if (!priv->lastMotionEvent)
priv->lastMotionEvent = MotionEvent(GTK_WIDGET(webViewBase), nullptr);
priv->pointerLockManager = PointerLockManager::create(*priv->pageProxy, priv->lastMotionEvent->position, priv->lastMotionEvent->globalPosition,
priv->lastMotionEvent->button, priv->lastMotionEvent->buttons, priv->lastMotionEvent->modifiers);
if (priv->pointerLockManager->lock()) {
priv->pageProxy->didAllowPointerLock();
return;
}
priv->pointerLockManager = nullptr;
priv->pageProxy->didDenyPointerLock();
}
void webkitWebViewBaseDidLosePointerLock(WebKitWebViewBase* webViewBase)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (!priv->pointerLockManager)
return;
priv->pointerLockManager->unlock();
priv->pointerLockManager = nullptr;
}
void webkitWebViewBaseSetInputMethodContext(WebKitWebViewBase* webViewBase, WebKitInputMethodContext* context)
{
webViewBase->priv->inputMethodFilter.setContext(context);
}
WebKitInputMethodContext* webkitWebViewBaseGetInputMethodContext(WebKitWebViewBase* webViewBase)
{
return webViewBase->priv->inputMethodFilter.context();
}
void webkitWebViewBaseSynthesizeCompositionKeyPress(WebKitWebViewBase* webViewBase, const String& text, Optional<Vector<CompositionUnderline>>&& underlines, Optional<EditingRange>&& selectionRange)
{
webViewBase->priv->pageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(text, WTFMove(underlines), WTFMove(selectionRange)));
}
static inline OptionSet<WebEvent::Modifier> toWebKitModifiers(unsigned modifiers)
{
OptionSet<WebEvent::Modifier> webEventModifiers;
if (modifiers & GDK_CONTROL_MASK)
webEventModifiers.add(WebEvent::Modifier::ControlKey);
if (modifiers & GDK_SHIFT_MASK)
webEventModifiers.add(WebEvent::Modifier::ShiftKey);
if (modifiers & GDK_MOD1_MASK)
webEventModifiers.add(WebEvent::Modifier::AltKey);
if (modifiers & GDK_META_MASK)
webEventModifiers.add(WebEvent::Modifier::MetaKey);
if (PlatformKeyboardEvent::modifiersContainCapsLock(modifiers))
webEventModifiers.add(WebEvent::Modifier::CapsLockKey);
return webEventModifiers;
}
void webkitWebViewBaseSynthesizeMouseEvent(WebKitWebViewBase* webViewBase, MouseEventType type, unsigned button, unsigned short buttons, int x, int y, unsigned modifiers, int clickCount)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (priv->dialog)
return;
if (priv->pointerLockManager) {
priv->pointerLockManager->didReceiveMotionEvent(FloatPoint(x, y));
return;
}
WebMouseEvent::Button webEventButton = WebMouseEvent::NoButton;
switch (button) {
case 0:
webEventButton = WebMouseEvent::NoButton;
break;
case 1:
webEventButton = WebMouseEvent::LeftButton;
break;
case 2:
webEventButton = WebMouseEvent::MiddleButton;
break;
case 3:
webEventButton = WebMouseEvent::RightButton;
break;
}
unsigned short webEventButtons = 0;
if (buttons & GDK_BUTTON1_MASK)
webEventButtons |= 1;
if (buttons & GDK_BUTTON2_MASK)
webEventButtons |= 4;
if (buttons & GDK_BUTTON3_MASK)
webEventButtons |= 2;
Optional<FloatSize> movementDelta;
WebEvent::Type webEventType;
switch (type) {
case MouseEventType::Press:
webEventType = WebEvent::MouseDown;
priv->inputMethodFilter.cancelComposition();
gtk_widget_grab_focus(GTK_WIDGET(webViewBase));
break;
case MouseEventType::Release:
webEventType = WebEvent::MouseUp;
gtk_widget_grab_focus(GTK_WIDGET(webViewBase));
break;
case MouseEventType::Motion:
webEventType = WebEvent::MouseMove;
if (buttons & GDK_BUTTON1_MASK)
webEventButton = WebMouseEvent::LeftButton;
else if (buttons & GDK_BUTTON2_MASK)
webEventButton = WebMouseEvent::MiddleButton;
else if (buttons & GDK_BUTTON3_MASK)
webEventButton = WebMouseEvent::RightButton;
if (priv->lastMotionEvent)
movementDelta = FloatPoint(x, y) - priv->lastMotionEvent->globalPosition;
priv->lastMotionEvent = MotionEvent(FloatPoint(x, y), widgetRootCoords(GTK_WIDGET(webViewBase), x, y), webEventButton, webEventButtons, toWebKitModifiers(modifiers));
break;
}
priv->pageProxy->handleMouseEvent(NativeWebMouseEvent(webEventType, webEventButton, webEventButtons, { x, y },
widgetRootCoords(GTK_WIDGET(webViewBase), x, y), clickCount, toWebKitModifiers(modifiers), movementDelta));
}
void webkitWebViewBaseSynthesizeKeyEvent(WebKitWebViewBase* webViewBase, KeyEventType type, unsigned keyval, unsigned modifiers, ShouldTranslateKeyboardState shouldTranslate)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (priv->dialog)
return;
if (type != KeyEventType::Release) {
if (auto* popupMenu = priv->pageProxy->activePopupMenu()) {
auto* gtkPopupMenu = static_cast<WebPopupMenuProxyGtk*>(popupMenu);
if (gtkPopupMenu->handleKeyPress(keyval, GDK_CURRENT_TIME))
return;
if (keyval == GDK_KEY_Return) {
gtkPopupMenu->activateSelectedItem();
return;
}
}
#if ENABLE(FULLSCREEN_API)
if (priv->fullScreenModeActive) {
switch (keyval) {
case GDK_KEY_Escape:
case GDK_KEY_f:
case GDK_KEY_F:
priv->pageProxy->fullScreenManager()->requestExitFullScreen();
return;
default:
break;
}
}
#endif
#if !USE(GTK4)
if (keyval == GDK_KEY_Menu) {
webkitWebViewBasePopupMenu(GTK_WIDGET(webViewBase));
return;
}
#endif
}
auto keycode = widgetKeyvalToKeycode(GTK_WIDGET(webViewBase), keyval);
if (modifiers && shouldTranslate == ShouldTranslateKeyboardState::Yes) {
auto* display = gtk_widget_get_display(GTK_WIDGET(webViewBase));
#if USE(GTK4)
gdk_display_translate_key(display, keycode, static_cast<GdkModifierType>(modifiers), 0, &keyval, nullptr, nullptr, nullptr);
#else
gdk_keymap_translate_keyboard_state(gdk_keymap_get_for_display(display), keycode, static_cast<GdkModifierType>(modifiers), 0, &keyval, nullptr, nullptr, nullptr);
#endif
}
auto webEventModifiers = toWebKitModifiers(modifiers);
if (type != KeyEventType::Release) {
priv->pageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(
WebEvent::KeyDown,
PlatformKeyboardEvent::singleCharacterString(keyval),
PlatformKeyboardEvent::keyValueForGdkKeyCode(keyval),
PlatformKeyboardEvent::keyCodeForHardwareKeyCode(keycode),
PlatformKeyboardEvent::keyIdentifierForGdkKeyCode(keyval),
PlatformKeyboardEvent::windowsKeyCodeForGdkKeyCode(keyval),
static_cast<int>(keyval),
priv->keyBindingTranslator.commandsForKeyval(keyval, modifiers),
keyval >= GDK_KEY_KP_Space && keyval <= GDK_KEY_KP_9,
webEventModifiers));
}
if (type != KeyEventType::Press) {
priv->pageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(
WebEvent::KeyUp,
PlatformKeyboardEvent::singleCharacterString(keyval),
PlatformKeyboardEvent::keyValueForGdkKeyCode(keyval),
PlatformKeyboardEvent::keyCodeForHardwareKeyCode(keycode),
PlatformKeyboardEvent::keyIdentifierForGdkKeyCode(keyval),
PlatformKeyboardEvent::windowsKeyCodeForGdkKeyCode(keyval),
static_cast<int>(keyval),
{ },
keyval >= GDK_KEY_KP_Space && keyval <= GDK_KEY_KP_9,
webEventModifiers));
}
}
static inline WebWheelEvent::Phase toWebKitWheelEventPhase(WheelEventPhase phase)
{
switch (phase) {
case WheelEventPhase::NoPhase:
return WebWheelEvent::Phase::PhaseNone;
case WheelEventPhase::Began:
return WebWheelEvent::Phase::PhaseBegan;
case WheelEventPhase::Changed:
return WebWheelEvent::Phase::PhaseChanged;
case WheelEventPhase::Ended:
return WebWheelEvent::Phase::PhaseEnded;
case WheelEventPhase::Cancelled:
return WebWheelEvent::Phase::PhaseCancelled;
case WheelEventPhase::MayBegin:
return WebWheelEvent::Phase::PhaseMayBegin;
}
RELEASE_ASSERT_NOT_REACHED();
}
void webkitWebViewBaseSynthesizeWheelEvent(WebKitWebViewBase* webViewBase, double deltaX, double deltaY, int x, int y, WheelEventPhase phase, WheelEventPhase momentumPhase)
{
WebKitWebViewBasePrivate* priv = webViewBase->priv;
if (priv->dialog)
return;
FloatSize wheelTicks(deltaX, deltaY);
FloatSize delta(wheelTicks);
delta.scale(static_cast<float>(Scrollbar::pixelsPerLineStep()));
priv->pageProxy->handleWheelEvent(NativeWebWheelEvent({ x, y }, widgetRootCoords(GTK_WIDGET(webViewBase), x, y),
delta, wheelTicks, toWebKitWheelEventPhase(phase), toWebKitWheelEventPhase(momentumPhase)));
}