v.0.1
Introduction

Seed, first and foremost, provides an easily embeddable Javascript engine to developers looking for a straightforward way to create extensible applications. It also provides bindings between GObject and the WebKit Javascript engine, giving new developers access to the power of the GNOME stack from a familiar and simple language, and allowing rapid prototyping of applications for hardened GNOME developers.

This tutorial begins with a few brief examples, and then dives right in, following the development of a simple Seed program, from beginning to end. By the end of the tutorial, you'll have your very own tiny WebKit-based web browser, as well as a summary knowledge of the use of Seed to build Gtk+ applications.

Beginning Seed

It makes sense to start our exploration with a program you're probably quite familiar with:

#!/usr/bin/env seed

Seed.print("Hello, world!");

If you were to make this script executable (chmod +x hello.js), and run it, you'd hopefully see the following, just as expected (if you don't, for some reason, make sure you have the latest version of Seed installed, then email us):

Hello, world!

In order to make the file executable, include (#!/usr/bin/env seed) at the top of every Seed program you write. This is known as the shebang line, and tells your shell where to find the seed interpreter; I'm only going to include it when listing a whole file, from now on.

Variables in Javascript are not given any type, and conversion between different kinds of values is automatic and painless. For example, you can:

There is one exception: in order to convert a string of digits into a 'number', Javascript needs to be explicitly instructed to do so: parseFloat("42.5").

Seed also provides a very simple interface to the GNU Readline library, which allows programs to ask the user for input. The only argument Seed.readline() requires is the prompt for the user. Also, the current version of Seed ensures that everything typed is automatically saved in the prompt's history; if you press the up key while at a prompt, you can access and edit lines you've previously entered. Future versions of Seed will provide more control over the history and other parts of readline.

var my_name = Seed.readline("Your name? ");
var my_age = Seed.readline("Your age? ");
var old = 25;
var old_age = old + parseFloat(my_age);
Seed.print(my_name + " will be " + old_age + " in " + old + " years!");

You've probably noticed that the word 'var' precedes the first use of every variable in Javascript. This is important, because it ensures that the memory consumed by the variable is freed to be used elsewhere at the end of the current block of code, when the variable goes out of scope. If, instead, you want to create a variable which is global (available forever, after it is created), you can omit the 'var'. Keep in mind that making many global variables is generally considered bad practice, and can be expensive in terms of memory use.

A Javascript Shell

Javascript, being a scripting language, includes a construct, eval() which allows you to evaluate a string of Javascript. This allows, for example, a user to input Javascript with readline, and it to be executed as if it had been part of your source file. In addition, eval()'s return value is the return value of the snippet of code. For example:

var output = eval("2+2");
Seed.print(output);

Will output:

4.000000

When something goes wrong in a piece of Javascript code, the program will exit, most likely leaving the user in a confused state. For example, if you try to access a variable that doesn't exist: Seed.print(asdf); Seed will exit with the message: ReferenceError Can't find variable: asdf. It is possible to catch this sort of error, or exception, inside of your Javascript program, ensuring that it doesn't terminate your program - or that if it does, it prints a useful error message. The try/catch construct provides a way to try to execute a segment of Javascript, and, if it fails, run a second segment, without exiting the program. The second segment could print a user-friendly error message, ignore the exception entirely, or try to work around the problem. A quick example of try/catch:

try
{
    Seed.print(asdf);
}
catch(e)
{
    Seed.print("Something went wrong!");
}

It's also possible to determine what, exactly, went wrong. The 'e' in the catch statement (which, by the way, you cannot omit) is actually an object containing information about the exception! We can access some of the basic properties of this object:

try
{
    Seed.print(asdf);
}
catch(e)
{
    Seed.print("Something went wrong!");
    Seed.print(e.name);
    Seed.print(e.message);
}

This will print a message similar to what would be printed if you hadn't caught the exception, but without exiting the program!

Combining readline, eval, exceptions, and print, we can write a simple shell, allowing interactive use of Seed. This shell is included in the Seed distribution, in examples/repl.js. Looking at the source, you'll note that it takes very little code to implement a shell:

examples/repl.js
#!/usr/bin/env seed

while(1)
{
    try
    {
        Seed.print(eval(Seed.readline("> ")));
    }
    catch(e)
    {
        Seed.print(e.name + " " + e.message);
    }
}

You can (and should!) use this shell in order to experiment with and learn to use Seed.

Getting GTK Going

Thus far in this tutorial, we've been completely ignoring the most useful part of Seed: the ability to use external libraries from within Javascript. The single most useful of these libraries is GTK, the widget and windowing toolkit used by all GNOME applications, which will provide the ability to create and manipulate graphical windows, as well as just about any sort of widget you should require.

In order to use GTK (or any other external library) in a Seed program, you first have to import the functions from said library. Seed.import_namespace(), taking as its only argument the name of the library to import, does this for us.

Once the library has been imported, all of its functions are available on a global object with the same name as the library. For example, if we Seed.import_namespace("Gtk"), all of the imported functions are available on the Gtk object: Gtk.init(), etc.

Let's start off the development of our browser by getting Gtk working. It takes very little to get a window displayed with Seed:

#!/usr/bin/env seed

Seed.import_namespace("Gtk");
Gtk.init(null, null);

var window = new Gtk.Window();
window.show_all();

Gtk.main();

If you've ever used GTK from C, you'll notice some similarities here. All of the GTK functions have been mapped into Javascript in a reasonable way, but it will certainly take a bit to get used to, for example, new Gtk.Window() instead of gtk_window_new().

Executing the above script should give you a window that looks entirely empty and boring, something like the following:

Blank GTK Window
JSON Constructors

Notice that the title of the window is 'seed'. We'll fix that, using another Seed feature: you can use JSON notation to set properties while constructing objects, like so:

var window = new Gtk.Window({title: "Browser"});

This saves a lot of typing from the alternative, conventional method:

var window = new Gtk.Window();
window.set_title("Browser");

You can set any number of properties this way, by separating them by commas ({"title": "Browser", "default-height": 500}, etc.). This method should work for any GObject constructor.

Signals

You'll notice that our program, as it stands, fails to quit when you click the 'Close' button. You can, of course, quit it with Ctrl-C, but this is certainly unacceptable behaviour. To fix it, we'll connect a Javascript function to the signal that gets emitted when the 'Close' button is clicked:

function quit()
{
    Gtk.main_quit();
}

window.signal.hide.connect(quit);

The signal names are the same as in the GTK documentation, except using underscores instead of dashes between words.

Local Context with 'this'

Javascript, like some other object-oriented languages, has the concept of a local context. Accessed with the 'this' keyword, local contexts allow for neatly contained, transparent signal callbacks, among other things. Imagine we have, a WebKit view, say, browser, and a button, call it back_button. We could certainly make the browser object global, but this would make having multiple browsers (think tabs!) rather annoying. Instead, when setting up the callback, we can provide browser as the 'this' object. This gives us the following code:

function back(button)
{
    this.go_back();
}

function create_browser()
{
    var browser;
    var back_button;

    ...

    back_button.signal.clicked.connect(back, browser);
}

In this case, the browser object is passed as the magical this object into the back() function, so go_back() is actually called on browser. The upside to this model is that, no matter how many times create_browser() is called, back() is always provided the browser that corresponds with its back_button.

Working with Widgets

We'll start by making the browser's toolbar buttons. GTK provides a ToolButton widget, which is generally used for making such toolbars, as well as various different stock icons (to ensure consistency within all GTK applications). Browsing through the GTK Stock Item documentation, we find that we're looking for "gtk-go-back", "gtk-go-forward", and "gtk-refresh". A glance at the GtkToolButton documentation shows us that we can choose a stock icon by setting the stock-id property - we'll use JSON constructors to keep things tidy. Do note that we use underscores instead of dashes, because the property name isn't quoted (thus, a dash would indicate subtraction, which isn't what we're looking for!):

function create_ui()
{
    var main_ui = new Gtk.VBox();
    var toolbar = new Gtk.HBox();

    var back_button = new Gtk.ToolButton({stock_id: "gtk-go-back"});
    var forward_button = new Gtk.ToolButton({stock_id: "gtk-go-forward"});
    var refresh_button = new Gtk.ToolButton({stock_id: "gtk-refresh"});

    var url_entry = new Gtk.Entry();

    back_button.signal.clicked.connect(back);
    forward_button.signal.clicked.connect(forward);
    refresh_button.signal.clicked.connect(refresh);

    url_entry.signal.activate.connect(browse);

    toolbar.pack_start(back_button);
    toolbar.pack_start(forward_button);
    toolbar.pack_start(refresh_button);
    toolbar.pack_start(url_entry, true, true);

    main_ui.pack_start(toolbar);
    return main_ui;
}

There are a few things in the snippet above which you probably haven't seen before (unless you've used GTK in another language). Firstly, the Gtk.Entry widget is a simple text entry field, like you would expect in a browser's URL bar. Secondly, you'll notice the use of the Gtk.HBox widget, and its pack_start() function. These serve as the foundation of GUI layout in GTK: a window is subdivided into boxes, which 'pack' widgets in a particular direction (HBoxes pack horizontally, VBoxes pack vertically, as expected). We use a HBox, since we want our toolbar arranged horizontally. pack_start() adds a widget to a Box; widgets are packed in the order they're added. There are optional arguments, which are addressed in more depth in the GtkBox documentation, which allow you to force widgets to expand into the usable space (the second and third arguments used when packing url_entry above serve this purpose).

