eff59862b0b2f3f92fa53cba2a1186c5c6c80837
[WebKit-https.git] / Source / WebKit2 / UIProcess / API / gtk / tests / TestContextMenu.cpp
1 /*
2  * Copyright (C) 2012 Igalia S.L.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2,1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #include "config.h"
21 #include "WebViewTest.h"
22 #include <wtf/gobject/GRefPtr.h>
23
24 class ContextMenuTest: public WebViewTest {
25 public:
26     enum ContextMenuItemStateFlags {
27         Visible = 1 << 0,
28         Enabled = 1 << 1,
29         Checked = 1 << 2
30     };
31
32     void checkContextMenuEvent(GdkEvent* event)
33     {
34         g_assert(event);
35         g_assert_cmpint(event->type, ==, GDK_BUTTON_PRESS);
36         g_assert_cmpint(event->button.button, ==, 3);
37         g_assert_cmpint(event->button.x, ==, m_menuPositionX);
38         g_assert_cmpint(event->button.y, ==, m_menuPositionY);
39     }
40
41     static gboolean contextMenuCallback(WebKitWebView* webView, WebKitContextMenu* contextMenu, GdkEvent* event, WebKitHitTestResult* hitTestResult, ContextMenuTest* test)
42     {
43         g_assert(WEBKIT_IS_CONTEXT_MENU(contextMenu));
44         test->assertObjectIsDeletedWhenTestFinishes(G_OBJECT(contextMenu));
45         test->checkContextMenuEvent(event);
46         g_assert(WEBKIT_IS_HIT_TEST_RESULT(hitTestResult));
47         test->assertObjectIsDeletedWhenTestFinishes(G_OBJECT(hitTestResult));
48
49         return test->contextMenu(contextMenu, event, hitTestResult);
50     }
51
52     static void contextMenuDismissedCallback(WebKitWebView*, ContextMenuTest* test)
53     {
54         test->contextMenuDismissed();
55     }
56
57     ContextMenuTest()
58         : m_menuPositionX(0)
59         , m_menuPositionY(0)
60     {
61         g_signal_connect(m_webView, "context-menu", G_CALLBACK(contextMenuCallback), this);
62         g_signal_connect(m_webView, "context-menu-dismissed", G_CALLBACK(contextMenuDismissedCallback), this);
63     }
64
65     ~ContextMenuTest()
66     {
67         g_signal_handlers_disconnect_matched(m_webView, G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, this);
68     }
69
70     virtual bool contextMenu(WebKitContextMenu*, GdkEvent*, WebKitHitTestResult*) = 0;
71
72     virtual void contextMenuDismissed()
73     {
74         quitMainLoop();
75     }
76
77     GtkMenu* getPopupMenu()
78     {
79         GOwnPtr<GList> toplevels(gtk_window_list_toplevels());
80         for (GList* iter = toplevels.get(); iter; iter = g_list_next(iter)) {
81             if (!GTK_IS_WINDOW(iter->data))
82                 continue;
83
84             GtkWidget* child = gtk_bin_get_child(GTK_BIN(iter->data));
85             if (!GTK_IS_MENU(child))
86                 continue;
87
88             if (gtk_menu_get_attach_widget(GTK_MENU(child)) == GTK_WIDGET(m_webView))
89                 return GTK_MENU(child);
90         }
91         g_assert_not_reached();
92         return 0;
93     }
94
95     bool shouldShowInputMethodsMenu()
96     {
97         GtkSettings* settings = gtk_widget_get_settings(GTK_WIDGET(m_webView));
98         if (!settings)
99             return true;
100
101         gboolean showInputMethodMenu;
102         g_object_get(settings, "gtk-show-input-method-menu", &showInputMethodMenu, NULL);
103         return showInputMethodMenu;
104     }
105
106     void checkActionState(GtkAction* action, unsigned state)
107     {
108         if (state & Visible)
109             g_assert(gtk_action_get_visible(action));
110         else
111             g_assert(!gtk_action_get_visible(action));
112
113         if (state & Enabled)
114             g_assert(gtk_action_get_sensitive(action));
115         else
116             g_assert(!gtk_action_get_sensitive(action));
117
118         if (GTK_IS_TOGGLE_ACTION(action)) {
119             if (state & Checked)
120                 g_assert(gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action)));
121             else
122                 g_assert(!gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action)));
123         }
124     }
125
126     GList* checkCurrentItemIsStockActionAndGetNext(GList* items, WebKitContextMenuAction stockAction, unsigned state)
127     {
128         g_assert(items);
129         g_assert(WEBKIT_IS_CONTEXT_MENU_ITEM(items->data));
130
131         WebKitContextMenuItem* item = WEBKIT_CONTEXT_MENU_ITEM(items->data);
132         assertObjectIsDeletedWhenTestFinishes(G_OBJECT(item));
133
134         GtkAction* action = webkit_context_menu_item_get_action(item);
135         g_assert(GTK_IS_ACTION(action));
136
137         g_assert_cmpint(webkit_context_menu_item_get_stock_action(item), ==, stockAction);
138
139         checkActionState(action, state);
140
141         return g_list_next(items);
142     }
143
144     GList* checkCurrentItemIsCustomActionAndGetNext(GList* items, const char* label, unsigned state)
145     {
146         g_assert(items);
147         g_assert(WEBKIT_IS_CONTEXT_MENU_ITEM(items->data));
148
149         WebKitContextMenuItem* item = WEBKIT_CONTEXT_MENU_ITEM(items->data);
150         assertObjectIsDeletedWhenTestFinishes(G_OBJECT(item));
151
152         GtkAction* action = webkit_context_menu_item_get_action(item);
153         g_assert(GTK_IS_ACTION(action));
154
155         g_assert_cmpint(webkit_context_menu_item_get_stock_action(item), ==, WEBKIT_CONTEXT_MENU_ACTION_CUSTOM);
156         g_assert_cmpstr(gtk_action_get_label(action), ==, label);
157
158         checkActionState(action, state);
159
160         return g_list_next(items);
161     }
162
163     GList* checkCurrentItemIsSubMenuAndGetNext(GList* items, const char* label, unsigned state, GList** subMenuIter)
164     {
165         g_assert(items);
166         g_assert(WEBKIT_IS_CONTEXT_MENU_ITEM(items->data));
167
168         WebKitContextMenuItem* item = WEBKIT_CONTEXT_MENU_ITEM(items->data);
169         assertObjectIsDeletedWhenTestFinishes(G_OBJECT(item));
170
171         GtkAction* action = webkit_context_menu_item_get_action(item);
172         g_assert(GTK_IS_ACTION(action));
173
174         g_assert_cmpstr(gtk_action_get_label(action), ==, label);
175         checkActionState(action, state);
176
177         WebKitContextMenu* subMenu = webkit_context_menu_item_get_submenu(item);
178         g_assert(WEBKIT_IS_CONTEXT_MENU(subMenu));
179         if (subMenuIter)
180             *subMenuIter = webkit_context_menu_get_items(subMenu);
181
182         return g_list_next(items);
183     }
184
185     GList* checkCurrentItemIsSeparatorAndGetNext(GList* items)
186     {
187         g_assert(items);
188         g_assert(WEBKIT_IS_CONTEXT_MENU_ITEM(items->data));
189
190         WebKitContextMenuItem* item = WEBKIT_CONTEXT_MENU_ITEM(items->data);
191         g_assert(webkit_context_menu_item_is_separator(item));
192
193         return g_list_next(items);
194     }
195
196     static gboolean doRightClickIdleCallback(ContextMenuTest* test)
197     {
198         test->clickMouseButton(test->m_menuPositionX, test->m_menuPositionY, 3);
199         return FALSE;
200     }
201
202     void showContextMenuAtPositionAndWaitUntilFinished(int x, int y)
203     {
204         m_menuPositionX = x;
205         m_menuPositionY = y;
206         g_idle_add(reinterpret_cast<GSourceFunc>(doRightClickIdleCallback), this);
207         g_main_loop_run(m_mainLoop);
208     }
209
210     void showContextMenuAndWaitUntilFinished()
211     {
212         showContextMenuAtPositionAndWaitUntilFinished(0, 0);
213     }
214
215     static gboolean simulateEscKeyIdleCallback(ContextMenuTest* test)
216     {
217         test->keyStroke(GDK_KEY_Escape);
218         return FALSE;
219     }
220
221     void dismissContextMenuAndWaitUntilFinished()
222     {
223         g_idle_add(reinterpret_cast<GSourceFunc>(simulateEscKeyIdleCallback), this);
224         g_main_loop_run(m_mainLoop);
225     }
226
227     double m_menuPositionX;
228     double m_menuPositionY;
229 };
230
231 class ContextMenuDefaultTest: public ContextMenuTest {
232 public:
233     MAKE_GLIB_TEST_FIXTURE(ContextMenuDefaultTest);
234
235     enum DefaultMenuType {
236         Navigation,
237         Link,
238         Image,
239         LinkImage,
240         Video,
241         Editable
242     };
243
244     ContextMenuDefaultTest()
245         : m_expectedMenuType(Navigation)
246     {
247     }
248
249     bool contextMenu(WebKitContextMenu* contextMenu, GdkEvent* event, WebKitHitTestResult* hitTestResult)
250     {
251         GList* iter = webkit_context_menu_get_items(contextMenu);
252
253         switch (m_expectedMenuType) {
254         case Navigation:
255             g_assert(!webkit_hit_test_result_context_is_link(hitTestResult));
256             g_assert(!webkit_hit_test_result_context_is_image(hitTestResult));
257             g_assert(!webkit_hit_test_result_context_is_media(hitTestResult));
258             g_assert(!webkit_hit_test_result_context_is_editable(hitTestResult));
259             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_GO_BACK, Visible);
260             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_GO_FORWARD, Visible);
261             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_STOP, Visible);
262             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_RELOAD, Visible | Enabled);
263             break;
264         case Link:
265             g_assert(webkit_hit_test_result_context_is_link(hitTestResult));
266             g_assert(!webkit_hit_test_result_context_is_image(hitTestResult));
267             g_assert(!webkit_hit_test_result_context_is_media(hitTestResult));
268             g_assert(!webkit_hit_test_result_context_is_editable(hitTestResult));
269             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_OPEN_LINK, Visible | Enabled);
270             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_OPEN_LINK_IN_NEW_WINDOW, Visible | Enabled);
271             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_DOWNLOAD_LINK_TO_DISK, Visible | Enabled);
272             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_COPY_LINK_TO_CLIPBOARD, Visible | Enabled);
273             break;
274         case Image:
275             g_assert(!webkit_hit_test_result_context_is_link(hitTestResult));
276             g_assert(webkit_hit_test_result_context_is_image(hitTestResult));
277             g_assert(!webkit_hit_test_result_context_is_media(hitTestResult));
278             g_assert(!webkit_hit_test_result_context_is_editable(hitTestResult));
279             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_OPEN_IMAGE_IN_NEW_WINDOW, Visible | Enabled);
280             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_DOWNLOAD_IMAGE_TO_DISK, Visible | Enabled);
281             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_COPY_IMAGE_TO_CLIPBOARD, Visible | Enabled);
282             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_COPY_IMAGE_URL_TO_CLIPBOARD, Visible | Enabled);
283             break;
284         case LinkImage:
285             g_assert(webkit_hit_test_result_context_is_link(hitTestResult));
286             g_assert(webkit_hit_test_result_context_is_image(hitTestResult));
287             g_assert(!webkit_hit_test_result_context_is_media(hitTestResult));
288             g_assert(!webkit_hit_test_result_context_is_editable(hitTestResult));
289             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_OPEN_LINK, Visible | Enabled);
290             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_OPEN_LINK_IN_NEW_WINDOW, Visible | Enabled);
291             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_DOWNLOAD_LINK_TO_DISK, Visible | Enabled);
292             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_COPY_LINK_TO_CLIPBOARD, Visible | Enabled);
293             iter = checkCurrentItemIsSeparatorAndGetNext(iter);
294             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_OPEN_IMAGE_IN_NEW_WINDOW, Visible | Enabled);
295             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_DOWNLOAD_IMAGE_TO_DISK, Visible | Enabled);
296             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_COPY_IMAGE_TO_CLIPBOARD, Visible | Enabled);
297             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_COPY_IMAGE_URL_TO_CLIPBOARD, Visible | Enabled);
298             break;
299         case Video:
300             g_assert(!webkit_hit_test_result_context_is_link(hitTestResult));
301             g_assert(!webkit_hit_test_result_context_is_image(hitTestResult));
302             g_assert(webkit_hit_test_result_context_is_media(hitTestResult));
303             g_assert(!webkit_hit_test_result_context_is_editable(hitTestResult));
304             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_MEDIA_PLAY, Visible | Enabled);
305             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_MEDIA_MUTE, Visible);
306             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_TOGGLE_MEDIA_CONTROLS, Visible | Enabled | Checked);
307             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_TOGGLE_MEDIA_LOOP, Visible | Enabled);
308             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_ENTER_VIDEO_FULLSCREEN, Visible);
309             iter = checkCurrentItemIsSeparatorAndGetNext(iter);
310             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_COPY_VIDEO_LINK_TO_CLIPBOARD, Visible | Enabled);
311             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_OPEN_VIDEO_IN_NEW_WINDOW, Visible | Enabled);
312             break;
313         case Editable:
314             g_assert(!webkit_hit_test_result_context_is_link(hitTestResult));
315             g_assert(!webkit_hit_test_result_context_is_image(hitTestResult));
316             g_assert(!webkit_hit_test_result_context_is_media(hitTestResult));
317             g_assert(webkit_hit_test_result_context_is_editable(hitTestResult));
318             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_CUT, Visible);
319             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_COPY, Visible);
320             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_PASTE, Visible | Enabled);
321             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_DELETE, Visible);
322             iter = checkCurrentItemIsSeparatorAndGetNext(iter);
323             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_SELECT_ALL, Visible | Enabled);
324             if (shouldShowInputMethodsMenu()) {
325                 iter = checkCurrentItemIsSeparatorAndGetNext(iter);
326                 iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_INPUT_METHODS, Visible | Enabled);
327                 iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_UNICODE, Visible | Enabled);
328             }
329             break;
330         default:
331             g_assert_not_reached();
332         }
333
334         if (webkit_settings_get_enable_developer_extras(webkit_web_view_get_settings(m_webView))) {
335             iter = checkCurrentItemIsSeparatorAndGetNext(iter);
336             iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_INSPECT_ELEMENT, Visible | Enabled);
337         }
338         g_assert(!iter);
339
340         quitMainLoop();
341
342         return true;
343     }
344
345     DefaultMenuType m_expectedMenuType;
346 };
347
348 static void testContextMenuDefaultMenu(ContextMenuDefaultTest* test, gconstpointer)
349 {
350     test->showInWindowAndWaitUntilMapped();
351
352     const char* linksHTML =
353         "<html><body>"
354         " <a style='position:absolute; left:1; top:1' href='http://www.webkitgtk.org' title='WebKitGTK+ Title'>WebKitGTK+ Website</a>"
355         " <img style='position:absolute; left:1; top:10' src='0xdeadbeef' width=5 height=5></img>"
356         " <a style='position:absolute; left:1; top:20' href='http://www.webkitgtk.org/logo' title='WebKitGTK+ Logo'><img src='0xdeadbeef' width=5 height=5></img></a>"
357         " <video style='position:absolute; left:1; top:30' width=10 height=10 controls='controls'><source src='movie.ogg' type='video/ogg' /></video>"
358         " <input style='position:absolute; left:1; top:50' size='10'></input>"
359         "</body></html>";
360     test->loadHtml(linksHTML, "file:///");
361     test->waitUntilLoadFinished();
362
363     // Context menu for document.
364     test->m_expectedMenuType = ContextMenuDefaultTest::Navigation;
365     test->showContextMenuAtPositionAndWaitUntilFinished(0, 0);
366
367     // Context menu for link.
368     test->m_expectedMenuType = ContextMenuDefaultTest::Link;
369     test->showContextMenuAtPositionAndWaitUntilFinished(1, 1);
370
371     // Context menu for image.
372     test->m_expectedMenuType = ContextMenuDefaultTest::Image;
373     test->showContextMenuAtPositionAndWaitUntilFinished(1, 10);
374
375     // Enable developer extras now, so that inspector element
376     // will be shown in the default context menu.
377     webkit_settings_set_enable_developer_extras(webkit_web_view_get_settings(test->m_webView), TRUE);
378
379     // Context menu for image link.
380     test->m_expectedMenuType = ContextMenuDefaultTest::LinkImage;
381     test->showContextMenuAtPositionAndWaitUntilFinished(1, 20);
382
383     // Context menu for image video.
384     test->m_expectedMenuType = ContextMenuDefaultTest::Video;
385     test->showContextMenuAtPositionAndWaitUntilFinished(1, 30);
386
387     // Context menu for editable.
388     test->m_expectedMenuType = ContextMenuDefaultTest::Editable;
389     test->showContextMenuAtPositionAndWaitUntilFinished(5, 55);
390 }
391
392 class ContextMenuCustomTest: public ContextMenuTest {
393 public:
394     MAKE_GLIB_TEST_FIXTURE(ContextMenuCustomTest);
395
396     ContextMenuCustomTest()
397         : m_itemToActivateLabel(0)
398         , m_activated(false)
399         , m_toggled(false)
400     {
401     }
402
403     bool contextMenu(WebKitContextMenu* contextMenu, GdkEvent*, WebKitHitTestResult* hitTestResult)
404     {
405         // Append our custom item to the default menu.
406         webkit_context_menu_append(contextMenu, webkit_context_menu_item_new(m_action.get()));
407         quitMainLoop();
408
409         return false;
410     }
411
412     GtkMenuItem* getMenuItem(GtkMenu* menu, const gchar* itemLabel)
413     {
414         GOwnPtr<GList> items(gtk_container_get_children(GTK_CONTAINER(menu)));
415         for (GList* iter = items.get(); iter; iter = g_list_next(iter)) {
416             GtkMenuItem* child = GTK_MENU_ITEM(iter->data);
417             if (g_str_equal(itemLabel, gtk_menu_item_get_label(child)))
418                 return child;
419         }
420         g_assert_not_reached();
421         return 0;
422     }
423
424     void activateMenuItem()
425     {
426         g_assert(m_itemToActivateLabel);
427         GtkMenu* menu = getPopupMenu();
428         GtkMenuItem* item = getMenuItem(menu, m_itemToActivateLabel);
429         gtk_menu_shell_activate_item(GTK_MENU_SHELL(menu), GTK_WIDGET(item), TRUE);
430         m_itemToActivateLabel = 0;
431     }
432
433     static gboolean activateMenuItemIdleCallback(gpointer userData)
434     {
435         ContextMenuCustomTest* test = static_cast<ContextMenuCustomTest*>(userData);
436         test->activateMenuItem();
437         return FALSE;
438     }
439
440     void activateCustomMenuItemAndWaitUntilActivated(const char* actionLabel)
441     {
442         m_activated = m_toggled = false;
443         m_itemToActivateLabel = actionLabel;
444         g_idle_add(activateMenuItemIdleCallback, this);
445         g_main_loop_run(m_mainLoop);
446     }
447
448     void toggleCustomMenuItemAndWaitUntilToggled(const char* actionLabel)
449     {
450         activateCustomMenuItemAndWaitUntilActivated(actionLabel);
451     }
452
453     static void actionActivatedCallback(GtkAction*, ContextMenuCustomTest* test)
454     {
455         test->m_activated = true;
456     }
457
458     static void actionToggledCallback(GtkAction*, ContextMenuCustomTest* test)
459     {
460         test->m_toggled = true;
461     }
462
463     void setAction(GtkAction* action)
464     {
465         m_action = action;
466         if (GTK_IS_TOGGLE_ACTION(action))
467             g_signal_connect(action, "toggled", G_CALLBACK(actionToggledCallback), this);
468         else
469             g_signal_connect(action, "activate", G_CALLBACK(actionActivatedCallback), this);
470     }
471
472     GRefPtr<GtkAction> m_action;
473     const char* m_itemToActivateLabel;
474     bool m_activated;
475     bool m_toggled;
476 };
477
478 static void testContextMenuPopulateMenu(ContextMenuCustomTest* test, gconstpointer)
479 {
480     test->showInWindowAndWaitUntilMapped();
481
482     test->loadHtml("<html><body>WebKitGTK+ Context menu tests</body></html>", "file:///");
483     test->waitUntilLoadFinished();
484
485     // Create a custom menu item.
486     GRefPtr<GtkAction> action = adoptGRef(gtk_action_new("WebKitGTK+CustomAction", "Custom _Action", 0, 0));
487     test->setAction(action.get());
488     test->showContextMenuAndWaitUntilFinished();
489     test->activateCustomMenuItemAndWaitUntilActivated(gtk_action_get_label(action.get()));
490     g_assert(test->m_activated);
491     g_assert(!test->m_toggled);
492
493     // Create a custom toggle menu item.
494     GRefPtr<GtkAction> toggleAction = adoptGRef(GTK_ACTION(gtk_toggle_action_new("WebKitGTK+CustomToggleAction", "Custom _Toggle Action", 0, 0)));
495     test->setAction(toggleAction.get());
496     test->showContextMenuAndWaitUntilFinished();
497     test->toggleCustomMenuItemAndWaitUntilToggled(gtk_action_get_label(toggleAction.get()));
498     g_assert(!test->m_activated);
499     g_assert(test->m_toggled);
500 }
501
502 class ContextMenuCustomFullTest: public ContextMenuTest {
503 public:
504     MAKE_GLIB_TEST_FIXTURE(ContextMenuCustomFullTest);
505
506     bool contextMenu(WebKitContextMenu* contextMenu, GdkEvent*, WebKitHitTestResult*)
507     {
508         // Clear proposed menu and build our own.
509         webkit_context_menu_remove_all(contextMenu);
510         g_assert_cmpint(webkit_context_menu_get_n_items(contextMenu), ==, 0);
511
512         // Add actions from stock.
513         webkit_context_menu_prepend(contextMenu, webkit_context_menu_item_new_from_stock_action(WEBKIT_CONTEXT_MENU_ACTION_GO_BACK));
514         webkit_context_menu_append(contextMenu, webkit_context_menu_item_new_from_stock_action(WEBKIT_CONTEXT_MENU_ACTION_GO_FORWARD));
515         webkit_context_menu_insert(contextMenu, webkit_context_menu_item_new_separator(), 2);
516
517         // Add custom actions.
518         GRefPtr<GtkAction> action = adoptGRef(gtk_action_new("WebKitGTK+CustomAction", "Custom _Action", 0, 0));
519         gtk_action_set_sensitive(action.get(), FALSE);
520         webkit_context_menu_insert(contextMenu, webkit_context_menu_item_new(action.get()), -1);
521         GRefPtr<GtkAction> toggleAction = adoptGRef(GTK_ACTION(gtk_toggle_action_new("WebKitGTK+CustomToggleAction", "Custom _Toggle Action", 0, 0)));
522         gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(toggleAction.get()), TRUE);
523         webkit_context_menu_append(contextMenu, webkit_context_menu_item_new(toggleAction.get()));
524         webkit_context_menu_append(contextMenu, webkit_context_menu_item_new_separator());
525
526         // Add a submenu.
527         GRefPtr<WebKitContextMenu> subMenu = adoptGRef(webkit_context_menu_new());
528         assertObjectIsDeletedWhenTestFinishes(G_OBJECT(subMenu.get()));
529         webkit_context_menu_insert(subMenu.get(), webkit_context_menu_item_new_from_stock_action_with_label(WEBKIT_CONTEXT_MENU_ACTION_STOP, "Stop Load"), 0);
530         webkit_context_menu_insert(subMenu.get(), webkit_context_menu_item_new_from_stock_action(WEBKIT_CONTEXT_MENU_ACTION_RELOAD), -1);
531         webkit_context_menu_append(contextMenu, webkit_context_menu_item_new_with_submenu("Load options", subMenu.get()));
532         webkit_context_menu_append(contextMenu, webkit_context_menu_item_new_separator());
533
534         // Move Load submenu before custom actions.
535         webkit_context_menu_move_item(contextMenu, webkit_context_menu_last(contextMenu), 3);
536         webkit_context_menu_move_item(contextMenu, webkit_context_menu_last(contextMenu), 3);
537
538         // If last item is a separator, remove it.
539         if (webkit_context_menu_item_is_separator(webkit_context_menu_last(contextMenu)))
540             webkit_context_menu_remove(contextMenu, webkit_context_menu_last(contextMenu));
541
542         // Check the menu.
543         GList* iter = webkit_context_menu_get_items(contextMenu);
544
545         iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_GO_BACK, Visible | Enabled);
546         iter = checkCurrentItemIsStockActionAndGetNext(iter, WEBKIT_CONTEXT_MENU_ACTION_GO_FORWARD, Visible | Enabled);
547         iter = checkCurrentItemIsSeparatorAndGetNext(iter);
548
549         GList* subMenuIter = 0;
550         iter = checkCurrentItemIsSubMenuAndGetNext(iter, "Load options", Visible | Enabled, &subMenuIter);
551         subMenuIter = checkCurrentItemIsStockActionAndGetNext(subMenuIter, WEBKIT_CONTEXT_MENU_ACTION_STOP, Visible | Enabled);
552         subMenuIter = checkCurrentItemIsStockActionAndGetNext(subMenuIter, WEBKIT_CONTEXT_MENU_ACTION_RELOAD, Visible | Enabled);
553         iter = checkCurrentItemIsSeparatorAndGetNext(iter);
554
555         iter = checkCurrentItemIsCustomActionAndGetNext(iter, "Custom _Action", Visible);
556         iter = checkCurrentItemIsCustomActionAndGetNext(iter, "Custom _Toggle Action", Visible | Enabled | Checked);
557         g_assert(!iter);
558
559         quitMainLoop();
560
561         return true;
562     }
563 };
564
565 static void testContextMenuCustomMenu(ContextMenuCustomFullTest* test, gconstpointer)
566 {
567     test->showInWindowAndWaitUntilMapped();
568
569     test->loadHtml("<html><body>WebKitGTK+ Context menu tests</body></html>", "file:///");
570     test->waitUntilLoadFinished();
571
572     test->showContextMenuAndWaitUntilFinished();
573 }
574
575 class ContextMenuDisabledTest: public ContextMenuTest {
576 public:
577     MAKE_GLIB_TEST_FIXTURE(ContextMenuDisabledTest);
578
579     enum DisableMode {
580         IgnoreClicks,
581         IgnoreDefaultMenu
582     };
583
584     static gboolean buttonPressEventCallback(GtkWidget*, GdkEvent* event, ContextMenuDisabledTest* test)
585     {
586         if (event->button.button != 3)
587             return FALSE;
588         return test->rightButtonPressed();
589     }
590
591     ContextMenuDisabledTest()
592         : m_disableMode(IgnoreClicks)
593     {
594         g_signal_connect(m_webView, "button-press-event", G_CALLBACK(buttonPressEventCallback), this);
595     }
596
597     bool contextMenu(WebKitContextMenu* contextMenu, GdkEvent*, WebKitHitTestResult*)
598     {
599         if (m_disableMode == IgnoreClicks)
600             g_assert_not_reached();
601         else
602             quitMainLoop();
603
604         return true;
605     }
606
607     bool rightButtonPressed()
608     {
609         if (m_disableMode == IgnoreClicks) {
610             quitMainLoopAfterProcessingPendingEvents();
611             return true;
612         }
613         return false;
614     }
615
616     DisableMode m_disableMode;
617 };
618
619 static void testContextMenuDisableMenu(ContextMenuDisabledTest* test, gconstpointer)
620 {
621     test->showInWindowAndWaitUntilMapped();
622
623     test->loadHtml("<html><body>WebKitGTK+ Context menu tests</body></html>", "file:///");
624     test->waitUntilLoadFinished();
625
626     test->m_disableMode = ContextMenuDisabledTest::IgnoreDefaultMenu;
627     test->showContextMenuAndWaitUntilFinished();
628
629     test->m_disableMode = ContextMenuDisabledTest::IgnoreClicks;
630     test->showContextMenuAndWaitUntilFinished();
631 }
632
633 class ContextMenuSubmenuTest: public ContextMenuTest {
634 public:
635     MAKE_GLIB_TEST_FIXTURE(ContextMenuSubmenuTest);
636
637     bool contextMenu(WebKitContextMenu* contextMenu, GdkEvent*, WebKitHitTestResult*)
638     {
639         size_t menuSize = webkit_context_menu_get_n_items(contextMenu);
640         GRefPtr<WebKitContextMenu> subMenu = adoptGRef(webkit_context_menu_new());
641         webkit_context_menu_append(contextMenu, webkit_context_menu_item_new_with_submenu("SubMenuItem", subMenu.get()));
642         g_assert_cmpuint(webkit_context_menu_get_n_items(contextMenu), ==, menuSize + 1);
643
644         GRefPtr<WebKitContextMenu> subMenu2 = adoptGRef(webkit_context_menu_new());
645         GRefPtr<WebKitContextMenuItem> item = webkit_context_menu_item_new_from_stock_action(WEBKIT_CONTEXT_MENU_ACTION_OPEN_LINK);
646
647         // Add submenu to newly created item.
648         g_assert(!webkit_context_menu_item_get_submenu(item.get()));
649         webkit_context_menu_item_set_submenu(item.get(), subMenu2.get());
650         g_assert(webkit_context_menu_item_get_submenu(item.get()) == subMenu2.get());
651
652         // Replace the submenu.
653         webkit_context_menu_item_set_submenu(item.get(), 0);
654         g_assert(!webkit_context_menu_item_get_submenu(item.get()));
655
656         // Try to add a submenu already added to another item.
657         removeLogFatalFlag(G_LOG_LEVEL_WARNING);
658         webkit_context_menu_item_set_submenu(item.get(), subMenu.get());
659         addLogFatalFlag(G_LOG_LEVEL_WARNING);
660         g_assert(!webkit_context_menu_item_get_submenu(item.get()));
661
662         // A removed submenu shouldn't have a parent.
663         webkit_context_menu_item_set_submenu(item.get(), subMenu2.get());
664         g_assert(webkit_context_menu_item_get_submenu(item.get()) == subMenu2.get());
665
666         quitMainLoop();
667
668         return true;
669     }
670 };
671
672 static void testContextMenuSubMenu(ContextMenuSubmenuTest* test, gconstpointer)
673 {
674     test->showInWindowAndWaitUntilMapped();
675
676     test->loadHtml("<html><body>WebKitGTK+ Context menu tests</body></html>", "file:///");
677     test->waitUntilLoadFinished();
678
679     test->showContextMenuAndWaitUntilFinished();
680 }
681
682 class ContextMenuDismissedTest: public ContextMenuTest {
683 public:
684     MAKE_GLIB_TEST_FIXTURE(ContextMenuDismissedTest);
685
686     ContextMenuDismissedTest()
687         : m_dismissed(false)
688     {
689     }
690
691     bool contextMenu(WebKitContextMenu* contextMenu, GdkEvent*, WebKitHitTestResult*)
692     {
693         quitMainLoop();
694         // Show the default context menu.
695         return false;
696     }
697
698     void contextMenuDismissed()
699     {
700         m_dismissed = true;
701         ContextMenuTest::contextMenuDismissed();
702     }
703
704     void showContextMenuAndWaitUntilDismissed()
705     {
706         showContextMenuAndWaitUntilFinished();
707         dismissContextMenuAndWaitUntilFinished();
708     }
709
710     bool m_dismissed;
711 };
712
713 static void testContextMenuDismissed(ContextMenuDismissedTest* test, gconstpointer)
714 {
715     test->showInWindowAndWaitUntilMapped();
716
717     test->loadHtml("<html><body>WebKitGTK+ Context menu tests</body></html>", "file:///");
718     test->waitUntilLoadFinished();
719
720     test->showContextMenuAndWaitUntilDismissed();
721     g_assert(test->m_dismissed);
722 }
723
724 class ContextMenuSmartSeparatorsTest: public ContextMenuTest {
725 public:
726     MAKE_GLIB_TEST_FIXTURE(ContextMenuSmartSeparatorsTest);
727
728     bool contextMenu(WebKitContextMenu* contextMenu, GdkEvent*, WebKitHitTestResult*)
729     {
730         webkit_context_menu_remove_all(contextMenu);
731
732         webkit_context_menu_append(contextMenu, webkit_context_menu_item_new_separator());
733         webkit_context_menu_append(contextMenu, webkit_context_menu_item_new_separator());
734         webkit_context_menu_append(contextMenu, webkit_context_menu_item_new_from_stock_action(WEBKIT_CONTEXT_MENU_ACTION_GO_BACK));
735         webkit_context_menu_append(contextMenu, webkit_context_menu_item_new_from_stock_action(WEBKIT_CONTEXT_MENU_ACTION_GO_FORWARD));
736         webkit_context_menu_append(contextMenu, webkit_context_menu_item_new_separator());
737         webkit_context_menu_append(contextMenu, webkit_context_menu_item_new_separator());
738         webkit_context_menu_append(contextMenu, webkit_context_menu_item_new_from_stock_action(WEBKIT_CONTEXT_MENU_ACTION_COPY));
739         webkit_context_menu_append(contextMenu, webkit_context_menu_item_new_separator());
740         webkit_context_menu_append(contextMenu, webkit_context_menu_item_new_from_stock_action(WEBKIT_CONTEXT_MENU_ACTION_INSPECT_ELEMENT));
741         webkit_context_menu_append(contextMenu, webkit_context_menu_item_new_separator());
742         webkit_context_menu_append(contextMenu, webkit_context_menu_item_new_separator());
743
744         quitMainLoop();
745
746         return false;
747     }
748
749     GtkMenu* showContextMenuAndGetGtkMenu()
750     {
751         showContextMenuAndWaitUntilFinished();
752         return getPopupMenu();
753     }
754 };
755
756 static void testContextMenuSmartSeparators(ContextMenuSmartSeparatorsTest* test, gconstpointer)
757 {
758     test->showInWindowAndWaitUntilMapped();
759
760     test->loadHtml("<html><body>WebKitGTK+ Context menu tests</body></html>", "file:///");
761     test->waitUntilLoadFinished();
762
763     GtkMenu* menu = test->showContextMenuAndGetGtkMenu();
764     g_assert(menu);
765
766     // Leading and trailing separators are not added to the context menu.
767     GOwnPtr<GList> menuItems(gtk_container_get_children(GTK_CONTAINER(menu)));
768     g_assert_cmpuint(g_list_length(menuItems.get()), ==, 6);
769     GtkWidget* item = GTK_WIDGET(g_list_nth_data(menuItems.get(), 0));
770     g_assert(!GTK_IS_SEPARATOR_MENU_ITEM(item) && gtk_widget_get_visible(item));
771     item = GTK_WIDGET(g_list_nth_data(menuItems.get(), 1));
772     g_assert(!GTK_IS_SEPARATOR_MENU_ITEM(item) && gtk_widget_get_visible(item));
773     item = GTK_WIDGET(g_list_nth_data(menuItems.get(), 2));
774     g_assert(GTK_IS_SEPARATOR_MENU_ITEM(item) && gtk_widget_get_visible(item));
775     item = GTK_WIDGET(g_list_nth_data(menuItems.get(), 3));
776     g_assert(!GTK_IS_SEPARATOR_MENU_ITEM(item) && gtk_widget_get_visible(item));
777     item = GTK_WIDGET(g_list_nth_data(menuItems.get(), 4));
778     g_assert(GTK_IS_SEPARATOR_MENU_ITEM(item) && gtk_widget_get_visible(item));
779     item = GTK_WIDGET(g_list_nth_data(menuItems.get(), 5));
780     g_assert(!GTK_IS_SEPARATOR_MENU_ITEM(item) && gtk_widget_get_visible(item));
781
782     // Hiding a menu item between two separators hides the following separator.
783     GtkAction* action = gtk_activatable_get_related_action(GTK_ACTIVATABLE(g_list_nth_data(menuItems.get(), 3)));
784     gtk_action_set_visible(action, FALSE);
785     menuItems.set(gtk_container_get_children(GTK_CONTAINER(menu)));
786     g_assert_cmpuint(g_list_length(menuItems.get()), ==, 6);
787     item = GTK_WIDGET(g_list_nth_data(menuItems.get(), 0));
788     g_assert(!GTK_IS_SEPARATOR_MENU_ITEM(item) && gtk_widget_get_visible(item));
789     item = GTK_WIDGET(g_list_nth_data(menuItems.get(), 1));
790     g_assert(!GTK_IS_SEPARATOR_MENU_ITEM(item) && gtk_widget_get_visible(item));
791     item = GTK_WIDGET(g_list_nth_data(menuItems.get(), 2));
792     g_assert(GTK_IS_SEPARATOR_MENU_ITEM(item) && gtk_widget_get_visible(item));
793     item = GTK_WIDGET(g_list_nth_data(menuItems.get(), 3));
794     g_assert(!GTK_IS_SEPARATOR_MENU_ITEM(item) && !gtk_widget_get_visible(item));
795     item = GTK_WIDGET(g_list_nth_data(menuItems.get(), 4));
796     g_assert(GTK_IS_SEPARATOR_MENU_ITEM(item) && !gtk_widget_get_visible(item));
797     item = GTK_WIDGET(g_list_nth_data(menuItems.get(), 5));
798     g_assert(!GTK_IS_SEPARATOR_MENU_ITEM(item) && gtk_widget_get_visible(item));
799     gtk_action_set_visible(action, TRUE);
800
801     // Showing an action between two separators shows the hidden separator.
802     item = GTK_WIDGET(g_list_nth_data(menuItems.get(), 0));
803     g_assert(!GTK_IS_SEPARATOR_MENU_ITEM(item) && gtk_widget_get_visible(item));
804     item = GTK_WIDGET(g_list_nth_data(menuItems.get(), 1));
805     g_assert(!GTK_IS_SEPARATOR_MENU_ITEM(item) && gtk_widget_get_visible(item));
806     item = GTK_WIDGET(g_list_nth_data(menuItems.get(), 2));
807     g_assert(GTK_IS_SEPARATOR_MENU_ITEM(item) && gtk_widget_get_visible(item));
808     item = GTK_WIDGET(g_list_nth_data(menuItems.get(), 3));
809     g_assert(!GTK_IS_SEPARATOR_MENU_ITEM(item) && gtk_widget_get_visible(item));
810     item = GTK_WIDGET(g_list_nth_data(menuItems.get(), 4));
811     g_assert(GTK_IS_SEPARATOR_MENU_ITEM(item) && gtk_widget_get_visible(item));
812     item = GTK_WIDGET(g_list_nth_data(menuItems.get(), 5));
813     g_assert(!GTK_IS_SEPARATOR_MENU_ITEM(item) && gtk_widget_get_visible(item));
814
815     // Trailing separators are hidden too.
816     action = gtk_activatable_get_related_action(GTK_ACTIVATABLE(g_list_nth_data(menuItems.get(), 5)));
817     gtk_action_set_visible(action, FALSE);
818     menuItems.set(gtk_container_get_children(GTK_CONTAINER(menu)));
819     item = GTK_WIDGET(g_list_nth_data(menuItems.get(), 0));
820     g_assert(!GTK_IS_SEPARATOR_MENU_ITEM(item) && gtk_widget_get_visible(item));
821     item = GTK_WIDGET(g_list_nth_data(menuItems.get(), 1));
822     g_assert(!GTK_IS_SEPARATOR_MENU_ITEM(item) && gtk_widget_get_visible(item));
823     item = GTK_WIDGET(g_list_nth_data(menuItems.get(), 2));
824     g_assert(GTK_IS_SEPARATOR_MENU_ITEM(item) && gtk_widget_get_visible(item));
825     item = GTK_WIDGET(g_list_nth_data(menuItems.get(), 3));
826     g_assert(!GTK_IS_SEPARATOR_MENU_ITEM(item) && gtk_widget_get_visible(item));
827     item = GTK_WIDGET(g_list_nth_data(menuItems.get(), 4));
828     g_assert(GTK_IS_SEPARATOR_MENU_ITEM(item) && !gtk_widget_get_visible(item));
829     item = GTK_WIDGET(g_list_nth_data(menuItems.get(), 5));
830     g_assert(!GTK_IS_SEPARATOR_MENU_ITEM(item) && !gtk_widget_get_visible(item));
831 }
832
833 void beforeAll()
834 {
835     ContextMenuDefaultTest::add("WebKitWebView", "default-menu", testContextMenuDefaultMenu);
836     ContextMenuCustomTest::add("WebKitWebView", "populate-menu", testContextMenuPopulateMenu);
837     ContextMenuCustomFullTest::add("WebKitWebView", "custom-menu", testContextMenuCustomMenu);
838     ContextMenuDisabledTest::add("WebKitWebView", "disable-menu", testContextMenuDisableMenu);
839     ContextMenuSubmenuTest::add("WebKitWebView", "submenu", testContextMenuSubMenu);
840     ContextMenuDismissedTest::add("WebKitWebView", "menu-dismissed", testContextMenuDismissed);
841     ContextMenuSmartSeparatorsTest::add("WebKitWebView", "smart-separators", testContextMenuSmartSeparators);
842 }
843
844 void afterAll()
845 {
846 }