I’ve been experimenting with various web runtimes for running the system chrome of Webian Shell.
Requirements:
- Enable me to create browser chrome which can safely embed web content
- Provides an API to expose aspects of the content via methods and events (e.g. location and title changes, icons, app manifests etc.)
- Ideally allow me to write the chrome in HTML, CSS and JavaScript
- Ideally be portable across desktop and tablet hardware as well as other form factors like TV and VR in future
- Provide a route to create an OS which boots directly to Shell without any other graphical applications running
Previously Webian Shell has used used XULRunner, Mozilla Chromeless and B2G from Mozilla, but all of these projects have now been discontinued. Gecko is no longer seen as a platform for building applications, just the back end of Firefox. This means it’s very difficult these days to build anything except Firefox with Gecko.
During the transition of B2G to the community I thought it would be possible for me to continue using B2G OS as a base, using the new simplified architecture we had created with a split between system chrome and system web services. Unfortunately all B2G code is now being ripped out of Gecko so that isn’t an option.
Since then, the runtimes I’ve investigated include:
- Positron
- GeckoView
- Electron
- Android WebView
- React Native
- qbrt
- Muon
Positron
Positron was meant to be an Electron-compatible app runtime for creating desktop apps based on Gecko. This would have been ideal for the desktop version of Shell, but unfortunately that project has also now been discontinued.
GeckoView
On the mobile side, GeckoView is meant to be a Gecko-based alternative to Android’s WebView. I experimented with creating a tablet version of Shell using GeckoView as part of an Android app. My idea was to use Android Things to boot to Shell as the only Android app running on a tablet device.
I got Android Things running on a Raspberry Pi booting a basic Android app which provides a status bar and a clock. The downside of this approach is that it would mean a completely separate code base for desktop and tablet versions, as GeckoView is designed to have chrome written in Java. I tried to get GeckoView running chrome-privileged HTML so that I could write the chrome in HTML, CSS and JavaScript but there’s currently no obvious way to package HTML (or XUL) chrome in an Android app for use with Gecko. Also, GeckoView is still very immature, tricky to use and limited in features. Firefox Focus is still using Android’s native WebView and although there’s a branch using GeckoView I don’t have confidence that’s going to ship any time soon. GeckoView remains an experiment.
Electron
Electron is a well supported desktop app runtime created by GitHub for the Atom text editor which allows you to write desktop applications in HTML, CSS and JavaScript. An advantage of Electron is that it uses the Chromium code base but also allows you to use NodeJS APIs. With a chrome + web services split for the Shell architecture this could mean being able to run the front end chrome and back end web services using the same runtime, which is quite attractive.
The downside of Electron is that it’s not really designed for writing browser applications. The Tofino team discovered some limitations when trying to prototype new desktop browser experiences. Some of these limitations aren’t so much of a problem for Shell, but it seems likely that at some point I’m going to come across a missing feature.
One example of a feature which Gecko has which Electron lacks is an event to tell the <webview> embedder when a manifest link relation is detected in a web page. This is needed by Shell to detect and install web applications. I filed a feature request for this but in the meantime the community suggested a workaround by using a “preload script” which allows you to inject a privileged script into web pages which runs before the content is rendered. I don’t really like this approach but it does provide a route for me to polyfill over missing features.
In terms of providing a route to booting an OS straight to Shell I discovered that resin.io has a template for booting Resin OS straight to an ElectronJS application. What’s particularly cool about this is that they have support for various IoT developer boards including the Raspberry Pi and apparently have support for touch events. As I’ve been trying to prototype the tablet version of Shell using a Raspberry Pi with a touch screen (for use as a smart home controller), this could be ideal. It would allow me to write both the desktop and tablet versions of Shell using the same runtime and share code between them. The question is how well this performs in practice, which is going to require some more experimentation.
Android WebView
As GeckoView is still quite immature I tried using the native Android WebView instead. This obviously works fine as an Android app, but I got stuck when I realised that Android Things on Raspberry Pi didn’t yet support WebView because it lacked OpenGL support. Trying to add a WebView to an Android app would just crash the app. Presumably this would also prevent me running a GeckoView based app on Android Things.
This bug has just been fixed so it should be possible to use WebView in Preview 5 of Android Things. But it would still mean a completely separate code base for the tablet version of Shell and I’m not sure the native Android WebView has all the features I need.
React Native
React is an interesting option. React Native compiles to native Java for Android and has a webview component, albeit probably not very tested for building a full browser application. React can also be used with Electron on desktop, although code probably needs to be different between desktop and Android. This is attractive as it would allow sharing some code between desktop and tablet versions of Shell. But writing in React is not really writing in HTML, CSS and JavaScript. It’s writing in JavaScript which is compiled to HTML, CSS and JavaScript, or to a Java Android application. It would have to be a conscious choice to re-write the whole Shell application in a React style using JSX and style definitions in JavaScript.
qbrt
The “Quantum Browser Runtime” rose from the ashes of Positron with a less ambitious goal of providing a desktop application runtime using Firefox and existing Gecko APIs, rather than trying to be compatible with Electron.
This was great news and really unblocked me in moving forward on the desktop version of Shell. qbrt can easily be installed using npm and allowed me to run chrome written in HTML, CSS and JavaScript with a simple command, much like XULRunner and Mozilla Chromeless from the days of old.
The downside is that because Gecko is so tightly coupled with Firefox these days, qbrt includes a bit too much of Firefox in its current incarnation. This makes for a bulky download and weird issues like my application calling home to Mozilla for things because it thinks it’s still Firefox. These things may well be fixed over time, but qbrt still feels like a side project and I don’t have confidence that it’s going to stick around very long.
Technically Firefox is still using B2G’s Browser API which I need to create Shell and there is a vague plan to move Firefox from XUL to HTML over time, so my needs should be fairly in line with the long term plans for Firefox. But this plan is slow moving and while the “platform” team (now the Firefox back end team) is laser focused on the current needs of Firefox, anything that gets in their way gets ripped out of Gecko. For example, methods on the Browser API which were used by B2G to set the visibility of browser tabs to prioritise processes were removed without notice to make way for a new process priority manager in Firefox.
I really want to use Gecko for Webian Shell, but I’m finding the Firefox team are quite hostile to anyone except the Firefox team using Gecko to build things and it’s getting harder and harder to do so.
Muon
Muon is a fork of Electron created by Brave for their desktop web browser. It’s like Electron but designed for building browsers and browser-like applications using HTML, CSS and JavaScript. It uses the Chromium source code directly rather than Electron’s fork (so it can keep better track of new Chromium features), supports Chrome extensions and has better security features for building browsers.
I haven’t yet come across any of the limitations of Electron that Muon is designed to fix, but I imagine that eventually I will. Also, I understand that the Kai OS team is using Muon to try to replace Gecko on B2G by porting Blink and Muon to the Gonk OS layer (basically the lower layers of Android). If they made that open source it would mean that there could be a path to running Muon-based applications on Android hardware in the future, which is quite exciting.
Conclusions
There is no obvious solution which currently meets all of my requirements. I think the neatest solution I’ve found so far is Electron. It would allow me to embed web content in a <webview> which has a fairly mature API and a mechanism to polyfill missing webview features. It would allow me to write the chrome in HTML, CSS and JavaScript. It may be possible to run it on both desktop and touch-based hardware (at least using a Raspberry Pi) and a has an existing potential route to creating an OS which boots directly to Shell (via Resin OS). As a bonus it would allow me to run both the front end chrome and back end web services using the same runtime.
Electron has some limitations when it comes to building a browser and I’m not sure how well it will perform on touch based hardware compared with say a native Android app running on Android Things, and there’s currently no easy way to get it running on Android tablet hardware. Also, it would mean leaving behind Gecko which Webian Shell has been using from the start.
In an attempt to hedge my bets I’ve been trying to write a common layer of abstraction on top of both Electron’s <webview> and qbrt’s <iframe mozbrowser> HTML elements by creating a <webian-webview> web component with my own WebView API. This would allow me to support both Electron and qbrt with the same front end chrome code. This in itself was not easy because Gecko still doesn’t support Custom Elements or the Shadow DOM. I was able to achieve it using the webcomponents.js polyfill, although there’s a bug with that library which prevents my current implementation of the chrome from running in Gecko. I also created a startup script which can detect whether it’s running in Electron or qbrt and follow the correct code path accordingly. Whilst I like the flexibility of being able to run on both Gecko and Blink, this adds an extra layer of abstraction which will probably hurt performance in the long term and may get quite complicated as I get into more advanced features which are significantly different or simply missing from qbrt or Electron.
I think my overall conclusion is to start with Electron. Electron is well supported, comes pretty close to what I need and it provides an easy path to migrate to Muon and/or a potential KaiOS runtime in future. If Spidernode goes anywhere and Mozilla puts some real commitment behind a Gecko app runtime then I could potentially migrate back to Gecko in future, but for now it’s just too damn hard to use. The Electron approach might not work so well for touch-based devices if the Kai OS runtime doesn’t get open sourced as I suspect performance on Resin OS isn’t going to be great and I’ll have to implement my own on-screen keyboard which I’d probably get for free with Android Things. So I may keep experimenting with a native Android version or even using React and compiling to different platforms.