GTK+ 3 Plugins in WebKitGTK+ and Evince Browser Plugin

GTK+ 3 plugins in WebKitGTK+

The WebKit2 GTK+ API has always been GTK+ 3 only, but WebKitGTK+ still had a hard dependency on GTK+ 2 because of the plugin process. Some popular browser plugins like flash or Java use GTK+ 2 unconditionally (and it seems they are not going to be ported to GTK+ 3, at least not in the short term). These plugins stopped working in Epiphany when it switched to GTK+ 3 and started to work again when Epiphany moved to WebKit2.

To support GTK+ 2 plugins we had to build the plugin process with GTK+ 2, but also some parts of WebCore and WebKit2 (the ones depending on GTK+ and used by the plugin process) were built twice. As a result we had a WebKitPluginProcess binary of ~40MB, that was always used for all the plugins. This kind of made sense, since there were no plugins using GTK+ 3, and the GTK+ 2 dependency was harmless for plugins not using GTK+ at all. However, we realized we were making a rule for the exception, since most of the plugins don’t even use GTK+, and there weren’t plugins using GTK+ 3 because they were not supported by any browser (kind of chicken-egg problem).

Since WebKitGTK+ 2.5.1 we have two binaries for the plugin process: WebKitPluginProcess2 which is exactly the same 40MB binary using GTK+ 2 that we have always had, but that now is only used to load plugins using GTK+ 2; and WebKitPluginProcess, a 7,4K binary that is now used by default for everything except loading plugins that use GTK+ 2. And since it links to GTK+ 3, it might load plugins using GTK+ 3 as well. Another side effect is that now we can make GTK+ 2 optional, WebKitPluginProcess2 wouldn’t be built and only plugins using GTK+ 2 wouldn’t be supported.

Evince Browser Plugin

For a long time, we have maintained that PDF documents shouldn’t be opened inside the browser, but downloaded and then opened by the default document viewer. But then the GNOME design team came up with new mockups for Epiphany were everything was integrated in the browser, including PDF documents. It’s something all the major browsers do nowadays, using different approaches though (Custom PDF plugin inside the web engine, JavaScript libraries, etc.).

At the WebKitGTK+ hackfest in 2012 we started to think about how to implement the integrated document reading in Epiphany based on the design mockups. We quickly discarded the idea of implementing it as a NPAPI plugin, because that would mean we had to use a very old evince version using GTK+ 2. We can’t implement it inside WebKit using libevince because it’s a GPL library, so the first approach was to implement it inside Epiphany using libevince. I wrote a first patch, it was mostly a proof of concept hack, that added a new view widget based on EvView to be used instead of a WebView when a document supported by evince was requested. This approach has a lot of limitations, since it only works when the main resource is a document, but not for documents embedded in a HTML page or an iframe, and a lot of integration problems that makes it quite difficult to maintain inside Epiphany. All of these issues would be solved by implementing it as a NPAPI plugin and it wouldn’t require any change in Epiphany. Now that WebKitGTK+ supports GTK+ 3 plugins, there’s no reason not to do so.

Epiphany Evince Plugin

Thanks to a project in Igalia I’ve been able to work on it, and today I’ve landed an initial implementation of the browser plugin to Evince git master. It’s only a first implementation (written in C++ 11) with the basic features (page navigation, view modes, zoom and printing), and a very simple UI that needs to be updated to match the mockups. It can be disabled at compile time like all other frontends inside Evince (thumbnailer, previewer, nautilus properties page).

Epiphany embedded PDF document Epiphany standalone PDF document

Another advantage of being a NPAPI plugin is that it’s scriptable so that you can control the viewer using JavaScript.

Epiphany scriptable PDF

And you can pass initial parameters (like current page, zoom level, view mode, etc.) from the HTML tag.

<object data="test.pdf" type="application/pdf" width="600" height="300" 
                currentPage="2" zoomMode="fit-page" continuous="false">
  The pdf could not be rendered.
</object>

