3 min read

Loading JavaScriptCore extensions dynamically

Topics:

When doing a WebKit port many people are looking to extend JavaScriptCore with custom functions which expose features of their particular hardware platform. Usually, they are also looking to load JavaScript extensions dynamically at runtime rather than being forced to recompile the WebKit libraries each time they want to add or change a function.

This blog posting looks at two ways in which WebKit can support dynamically loading JavaScript. JavaScriptCore can also be extended by compiling in extensions and writing IDL files, but that is a subject for a future post.

JavaScriptCore provides a C public interface, which can be found under Source/JavaScriptCore/API. There are JavaDoc style comments describing the individual functions, and some documentation is available on Apple's site. Another blog posting provides a nice, although somewhat OS X specific, overview of the API.

A good strategy when creating your dynamic library is to have a single entry point which will create and return a JavaScript object containing your extension functions and objects. This function should have a signature something like this:

JSObject jsextension_load(JSContextRef ctx);

This function will use the supplied JSContextRef to create a class and make and return an object of that class. The class will contain static functions and possibly values that you want the extension to export and is defined through a JSClassDefinition structure instance.

The class definition is passed in as an argument to a call to JSClassCreate. You only need a single class instance for the lifetime of your  dynamic library. Once the class instance exists, an object can be made using the JSObjectMake call, and this object is the return value of your jsextension_load function.

The next step to to build a dynamic library which exports jsextension_load. Once this is complete, you then need a way of loading the extensions. This could involve specifying the name of the dynamic library to open, or simply scanning a specified directory for dynamic libraries, opening them and seeing if they export a jsextension_load symbol.

This post will look at two ways of loading extensions. The first is by using something similar to the CommonJS "require" function. The second is adding a method to the WebView to load an extension.

WebKit does not support the CommonJS require function, so this will have to be added to the JavaScript interpreter. An easy way of doing this is to add it to the set of static functions associated with the JavaScriptCore global object. These functions are defined in JSGlobalObjectFunctions.h and JSGlobalObjectFunctions.cpp which are found in JavaScriptCore/runtime, and the require function can be added to these files, along with a reference to it in the global object table found in JSGlobalObject.cpp.

The require function can be given a string as an argument and then attempt to dlopen a dynamic library corresponding to that string. The advantage of this approach as opposed to modifying WebView is that the extensions are available to the standalone JavaScript interpreter and the extensions to be loaded are controlled through JavaScript rather than through the WebView.

A second way of loading extensions is to modify the WebView API to support an extension loading function. The problem that occurs with this approach is to ensure you have a valid JavaScript context that can be used when loading the dynamic libraries. The current JavaScript context can be retrieved by calling

toGlobalRef(frame->script()->globalObject(mainThreadNormalWorld())->globalExec()

where frame is the current "main" frame in the WebView.

This is fine, unless you want to load an extension once and have it persist across page loads, as each page load will cause a new JavaScript context to be  created. Fortunately, there is a callback in the FrameLoaderClient which is called after a new global object is created, called dispatchDidClearWindowObject. This callback can be used to trigger the WebView to reload any extension into the new JavaScript context.

The advantage of managing extensions through the WebView rather than through JavaScriptCore is that is minimizes the amount of change to core WebKit code and makes the full interface provided by the WebView available to extensions.