In recent years, JavaScript has become a very popular language. Google has promoted rich web applications which compete with desktop programs: Gmail, Google Maps and Google Docs are rivals of Outlook, Google Earth and Office. Today HTTP, HTML, CSS and JavaScript are key technologies in which companies are heavily investing so they can develop even more powerful web applications, i.e. HTML5 features enable off-line web applications (i.e. store local data).
JavaScript has a restricted programming model in order to meet security concerns. But, wouldn't be fun if we could extend JavaScript to create wonderful programs which mix desktop and web technologies? Absolutely! In order to do that, we'll use WebKit and Gtk+.
The source code of this tutorial is available at github.com/vrruiz/WebKit-JavaScriptCore-Extensions.
WebKit
Chances are that you're using a browser which comes with WebKit. This open source technology was originally developed by KDE, and forked by Apple. WebKit powers Apple's Safari, Google's Chrome and many other browsers.
WebKit is to browsers what an engine is to cars: many bodyworks can carry the same engine model. WebKit provides the basic functions to download, parse, run and display web pages. However, WebKit doesn't provide a user interface to introduce URL address, change settings, navigation buttons, etc. That's the developer's job.
WebKit is multiplatform. Interesting to us is that WebKit is extensible and provides ways to interact with its JavaScript default engine, JavaScriptCore.
Currently, KDE, Gnome and MacOS X support WebKit, to provide HTML views inside desktop applications. WebKit has been ported to many platforms (Mac, Linux, Windows), many SDK's (Cocoa, Gtk, Qt) and many languages. One of this ports is WebKitGTK+.
WebKitGTK+
GTK+ is multiplatform graphical toolkit, a set of graphical libraries to program desktop applications (the popular Linux desktop enviroment GNOME is built upon GTK+). WebKitGTK+ allows GTK+ applications to display web pages using WebKit. Originally, WebKit is programmed in C++, but WebKitGTK+ has a C interface using GObject (part of GLib, which "enables" object-oriented programming in plain C).
To install GTK+ and WebKitGTK+ development files in Ubuntu do this in the command line:
$ sudo apt-get install gnome-devel libwebkitgtk-3.0-dev
A simple web view.
This program creates a GTK+ application. The main window has 800 x 600 pixels, and contains a scrolled window which finally holds the web view. The web view displays this blog.
#include <gtk/gtk.h> #include <webkit/webkit.h> /* Destroy callback */ static void destroy_cb( GtkWidget *widget, gpointer data ) { gtk_main_quit(); } int main (int argc, char* argv[]) { /* Initialize the widget set */ gtk_init (&argc, &argv); /* Create the window widgets */ GtkWidget *main_window = gtk_window_new (GTK_WINDOW_TOPLEVEL); GtkWidget *scrolled_window = gtk_scrolled_window_new (NULL, NULL); /* Create the WebKit Web View widget */ GtkWidget *web_view = webkit_web_view_new (); /* Place the WebKitWebView in the GtkScrolledWindow */ gtk_container_add (GTK_CONTAINER (scrolled_window), web_view); gtk_container_add (GTK_CONTAINER (main_window), scrolled_window); /* Connect the destroy window event with destroy function */ g_signal_connect (G_OBJECT (main_window), "destroy", G_CALLBACK (destroy_cb), NULL); /* Open webpage */ webkit_web_view_load_uri (WEBKIT_WEB_VIEW (web_view), "http://rvr.typepad.com/"); /* Create the main window */ gtk_window_set_default_size (GTK_WINDOW (main_window), 800, 600); /* Show the application window */ gtk_widget_show_all (main_window); /* Enter the main event loop, and wait for user interaction */ gtk_main (); /* The user lost interest */ return 0; }
To compile and run this program, type:
$ gcc webkit-01.c -o webkit-01 `pkg-config --cflags --libs webkitgtk-3.0`
$ ./webkit-01
An application with a web view will appear.
Your own browser in just a minute. Easy, isn't it?
Interacting with JavaScriptCore.
What's great about WebKit is that the JavaScript engine can be extended to support custom functions. PhoneGap SDK actually uses this feature to provide mobile developers access to low-level OS features via JavaScript (i.e. accelerometer).
As previously stated, JavaScriptCore is WebKit's JavaScript engine, at it provides an API to extend JavaScript and add new classes. Basically, for each class we need to provide callbacks for the constructor and destructor class, and a list of class methods and a callback for each of them.
Next is the source code of a bare JavaScript class declaration. It does nothing, except to print a messages in the console when the class is initialized and the constructor method called.
#include <gtk/gtk.h>
#include <webkit/webkit.h>
#include <JavaScriptCore/JavaScript.h>
/* Class initialize */
static void class_init_cb(JSContextRef ctx,
JSObjectRef object)
{
g_message("Custom class initialize.");
}
/* Class finalize */
static void class_finalize_cb(JSObjectRef object)
{
g_message("Custom class finalize.");
}
/* Class constructor. Called at "new CustomClass()" */
JSObjectRef class_constructor_cb(JSContextRef ctx,
JSObjectRef constructor,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception)
{
g_message("Custom class constructor");
}
static const JSClassDefinition class_def =
{
0, // version
kJSClassAttributeNone, // attributes
"CustomClass", // className
NULL, // parentClass
NULL, // staticValues
NULL, // staticFunctions
class_init_cb, // initialize
class_finalize_cb, // finalize
NULL, // hasProperty
NULL, // getProperty
NULL, // setProperty
NULL, // deleteProperty
NULL, // getPropertyNames
NULL, // callAsFunction
class_constructor_cb, // callAsConstructor
NULL, // hasInstance
NULL // convertToType
};
/* Callback - JavaScript window object has been cleared */
static void window_object_cleared_cb(WebKitWebView *web_view,
WebKitWebFrame *frame,
gpointer context,
gpointer window_object,
gpointer user_data)
{
/* Add classes to JavaScriptCore */
JSClassRef classDef = JSClassCreate(&class_def);
JSObjectRef classObj = JSObjectMake(context, classDef, context);
JSObjectRef globalObj = JSContextGetGlobalObject(context);
JSStringRef str = JSStringCreateWithUTF8CString("CustomClass");
JSObjectSetProperty(context, globalObj, str, classObj, kJSPropertyAttributeNone, NULL);
}
/* Destroy callback */
static void destroy(GtkWidget *widget,
gpointer data )
{
gtk_main_quit();
}
int
main (int argc, char* argv[])
{
/* Initialize the widget set */
gtk_init (&argc, &argv);
/* Create the window widgets */
GtkWidget *main_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
GtkWidget *scrolled_window = gtk_scrolled_window_new (NULL, NULL);
/* Create the WebKit Web View widget */
GtkWidget *web_view = webkit_web_view_new ();
/* Connect the window object cleared event with callback */
g_signal_connect (G_OBJECT (web_view), "window-object-cleared", G_CALLBACK(window_object_cleared_cb), web_view);
/* Place the WebKitWebView in the GtkScrolledWindow */
gtk_container_add (GTK_CONTAINER (scrolled_window), web_view);
gtk_container_add (GTK_CONTAINER (main_window), scrolled_window);
/* Connect the destroy window event with destroy function */
g_signal_connect (G_OBJECT (main_window), "destroy", G_CALLBACK (destroy), NULL);
/* Open webpage */
webkit_web_view_load_uri (WEBKIT_WEB_VIEW (web_view), "file://webkit-class.html");
/* Create the main window */
gtk_window_set_default_size (GTK_WINDOW (main_window), 800, 600);
/* Show the application window */
gtk_widget_show_all (main_window);
/* Enter the main event loop, and wait for user interaction */
gtk_main ();
/* The user lost interest */
return 0;
}
This program loads a local file, webkit-02.html, which only defines a CustomClass object.
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>Extending JavaScript with WebKit. Custom Class.</h1>
<script type="text/javascript">
custom = new CustomClass();
</script>
</body>
</html>
To compile this, type:
$ gcc webkit-02.c -o webkit-02 `pkg-config --cflags --libs webkitgtk-3.0`
The execution of this page will run our callback methods.
$ ./webkit-02
** Message: Custom class initialize.
** Message: Custom class constructor.
Of course, our custom class do nothing, except to print messages.
In the next post, we'll see add some real features to JavaScript. Enjoy!
Hi,
I noticed an issue with this code:
/* Class constructor. Called at "new CustomClass()" */
JSObjectRef class_constructor_cb(JSContextRef ctx,
JSObjectRef constructor,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception)
{
g_message("Custom class constructor");
}
Will cause an segmentation fault, this method needs to return something, I fixed it in my code with:
/* Class constructor. Called at "new CustomClass()" */
JSObjectRef class_constructor_cb(JSContextRef context,
JSObjectRef constructor,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception)
{
g_message("Custom class constructor");
if (argumentCount > 0 && JSValueIsStrictEqual(ctx, arguments[0], JSValueMakeNumber(context, 0)))
return JSValueToObject(context, JSValueMakeNumber(context, 1), exception);
return JSValueToObject(context, JSValueMakeNumber(context, 0), exception);
}
Posted by: Tw1nk | December 25, 2012 at 07:57 AM
I had the same issue, and Tw1nk's fix was good. Why does that even compile without the return?
Posted by: Jack O'Connor | January 22, 2013 at 12:28 AM
Hi,
I use WebKit2 which may cause that my WebKitWebView does not
have the "window-object-cleared" signal. Therefore it cannot be
connected. I have tried to "hook" it to the "resource-load-started"
with the following callback function:
static void resource_load_started_cb(WebKitWebView *web_view,
WebKitWebResource *resource, WebKitURIRequest *request, gpointer
user_data)
{
JSGlobalContextRef * context
=webkit_web_view_get_javascript_global_context(web_view);
JSClassRef classDef = JSClassCreate(&class_def);
JSObjectRef classObj = JSObjectMake(context, classDef, context);
JSObjectRef globalObj = JSContextGetGlobalObject(context);
JSStringRef str = JSStringCreateWithUTF8CString("CustomClass");
JSObjectSetProperty(context, globalObj, str, classObj,
kJSPropertyAttributeNone, NULL);
}
However, when I use my CustomClass in Javascript, WebKit cannot find the class.
Can someone guide me how to do this? Currently I use the Minibrowser
as a sample program to implement it.
Posted by: Christian Frost | February 22, 2013 at 07:39 AM