You can even hide the default toolbar and build your own one using HTML and JavaScript.

WebKitGTK+ 2.5.1: Good bye WebKit1

WebKitGTK+ 2.5.1 is the first version of this release cycle. It comes very late mainly due to the regressions introduced by the switch to CMake and the problems we found after removing WebKit1 from the tree. It also includes some new features that I’ll talk about in other posts, probably when 2.6.0 is released. In this post I’ll only focus on the breaks introduced in this release, in order to help everybody to adapt their applications to the API changes if needed.

Wait, but why breaking the API?

Since the release of WebKitGTK+ 2.0 the WebKit1 API has been considered deprecated and in maintenance mode. The new WebKit2 API is quite complete and stable now, so the plan for WebKitGTK+ 2.6 was removing WebKit1, leaving it alive, but still in maintenance mode, in the 2.4 branch. After removing the code from trunk we realized that newer versions of WebKitGTK+ that are WebKit2 only should be parallel installable with older versions of WebKitGTK+ that also include WebKit1. After some discussions trying to find the best solution, we reached the conclusion that we had to bump the binary version. But then I thought, since we were going to force everybody to recompile, why not take advantage to introduce some small (but necessary) API changes that in most of the cases will not affect the the users anyway? And then I started to review the API and proposing some changes. I also wanted to make sure all API changes were introduced in the first unstable release, so that users only have to adapt their applications once, and that’s the main reason why the release has taken so long.

Binary version bump

The new binary version is 4.0, so to use this new release you need to update your build system to look for webkit2gtk-4.0 pkg-config file.

GObject DOM Bindings

The GObject DOM bindings API situation was actually the main reason for breaking the API. The problem was that the code for the DOM bindings is generated automatically from the IDL files. This means that every time a new IDL file was added to the build system, we ended up exposing a new class in our public API without even noticing. Same happened when a API incompatible change was introduced in an IDL file, for example to update it to the current standard. We added a script to our build bots to warn us when that happened, and then we had to manually deprecate the existing API and add exceptions to the code generator. This was a lot of work just to keep backwards compatibility of an API nobody was using. Most of the people actually use a 5-10% of the DOM bindings API.

Since WebKitGTK+ 2.5.1 the GObject DOM bindings API is split into stable and unstable parts. The stable part contains the most commonly used API that it’s unlikely to change. We will keep maintaining backwards compatibility of this part of the API. The rest of the API is considered unstable and might change at any time, you can still use it but at your own risk. We thought this solution was better than just removing the unstable API. There are two kind of unstable APIs:

  • Classes that are considered unstable: the entire class is considered unstable. The header is not included in the main webkitdom.h header, so to use them you have to include the header file explicitly.
  • Unstable symbols of stable classes: a method or constant in a stable class that is considered unstable. In this case the header file is included by the main webkitfom.h header, but it doesn’t contain any unstable symbols, they are included in a new header WebKitDOMClassNameUnstable.h that also needs to be included explicitly.

In both cases you need to define WEBKIT_DOM_USE_UNSTABLE_API before including the headers

#define WEBKIT_DOM_USE_UNSTABLE_API
#include <webkitdom/WebKitDOMHTMLMediaElement.h>
#include <webkitdom/WebKitDOMElementUnstable.h>

WebKit2 GTK+ API

