Bug Summary

File:build/../Source/WebKit/UIProcess/gtk/WebPopupMenuProxyGtk.cpp
Warning:line 50, column 5
Call to virtual function during destruction will not dispatch to derived class

Annotated Source Code

1/*
2 * Copyright (C) 2011 Igalia S.L.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "WebPopupMenuProxyGtk.h"
28
29#include "NativeWebMouseEvent.h"
30#include "WebPopupItem.h"
31#include <WebCore/GtkUtilities.h>
32#include <WebCore/IntRect.h>
33#include <gtk/gtk.h>
34#include <wtf/glib/GUniquePtr.h>
35#include <wtf/text/CString.h>
36
37using namespace WebCore;
38
39namespace WebKit {
40
41WebPopupMenuProxyGtk::WebPopupMenuProxyGtk(GtkWidget* webView, WebPopupMenuProxy::Client& client)
42 : WebPopupMenuProxy(client)
43 , m_webView(webView)
44 , m_dismissMenuTimer(RunLoop::main(), this, &WebPopupMenuProxyGtk::dismissMenuTimerFired)
45{
46}
47
48WebPopupMenuProxyGtk::~WebPopupMenuProxyGtk()
49{
50 cancelTracking();
Call to virtual function during destruction will not dispatch to derived class
51}
52
53void WebPopupMenuProxyGtk::populatePopupMenu(const Vector<WebPopupItem>& items)
54{
55 int itemIndex = 0;
56 for (const auto& item : items) {
57 if (item.m_type == WebPopupItem::Separator) {
58 GtkWidget* menuItem = gtk_separator_menu_item_new();
59 gtk_menu_shell_append(GTK_MENU_SHELL(m_popup)((((GtkMenuShell*) g_type_check_instance_cast ((GTypeInstance
*) ((m_popup)), ((gtk_menu_shell_get_type ()))))))
, menuItem);
60 gtk_widget_show(menuItem);
61 } else {
62 GtkWidget* menuItem = gtk_menu_item_new_with_label(item.m_text.utf8().data());
63 gtk_widget_set_tooltip_text(menuItem, item.m_toolTip.utf8().data());
64 gtk_widget_set_sensitive(menuItem, item.m_isEnabled);
65 g_object_set_data(G_OBJECT(menuItem)((((GObject*) g_type_check_instance_cast ((GTypeInstance*) ((
menuItem)), (((GType) ((20) << (2))))))))
, "popup-menu-item-index", GINT_TO_POINTER(itemIndex)((gpointer) (glong) (itemIndex)));
66 g_signal_connect(menuItem, "activate", G_CALLBACK(menuItemActivated), this)g_signal_connect_data ((menuItem), ("activate"), (((GCallback
) (menuItemActivated))), (this), __null, (GConnectFlags) 0)
;
67 g_signal_connect(menuItem, "select", G_CALLBACK(selectItemCallback), this)g_signal_connect_data ((menuItem), ("select"), (((GCallback) (
selectItemCallback))), (this), __null, (GConnectFlags) 0)
;
68 gtk_menu_shell_append(GTK_MENU_SHELL(m_popup)((((GtkMenuShell*) g_type_check_instance_cast ((GTypeInstance
*) ((m_popup)), ((gtk_menu_shell_get_type ()))))))
, menuItem);
69 gtk_widget_show(menuItem);
70 }
71 itemIndex++;
72 }
73}
74
75void WebPopupMenuProxyGtk::showPopupMenu(const IntRect& rect, TextDirection, double /* pageScaleFactor */, const Vector<WebPopupItem>& items, const PlatformPopupMenuData&, int32_t selectedIndex)
76{
77 ASSERT(!m_popup)((void)0);
78 m_popup = gtk_menu_new();
79 g_signal_connect(m_popup, "key-press-event", G_CALLBACK(keyPressEventCallback), this)g_signal_connect_data ((m_popup), ("key-press-event"), (((GCallback
) (keyPressEventCallback))), (this), __null, (GConnectFlags) 0
)
;
80 g_signal_connect(m_popup, "unmap", G_CALLBACK(menuUnmappedCallback), this)g_signal_connect_data ((m_popup), ("unmap"), (((GCallback) (menuUnmappedCallback
))), (this), __null, (GConnectFlags) 0)
;
81
82 populatePopupMenu(items);
83 gtk_menu_set_active(GTK_MENU(m_popup)((((GtkMenu*) g_type_check_instance_cast ((GTypeInstance*) ((
m_popup)), ((gtk_menu_get_type ()))))))
, selectedIndex);
84
85 resetTypeAheadFindState();
86
87 IntPoint menuPosition = convertWidgetPointToScreenPoint(m_webView, rect.location());
88 menuPosition.move(0, rect.height());
89
90 // This approach follows the one in gtkcombobox.c.
91 GtkRequisition requisition;
92 gtk_widget_set_size_request(m_popup, -1, -1);
93 gtk_widget_get_preferred_size(m_popup, &requisition, nullptr);
94 gtk_widget_set_size_request(m_popup, std::max(rect.width(), requisition.width), -1);
95
96 if (int itemCount = items.size()) {
97 GUniquePtr<GList> children(gtk_container_get_children(GTK_CONTAINER(m_popup)((((GtkContainer*) g_type_check_instance_cast ((GTypeInstance
*) ((m_popup)), ((gtk_container_get_type ()))))))
));
98 int i;
99 GList* child;
100 for (i = 0, child = children.get(); i < itemCount; i++, child = g_list_next(child)((child) ? (((GList *)(child))->next) : __null)) {
101 if (i > selectedIndex)
102 break;
103
104 GtkWidget* item = GTK_WIDGET(child->data)((((GtkWidget*) g_type_check_instance_cast ((GTypeInstance*) (
(child->data)), ((gtk_widget_get_type ()))))))
;
105 GtkRequisition itemRequisition;
106 gtk_widget_get_preferred_size(item, &itemRequisition, nullptr);
107 menuPosition.setY(menuPosition.y() - itemRequisition.height);
108 }
109 } else {
110 // Center vertically the empty popup in the combo box area.
111 menuPosition.setY(menuPosition.y() - rect.height() / 2);
112 }
113
114 gtk_menu_attach_to_widget(GTK_MENU(m_popup)((((GtkMenu*) g_type_check_instance_cast ((GTypeInstance*) ((
m_popup)), ((gtk_menu_get_type ()))))))
, GTK_WIDGET(m_webView)((((GtkWidget*) g_type_check_instance_cast ((GTypeInstance*) (
(m_webView)), ((gtk_widget_get_type ()))))))
, nullptr);
115
116 const GdkEvent* event = m_client->currentlyProcessedMouseDownEvent() ? m_client->currentlyProcessedMouseDownEvent()->nativeEvent() : nullptr;
117 gtk_menu_popup_for_device(GTK_MENU(m_popup)((((GtkMenu*) g_type_check_instance_cast ((GTypeInstance*) ((
m_popup)), ((gtk_menu_get_type ()))))))
, event ? gdk_event_get_device(event) : nullptr, nullptr, nullptr,
118 [](GtkMenu*, gint* x, gint* y, gboolean* pushIn, gpointer userData) {
119 // We can pass a pointer to the menuPosition local variable because the nested main loop ensures this is called in the function context.
120 IntPoint* menuPosition = static_cast<IntPoint*>(userData);
121 *x = menuPosition->x();
122 *y = menuPosition->y();
123 *pushIn = menuPosition->y() < 0;
124 }, &menuPosition, nullptr, event && event->type == GDK_BUTTON_PRESS ? event->button.button : 1,
125 event ? gdk_event_get_time(event) : GDK_CURRENT_TIME0L);
126
127 // Now that the menu has a position, schedule a resize to make sure it's resized to fit vertically in the work area.
128 gtk_widget_queue_resize(m_popup);
129
130 // PopupMenu can fail to open when there is no mouse grab.
131 // Ensure WebCore does not go into some pesky state.
132 if (!gtk_widget_get_visible(m_popup)) {
133 m_client->failedToShowPopupMenu();
134 return;
135 }
136
137 // This ensures that the active item gets selected after popping up the menu, and
138 // as it says in "gtkcombobox.c" (line ~1606): it's ugly, but gets the job done.
139 GtkWidget* activeChild = gtk_menu_get_active(GTK_MENU(m_popup)((((GtkMenu*) g_type_check_instance_cast ((GTypeInstance*) ((
m_popup)), ((gtk_menu_get_type ()))))))
);
140 if (activeChild && gtk_widget_get_visible(activeChild))
141 gtk_menu_shell_select_item(GTK_MENU_SHELL(m_popup)((((GtkMenuShell*) g_type_check_instance_cast ((GTypeInstance
*) ((m_popup)), ((gtk_menu_shell_get_type ()))))))
, activeChild);
142}
143
144void WebPopupMenuProxyGtk::hidePopupMenu()
145{
146 if (!m_popup)
147 return;
148
149 gtk_menu_popdown(GTK_MENU(m_popup)((((GtkMenu*) g_type_check_instance_cast ((GTypeInstance*) ((
m_popup)), ((gtk_menu_get_type ()))))))
);
150 resetTypeAheadFindState();
151}
152
153void WebPopupMenuProxyGtk::cancelTracking()
154{
155 if (!m_popup)
156 return;
157
158 m_dismissMenuTimer.stop();
159 g_signal_handlers_disconnect_matched(m_popup, G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this);
160 hidePopupMenu();
161 gtk_widget_destroy(m_popup);
162 m_popup = nullptr;
163}
164
165bool WebPopupMenuProxyGtk::typeAheadFind(GdkEventKey* event)
166{
167 // If we were given a non-printable character just skip it.
168 gunichar unicodeCharacter = gdk_keyval_to_unicode(event->keyval);
169 if (!g_unichar_isprint(unicodeCharacter)) {
170 resetTypeAheadFindState();
171 return false;
172 }
173
174 glong charactersWritten;
175 GUniquePtr<gunichar2> utf16String(g_ucs4_to_utf16(&unicodeCharacter, 1, nullptr, &charactersWritten, nullptr));
176 if (!utf16String) {
177 resetTypeAheadFindState();
178 return false;
179 }
180
181 // If the character is the same as the last character, the user is probably trying to
182 // cycle through the menulist entries. This matches the WebCore behavior for collapsed menulists.
183 static const uint32_t searchTimeoutMs = 1000;
184 bool repeatingCharacter = unicodeCharacter != m_previousKeyEventCharacter;
185 if (event->time - m_previousKeyEventTimestamp > searchTimeoutMs)
186 m_currentSearchString = String(reinterpret_cast<UChar*>(utf16String.get()), charactersWritten);
187 else if (repeatingCharacter)
188 m_currentSearchString.append(String(reinterpret_cast<UChar*>(utf16String.get()), charactersWritten));
189
190 m_previousKeyEventTimestamp = event->time;
191 m_previousKeyEventCharacter = unicodeCharacter;
192
193 GUniquePtr<GList> children(gtk_container_get_children(GTK_CONTAINER(m_popup)((((GtkContainer*) g_type_check_instance_cast ((GTypeInstance
*) ((m_popup)), ((gtk_container_get_type ()))))))
));
194 if (!children)
195 return true;
196
197 // We case fold before searching, because strncmp does not handle non-ASCII characters.
198 GUniquePtr<gchar> searchStringWithCaseFolded(g_utf8_casefold(m_currentSearchString.utf8().data(), -1));
199 size_t prefixLength = strlen(searchStringWithCaseFolded.get());
200
201 // If a menu item has already been selected, start searching from the current
202 // item down the list. This will make multiple key presses of the same character
203 // advance the selection.
204 GList* currentChild = children.get();
205 if (m_currentlySelectedMenuItem) {
206 currentChild = g_list_find(children.get(), m_currentlySelectedMenuItem);
207 if (!currentChild) {
208 m_currentlySelectedMenuItem = nullptr;
209 currentChild = children.get();
210 }
211
212 // Repeating characters should iterate.
213 if (repeatingCharacter) {
214 if (GList* nextChild = g_list_next(currentChild)((currentChild) ? (((GList *)(currentChild))->next) : __null
)
)
215 currentChild = nextChild;
216 }
217 }
218
219 GList* firstChild = currentChild;
220 do {
221 currentChild = g_list_next(currentChild)((currentChild) ? (((GList *)(currentChild))->next) : __null
)
;
222 if (!currentChild)
223 currentChild = children.get();
224
225 GUniquePtr<gchar> itemText(g_utf8_casefold(gtk_menu_item_get_label(GTK_MENU_ITEM(currentChild->data)((((GtkMenuItem*) g_type_check_instance_cast ((GTypeInstance*
) ((currentChild->data)), ((gtk_menu_item_get_type ())))))
)
), -1));
226 if (!strncmp(searchStringWithCaseFolded.get(), itemText.get(), prefixLength)) {
227 gtk_menu_shell_select_item(GTK_MENU_SHELL(m_popup)((((GtkMenuShell*) g_type_check_instance_cast ((GTypeInstance
*) ((m_popup)), ((gtk_menu_shell_get_type ()))))))
, GTK_WIDGET(currentChild->data)((((GtkWidget*) g_type_check_instance_cast ((GTypeInstance*) (
(currentChild->data)), ((gtk_widget_get_type ()))))))
);
228 break;
229 }
230 } while (currentChild != firstChild);
231
232 return true;
233}
234
235void WebPopupMenuProxyGtk::resetTypeAheadFindState()
236{
237 m_currentlySelectedMenuItem = nullptr;
238 m_previousKeyEventCharacter = 0;
239 m_previousKeyEventTimestamp = 0;
240 m_currentSearchString = emptyString();
241}
242
243void WebPopupMenuProxyGtk::menuItemActivated(GtkMenuItem* menuItem, WebPopupMenuProxyGtk* popupMenu)
244{
245 popupMenu->m_dismissMenuTimer.stop();
246 if (popupMenu->m_client)
247 popupMenu->m_client->valueChangedForPopupMenu(popupMenu, GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menuItem), "popup-menu-item-index"))((gint) (glong) (g_object_get_data(((((GObject*) g_type_check_instance_cast
((GTypeInstance*) ((menuItem)), (((GType) ((20) << (2)
))))))), "popup-menu-item-index")))
);
248}
249
250void WebPopupMenuProxyGtk::dismissMenuTimerFired()
251{
252 if (m_client)
253 m_client->valueChangedForPopupMenu(this, -1);
254}
255
256void WebPopupMenuProxyGtk::menuUnmappedCallback(GtkWidget*, WebPopupMenuProxyGtk* popupMenu)
257{
258 if (!popupMenu->m_client)
259 return;
260
261 // When an item is activated, the menu is first hidden and then activate signal is emitted, so at this point we don't know
262 // if the menu has been hidden because an item has been selected or because the menu has been dismissed. Wait until the next
263 // main loop iteration to dismiss the menu, if an item is activated the timer will be cancelled.
264 popupMenu->m_dismissMenuTimer.startOneShot(0_s);
265}
266
267void WebPopupMenuProxyGtk::selectItemCallback(GtkWidget* item, WebPopupMenuProxyGtk* popupMenu)
268{
269 popupMenu->setCurrentlySelectedMenuItem(item);
270}
271
272gboolean WebPopupMenuProxyGtk::keyPressEventCallback(GtkWidget*, GdkEventKey* event, WebPopupMenuProxyGtk* popupMenu)
273{
274 return popupMenu->typeAheadFind(event);
275}
276
277} // namespace WebKit