{"id":669,"date":"2013-09-10T10:22:05","date_gmt":"2013-09-10T08:22:05","guid":{"rendered":"http:\/\/blogs.igalia.com\/carlosgc\/?p=669"},"modified":"2013-09-10T10:22:05","modified_gmt":"2013-09-10T08:22:05","slug":"webkit2gtk-web-process-extensions","status":"publish","type":"post","link":"https:\/\/blogs.igalia.com\/carlosgc\/2013\/09\/10\/webkit2gtk-web-process-extensions\/","title":{"rendered":"WebKit2GTK+ Web Process Extensions"},"content":{"rendered":"<p>The multiprocess architecture of WebKit2 brought us <a href=\"http:\/\/blogs.igalia.com\/carlosgc\/2013\/04\/11\/webkitgtk-2-0-0\/\">a lot of advantages<\/a>, but it also introduced important challenges, like how to expose some features that now live in the Web Process (DOM, JavaScript, etc.). The <a href=\"http:\/\/webkitgtk.org\/reference\/webkit2gtk\/stable\/ch01.html\">UI process API<\/a> is fully asynchronous to make sure the UI is never blocked, but some APIs like the DOM bindings are synchronous by design. To expose those features that live in the Web Process, WebKit2GTK+ provides a Web Extensions mechanism. A Web Extension is like a plugin for the Web Process, that is loaded at start up, similar to a GTK module or gio extension, but that runs in the Web Process. WebKit2GTK+ exposes a <a href=\"http:\/\/webkitgtk.org\/reference\/webkit2gtk\/stable\/ch02.html\">simple low level API<\/a> that at the moment provides access to three main features:<\/p>\n<ul>\n<li><span style=\"line-height: 13px\"><strong>GObject DOM bindings<\/strong>: The exactly same API used in WebKit1 is available in WebKit2.<\/span><\/li>\n<li><strong>WebKitWebPage::send-request<\/strong> signal: It allows to change any request before it is sent to the server, or even simply prevent it from being sent.<\/li>\n<li><strong>Custom JavaScript injection<\/strong>: It provides a signal, equivalent to\u00a0<code>WebKitWebView::window-object-cleared<\/code> in WebKit1, to inject custom JavaScript using the JavaScriptCore API. (Since 2.2)<\/li>\n<\/ul>\n<p>This simple API doesn&#8217;t provide any way of communication with the UI Process, so that the user can use any IPC mechanism without interfering with the internal WebKit IPC traffic.\u00a0<a href=\"https:\/\/projects.gnome.org\/epiphany\/\">Epiphany<\/a> currently installs a <a href=\"https:\/\/git.gnome.org\/browse\/epiphany\/tree\/embed\/web-extension\">Web Extension<\/a> to implement some of its features such us pre-filled forms, ads blocker or Do Not Track using D-BUS for the communication between the Web Extension and the UI Process.<\/p>\n<h2>How to write a Web Extension?<\/h2>\n<p>Web Extensions are shared libraries loaded at run time by the Web Process, so they don&#8217;t have a main function, but they have an entry point called by the WebProcess right after the extension is loaded. The initialization function must be called\u00a0<code>webkit_web_extension_initialize()<\/code> and it receives a\u00a0<code>WebKitWebExtension<\/code> object as parameter. It should also be public, so make sure to use the\u00a0<code>G_MODULE_EXPORT<\/code> macro. This is the function to initialize the Web Extension and can be used, for example, to be notified when a web page is created.<\/p>\n<pre>static void\r\nweb_page_created_callback (WebKitWebExtension *extension,\r\n                           WebKitWebPage      *web_page,\r\n                           gpointer            user_data)\r\n{\r\n    g_print (\"Page %d created for %s\\n\", \r\n             webkit_web_page_get_id (web_page),\r\n             webkit_web_page_get_uri (web_page));\r\n}\r\n\r\nG_MODULE_EXPORT void\r\nwebkit_web_extension_initialize (WebKitWebExtension *extension)\r\n{\r\n    g_signal_connect (extension, \"page-created\",\u00a0\r\n                      G_CALLBACK (web_page_created_callback),\u00a0\r\n                      NULL);\r\n}<\/pre>\n<p>This would be a minimal Web Extension, it does nothing yet, but it can be compiled and loaded so let&#8217;s see how to create a Makefile.am file to build the extension.<\/p>\n<pre>webextension_LTLIBRARIES = libmyappwebextension.la\r\nwebextensiondir = $(libdir)\/MyApp\/web-extension\r\nlibmyappwebextension_la_SOURCES = my-app-web-extension.c\r\nlibmyappwebextension_la_CFLAGS = $(WEB_EXTENSION_CFLAGS)\r\nlibmyappwebextension_la_LIBADD = $(WEB_EXTENSION_LIBS)\r\nlibmyappwebextension_la_LDFLAGS = -module -avoid-version -no-undefined<\/pre>\n<p>The extension will be installed in <code>$(libdir)\/MyApp\/web-extension<\/code> so we need to tell WebKit where to find web extensions before the Web Process is spawned. Call <code>webkit_web_context_set_web_extensions_directory()<\/code> as soon as possible in your application, before any other WebKit call to make sure it&#8217;s called before a Web Process is launched. You can create a preprocessor macro in the Makefile.am to pass the value of the Web Extensions directory.<\/p>\n<pre>myapp_CPPFLAGS = -DMYAPP_WEB_EXTENSIONS_DIR=\\\"\"$(libdir)\/MyApp\/web-extension\"\\\"<\/pre>\n<p>And then in the code<\/p>\n<pre>webkit_web_context_set_web_extensions_directory (webkit_web_context_get_default (), \r\n                                                 MYAPP_WEB_EXTENSIONS_DIR);<\/pre>\n<p>The Web Extension only needs WebKit2GTK+ to build, so in the configure.ac you can define <code>WEB_EXTENSION_CFLAGS<\/code> and <code>WEB_EXTENSION_LIBS<\/code> using pkg-config macros.<\/p>\n<pre>PKG_CHECK_MODULES(WEB_EXTENSION, [webkit2gtk-3.0 &gt;= 2.0.0])\r\nAC_SUBST(WEB_EXTENSION_CFLAGS)\r\nAC_SUBST(WEB_EXTENSION_LIBS)<\/pre>\n<p>This should be enough. You should be able to build and install the Web Extension with you program and see the printf message every time a page is created. But that&#8217;s a useless example, let&#8217;s see how to use the Web Extensions API to do something useful.<\/p>\n<h2>Accessing the DOM<\/h2>\n<p>The GObject DOM bindings API available in WebKit1 is also exposed in WebKit2 from the Web Extensions API. We only need to call <code>webkit_web_page_get_dom_document()<\/code> to get the <code>WebKitDOMDocument<\/code> of the given web page.<\/p>\n<pre>static void\r\nweb_page_created_callback (WebKitWebExtension *extension,\r\n                           WebKitWebPage      *web_page,\r\n                           gpointer            user_data)\r\n{\r\n    WebKitDOMDocument *document;\r\n    gchar             *title;\r\n\r\n    document = webkit_web_page_get_dom_document (web_page);\r\n    title = webkit_dom_document_get_title (document);\r\n    g_print (\"Page %d created for %s with title %s\\n\", \r\n             webkit_web_page_get_id (web_page),\r\n             webkit_web_page_get_uri (web_page),\r\n             title);\r\n    g_free (title);\r\n}<\/pre>\n<h2>Using WebKitWebPage::send-request signal<\/h2>\n<p>Using the Web Extensions API it&#8217;s possible to modify the request of any resource before it&#8217;s sent to the server, adding HTTP headers or modifying the URI. You can also make WebKit ignore a request, for example to block resources depending on the URI, by simply connecting to the signal and returning TRUE.<\/p>\n<pre>static gboolean\r\nweb_page_send_request (WebKitWebPage     *web_page,\r\n                       WebKitURIRequest  *request,\r\n                       WebKitURIResponse *redirected_response,\r\n                       gpointer           user_data)\r\n{\r\n    const char *request_uri;\r\n    const char *page_uri;\r\n\r\n    request_uri = webkit_uri_request_get_uri (request);\r\n    page_uri = webkit_web_page_get_uri (web_page);\r\n\r\n    return uri_is_an_advertisement (request_uri, page_uri);\r\n}\r\n\r\nstatic void\r\nweb_page_created_callback (WebKitWebExtension *extension,\r\n                           WebKitWebPage      *web_page,\r\n                           gpointer            user_data)\r\n{\r\n    g_signal_connect_object (web_page, \"send-request\",\r\n                             G_CALLBACK (web_page_send_request),\r\n                             NULL, 0);\r\n}<\/pre>\n<h2>Extending JavaScript<\/h2>\n<p>Using the JavaScriptCore API it&#8217;s possible to inject custom JavaScript code by connecting to the <code>window-object-cleared<\/code> signal of the default <code>WebKitScriptWorld<\/code>. You can get the global JavaScript execution context by calling <code>webkit_frame_get_javascript_context_for_script_world()<\/code> for the <code>WebKitFrame<\/code> passed as parameter of the <code>window-object-cleared<\/code> signal.<\/p>\n<pre>static void \r\nwindow_object_cleared_callback (WebKitScriptWorld *world, \r\n                                WebKitWebPage     *web_page, \r\n                                WebKitFrame       *frame, \r\n                                gpointer           user_data)\r\n{\r\n    JSGlobalContextRef jsContext;\r\n    JSObjectRef        globalObject;\r\n\r\n    jsContext = webkit_frame_get_javascript_context_for_script_world (frame, world);\r\n    globalObject = JSContextGetGlobalObject (jsContext);\r\n\r\n    \/* Use JSC API to add the JavaScript code you want *\/\r\n}\r\n\r\nG_MODULE_EXPORT void\r\nwebkit_web_extension_initialize (WebKitWebExtension *extension)\r\n{\r\n    g_signal_connect (webkit_script_world_get_default (), \r\n                      \"window-object-cleared\",\u00a0\r\n                      G_CALLBACK (window_object_cleared_callback),\u00a0\r\n                      NULL);\r\n}<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>The multiprocess architecture of WebKit2 brought us a lot of advantages, but it also introduced important challenges, like how to expose some features that now live in the Web Process (DOM, JavaScript, etc.). The UI process API is fully asynchronous &hellip; <a href=\"https:\/\/blogs.igalia.com\/carlosgc\/2013\/09\/10\/webkit2gtk-web-process-extensions\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":9,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3,6,7,8],"tags":[21,22],"class_list":["post-669","post","type-post","status-publish","format-standard","hentry","category-free-software","category-gnome","category-igalia","category-webkit","tag-epiphany","tag-webkit"],"_links":{"self":[{"href":"https:\/\/blogs.igalia.com\/carlosgc\/wp-json\/wp\/v2\/posts\/669","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.igalia.com\/carlosgc\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.igalia.com\/carlosgc\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.igalia.com\/carlosgc\/wp-json\/wp\/v2\/users\/9"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.igalia.com\/carlosgc\/wp-json\/wp\/v2\/comments?post=669"}],"version-history":[{"count":10,"href":"https:\/\/blogs.igalia.com\/carlosgc\/wp-json\/wp\/v2\/posts\/669\/revisions"}],"predecessor-version":[{"id":683,"href":"https:\/\/blogs.igalia.com\/carlosgc\/wp-json\/wp\/v2\/posts\/669\/revisions\/683"}],"wp:attachment":[{"href":"https:\/\/blogs.igalia.com\/carlosgc\/wp-json\/wp\/v2\/media?parent=669"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.igalia.com\/carlosgc\/wp-json\/wp\/v2\/categories?post=669"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.igalia.com\/carlosgc\/wp-json\/wp\/v2\/tags?post=669"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}