The API changes in the WebKit2 GTK+ API could have been avoided, by deprecating symbols and adding new ones, but since we were going to break the API anyway, and the affected symbols are not that commonly used we thought it was worth it.

  • WebKitWebView::create: the signal now receives a WebKitNavigationAction parameter containing information about the navigation action that triggered the event. So now you can know the type of event (if it was a link clicked, a form submitted, etc.), the mouse button and keyboard modifiers, the URI request or even if it was a user gesture. This information is very useful to implement a popup blocker, for example.
    /* before */
    static WebKitWebView *
    web_view_created_cb (WebKitWebView *web_view,
                         gpointer       user_data)
    
    /* after */
    static WebKitWebView *
    web_view_created_cb (WebKitWebView          *web_view,
                         WebKitNavigationAction *navigation_action,
                         gpointer                user_data)
  • WebKitWebViewGroup has been removed. This class was only introduced to add the user stylesheets API, since most of the people actually use the default web view group. The grouping of pages inside WebKit2 is something that will be eventually removed, in favor of users doing the groups they need. The user stylesheets API has been moved to a new class WebKitUserContentManager that will also be extended to support user scripts. The settings can still be handled directly with the WebKitWebView API, so that if you want a group of web views to share the same settings you can simply call webkit_web_view_set_settings() for all the web views passing the same WebKitSettings object.
    /* before */
    WebKitWebViewGroup *group = webkit_web_view_get_group (web_view);
    
    webkit_web_view_group_add_user_style_sheet (group, 
                                                buffer, 
                                                NULL, /* base URI */
                                                NULL, /* whitelist */
                                                NULL, /* blacklist */
                                                WEBKIT_INJECTED_CONTENT_FRAMES_ALL);
    
    /* after */
    WebKitUserContentManager *user_content;
    WebKitUserStyleSheet     *style_sheet;
    
    style_sheet = webkit_user_style_sheet_new (buffer,
                                               WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
                                               WEBKIT_USER_STYLE_LEVEL_USER,
                                               NULL, /* whitelist */
                                               NULL /* blacklist */);
    user_content = webkit_web_view_get_user_content_manager (web_view);
    webkit_user_content_manager_add_style_sheet (user_content, style_sheet);
    webkit_user_style_sheet_unref (style_sheet);
  • WebKitCertificateInfo has been removed. This was supposed to be a convenient way of handling TLS certificates, but when trying to use it in a real case, it ended up being unconvenient. The WebKitWebView::load-failed-with-tls-errors signal now receives a GTlsCertificate and TlsCertificateFlags, and webkit_web_context_allow_tls_certificate_for_host() receives a GTlsCertificate.
    /* before */
    static gboolean
    load_failed_with_tls_errors_cb (WebKitWebView         *web_view,
                                    WebKitCertificateInfo *info,
                                    const gchar           *host,
                                    gpointer               user_data)
    {
      WebKitWebContext *context = webkit_web_view_get_context (web_view);
      GTlsCertificate *certificate = webkit_certificate_info_get_tls_certificate (info);
      GTlsCertificateFlags errors = webkit_certificate_info_get_tls_errors (info);
    
      if (add_exception_for_error (host, errors))
        webkit_web_context_allow_tls_certificate_for_host (context, info, host);
    }
    
    /* after */
    static gboolean
    load_failed_with_tls_errors_cb (WebKitWebView       *web_view,
                                    GTlsCertificate     *certificate,
                                    GTlsCertificateFlags errors,
                                    const gchar         *host,
                                    gpointer             user_data)
    {
      WebKitWebContext *context = webkit_web_view_get_context (web_view);
    
      if (add_exception_for_error (host, errors))
        webkit_web_context_allow_tls_certificate_for_host (context, certificate, host);
    }
  • View mode API: The view source mode was removed from WebCore, and the API was already marked as deprecated. Since it’s very unlikely we add more view modes, we just removed the API. There’s no replacement for this, but it could be easily implemented either using a external window with a GtkSourceView or embedded into a WebKitWebView by using a custom URI scheme and a JavaScript library for syntax highlighting.

CMake

Since version 2.5.1 WebKitGTK+ uses CMake instead autotools as its build system. The equivalent to configure, make and make install now would be something like this:

$ cd webkitgtk-2.5.1
$ cmake -DCMAKE_INSTALL_PREFIX= -DCMAKE_INSTALL_LIBDIR=lib -DPORT=GTK \
-DCMAKE_BUILD_TYPE=Release .
$ make
(enjoy the summer in the meantime)
# make install

Help!

Sure, we are available as usual in the #webkitgtk+ IRC channel at FreeNode and our mailing list webkit-gtk@lists.webkit.org.