In my last blog post I mentioned the embedding of a javascript library inside Stoq. I got a couple of requests which asked me how this was accomplished, this blog post attempts to explain some of it.
Of course we need to use the great WebKitGtk library. Unfortunately we cannot use the introspection based bindings as this needs to work on Gtk+ 2.18 and PyGTK 2.17 which were shipped in the last Ubuntu LTS release.
WebView will do all the html/css/js parts. It’s almost as simple as a normal GtkTextView, add it to a scrolled window, load the content and off you go.
The first challange comes when you want to open http:// links in your normal browser, instead of handling them in your webkit. To do that you need to listen the navigation-policy-decision-requested signal ignore certain requests. You don’t actually need to use http protocols, you can invent any url which is parsable.
Next problem is AJAX, to write a proper asynchronous widget you don’t want to reload the whole page when something changes. Since we cannot implement our own protocols in the old libsoup bindings shipped for PyGTK we need to run our own http server. That is good for other reasons as well, we can do heavy IO such as database queries in there without actually blocking the user interaction.
When we need to execute scripting in gtk we just call web_view_execute_script() which will just execute a piece of javascript. For instance,
view.execute_script(“document.title = $(‘fc-header-title’).text()” is a actual line in Stoq, it sets the window title based on calendar header title from the dom.
Going the other direction is a bit uglier, the only way of communcation I found out was opening new urls, so I implemented an application specific domain which opens a dialog or some other action within the gtk application.
I know that some of these tricks are already outdated, in newer webkitgtk versions you should write your own libsoup handlers, use the gobject dom bindings for communication, but I didn’t have these options when writing this post
TL;DR
- Listen to the ::navigation-policy-decision-requested to implement your own uri handling if you can’t do it via libsoup.
- Run a separate daemon process which will serve as an internal webserver, so AJAX calls work and won’t block on IO.
- Use web_view_execute_script() for Gtk->Javascript communication
- Use window.location = “customprotocol://” for Javascript ->Gtk communication
- Make the javascript parts work in a normal browser so the normal developer tools can be used
- If possible, use a newer version of WebKitGtk and avoid all of this.
Xan and the other webkit hackers will probably look at me in disgust for telling you how to do all of these dirty hacks 😉
Alternative way to communicate data out: set window.status (or page title) with JavaScript, which will fire a Gtk signal (window-status-changed or something; I forget the details) and then you can read the status line back out in the signal. Might be easier than constructing a URL, might not. 🙂
You’ll be sooooooooo happy when you’re able to update to newer versions! 🙂
At ProFUSION we have our own EFL browser called “Endeavour” and it does W3C widgets, requiring us to load pages from ZIP files called “wgt”, we do it using libarchive.
Also we use a technique to inject our own C objects as JavaScript objects in some cases, allowing us to communicate with the webpage. This is also used in some W3C/WAC APIs such as the geolocation, etc. Calling a method in JS calls your C function as a normal wrapper.
Console.log() is your new best friend. I used it to build a Web scraper app based on seed’s webkit browser example.
You can send Javascript into the browser a you described, then receive json encoded responses using console.log
See http://roojs.com click on blog.
There is a article on my blog, and the code its on github
I’ve done something similar – but found an approach like the above frustrating – mainly because I like the ability to have functions that return values…
I ended up using the ‘window-object-cleared’ signal on WebKitWebView followed by webkit_web_frame_get_global_context() to get the javascript context & then the JavaScriptCore API to register functions which callback directly into my app. And then I tend to use JSON to pass data.
Admittedly I’m doing this in the context of a single-threaded Ruby-GNOME2 application which simplifies a bunch of things.
My webkit bindings are http://github.com/geoffyoungs/gtk-webkit-ruby if you’re interested…
I’ve done something similar, but I use alert() with the script-alert signal to get messages back from JavaScript.