We also need a bunch of callbacks (for all three buttons, and for when you're done entering text in the URL bar). We'll make them just print the function they're supposed to perform, for now, since we don't have a WebKit view to operate on yet.

function forward(button)
{
    Seed.print("forward");
}

function back(button)
{
    Seed.print("back");
}

function refresh(button)
{
    Seed.print("refresh");
}

function browse(button)
{
    Seed.print("browser");
}

You'll notice that create_ui() returns an VBox with all of the widgets in it - thinking ahead, we're packing the toolbar HBox into a VBox (eventually, we'll add the WebKit view, too!). In fact, to try and get a more visual feel of packing, let's take a look at the Box layout for our browser:

Packing Layout

Right now, nothing's calling create_ui(), so you won't see the toolbar drawn. To remedy this, before window.show_all(), add a line to pack the toolbar:

window.add(create_ui());

Your code should be in a runnable state now; take a minute to try it out, stand back, and admire what you've learned:

GTK Window with buttons and text entry field

If, for some reason, something doesn't work, compare your code to the tutorial version.

Adding WebKit

It's finally time to start displaying some web pages with our little browser! Let's create and pack a WebKit web view below our toolbar, first. Create the browser at the top of the create_ui() function (we'll also need to pass the browser as the this object for our button callbacks, so it needs to already be created), and pack it into the main_ui VBox after you pack the toolbar. Here's an updated version of our create_ui() function:

function create_ui()
{
    var main_ui = new Gtk.VBox();
    var toolbar = new Gtk.HBox();

    var browser = new WebKit.WebView();

    var back_button = new Gtk.ToolButton({stock_id: "gtk-go-back"});
    var forward_button = new Gtk.ToolButton({stock_id: "gtk-go-forward"});
    var refresh_button = new Gtk.ToolButton({stock_id: "gtk-refresh"});

    var url_entry = new Gtk.Entry();

    back_button.signal.clicked.connect(back, browser);
    forward_button.signal.clicked.connect(forward, browser);
    refresh_button.signal.clicked.connect(refresh, browser);

    url_entry.signal.activate.connect(browse, browser);

    toolbar.pack_start(back_button);
    toolbar.pack_start(forward_button);
    toolbar.pack_start(refresh_button);
    toolbar.pack_start(url_entry, true, true);

    main_ui.pack_start(toolbar);
    main_ui.pack_start(browser, true, true);
    return main_ui;
}

Also, remember that we need to import a namespace before its functions are available to us! So, go back to the top of the file and import "WebKit", just after you import "Gtk". One final thing, before you again try to run your browser: we haven't yet specified a 'recommended' size for our window - let's go ahead and do that (if we didn't do so, the WebKit view would have no space to fill!). Just after you create the Gtk.Window(), add:

window.resize(600,600);

Now, fire up your browser! Hopefully, you'll see a nice blank WebKit view, like below. If you don't, take a peek at our version.

GTK Window with toolbar and empty browser view

That's really not a very interesting browser, at all - nothing works yet, and there's no way to navigate! Still, we're almost done.

Finishing our Browser

Poking around in the WebKit documentation (the WebKit team is a bit behind on documentation, so all we have to work with is header files), we find that the open() function on a WebView allows you to navigate to a particular page. Just after you create the WebView, have it navigate to your favorite page:

browser.open("http://www.gnome.org");

A quick note about WebKit: if you omit the protocol part of a URL (e.g., http://), WebKit won't even bother to try to figure it out - so make sure you specify it! This is also important for the browse() callback, which is called when you press enter in the URL field, which we'll implement now. To get around this shortcoming, we'll use Javascript's string search function to see if a protocol has been specified, and, if it hasn't, we'll assume it's 'http://':

function browse(url)
{
    if(url.text.search("://") < 0)
    {
        url.text = "http://" + url.text;
    }

    this.open(url.text);
}

Almost done! Next we need to implement the button callbacks. Again browsing webkitwebview.h, we find reload(), go_forward(), and go_back() - the last functions needed to finish our browser! Remembering that the browser view is passed as 'this' to the functions, go ahead and fill them in:

function forward(button)
{
    this.go_forward();
}

function back(button)
{
    this.go_back();
}

function refresh(button)
{
    this.reload();
}

Our final modification will listen to a WebKit signal, and adjust the URL bar when you click a link. The aforementioned webkitwebview.h header file provides us with the name of the signal (load_committed). This signal is different than those we've worked with in the past, as it provides two arguments: the WebView, and a WebFrame. The distinction is important in WebKit-land, but we'll ignore it, noting only that (from the headers, again - this time, webkitwebframe.h) there is a WebFrame get_uri() function which provides the current URL of the frame. Let's first add our signal connection code. Make sure to connect to load_committed after you've created both the WebView and the URL entry field, as the signal is on the browser view, and we want to pass the URL entry field as its this object:

    browser.signal.load_committed.connect(url_changed, url_entry);

Next, the callback, url_changed. Remember that we're given two arguments, and that this is the URL entry field:

function url_changed(browser, frame)
{
    this.text = frame.get_uri();
}

If all goes well, your browser should now be in a working state, looking much like the following:

GTK Window with toolbar and browser view at GNOME.org

You will probably notice, at some point, that opening content in a new tab or new window doesn't work in your browser. This is, in fact, due to an open WebKit bug, #19130. Once this bug is fixed, the straightforward design of your browser will make it simple to add support for multiple windows.

The final version of the tutorial's source code is available if you're having trouble; if, however, you made easy work of the tutorial, you should consider making some improvements to your browser: change the window title when the web page title changes (look at the title_changed signal!); add tabs (GtkNotebook is probably what you're looking for); bookmarks are often useful!; perhaps a status menu? Or, go ahead and write your own application in Seed!