An old/new approach to QtWebKit Hybrid

In a previous blog post, we were talking about the new direction we're taking with WebKit2, and a few people were concerned about the future of hybrid application development. Re-iterating the issues we were facing (see Ademar's comment in the above blog), it's extremely costly to maintain a deep C++ API like the one we provided for QtWebKit on top a fast-moving project like WebKit. Since I'm a fan of the hybrid approach,  I was thinking about it a lot. Though I'd much rather start my blog post with "The QtWebKit bridge now works with WebKit2", that's unfortunately not feasible. The QtWebKit bridge relies too much on the internals of WebKit(1), which has the application and the web-view bound together in a single process.

What we're trying to achieve with hybrid can be divided into three categories:

  1. Tweak and control the browser window and interactions. This would still be possible to some extent as we're still exposing a WebView to QML/C++.
  2. Interacting with existing web sites, for example by injecting JavaScript into the frame. This is currently not exposed in WebKit2.
  3. Exposing functionality and "local" data to the web view that would otherwise not be  available to the browser.

The problem I'm trying to address here is the third one. If we look at what kind of functionality we want to access from our web view, it's in most cases data or off-screen functionality. For example, using C++ for heavier computing, a custom caching technique, or accessing capabilities such as a local calendar or microphone.
This brings me back to the way this had been achieved in the past. Since one of the things web views could always do is access the network via http, creating a small local HTTP server that exposes local functionality would give us the same value, without tying us to a particular implementation detail in WebKit, such as the QtWebKit bridge.

So what am I selling?

The idea is to use a headless web-server as a "channel" between the web-view and the host application. The server would listen on a port which would be accessible to the HTML page via HTTP requests, and the Qt application can respond to those requests and publish messages to that channel. This approach gives us that same data/functionality channel between the Qt application and the HTML page, without the need for custom APIs inside WebKit.
This is not a new approach. But, there have been a couple of issues with it in the past. First of all, security. Unlike the bridge, a local web server is not tied to a particular web view, which means that any web site can attempt to access your locally exposed functionality if it knows what port you use for your web server.
Second, integrating a full-blown Apache web-server or even lighttpd adds a lot of complexity to the equation, and buys us very little, as we don't necessarily need to support PHP, CGI, serving files with different mime-types, or a scalable process model. This becomes even more apparent when compared to the ease of use of the QtWebKit bridge, which really makes writing a standalone Qt application with a web view easier.

Introducing Qt Web Channel

So I decided to try and come up with the smallest possible implementation of a web server that would give us two things - a web server that is only accessible to certain web-views, and tight integration with the host Qt application, without changing anything in the web view internals.  A key guideline to this implementation was to not try to solve any problem beyond that. The result is below. The HTML page can send requests, and we can respond to those requests from our QML or Qt-C++ container.  The web view can also subscribe to a named message channel that QML/C++ can later publish messages to.

From JavaScript, it might look like this:

navigator.webChannel.exec("doSomething", function(response) {
//    do something with the response.
});
While this can be handled from QML:
WebChannel {
        onExecute: {
            if (requestData == "doSomething")
                response.send("OK");
        }
}
The per-web-view security is handled through a simple shared secret, that needs to be passed to the web-view, for example via the url. The secret is a string that the HTML page needs to send with ever request to our web server, otherwise the request will automatically be rejected. At the example below, a random website cannot access your local functionality without knowing your web channel's base url, which contains that shared secret.
WebView {
        url: "index.html?webChannelBaseUrl=" + webChannel.baseUrl;
}

This can be installed and accessed easily as a QML import, which allows you to use this web-server as a channel between web and native in your own QML application. It's also possible to use this from C++, without QML. See the README and examples.

A nice outcome of this endeavor was that it was pretty easy to write an abstraction on top of it that produces the same QObject integration as the good-old bridge. See http://qt.gitorious.org/qt-labs/qwebchannel/trees/master/examples/qtobject.
But there are some things in the current API that this approach doesn't handle, such as injecting javascript into an existing page, or C++ manipulation of web elements. Those would unfortunately require an actual additional WebKit API.

Flow of a web-channel based application

To explain a bit how the application would behave in reality, this is the basic flow:

  1. A WebChannel element is created from QML.
  2. The WebChannel generates a secret-protected URL pointing to a script.
  3. The QML application passes the script URL to the WebView.
  4. The HTML page in the WebView load the script from the URL.
  5. The HTML page now has a webChannel object, that looks something like this:
    {
        exec: function(message, onSuccess) { ... },
        subscribe: function(eventID, onMessage) { ... }
    }
  6. The web page can execute or subscribe to messages that are handled in the QML container.

What's next

This is, for now, a concept. Whether or not it gains momentum, is up to you.
Code is at  http://qt.gitorious.org/qt-labs/qwebchannel.
Thoughts?

Blog Topics:

Comments