Welcome back to the latest news on GJS, the Javascript engine that powers GNOME Shell, Endless OS, Polari, GNOME Documents, and many other apps.
GNOME 3.24 has been released for about three weeks now, and with it went GJS 1.48.0. Here’s what’s new!
Javascript upgrade!
First of all, we have a more modern Javascript engine. GJS is based on Mozilla’s SpiderMonkey, the same Javascript engine that runs in the Firefox browser. Back in GNOME 3.22, GJS was based on version 24, which was released in September 2013. Now we’ve moved to version 38, which although still old, was released almost two years later in May 2015.
(The number of each SpiderMonkey release increases by 7 each time, because they make a standalone SpiderMonkey release for each Extended Support Release of Firefox, which is one out of every 7. That’s why you might also hear them referred to as “ESR 38”, etc.)
This brings a lot of new Javascript language features with it. Here are some of the ones I’m most excited about.
Promises
Promises allow you to do asynchronous operations (like reading files, or waiting, or fetching things from the network) in a much more intuitive way. With Promises, the code reads from top to bottom as if it were synchronous, instead of from nested level to nested level (often called “callback hell“.)
Here’s an example, a Promises version of examples/gio-cat.js that’s included in GJS’s source distribution:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const GLib = imports.gi.GLib; | |
const Gio = imports.gi.Gio; | |
function wrapPromise(obj, cancellable, asyncName, finishName, …inArgs) { | |
return new Promise((resolve, reject) => { | |
let callerStack = new Error().stack | |
.split('\n') | |
.filter(line => !line.match(/<Promise>|wrapPromise/)) | |
.join('\n'); | |
obj[asyncName](…inArgs, cancellable, (obj, res) => { | |
try { | |
let results = obj[finishName](res); | |
resolve(results); | |
} catch (e) { | |
e.stack += '— Called from: —\n' + callerStack; | |
reject(e); | |
} | |
}); | |
}); | |
} | |
function loadContents(file, cancellable=null) { | |
return wrapPromise(file, cancellable, 'load_contents_async', | |
'load_contents_finish'); | |
} | |
// ————————- | |
let loop = GLib.MainLoop.new(null, false); | |
function cat(filename) { | |
let f = Gio.File.new_for_commandline_arg(filename); | |
loadContents(f) | |
.then(([ok, contents]) => { | |
print(contents); | |
}) | |
.catch(e => { | |
logError(e); | |
}) | |
.then(() => loop.quit()); | |
loop.run(); | |
} | |
if (ARGV.length != 1) { | |
printerr("Usage: gio-cat.js filename"); | |
} else { | |
cat(ARGV[0]); | |
} |
This is much longer than the original program, but only the lower part of the program is actually the equivalent of the old callback-based code. The top part would ideally be provided by GJS itself. I’m still figuring out what is the best API for wrapPromise
but it’s definitely a candidate for including in a future version of GJS.
This code calls loadContents
, prints the contents, and exits the main loop. If an exception is thrown anywhere in the chain before .catch
, then the function provided to the catch
call will log the error message. In any case, no matter whether the operation succeeded or not, the last then
call will make sure the main loop exits.
Template literals
Template literals will change your life if you work with text in your GJS program. They are regular strings in backticks, with interpolation. Say goodbye to this:
const Format = imports.format; String.prototype.format = Format.format; log("%s, %s!".format(greeting, name));
Also say goodbye to this:
log(greeting + ", " + name + "!");
Instead, from now on you’ll do this:
log(`${greeting}, ${name}!`);
It’s a lot more readable and intuitive.
Template literals can also cover more than one line, and they do real interpolation of expressions too, not just variable names:
const CSS = ` label { font-size: ${fontdesc.get_size()}; }`;
You can also “tag” templates which is out of scope of this blog post, but there is one built-in tag which serves the same purpose as r''
string literals do in Python:
String.raw`I'm writing some \LaTeX\ code here and I \textbf{don't} want to deal with escaping it: \[ E = mc^2 \]`
Generators
Generators are a great addition to the Javascript toolbox. They were actually already available in GJS, but only in Mozilla’s nonstandard extension form. They are introduced with the function*
keyword instead of function
, and they work a lot like Python’s generators. Here’s an example, implementing the xrange()
function similar to the one in Python using a generator:
function* xrange(limit) { for(let count = 0; count < limit; count++) yield count; }
The yield
statement returns control back to the caller, while preserving the state of the generator until the next call. You can get all the values one by one, calling a generator’s next()
method, but for...of
loops will also deal with generators:
for (let ix of xrange(5)) print(`Counting from 0 to 4: ${ix}`);
If you want to empty a generator into an array, you can also use the spread operator: [...xrange(5)]
will give you an array of numbers from 0 to 4.
Here’s a more complicated example showing the yield*
statement which allows you to compose more than one generator:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const Gio = imports.gi.Gio; | |
function* leafnodes(file) { | |
let enumerator = file.enumerate_children('standard::*', 0, null); | |
let info; | |
while ((info = enumerator.next_file(null))) { | |
let child = enumerator.get_child(info); | |
if (info.get_file_type() === Gio.FileType.DIRECTORY) | |
yield* leafnodes(child); | |
else | |
yield child.get_basename(); | |
} | |
} | |
let file = Gio.File.new_for_commandline_arg(ARGV[0]); | |
for (let leaf of leafnodes(file)) | |
print(leaf); |
This code prints looks at the directory that it’s given, and prints all the files in it that are not themselves directories (the “leaf nodes”.) If one of the files is a directory, it will descend into that directory and repeat the process, thanks to yield*
.
Want to know more?
Since there’s a lot more than I can cover in a comfortably readable blog post, I made a slide deck. I tried to put it together in such a way that you can use it as reference material.
For more information on all of these cool things, I highly recommend this “ES6 Explained” series of posts from the Mozilla Hacks blog. Some of these features, such as classes and modules, are still to come in GJS.
Maintainer life
The Javascript engine upgrade was the major feature, but I also spent some time on making things easier for myself as the maintainer. A well-tended garden will hopefully attract more gardeners. Happily, some other people joined in for this part.
I cleaned up the build system, using more modern and concise Autotools code. I also spent some time cleaning up compiler warnings, both on GCC and Clang. Now the build and test runs are faster, and the cleaner output makes it much easier to see when something goes wrong. I also made sure that GJS builds on macOS, or at least it did until my Apple hardware broke down. Chun-wei Fan made some improvements that ensure GJS builds on Windows with MSVC. Claudio André implemented continuous integration in a Docker container, with the intention to run it on Travis CI, but sadly we do not have permission to flip the bit to get Travis to build it.
Having written Jasmine GJS in order to bring some of that convenient unit testing technology from the Node world into GJS applications, I also wanted to use it for writing GJS’s own unit tests. I couldn’t use it directly because that would have been a circular dependency, of course, but I embedded a copy of upstream Jasmine plus a very stripped-down version of Jasmine GJS, and called it “Minijasmine”. It’s now a lot easier, and dare I say less of a drag, to write unit tests for GJS. Accordingly, we’ll now try to cover every bug fix with a regression test.
And I worked on getting the bug tracker down to a less daunting number of bugs. It was fun to make the bug chart in my last post, so here’s another one: this is the number of open bugs during the release cycle from 1.46.0 to 1.48.0.
Unfortunately the chart will not look like this again next time around. The big drop was me closing all the obsolete or already-fixed bugs during November Bug Squash Month. We are down from about 160 to about 100 bugs, but those were all the easy ones; there are only hard ones left now.
Thanks
Thanks to everyone who participated to bring GJS to GNOME 3.24: Chun-wei Fan, Claudio André, Florian Müllner, Alexander Larsson, Iain Lane, Jonh Wendell, and Lionel Landwerlin.
As well, this release incorporated a lot of patches that people contributed a long time ago, even up to 8 years, that for various reasons had not been reviewed yet. (Many from emeritus GJS maintainers!) Thanks to those people for participating in the past, and I’m glad we were able to finally bring your contributions into the project: Giovanni Campagna, Jasper St. Pierre, Sam Spilsbury, Havoc Pennington, Joe Shaw, Paolo Borelli, Shawn Walker, and Tim Lunn.
Luke Jones and Hussam Al-Tayeb identified a serious memory leak right before the final 1.48.0 release and without their contribution, it would have been a different and much sadder story. As it was, 1.48.0 still contained another serious bug that made GNOME Shell quite unusable for an unlucky few people. Thanks to Georges Stavracas for rewriting a happy ending for 1.48.2.
Special thanks to Cosimo Cecchi, for reviewing almost every single line of the code I wrote for this release: about 20000 lines, many of them boring and repetitive.
Thanks also to my employer Endless which sponsored most of the Javascript engine upgrade, and a good chunk of miscellaneous bug fixing time.
Looking forward
My next post will be about what’s to come in GJS for GNOME 3.26.
Hi Philip,
thanks for all your hard work on GJS. I’m indeed impressed by how much this update improved the platform, and I’m definitely looking forward. Keep up the good work!
Pingback: GJS: What’s next? | The Mad Scientist Review