The server side of Browservice is written in C++ on top of Chromium Embedded Framework (CEF), using the Poco library for the HTTP server, zlib and libjpeg-turbo for image compression, Pango and FreeType2 for text rendering and XCB for clipboard handling. A simple custom GUI widget system (used for the browser control bar) and a multithreaded PNG compressor (for reduced compression latency) have been implemented for this project.
The client side is implemented in HTML and JavaScript. As compatibility with old browsers is a priority for the project, great care is taken to test that the essential features work for all of the supported client browsers (listed in README.md
). Testing is done manually; to help with this, test/index.html
contains a checklist of features that need to work (see test/README.md
for further information). It is impractical to thoroughly test all supported client browsers for all minor changes, and thus the amount of testing must be proportional to the assessed risk of breaking compatibility. Contributors cannot be expected to test all the client browsers before submitting a pull request.
CEF/Chromium and the client browser do most of the heavy lifting, and thus our code is not really performance critical (one exception to this is the parallel PNG compressor). Thus safety, simplicity and compatibility is prioritized over efficiency in most of the code.
Starting from release 0.9.2.0, the code for serving the browser UI to the user through a HTTP server has been spun off to a plugin called Retrojsvice. The plugin is still developed in the same repository as Browservice in subdirectory viceplugins/retrojsvice
. The main Browservice executable links to the plugin library dynamically, and the plugin may easily be replaced by another plugin that implements the same C API; this makes it easier to implement support for Browservice native clients. The API is documented in vice_plugin_api.h
.
Some conventions and notes about the server-side code:
-
Most objects are reference counted; to handle the boilerplate of enforcing that
shared_ptr
is used to manage the lifetime and disabling copy and move constructors/assignment operators, we use theSHARED_ONLY_CLASS
macro defined incommon.hpp
.-
SHARED_ONLY_CLASS
objects are constructed by calling the static create method; the arguments are forwarded to the constructor, along with a privateCKey
dummy value. To obtain ashared_ptr
to itself, the object may implement theafterConstruct_
member function, which is called immediately after the constructor. -
To help avoid reference cycles, in debug builds we check at the end of the program that all
SHARED_ONLY_CLASS
objects have been destructed.
-
-
The
REQUIRE
macro defined incommon.hpp
is used abundantly to check assumptions made in the code. These checks are enabled also for release builds, just to be safe. -
Typically the service-centric objects (such as
Server
,Window
,HTTPServer
and UI widgets) form a tree structure, in which the parents haveshared_ptr
s to their children and call their member functions directly. Information flow to the opposite direction, where a child notifies or queries its parent, is typically implemented by having the parent implement an event handler interface of the child and giving the child a pointer to the parent (reference cycles are avoided by usingweak_ptr
or breaking the cycle in the shutdown procedure of one of the objects). To avoid re-entrancy issues (where a function indirectly calls itself recursively by accident and does not take this possibility into account), calls to the member functions of a child object should not directly result in calls to the event handlers; this can be avoided for example by calling the event handler functions usingpostTask
incommon.hpp
so that they are called from the event loop. Exceptions to this rule are documented in the event handler classes.- In Retrojsvice, the
mce
marker object of typeMCE
, when used as the first argument to a function, is used to denote functions that may result in direct calls to the event handlers. This convention has not been extended to Browservice yet.
- In Retrojsvice, the
-
We avoid relying on inheritance (apart from pure interfaces) as it tends to make the code hard to understand. We make an exception for
Widget
s, because in that case, implementing the commonWidget
behavior such as event routing and render handling in the common baseWidget
class significantly reduces the amount of boilerplate code in each widget (each widget only has to implement a few protected virtual functions to fill in the behavior specific to it). -
Most of the code runs in the CEF browser process UI thread event loop (in debug builds we often check this using the macro
REQUIRE_UI_THREAD
). To run functions in the UI thread from other threads, thepostTask
function incommon.hpp
should be used. The use of other threads is typically isolated to individual modules:-
In Retrojsvice
http.hpp
, we use the Poco thread pool for writing the responses to avoid IO blocking the UI thread. The actual request handlers are called in the CEF UI thread, and communication with the Poco thread is handled behind the scenes. Only the callback function that writes the response body supplied by the request handler needs to be safe to call in a non-CEF UI thread. -
In Retrojsvice
image_compressor.hpp
andpng.hpp
, we use worker threads to handle image compression as it is quite CPU intensive, and we want the UI to keep running at the same time. -
In
xwindow.hpp
, we use a worker thread to handle X11 events received through XCB.
-
-
Even though most of our code runs in the CEF UI thread, CEF might hold shared pointers to our objects in other threads and thus it is possible that our objects are destructed outside the CEF UI thread. Therefore destructors should not directly call functions of other objects that expect to be called in CEF UI thread (without
postTask
). Typically we keep our destructors as simple as possible. -
We try to keep error handling as simple as possible: most errors simply result in aborting the program (typically through the
REQUIRE
macro). For errors that may happen in normal use, we try to recover from the error and optionally log a message using theINFO_LOG
,WARNING_LOG
andERROR_LOG
macros in defined incommon.hpp
. We follow the CEF/Chrome convention of not using exceptions; however, we must keep in mind that Poco might throw exceptions. -
The code of Retrojsvice mostly follows the same conventions as the code of Browservice, even though some of the infrastructure (such as the logging macros and task queue) have been implemented differently as they have to interoperate through the plugin API.
-
In Retrojsvice, we use code generation for HTML templates (in the
html
directory). They are compiled into C++ functions (ingen/html.cpp
) by the Makefile using the Python scriptgen_html_cpp.py
that implements a very simple templating language (%-VAR-%
is replaced by the value ofVAR
in the struct specified for each function in html.hpp without escaping).