[webkit-gtk] Context menu API in WebKit2

Carlos Garcia Campos cgarcia at igalia.com
Wed Mar 7 08:42:54 PST 2012


I've started to work (mainly think about) on the context menu client for
webkit2. Current wk1 API provides a web setting to disable the default
context menu and a populate-popup signal that is emitted right before
the default context menu is shown. 
This API is good enough for simple cases when you just want to add some
custom actions to the default context menu and you are sure they are not
already in the menu. The signal passes a GtkMenu as a parameter, that
you can use to add items. The main problem of this approach is that it's
difficult to know what actions are already in the menu, because menu
item labels are not actually part of the API so you shouldn't rely on
them. 

So, I think the minimum requirements of the API would be:

 - It should be a single signal that allows you to disable the default
context menu without using a setting.
 - It should be easy to add/remove and reorder menu items.
 - It should be possible to identify the actions included in the default
context menu
 - It should allow the following 4 use cases:
  + Apps that don't want to show any context menu at all.
  + Apps that just want to show the default context menu
  + Apps that want to customize the default context menu
  + Apps that want to build their own context menu

So the idea is to provide a single signal WebKitWebView::context-menu
that passes a Menu and a WebKitHitTestResult. We should probably need to
pass the GdkEvent, because this is now asynchronous, so that when the
context menu is shown, the button press callback has returned already,
and gtk_get_current_event() return NULL. That event is cached by the web
page proxy for that reason, so we could get the event and pass it as
part of the signal. The signal would be true_handled, so that returning
TRUE means the default context menu won't be shown, and FALSE that the
menu will be shown. The 4 uses cases would be:

 + Apps that don't want to show any context menu at all. Connect to the
context-menu signal and simply return TRUE.
 + Apps that just want to show the default context menu. That's the
default behaviour so apps don't need to do anything.
 + Apps that want to customize the default context menu. Connect to
context-menu signal, use the Menu object to add/remove/reorder/whatever
and return FALSE.
 + Apps that want to build their own context menu. Connect to
context-menu signal, build your own menu using the HitTestResult to
decide what items to add, and the return TRUE. Depending on what Menu
object we use, this could be implemented also removing all items from
the menu passed, adding your own items and returning FALSE. This option
probably makes more sense taking into account how context menu works in
WebKit2.

In WebKit2, a context menu is a Vector of WebContextMenuItemData. The
context menu client has a callback getContextMenuFromProposedMenu,
called right before the context menu is shown, so that you can customize
the menu. To show the menu, a WebContextMenuProxy object is created,
that creates a GtkMenu from the Vector using the WebCore API and calls
gtk_menu_popup to show the menu.

In order to customize the menu it's important to be able to easily
identify the items in the default context menu, or even group of
items/actions (navigation actions, editing actions, link actions,
etc.). 

So, the key point is what to pass as Menu object to the signal. A
GtkMenu is not enough, because of the reasons I mentioned before, mainly
that we can't rely on menu item labels to identify menu items. However,
if the menu item has been created from a GtkAction, we could use the
action name to identify the item. 

We have, at least, the following options:

 a) Use a GtkMenu and use GtkMenuShell api to add/remove items. Items in
the default context menu have a GtkAction associated with a name that
allows us to identify it. This is the approach we decided to follow in
webkit1, see bug https://bugs.webkit.org/show_bug.cgi?id=67660 

 b) Use a GtkUIManager. The default context menu would define a XML
using placeholders for the different action groups (navigation actions,
editing actions, etc.). The user can add new items to the menu by
merging their own xml, and adding actions and action groups to the ui
manager. 

 c) Use new GMenu API. We would pass a GMenuModel so that user can
add/remove items using the GMenu API. Default menu would contain
GActions with a name that allows us to identify it. We could have
actions groups using GActionGroup.

 d) Use our thing. Add WebKitContexMenu and WebKitContextMenuIem objects
with the API needed to meet all the requirements.

Option a) is probably the simplest one, we would need to add api to get
the well-known name for the action associated with a menu item, and also
api to add an item to the menu using a well known name, so that users
can also add items to the menu that are known by WebKit without having
to create them manually. Those methods would be global (not belonging to
any WebKit object). This wouldn't allow to use action groups. The main
problem I see with this is that getContextMenuFromProposedMenu callback
should return an array of items, so we would need to convert the GtkMenu
to an array of items, and then the WebContextMenuProxy converts the
array again into a GtkMenu, creating new menu items and actions, so the
GtkMenu passed to the signal is not the one that it will be popped up. A
possible solution might be to create the GtkMenu in
getContextMenuFromProposedMenu and pass that menu to the
WebContextMenuProxy, then when WebContextMenuProxy::showContextMenu() is
called, we just ignore the items Vector and just show the cached
GtkMenu.

Option b) requires to write XML which is convenient for static menus,
but maybe not so convenient for this kind of dynamic menus. Also, it
seems UIManager API will be deprecated sooner or later in GTK+.

Option c) was my original idea when GMenu API was added to GLib and GTK
+. GMenuModel API provides everything we need (and probably more that we
don't need). The problem is that it's not easy to create a GtkMenu from
a GMenu. There's gtk_menu_new_from_model() in GTK+ but GMenu API is
designed to work with GtkApplication and GtkApplicationWindow, so that
method only populates the menu it's attached to a widget inside a
GtkApplicationWindow. I've talked to gtk+ devs and it seems GAction and
GMenu might be used for other use cases like popup menus in the future,
but it's not still clear how at this point, so it seems to me that is
unlikely to happen in a near future.

GtkAction and GtkActionGroup provides everything we need, so we don't
really need GAction and GMenu API. So, option d) could be based on
GtkAction and GtkActionGroup API. Current WebCore context menu API is
already based on GtkAction indeed. If we don't really need action
groups, this could be very similar to option a), but with the API to get
the well known name of a menu item in WebKitContextMenuItem and the API
to add a predefined item in WebKitContextMenu. If actions are created by
the user, we have the same problem than with a), the menu is converted
to Vector of items and then converted to a GtkMenu again, in this case,
since we are not exposing GtkMenu nor GtkMenuItem, we just need to make
sure that the GtkActions used to build the new menu in
WebContextMenuProxy::showContextMenu() are the same. We could add
specific API to WebContextMenuProxy and use it directly to store the new
menu, so that in WebContextMenuProxy::showContextMenu() we can ignore
the given Vector and just popup the current menu.

Opinions? what option do you think it's better?

-- 
Carlos Garcia Campos
http://pgp.rediris.es:11371/pks/lookup?op=get&search=0xF3D322D0EC4582C3
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 198 bytes
Desc: This is a digitally signed message part
URL: <http://lists.webkit.org/pipermail/webkit-gtk/attachments/20120307/803b7023/attachment.bin>


More information about the webkit-gtk mailing list