[GTK] Need WebKitContextMenuItemType to open emoji picker
authorcarlosgc@webkit.org <carlosgc@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 17 May 2019 12:39:30 +0000 (12:39 +0000)
committercarlosgc@webkit.org <carlosgc@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 17 May 2019 12:39:30 +0000 (12:39 +0000)
https://bugs.webkit.org/show_bug.cgi?id=176760

Reviewed by Michael Catanzaro.

Source/WebCore:

Add a new context menu item to insert an emoji.

* loader/EmptyClients.cpp: Empty implementation of ContextMenuClient::insertEmoji().
* page/ContextMenuClient.h: Add insertEmoji for GTK port.
* page/ContextMenuController.cpp:
(WebCore::ContextMenuController::contextMenuItemSelected): Handle insert emoji action.
(WebCore::ContextMenuController::populate): Add insert emoji item after select all.
(WebCore::ContextMenuController::checkOrEnableIfNeeded const): Handle insert emoji action.
* platform/ContextMenuItem.h: Add insert emoji action.
* platform/LocalizedStrings.h:
* platform/gtk/LocalizedStringsGtk.cpp:
(WebCore::contextMenuItemTagInsertEmoji):

Source/WebCore/platform/gtk/po:

* POTFILES.in: Add WebKitEmojiChooser.cpp.

Source/WebKit:

Add a default implementation to show the emoji chooser when requested by the application, either using the
context menu or keyboard shortcuts. GtkEmojiChooser is private in GTK, so we include our own copy, adapted to
the WebKit coding style. The emoji chooser is always shown by default when using GTK >= 3.24 for any editable
content. I'm going to add public API in a follow up patch to be able to use your own chooser, or even prevent
the default chooser from being shown, similar to what we do for other UI elements like file chooser, color
chooser, print dialog, etc.

* Shared/API/glib/WebKitContextMenuActions.cpp:
(webkitContextMenuActionGetActionTag): Handle insert emoji action.
(webkitContextMenuActionGetForContextMenuItem): Ditto.
(webkitContextMenuActionGetLabel): Ditto.
* SourcesGTK.txt:
* UIProcess/API/gtk/WebKitContextMenuActions.h:
* UIProcess/API/gtk/WebKitEmojiChooser.cpp: Added.
(webkitEmojiChooserAddEmoji):
(webkitEmojiChooserAddRecentItem):
(emojiActivated):
(emojiDataHasVariations):
(webkitEmojiChooserShowVariations):
(emojiLongPressed):
(emojiPressed):
(emojiPopupMenu):
(verticalAdjustmentChanged):
(webkitEmojiChooserSetupSectionBox):
(scrollToSection):
(webkitEmojiChooserSetupSectionButton):
(webkitEmojiChooserSetupRecent):
(webkitEmojiChooserEnsureEmptyResult):
(webkitEmojiChooserSearchChanged):
(webkitEmojiChooserSetupFilters):
(webkitEmojiChooserInitializeEmojiMaxWidth):
(webkitEmojiChooserConstructed):
(webkitEmojiChooserShow):
(webkit_emoji_chooser_class_init):
(webkitEmojiChooserNew):
* UIProcess/API/gtk/WebKitEmojiChooser.h: Added.
* UIProcess/API/gtk/WebKitWebViewBase.cpp:
(_WebKitWebViewBasePrivate::_WebKitWebViewBasePrivate): Add a timer to release the emoji chooser if not used
after 2 minutes.
(_WebKitWebViewBasePrivate::releaseEmojiChooserTimerFired): Destroy the emoji chooser.
(emojiChooserEmojiPicked): Complete the operation using the given emoji text.
(emojiChooserClosed): Complete the operation if needed using an empty string.
(webkitWebViewBaseShowEmojiChooser): Create the emoji chooser if needed and show it.
* UIProcess/API/gtk/WebKitWebViewBasePrivate.h:
* UIProcess/WebPageProxy.h: Add showEmojiPicker().
* UIProcess/WebPageProxy.messages.in: Add ShowEmojiPicker message.
* UIProcess/gtk/KeyBindingTranslator.cpp:
(WebKit::insertEmojiCallback): Add GtkInsertEmoji command.
(WebKit::KeyBindingTranslator::KeyBindingTranslator): Connect to insert-emoji signal.
* UIProcess/gtk/WebPageProxyGtk.cpp:
(WebKit::WebPageProxy::showEmojiPicker): Call webkitWebViewBaseShowEmojiChooser().
* WebProcess/WebCoreSupport/WebContextMenuClient.h: Override insertEmoji() for GTK port.
* WebProcess/WebCoreSupport/WebEditorClient.h: Add insertEmoji() for GTK port.
* WebProcess/WebCoreSupport/gtk/WebContextMenuClientGtk.cpp:
(WebKit::WebContextMenuClient::insertEmoji): Call WebPage::showEmojiPicker().
* WebProcess/WebCoreSupport/gtk/WebEditorClientGtk.cpp:
(WebKit::WebEditorClient::handleGtkEditorCommand): Call WebPage::showEmojiPicker() if command is GtkInsertEmoji.
(WebKit::WebEditorClient::executePendingEditorCommands): Handle Gtk specific commands.
(WebKit::WebEditorClient::handleKeyboardEvent): Use a reference instead of a pointer for Frame.
* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/gtk/WebPageGtk.cpp:
(WebKit::WebPage::showEmojiPicker): Send ShowEmojiPicker message to the UI process.

Tools:

Update context menu test to check insert emoji action is included in default context menu for editable content.

* TestWebKitAPI/Tests/WebKitGtk/TestContextMenu.cpp:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@245460 268f45cc-cd09-0410-ab3c-d52691b4dbfc

29 files changed:
Source/WebCore/ChangeLog
Source/WebCore/loader/EmptyClients.cpp
Source/WebCore/page/ContextMenuClient.h
Source/WebCore/page/ContextMenuController.cpp
Source/WebCore/platform/ContextMenuItem.h
Source/WebCore/platform/LocalizedStrings.h
Source/WebCore/platform/gtk/LocalizedStringsGtk.cpp
Source/WebCore/platform/gtk/po/ChangeLog
Source/WebCore/platform/gtk/po/POTFILES.in
Source/WebKit/ChangeLog
Source/WebKit/Shared/API/glib/WebKitContextMenuActions.cpp
Source/WebKit/SourcesGTK.txt
Source/WebKit/UIProcess/API/gtk/WebKitContextMenuActions.h
Source/WebKit/UIProcess/API/gtk/WebKitEmojiChooser.cpp [new file with mode: 0644]
Source/WebKit/UIProcess/API/gtk/WebKitEmojiChooser.h [new file with mode: 0644]
Source/WebKit/UIProcess/API/gtk/WebKitWebViewBase.cpp
Source/WebKit/UIProcess/API/gtk/WebKitWebViewBasePrivate.h
Source/WebKit/UIProcess/WebPageProxy.h
Source/WebKit/UIProcess/WebPageProxy.messages.in
Source/WebKit/UIProcess/gtk/KeyBindingTranslator.cpp
Source/WebKit/UIProcess/gtk/WebPageProxyGtk.cpp
Source/WebKit/WebProcess/WebCoreSupport/WebContextMenuClient.h
Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.h
Source/WebKit/WebProcess/WebCoreSupport/gtk/WebContextMenuClientGtk.cpp
Source/WebKit/WebProcess/WebCoreSupport/gtk/WebEditorClientGtk.cpp
Source/WebKit/WebProcess/WebPage/WebPage.h
Source/WebKit/WebProcess/WebPage/gtk/WebPageGtk.cpp
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/WebKitGtk/TestContextMenu.cpp

index 6e0da59..e4ededd 100644 (file)
@@ -1,3 +1,23 @@
+2019-05-16  Carlos Garcia Campos  <cgarcia@igalia.com>
+
+        [GTK] Need WebKitContextMenuItemType to open emoji picker
+        https://bugs.webkit.org/show_bug.cgi?id=176760
+
+        Reviewed by Michael Catanzaro.
+
+        Add a new context menu item to insert an emoji.
+
+        * loader/EmptyClients.cpp: Empty implementation of ContextMenuClient::insertEmoji().
+        * page/ContextMenuClient.h: Add insertEmoji for GTK port.
+        * page/ContextMenuController.cpp:
+        (WebCore::ContextMenuController::contextMenuItemSelected): Handle insert emoji action.
+        (WebCore::ContextMenuController::populate): Add insert emoji item after select all.
+        (WebCore::ContextMenuController::checkOrEnableIfNeeded const): Handle insert emoji action.
+        * platform/ContextMenuItem.h: Add insert emoji action.
+        * platform/LocalizedStrings.h:
+        * platform/gtk/LocalizedStringsGtk.cpp:
+        (WebCore::contextMenuItemTagInsertEmoji):
+
 2019-05-16  Greg Doolittle  <gr3g@apple.com>
 
         AX: Unship some ARIA string reflectors that are to-be-replaced by element reflection
