Huge progress is being made on the Servo browser engine, and development continues moving forward at full speed. Now, it’s even possible to write applications that embed Servo to display web content, and these applications can drop Chromium in at any point, with very few changes, in order to have a more functional product while Servo continues its heavy development. This article will take a look at the new code that provides this detection ability to toggle functionality based on the running engine, in addition to the new improvements that have been introduced to Servo’s rendering and embedding capabilities.
Detecting Servo to Work in Harmony With Chromium
Detection of the engine is made possible by a symbol added into Servo’s embedding library, which can be detected in C with a bit of code like this:
servo = !!dlsym(NULL, "servo_test");
This returns the address of the symbol and sets a boolean variable “servo” based on the symbol’s presence. When using Chromium, it will return NULL, and this can allow different steps to be taken in the application. For example, in one of our demo browsers the following code snippet can be found:
Evas_Object *ic; ic = elm_image_add(win); if (servo) elm_image_file_set(ic, "doge.png", NULL); else elm_image_file_set(ic, "chromium.png", NULL); elm_win_icon_object_set(win, elm_image_object_get(ic));
This will produce a different icon for the window based on the running engine, and provides a visual hint to the user or developer.
Framework Under Construction
As discussed in a previous post, Servo has chosen to use the API from Chromium Embedded Framework (CEF) for embedding. There have been some challenges in adopting this, the first is that Servo has a different rendering mechanism than Chromium.
Let’s examine the rendering interface provided by CEF:
void (CEF_CALLBACK *on_paint)(struct _cef_render_handler_t* self, struct _cef_browser_t* browser, cef_paint_element_type_t type, size_t dirtyRectsCount, cef_rect_t const* dirtyRects, const void* buffer, int width, int height);
This is called when an element should be painted. Pixel values passed to this function are scaled relative to view coordinates based on the value of CefScreenInfo.device_scale_factor returned from GetScreenInfo.
- |type| indicates whether the element is the view or the popup widget.
- |buffer| contains the pixel data for the whole image.
- |dirtyRects| contains the set of rectangles in pixel coordinates that need to be repainted.
- |buffer| will be |width|*|height|*4 bytes in size and represents a BGRA image with an upper-left origin.
This callback is triggered any time Chromium wants to draw or update its displayed content, and it’s the only available mechanism for integrating the application canvas with the web canvas. The only alternative to it is to allow CEF to create its own window and re-parent it to the application’s own window: a poor choice since it then becomes impossible to render any application content in the space occupied by the browser content.
When using this callback, the user is provided with a pixel buffer that has no lifetime guarantees and must be rendered “now.” The onus is on the application to perform the actual blitting to the screen. Even though time was already spent in the engine creating this pixel buffer, no drawing to the output occurred. The application is either left with a texture upload or a full software render for the region, and neither of these were possible in Servo.
The problem here is in the architecture: Servo does all its rendering in parallel using output tiles, so there is no pixel buffer to provide. A decision was made that Servo should not just be a passive user of CEF, but should also engage in driving its API development. The result is that Servo now has extra methods in the rendering interface which are being tested and iterated upon. Once they are deemed ‘good,’ the plan is to try merging the methods into upstream CEF. However, at present they are simply added onto the ends of the interface structs in order to preserve binary compatibility with CEF.
The following video displays this ABI compatibility:
Composite Smarter, Not Harder
Servo’s CEF render interface now contains a few additional methods:
void (CEF_CALLBACK *composite)(struct _cef_browser_host_t* self);
This instructs the browser to perform an accelerated composite. The appropriate Direct3D or OpenGL state must have been set up before calling this function.
void (CEF_CALLBACK *initialize_compositing)(struct _cef_browser_host_t* self);
This instructs the browser to initialize accelerated compositing. The appropriate Direct3D or OpenGL state must have been set up before calling this function.
int (CEF_CALLBACK *get_backing_rect)(struct _cef_render_handler_t* self, struct _cef_browser_t* browser, cef_rect_t* rect);
This is called to retrieve the backing size of the view rectangle which is relative to screen coordinates. On HiDPI displays, the backing size can differ from the view size as returned by |GetViewRect|. Return true (1) if the rectangle was provided. Only used on Mac OS.
void (CEF_CALLBACK *on_present)(struct _cef_render_handler_t* self, struct _cef_browser_t* browser);
This is called when an element should be presented (e.g. double buffers should page flip). This is called only during accelerated compositing.
These provide a number of advantages and improvements to the hardware-accelerated rendering pipeline, effectively allowing the application to set up its render surface for the browser to draw directly onto. The application can then execute a swap to display the newly-rendered content with no extra steps. This ends up being simpler in many cases for applications.
The biggest benefit, however, is that it provides compatibility with different types of rendering mechanisms, such as deferred renderers. CEF provides no lifetime guarantees for the buffers provided in its paint callback, meaning that any developer wanting to safely render the buffer asynchronously should copy it first to ensure that it is not deleted without warning.
Using the new methods added in Servo’s branch of CEF, an application can prepare its render surfaces before calling initialize_compositing() to enable rendering. A paint callback occurs as in upstream CEF, but there is no pixel buffer provided; the application simply executes the composite() method at any convenient time to trigger the render and then performs the swap when the engine sends the present() callback.
Bringing it All Together
A short video was recently recorded which features a development browser chrome that includes all of the functionality described in this post. It’s worth noting that, at the time of writing, the biggest speed bottleneck in Servo’s page load is the network resource fetching code. Servo does not yet implement connection pooling or resource prefetching, which causes many sequential resource loads and creates the long delays seen in the video.
Guest Author: Lars Bergstrom
Lars Bergstrom is a researcher at Mozilla, currently working on the servo parallel web browser project.He received his Ph.D. from the University of Chicago‘s Computer Science department, studying under Dr. John Reppy. His Master’s paper was on the implementation of analysis and optimization passes in our parallel compiler, Manticore. His Ph.D. research was on how to add mutation safely and efficiently into a functional parallel programming language. He has also been doing work on a runtime, garbage collector, and most recently some extensions to control-flow analysis. Before that, he was a manager and a developer at Microsoft in the Visual Studio organization, working on next-generation software development tools technology out at the Redmond, WA offices.