Servo is almost ready for Cordova, but there are two missing pieces

6 min read Original article ↗

Thanks to some inspiration and funding from NLnet I’ve recently done some fun hacking to find out if Servo is a viable WebView for app developer to choose and use in an Apache Cordova app. You can try it yourself with this plugin.

I’d like to give a little introduction on how Servo is implemented in Cordova. If you are just interested what we need in Servo skip ahead.

Their main slogan on the website sounds promising and suggests that Cordova is the perfect use case for a web engine like Servo:

Servo aims to empower developers with a lightweight, high-performance alternative for embedding web technologies in applications.

After some testing and digging with their example apps (servoshell) and having a try at building Servo myself I quickly realized all prerequisites for using Servo on Android are met. Servo already has a Java Native Interface (JNI) and all the tooling is in place to build an Android library (aar). Therefore, getting Servo into a Cordova app was not too hard.

With Crosswalk and the Ionic WebView plugin I had some good inspiration on how to build a plugin that replaces the default Android WebView engine if an app developer wants to do so. In Cordova the WebView engine is encapsulated pretty well, so it’s just a matter of implementing a few classes.

Challenges

After I got the build process working and loading internet sites working I went ahead and did Cordova things: loading files from the app bundle and talking to plugins with JavaScript.

Loading files with the file:/// protocol didn’t really work and creates problem for many web frameworks anyway. Therefore, I quickly added a local web server to host all the files from Cordova’s wwwdirectory. AndroidAsync is pretty straightforward to implement and allows setting up web server with WebSocket support which is also required for the second challenge.

One major reason to use Cordova is plugins. With plugins you can use advanced native features that are not available in browsers. Cordova has a bridge that makes the communication between JavaScript and native code possible.

Bridge modes

Android WebView has two methods addJavascriptInterface and evaluateJavascript which are used for the bidirectional communication between WebView and native code. Cordova Android uses these methods to exchanges data between the web and native land with the so-called eval bridge mode.

In the code we can still find the other bridge modes that have been used before these functions where added to Android WebView:

var jsToNativeModes = {
    PROMPT: 0,
    JS_OBJECT: 1
};
var nativeToJsModes = {
    // Polls for messages using the JS->Native bridge.
    POLLING: 0,
    // For LOAD_URL to be viable, it would need to have a work-around for
    // the bug where the soft-keyboard gets dismissed when a message is sent.
    LOAD_URL: 1,
    // For the ONLINE_EVENT to be viable, it would need to intercept all event
    // listeners (both through addEventListener and window.ononline) as well
    // as set the navigator property itself.
    ONLINE_EVENT: 2,
    EVAL_BRIDGE: 3
};

Because the Java API of ServoView does not have evaluateJavascript I had to find a new way and hack in a new bridge mode with WebSockets. That’s where it got really ugly. Adding the WebSocket server was really easy because the AndroidAsync library supports it out of the box. Adding the WebSocket client code was more tricky because I’m implementing Servo as a plugin and not as a Cordova platform. The bridge code is part of the cordova-android platform and I don’t want to introduce weird hooks that modify the platform code when you add the plugin.

The solution I came up with was to overwrite the window.prompt function and set the bridge mode to the old prompt mode. In the JavaScript code for my plugin window.prompt now handles the WebSocket messages. This makes the bridge work but currently is terribly unreliable! The message queue and encoding is not implemented correctly, yet. If I or somebody else spends some more time this solution might become reliable. Currently, is just a prototype and it’s good enough for me to show it works.

Essential APIs a WebView needs

As you can see from the challenges we expect some capabilities from WebViews to implement Cordova. For Servo this means two APIs are missing and once they are added the Cordova plugin can provide a proper WebViewEngine implementation to the cordova-android platform. All fully-fledged WebViews have these APIs to interact with the WebView in some way, shape, or form.

I’d love to contribute as much as possible of this to Servo, but I’m neither a Rust nor web engine developer. Additionally, these APIs are very powerful and might introduce security risks if not implemented carefully. I really would appreciate if new Servo APIs would work similar to existing APIs from other WebViews. WebView APIs suffer a lot from fragmentation and minor differences, and we should avoid more complications that need to be explained on caniwebview.com.

Custom Scheme Handler

As discussed in this issue making the registerProtocolHandler feature Servo already has available in Java would be a huge help for Servo WebViews on Android. Then we don’t need a local web server and could make files available under our own custom scheme like cordova://localhost or even implement more interesting things with intercepting HTTP requests to the custom scheme. A good inspiration is WKURLSchemeHandler on iOS.

If we are following standards and existing APIs, I won’t have to write another chapter on WebView origin issues and developers don’t have to customize their Cordova app to work differently for Servo.

JavaScript bridge

Another very important capability of a fully-fledged WebView is to interact with JavaScript from the native side. For this Servo can take inspiration from Android or other WebViews like WKWebView. This is discussed in this issue. If WebViews are used for hybrid apps this is basically a must-have feature. With a proper JavaScript bridge we can implement Cordova’s plugin system and other frameworks do something very similar.

Why all of this?

Having an alternative to Android WebView sounds pretty cool to me. It’s fun to dig into the internals of Cordova, but main goal here is to help advance WebViews on Android. The WebView space deserves some competition and possibly this sparks innovation for all WebViews.