index 55d083e..a754aad 100644 (file)
@@ -109,6 +109,10 @@ class EmptyContextMenuClient final : public ContextMenuClient {
     void searchWithSpotlight() final { }
 #endif
 
+#if PLATFORM(GTK)
+    void insertEmoji(Frame&) final { }
+#endif
+
 #if USE(ACCESSIBILITY_CONTEXT_MENUS)
     void showContextMenu() final { }
 #endif
index 889ee99..5c5d50a 100644 (file)
@@ -49,6 +49,10 @@ public:
     virtual void searchWithSpotlight() = 0;
 #endif
 
+#if PLATFORM(GTK)
+    virtual void insertEmoji(Frame&) = 0;
+#endif
+
 #if USE(ACCESSIBILITY_CONTEXT_MENUS)
     virtual void showContextMenu() = 0;
 #endif
index bd7a3db..1582575 100644 (file)
@@ -357,11 +357,12 @@ void ContextMenuController::contextMenuItemSelected(ContextMenuAction action, co
     case ContextMenuItemTagUnicodeInsertZWNJMark:
         insertUnicodeCharacter(zeroWidthNonJoiner, *frame);
         break;
-#endif
-#if PLATFORM(GTK)
     case ContextMenuItemTagSelectAll:
         frame->editor().command("SelectAll").execute();
         break;
+    case ContextMenuItemTagInsertEmoji:
+        m_client.insertEmoji(*frame);
+        break;
 #endif
     case ContextMenuItemTagSpellingGuess: {
         VisibleSelection selection = frame->selection().selection();
@@ -811,9 +812,8 @@ void ContextMenuController::populate()
     ContextMenuItem PasteItem(ActionType, ContextMenuItemTagPaste, contextMenuItemTagPaste());
 #if PLATFORM(GTK)
     ContextMenuItem DeleteItem(ActionType, ContextMenuItemTagDelete, contextMenuItemTagDelete());
-#endif
-#if PLATFORM(GTK)
     ContextMenuItem SelectAllItem(ActionType, ContextMenuItemTagSelectAll, contextMenuItemTagSelectAll());
+    ContextMenuItem InsertEmojiItem(ActionType, ContextMenuItemTagInsertEmoji, contextMenuItemTagInsertEmoji());
 #endif
 
 #if PLATFORM(GTK) || PLATFORM(WIN)
@@ -1049,9 +1049,8 @@ void ContextMenuController::populate()
 #if PLATFORM(GTK)
         appendItem(DeleteItem, m_contextMenu.get());
         appendItem(*separatorItem(), m_contextMenu.get());
-#endif
-#if PLATFORM(GTK)
         appendItem(SelectAllItem, m_contextMenu.get());
+        appendItem(InsertEmojiItem, m_contextMenu.get());
 #endif
 
         if (!inPasswordField) {
@@ -1206,6 +1205,10 @@ void ContextMenuController::checkOrEnableIfNeeded(ContextMenuItem& item) const
         case ContextMenuItemTagDelete:
             shouldEnable = frame->editor().canDelete();
             break;
+        case ContextMenuItemTagInsertEmoji:
+            shouldEnable = frame->editor().canEdit();
+            break;
+        case ContextMenuItemTagSelectAll:
         case ContextMenuItemTagInputMethods:
         case ContextMenuItemTagUnicode:
         case ContextMenuItemTagUnicodeInsertLRMMark:
@@ -1221,11 +1224,6 @@ void ContextMenuController::checkOrEnableIfNeeded(ContextMenuItem& item) const
             shouldEnable = true;
             break;
 #endif
-#if PLATFORM(GTK)
-        case ContextMenuItemTagSelectAll:
-            shouldEnable = true;
-            break;
-#endif
         case ContextMenuItemTagUnderline: {
             shouldCheck = frame->editor().selectionHasStyle(CSSPropertyWebkitTextDecorationsInEffect, "underline") != FalseTriState;
             shouldEnable = frame->editor().canEditRichly();
index 1b0f745..d633d23 100644 (file)
@@ -68,6 +68,7 @@ enum ContextMenuAction {
     ContextMenuItemTagUnicodeInsertZWSMark,
     ContextMenuItemTagUnicodeInsertZWJMark,
     ContextMenuItemTagUnicodeInsertZWNJMark,
+    ContextMenuItemTagInsertEmoji,
 #endif
     ContextMenuItemTagSpellingGuess,
     ContextMenuItemTagNoGuessesFound,
index e8029ff..8a7ac88 100644 (file)
@@ -83,9 +83,8 @@ namespace WebCore {
     String contextMenuItemTagUnicodeInsertZWSMark();
     String contextMenuItemTagUnicodeInsertZWJMark();
     String contextMenuItemTagUnicodeInsertZWNJMark();
-#endif
-#if PLATFORM(GTK)
     String contextMenuItemTagSelectAll();
+    String contextMenuItemTagInsertEmoji();
 #endif
     String contextMenuItemTagNoGuessesFound();
     String contextMenuItemTagIgnoreSpelling();
index ee74b5a..07b7f47 100644 (file)
@@ -98,6 +98,11 @@ String contextMenuItemTagSelectAll()
     return String::fromUTF8(_("Select _All"));
 }
 
+String contextMenuItemTagInsertEmoji()
+{
+    return String::fromUTF8(_("Insert _Emoji"));
+}
+
 String contextMenuItemTagUnicode()
 {
     return String::fromUTF8(_("_Insert Unicode Control Character"));
index dd104a8..e3e2335 100644 (file)
@@ -1,3 +1,12 @@
+2019-05-16  Carlos Garcia Campos  <cgarcia@igalia.com>
+
+        [GTK] Need WebKitContextMenuItemType to open emoji picker
+        https://bugs.webkit.org/show_bug.cgi?id=176760
+
+        Reviewed by Michael Catanzaro.
+
+        * POTFILES.in: Add WebKitEmojiChooser.cpp.
+
 2019-04-30  Álvaro Torralba  <donfrutosgomez@gmail.com>
 
         Update Spanish Translation
index a329a5b..b54d21d 100644 (file)
@@ -31,6 +31,7 @@ LocalizedStringsGtk.cpp
 ../../../WebKit/UIProcess/API/glib/WebKitWindowProperties.cpp
 ../../../WebKit/UIProcess/API/gtk/WebKitAuthenticationDialog.cpp
 ../../../WebKit/UIProcess/API/gtk/WebKitColorChooserRequest.cpp
+../../../WebKit/UIProcess/API/gtk/WebKitEmojiChooser.cpp
 ../../../WebKit/UIProcess/API/gtk/WebKitPrintCustomWidget.cpp
 ../../../WebKit/UIProcess/API/gtk/WebKitPrintOperation.cpp
 ../../../WebKit/UIProcess/API/gtk/WebKitScriptDialogImpl.cpp
index d6247a9..09beb5b 100644 (file)
@@ -1,3 +1,73 @@
+2019-05-16  Carlos Garcia Campos  <cgarcia@igalia.com>
+
+        [GTK] Need WebKitContextMenuItemType to open emoji picker
+        https://bugs.webkit.org/show_bug.cgi?id=176760
+
+        Reviewed by Michael Catanzaro.
+
+        Add a default implementation to show the emoji chooser when requested by the application, either using the
+        context menu or keyboard shortcuts. GtkEmojiChooser is private in GTK, so we include our own copy, adapted to
+        the WebKit coding style. The emoji chooser is always shown by default when using GTK >= 3.24 for any editable
+        content. I'm going to add public API in a follow up patch to be able to use your own chooser, or even prevent
+        the default chooser from being shown, similar to what we do for other UI elements like file chooser, color
+        chooser, print dialog, etc.
+
+        * Shared/API/glib/WebKitContextMenuActions.cpp:
+        (webkitContextMenuActionGetActionTag): Handle insert emoji action.
+        (webkitContextMenuActionGetForContextMenuItem): Ditto.
+        (webkitContextMenuActionGetLabel): Ditto.
+        * SourcesGTK.txt:
+        * UIProcess/API/gtk/WebKitContextMenuActions.h:
+        * UIProcess/API/gtk/WebKitEmojiChooser.cpp: Added.
+        (webkitEmojiChooserAddEmoji):
+        (webkitEmojiChooserAddRecentItem):
+        (emojiActivated):
+        (emojiDataHasVariations):
+        (webkitEmojiChooserShowVariations):
+        (emojiLongPressed):
+        (emojiPressed):
+        (emojiPopupMenu):
+        (verticalAdjustmentChanged):
+        (webkitEmojiChooserSetupSectionBox):
+        (scrollToSection):
+        (webkitEmojiChooserSetupSectionButton):
+        (webkitEmojiChooserSetupRecent):
+        (webkitEmojiChooserEnsureEmptyResult):
+        (webkitEmojiChooserSearchChanged):
+        (webkitEmojiChooserSetupFilters):
+        (webkitEmojiChooserInitializeEmojiMaxWidth):
+        (webkitEmojiChooserConstructed):
+        (webkitEmojiChooserShow):
+        (webkit_emoji_chooser_class_init):
+        (webkitEmojiChooserNew):
+        * UIProcess/API/gtk/WebKitEmojiChooser.h: Added.
+        * UIProcess/API/gtk/WebKitWebViewBase.cpp:
+        (_WebKitWebViewBasePrivate::_WebKitWebViewBasePrivate): Add a timer to release the emoji chooser if not used
+        after 2 minutes.
+        (_WebKitWebViewBasePrivate::releaseEmojiChooserTimerFired): Destroy the emoji chooser.
+        (emojiChooserEmojiPicked): Complete the operation using the given emoji text.
+        (emojiChooserClosed): Complete the operation if needed using an empty string.
+        (webkitWebViewBaseShowEmojiChooser): Create the emoji chooser if needed and show it.
+        * UIProcess/API/gtk/WebKitWebViewBasePrivate.h:
+        * UIProcess/WebPageProxy.h: Add showEmojiPicker().
+        * UIProcess/WebPageProxy.messages.in: Add ShowEmojiPicker message.
+        * UIProcess/gtk/KeyBindingTranslator.cpp:
+        (WebKit::insertEmojiCallback): Add GtkInsertEmoji command.
+        (WebKit::KeyBindingTranslator::KeyBindingTranslator): Connect to insert-emoji signal.
+        * UIProcess/gtk/WebPageProxyGtk.cpp:
+        (WebKit::WebPageProxy::showEmojiPicker): Call webkitWebViewBaseShowEmojiChooser().
+        * WebProcess/WebCoreSupport/WebContextMenuClient.h: Override insertEmoji() for GTK port.
+        * WebProcess/WebCoreSupport/WebEditorClient.h: Add insertEmoji() for GTK port.
+        * WebProcess/WebCoreSupport/gtk/WebContextMenuClientGtk.cpp:
+        (WebKit::WebContextMenuClient::insertEmoji): Call WebPage::showEmojiPicker().
+        * WebProcess/WebCoreSupport/gtk/WebEditorClientGtk.cpp:
+        (WebKit::WebEditorClient::handleGtkEditorCommand): Call WebPage::showEmojiPicker() if command is GtkInsertEmoji.
+        (WebKit::WebEditorClient::executePendingEditorCommands): Handle Gtk specific commands.
+        (WebKit::WebEditorClient::handleKeyboardEvent): Use a reference instead of a pointer for Frame.
+        * WebProcess/WebPage/WebPage.h:
+        * WebProcess/WebPage/gtk/WebPageGtk.cpp:
+        (WebKit::WebPage::showEmojiPicker): Send ShowEmojiPicker message to the UI process.
+
 2019-05-16  John Wilander  <wilander@apple.com>
 
         Storage Access API: Call completion handlers in NetworkConnectionToWebProcess::hasStorageAccess() and NetworkConnectionToWebProcess::requestStorageAccess() when feature is off
index dc57d53..2150e89 100644 (file)
@@ -83,6 +83,8 @@ ContextMenuAction webkitContextMenuActionGetActionTag(WebKitContextMenuAction ac
         return ContextMenuItemTagDelete;
     case WEBKIT_CONTEXT_MENU_ACTION_SELECT_ALL:
         return ContextMenuItemTagSelectAll;
+    case WEBKIT_CONTEXT_MENU_ACTION_INSERT_EMOJI:
+        return ContextMenuItemTagInsertEmoji;
     case WEBKIT_CONTEXT_MENU_ACTION_INPUT_METHODS:
         return ContextMenuItemTagInputMethods;
     case WEBKIT_CONTEXT_MENU_ACTION_UNICODE:
@@ -183,6 +185,8 @@ WebKitContextMenuAction webkitContextMenuActionGetForContextMenuItem(const WebKi
         return WEBKIT_CONTEXT_MENU_ACTION_DELETE;
     case ContextMenuItemTagSelectAll:
         return WEBKIT_CONTEXT_MENU_ACTION_SELECT_ALL;
+    case ContextMenuItemTagInsertEmoji:
+        return WEBKIT_CONTEXT_MENU_ACTION_INSERT_EMOJI;
     case ContextMenuItemTagInputMethods:
         return WEBKIT_CONTEXT_MENU_ACTION_INPUT_METHODS;
     case ContextMenuItemTagUnicode:
@@ -281,6 +285,8 @@ String webkitContextMenuActionGetLabel(WebKitContextMenuAction action)
         return contextMenuItemTagDelete();
     case WEBKIT_CONTEXT_MENU_ACTION_SELECT_ALL:
         return contextMenuItemTagSelectAll();
+    case WEBKIT_CONTEXT_MENU_ACTION_INSERT_EMOJI:
+        return contextMenuItemTagInsertEmoji();
     case WEBKIT_CONTEXT_MENU_ACTION_INPUT_METHODS:
         return contextMenuItemTagInputMethods();
     case WEBKIT_CONTEXT_MENU_ACTION_UNICODE:
index 7c3d86f..8b5367f 100644 (file)
@@ -189,6 +189,7 @@ UIProcess/API/gtk/PageClientImpl.cpp @no-unify
 UIProcess/API/gtk/WebKitAuthenticationDialog.cpp @no-unify
 UIProcess/API/gtk/WebKitColorChooser.cpp @no-unify
 UIProcess/API/gtk/WebKitColorChooserRequest.cpp @no-unify
+UIProcess/API/gtk/WebKitEmojiChooser.cpp @no-unify
 UIProcess/API/gtk/WebKitOptionMenu.cpp @no-unify
 UIProcess/API/gtk/WebKitOptionMenuItem.cpp @no-unify
 UIProcess/API/gtk/WebKitPopupMenu.cpp @no-unify
index 61dc243..48c8fdd 100644 (file)
@@ -74,6 +74,7 @@ G_BEGIN_DECLS
  * @WEBKIT_CONTEXT_MENU_ACTION_MEDIA_MUTE: Mute current media element.
  * @WEBKIT_CONTEXT_MENU_ACTION_DOWNLOAD_VIDEO_TO_DISK: Download video to disk. Since 2.2
  * @WEBKIT_CONTEXT_MENU_ACTION_DOWNLOAD_AUDIO_TO_DISK: Download audio to disk. Since 2.2
+ * @WEBKIT_CONTEXT_MENU_ACTION_INSERT_EMOJI: Insert an emoji. Since 2.26
  * @WEBKIT_CONTEXT_MENU_ACTION_CUSTOM: Custom action defined by applications.
  *
  * Enum values used to denote the stock actions for
@@ -125,6 +126,7 @@ typedef enum {
     WEBKIT_CONTEXT_MENU_ACTION_MEDIA_MUTE,
     WEBKIT_CONTEXT_MENU_ACTION_DOWNLOAD_VIDEO_TO_DISK,
     WEBKIT_CONTEXT_MENU_ACTION_DOWNLOAD_AUDIO_TO_DISK,
+    WEBKIT_CONTEXT_MENU_ACTION_INSERT_EMOJI,
 
     WEBKIT_CONTEXT_MENU_ACTION_CUSTOM = 10000
 } WebKitContextMenuAction;
diff --git a/Source/WebKit/UIProcess/API/gtk/WebKitEmojiChooser.cpp b/Source/WebKit/UIProcess/API/gtk/WebKitEmojiChooser.cpp
new file mode 100644 (file)
index 0000000..4e741d6
--- /dev/null
@@ -0,0 +1,607 @@
+/*
+ * Copyright (C) 2019 Igalia S.L.
+ * Copyright (C) 2017 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+// GtkEmojiChooser is private in GTK 3, so this is based in the GTK code, just adapted to
+// WebKit coding style, using some internal types from WTF to simplify the implementation
+// and not using GtkBuilder for the UI.
+
+#include "config.h"
+#include "WebKitEmojiChooser.h"
+
+#if GTK_CHECK_VERSION(3, 24, 0)
+
+#include <glib/gi18n-lib.h>
+#include <wtf/HashSet.h>
+#include <wtf/Vector.h>
+#include <wtf/glib/GRefPtr.h>
+#include <wtf/glib/GUniquePtr.h>
+#include <wtf/glib/WTFGType.h>
+#include <wtf/text/CString.h>
+
+enum {
+    EMOJI_PICKED,
+
+    LAST_SIGNAL
+};
+
+struct EmojiSection {
+    GtkWidget* heading { nullptr };
+    GtkWidget* box { nullptr };
+    GtkWidget* button { nullptr };
+    bool isEmpty { false };
+};
+
+using SectionList = Vector<EmojiSection, 9>;
+
+struct _WebKitEmojiChooserPrivate {
+    GtkWidget* stack;
+    GtkWidget* swindow;
+    GtkWidget* searchEntry;
+    SectionList sections;
+    GRefPtr<GSettings> settings;
+    HashSet<GRefPtr<GtkGesture>> gestures;
+    int emojiMaxWidth;
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+WEBKIT_DEFINE_TYPE(WebKitEmojiChooser, webkit_emoji_chooser, GTK_TYPE_POPOVER)
+
+static void emojiPopupMenu(GtkWidget*, WebKitEmojiChooser*);
+
+static const unsigned boxSpace = 6;
+
+static void emojiHovered(GtkWidget* widget, GdkEvent* event)
+{
+    if (event->type == GDK_ENTER_NOTIFY)
+        gtk_widget_set_state_flags(widget, GTK_STATE_FLAG_PRELIGHT, FALSE);
+    else
+        gtk_widget_unset_state_flags(widget, GTK_STATE_FLAG_PRELIGHT);
+}
+
+static GtkWidget* webkitEmojiChooserAddEmoji(WebKitEmojiChooser* chooser, GtkFlowBox* parent, GVariant* item, bool prepend = false, gunichar modifier = 0)
+{
+    char text[64];
+    char* textPtr = text;
+    GRefPtr<GVariant> codes = adoptGRef(g_variant_get_child_value(item, 0));
+    for (unsigned i = 0; i < g_variant_n_children(codes.get()); ++i) {
+        gunichar code;
+        g_variant_get_child(codes.get(), i, "u", &code);
+        if (!code)
+            code = modifier;
+        if (code)
+            textPtr += g_unichar_to_utf8(code, textPtr);
+    }
+    // U+FE0F is the Emoji variation selector
+    textPtr += g_unichar_to_utf8(0xFE0F, textPtr);
+    textPtr[0] = '\0';
+
+    GtkWidget* label = gtk_label_new(text);
+    PangoAttrList* attributes = pango_attr_list_new();
+    pango_attr_list_insert(attributes, pango_attr_scale_new(PANGO_SCALE_X_LARGE));
+    gtk_label_set_attributes(GTK_LABEL(label), attributes);
+    pango_attr_list_unref(attributes);
+
+    PangoLayout* layout = gtk_label_get_layout(GTK_LABEL(label));
+    PangoRectangle rect;
+    pango_layout_get_extents(layout, &rect, nullptr);
+    // Check for fallback rendering that generates too wide items.
+    if (pango_layout_get_unknown_glyphs_count(layout) || rect.width >= 1.5 * chooser->priv->emojiMaxWidth) {
+        gtk_widget_destroy(label);
+        return nullptr;
+    }
+
+    GtkWidget* child = gtk_flow_box_child_new();
+    gtk_style_context_add_class(gtk_widget_get_style_context(child), "emoji");
+    g_object_set_data_full(G_OBJECT(child), "emoji-data", g_variant_ref(item), reinterpret_cast<GDestroyNotify>(g_variant_unref));
+    if (modifier)
+        g_object_set_data(G_OBJECT(child), "modifier", GUINT_TO_POINTER(modifier));
+
+    GtkWidget* eventBox = gtk_event_box_new();
+    gtk_widget_add_events(eventBox, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
+    g_signal_connect(eventBox, "enter-notify-event", G_CALLBACK(emojiHovered), nullptr);
+    g_signal_connect(eventBox, "leave-notify-event", G_CALLBACK(emojiHovered), nullptr);
+    gtk_container_add(GTK_CONTAINER(eventBox), label);
+    gtk_widget_show(label);
+
+    gtk_container_add(GTK_CONTAINER(child), eventBox);
+    gtk_widget_show(eventBox);
+
+    gtk_flow_box_insert(parent, child, prepend ? 0 : -1);
+    gtk_widget_show(child);
+
+    return child;
+}
+
+static void webkitEmojiChooserAddRecentItem(WebKitEmojiChooser* chooser, GVariant* item, gunichar modifier)
+{
+    GRefPtr<GVariant> protectItem(item);
+    GVariantBuilder builder;
+    g_variant_builder_init(&builder, G_VARIANT_TYPE("a((auss)u)"));
+    g_variant_builder_add(&builder, "(@(auss)u)", item, modifier);
+
+    auto& section = chooser->priv->sections.first();
+
+    static const unsigned maxRecentItems = 7 * 3;
+
+    GUniquePtr<GList> children(gtk_container_get_children(GTK_CONTAINER(section.box)));
+    unsigned i = 1;
+    for (auto* l = children.get(); l; l = g_list_next(l), ++i) {
+        auto* item2 = static_cast<GVariant*>(g_object_get_data(G_OBJECT(l->data), "emoji-data"));
+        auto modifier2 = static_cast<gunichar>(GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(l->data), "modifier")));
+        if (modifier == modifier2 && g_variant_equal(item, item2)) {
+            gtk_widget_destroy(GTK_WIDGET(l->data));
+            --i;
+            continue;
+        }
+
+        if (i >= maxRecentItems) {
+            gtk_widget_destroy(GTK_WIDGET(l->data));
+            continue;
+        }
+
+        g_variant_builder_add(&builder, "(@(auss)u)", item2, modifier2);
+    }
+
+    auto* child = webkitEmojiChooserAddEmoji(chooser, GTK_FLOW_BOX(section.box), item, true, modifier);
+    if (child)
+        g_signal_connect(child, "popup-menu", G_CALLBACK(emojiPopupMenu), chooser);
+
+    gtk_widget_show(section.box);
+    gtk_widget_set_sensitive(section.button, TRUE);
+
+    g_settings_set_value(chooser->priv->settings.get(), "recent-emoji", g_variant_builder_end(&builder));
+}
+
+static void emojiActivated(GtkFlowBox* box, GtkFlowBoxChild* child, WebKitEmojiChooser* chooser)
+{
+    GtkWidget* label = gtk_bin_get_child(GTK_BIN(gtk_bin_get_child(GTK_BIN(child))));
+    GUniquePtr<char> text(g_strdup(gtk_label_get_label(GTK_LABEL(label))));
+
+    auto* item = static_cast<GVariant*>(g_object_get_data(G_OBJECT(child), "emoji-data"));
+    auto modifier = static_cast<gunichar>(GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(child), "modifier")));
+    webkitEmojiChooserAddRecentItem(chooser, item, modifier);
+    g_signal_emit(chooser, signals[EMOJI_PICKED], 0, text.get());
+
+    gtk_popover_popdown(GTK_POPOVER(chooser));
+}
+
+static bool emojiDataHasVariations(GVariant* emojiData)
+{
+    GRefPtr<GVariant> codes = adoptGRef(g_variant_get_child_value(emojiData, 0));
+    for (size_t i = 0; i < g_variant_n_children(codes.get()); ++i) {
+        gunichar code;
+        g_variant_get_child(codes.get(), i, "u", &code);
+        if (!code)
+            return true;
+    }
+    return false;
+}
+
+static void webkitEmojiChooserShowVariations(WebKitEmojiChooser* chooser, GtkWidget* child)
+{
+    if (!child)
+        return;
+
+    auto* emojiData = static_cast<GVariant*>(g_object_get_data(G_OBJECT(child), "emoji-data"));
+    if (!emojiData)
+        return;
+
+    if (!emojiDataHasVariations(emojiData))
+        return;
+
+    GtkWidget* popover = gtk_popover_new(child);
+    GtkWidget* view = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+    gtk_style_context_add_class(gtk_widget_get_style_context(view), "view");
+    GtkWidget* box = gtk_flow_box_new();
+    g_signal_connect(box, "child-activated", G_CALLBACK(emojiActivated), chooser);
+    gtk_flow_box_set_homogeneous(GTK_FLOW_BOX(box), TRUE);
+    gtk_flow_box_set_min_children_per_line(GTK_FLOW_BOX(box), 6);
+    gtk_flow_box_set_max_children_per_line(GTK_FLOW_BOX(box), 6);
+    gtk_flow_box_set_activate_on_single_click(GTK_FLOW_BOX(box), TRUE);
+    gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(box), GTK_SELECTION_NONE);
+    gtk_container_add(GTK_CONTAINER(view), box);
+    gtk_widget_show(box);
+    gtk_container_add(GTK_CONTAINER(popover), view);
+    gtk_widget_show(view);
+
+    webkitEmojiChooserAddEmoji(chooser, GTK_FLOW_BOX(box), emojiData);
+    for (gunichar modifier = 0x1F3FB; modifier <= 0x1F3FF; ++modifier)
+        webkitEmojiChooserAddEmoji(chooser, GTK_FLOW_BOX(box), emojiData, modifier);
+
+    gtk_popover_popup(GTK_POPOVER(popover));
+}
+
+static void emojiLongPressed(GtkGesture* gesture, double x, double y, WebKitEmojiChooser* chooser)
+{
+    auto* box = GTK_FLOW_BOX(gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture)));
+    webkitEmojiChooserShowVariations(chooser, GTK_WIDGET(gtk_flow_box_get_child_at_pos(box, x, y)));
+}
+
+static void emojiPressed(GtkGesture* gesture, int, double x, double y, WebKitEmojiChooser* chooser)
+{
+    emojiLongPressed(gesture, x, y, chooser);
+}
+
+static void emojiPopupMenu(GtkWidget* child, WebKitEmojiChooser* chooser)
+{
+    webkitEmojiChooserShowVariations(chooser, child);
+}
+
+static void verticalAdjustmentChanged(GtkAdjustment* adjustment, WebKitEmojiChooser* chooser)
+{
+    double value = gtk_adjustment_get_value(adjustment);
+    EmojiSection* sectionToSelect = nullptr;
+    for (auto& section : chooser->priv->sections) {
+        GtkAllocation allocation;
+        if (section.heading)
+            gtk_widget_get_allocation(section.heading, &allocation);
+        else
+            gtk_widget_get_allocation(section.box, &allocation);
+
+        if (value < allocation.y - boxSpace)
+            break;
+
+        sectionToSelect = &section;
+    }
+
+    if (!sectionToSelect)
+        sectionToSelect = &chooser->priv->sections[0];
+
+    for (auto& section : chooser->priv->sections) {
+        if (&section == sectionToSelect)
+            gtk_widget_set_state_flags(section.button, GTK_STATE_FLAG_CHECKED, FALSE);
+        else
+            gtk_widget_unset_state_flags(section.button, GTK_STATE_FLAG_CHECKED);
+    }
+}
+
+static GtkWidget* webkitEmojiChooserSetupSectionBox(WebKitEmojiChooser* chooser, GtkBox* parent, const char* title, GtkAdjustment* adjustment, gboolean canHaveVariations = FALSE)
+{
+    EmojiSection section;
+    if (title) {
+        GtkWidget* label = gtk_label_new(title);
+        section.heading = label;
+        gtk_label_set_xalign(GTK_LABEL(label), 0);
+        gtk_box_pack_start(parent, label, FALSE, FALSE, 0);
+        gtk_widget_show(label);
+    }
+
+    GtkWidget* box = gtk_flow_box_new();
+    section.box = box;
+    g_signal_connect(box, "child-activated", G_CALLBACK(emojiActivated), chooser);
+    gtk_flow_box_set_homogeneous(GTK_FLOW_BOX(box), TRUE);
+    gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(box), GTK_SELECTION_NONE);
+    gtk_container_set_focus_vadjustment(GTK_CONTAINER(box), adjustment);
+    gtk_box_pack_start(parent, box, FALSE, FALSE, 0);
+    gtk_widget_show(box);
+
+    if (canHaveVariations) {
+        GRefPtr<GtkGesture> gesture = adoptGRef(gtk_gesture_long_press_new(box));
+        g_signal_connect(gesture.get(), "pressed", G_CALLBACK(emojiLongPressed), chooser);
+        chooser->priv->gestures.add(WTFMove(gesture));
+        GRefPtr<GtkGesture> multiGesture = adoptGRef(gtk_gesture_multi_press_new(box));
+        gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(multiGesture.get()), GDK_BUTTON_SECONDARY);
+        g_signal_connect(multiGesture.get(), "pressed", G_CALLBACK(emojiPressed), chooser);
+        chooser->priv->gestures.add(WTFMove(multiGesture));
+    }
+
+    chooser->priv->sections.append(WTFMove(section));
+    return box;
+}
+
+static void scrollToSection(GtkButton* button, gpointer data)
+{
+    auto* chooser = WEBKIT_EMOJI_CHOOSER(gtk_widget_get_ancestor(GTK_WIDGET(button), WEBKIT_TYPE_EMOJI_CHOOSER));
+    auto& section = chooser->priv->sections[GPOINTER_TO_UINT(data)];
+    GtkAdjustment* adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(chooser->priv->swindow));
+    if (section.heading) {
+        GtkAllocation allocation = { 0, 0, 0, 0 };
+        gtk_widget_get_allocation(section.heading, &allocation);
+        gtk_adjustment_set_value(adjustment, allocation.y - boxSpace);
+    } else
+        gtk_adjustment_set_value(adjustment, 0);
+}
+
+static void webkitEmojiChooserSetupSectionButton(WebKitEmojiChooser* chooser, GtkBox* parent, const char* iconName, const char* tooltip)
+{
+    GtkWidget* button = gtk_button_new_from_icon_name(iconName, GTK_ICON_SIZE_BUTTON);
+    chooser->priv->sections.last().button = button;
+    gtk_style_context_add_class(gtk_widget_get_style_context(button), "emoji-section");
+    gtk_widget_set_tooltip_text(button, tooltip);
+    g_signal_connect(button, "clicked", G_CALLBACK(scrollToSection), GUINT_TO_POINTER(chooser->priv->sections.size() - 1));
+    gtk_box_pack_start(parent, button, FALSE, FALSE, 0);
+    gtk_widget_show(button);
+}
+
+static void webkitEmojiChooserSetupRecent(WebKitEmojiChooser* chooser, GtkBox* emojiBox, GtkBox* buttonBox, GtkAdjustment* adjustment)
+{
+    GtkWidget* flowBox = webkitEmojiChooserSetupSectionBox(chooser, emojiBox, nullptr, adjustment, true);
+    webkitEmojiChooserSetupSectionButton(chooser, buttonBox, "emoji-recent-symbolic", _("Recent"));
+
+    bool isEmpty = true;
+    GRefPtr<GVariant> variant = adoptGRef(g_settings_get_value(chooser->priv->settings.get(), "recent-emoji"));
+    GVariantIter iter;
+    g_variant_iter_init(&iter, variant.get());
+    while (GRefPtr<GVariant> item = adoptGRef(g_variant_iter_next_value(&iter))) {
+        GRefPtr<GVariant> emojiData = adoptGRef(g_variant_get_child_value(item.get(), 0));
+        gunichar modifier;
+        g_variant_get_child(item.get(), 1, "u", &modifier);
+        if (auto* child = webkitEmojiChooserAddEmoji(chooser, GTK_FLOW_BOX(flowBox), emojiData.get(), true, modifier))
+            g_signal_connect(child, "popup-menu", G_CALLBACK(emojiPopupMenu), chooser);
+        isEmpty = false;
+    }
+
+    if (isEmpty) {
+        gtk_widget_hide(flowBox);
+        gtk_widget_set_sensitive(chooser->priv->sections.first().button, FALSE);
+    }
+}
+
+static void webkitEmojiChooserEnsureEmptyResult(WebKitEmojiChooser* chooser)
+{
+    if (gtk_stack_get_child_by_name(GTK_STACK(chooser->priv->stack), "empty"))
+        return;
+
+    GtkWidget* grid = gtk_grid_new();
+    gtk_grid_set_row_spacing(GTK_GRID(grid), 12);
+    gtk_widget_set_halign(grid, GTK_ALIGN_CENTER);
+    gtk_widget_set_valign(grid, GTK_ALIGN_CENTER);
+    gtk_style_context_add_class(gtk_widget_get_style_context(grid), "dim-label");
+
+    GtkWidget* image = gtk_image_new_from_icon_name("edit-find-symbolic", GTK_ICON_SIZE_DIALOG);
+    gtk_image_set_pixel_size(GTK_IMAGE(image), 72);
+    gtk_style_context_add_class(gtk_widget_get_style_context(image), "dim-label");
+    gtk_grid_attach(GTK_GRID(grid), image, 0, 0, 1, 1);
+    gtk_widget_show(image);
+
+    GtkWidget* label = gtk_label_new(_("No Results Found"));
+    PangoAttrList* attributes = pango_attr_list_new();
+    pango_attr_list_insert(attributes, pango_attr_scale_new(1.44));
+    pango_attr_list_insert(attributes, pango_attr_weight_new(PANGO_WEIGHT_BOLD));
+    gtk_label_set_attributes(GTK_LABEL(label), attributes);
+    pango_attr_list_unref(attributes);
+    gtk_grid_attach(GTK_GRID(grid), label, 0, 1, 1, 1);
+    gtk_widget_show(label);
+
+    label = gtk_label_new(_("Try a different search"));
+    gtk_style_context_add_class(gtk_widget_get_style_context(label), "dim-label");
+    gtk_grid_attach(GTK_GRID(grid), label, 0, 2, 1, 1);
+    gtk_widget_show(label);
+
+    gtk_stack_add_named(GTK_STACK(chooser->priv->stack), grid, "empty");
+    gtk_widget_show(grid);
+}
+
+static void webkitEmojiChooserSearchChanged(WebKitEmojiChooser* chooser)
+{
+    for (auto& section : chooser->priv->sections) {
+        section.isEmpty = true;
+        gtk_flow_box_invalidate_filter(GTK_FLOW_BOX(section.box));
+    }
+
+    bool resultsFound = false;
+    for (auto& section : chooser->priv->sections) {
+        if (section.heading) {
+            gtk_widget_set_visible(section.heading, !section.isEmpty);
+            gtk_widget_set_visible(section.box, !section.isEmpty);
+        }
+        resultsFound = resultsFound || !section.isEmpty;
+    }
+
+    if (!resultsFound) {
+        webkitEmojiChooserEnsureEmptyResult(chooser);
+        gtk_stack_set_visible_child_name(GTK_STACK(chooser->priv->stack), "empty");
+    } else
+        gtk_stack_set_visible_child_name(GTK_STACK(chooser->priv->stack), "list");
+}
+
+static void webkitEmojiChooserSetupFilters(WebKitEmojiChooser* chooser)
+{
+    for (size_t i = 0; i < chooser->priv->sections.size(); ++i) {
+        gtk_flow_box_set_filter_func(GTK_FLOW_BOX(chooser->priv->sections[i].box), [](GtkFlowBoxChild* child, gpointer userData) -> gboolean {
+            auto* chooser = WEBKIT_EMOJI_CHOOSER(gtk_widget_get_ancestor(GTK_WIDGET(child), WEBKIT_TYPE_EMOJI_CHOOSER));
+            auto& section = chooser->priv->sections[GPOINTER_TO_UINT(userData)];
+            const char* text = gtk_entry_get_text(GTK_ENTRY(chooser->priv->searchEntry));
+            if (!text || !*text) {
+                section.isEmpty = false;
+                return TRUE;
+            }
+
+            auto* emojiData = static_cast<GVariant*>(g_object_get_data(G_OBJECT(child), "emoji-data"));
+            if (!emojiData) {
+                section.isEmpty = false;
+                return TRUE;
+            }
+
+            const char* name;
+            g_variant_get_child(emojiData, 1, "&s", &name);
+            if (g_str_match_string(text, name, TRUE)) {
+                section.isEmpty = false;
+                return TRUE;
+            }
+
+            return FALSE;
+        }, GUINT_TO_POINTER(i), nullptr);
+    }
+}
+
+static void webkitEmojiChooserInitializeEmojiMaxWidth(WebKitEmojiChooser* chooser)
+{
+    // Get a reasonable maximum width for an emoji. We do this to skip overly wide fallback
+    // rendering for certain emojis the font does not contain and therefore end up being
+    // rendered as multiple glyphs.
+    GRefPtr<PangoLayout> layout = adoptGRef(gtk_widget_create_pango_layout(GTK_WIDGET(chooser), "🙂"));
+    auto* attributes = pango_attr_list_new();
+    pango_attr_list_insert(attributes, pango_attr_scale_new(PANGO_SCALE_X_LARGE));
+    pango_layout_set_attributes(layout.get(), attributes);
+    pango_attr_list_unref(attributes);
+
+    PangoRectangle rect;
+    pango_layout_get_extents(layout.get(), &rect, nullptr);
+    chooser->priv->emojiMaxWidth = rect.width;
+}
+
+static void webkitEmojiChooserConstructed(GObject* object)
+{
+    WebKitEmojiChooser* chooser = WEBKIT_EMOJI_CHOOSER(object);
+    chooser->priv->settings = adoptGRef(g_settings_new("org.gtk.Settings.EmojiChooser"));
+
+    G_OBJECT_CLASS(webkit_emoji_chooser_parent_class)->constructed(object);
+
+    webkitEmojiChooserInitializeEmojiMaxWidth(chooser);
+
+    gtk_style_context_add_class(gtk_widget_get_style_context(GTK_WIDGET(object)), "emoji-picker");
+
+    GtkWidget* mainBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+    GtkWidget* searchEntry = gtk_search_entry_new();
+    chooser->priv->searchEntry = searchEntry;
+    g_signal_connect_swapped(searchEntry, "search-changed", G_CALLBACK(webkitEmojiChooserSearchChanged), chooser);
+    gtk_entry_set_input_hints(GTK_ENTRY(searchEntry), GTK_INPUT_HINT_NO_EMOJI);
+    gtk_box_pack_start(GTK_BOX(mainBox), searchEntry, TRUE, FALSE, 0);
+    gtk_widget_show(searchEntry);
+
+    GtkWidget* stack = gtk_stack_new();
+    chooser->priv->stack = stack;
+    GtkWidget* box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+    GtkWidget* swindow = gtk_scrolled_window_new(nullptr, nullptr);
+    chooser->priv->swindow = swindow;
+    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swindow), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+    gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(swindow), 250);
+    gtk_style_context_add_class(gtk_widget_get_style_context(swindow), "view");
+    gtk_box_pack_start(GTK_BOX(box), swindow, TRUE, TRUE, 0);
+    gtk_widget_show(swindow);
+
+    GtkWidget* emojiBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
+    g_object_set(emojiBox, "margin", 6, nullptr);
+    gtk_container_add(GTK_CONTAINER(swindow), emojiBox);
+    gtk_widget_show(emojiBox);
+
+    GtkWidget* buttonBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+    gtk_box_pack_start(GTK_BOX(box), buttonBox, TRUE, FALSE, 0);
+    gtk_widget_show(buttonBox);
+
+    GtkAdjustment* vAdjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(swindow));
+    g_signal_connect(vAdjustment, "value-changed", G_CALLBACK(verticalAdjustmentChanged), chooser);
+
+    webkitEmojiChooserSetupRecent(chooser, GTK_BOX(emojiBox), GTK_BOX(buttonBox), vAdjustment);
+
+    GRefPtr<GBytes> bytes = adoptGRef(g_resources_lookup_data("/org/gtk/libgtk/emoji/emoji.data", G_RESOURCE_LOOKUP_FLAGS_NONE, nullptr));
+    GRefPtr<GVariant> data = g_variant_new_from_bytes(G_VARIANT_TYPE("a(auss)"), bytes.get(), TRUE);
+    GVariantIter iter;
+    g_variant_iter_init(&iter, data.get());
+    GtkWidget* flowBox = nullptr;
+    while (GRefPtr<GVariant> item = adoptGRef(g_variant_iter_next_value(&iter))) {
+        const char* name;
+        g_variant_get_child(item.get(), 1, "&s", &name);
+
+        if (!g_strcmp0(name, "grinning face")) {
+            const char* title = _("Smileys & People");
+            flowBox = webkitEmojiChooserSetupSectionBox(chooser, GTK_BOX(emojiBox), title, vAdjustment, true);
+            webkitEmojiChooserSetupSectionButton(chooser, GTK_BOX(buttonBox), "emoji-people-symbolic", title);
+        } else if (!g_strcmp0(name, "selfie")) {
+            const char* title = _("Body & Clothing");
+            flowBox = webkitEmojiChooserSetupSectionBox(chooser, GTK_BOX(emojiBox), title, vAdjustment, true);
+            webkitEmojiChooserSetupSectionButton(chooser, GTK_BOX(buttonBox), "emoji-body-symbolic", title);
+        } else if (!g_strcmp0(name, "monkey")) {
+            const char* title = _("Animals & Nature");
+            flowBox = webkitEmojiChooserSetupSectionBox(chooser, GTK_BOX(emojiBox), title, vAdjustment);
+            webkitEmojiChooserSetupSectionButton(chooser, GTK_BOX(buttonBox), "emoji-nature-symbolic", title);
+        } else if (!g_strcmp0(name, "grapes")) {
+            const char* title = _("Food & Drink");
+            flowBox = webkitEmojiChooserSetupSectionBox(chooser, GTK_BOX(emojiBox), title, vAdjustment);
+            webkitEmojiChooserSetupSectionButton(chooser, GTK_BOX(buttonBox), "emoji-food-symbolic", title);
+        } else if (!g_strcmp0(name, "globe showing Europe-Africa")) {
+            const char* title = _("Travel & Places");
+            flowBox = webkitEmojiChooserSetupSectionBox(chooser, GTK_BOX(emojiBox), title, vAdjustment);
+            webkitEmojiChooserSetupSectionButton(chooser, GTK_BOX(buttonBox), "emoji-travel-symbolic", title);
+        } else if (!g_strcmp0(name, "jack-o-lantern")) {
+            const char* title = _("Activities");
+            flowBox = webkitEmojiChooserSetupSectionBox(chooser, GTK_BOX(emojiBox), title, vAdjustment);
+            webkitEmojiChooserSetupSectionButton(chooser, GTK_BOX(buttonBox), "emoji-activities-symbolic", title);
+        } else if (!g_strcmp0(name, "muted speaker")) {
+            const char* title = _("Objects");
+            flowBox = webkitEmojiChooserSetupSectionBox(chooser, GTK_BOX(emojiBox), title, vAdjustment);
+            webkitEmojiChooserSetupSectionButton(chooser, GTK_BOX(buttonBox), "emoji-objects-symbolic", title);
+        } else if (!g_strcmp0(name, "ATM sign")) {
+            const char* title = _("Symbols");
+            flowBox = webkitEmojiChooserSetupSectionBox(chooser, GTK_BOX(emojiBox), title, vAdjustment);
+            webkitEmojiChooserSetupSectionButton(chooser, GTK_BOX(buttonBox), "emoji-symbols-symbolic", title);
+        } else if (!g_strcmp0(name, "chequered flag")) {
+            const char* title = _("Flags");
+            flowBox = webkitEmojiChooserSetupSectionBox(chooser, GTK_BOX(emojiBox), title, vAdjustment);
+            webkitEmojiChooserSetupSectionButton(chooser, GTK_BOX(buttonBox), "emoji-flags-symbolic", title);
+        }
+        auto* child = webkitEmojiChooserAddEmoji(chooser, GTK_FLOW_BOX(flowBox), item.get());
+        if (child)
+            g_signal_connect(child, "popup-menu", G_CALLBACK(emojiPopupMenu), chooser);
+    }
+
+    gtk_widget_set_state_flags(chooser->priv->sections.first().button, GTK_STATE_FLAG_CHECKED, FALSE);
+
+    gtk_stack_add_named(GTK_STACK(stack), box, "list");
+    gtk_widget_show(box);
+
+    gtk_box_pack_start(GTK_BOX(mainBox), stack, TRUE, TRUE, 0);
+    gtk_widget_show(stack);
+
+    gtk_container_add(GTK_CONTAINER(object), mainBox);
+    gtk_widget_show(mainBox);
+
+    webkitEmojiChooserSetupFilters(chooser);
+}
+
+static void webkitEmojiChooserShow(GtkWidget* widget)
+{
+    GTK_WIDGET_CLASS(webkit_emoji_chooser_parent_class)->show(widget);
+
+    WebKitEmojiChooser* chooser = WEBKIT_EMOJI_CHOOSER(widget);
+    auto* adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(chooser->priv->swindow));
+    gtk_adjustment_set_value(adjustment, 0);
+
+    gtk_entry_set_text(GTK_ENTRY(chooser->priv->searchEntry), "");
+}
+
+static void webkit_emoji_chooser_class_init(WebKitEmojiChooserClass* klass)
+{
+    GObjectClass* objectClass = G_OBJECT_CLASS(klass);
+    objectClass->constructed = webkitEmojiChooserConstructed;
+
+    GtkWidgetClass* widgetClass = GTK_WIDGET_CLASS(klass);
+    widgetClass->show = webkitEmojiChooserShow;
+
+    signals[EMOJI_PICKED] = g_signal_new(
+        "emoji-picked",
+        G_OBJECT_CLASS_TYPE(objectClass),
+        G_SIGNAL_RUN_LAST,
+        0,
+        nullptr, nullptr,
+        nullptr,
+        G_TYPE_NONE, 1,
+        G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE);
+}
+
+GtkWidget* webkitEmojiChooserNew()
+{
+    WebKitEmojiChooser* authDialog = WEBKIT_EMOJI_CHOOSER(g_object_new(WEBKIT_TYPE_EMOJI_CHOOSER, nullptr));
+    return GTK_WIDGET(authDialog);
+}
+
+#endif // GTK_CHECK_VERSION(3, 24, 0)
diff --git a/Source/WebKit/UIProcess/API/gtk/WebKitEmojiChooser.h b/Source/WebKit/UIProcess/API/gtk/WebKitEmojiChooser.h
new file mode 100644 (file)
index 0000000..2b3d0e5
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 Igalia S.L.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#if GTK_CHECK_VERSION(3, 24, 0)
+
+G_BEGIN_DECLS
+
+#define WEBKIT_TYPE_EMOJI_CHOOSER            (webkit_emoji_chooser_get_type())
+#define WEBKIT_EMOJI_CHOOSER(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), WEBKIT_TYPE_EMOJI_CHOOSER, WebKitEmojiChooser))
+#define WEBKIT_IS_EMOJI_CHOOSER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), WEBKIT_TYPE_EMOJI_CHOOSER))
+#define WEBKIT_EMOJI_CHOOSER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass),  WEBKIT_TYPE_EMOJI_CHOOSER, WebKitEmojiChooserClass))
+#define WEBKIT_IS_EMOJI_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),  WEBKIT_TYPE_EMOJI_CHOOSER))
+#define WEBKIT_EMOJI_CHOOSER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj),  WEBKIT_TYPE_EMOJI_CHOOSER, WebKitEmojiChooserClass))
+
+typedef struct _WebKitEmojiChooser        WebKitEmojiChooser;
+typedef struct _WebKitEmojiChooserClass   WebKitEmojiChooserClass;
+typedef struct _WebKitEmojiChooserPrivate WebKitEmojiChooserPrivate;
+
+struct _WebKitEmojiChooser {
+    GtkPopover parent;
+
+    WebKitEmojiChooserPrivate* priv;
+};
+
+struct _WebKitEmojiChooserClass {
+    GtkPopoverClass parentClass;
+};
+
+GType webkit_emoji_chooser_get_type();
+GtkWidget* webkitEmojiChooserNew();
+
+G_END_DECLS
+
+#endif // GTK_CHECK_VERSION(3, 24, 0)
index 18b6097..e545dc7 100644 (file)
@@ -42,6 +42,7 @@
 #include "WebEventFactory.h"
 #include "WebInspectorProxy.h"
 #include "WebKit2Initialize.h"
+#include "WebKitEmojiChooser.h"
 #include "WebKitWebViewBaseAccessible.h"
 #include "WebKitWebViewBasePrivate.h"
 #include "WebPageGroup.h"
@@ -67,6 +68,7 @@
 #include <wtf/Compiler.h>
 #include <wtf/HashMap.h>
 #include <wtf/glib/GRefPtr.h>
+#include <wtf/glib/RunLoopSourcePriority.h>
 #include <wtf/glib/WTFGType.h>
 #include <wtf/text/CString.h>
 
@@ -147,7 +149,13 @@ 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()
@@ -158,6 +166,16 @@ struct _WebKitWebViewBasePrivate {
         activityStateFlagsToUpdate = { };
     }
 
+#if GTK_CHECK_VERSION(3, 24, 0)
+    void releaseEmojiChooserTimerFired()
+    {
+        if (emojiChooser) {
+            gtk_widget_destroy(emojiChooser);
+            emojiChooser = nullptr;
+        }
+    }
+#endif
+
     WebKitWebViewChildrenMap children;
     std::unique_ptr<PageClientImpl> pageClient;
     RefPtr<WebPageProxy> pageProxy;
@@ -207,6 +225,12 @@ struct _WebKitWebViewBasePrivate {
 #endif
     std::unique_ptr<ViewGestureController> viewGestureController;
     bool isBackForwardNavigationGestureEnabled { false };
+
+#if GTK_CHECK_VERSION(3, 24, 0)
+    GtkWidget* emojiChooser;
+    CompletionHandler<void(String)> emojiChooserCompletionHandler;
+    RunLoop::Timer<WebKitWebViewBasePrivate> releaseEmojiChooserTimer;
+#endif
 };
 
 WEBKIT_DEFINE_TYPE(WebKitWebViewBase, webkit_web_view_base, GTK_TYPE_CONTAINER)
@@ -525,10 +549,21 @@ void webkitWebViewBaseChildMoveResize(WebKitWebViewBase* webView, GtkWidget* chi
     gtk_widget_queue_resize_no_redraw(GTK_WIDGET(webView));
 }
 
+#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);
     webkitWebViewBaseSetToplevelOnScreenWindow(webView, nullptr);
+#if GTK_CHECK_VERSION(3, 24, 0)
+    webkitWebViewBaseCompleteEmojiChooserRequest(webView, emptyString());
+#endif
     webView->priv->pageProxy->close();
     webView->priv->acceleratedBackingStore = nullptr;
     webView->priv->sleepDisabler = nullptr;
@@ -1754,3 +1789,41 @@ void webkitWebViewBaseDidRestoreScrollPosition(WebKitWebViewBase* webkitWebViewB
     if (controller && controller->isSwipeGestureEnabled())
         webkitWebViewBase->priv->viewGestureController->didRestoreScrollPosition();
 }
+
+#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)
+{
+    webkitWebViewBaseCompleteEmojiChooserRequest(webkitWebViewBase, 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) {
+        priv->emojiChooser = webkitEmojiChooserNew();
+        g_signal_connect_swapped(priv->emojiChooser, "emoji-picked", G_CALLBACK(emojiChooserEmojiPicked), webkitWebViewBase);
+        g_signal_connect_swapped(priv->emojiChooser, "closed", G_CALLBACK(emojiChooserClosed), webkitWebViewBase);
+        gtk_popover_set_relative_to(GTK_POPOVER(priv->emojiChooser), GTK_WIDGET(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
+}
index 54fe01e..66393f7 100644 (file)
@@ -93,3 +93,5 @@ void webkitWebViewBaseDidFinishLoadForMainFrame(WebKitWebViewBase*);
 void webkitWebViewBaseDidFailLoadForMainFrame(WebKitWebViewBase*);
 void webkitWebViewBaseDidSameDocumentNavigationForMainFrame(WebKitWebViewBase*, WebKit::SameDocumentNavigationType);
 void webkitWebViewBaseDidRestoreScrollPosition(WebKitWebViewBase*);
+
+void webkitWebViewBaseShowEmojiChooser(WebKitWebViewBase*, const WebCore::IntRect&, CompletionHandler<void(String)>&&);
index df8a4f5..062a4c8 100644 (file)
@@ -1774,6 +1774,7 @@ private:
 #if PLATFORM(GTK)
     void getEditorCommandsForKeyEvent(const AtomicString&, Vector<String>&);
     void bindAccessibilityTree(const String&);
+    void showEmojiPicker(const WebCore::IntRect&, CompletionHandler<void(String)>&&);
 #endif
 
     // Popup Menu.
index 691b417..8a7f8c4 100644 (file)
@@ -563,4 +563,8 @@ messages -> WebPageProxy {
 #endif
 
     ConfigureLoggingChannel(String channelName, enum:uint8_t WTFLogChannelState state, enum:uint8_t WTFLogLevel level)
+
+#if PLATFORM(GTK)
+    ShowEmojiPicker(WebCore::IntRect caretRect) -> (String result) Async
+#endif
 }
index f179f16..4490ce6 100644 (file)
@@ -60,6 +60,14 @@ static void toggleOverwriteCallback(GtkWidget* widget, KeyBindingTranslator* tra
     translator->addPendingEditorCommand("OverWrite");
 }
 
+#if GTK_CHECK_VERSION(3, 24, 0)
+static void insertEmojiCallback(GtkWidget* widget, KeyBindingTranslator* translator)
+{
+    g_signal_stop_emission_by_name(widget, "insert-emoji");
+    translator->addPendingEditorCommand("GtkInsertEmoji");
+}
+#endif
+
 // GTK+ will still send these signals to the web view. So we can safely stop signal
 // emission without breaking accessibility.
 static void popupMenuCallback(GtkWidget* widget, KeyBindingTranslator*)
@@ -173,6 +181,9 @@ KeyBindingTranslator::KeyBindingTranslator()
     g_signal_connect(m_nativeWidget.get(), "toggle-overwrite", G_CALLBACK(toggleOverwriteCallback), this);
     g_signal_connect(m_nativeWidget.get(), "popup-menu", G_CALLBACK(popupMenuCallback), this);
     g_signal_connect(m_nativeWidget.get(), "show-help", G_CALLBACK(showHelpCallback), this);
+#if GTK_CHECK_VERSION(3, 24, 0)
+    g_signal_connect(m_nativeWidget.get(), "insert-emoji", G_CALLBACK(insertEmojiCallback), this);
+#endif
 }
 
 struct KeyCombinationEntry {
index f6186a5..520aac9 100644 (file)
@@ -163,4 +163,9 @@ bool WebPageProxy::makeGLContextCurrent()
     return webkitWebViewBaseMakeGLContextCurrent(WEBKIT_WEB_VIEW_BASE(viewWidget()));
 }
 
+void WebPageProxy::showEmojiPicker(const WebCore::IntRect& caretRect, CompletionHandler<void(String)>&& completionHandler)
+{
+    webkitWebViewBaseShowEmojiChooser(WEBKIT_WEB_VIEW_BASE(viewWidget()), caretRect, WTFMove(completionHandler));
+}
+
 } // namespace WebKit
index 5d8e7d6..6c29b4e 100644 (file)
@@ -55,6 +55,10 @@ private:
     void searchWithSpotlight() override;
 #endif
 
+#if PLATFORM(GTK)
+    void insertEmoji(WebCore::Frame&) override;
+#endif
+
 #if USE(ACCESSIBILITY_CONTEXT_MENUS)
     void showContextMenu() override;
 #endif
index 1964860..63673c6 100644 (file)
@@ -140,7 +140,8 @@ private:
 #endif
 
 #if PLATFORM(GTK)
-    bool executePendingEditorCommands(WebCore::Frame*, const Vector<WTF::String>&, bool);
+    bool executePendingEditorCommands(WebCore::Frame&, const Vector<WTF::String>&, bool);
+    bool handleGtkEditorCommand(WebCore::Frame&, const String& command, bool);
     void getEditorCommandsForKeyEvent(const WebCore::KeyboardEvent*, Vector<WTF::String>&);
     void updateGlobalSelection(WebCore::Frame*);
 #endif
index 9cd11d4..2fe27cf 100644 (file)
@@ -29,6 +29,7 @@
 
 #if ENABLE(CONTEXT_MENUS)
 
+#include "WebPage.h"
 #include <WebCore/NotImplemented.h>
 
 namespace WebKit {
@@ -55,5 +56,10 @@ void WebContextMenuClient::stopSpeaking()
     notImplemented();
 }
 
+void WebContextMenuClient::insertEmoji(Frame& frame)
+{
+    m_page->showEmojiPicker(frame);
+}
+
 } // namespace WebKit
 #endif // ENABLE(CONTEXT_MENUS)
index db99772..b275dea 100644 (file)
 #include <WebCore/Pasteboard.h>
 #include <WebCore/PlatformKeyboardEvent.h>
 #include <WebCore/markup.h>
+#include <wtf/Variant.h>
 #include <wtf/glib/GRefPtr.h>
 
 namespace WebKit {
 using namespace WebCore;
 
-bool WebEditorClient::executePendingEditorCommands(Frame* frame, const Vector<WTF::String>& pendingEditorCommands, bool allowTextInsertion)
+bool WebEditorClient::handleGtkEditorCommand(Frame& frame, const String& command, bool allowTextInsertion)
 {
-    Vector<Editor::Command> commands;
-    for (auto& commandString : pendingEditorCommands) {
-        Editor::Command command = frame->editor().command(commandString);
-        if (command.isTextInsertion() && !allowTextInsertion)
+    if (command == "GtkInsertEmoji"_s) {
+        if (!allowTextInsertion)
             return false;
+        m_page->showEmojiPicker(frame);
+        return true;
+    }
 
-        commands.append(WTFMove(command));
+    return false;
+}
+
+bool WebEditorClient::executePendingEditorCommands(Frame& frame, const Vector<WTF::String>& pendingEditorCommands, bool allowTextInsertion)
+{
+    Vector<Variant<Editor::Command, String>> commands;
+    for (auto& commandString : pendingEditorCommands) {
+        if (commandString.startsWith("Gtk"))
+            commands.append(commandString);
+        else {
+            Editor::Command command = frame.editor().command(commandString);
+            if (command.isTextInsertion() && !allowTextInsertion)
+                return false;
+
+            commands.append(WTFMove(command));
+        }
     }
 
-    for (auto& command : commands) {
-        if (!command.execute())
-            return false;
+    for (auto& commandVariant : commands) {
+        if (WTF::holds_alternative<String>(commandVariant)) {
+            if (!handleGtkEditorCommand(frame, WTF::get<String>(commandVariant), allowTextInsertion))
+                return false;
+        } else {
+            auto& command = WTF::get<Editor::Command>(commandVariant);
+            if (!command.execute())
+                return false;
+        }
     }
 
     return true;
@@ -73,14 +96,14 @@ void WebEditorClient::handleKeyboardEvent(KeyboardEvent& event)
         // the insertion until the keypress event. We want keydown to bubble up
         // through the DOM first.
         if (platformEvent->type() == PlatformEvent::RawKeyDown) {
-            if (executePendingEditorCommands(frame, pendingEditorCommands, false))
+            if (executePendingEditorCommands(*frame, pendingEditorCommands, false))
                 event.setDefaultHandled();
 
             return;
         }
 
         // Only allow text insertion commands if the current node is editable.
-        if (executePendingEditorCommands(frame, pendingEditorCommands, frame->editor().canEdit())) {
+        if (executePendingEditorCommands(*frame, pendingEditorCommands, frame->editor().canEdit())) {
             event.setDefaultHandled();
             return;
         }
index 732890c..c486fc4 100644 (file)
@@ -763,6 +763,7 @@ public:
     void cancelComposition();
 
     void collapseSelectionInFrame(uint64_t frameID);
+    void showEmojiPicker(WebCore::Frame&);
 #endif
 
 #if PLATFORM (GTK) && HAVE(GTK_GESTURES)
index 47e2532..8c3a3d1 100644 (file)
@@ -195,6 +195,15 @@ void WebPage::collapseSelectionInFrame(uint64_t frameID)
     frame->coreFrame()->selection().setBase(selection.extent(), selection.affinity());
 }
 
+void WebPage::showEmojiPicker(Frame& frame)
+{
+    CompletionHandler<void(String)> completionHandler = [frame = makeRef(frame)](String result) {
+        if (!result.isEmpty())
+            frame->editor().insertText(result, nullptr);
+    };
+    sendWithAsyncReply(Messages::WebPageProxy::ShowEmojiPicker(frame.selection().absoluteCaretBounds()), WTFMove(completionHandler));
+}
+
 void WebPage::effectiveAppearanceDidChange(bool useDarkAppearance, bool useInactiveAppearance)
 {
     if (auto* settings = gtk_settings_get_default())
index 6a7c88f..e5def46 100644 (file)
@@ -1,3 +1,14 @@
+2019-05-16  Carlos Garcia Campos  <cgarcia@igalia.com>
+
+        [GTK] Need WebKitContextMenuItemType to open emoji picker
+        https://bugs.webkit.org/show_bug.cgi?id=176760
+
+        Reviewed by Michael Catanzaro.
+
+        Update context menu test to check insert emoji action is included in default context menu for editable content.
+
+        * TestWebKitAPI/Tests/WebKitGtk/TestContextMenu.cpp:
+
 2019-05-16  Aakash Jain  <aakash_jain@apple.com>
 
         [ews-build] Download archives from S3
index d7fc734..502b5e6 100644 (file)
@@ -402,6 +402,7 @@ public:
             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_DELETE, Visible);
             iter = checkCurrentItemIsSeparatorAndGetNext(iter);
             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_SELECT_ALL, Visible | Enabled);
+            iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_INSERT_EMOJI, Visible | Enabled);
             iter = checkCurrentItemIsSeparatorAndGetNext(iter);
             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_UNICODE, Visible | Enabled);
             break;