[seed] Add gjs compatibility layer to seed
- From: Alan Knowles <alank src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [seed] Add gjs compatibility layer to seed
- Date: Mon, 21 Dec 2015 02:47:13 +0000 (UTC)
commit caab25e4c8ae5a463eb256e3012c7e4f4d7be7bc
Author: Peter Rustler <peter rustler basyskom com>
Date: Thu Dec 17 14:33:31 2015 +0000
Add gjs compatibility layer to seed
This Adds the following:
* Add all js modules and extensions from gjs to the
seed codebase.
* Add a compile time flag to build with and without support.
* Add a runtime commandline to switch compatibility mode on and off.
* Loads the gjs modules first if commandline switch is given and do
not load seed modules if a gjs module is present.
Note:
Not all gjs modules are working because of missing native modules in seed.
There are: window, system, cairo and console.
They need to be ported.
The gjs modules are expected to start working when they are ported.
This patch will not change current behaviour of seed when
--seed-gjs-compatibility commandline is not given or when gjs support
is not build in.
Current seed code should still work.
configure.ac | 17 +
extensions/Makefile.am | 4 +
extensions/gjs/GLib.js | 285 +++++++++++++
extensions/gjs/GObject.js | 408 ++++++++++++++++++
extensions/gjs/Gio.js | 394 ++++++++++++++++++
extensions/gjs/Gtk.js | 113 +++++
extensions/gjs/Makefile.am | 4 +
libseed/Makefile.am | 1 +
libseed/seed-engine.c | 10 +
libseed/seed-importer.c | 97 +++--
libseed/seed-private.h | 4 +
modules/Makefile.am | 4 +
modules/gjs/Makefile.am | 8 +
modules/gjs/cairo.js | 147 +++++++
modules/gjs/coverage.js | 978 ++++++++++++++++++++++++++++++++++++++++++++
modules/gjs/format.js | 86 ++++
modules/gjs/gettext.js | 99 +++++
modules/gjs/jsUnit.js | 473 +++++++++++++++++++++
modules/gjs/lang.js | 488 ++++++++++++++++++++++
modules/gjs/mainloop.js | 87 ++++
modules/gjs/package.js | 247 +++++++++++
modules/gjs/signals.js | 146 +++++++
22 files changed, 4064 insertions(+), 36 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 3d5d482..7985583 100644
--- a/configure.ac
+++ b/configure.ac
@@ -446,6 +446,19 @@ AC_SUBST(SEED_DEBUG_CFLAGS)
AC_CHECK_HEADERS(pty.h)
+dnl =============================Gjs-Compatibility=============================
+AC_ARG_ENABLE(gjs-compatibility,
+ AC_HELP_STRING([--enable-gjs-compatibility],
+ [enable gjs compatibility. [default=no]]),
+ [enable_gjscompat="yes"],[enable_gjscompat="no"])
+
+if test "x$enable_gjscompat" = "xyes"; then
+ SEED_GJSCOMPAT_CFLAGS="-DSEED_ENABLE_GJSCOMPAT"
+fi
+
+AC_SUBST(SEED_GJSCOMPAT_CFLAGS)
+AM_CONDITIONAL(SEED_ENABLE_GJSCOMPAT, test "x$enable_gjscompat" = "xyes")
+
dnl =============================gtk-doc=======================================
GTK_DOC_CHECK(1.9)
@@ -507,6 +520,9 @@ modules/mpfr/Makefile
modules/ffi/Makefile
modules/DynamicObject/Makefile
+extensions/gjs/Makefile
+modules/gjs/Makefile
+
libseed/seed-path.h
])
AC_OUTPUT
@@ -517,6 +533,7 @@ Build Configuration:
Profiling/Coverage.........$enable_profile
Profiling for Modules......$enable_profile_modules
gtk-doc....................$enable_gtk_doc
+ Gjs compatiblity...........$enable_gjscompat
Installation:
Prefix.....................$prefix
diff --git a/extensions/Makefile.am b/extensions/Makefile.am
index e9f11bf..241c9d9 100644
--- a/extensions/Makefile.am
+++ b/extensions/Makefile.am
@@ -1,3 +1,7 @@
+if SEED_ENABLE_GJSCOMPAT
+SUBDIRS = gjs
+endif
+
EXTRA_DIST= GLib.js Gio.js Seed.js.in Gtk.js GObject.js Clutter.js Gst.js repl.js
extensiondir=$(datadir)/seed SEED_GTK_VERSION@/extensions
diff --git a/extensions/gjs/GLib.js b/extensions/gjs/GLib.js
new file mode 100644
index 0000000..f4161a2
--- /dev/null
+++ b/extensions/gjs/GLib.js
@@ -0,0 +1,285 @@
+// Copyright 2011 Giovanni Campagna
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+const ByteArray = imports.byteArray;
+
+let GLib;
+let originalVariantClass;
+
+const SIMPLE_TYPES = ['b', 'y', 'n', 'q', 'i', 'u', 'x', 't', 'h', 'd', 's', 'o', 'g'];
+
+function _read_single_type(signature, forceSimple) {
+ let char = signature.shift();
+ let isSimple = false;
+
+ if (SIMPLE_TYPES.indexOf(char) == -1) {
+ if (forceSimple)
+ throw new TypeError('Invalid GVariant signature (a simple type was expected)');
+ } else
+ isSimple = true;
+
+ if (char == 'm' || char == 'a')
+ return [char].concat(_read_single_type(signature, false));
+ if (char == '{') {
+ let key = _read_single_type(signature, true);
+ let val = _read_single_type(signature, false);
+ let close = signature.shift();
+ if (close != '}')
+ throw new TypeError('Invalid GVariant signature for type DICT_ENTRY (expected "}"');
+ return [char].concat(key, val, close);
+ }
+ if (char == '(') {
+ let res = [char];
+ while (true) {
+ if (signature.length == 0)
+ throw new TypeError('Invalid GVariant signature for type TUPLE (expected ")")');
+ let next = signature[0];
+ if (next == ')') {
+ signature.shift();
+ return res.concat(next);
+ }
+ let el = _read_single_type(signature);
+ res = res.concat(el);
+ }
+ }
+
+ // Valid types are simple types, arrays, maybes, tuples, dictionary entries and variants
+ if (!isSimple && char != 'v')
+ throw new TypeError('Invalid GVariant signature (' + char + ' is not a valid type)');
+
+ return [char];
+}
+
+function _makeBytes(byteArray) {
+ if (byteArray instanceof ByteArray.ByteArray)
+ return byteArray.toGBytes();
+ else
+ return new GLib.Bytes(byteArray);
+}
+
+function _pack_variant(signature, value) {
+ if (signature.length == 0)
+ throw new TypeError('GVariant signature cannot be empty');
+
+ let char = signature.shift();
+ switch (char) {
+ case 'b':
+ return GLib.Variant.new_boolean(value);
+ case 'y':
+ return GLib.Variant.new_byte(value);
+ case 'n':
+ return GLib.Variant.new_int16(value);
+ case 'q':
+ return GLib.Variant.new_uint16(value);
+ case 'i':
+ return GLib.Variant.new_int32(value);
+ case 'u':
+ return GLib.Variant.new_uint32(value);
+ case 'x':
+ return GLib.Variant.new_int64(value);
+ case 't':
+ return GLib.Variant.new_uint64(value);
+ case 'h':
+ return GLib.Variant.new_handle(value);
+ case 'd':
+ return GLib.Variant.new_double(value);
+ case 's':
+ return GLib.Variant.new_string(value);
+ case 'o':
+ return GLib.Variant.new_object_path(value);
+ case 'g':
+ return GLib.Variant.new_signature(value);
+ case 'v':
+ return GLib.Variant.new_variant(value);
+ case 'm':
+ if (value != null)
+ return GLib.Variant.new_maybe(null, _pack_variant(signature, value));
+ else
+ return GLib.Variant.new_maybe(new GLib.VariantType(_read_single_type(signature, false).join('')),
null);
+ case 'a':
+ let arrayType = _read_single_type(signature, false);
+ if (arrayType[0] == 's') {
+ // special case for array of strings
+ return GLib.Variant.new_strv(value);
+ }
+ if (arrayType[0] == 'y') {
+ // special case for array of bytes
+ return GLib.Variant.new_from_bytes(new GLib.VariantType('ay'),
+ _makeBytes(value), true);
+ }
+
+ let arrayValue = [];
+ if (arrayType[0] == '{') {
+ // special case for dictionaries
+ for (let key in value) {
+ let copy = [].concat(arrayType);
+ let child = _pack_variant(copy, [key, value[key]]);
+ arrayValue.push(child);
+ }
+ } else {
+ for (let i = 0; i < value.length; i++) {
+ let copy = [].concat(arrayType);
+ let child = _pack_variant(copy, value[i]);
+ arrayValue.push(child);
+ }
+ }
+ return GLib.Variant.new_array(new GLib.VariantType(arrayType.join('')), arrayValue);
+
+ case '(':
+ let children = [ ];
+ for (let i = 0; i < value.length; i++) {
+ let next = signature[0];
+ if (next == ')')
+ break;
+ children.push(_pack_variant(signature, value[i]));
+ }
+
+ if (signature[0] != ')')
+ throw new TypeError('Invalid GVariant signature for type TUPLE (expected ")")');
+ signature.shift();
+ return GLib.Variant.new_tuple(children);
+ case '{':
+ let key = _pack_variant(signature, value[0]);
+ let child = _pack_variant(signature, value[1]);
+
+ if (signature[0] != '}')
+ throw new TypeError('Invalid GVariant signature for type DICT_ENTRY (expected "}")');
+ signature.shift();
+
+ return GLib.Variant.new_dict_entry(key, child);
+ default:
+ throw new TypeError('Invalid GVariant signature (unexpected character ' + char + ')');
+ }
+}
+
+function _unpack_variant(variant, deep) {
+ switch (String.fromCharCode(variant.classify())) {
+ case 'b':
+ return variant.get_boolean();
+ case 'y':
+ return variant.get_byte();
+ case 'n':
+ return variant.get_int16();
+ case 'q':
+ return variant.get_uint16();
+ case 'i':
+ return variant.get_int32();
+ case 'u':
+ return variant.get_uint32();
+ case 'x':
+ return variant.get_int64();
+ case 't':
+ return variant.get_uint64();
+ case 'h':
+ return variant.get_handle();
+ case 'd':
+ return variant.get_double();
+ case 'o':
+ case 'g':
+ case 's':
+ // g_variant_get_string has length as out argument
+ return variant.get_string()[0];
+ case 'v':
+ return variant.get_variant();
+ case 'm':
+ let val = variant.get_maybe();
+ if (deep && val)
+ return _unpack_variant(val, deep);
+ else
+ return val;
+ case 'a':
+ if (variant.is_of_type(new GLib.VariantType('a{?*}'))) {
+ // special case containers
+ let ret = { };
+ let nElements = variant.n_children();
+ for (let i = 0; i < nElements; i++) {
+ // always unpack the dictionary entry, and always unpack
+ // the key (or it cannot be added as a key)
+ let val = _unpack_variant(variant.get_child_value(i), deep);
+ let key;
+ if (!deep)
+ key = _unpack_variant(val[0], true);
+ else
+ key = val[0];
+ ret[key] = val[1];
+ }
+ return ret;
+ }
+ if (variant.is_of_type(new GLib.VariantType('ay'))) {
+ // special case byte arrays
+ return variant.get_data_as_bytes().toArray();
+ }
+
+ // fall through
+ case '(':
+ case '{':
+ let ret = [ ];
+ let nElements = variant.n_children();
+ for (let i = 0; i < nElements; i++) {
+ let val = variant.get_child_value(i);
+ if (deep)
+ ret.push(_unpack_variant(val, deep));
+ else
+ ret.push(val);
+ }
+ return ret;
+ }
+
+ throw new Error('Assertion failure: this code should not be reached');
+}
+
+function _init() {
+ // this is imports.gi.GLib
+
+ GLib = this;
+
+ // small HACK: we add a matches() method to standard Errors so that
+ // you can do "catch(e if e.matches(Ns.FooError, Ns.FooError.SOME_CODE))"
+ // without checking instanceof
+ Error.prototype.matches = function() { return false; };
+
+ this.Variant._new_internal = function(sig, value) {
+ let signature = Array.prototype.slice.call(sig);
+
+ let variant = _pack_variant(signature, value);
+ if (signature.length != 0)
+ throw new TypeError('Invalid GVariant signature (more than one single complete type)');
+
+ return variant;
+ };
+
+ // Deprecate version of new GLib.Variant()
+ this.Variant.new = function(sig, value) {
+ return new GLib.Variant(sig, value);
+ };
+ this.Variant.prototype.unpack = function() {
+ return _unpack_variant(this, false);
+ };
+ this.Variant.prototype.deep_unpack = function() {
+ return _unpack_variant(this, true);
+ };
+ this.Variant.prototype.toString = function() {
+ return '[object variant of type "' + this.get_type_string() + '"]';
+ };
+
+ this.Bytes.prototype.toArray = function() {
+ return imports.byteArray.fromGBytes(this);
+ };
+}
diff --git a/extensions/gjs/GObject.js b/extensions/gjs/GObject.js
new file mode 100644
index 0000000..cc41485
--- /dev/null
+++ b/extensions/gjs/GObject.js
@@ -0,0 +1,408 @@
+// Copyright 2011 Jasper St. Pierre
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+const Lang = imports.lang;
+const Gi = imports._gi;
+const GjsPrivate = imports.gi.GjsPrivate;
+
+let GObject;
+
+// Some common functions between GObject.Class and GObject.Interface
+
+function _createSignals(gtype, signals) {
+ for (let signalName in signals) {
+ let obj = signals[signalName];
+ let flags = (obj.flags !== undefined) ? obj.flags : GObject.SignalFlags.RUN_FIRST;
+ let accumulator = (obj.accumulator !== undefined) ? obj.accumulator : GObject.AccumulatorType.NONE;
+ let rtype = (obj.return_type !== undefined) ? obj.return_type : GObject.TYPE_NONE;
+ let paramtypes = (obj.param_types !== undefined) ? obj.param_types : [];
+
+ try {
+ obj.signal_id = Gi.signal_new(gtype, signalName, flags, accumulator, rtype, paramtypes);
+ } catch (e) {
+ throw new TypeError('Invalid signal ' + signalName + ': ' + e.message);
+ }
+ }
+}
+
+function _createGTypeName(name, gtypename) {
+ if (gtypename)
+ return gtypename;
+ else
+ return 'Gjs_' + name;
+}
+
+function _getGObjectInterfaces(interfaces) {
+ return interfaces.filter((iface) => iface.hasOwnProperty('$gtype'));
+}
+
+function _propertiesAsArray(propertyObj) {
+ let propertiesArray = [];
+ if (propertyObj) {
+ for (let prop in propertyObj) {
+ propertiesArray.push(propertyObj[prop]);
+ }
+ }
+ return propertiesArray;
+}
+
+const GObjectMeta = new Lang.Class({
+ Name: 'GObjectClass',
+ Extends: Lang.Class,
+
+ _init: function (params) {
+ // retrieve signals and remove them from params before chaining
+ let signals = params.Signals;
+ delete params.Signals;
+
+ this.parent(params);
+
+ if (signals)
+ _createSignals(this.$gtype, signals);
+
+ let propertyObj = { };
+ Object.getOwnPropertyNames(params).forEach(function(name) {
+ if (name == 'Name' || name == 'Extends' || name == 'Abstract')
+ return;
+
+ let descriptor = Object.getOwnPropertyDescriptor(params, name);
+
+ if (typeof descriptor.value === 'function') {
+ let wrapped = this.prototype[name];
+
+ if (name.slice(0, 6) == 'vfunc_') {
+ Gi.hook_up_vfunc(this.prototype, name.slice(6), wrapped);
+ } else if (name.slice(0, 3) == 'on_') {
+ let id = GObject.signal_lookup(name.slice(3).replace('_', '-'), this.$gtype);
+ if (id != 0) {
+ GObject.signal_override_class_closure(id, this.$gtype, function() {
+ let argArray = Array.prototype.slice.call(arguments);
+ let emitter = argArray.shift();
+
+ return wrapped.apply(emitter, argArray);
+ });
+ }
+ }
+ }
+ }.bind(this));
+ },
+
+ _isValidClass: function(klass) {
+ let proto = klass.prototype;
+
+ if (!proto)
+ return false;
+
+ // If proto == GObject.Object.prototype, then
+ // proto.__proto__ is Object, so "proto instanceof GObject.Object"
+ // will return false.
+ return proto == GObject.Object.prototype ||
+ proto instanceof GObject.Object;
+ },
+
+ // If we want an object with a custom JSClass, we can't just
+ // use a function. We have to use a custom constructor here.
+ _construct: function(params) {
+ if (!params.Name)
+ throw new TypeError("Classes require an explicit 'Name' parameter.");
+ let name = params.Name;
+
+ let gtypename = _createGTypeName(params.Name, params.GTypeName);
+
+ if (!params.Extends)
+ params.Extends = GObject.Object;
+ let parent = params.Extends;
+
+ if (!this._isValidClass(parent))
+ throw new TypeError('GObject.Class used with invalid base class (is ' + parent + ')');
+
+ let interfaces = params.Implements || [];
+ if (parent instanceof Lang.Class)
+ interfaces = interfaces.filter((iface) => !parent.implements(iface));
+ let gobjectInterfaces = _getGObjectInterfaces(interfaces);
+
+ let propertiesArray = _propertiesAsArray(params.Properties);
+ delete params.Properties;
+
+ let newClass = Gi.register_type(parent.prototype, gtypename,
+ gobjectInterfaces, propertiesArray);
+
+ // See Class.prototype._construct in lang.js for the reasoning
+ // behind this direct __proto__ set.
+ newClass.__proto__ = this.constructor.prototype;
+ newClass.__super__ = parent;
+
+ newClass._init.apply(newClass, arguments);
+
+ Object.defineProperties(newClass.prototype, {
+ '__metaclass__': { writable: false,
+ configurable: false,
+ enumerable: false,
+ value: this.constructor },
+ '__interfaces__': { writable: false,
+ configurable: false,
+ enumerable: false,
+ value: interfaces }
+ });
+
+ interfaces.forEach((iface) => {
+ if (iface instanceof Lang.Interface)
+ iface._check(newClass.prototype);
+ });
+
+ return newClass;
+ },
+
+ // Overrides Lang.Class.implements()
+ implements: function (iface) {
+ if (iface instanceof GObject.Interface) {
+ return GObject.type_is_a(this.$gtype, iface.$gtype);
+ } else {
+ return this.parent(iface);
+ }
+ }
+});
+
+function GObjectInterface(params) {
+ return this._construct.apply(this, arguments);
+}
+
+GObjectMeta.MetaInterface = GObjectInterface;
+
+GObjectInterface.__super__ = Lang.Interface;
+GObjectInterface.prototype = Object.create(Lang.Interface.prototype);
+GObjectInterface.prototype.constructor = GObjectInterface;
+GObjectInterface.prototype.__name__ = 'GObjectInterface';
+
+GObjectInterface.prototype._construct = function (params) {
+ if (!params.Name) {
+ throw new TypeError("Interfaces require an explicit 'Name' parameter.");
+ }
+
+ let gtypename = _createGTypeName(params.Name, params.GTypeName);
+ delete params.GTypeName;
+
+ let interfaces = params.Requires || [];
+ let gobjectInterfaces = _getGObjectInterfaces(interfaces);
+
+ let properties = _propertiesAsArray(params.Properties);
+ delete params.Properties;
+
+ let newInterface = Gi.register_interface(gtypename, gobjectInterfaces,
+ properties);
+
+ // See Class.prototype._construct in lang.js for the reasoning
+ // behind this direct __proto__ set.
+ newInterface.__proto__ = this.constructor.prototype;
+ newInterface.__super__ = GObjectInterface;
+ newInterface.prototype.constructor = newInterface;
+
+ newInterface._init.apply(newInterface, arguments);
+
+ Object.defineProperty(newInterface.prototype, '__metaclass__', {
+ writable: false,
+ configurable: false,
+ enumerable: false,
+ value: this.constructor
+ });
+
+ return newInterface;
+};
+
+GObjectInterface.prototype._init = function (params) {
+ let signals = params.Signals;
+ delete params.Signals;
+
+ Lang.Interface.prototype._init.call(this, params);
+
+ _createSignals(this.$gtype, signals);
+};
+
+function _init() {
+
+ GObject = this;
+
+ function _makeDummyClass(obj, name, upperName, gtypeName, actual) {
+ let gtype = GObject.type_from_name(gtypeName);
+ obj['TYPE_' + upperName] = gtype;
+ obj[name] = function(v) { return new actual(v); };
+ obj[name].$gtype = gtype;
+ }
+
+ _makeDummyClass(this, 'VoidType', 'NONE', 'void', function() {});
+ _makeDummyClass(this, 'Char', 'CHAR', 'gchar', Number);
+ _makeDummyClass(this, 'UChar', 'UCHAR', 'guchar', Number);
+ _makeDummyClass(this, 'Unichar', 'UNICHAR', 'gint', String);
+
+ this.TYPE_BOOLEAN = GObject.type_from_name('gboolean');
+ this.Boolean = Boolean;
+ Boolean.$gtype = this.TYPE_BOOLEAN;
+
+ _makeDummyClass(this, 'Int', 'INT', 'gint', Number);
+ _makeDummyClass(this, 'UInt', 'UINT', 'guint', Number);
+ _makeDummyClass(this, 'Long', 'LONG', 'glong', Number);
+ _makeDummyClass(this, 'ULong', 'ULONG', 'gulong', Number);
+ _makeDummyClass(this, 'Int64', 'INT64', 'gint64', Number);
+ _makeDummyClass(this, 'UInt64', 'UINT64', 'guint64', Number);
+
+ this.TYPE_ENUM = GObject.type_from_name('GEnum');
+ this.TYPE_FLAGS = GObject.type_from_name('GFlags');
+
+ _makeDummyClass(this, 'Float', 'FLOAT', 'gfloat', Number);
+ this.TYPE_DOUBLE = GObject.type_from_name('gdouble');
+ this.Double = Number;
+ Number.$gtype = this.TYPE_DOUBLE;
+
+ this.TYPE_STRING = GObject.type_from_name('gchararray');
+ this.String = String;
+ String.$gtype = this.TYPE_STRING;
+
+ this.TYPE_POINTER = GObject.type_from_name('gpointer');
+ this.TYPE_BOXED = GObject.type_from_name('GBoxed');
+ this.TYPE_PARAM = GObject.type_from_name('GParam');
+ this.TYPE_INTERFACE = GObject.type_from_name('GInterface');
+ this.TYPE_OBJECT = GObject.type_from_name('GObject');
+ this.TYPE_VARIANT = GObject.type_from_name('GVariant');
+
+ _makeDummyClass(this, 'Type', 'GTYPE', 'GType', GObject.type_from_name);
+
+ this.ParamSpec.char = function(name, nick, blurb, flags, minimum, maximum, default_value) {
+ return GObject.param_spec_char(name, nick, blurb, minimum, maximum, default_value, flags);
+ };
+
+ this.ParamSpec.uchar = function(name, nick, blurb, flags, minimum, maximum, default_value) {
+ return GObject.param_spec_uchar(name, nick, blurb, minimum, maximum, default_value, flags);
+ };
+
+ this.ParamSpec.int = function(name, nick, blurb, flags, minimum, maximum, default_value) {
+ return GObject.param_spec_int(name, nick, blurb, minimum, maximum, default_value, flags);
+ };
+
+ this.ParamSpec.uint = function(name, nick, blurb, flags, minimum, maximum, default_value) {
+ return GObject.param_spec_uint(name, nick, blurb, minimum, maximum, default_value, flags);
+ };
+
+ this.ParamSpec.long = function(name, nick, blurb, flags, minimum, maximum, default_value) {
+ return GObject.param_spec_long(name, nick, blurb, minimum, maximum, default_value, flags);
+ };
+
+ this.ParamSpec.ulong = function(name, nick, blurb, flags, minimum, maximum, default_value) {
+ return GObject.param_spec_ulong(name, nick, blurb, minimum, maximum, default_value, flags);
+ };
+
+ this.ParamSpec.int64 = function(name, nick, blurb, flags, minimum, maximum, default_value) {
+ return GObject.param_spec_int64(name, nick, blurb, minimum, maximum, default_value, flags);
+ };
+
+ this.ParamSpec.uint64 = function(name, nick, blurb, flags, minimum, maximum, default_value) {
+ return GObject.param_spec_uint64(name, nick, blurb, minimum, maximum, default_value, flags);
+ };
+
+ this.ParamSpec.float = function(name, nick, blurb, flags, minimum, maximum, default_value) {
+ return GObject.param_spec_float(name, nick, blurb, minimum, maximum, default_value, flags);
+ };
+
+ this.ParamSpec.boolean = function(name, nick, blurb, flags, default_value) {
+ return GObject.param_spec_boolean(name, nick, blurb, default_value, flags);
+ };
+
+ this.ParamSpec.flags = function(name, nick, blurb, flags, flags_type, default_value) {
+ return GObject.param_spec_flags(name, nick, blurb, flags_type, default_value, flags);
+ };
+
+ this.ParamSpec.enum = function(name, nick, blurb, flags, enum_type, default_value) {
+ return GObject.param_spec_enum(name, nick, blurb, enum_type, default_value, flags);
+ };
+
+ this.ParamSpec.double = function(name, nick, blurb, flags, minimum, maximum, default_value) {
+ return GObject.param_spec_double(name, nick, blurb, minimum, maximum, default_value, flags);
+ };
+
+ this.ParamSpec.string = function(name, nick, blurb, flags, default_value) {
+ return GObject.param_spec_string(name, nick, blurb, default_value, flags);
+ };
+
+ this.ParamSpec.boxed = function(name, nick, blurb, flags, boxed_type) {
+ return GObject.param_spec_boxed(name, nick, blurb, boxed_type, flags);
+ };
+
+ this.ParamSpec.object = function(name, nick, blurb, flags, object_type) {
+ return GObject.param_spec_object(name, nick, blurb, object_type, flags);
+ };
+
+ this.ParamSpec.param = function(name, nick, blurb, flags, param_type) {
+ return GObject.param_spec_param(name, nick, blurb, param_type, flags);
+ };
+
+ this.ParamSpec.override = Gi.override_property;
+
+ Object.defineProperties(this.ParamSpec.prototype, {
+ 'name': { configurable: false,
+ enumerable: false,
+ get: function() { return this.get_name() } },
+ '_nick': { configurable: false,
+ enumerable: false,
+ get: function() { return this.get_nick() } },
+ 'nick': { configurable: false,
+ enumerable: false,
+ get: function() { return this.get_nick() } },
+ '_blurb': { configurable: false,
+ enumerable: false,
+ get: function() { return this.get_blurb() } },
+ 'blurb': { configurable: false,
+ enumerable: false,
+ get: function() { return this.get_blurb() } },
+ 'default_value': { configurable: false,
+ enumerable: false,
+ get: function() { return this.get_default_value() } },
+ 'flags': { configurable: false,
+ enumerable: false,
+ get: function() { return GjsPrivate.param_spec_get_flags(this) } },
+ 'value_type': { configurable: false,
+ enumerable: false,
+ get: function() { return GjsPrivate.param_spec_get_value_type(this) } },
+ 'owner_type': { configurable: false,
+ enumerable: false,
+ get: function() { return GjsPrivate.param_spec_get_owner_type(this) } },
+ });
+
+
+ this.Class = GObjectMeta;
+ this.Interface = GObjectInterface;
+ this.Object.prototype.__metaclass__ = this.Class;
+
+ // For compatibility with Lang.Class... we need a _construct
+ // or the Lang.Class constructor will fail.
+ this.Object.prototype._construct = function() {
+ this._init.apply(this, arguments);
+ return this;
+ };
+
+ // fake enum for signal accumulators, keep in sync with gi/object.c
+ this.AccumulatorType = {
+ NONE: 0,
+ FIRST_WINS: 1,
+ TRUE_HANDLED: 2
+ };
+
+ this.Object.prototype.disconnect = function(id) {
+ return GObject.signal_handler_disconnect(this, id);
+ };
+}
diff --git a/extensions/gjs/Gio.js b/extensions/gjs/Gio.js
new file mode 100644
index 0000000..950641b
--- /dev/null
+++ b/extensions/gjs/Gio.js
@@ -0,0 +1,394 @@
+// Copyright 2011 Giovanni Campagna
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+var GLib = imports.gi.GLib;
+var GObject = imports.gi.GObject;
+var GjsPrivate = imports.gi.GjsPrivate;
+var Lang = imports.lang;
+var Signals = imports.signals;
+var Gio;
+
+function _signatureLength(sig) {
+ var counter = 0;
+ // make it an array
+ var signature = Array.prototype.slice.call(sig);
+ while (signature.length) {
+ GLib._read_single_type(sig);
+ counter++;
+ }
+ return counter;
+}
+
+function _proxyInvoker(methodName, sync, inSignature, arg_array) {
+ var replyFunc;
+ var flags = 0;
+ var cancellable = null;
+
+ /* Convert arg_array to a *real* array */
+ arg_array = Array.prototype.slice.call(arg_array);
+
+ /* The default replyFunc only logs the responses */
+ replyFunc = _logReply;
+
+ var signatureLength = inSignature.length;
+ var minNumberArgs = signatureLength;
+ var maxNumberArgs = signatureLength + 3;
+
+ if (arg_array.length < minNumberArgs) {
+ throw new Error("Not enough arguments passed for method: " + methodName +
+ ". Expected " + minNumberArgs + ", got " + arg_array.length);
+ } else if (arg_array.length > maxNumberArgs) {
+ throw new Error("Too many arguments passed for method: " + methodName +
+ ". Maximum is " + maxNumberArgs +
+ " + one callback and/or flags");
+ }
+
+ while (arg_array.length > signatureLength) {
+ var argNum = arg_array.length - 1;
+ var arg = arg_array.pop();
+ if (typeof(arg) == "function" && !sync) {
+ replyFunc = arg;
+ } else if (typeof(arg) == "number") {
+ flags = arg;
+ } else if (arg instanceof Gio.Cancellable) {
+ cancellable = arg;
+ } else {
+ throw new Error("Argument " + argNum + " of method " + methodName +
+ " is " + typeof(arg) + ". It should be a callback, flags or a Gio.Cancellable");
+ }
+ }
+
+ var inVariant = new GLib.Variant('(' + inSignature.join('') + ')', arg_array);
+
+ var asyncCallback = function (proxy, result) {
+ var outVariant = null, succeeded = false;
+ try {
+ outVariant = proxy.call_finish(result);
+ succeeded = true;
+ } catch (e) {
+ replyFunc(null, e);
+ }
+
+ if (succeeded)
+ replyFunc(outVariant.deep_unpack(), null);
+ };
+
+ if (sync) {
+ return this.call_sync(methodName,
+ inVariant,
+ flags,
+ -1,
+ cancellable).deep_unpack();
+ } else {
+ return this.call(methodName,
+ inVariant,
+ flags,
+ -1,
+ cancellable,
+ asyncCallback);
+ }
+}
+
+function _logReply(result, exc) {
+ if (exc != null) {
+ log("Ignored exception from dbus method: " + exc.toString());
+ }
+}
+
+function _makeProxyMethod(method, sync) {
+ var i;
+ var name = method.name;
+ var inArgs = method.in_args;
+ var inSignature = [ ];
+ for (i = 0; i < inArgs.length; i++)
+ inSignature.push(inArgs[i].signature);
+
+ return function() {
+ return _proxyInvoker.call(this, name, sync, inSignature, arguments);
+ };
+}
+
+function _convertToNativeSignal(proxy, sender_name, signal_name, parameters) {
+ Signals._emit.call(proxy, signal_name, sender_name, parameters.deep_unpack());
+}
+
+function _propertyGetter(name) {
+ let value = this.get_cached_property(name);
+ return value ? value.deep_unpack() : null;
+}
+
+function _propertySetter(value, name, signature) {
+ let variant = new GLib.Variant(signature, value);
+ this.set_cached_property(name, variant);
+
+ this.call('org.freedesktop.DBus.Properties.Set',
+ new GLib.Variant('(ssv)',
+ [this.g_interface_name,
+ name, variant]),
+ Gio.DBusCallFlags.NONE, -1, null,
+ Lang.bind(this, function(proxy, result) {
+ try {
+ this.call_finish(result);
+ } catch(e) {
+ log('Could not set property ' + name + ' on remote object ' +
+ this.g_object_path + ': ' + e.message);
+ }
+ }));
+}
+
+function _addDBusConvenience() {
+ let info = this.g_interface_info;
+ if (!info)
+ return;
+
+ if (info.signals.length > 0)
+ this.connect('g-signal', _convertToNativeSignal);
+
+ let i, methods = info.methods;
+ for (i = 0; i < methods.length; i++) {
+ var method = methods[i];
+ this[method.name + 'Remote'] = _makeProxyMethod(methods[i], false);
+ this[method.name + 'Sync'] = _makeProxyMethod(methods[i], true);
+ }
+
+ let properties = info.properties;
+ for (i = 0; i < properties.length; i++) {
+ let name = properties[i].name;
+ let signature = properties[i].signature;
+ Object.defineProperty(this, name, { get: Lang.bind(this, _propertyGetter, name),
+ set: Lang.bind(this, _propertySetter, name, signature),
+ configurable: true,
+ enumerable: true });
+ }
+}
+
+function _makeProxyWrapper(interfaceXml) {
+ var info = _newInterfaceInfo(interfaceXml);
+ var iname = info.name;
+ return function(bus, name, object, asyncCallback, cancellable) {
+ var obj = new Gio.DBusProxy({ g_connection: bus,
+ g_interface_name: iname,
+ g_interface_info: info,
+ g_name: name,
+ g_object_path: object });
+ if (!cancellable)
+ cancellable = null;
+ if (asyncCallback)
+ obj.init_async(GLib.PRIORITY_DEFAULT, cancellable, function(initable, result) {
+ let caughtErrorWhenInitting = null;
+ try {
+ initable.init_finish(result);
+ } catch(e) {
+ caughtErrorWhenInitting = e;
+ }
+
+ if (caughtErrorWhenInitting === null) {
+ asyncCallback(initable, null);
+ } else {
+ asyncCallback(null, caughtErrorWhenInitting);
+ }
+ });
+ else
+ obj.init(cancellable);
+ return obj;
+ };
+}
+
+
+function _newNodeInfo(constructor, value) {
+ if (typeof value == 'string')
+ return constructor(value);
+ else if (value instanceof XML)
+ return constructor(value.toXMLString());
+ else
+ throw TypeError('Invalid type ' + Object.prototype.toString.call(value));
+}
+
+function _newInterfaceInfo(value) {
+ var nodeInfo = Gio.DBusNodeInfo.new_for_xml(value);
+ return nodeInfo.interfaces[0];
+}
+
+function _injectToMethod(klass, method, addition) {
+ var previous = klass[method];
+
+ klass[method] = function() {
+ addition.apply(this, arguments);
+ return previous.apply(this, arguments);
+ };
+}
+
+function _wrapFunction(klass, method, addition) {
+ var previous = klass[method];
+
+ klass[method] = function() {
+ var args = Array.prototype.slice.call(arguments);
+ args.unshift(previous);
+ return addition.apply(this, args);
+ };
+}
+
+function _makeOutSignature(args) {
+ var ret = '(';
+ for (var i = 0; i < args.length; i++)
+ ret += args[i].signature;
+
+ return ret + ')';
+}
+
+function _handleMethodCall(info, impl, method_name, parameters, invocation) {
+ // prefer a sync version if available
+ if (this[method_name]) {
+ let retval;
+ try {
+ retval = this[method_name].apply(this, parameters.deep_unpack());
+ } catch (e) {
+ if (e instanceof GLib.Error) {
+ invocation.return_gerror(e);
+ } else {
+ let name = e.name;
+ if (name.indexOf('.') == -1) {
+ // likely to be a normal JS error
+ name = 'org.gnome.gjs.JSError.' + name;
+ }
+ logError(e, "Exception in method call: " + method_name);
+ invocation.return_dbus_error(name, e.message);
+ }
+ return;
+ }
+ if (retval === undefined) {
+ // undefined (no return value) is the empty tuple
+ retval = new GLib.Variant('()', []);
+ }
+ try {
+ if (!(retval instanceof GLib.Variant)) {
+ // attempt packing according to out signature
+ let methodInfo = info.lookup_method(method_name);
+ let outArgs = methodInfo.out_args;
+ let outSignature = _makeOutSignature(outArgs);
+ if (outArgs.length == 1) {
+ // if one arg, we don't require the handler wrapping it
+ // into an Array
+ retval = [retval];
+ }
+ retval = new GLib.Variant(outSignature, retval);
+ }
+ invocation.return_value(retval);
+ } catch(e) {
+ // if we don't do this, the other side will never see a reply
+ invocation.return_dbus_error('org.gnome.gjs.JSError.ValueError',
+ "Service implementation returned an incorrect value type");
+ }
+ } else if (this[method_name + 'Async']) {
+ this[method_name + 'Async'](parameters.deep_unpack(), invocation);
+ } else {
+ log('Missing handler for DBus method ' + method_name);
+ invocation.return_gerror(new Gio.DBusError({ code: Gio.DBusError.UNKNOWN_METHOD,
+ message: 'Method ' + method_name + ' is not
implemented' }));
+ }
+}
+
+function _handlePropertyGet(info, impl, property_name) {
+ let propInfo = info.lookup_property(property_name);
+ let jsval = this[property_name];
+ if (jsval != undefined)
+ return new GLib.Variant(propInfo.signature, jsval);
+ else
+ return null;
+}
+
+function _handlePropertySet(info, impl, property_name, new_value) {
+ this[property_name] = new_value.deep_unpack();
+}
+
+function _wrapJSObject(interfaceInfo, jsObj) {
+ var info;
+ if (interfaceInfo instanceof Gio.DBusInterfaceInfo)
+ info = interfaceInfo;
+ else
+ info = Gio.DBusInterfaceInfo.new_for_xml(interfaceInfo);
+ info.cache_build();
+
+ var impl = new GjsPrivate.DBusImplementation({ g_interface_info: info });
+ impl.connect('handle-method-call', function(impl, method_name, parameters, invocation) {
+ return _handleMethodCall.call(jsObj, info, impl, method_name, parameters, invocation);
+ });
+ impl.connect('handle-property-get', function(impl, property_name) {
+ return _handlePropertyGet.call(jsObj, info, impl, property_name);
+ });
+ impl.connect('handle-property-set', function(impl, property_name, value) {
+ return _handlePropertySet.call(jsObj, info, impl, property_name, value);
+ });
+
+ return impl;
+}
+
+function _init() {
+ Gio = this;
+
+ Gio.DBus = {
+ get session() {
+ return Gio.bus_get_sync(Gio.BusType.SESSION, null);
+ },
+ get system() {
+ return Gio.bus_get_sync(Gio.BusType.SYSTEM, null);
+ },
+
+ // Namespace some functions
+ get: Gio.bus_get,
+ get_finish: Gio.bus_get_finish,
+ get_sync: Gio.bus_get_sync,
+
+ own_name: Gio.bus_own_name,
+ own_name_on_connection: Gio.bus_own_name_on_connection,
+ unown_name: Gio.bus_unown_name,
+
+ watch_name: Gio.bus_watch_name,
+ watch_name_on_connection: Gio.bus_watch_name_on_connection,
+ unwatch_name: Gio.bus_unwatch_name
+ };
+
+ Gio.DBusConnection.prototype.watch_name = function(name, flags, appeared, vanished) {
+ return Gio.bus_watch_name_on_connection(this, name, flags, appeared, vanished);
+ };
+ Gio.DBusConnection.prototype.unwatch_name = function(id) {
+ return Gio.bus_unwatch_name(id);
+ };
+ Gio.DBusConnection.prototype.own_name = function(name, flags, acquired, lost) {
+ return Gio.bus_own_name_on_connection(this, name, flags, acquired, lost);
+ };
+ Gio.DBusConnection.prototype.unown_name = function(id) {
+ return Gio.bus_unown_name(id);
+ };
+
+ _injectToMethod(Gio.DBusProxy.prototype, 'init', _addDBusConvenience);
+ _injectToMethod(Gio.DBusProxy.prototype, 'init_async', _addDBusConvenience);
+ Gio.DBusProxy.prototype.connectSignal = Signals._connect;
+ Gio.DBusProxy.prototype.disconnectSignal = Signals._disconnect;
+
+ Gio.DBusProxy.makeProxyWrapper = _makeProxyWrapper;
+
+ // Some helpers
+ _wrapFunction(Gio.DBusNodeInfo, 'new_for_xml', _newNodeInfo);
+ Gio.DBusInterfaceInfo.new_for_xml = _newInterfaceInfo;
+
+ Gio.DBusExportedObject = GjsPrivate.DBusImplementation;
+ Gio.DBusExportedObject.wrapJSObject = _wrapJSObject;
+}
diff --git a/extensions/gjs/Gtk.js b/extensions/gjs/Gtk.js
new file mode 100644
index 0000000..8a23977
--- /dev/null
+++ b/extensions/gjs/Gtk.js
@@ -0,0 +1,113 @@
+// application/javascript;version=1.8
+// Copyright 2013 Giovanni Campagna
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+const Lang = imports.lang;
+const GObject = imports.gi.GObject;
+
+var GjsPrivate = imports.gi.GjsPrivate;
+
+let Gtk;
+
+const GtkWidgetClass = new Lang.Class({
+ Name: 'GtkWidgetClass',
+ Extends: GObject.Class,
+
+ _init: function(params) {
+ let template = params.Template;
+ delete params.Template;
+
+ let children = params.Children;
+ delete params.Children;
+
+ let internalChildren = params.InternalChildren;
+ delete params.InternalChildren;
+
+ if (template) {
+ params._instance_init = function() {
+ this.init_template();
+ };
+ }
+
+ this.parent(params);
+
+ if (template) {
+ if (typeof template == 'string' &&
+ template.startsWith('resource:///'))
+ Gtk.Widget.set_template_from_resource.call(this, template.slice(11));
+ else
+ Gtk.Widget.set_template.call(this, template);
+ }
+
+ this.Template = template;
+ this.Children = children;
+ this.InternalChildren = internalChildren;
+
+ if (children) {
+ for (let i = 0; i < children.length; i++)
+ Gtk.Widget.bind_template_child_full.call(this, children[i], false, 0);
+ }
+
+ if (internalChildren) {
+ for (let i = 0; i < internalChildren.length; i++)
+ Gtk.Widget.bind_template_child_full.call(this, internalChildren[i], true, 0);
+ }
+ },
+
+ _isValidClass: function(klass) {
+ let proto = klass.prototype;
+
+ if (!proto)
+ return false;
+
+ // If proto == Gtk.Widget.prototype, then
+ // proto.__proto__ is GObject.InitiallyUnowned, so
+ // "proto instanceof Gtk.Widget"
+ // will return false.
+ return proto == Gtk.Widget.prototype ||
+ proto instanceof Gtk.Widget;
+ },
+});
+
+function _init() {
+
+ Gtk = this;
+
+ Gtk.Widget.prototype.__metaclass__ = GtkWidgetClass;
+ if (GjsPrivate.gtk_container_child_set_property) {
+ Gtk.Container.prototype.child_set_property = function(child, property, value) {
+ GjsPrivate.gtk_container_child_set_property(this, child, property, value);
+ };
+ }
+
+ Gtk.Widget.prototype._init = function(params) {
+ GObject.Object.prototype._init.call(this, params);
+
+ if (this.constructor.Template) {
+ let children = this.constructor.Children || [];
+ for (let child of children)
+ this[child.replace('-', '_', 'g')] = this.get_template_child(this.constructor, child);
+
+ let internalChildren = this.constructor.InternalChildren || [];
+ for (let child of internalChildren)
+ this['_' + child.replace('-', '_', 'g')] = this.get_template_child(this.constructor, child);
+ }
+ };
+}
diff --git a/extensions/gjs/Makefile.am b/extensions/gjs/Makefile.am
new file mode 100644
index 0000000..ab5906f
--- /dev/null
+++ b/extensions/gjs/Makefile.am
@@ -0,0 +1,4 @@
+EXTRA_DIST= Gio.js GLib.js GObject.js Gtk.js
+
+gjs_overridesdir=$(datadir)/seed SEED_GTK_VERSION@/extensions/gjs
+gjs_overrides_DATA = Gio.js GLib.js GObject.js Gtk.js
diff --git a/libseed/Makefile.am b/libseed/Makefile.am
index d46cc56..4afeffe 100644
--- a/libseed/Makefile.am
+++ b/libseed/Makefile.am
@@ -31,6 +31,7 @@ libseed SEED_GTK_VERSION@_la_CFLAGS = \
$(WEBKIT_CFLAGS) \
$(SEED_OSX_CFLAGS) \
$(SEED_DEBUG_CFLAGS) \
+ $(SEED_GJSCOMPAT_CFLAGS) \
$(SEED_PROFILE_CFLAGS) \
$(FFI_CFLAGS) \
-DGOBJECT_INTROSPECTION_VERSION=$(GOBJECT_INTROSPECTION_VERSION)
diff --git a/libseed/seed-engine.c b/libseed/seed-engine.c
index 35be98f..1341551 100644
--- a/libseed/seed-engine.c
+++ b/libseed/seed-engine.c
@@ -53,6 +53,9 @@ GQuark js_ref_quark;
guint seed_debug_flags = 0; /* global seed debug flag */
gboolean seed_arg_print_version = FALSE; // Flag to print version and quit
+#ifdef SEED_ENABLE_GJSCOMPAT
+gboolean seed_arg_gjs_compatiblity = FALSE;
+#endif
pthread_key_t seed_next_gobject_wrapper_key;
@@ -106,6 +109,9 @@ seed_prepare_global_context (JSContextRef ctx)
seed_object_set_property (ctx, global, "imports", importer);
seed_object_set_property (ctx, global, "GType", seed_gtype_constructor);
seed_object_set_property (ctx, global, "Seed", seed_obj_ref);
+#ifdef SEED_ENABLE_GJSCOMPAT
+ seed_object_set_property (ctx, global, "ARGV", ARGV_obj_ref);
+#endif
seed_object_set_property (ctx, global, "print", seed_print_ref);
seed_object_set_property (ctx, global, "printerr", seed_printerr_ref);
@@ -1659,6 +1665,10 @@ static GOptionEntry seed_args[] = {
{"seed-no-debug", 0, 0, G_OPTION_ARG_CALLBACK, seed_arg_no_debug_cb,
"Disable Seed debugging", "FLAGS"},
#endif /* SEED_ENABLE_DEBUG */
+#ifdef SEED_ENABLE_GJSCOMPAT
+ {"seed-gjs-compatibility", 0, 0, G_OPTION_ARG_NONE, &seed_arg_gjs_compatiblity,
+ "Enable gjs compatibility", 0},
+#endif /* SEED_ENABLE_GJSCOMPAT */
{"seed-version", 0, 0, G_OPTION_ARG_NONE, &seed_arg_print_version,
"Print libseed version", 0},
{NULL,},
diff --git a/libseed/seed-importer.c b/libseed/seed-importer.c
index 380e18f..ae75d1f 100644
--- a/libseed/seed-importer.c
+++ b/libseed/seed-importer.c
@@ -811,6 +811,46 @@ seed_importer_handle_file (JSContextRef ctx,
return global;
}
+static JSObjectRef seed_importer_try_load (JSContextRef ctx,
+ const gchar *test_path,
+ const gchar *script_path,
+ const gchar *prop,
+ const gchar *prop_as_js,
+ const gchar *prop_as_lib,
+ JSValueRef *exception)
+{
+ gchar *file_path = NULL;
+ JSObjectRef ret = NULL;
+ // replace '.' with current script_path if not null
+ if(script_path && !g_strcmp0(".",test_path))
+ test_path = script_path;
+
+ // check if prop is a file or dir (imports['foo.js'] or imports.mydir)
+ file_path = g_build_filename (test_path, prop, NULL);
+ if (g_file_test (file_path, G_FILE_TEST_IS_REGULAR | G_FILE_TEST_IS_DIR)) {
+ ret = seed_importer_handle_file (ctx, test_path, prop, exception);
+ g_free (file_path);
+ return ret;
+ }
+
+ // check if prop is file ending with '.js'
+ file_path = g_build_filename (test_path, prop_as_js, NULL);
+ if (g_file_test (file_path, G_FILE_TEST_IS_REGULAR)) {
+ ret = seed_importer_handle_file (ctx, test_path, prop_as_js, exception);
+ g_free (file_path);
+ return ret;
+ }
+
+ // check if file is native module
+ file_path = g_build_filename (test_path, prop_as_lib, NULL);
+ if (g_file_test (file_path, G_FILE_TEST_IS_REGULAR)) {
+ ret = seed_importer_handle_native_module (ctx, test_path, prop, exception);
+ g_free (file_path);
+ return ret;
+ }
+ return ret;
+}
+
static JSObjectRef
seed_importer_search_dirs (JSContextRef ctx, GSList *path, gchar *prop, JSValueRef *exception)
{
@@ -832,43 +872,28 @@ seed_importer_search_dirs (JSContextRef ctx, GSList *path, gchar *prop, JSValueR
ret = NULL;
walk = path;
- while (walk) {
- gchar *test_path = walk->data;
- gchar *file_path;
-
- // replace '.' with current script_path if not null
- if(script_path && !g_strcmp0(".",test_path))
- test_path = script_path;
-
- // check if prop is a file or dir (imports['foo.js'] or imports.mydir)
- file_path = g_build_filename (test_path, prop, NULL);
- if (g_file_test (file_path, G_FILE_TEST_IS_REGULAR | G_FILE_TEST_IS_DIR)) {
- ret = seed_importer_handle_file (ctx, test_path, prop, exception);
- g_free (file_path);
- break;
- }
- g_free (file_path);
-
- // check if prop is file ending with '.js'
- file_path = g_build_filename (test_path, prop_as_js, NULL);
- if (g_file_test (file_path, G_FILE_TEST_IS_REGULAR)) {
- ret = seed_importer_handle_file (ctx, test_path, prop_as_js, exception);
- g_free (file_path);
- break;
- }
- g_free (file_path);
-
- // check if file is native module
- file_path = g_build_filename (test_path, prop_as_lib, NULL);
- if (g_file_test (file_path, G_FILE_TEST_IS_REGULAR)) {
- ret = seed_importer_handle_native_module (ctx, test_path, prop, exception);
- g_free (file_path);
- break;
- }
- g_free (file_path);
-
- walk = walk->next;
+#ifdef SEED_ENABLE_GJSCOMPAT
+ if (seed_arg_gjs_compatiblity) {
+ while (walk) {
+ gchar *test_path = g_strconcat(walk->data, "/gjs", NULL);
+
+ ret = seed_importer_try_load (ctx, test_path, script_path, prop, prop_as_js,
+ prop_as_lib, exception);
+ g_free (test_path);
+ walk = walk->next;
+ }
}
+#endif
+ if (!ret) {
+ walk = path;
+ while (walk) {
+ gchar *test_path = walk->data;
+
+ ret = seed_importer_try_load (ctx, test_path, script_path, prop, prop_as_js,
+ prop_as_lib, exception);
+ walk = walk->next;
+ }
+ }
g_free (prop_as_lib);
g_free (prop_as_js);
diff --git a/libseed/seed-private.h b/libseed/seed-private.h
index c60ac38..1dd6275 100644
--- a/libseed/seed-private.h
+++ b/libseed/seed-private.h
@@ -54,4 +54,8 @@ struct _SeedEngine
#include "seed-exceptions.h"
#include "seed-importer.h"
+#ifdef SEED_ENABLE_GJSCOMPAT
+extern gboolean seed_arg_gjs_compatiblity;
+#endif
+
#endif
diff --git a/modules/Makefile.am b/modules/Makefile.am
index 7a99ba2..3ed114d 100644
--- a/modules/Makefile.am
+++ b/modules/Makefile.am
@@ -1 +1,5 @@
SUBDIRS = example sqlite canvas multiprocessing readline os sandbox dbus libxml cairo gtkbuilder gettext
mpfr ffi DynamicObject xorg
+
+if SEED_ENABLE_GJSCOMPAT
+SUBDIRS += gjs
+endif
diff --git a/modules/gjs/Makefile.am b/modules/gjs/Makefile.am
new file mode 100644
index 0000000..b83f7ee
--- /dev/null
+++ b/modules/gjs/Makefile.am
@@ -0,0 +1,8 @@
+if SEED_ENABLE_GJSCOMPAT
+
+EXTRA_DIST= cairo.js coverage.js format.js gettext.js jsUnit.js lang.js mainloop.js package.js signals.js
+
+gjs_extensiondir=$(datadir)/seed SEED_GTK_VERSION@/gjs
+gjs_extension_DATA = cairo.js coverage.js format.js gettext.js jsUnit.js lang.js mainloop.js package.js
signals.js
+
+endif
\ No newline at end of file
diff --git a/modules/gjs/cairo.js b/modules/gjs/cairo.js
new file mode 100644
index 0000000..7555c3c
--- /dev/null
+++ b/modules/gjs/cairo.js
@@ -0,0 +1,147 @@
+// Copyright 2010 litl, LLC.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+const Lang = imports.lang;
+
+const Antialias = {
+ DEFAULT: 0,
+ NONE: 1,
+ GRAY: 2,
+ SUBPIXEL: 3
+};
+
+const Content = {
+ COLOR : 0x1000,
+ ALPHA : 0x2000,
+ COLOR_ALPHA : 0x3000
+};
+
+const Extend = {
+ NONE : 0,
+ REPEAT : 1,
+ REFLECT : 2,
+ PAD : 3
+};
+
+const FillRule = {
+ WINDING: 0,
+ EVEN_ODD: 1
+};
+
+const Filter = {
+ FAST : 0,
+ GOOD : 1,
+ BEST : 2,
+ NEAREST : 3,
+ BILINEAR : 4,
+ GAUSSIAN : 5
+};
+
+const FontSlant = {
+ NORMAL: 0,
+ ITALIC: 1,
+ OBLIQUE: 2
+};
+
+const FontWeight = {
+ NORMAL : 0,
+ BOLD : 1
+};
+
+const Format = {
+ ARGB32 : 0,
+ RGB24 : 1,
+ A8 : 2,
+ A1 : 3,
+ // The value of 4 is reserved by a deprecated enum value
+ RGB16_565: 5
+};
+
+const LineCap = {
+ BUTT: 0,
+ ROUND: 1,
+ SQUASH: 2
+};
+
+const LineJoin = {
+ MITER: 0,
+ ROUND: 1,
+ BEVEL: 2
+};
+
+const Operator = {
+ CLEAR: 0,
+ SOURCE: 1,
+ OVER: 2,
+ IN : 3,
+ OUT : 4,
+ ATOP : 5,
+ DEST : 6,
+ DEST_OVER : 7,
+ DEST_IN : 8,
+ DEST_OUT : 9,
+ DEST_ATOP : 10,
+ XOR : 11,
+ ADD : 12,
+ SATURATE : 13,
+ MULTIPLY : 14,
+ SCREEN : 15,
+ OVERLAY : 16,
+ DARKEN : 17,
+ LIGHTEN : 18,
+ COLOR_DODGE : 19,
+ COLOR_BURN : 20,
+ HARD_LIGHT : 21,
+ SOFT_LIGHT : 22,
+ DIFFERENCE : 23,
+ EXCLUSION : 24,
+ HSL_HUE : 25,
+ HSL_SATURATION : 26,
+ HSL_COLOR : 27,
+ HSL_LUMINOSITY : 28
+};
+
+const PatternType = {
+ SOLID : 0,
+ SURFACE : 1,
+ LINEAR : 2,
+ RADIAL : 3
+};
+
+const SurfaceType = {
+ IMAGE : 0,
+ PDF : 1,
+ PS : 2,
+ XLIB : 3,
+ XCB : 4,
+ GLITZ : 5,
+ QUARTZ : 6,
+ WIN32 : 7,
+ BEOS : 8,
+ DIRECTFB : 9,
+ SVG : 10,
+ OS2 : 11,
+ WIN32_PRINTING : 12,
+ QUARTZ_IMAGE : 13
+};
+
+// Merge stuff defined in native code
+Lang.copyProperties(imports.cairoNative, this);
+
diff --git a/modules/gjs/coverage.js b/modules/gjs/coverage.js
new file mode 100644
index 0000000..1b4f2cc
--- /dev/null
+++ b/modules/gjs/coverage.js
@@ -0,0 +1,978 @@
+/*
+ * Copyright (c) 2014 Endless Mobile, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * Authored By: Sam Spilsbury <sam endlessm com>
+ */
+
+function getSubNodesForNode(node) {
+ let subNodes = [];
+ switch (node.type) {
+ /* These statements have a single body */
+ case 'LabelledStatement':
+ case 'WithStatement':
+ case 'FunctionDeclaration':
+ case 'FunctionExpression':
+ case 'CatchClause':
+ subNodes.push(node.body);
+ break;
+ case 'LetStatement':
+ Array.prototype.push.apply(subNodes, node.head);
+ subNodes.push(node.body);
+ break;
+ case 'WhileStatement':
+ case 'DoWhileStatement':
+ subNodes.push(node.body);
+ subNodes.push(node.test);
+ break;
+ case 'ForStatement':
+ if (node.init !== null)
+ subNodes.push(node.init);
+ if (node.test !== null)
+ subNodes.push(node.test);
+ if (node.update !== null)
+ subNodes.push(node.update);
+
+ subNodes.push(node.body);
+ break;
+ case 'ForInStatement':
+ if (node.each)
+ subNodes.push(node.left);
+
+ subNodes.push(node.right, node.body);
+ break;
+ case 'ForOfStatement':
+ subNodes.push(node.left, node.right, node.body);
+ break;
+ case 'BlockStatement':
+ Array.prototype.push.apply(subNodes, node.body);
+ break;
+ case 'ThrowStatement':
+ case 'ReturnStatement':
+ case 'YieldExpression':
+ if (node.argument !== null)
+ subNodes.push(node.argument);
+ break;
+ case 'ExpressionStatement':
+ subNodes.push(node.expression);
+ break;
+ case 'AssignmentExpression':
+ case 'BinaryExpression':
+ case 'LogicalExpression':
+ subNodes.push(node.left, node.right);
+ break;
+ case 'ConditionalExpression':
+ subNodes.push(node.test, node.consequent, node.alternate);
+ break;
+ case 'ObjectExpression':
+ node.properties.forEach(function(prop) {
+ subNodes.push(prop.value);
+ });
+ break;
+ case 'ArrayExpression':
+ node.elements.forEach(function(elem) {
+ if (elem !== null)
+ subNodes.push(elem);
+ });
+ break;
+ case 'ArrowExpression':
+ Array.prototype.push.apply(subNodes, node.defaults);
+ subNodes.push(node.body);
+ break;
+ case 'SequenceExpression':
+ Array.prototype.push.apply(subNodes, node.expressions);
+ break;
+ case 'UnaryExpression':
+ case 'UpdateExpression':
+ subNodes.push(node.argument);
+ break;
+ case 'ComprehensionExpression':
+ case 'GeneratorExpression':
+ subNodes.push(node.body);
+ Array.prototype.push.apply(subNodes, node.blocks);
+ if (node.filter !== null)
+ subNodes.push(node.filter);
+ break;
+ case 'ComprehensionBlock':
+ subNodes.push(node.right);
+ break;
+ /* It is very possible that there might be something
+ * interesting in the function arguments, so we need to
+ * walk them too */
+ case 'NewExpression':
+ case 'CallExpression':
+ Array.prototype.push.apply(subNodes, node.arguments);
+ subNodes.push(node.callee);
+ break;
+ /* These statements might have multiple different bodies
+ * depending on whether or not they were entered */
+ case 'IfStatement':
+ subNodes = [node.test, node.consequent];
+ if (node.alternate !== null)
+ subNodes.push(node.alternate);
+ break;
+ case 'TryStatement':
+ subNodes = [node.block];
+ if (node.handler !== null)
+ subNodes.push(node.handler);
+ if (node.finalizer !== null)
+ subNodes.push(node.finalizer);
+ break;
+ case 'SwitchStatement':
+ for (let caseClause of node.cases) {
+ caseClause.consequent.forEach(function(expression) {
+ subNodes.push(expression);
+ });
+ }
+ break;
+ case 'VariableDeclaration':
+ Array.prototype.push.apply(subNodes, node.declarations);
+ break;
+ case 'VariableDeclarator':
+ if (node.init !== null)
+ subNodes.push(node.init);
+ break;
+ case 'MemberExpression':
+ subNodes.push(node.object);
+ if (node.computed)
+ subNodes.push(node.property);
+
+ break;
+ }
+
+ return subNodes;
+}
+
+function collectForSubNodes(subNodes, collector) {
+ let result = [];
+ if (subNodes !== undefined &&
+ subNodes.length > 0) {
+
+ subNodes.forEach(function(node) {
+ let nodeResult = collector(node);
+ if (nodeResult !== undefined)
+ Array.prototype.push.apply(result, nodeResult);
+
+ let subNodeResults = collectForSubNodes(getSubNodesForNode(node),
+ collector);
+
+ Array.prototype.push.apply(result, subNodeResults);
+ });
+ }
+
+ return result;
+}
+
+function _getFunctionKeyFromReflectedFunction(node) {
+ let name = node.id !== null ? node.id.name : '(anonymous)';
+ let line = node.loc.start.line;
+ let n_params = node.params.length;
+
+ return [name, line, n_params].join(':');
+}
+
+/* Unfortunately, the Reflect API doesn't give us enough information to
+ * uniquely identify a function. A function might be anonymous, in which
+ * case the JS engine uses some heurisitics to get a unique string identifier
+ * but that isn't available to us here.
+ *
+ * There's also the edge-case where functions with the same name might be
+ * defined within the same scope, or multiple anonymous functions might
+ * be defined on the same line. In that case, it will look like we entered
+ * the same function multiple times since we can't get column information
+ * from the engine-side.
+ *
+ * For instance:
+ *
+ * 1. function f() {
+ * function f() {
+ * }
+ * }
+ *
+ * 2. let a = function() { function(a, b) {} };
+ *
+ * 3. let a = function() { function () {} }
+ *
+ * We can work-around case 1 by using the line numbers to get a unique identifier.
+ * We can work-around case 2 by using the arguments length to get a unique identifier
+ * We can't work-around case 3. The best thing we can do is warn that coverage
+ * reports might be inaccurate as a result */
+function functionsForNode(node) {
+ let functionNames = [];
+ switch (node.type) {
+ case 'FunctionDeclaration':
+ case 'FunctionExpression':
+ case 'ArrowExpression':
+ functionNames.push({ key: _getFunctionKeyFromReflectedFunction(node),
+ line: node.loc.start.line,
+ n_params: node.params.length });
+ }
+
+ return functionNames;
+}
+
+function functionsForAST(ast) {
+ return collectForSubNodes(ast.body, functionsForNode);
+}
+
+/* If a branch' consequent is a block statement, there's
+ * a chance that it could start on the same line, although
+ * that's not where execution really starts. If it is
+ * a block statement then handle the case and go
+ * to the first line where execution starts */
+function getBranchExitStartLine(branchBodyNode) {
+ switch (branchBodyNode.type) {
+ case 'BlockStatement':
+ /* Hit a block statement, but nothing inside, can never
+ * be executed, tell the upper level to move on to the next
+ * statement */
+ if (branchBodyNode.body.length === 0)
+ return -1;
+
+ /* Handle the case where we have nested block statements
+ * that never actually get to executable code by handling
+ * all statements within a block */
+ for (let statement of branchBodyNode.body) {
+ let startLine = getBranchExitStartLine(statement);
+ if (startLine !== -1)
+ return startLine;
+ }
+
+ /* Couldn't find an executable line inside this block */
+ return -1;
+
+ case 'SwitchCase':
+ /* Hit a switch, but nothing inside, can never
+ * be executed, tell the upper level to move on to the next
+ * statement */
+ if (branchBodyNode.consequent.length === 0)
+ return -1;
+
+ /* Handle the case where we have nested block statements
+ * that never actually get to executable code by handling
+ * all statements within a block */
+ for (let statement of branchBodyNode.consequent) {
+ let startLine = getBranchExitStartLine(statement);
+ if (startLine !== -1) {
+ return startLine;
+ }
+ }
+
+ /* Couldn't find an executable line inside this block */
+ return -1;
+ /* These types of statements are never executable */
+ case 'EmptyStatement':
+ case 'LabelledStatement':
+ return -1;
+ default:
+ break;
+ }
+
+ return branchBodyNode.loc.start.line;
+}
+
+function branchesForNode(node) {
+ let branches = [];
+
+ let branchExitNodes = [];
+ switch (node.type) {
+ case 'IfStatement':
+ branchExitNodes.push(node.consequent);
+ if (node.alternate !== null)
+ branchExitNodes.push(node.alternate);
+ break;
+ case 'WhileStatement':
+ case 'DoWhileStatement':
+ branchExitNodes.push(node.body);
+ break;
+ case 'SwitchStatement':
+
+ /* The case clauses by themselves are never executable
+ * so find the actual exits */
+ Array.prototype.push.apply(branchExitNodes, node.cases);
+ break;
+ default:
+ break;
+ }
+
+ let branchExitStartLines = branchExitNodes.map(getBranchExitStartLine);
+ branchExitStartLines = branchExitStartLines.filter(function(line) {
+ return line !== -1;
+ });
+
+ /* Branch must have at least one exit */
+ if (branchExitStartLines.length) {
+ branches.push({ point: node.loc.start.line,
+ exits: branchExitStartLines });
+ }
+
+ return branches;
+}
+
+function branchesForAST(ast) {
+ return collectForSubNodes(ast.body, branchesForNode);
+}
+
+function expressionLinesForNode(statement) {
+ let expressionLines = [];
+
+ let expressionNodeTypes = ['Expression',
+ 'Declaration',
+ 'Statement',
+ 'Clause',
+ 'Literal',
+ 'Identifier'];
+
+ if (expressionNodeTypes.some(function(type) {
+ return statement.type.indexOf(type) !== -1;
+ })) {
+
+ /* These expressions aren't executable on their own */
+ switch (statement.type) {
+ case 'FunctionDeclaration':
+ case 'LiteralExpression':
+ break;
+ /* Perplexingly, an empty block statement is actually executable,
+ * push it if it is */
+ case 'BlockStatement':
+ if (statement.body.length !== 0)
+ break;
+ expressionLines.push(statement.loc.start.line);
+ break;
+ default:
+ expressionLines.push(statement.loc.start.line);
+ break;
+ }
+ }
+
+ return expressionLines;
+}
+
+function deduplicate(list) {
+ return list.filter(function(elem, pos, self) {
+ return self.indexOf(elem) === pos;
+ });
+}
+
+function expressionLinesForAST(ast) {
+ let allExpressions = collectForSubNodes(ast.body, expressionLinesForNode);
+ allExpressions = deduplicate(allExpressions);
+
+ return allExpressions;
+}
+
+function _getNumberOfLinesForScript(scriptContents) {
+ let scriptLines = scriptContents.split("\n");
+ let scriptLineCount = scriptLines.length;
+
+ return scriptLineCount;
+}
+
+/*
+ * The created array is a 1-1 representation of the hitcount in the filename. Each
+ * element refers to an individual line. In order to avoid confusion, our array
+ * is zero indexed, but the zero'th line is always ignored and the first element
+ * refers to the first line of the file.
+ *
+ * A value of undefined for an element means that the line is non-executable and never actually
+ * reached. A value of 0 means that it was executable but never reached. A positive value
+ * indicates the hit count.
+ *
+ * We care about non-executable lines because we don't want to report coverage misses for
+ * lines that could have never been executed anyways.
+ *
+ * The reason for using a 1-1 mapping as opposed to an array of key-value pairs for executable
+ * lines is:
+ * 1. Lookup speed is O(1) instead of O(log(n))
+ * 2. There's a possibility we might hit a line which we thought was non-executable, in which
+ * case we can neatly handle the error by marking that line executable. A hit on a line
+ * we thought was non-executable is not as much of a problem as noise generated by
+ * ostensible "misses" which could in fact never be executed.
+ *
+ */
+function _expressionLinesToCounters(expressionLines, nLines) {
+ expressionLines.sort(function(left, right) { return left - right; });
+
+ let expressionLinesIndex = 0;
+ let counters = new Array(nLines + 1);
+
+ if (expressionLines.length === 0)
+ return counters;
+
+ for (let i = 1; i < counters.length; i++) {
+ if (expressionLines[expressionLinesIndex] == i) {
+ counters[i] = 0;
+ expressionLinesIndex++;
+ }
+ }
+
+ return counters;
+}
+
+/* As above, we are creating a 1-1 representation of script lines to potential branches
+ * where each element refers to a 1-index line (with the zero'th ignored).
+ *
+ * Each element is a GjsCoverageBranchData which, if the line at the element
+ * position describes a branch, will be populated with a GjsReflectedScriptBranchInfo
+ * and an array of unsigned each specifying the hit-count for each potential branch
+ * in the branch info */
+function _branchesToBranchCounters(branches, nLines) {
+ branches.sort(function(left, right) {
+ return left.point - right.point;
+ });
+
+ let branchIndex = 0;
+ let counters = new Array(nLines + 1);
+
+ if (branches.length === 0)
+ return counters;
+
+ for (let i = 1; i < counters.length; i++) {
+ let branch = branches[branchIndex];
+ let branchPoint = branch.point;
+
+ if (branchPoint == i) {
+ counters[i] = {
+ point: branchPoint,
+ exits: branch.exits.map(function(exit) {
+ return {
+ line: exit,
+ hitCount: 0
+ };
+ }),
+ lastExit: (function() {
+ let lastExitLine = 0;
+ for (let exit of branch.exits) {
+ if (lastExitLine < exit)
+ lastExitLine = exit;
+ }
+
+ return lastExitLine;
+ })(),
+ hit: false
+ };
+
+ if (++branchIndex >= branches.length)
+ break;
+ }
+ }
+
+ return counters;
+}
+
+function _functionsToFunctionCounters(script, functions) {
+ let functionCounters = {};
+
+ functions.forEach(function(func) {
+ let [name, line, args] = func.key.split(':');
+
+ if (functionCounters[name] === undefined) {
+ functionCounters[name] = {};
+ }
+
+ if (functionCounters[name][line] === undefined) {
+ functionCounters[name][line] = {};
+ }
+
+ if (functionCounters[name][line][args] === undefined) {
+ functionCounters[name][line][args] = { hitCount: 0 };
+ } else {
+ log(script + ':' + line + ' Function identified as ' +
+ func.key + ' already seen in this file. Function coverage ' +
+ 'will be incomplete.');
+ }
+ });
+
+ return functionCounters;
+}
+
+function _populateKnownFunctions(functions, nLines) {
+ let knownFunctions = new Array(nLines + 1);
+
+ functions.forEach(function(func) {
+ knownFunctions[func.line] = true;
+ });
+
+ return knownFunctions;
+}
+
+function _identifyFunctionCounterInLinePartForDescription(linePart,
+ nArgs) {
+ /* There is only one potential option for this line. We might have been
+ * called with the wrong number of arguments, but it doesn't matter. */
+ if (Object.getOwnPropertyNames(linePart).length === 1)
+ return linePart[Object.getOwnPropertyNames(linePart)[0]];
+
+ /* Have to disambiguate using nArgs and we have an exact match. */
+ if (linePart[nArgs] !== undefined)
+ return linePart[nArgs];
+
+ /* There are multiple options on this line. Pick the one where
+ * we satisfy the number of arguments exactly, or failing that,
+ * go through each and pick the closest. */
+ let allNArgsOptions = Object.keys(linePart).map(function(key) {
+ return parseInt(key);
+ });
+
+ let closest = allNArgsOptions.reduce(function(prev, current, index, array) {
+ let nArgsOption = array[index];
+ if (Math.abs(nArgsOption - nArgs) < Math.abs(current - nArgs)) {
+ return nArgsOption;
+ }
+
+ return current;
+ });
+
+ return linePart[String(closest)];
+}
+
+function _identifyFunctionCounterForDescription(functionCounters,
+ name,
+ line,
+ nArgs) {
+ let candidateCounter = functionCounters[name];
+
+ if (candidateCounter === undefined)
+ return null;
+
+ if (Object.getOwnPropertyNames(candidateCounter).length === 1) {
+ let linePart = candidateCounter[Object.getOwnPropertyNames(candidateCounter)[0]];
+ return _identifyFunctionCounterInLinePartForDescription(linePart, nArgs);
+ }
+
+ let linePart = functionCounters[name][line];
+
+ if (linePart === undefined) {
+ return null;
+ }
+
+ return _identifyFunctionCounterInLinePartForDescription(linePart, nArgs);
+}
+
+/**
+ * _incrementFunctionCounters
+ *
+ * functionCounters: An object which is a key-value pair with the following schema:
+ * {
+ * "key" : { line, hitCount }
+ * }
+ * linesWithKnownFunctions: An array of either "true" or undefined, with true set to
+ * each element corresponding to a line that we know has a function on it.
+ * name: The name of the function or "(anonymous)" if it has no name
+ * line: The line at which execution first started on this function.
+ * nArgs: The number of arguments this function has.
+ */
+function _incrementFunctionCounters(functionCounters,
+ linesWithKnownFunctions,
+ name,
+ line,
+ nArgs) {
+ let functionCountersForKey = _identifyFunctionCounterForDescription(functionCounters,
+ name,
+ line,
+ nArgs);
+
+ /* Its possible that the JS Engine might enter a funciton
+ * at an executable line which is a little bit past the
+ * actual definition. Roll backwards until we reach the
+ * last known function definition line which we kept
+ * track of earlier to see if we can find this function first */
+ if (functionCountersForKey === null) {
+ do {
+ --line;
+ functionCountersForKey = _identifyFunctionCounterForDescription(functionCounters,
+ name,
+ line,
+ nArgs);
+ } while (linesWithKnownFunctions[line] !== true && line > 0);
+ }
+
+ if (functionCountersForKey !== null) {
+ functionCountersForKey.hitCount++;
+ } else {
+ let functionKey = [name, line, nArgs].join(':');
+ throw new Error("expected Reflect to find function " + functionKey);
+ }
+}
+
+/**
+ * _incrementExpressionCounters
+ *
+ * expressonCounters: An array of either a hit count for a found
+ * executable line or undefined for a known non-executable line.
+ * line: an executed line
+ */
+function _incrementExpressionCounters(expressionCounters,
+ script,
+ offsetLine) {
+ let expressionCountersLen = expressionCounters.length;
+
+ if (offsetLine >= expressionCountersLen)
+ throw new Error("Executed line " + offsetLine + " which was past the highest-found line " +
expressionCountersLen);
+
+ /* If this happens it is not a huge problem - though it does
+ * mean that the reflection machinery is not doing its job, so we should
+ * print a debug message about it in case someone is interested.
+ *
+ * The reason why we don't have a proper log is because it
+ * is difficult to determine what the SpiderMonkey program counter
+ * will actually pass over, especially function declarations for some
+ * reason:
+ *
+ * function f(a,b) {
+ * a = 1;
+ * }
+ *
+ * In some cases, the declaration itself will be executed
+ * but in other cases it won't be. Reflect.parse tells us that
+ * the only two expressions on that line are a FunctionDeclaration
+ * and BlockStatement, neither of which would ordinarily be
+ * executed */
+ if (expressionCounters[offsetLine] === undefined) {
+ log(script + ':' + offsetLine + ' Executed line previously marked ' +
+ 'non-executable by Reflect');
+ expressionCounters[offsetLine] = 0;
+ }
+
+ expressionCounters[offsetLine]++;
+}
+
+function _BranchTracker(branchCounters) {
+ this._branchCounters = branchCounters;
+ this._activeBranch = undefined;
+
+ this.incrementBranchCounters = function(offsetLine) {
+ /* Set branch exits or find a new active branch */
+ let activeBranch = this._activeBranch;
+ if (activeBranch !== undefined) {
+ activeBranch.exits.forEach(function(exit) {
+ if (exit.line === offsetLine) {
+ exit.hitCount++;
+ }
+ });
+
+ /* Only set the active branch to undefined once we're
+ * completely outside of it, since we might be in a case statement where
+ * we need to check every possible option before jumping to an
+ * exit */
+ if (offsetLine >= activeBranch.lastExit)
+ this._activeBranch = undefined;
+ }
+
+ let nextActiveBranch = branchCounters[offsetLine];
+ if (nextActiveBranch !== undefined) {
+ this._activeBranch = nextActiveBranch;
+ this._activeBranch.hit = true;
+ }
+ };
+}
+
+function _convertFunctionCountersToArray(functionCounters) {
+ let arrayReturn = [];
+ /* functionCounters is an object so explore it to create a
+ * set of function keys and then convert it to
+ * an array-of-object using the key as a property
+ * of that object */
+ for (let name of Object.getOwnPropertyNames(functionCounters)) {
+ let namePart = functionCounters[name];
+
+ for (let line of Object.getOwnPropertyNames(namePart)) {
+ let linePart = functionCounters[name][line];
+
+ for (let nArgs of Object.getOwnPropertyNames(linePart)) {
+ let functionKey = [name, line, nArgs].join(':');
+ arrayReturn.push({ name: functionKey,
+ line: Number(line),
+ nArgs: nArgs,
+ hitCount: linePart[nArgs].hitCount });
+ }
+ }
+ }
+
+ arrayReturn.sort(function(left, right) {
+ if (left.name < right.name)
+ return -1;
+ else if (left.name > right.name)
+ return 1;
+ else
+ return 0;
+ });
+ return arrayReturn;
+}
+
+/* Looks up filename in cache and fetches statistics
+ * directly from the cache */
+function _fetchCountersFromCache(filename, cache, nLines) {
+ if (!cache)
+ return null;
+
+ if (Object.keys(cache).indexOf(filename) !== -1) {
+ let cache_for_file = cache[filename];
+
+ if (cache_for_file.mtime) {
+ let mtime = getFileModificationTime(filename);
+ if (mtime[0] != cache[filename].mtime[0] ||
+ mtime[1] != cache[filename].mtime[1])
+ return null;
+ } else {
+ let checksum = getFileChecksum(filename);
+ if (checksum != cache[filename].checksum)
+ return null;
+ }
+
+ let functions = cache_for_file.functions;
+
+ return {
+ expressionCounters: _expressionLinesToCounters(cache_for_file.lines, nLines),
+ branchCounters: _branchesToBranchCounters(cache_for_file.branches, nLines),
+ functionCounters: _functionsToFunctionCounters(filename, functions),
+ linesWithKnownFunctions: _populateKnownFunctions(functions, nLines),
+ nLines: nLines
+ };
+ }
+
+ return null;
+}
+
+function _fetchCountersFromReflection(filename, contents, nLines) {
+ let reflection = Reflect.parse(contents);
+ let functions = functionsForAST(reflection);
+
+ return {
+ expressionCounters: _expressionLinesToCounters(expressionLinesForAST(reflection), nLines),
+ branchCounters: _branchesToBranchCounters(branchesForAST(reflection), nLines),
+ functionCounters: _functionsToFunctionCounters(filename, functions),
+ linesWithKnownFunctions: _populateKnownFunctions(functions, nLines),
+ nLines: nLines
+ };
+}
+
+function CoverageStatisticsContainer(prefixes, cache) {
+ /* Copy the files array, so that it can be re-used in the tests */
+ let cachedASTs = cache !== undefined ? JSON.parse(cache) : null;
+ let coveredFiles = {};
+ let cacheMisses = 0;
+
+ function wantsStatisticsFor(filename) {
+ return prefixes.some(function(prefix) {
+ return filename.startsWith(prefix);
+ });
+ }
+
+ function createStatisticsFor(filename) {
+ let contents = getFileContents(filename);
+ let nLines = _getNumberOfLinesForScript(contents);
+
+ let counters = _fetchCountersFromCache(filename, cachedASTs, nLines);
+ if (counters === null) {
+ cacheMisses++;
+ counters = _fetchCountersFromReflection(filename, contents, nLines);
+ }
+
+ if (counters === null)
+ throw new Error('Failed to parse and reflect file ' + filename);
+
+ /* Set contents here as we don't pass it to _fetchCountersFromCache. */
+ counters.contents = contents;
+
+ return counters;
+ }
+
+ function ensureStatisticsFor(filename) {
+ let wantStatistics = wantsStatisticsFor(filename);
+ let haveStatistics = !!coveredFiles[filename];
+
+ if (wantStatistics && !haveStatistics)
+ coveredFiles[filename] = createStatisticsFor(filename);
+ return coveredFiles[filename];
+ }
+
+ this.stringify = function() {
+ let cache_data = {};
+ Object.keys(coveredFiles).forEach(function(filename) {
+ let statisticsForFilename = coveredFiles[filename];
+ let mtime = getFileModificationTime(filename);
+ let cacheDataForFilename = {
+ mtime: mtime,
+ checksum: mtime === null ? getFileChecksum(filename) : null,
+ lines: [],
+ branches: [],
+ functions:
_convertFunctionCountersToArray(statisticsForFilename.functionCounters).map(function(func) {
+ return {
+ key: func.name,
+ line: func.line
+ };
+ })
+ };
+
+ /* We're using a index based loop here since we need access to the
+ * index, since it actually represents the current line number
+ * on the file (see _expressionLinesToCounters). */
+ for (let line_index = 0;
+ line_index < statisticsForFilename.expressionCounters.length;
+ ++line_index) {
+ if (statisticsForFilename.expressionCounters[line_index] !== undefined)
+ cacheDataForFilename.lines.push(line_index);
+
+ if (statisticsForFilename.branchCounters[line_index] !== undefined) {
+ let branchCounters = statisticsForFilename.branchCounters[line_index];
+ cacheDataForFilename.branches.push({
+ point: statisticsForFilename.branchCounters[line_index].point,
+ exits: statisticsForFilename.branchCounters[line_index].exits.map(function(exit) {
+ return exit.line;
+ })
+ });
+ }
+ }
+ cache_data[filename] = cacheDataForFilename;
+ });
+ return JSON.stringify(cache_data);
+ };
+
+ this.getCoveredFiles = function() {
+ return Object.keys(coveredFiles);
+ };
+
+ this.fetchStatistics = function(filename) {
+ return ensureStatisticsFor(filename);
+ };
+
+ this.staleCache = function() {
+ return cacheMisses > 0;
+ };
+
+ this.deleteStatistics = function(filename) {
+ coveredFiles[filename] = undefined;
+ };
+}
+
+/**
+ * Main class tying together the Debugger object and CoverageStatisticsContainer.
+ *
+ * It isn't poissible to unit test this class because it depends on running
+ * Debugger which in turn depends on objects injected in from another compartment */
+function CoverageStatistics(prefixes, cache) {
+ this.container = new CoverageStatisticsContainer(prefixes, cache);
+ let fetchStatistics = this.container.fetchStatistics.bind(this.container);
+ let deleteStatistics = this.container.deleteStatistics.bind(this.container);
+
+ /* 'debuggee' comes from the invocation from
+ * a separate compartment inside of coverage.cpp */
+ this.dbg = new Debugger(debuggee);
+
+ this.getCoveredFiles = function() {
+ return this.container.getCoveredFiles();
+ };
+
+ this.getNumberOfLinesFor = function(filename) {
+ return fetchStatistics(filename).nLines;
+ };
+
+ this.getExecutedLinesFor = function(filename) {
+ return fetchStatistics(filename).expressionCounters;
+ };
+
+ this.getBranchesFor = function(filename) {
+ return fetchStatistics(filename).branchCounters;
+ };
+
+ this.getFunctionsFor = function(filename) {
+ let functionCounters = fetchStatistics(filename).functionCounters;
+ return _convertFunctionCountersToArray(functionCounters);
+ };
+
+ this.dbg.onEnterFrame = function(frame) {
+ let statistics;
+
+ try {
+ statistics = fetchStatistics(frame.script.url);
+ if (!statistics) {
+ return undefined;
+ }
+ } catch (e) {
+ /* We don't care about this frame, return */
+ log(e.message + " " + e.stack);
+ return undefined;
+ }
+
+ function _logExceptionAndReset(exception, callee, line) {
+ log(exception.fileName + ":" + exception.lineNumber +
+ " (processing " + frame.script.url + ":" + callee + ":" +
+ line + ") - " + exception.message);
+ log("Will not log statistics for this file");
+ frame.onStep = undefined;
+ frame._branchTracker = undefined;
+ deleteStatistics(frame.script.url);
+ }
+
+ /* Log function calls */
+ if (frame.callee !== null && frame.callee.callable) {
+ let name = frame.callee.name ? frame.callee.name : "(anonymous)";
+ let line = frame.script.getOffsetLine(frame.offset);
+ let nArgs = frame.callee.parameterNames.length;
+
+ try {
+ _incrementFunctionCounters(statistics.functionCounters,
+ statistics.linesWithKnownFunctions,
+ name,
+ line,
+ nArgs);
+ } catch (e) {
+ /* Something bad happened. Log the exception and delete
+ * statistics for this file */
+ _logExceptionAndReset(e, name, line);
+ return undefined;
+ }
+ }
+
+ /* Upon entering the frame, the active branch is always inactive */
+ frame._branchTracker = new _BranchTracker(statistics.branchCounters);
+
+ /* Set single-step hook */
+ frame.onStep = function() {
+ /* Line counts */
+ let offset = this.offset;
+ let offsetLine = this.script.getOffsetLine(offset);
+
+ try {
+ _incrementExpressionCounters(statistics.expressionCounters,
+ frame.script.url,
+ offsetLine);
+ this._branchTracker.incrementBranchCounters(offsetLine);
+ } catch (e) {
+ /* Something bad happened. Log the exception and delete
+ * statistics for this file */
+ _logExceptionAndReset(e, frame.callee, offsetLine);
+ }
+ };
+
+ return undefined;
+ };
+
+ this.deactivate = function() {
+ /* This property is designed to be a one-stop-shop to
+ * disable the debugger for this debugee, without having
+ * to traverse all its scripts or frames */
+ this.dbg.enabled = false;
+ };
+
+ this.staleCache = this.container.staleCache.bind(this.container);
+ this.stringify = this.container.stringify.bind(this.container);
+}
diff --git a/modules/gjs/format.js b/modules/gjs/format.js
new file mode 100644
index 0000000..430b5d7
--- /dev/null
+++ b/modules/gjs/format.js
@@ -0,0 +1,86 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+
+const GjsPrivate = imports.gi.GjsPrivate;
+
+function vprintf(str, args) {
+ let i = 0;
+ let usePos = false;
+ return str.replace(/%(?:([1-9][0-9]*)\$)?(I+)?([0-9]+)?(?:\.([0-9]+))?(.)/g, function (str, posGroup,
flagsGroup, widthGroup, precisionGroup, genericGroup) {
+ if (precisionGroup != '' && genericGroup != 'f')
+ throw new Error("Precision can only be specified for 'f'");
+
+ let hasAlternativeIntFlag = (flagsGroup.indexOf('I') != -1);
+ if (hasAlternativeIntFlag && genericGroup != 'd')
+ throw new Error("Alternative output digits can only be specfied for 'd'");
+
+ let pos = parseInt(posGroup, 10) || 0;
+ if (usePos == false && i == 0)
+ usePos = pos > 0;
+ if (usePos && pos == 0 || !usePos && pos > 0)
+ throw new Error("Numbered and unnumbered conversion specifications cannot be mixed");
+
+ let fillChar = (widthGroup[0] == '0') ? '0' : ' ';
+ let width = parseInt(widthGroup, 10) || 0;
+
+ function fillWidth(s, c, w) {
+ let fill = '';
+ for (let i = 0; i < w; i++)
+ fill += c;
+ return fill.substr(s.length) + s;
+ }
+
+ function getArg() {
+ return usePos ? args[pos - 1] : args[i++];
+ }
+
+ let s = '';
+ switch (genericGroup) {
+ case '%':
+ return '%';
+ break;
+ case 's':
+ s = String(getArg());
+ break;
+ case 'd':
+ let intV = parseInt(getArg());
+ if (hasAlternativeIntFlag)
+ s = GjsPrivate.format_int_alternative_output(intV);
+ else
+ s = intV.toString();
+ break;
+ case 'x':
+ s = parseInt(getArg()).toString(16);
+ break;
+ case 'f':
+ if (precisionGroup == '')
+ s = parseFloat(getArg()).toString();
+ else
+ s = parseFloat(getArg()).toFixed(parseInt(precisionGroup));
+ break;
+ default:
+ throw new Error('Unsupported conversion character %' + genericGroup);
+ }
+ return fillWidth(s, fillChar, width);
+ });
+}
+
+function printf() {
+ let args = Array.prototype.slice.call(arguments);
+ let fmt = args.shift();
+ print(vprintf(fmt, args));
+}
+
+/*
+ * This function is intended to extend the String object and provide
+ * an String.format API for string formatting.
+ * It has to be set up using String.prototype.format = Format.format;
+ * Usage:
+ * "somestring %s %d".format('hello', 5);
+ * It supports %s, %d, %x and %f, for %f it also support precisions like
+ * "%.2f".format(1.526). All specifiers can be prefixed with a minimum
+ * field width, e.g. "%5s".format("foo"). Unless the width is prefixed
+ * with '0', the formatted string will be padded with spaces.
+ */
+function format() {
+ return vprintf(this, arguments);
+}
diff --git a/modules/gjs/gettext.js b/modules/gjs/gettext.js
new file mode 100644
index 0000000..67c4504
--- /dev/null
+++ b/modules/gjs/gettext.js
@@ -0,0 +1,99 @@
+// Copyright 2009 Red Hat, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+/**
+ * This module provides a convenience layer for the "gettext" family of functions,
+ * relying on GLib for the actual implementation.
+ *
+ * Usage:
+ *
+ * const Gettext = imports.gettext;
+ *
+ * Gettext.textdomain("myapp");
+ * Gettext.bindtextdomain("myapp", "/usr/share/locale");
+ *
+ * let translated = Gettext.gettext("Hello world!");
+ */
+
+const GLib = imports.gi.GLib;
+const GjsPrivate = imports.gi.GjsPrivate;
+
+const LocaleCategory = GjsPrivate.LocaleCategory;
+
+function setlocale(category, locale) {
+ return GjsPrivate.setlocale(category, locale);
+}
+
+function textdomain(domain) {
+ return GjsPrivate.textdomain(domain);
+}
+function bindtextdomain(domain, location) {
+ return GjsPrivate.bindtextdomain(domain, location);
+}
+
+function gettext(msgid) {
+ return GLib.dgettext(null, msgid);
+}
+function dgettext(domain, msgid) {
+ return GLib.dgettext(domain, msgid);
+}
+function dcgettext(domain, msgid, category) {
+ return GLib.dcgettext(domain, msgid, category);
+}
+
+function ngettext(msgid1, msgid2, n) {
+ return GLib.dngettext(null, msgid1, msgid2, n);
+}
+function dngettext(domain, msgid1, msgid2, n) {
+ return GLib.dngettext(domain, msgid1, msgid2, n);
+}
+// FIXME: missing dcngettext ?
+
+function pgettext(context, msgid) {
+ return GLib.dpgettext2(null, context, msgid);
+}
+function dpgettext(domain, context, msgid) {
+ return GLib.dpgettext2(domain, context, msgid);
+}
+
+/**
+ * Create an object with bindings for gettext, ngettext,
+ * and pgettext bound to a particular translation domain.
+ *
+ * @param domainName Translation domain string
+ * @returns: an object with gettext bindings
+ * @type: function
+ */
+var domain = function(domainName) {
+ return {
+ gettext: function(msgid) {
+ return GLib.dgettext(domainName, msgid);
+ },
+
+ ngettext: function(msgid1, msgid2, n) {
+ return GLib.dngettext(domainName, msgid1, msgid2, n);
+ },
+
+ pgettext: function(context, msgid) {
+ return GLib.dpgettext2(domainName, context, msgid);
+ }
+ };
+};
+
diff --git a/modules/gjs/jsUnit.js b/modules/gjs/jsUnit.js
new file mode 100644
index 0000000..93f5eef
--- /dev/null
+++ b/modules/gjs/jsUnit.js
@@ -0,0 +1,473 @@
+/* @author Edward Hieatt, edward jsunit net */
+
+/*
+ - JsUnit
+ - Copyright (C) 2001-4 Edward Hieatt, edward jsunit net
+ - Copyright (C) 2008 litl, LLC
+ -
+ - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ -
+ - The contents of this file are subject to the Mozilla Public License Version
+ - 1.1 (the "License"); you may not use this file except in compliance with
+ - the License. You may obtain a copy of the License at
+ - http://www.mozilla.org/MPL/
+ -
+ - Software distributed under the License is distributed on an "AS IS" basis,
+ - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ - for the specific language governing rights and limitations under the
+ - License.
+ -
+ - The Original Code is Edward Hieatt code.
+ -
+ - The Initial Developer of the Original Code is
+ - Edward Hieatt, edward jsunit net
+ - Portions created by the Initial Developer are Copyright (C) 2003
+ - the Initial Developer. All Rights Reserved.
+ -
+ - Author Edward Hieatt, edward jsunit net
+ -
+ - Alternatively, the contents of this file may be used under the terms of
+ - either the GNU General Public License Version 2 or later (the "GPL"), or
+ - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ - in which case the provisions of the GPL or the LGPL are applicable instead
+ - of those above. If you wish to allow use of your version of this file only
+ - under the terms of either the GPL or the LGPL, and not to allow others to
+ - use your version of this file under the terms of the MPL, indicate your
+ - decision by deleting the provisions above and replace them with the notice
+ - and other provisions required by the LGPL or the GPL. If you do not delete
+ - the provisions above, a recipient may use your version of this file under
+ - the terms of any one of the MPL, the GPL or the LGPL.
+*/
+
+var JSUNIT_UNDEFINED_VALUE;
+var JSUNIT_VERSION="2.1";
+var isTestPageLoaded = false;
+
+// GJS: introduce implicit variable to avoid exceptions
+var top = null;
+
+//hack for NS62 bug
+function jsUnitFixTop() {
+ var tempTop = top;
+ if (!tempTop) {
+ tempTop = window;
+ while (tempTop.parent) {
+ tempTop = tempTop.parent;
+ if (tempTop.top && tempTop.top.jsUnitTestSuite) {
+ tempTop = tempTop.top;
+ break;
+ }
+ }
+ }
+ top = tempTop;
+}
+
+jsUnitFixTop();
+
+function _displayStringForValue(aVar) {
+ if (aVar === null)
+ return 'null';
+
+ if (aVar === top.JSUNIT_UNDEFINED_VALUE)
+ return 'undefined';
+
+ return aVar;
+}
+
+function fail(failureMessage) {
+ throw new JsUnitException(null, failureMessage);
+}
+
+function error(errorMessage) {
+ throw new Error(errorMessage);
+}
+
+function argumentsIncludeComments(expectedNumberOfNonCommentArgs, args) {
+ return args.length == expectedNumberOfNonCommentArgs + 1;
+}
+
+function commentArg(expectedNumberOfNonCommentArgs, args) {
+ if (argumentsIncludeComments(expectedNumberOfNonCommentArgs, args))
+ return args[0];
+
+ return null;
+}
+
+function nonCommentArg(desiredNonCommentArgIndex, expectedNumberOfNonCommentArgs, args) {
+ return argumentsIncludeComments(expectedNumberOfNonCommentArgs, args) ?
+ args[desiredNonCommentArgIndex] :
+ args[desiredNonCommentArgIndex - 1];
+}
+
+function _validateArguments(expectedNumberOfNonCommentArgs, args) {
+ if (!( args.length == expectedNumberOfNonCommentArgs ||
+ (args.length == expectedNumberOfNonCommentArgs + 1 && typeof(args[0]) == 'string') ))
+ error('Incorrect arguments passed to assert function');
+}
+
+function _assert(comment, booleanValue, failureMessage) {
+ if (!booleanValue)
+ throw new JsUnitException(comment, failureMessage);
+}
+
+function assert() {
+ _validateArguments(1, arguments);
+ var booleanValue=nonCommentArg(1, 1, arguments);
+
+ if (typeof(booleanValue) != 'boolean')
+ error('Bad argument to assert(boolean)');
+
+ _assert(commentArg(1, arguments), booleanValue === true, 'Call to assert(boolean) with false');
+}
+
+function assertTrue() {
+ _validateArguments(1, arguments);
+ var booleanValue=nonCommentArg(1, 1, arguments);
+
+ if (typeof(booleanValue) != 'boolean')
+ error('Bad argument to assertTrue(boolean)');
+
+ _assert(commentArg(1, arguments), booleanValue === true, 'Call to assertTrue(boolean) with false');
+}
+
+function assertFalse() {
+ _validateArguments(1, arguments);
+ var booleanValue=nonCommentArg(1, 1, arguments);
+
+ if (typeof(booleanValue) != 'boolean')
+ error('Bad argument to assertFalse(boolean)');
+
+ _assert(commentArg(1, arguments), booleanValue === false, 'Call to assertFalse(boolean) with true');
+}
+
+function assertEquals() {
+ _validateArguments(2, arguments);
+ var var1=nonCommentArg(1, 2, arguments);
+ var var2=nonCommentArg(2, 2, arguments);
+ _assert(commentArg(2, arguments), var1 === var2, 'Expected ' + var1 + ' (' + typeof(var1) + ') but was ' +
_displayStringForValue(var2) + ' (' + typeof(var2) + ')');
+}
+
+function assertNotEquals() {
+ _validateArguments(2, arguments);
+ var var1=nonCommentArg(1, 2, arguments);
+ var var2=nonCommentArg(2, 2, arguments);
+ _assert(commentArg(2, arguments), var1 !== var2, 'Expected not to be ' + _displayStringForValue(var2));
+}
+
+function assertNull() {
+ _validateArguments(1, arguments);
+ var aVar=nonCommentArg(1, 1, arguments);
+ _assert(commentArg(1, arguments), aVar === null, 'Expected null but was ' + _displayStringForValue(aVar));
+}
+
+function assertNotNull() {
+ _validateArguments(1, arguments);
+ var aVar=nonCommentArg(1, 1, arguments);
+ _assert(commentArg(1, arguments), aVar !== null, 'Expected not to be null');
+}
+
+function assertUndefined() {
+ _validateArguments(1, arguments);
+ var aVar=nonCommentArg(1, 1, arguments);
+ _assert(commentArg(1, arguments), aVar === top.JSUNIT_UNDEFINED_VALUE, 'Expected undefined but was ' +
_displayStringForValue(aVar));
+}
+
+function assertNotUndefined() {
+ _validateArguments(1, arguments);
+ var aVar=nonCommentArg(1, 1, arguments);
+ _assert(commentArg(1, arguments), aVar !== top.JSUNIT_UNDEFINED_VALUE, 'Expected not to be undefined');
+}
+
+function assertNaN() {
+ _validateArguments(1, arguments);
+ var aVar=nonCommentArg(1, 1, arguments);
+ _assert(commentArg(1, arguments), isNaN(aVar), 'Expected NaN');
+}
+
+function assertNotNaN() {
+ _validateArguments(1, arguments);
+ var aVar=nonCommentArg(1, 1, arguments);
+ _assert(commentArg(1, arguments), !isNaN(aVar), 'Expected not NaN');
+}
+
+// GJS: assertRaises(function)
+function assertRaises() {
+ _validateArguments(1, arguments);
+ var fun=nonCommentArg(1, 1, arguments);
+ var exception;
+
+ if (typeof(fun) != 'function')
+ error("Bad argument to assertRaises(function)");
+
+ var retval;
+ try {
+ retval = fun();
+ } catch (e) {
+ exception = e;
+ }
+
+ _assert(commentArg(1, arguments), exception !== top.JSUNIT_UNDEFINED_VALUE, "Call to
assertRaises(function) did not raise an exception. Return value was " + _displayStringForValue(retval) + ' ('
+ typeof(retval) + ')');
+}
+
+function isLoaded() {
+ return isTestPageLoaded;
+}
+
+function setUp() {
+}
+
+function tearDown() {
+}
+
+function getFunctionName(aFunction) {
+ var name = aFunction.toString().match(/function (\w*)/)[1];
+
+ if ((name == null) || (name.length == 0))
+ name = 'anonymous';
+
+ return name;
+}
+
+function parseErrorStack(excp)
+{
+ var stack = [];
+ var name;
+
+ if (!excp || !excp.stack)
+ {
+ return stack;
+ }
+
+ var stacklist = excp.stack.split('\n');
+
+ for (var i = 0; i < stacklist.length - 1; i++)
+ {
+ var framedata = stacklist[i];
+
+ name = framedata.match(/^(\w*)/)[1];
+ if (!name) {
+ name = 'anonymous';
+ }
+
+ var line = framedata.match(/(:\d+)$/)[1];
+ if (line) {
+ name += line;
+ }
+
+ stack[stack.length] = name;
+ }
+ // remove top level anonymous functions to match IE
+
+ while (stack.length && stack[stack.length - 1] == 'anonymous')
+ {
+ stack.length = stack.length - 1;
+ }
+ return stack;
+}
+
+function JsUnitException(comment, message) {
+ this.isJsUnitException = true;
+ this.comment = comment;
+ this.message = message;
+ this.stack = (new Error()).stack;
+}
+
+JsUnitException.prototype = Object.create(Error.prototype, {});
+
+function warn() {
+ if (top.tracer != null)
+ top.tracer.warn(arguments[0], arguments[1]);
+}
+
+function inform() {
+ if (top.tracer != null)
+ top.tracer.inform(arguments[0], arguments[1]);
+}
+
+function info() {
+ inform(arguments[0], arguments[1]);
+}
+
+function debug() {
+ if (top.tracer != null)
+ top.tracer.debug(arguments[0], arguments[1]);
+}
+
+function setjsUnitTracer(ajsUnitTracer) {
+ top.tracer=ajsUnitTracer;
+}
+
+function trim(str) {
+ if (str == null)
+ return null;
+
+ var startingIndex = 0;
+ var endingIndex = str.length-1;
+
+ while (str.substring(startingIndex, startingIndex+1) == ' ')
+ startingIndex++;
+
+ while (str.substring(endingIndex, endingIndex+1) == ' ')
+ endingIndex--;
+
+ if (endingIndex < startingIndex)
+ return '';
+
+ return str.substring(startingIndex, endingIndex+1);
+}
+
+function isBlank(str) {
+ return trim(str) == '';
+}
+
+// the functions push(anArray, anObject) and pop(anArray)
+// exist because the JavaScript Array.push(anObject) and Array.pop()
+// functions are not available in IE 5.0
+
+function push(anArray, anObject) {
+ anArray[anArray.length]=anObject;
+}
+function pop(anArray) {
+ if (anArray.length>=1) {
+ delete anArray[anArray.length - 1];
+ anArray.length--;
+ }
+}
+
+// safe, strict access to jsUnitParmHash
+function jsUnitGetParm(name)
+{
+ if (typeof(top.jsUnitParmHash[name]) != 'undefined')
+ {
+ return top.jsUnitParmHash[name];
+ }
+ return null;
+}
+
+if (top && typeof(top.xbDEBUG) != 'undefined' && top.xbDEBUG.on && top.testManager)
+{
+ top.xbDebugTraceObject('top.testManager.containerTestFrame', 'JSUnitException');
+ // asserts
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', '_displayStringForValue');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'error');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'argumentsIncludeComments');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'commentArg');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'nonCommentArg');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', '_validateArguments');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', '_assert');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'assert');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'assertTrue');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'assertEquals');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'assertNotEquals');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'assertNull');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'assertNotNull');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'assertUndefined');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'assertNotUndefined');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'assertNaN');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'assertNotNaN');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'isLoaded');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'setUp');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'tearDown');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'getFunctionName');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'warn');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'inform');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'debug');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'setjsUnitTracer');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'trim');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'isBlank');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'newOnLoadEvent');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'push');
+ top.xbDebugTraceFunction('top.testManager.containerTestFrame', 'pop');
+}
+
+function newOnLoadEvent() {
+ isTestPageLoaded = true;
+}
+
+function jsUnitSetOnLoad(windowRef, onloadHandler)
+{
+ var isKonqueror = navigator.userAgent.indexOf('Konqueror/') != -1 ||
+ navigator.userAgent.indexOf('Safari/') != -1;
+
+ if (typeof(windowRef.attachEvent) != 'undefined') {
+ // Internet Explorer, Opera
+ windowRef.attachEvent("onload", onloadHandler);
+ } else if (typeof(windowRef.addEventListener) != 'undefined' && !isKonqueror){
+ // Mozilla, Konqueror
+ // exclude Konqueror due to load issues
+ windowRef.addEventListener("load", onloadHandler, false);
+ } else if (typeof(windowRef.document.addEventListener) != 'undefined' && !isKonqueror) {
+ // DOM 2 Events
+ // exclude Mozilla, Konqueror due to load issues
+ windowRef.document.addEventListener("load", onloadHandler, false);
+ } else if (typeof(windowRef.onload) != 'undefined' && windowRef.onload) {
+ windowRef.jsunit_original_onload = windowRef.onload;
+ windowRef.onload = function() { windowRef.jsunit_original_onload(); onloadHandler(); };
+ } else {
+ // browsers that do not support windowRef.attachEvent or
+ // windowRef.addEventListener will override a page's own onload event
+ windowRef.onload=onloadHandler;
+ }
+}
+
+// GJS: comment out as isLoaded() isn't terribly useful for us
+//jsUnitSetOnLoad(window, newOnLoadEvent);
+
+// GJS: entry point to run all functions named as test*, surrounded by
+// calls to setUp() and tearDown()
+function gjstestRun(window_, setUp, tearDown) {
+ var propName;
+ var rv = 0;
+ var failures = [];
+ if (!window_) window_ = window;
+ if (!setUp) setUp = window_.setUp;
+ if (!tearDown) tearDown = window_.tearDown;
+
+ for (propName in window_) {
+ if (!propName.match(/^test\w+/))
+ continue;
+
+ var testFunction = window_[propName];
+ if (typeof(testFunction) != 'function')
+ continue;
+
+ log("running test " + propName);
+
+ setUp();
+ try {
+ testFunction();
+ } catch (e) {
+ var result = null;
+ if (typeof(e.isJsUnitException) != 'undefined' && e.isJsUnitException) {
+ result = '';
+ if (e.comment != null)
+ result += ('"' + e.comment + '"\n');
+
+ result += e.message;
+
+ if (e.stack)
+ result += '\n\nStack trace follows:\n' + e.stack;
+
+ // assertion failure, kind of expected so just log it and flag the
+ // whole test as failed
+ log(result);
+ rv = 1;
+ failures.push(propName);
+ }
+ else {
+ // unexpected error, let the shell handle it
+ throw e;
+ }
+ }
+ tearDown();
+ }
+
+ if (failures.length > 0) {
+ log(failures.length + " tests failed in this file");
+ log("Failures were: " + failures.join(", "));
+ }
+
+ // if gjstestRun() is the last call in a file, this becomes the
+ // exit code of the test program, so 0 = success, 1 = failed
+ return rv;
+}
diff --git a/modules/gjs/lang.js b/modules/gjs/lang.js
new file mode 100644
index 0000000..1852dbe
--- /dev/null
+++ b/modules/gjs/lang.js
@@ -0,0 +1,488 @@
+/* -*- mode: js; indent-tabs-mode: nil; -*- */
+// Copyright (c) 2008 litl, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+// Utilities that are "meta-language" things like manipulating object props
+
+const Gi = imports._gi;
+
+function countProperties(obj) {
+ let count = 0;
+ for (let property in obj) {
+ count += 1;
+ }
+ return count;
+}
+
+function getPropertyDescriptor(obj, property) {
+ if (obj.hasOwnProperty(property))
+ return Object.getOwnPropertyDescriptor(obj, property);
+ return getPropertyDescriptor(Object.getPrototypeOf(obj), property);
+}
+
+function _copyProperty(source, dest, property) {
+ let descriptor = getPropertyDescriptor(source, property);
+ Object.defineProperty(dest, property, descriptor);
+}
+
+function copyProperties(source, dest) {
+ for (let property in source) {
+ _copyProperty(source, dest, property);
+ }
+}
+
+function copyPublicProperties(source, dest) {
+ for (let property in source) {
+ if (typeof(property) == 'string' &&
+ property.substring(0, 1) == '_') {
+ continue;
+ } else {
+ _copyProperty(source, dest, property);
+ }
+ }
+}
+
+/**
+ * Binds obj to callback. Makes it possible to refer to "obj"
+ * using this within the callback.
+ * @param {object} obj the object to bind
+ * @param {function} callback callback to bind obj in
+ * @param arguments additional arguments to the callback
+ * @returns: a new callback
+ * @type: function
+ */
+function bind(obj, callback) {
+ if (typeof(obj) != 'object') {
+ throw new Error(
+ "first argument to Lang.bind() must be an object, not " +
+ typeof(obj));
+ }
+
+ if (typeof(callback) != 'function') {
+ throw new Error(
+ "second argument to Lang.bind() must be a function, not " +
+ typeof(callback));
+ }
+
+ // Use ES5 Function.prototype.bind, but only if not passing any bindArguments,
+ // because ES5 has them at the beginning, not at the end
+ if (arguments.length == 2)
+ return callback.bind(obj);
+
+ let me = obj;
+ let bindArguments = Array.prototype.slice.call(arguments, 2);
+
+ return function() {
+ let args = Array.prototype.slice.call(arguments);
+ args = args.concat(bindArguments);
+ return callback.apply(me, args);
+ };
+}
+
+// Class magic
+// Adapted from MooTools, MIT license
+// https://github.com/mootools/mootools-core
+
+function _Base() {
+ throw new TypeError('Cannot instantiate abstract class _Base');
+}
+
+_Base.__super__ = null;
+_Base.prototype._init = function() { };
+_Base.prototype._construct = function() {
+ this._init.apply(this, arguments);
+ return this;
+};
+_Base.prototype.__name__ = '_Base';
+_Base.prototype.toString = function() {
+ return '[object ' + this.__name__ + ']';
+};
+
+function _parent() {
+ if (!this.__caller__)
+ throw new TypeError("The method 'parent' cannot be called");
+
+ let caller = this.__caller__;
+ let name = caller._name;
+ let parent = caller._owner.__super__;
+
+ let previous = parent ? parent.prototype[name] : undefined;
+
+ if (!previous)
+ throw new TypeError("The method '" + name + "' is not on the superclass");
+
+ return previous.apply(this, arguments);
+}
+
+function _interfacePresent(required, proto) {
+ if (!proto.__interfaces__)
+ return false;
+ if (proto.__interfaces__.indexOf(required) !== -1)
+ return true; // implemented here
+ // Might be implemented on a parent class
+ return _interfacePresent(required, proto.constructor.__super__.prototype);
+}
+
+function getMetaClass(params) {
+ if (params.MetaClass)
+ return params.MetaClass;
+
+ if (params.Extends && params.Extends.prototype.__metaclass__)
+ return params.Extends.prototype.__metaclass__;
+
+ return null;
+}
+
+function Class(params) {
+ let metaClass = getMetaClass(params);
+
+ if (metaClass && metaClass != this.constructor) {
+ // Trick to apply variadic arguments to constructors --
+ // bind the arguments into the constructor function.
+ let args = Array.prototype.slice.call(arguments);
+ let curried = Function.prototype.bind.apply(metaClass, [,].concat(args));
+ return new curried();
+ } else {
+ return this._construct.apply(this, arguments);
+ }
+}
+
+Class.__super__ = _Base;
+Class.prototype = Object.create(_Base.prototype);
+Class.prototype.constructor = Class;
+Class.prototype.__name__ = 'Class';
+
+Class.prototype.wrapFunction = function(name, meth) {
+ if (meth._origin) meth = meth._origin;
+
+ function wrapper() {
+ let prevCaller = this.__caller__;
+ this.__caller__ = wrapper;
+ let result = meth.apply(this, arguments);
+ this.__caller__ = prevCaller;
+ return result;
+ }
+
+ wrapper._origin = meth;
+ wrapper._name = name;
+ wrapper._owner = this;
+
+ return wrapper;
+}
+
+Class.prototype.toString = function() {
+ return '[object ' + this.__name__ + ' for ' + this.prototype.__name__ + ']';
+};
+
+Class.prototype._construct = function(params) {
+ if (!params.Name) {
+ throw new TypeError("Classes require an explicit 'Name' parameter.");
+ }
+ let name = params.Name;
+
+ let parent = params.Extends;
+ if (!parent)
+ parent = _Base;
+
+ let newClass;
+ if (params.Abstract) {
+ newClass = function() {
+ throw new TypeError('Cannot instantiate abstract class ' + name);
+ };
+ } else {
+ newClass = function() {
+ this.__caller__ = null;
+
+ return this._construct.apply(this, arguments);
+ };
+ }
+
+ // Since it's not possible to create a constructor with
+ // a custom [[Prototype]], we have to do this to make
+ // "newClass instanceof Class" work, and so we can inherit
+ // methods/properties of Class.prototype, like wrapFunction.
+ newClass.__proto__ = this.constructor.prototype;
+
+ newClass.__super__ = parent;
+ newClass.prototype = Object.create(parent.prototype);
+ newClass.prototype.constructor = newClass;
+
+ newClass._init.apply(newClass, arguments);
+
+ let interfaces = params.Implements || [];
+ // If the parent already implements an interface, then we do too
+ if (parent instanceof Class)
+ interfaces = interfaces.filter((iface) => !parent.implements(iface));
+
+ Object.defineProperties(newClass.prototype, {
+ '__metaclass__': { writable: false,
+ configurable: false,
+ enumerable: false,
+ value: this.constructor },
+ '__interfaces__': { writable: false,
+ configurable: false,
+ enumerable: false,
+ value: interfaces }
+ });
+
+ interfaces.forEach((iface) => {
+ iface._check(newClass.prototype);
+ });
+
+ return newClass;
+};
+
+/**
+ * Check whether this class conforms to the interface "iface".
+ * @param {object} iface a Lang.Interface
+ * @returns: whether this class implements iface
+ * @type: boolean
+ */
+Class.prototype.implements = function (iface) {
+ if (_interfacePresent(iface, this.prototype))
+ return true;
+ if (this.__super__ instanceof Class)
+ return this.__super__.implements(iface);
+ return false;
+};
+
+Class.prototype._init = function(params) {
+ let name = params.Name;
+
+ let propertyObj = { };
+
+ let interfaces = params.Implements || [];
+ interfaces.forEach((iface) => {
+ Object.getOwnPropertyNames(iface.prototype)
+ .filter((name) => !name.startsWith('__') && name !== 'constructor')
+ .forEach((name) => {
+ let descriptor = Object.getOwnPropertyDescriptor(iface.prototype,
+ name);
+ // writable and enumerable are inherited, see note below
+ descriptor.configurable = false;
+ propertyObj[name] = descriptor;
+ });
+ });
+
+ Object.getOwnPropertyNames(params).forEach(function(name) {
+ if (['Name', 'Extends', 'Abstract', 'Implements'].indexOf(name) !== -1)
+ return;
+
+ let descriptor = Object.getOwnPropertyDescriptor(params, name);
+
+ if (typeof descriptor.value === 'function')
+ descriptor.value = this.wrapFunction(name, descriptor.value);
+
+ // we inherit writable and enumerable from the property
+ // descriptor of params (they're both true if created from an
+ // object literal)
+ descriptor.configurable = false;
+
+ propertyObj[name] = descriptor;
+ }.bind(this));
+
+ Object.defineProperties(this.prototype, propertyObj);
+ Object.defineProperties(this.prototype, {
+ '__name__': { writable: false,
+ configurable: false,
+ enumerable: false,
+ value: name },
+ 'parent': { writable: false,
+ configurable: false,
+ enumerable: false,
+ value: _parent }});
+};
+
+// This introduces the concept of a "meta-interface" which is given by the
+// MetaInterface property on an object's metaclass. For objects whose metaclass
+// is Lang.Class, the meta-interface is Lang.Interface. Subclasses of Lang.Class
+// such as GObject.Class supply their own meta-interface.
+// This is in order to enable creating GObject interfaces with Lang.Interface,
+// much as you can create GObject classes with Lang.Class.
+function _getMetaInterface(params) {
+ if (!params.Requires || params.Requires.length === 0)
+ return null;
+
+ let metaInterface = params.Requires.map((req) => {
+ if (req instanceof Interface)
+ return req.__super__;
+ for (let metaclass = req.prototype.__metaclass__; metaclass;
+ metaclass = metaclass.__super__) {
+ if (metaclass.hasOwnProperty('MetaInterface'))
+ return metaclass.MetaInterface;
+ }
+ return null;
+ })
+ .reduce((best, candidate) => {
+ // This function reduces to the "most derived" meta interface in the list.
+ if (best === null)
+ return candidate;
+ if (candidate === null)
+ return best;
+ for (let sup = candidate; sup; sup = sup.__super__) {
+ if (sup === best)
+ return candidate;
+ }
+ return best;
+ }, null);
+
+ // If we reach this point and we don't know the meta-interface, then it's
+ // most likely because there were only pure-C interfaces listed in Requires
+ // (and those don't have our magic properties.) However, all pure-C
+ // interfaces should require GObject.Object anyway.
+ if (metaInterface === null)
+ throw new Error('Did you forget to include GObject.Object in Requires?');
+
+ return metaInterface;
+}
+
+function Interface(params) {
+ let metaInterface = _getMetaInterface(params);
+ if (metaInterface && metaInterface !== this.constructor) {
+ // Trick to apply variadic arguments to constructors --
+ // bind the arguments into the constructor function.
+ let args = Array.prototype.slice.call(arguments);
+ let curried = Function.prototype.bind.apply(metaInterface, [,].concat(args));
+ return new curried();
+ }
+ return this._construct.apply(this, arguments);
+}
+
+Class.MetaInterface = Interface;
+
+/**
+ * Use this to signify a function that must be overridden in an implementation
+ * of the interface. Creating a class that doesn't override the function will
+ * throw an error.
+ */
+Interface.UNIMPLEMENTED = function () {
+ throw new Error('Not implemented');
+};
+
+Interface.__super__ = _Base;
+Interface.prototype = Object.create(_Base.prototype);
+Interface.prototype.constructor = Interface;
+Interface.prototype.__name__ = 'Interface';
+
+Interface.prototype._construct = function (params) {
+ if (!params.Name)
+ throw new TypeError("Interfaces require an explicit 'Name' parameter.");
+ let name = params.Name;
+
+ let newInterface = function () {
+ throw new TypeError('Cannot instantiate interface ' + name);
+ };
+
+ // See note in Class._construct(); this makes "newInterface instanceof
+ // Interface" work, and allows inheritance.
+ newInterface.__proto__ = this.constructor.prototype;
+
+ newInterface.__super__ = Interface;
+ newInterface.prototype = Object.create(Interface.prototype);
+ newInterface.prototype.constructor = newInterface;
+
+ newInterface._init.apply(newInterface, arguments);
+
+ Object.defineProperty(newInterface.prototype, '__metaclass__',
+ { writable: false,
+ configurable: false,
+ enumerable: false,
+ value: this.constructor });
+
+ return newInterface;
+};
+
+Interface.prototype._check = function (proto) {
+ // Check that proto implements all of this interface's required interfaces.
+ // "proto" refers to the object's prototype (which implements the interface)
+ // whereas "this.prototype" is the interface's prototype (which may still
+ // contain unimplemented methods.)
+
+ let unfulfilledReqs = this.prototype.__requires__.filter((required) => {
+ // Either the interface is not present or it is not listed before the
+ // interface that requires it or the class does not inherit it. This is
+ // so that required interfaces don't copy over properties from other
+ // interfaces that require them.
+ let interfaces = proto.__interfaces__;
+ return ((!_interfacePresent(required, proto) ||
+ interfaces.indexOf(required) > interfaces.indexOf(this)) &&
+ !(proto instanceof required));
+ }).map((required) =>
+ // __name__ is only present on GJS-created classes and will be the most
+ // accurate name. required.name will be present on introspected GObjects
+ // but is not preferred because it will be the C name. The last option
+ // is just so that we print something if there is garbage in Requires.
+ required.prototype.__name__ || required.name || required);
+ if (unfulfilledReqs.length > 0) {
+ throw new Error('The following interfaces must be implemented before ' +
+ this.prototype.__name__ + ': ' + unfulfilledReqs.join(', '));
+ }
+
+ // Check that this interface's required methods are implemented
+ let unimplementedFns = Object.getOwnPropertyNames(this.prototype)
+ .filter((p) => this.prototype[p] === Interface.UNIMPLEMENTED)
+ .filter((p) => !(p in proto) || proto[p] === Interface.UNIMPLEMENTED);
+ if (unimplementedFns.length > 0)
+ throw new Error('The following members of ' + this.prototype.__name__ +
+ ' are not implemented yet: ' + unimplementedFns.join(', '));
+};
+
+Interface.prototype.toString = function () {
+ return '[interface ' + this.__name__ + ' for ' + this.prototype.__name__ + ']';
+};
+
+Interface.prototype._init = function (params) {
+ let name = params.Name;
+
+ let propertyObj = {};
+ Object.getOwnPropertyNames(params)
+ .filter((name) => ['Name', 'Requires'].indexOf(name) === -1)
+ .forEach((name) => {
+ let descriptor = Object.getOwnPropertyDescriptor(params, name);
+
+ // Create wrappers on the interface object so that generics work (e.g.
+ // SomeInterface.some_function(this, blah) instead of
+ // SomeInterface.prototype.some_function.call(this, blah)
+ if (typeof descriptor.value === 'function') {
+ let interfaceProto = this.prototype; // capture in closure
+ this[name] = function () {
+ return interfaceProto[name].call.apply(interfaceProto[name],
+ arguments);
+ };
+ }
+
+ // writable and enumerable are inherited, see note in Class._init()
+ descriptor.configurable = false;
+
+ propertyObj[name] = descriptor;
+ });
+
+ Object.defineProperties(this.prototype, propertyObj);
+ Object.defineProperties(this.prototype, {
+ '__name__': { writable: false,
+ configurable: false,
+ enumerable: false,
+ value: name },
+ '__requires__': { writable: false,
+ configurable: false,
+ enumerable: false,
+ value: params.Requires || [] }
+ });
+};
diff --git a/modules/gjs/mainloop.js b/modules/gjs/mainloop.js
new file mode 100644
index 0000000..89c37fe
--- /dev/null
+++ b/modules/gjs/mainloop.js
@@ -0,0 +1,87 @@
+/* -*- mode: js; indent-tabs-mode: nil; -*- */
+// Copyright (c) 2012 Giovanni Campagna <scampa giovanni gmail com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+// A layer of convenience and backwards-compatibility over GLib MainLoop facilities
+
+const GLib = imports.gi.GLib;
+const GObject = imports.gi.GObject;
+
+var _mainLoops = {};
+
+function run(name) {
+ if (!_mainLoops[name])
+ _mainLoops[name] = GLib.MainLoop.new(null, false);
+
+ _mainLoops[name].run();
+}
+
+function quit(name) {
+ if (!_mainLoops[name])
+ throw new Error("No main loop with this id");
+
+ let loop = _mainLoops[name];
+ delete _mainLoops[name];
+
+ if (!loop.is_running())
+ throw new Error("Main loop was stopped already");
+
+ loop.quit();
+}
+
+function idle_source(handler, priority) {
+ let s = GLib.idle_source_new();
+ GObject.source_set_closure(s, handler);
+ if (priority !== undefined)
+ s.set_priority(priority);
+ return s;
+}
+
+function idle_add(handler, priority) {
+ return idle_source(handler, priority).attach(null);
+}
+
+function timeout_source(timeout, handler, priority) {
+ let s = GLib.timeout_source_new(timeout);
+ GObject.source_set_closure(s, handler);
+ if (priority !== undefined)
+ s.set_priority(priority);
+ return s;
+}
+
+function timeout_seconds_source(timeout, handler, priority) {
+ let s = GLib.timeout_source_new_seconds(timeout);
+ GObject.source_set_closure(s, handler);
+ if (priority !== undefined)
+ s.set_priority(priority);
+ return s;
+}
+
+function timeout_add(timeout, handler, priority) {
+ return timeout_source(timeout, handler, priority).attach(null);
+}
+
+function timeout_add_seconds(timeout, handler, priority) {
+ return timeout_seconds_source(timeout, handler, priority).attach(null);
+}
+
+function source_remove(id) {
+ return GLib.source_remove(id);
+}
diff --git a/modules/gjs/package.js b/modules/gjs/package.js
new file mode 100644
index 0000000..fa1d5a8
--- /dev/null
+++ b/modules/gjs/package.js
@@ -0,0 +1,247 @@
+// Copyright 2012 Giovanni Campagna
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+/**
+ * This module provides a set of convenience APIs for building packaged
+ * applications.
+ */
+
+const GLib = imports.gi.GLib;
+const GIRepository = imports.gi.GIRepository;
+const Gio = imports.gi.Gio;
+const System = imports.system;
+
+const Gettext = imports.gettext;
+
+/*< public >*/
+var name;
+var version;
+var prefix;
+var datadir;
+var libdir;
+var pkgdatadir;
+var pkglibdir;
+var moduledir;
+var localedir;
+
+/*< private >*/
+let _pkgname;
+let _base;
+
+function _findEffectiveEntryPointName() {
+ let entryPoint = System.programInvocationName;
+ while (GLib.file_test(entryPoint, GLib.FileTest.IS_SYMLINK))
+ entryPoint = GLib.file_read_link(entryPoint);
+
+ return GLib.path_get_basename(entryPoint);
+}
+
+function _runningFromSource() {
+ let binary = Gio.File.new_for_path(System.programInvocationName);
+ let sourceBinary = Gio.File.new_for_path('./src/' + name);
+ return binary.equal(sourceBinary);
+}
+
+function _makeNamePath(name) {
+ return '/' + name.replace('.', '/', 'g');
+}
+
+/**
+ * init:
+ * @params: package parameters
+ *
+ * Initialize directories and global variables. Must be called
+ * before any of other API in Package is used.
+ * @params must be an object with at least the following keys:
+ * - name: the package name ($(PACKAGE_NAME) in autotools,
+ * eg. org.foo.Bar)
+ * - version: the package version
+ * - prefix: the installation prefix
+ *
+ * init() will take care to check if the program is running from
+ * the source directory or not, by looking for a 'src' directory.
+ *
+ * At the end, the global variable 'pkg' will contain the
+ * Package module (imports.package). Additionally, the following
+ * module variables will be available:
+ * - name: the base name of the entry point (eg. org.foo.Bar.App)
+ * - version: same as in @params
+ * - prefix: the installation prefix (as passed in @params)
+ * - datadir, libdir: the final datadir and libdir when installed;
+ * usually, these would be prefix + '/share' and
+ * and prefix + '/lib' (or '/lib64')
+ * - pkgdatadir: the directory to look for private data files, such as
+ * images, stylesheets and UI definitions;
+ * this will be datadir + name when installed and
+ * './data' when running from the source tree
+ * - pkglibdir: the directory to look for private typelibs and C
+ * libraries;
+ * this will be libdir + name when installed and
+ * './lib' when running from the source tree
+ * - moduledir: the directory to look for JS modules;
+ * this will be pkglibdir when installed and
+ * './src' when running from the source tree
+ * - localedir: the directory containing gettext translation files;
+ * this will be datadir + '/locale' when installed
+ * and './po' in the source tree
+ *
+ * All paths are absolute and will not end with '/'.
+ *
+ * As a side effect, init() calls GLib.set_prgname().
+ */
+function init(params) {
+ window.pkg = imports.package;
+ _pkgname = params.name;
+ name = _findEffectiveEntryPointName();
+ version = params.version;
+
+ // Must call it first, because it can only be called
+ // once, and other library calls might have it as a
+ // side effect
+ GLib.set_prgname(name);
+
+ prefix = params.prefix;
+ libdir = params.libdir;
+ datadir = GLib.build_filenamev([prefix, 'share']);
+ let libpath, girpath;
+
+ if (_runningFromSource()) {
+ log('Running from source tree, using local files');
+ // Running from source directory
+ _base = GLib.get_current_dir();
+ pkglibdir = GLib.build_filenamev([_base, 'lib']);
+ libpath = GLib.build_filenamev([pkglibdir, '.libs']);
+ girpath = pkglibdir;
+ pkgdatadir = GLib.build_filenamev([_base, 'data']);
+ localedir = GLib.build_filenamev([_base, 'po']);
+ moduledir = GLib.build_filenamev([_base, 'src']);
+ } else {
+ _base = prefix;
+ pkglibdir = GLib.build_filenamev([libdir, _pkgname]);
+ libpath = pkglibdir;
+ girpath = GLib.build_filenamev([pkglibdir, 'girepository-1.0']);
+ pkgdatadir = GLib.build_filenamev([datadir, _pkgname]);
+ localedir = GLib.build_filenamev([datadir, 'locale']);
+
+ try {
+ let resource = Gio.Resource.load(GLib.build_filenamev([pkgdatadir,
+ name + '.src.gresource']));
+ resource._register();
+
+ moduledir = 'resource://' + _makeNamePath(name) + '/js';
+ } catch(e) {
+ moduledir = pkgdatadir;
+ }
+ }
+
+ imports.searchPath.unshift(moduledir);
+ GIRepository.Repository.prepend_search_path(girpath);
+ GIRepository.Repository.prepend_library_path(libpath);
+
+ try {
+ let resource = Gio.Resource.load(GLib.build_filenamev([pkgdatadir,
+ name + '.data.gresource']));
+ resource._register();
+ } catch(e) { }
+}
+
+/**
+ * start:
+ * @params: see init()
+ *
+ * This is a convenience function if your package has a
+ * single entry point.
+ * You must define a main(ARGV) function inside a main.js
+ * module in moduledir.
+ */
+function start(params) {
+ init(params);
+ run(imports.main);
+}
+
+/**
+ * run:
+ * @module: the module to run
+ *
+ * This is the function to use if you want to have multiple
+ * entry points in one package.
+ * You must define a main(ARGV) function inside the passed
+ * in module, and then the launcher would be
+ *
+ * imports.package.init(...);
+ * imports.package.run(imports.entrypoint);
+ */
+function run(module) {
+ return module.main([System.programInvocationName].concat(ARGV));
+}
+
+/**
+ * require:
+ * @libs: the external dependencies to import
+ *
+ * Mark a dependency on a specific version of one or more
+ * external GI typelibs.
+ * @libs must be an object whose keys are a typelib name,
+ * and values are the respective version. The empty string
+ * indicates any version.
+ */
+function require(libs) {
+ for (let l in libs) {
+ let version = libs[l];
+
+ if (version != '')
+ imports.gi.versions[l] = version;
+
+ try {
+ imports.gi[l];
+ } catch(e) {
+ printerr('Unsatisfied dependency: ' + e.message);
+ System.exit(1);
+ }
+ }
+}
+
+function initGettext() {
+ Gettext.bindtextdomain(_pkgname, localedir);
+ Gettext.textdomain(_pkgname);
+
+ let gettext = imports.gettext;
+ window._ = gettext.gettext;
+ window.C_ = gettext.pgettext;
+ window.N_ = function(x) { return x; }
+}
+
+function initFormat() {
+ let format = imports.format;
+ String.prototype.format = format.format;
+}
+
+function initSubmodule(name) {
+ if (moduledir != pkgdatadir) {
+ // Running from source tree, add './name' to search paths
+
+ let submoduledir = GLib.build_filenamev([_base, name]);
+ let libpath = GLib.build_filenamev([submoduledir, '.libs']);
+ GIRepository.Repository.prepend_search_path(submoduledir);
+ GIRepository.Repository.prepend_library_path(libpath);
+ } else {
+ // Running installed, submodule is in $(pkglibdir), nothing to do
+ }
+}
diff --git a/modules/gjs/signals.js b/modules/gjs/signals.js
new file mode 100644
index 0000000..0905e53
--- /dev/null
+++ b/modules/gjs/signals.js
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2008 litl, LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+// A couple principals of this simple signal system:
+// 1) should look just like our GObject signal binding
+// 2) memory and safety matter more than speed of connect/disconnect/emit
+// 3) the expectation is that a given object will have a very small number of
+// connections, but they may be to different signal names
+
+function _connect(name, callback) {
+ // be paranoid about callback arg since we'd start to throw from emit()
+ // if it was messed up
+ if (typeof(callback) != 'function')
+ throw new Error("When connecting signal must give a callback that is a function");
+
+ // we instantiate the "signal machinery" only on-demand if anything
+ // gets connected.
+ if (!('_signalConnections' in this)) {
+ this._signalConnections = [];
+ this._nextConnectionId = 1;
+ }
+
+ let id = this._nextConnectionId;
+ this._nextConnectionId += 1;
+
+ // this makes it O(n) in total connections to emit, but I think
+ // it's right to optimize for low memory and reentrancy-safety
+ // rather than speed
+ this._signalConnections.push({ 'id' : id,
+ 'name' : name,
+ 'callback' : callback,
+ 'disconnected' : false
+ });
+ return id;
+}
+
+function _disconnect(id) {
+ if ('_signalConnections' in this) {
+ let i;
+ let length = this._signalConnections.length;
+ for (i = 0; i < length; ++i) {
+ let connection = this._signalConnections[i];
+ if (connection.id == id) {
+ if (connection.disconnected)
+ throw new Error("Signal handler id " + id + " already disconnected");
+
+ // set a flag to deal with removal during emission
+ connection.disconnected = true;
+ this._signalConnections.splice(i, 1);
+
+ return;
+ }
+ }
+ }
+ throw new Error("No signal connection " + id + " found");
+}
+
+function _disconnectAll() {
+ if ('_signalConnections' in this) {
+ while (this._signalConnections.length > 0) {
+ _disconnect.call(this, this._signalConnections[0].id);
+ }
+ }
+}
+
+function _emit(name /* , arg1, arg2 */) {
+ // may not be any signal handlers at all, if not then return
+ if (!('_signalConnections' in this))
+ return;
+
+ // To deal with re-entrancy (removal/addition while
+ // emitting), we copy out a list of what was connected
+ // at emission start; and just before invoking each
+ // handler we check its disconnected flag.
+ let handlers = [];
+ let i;
+ let length = this._signalConnections.length;
+ for (i = 0; i < length; ++i) {
+ let connection = this._signalConnections[i];
+ if (connection.name == name) {
+ handlers.push(connection);
+ }
+ }
+
+ // create arg array which is emitter + everything passed in except
+ // signal name. Would be more convenient not to pass emitter to
+ // the callback, but trying to be 100% consistent with GObject
+ // which does pass it in. Also if we pass in the emitter here,
+ // people don't create closures with the emitter in them,
+ // which would be a cycle.
+
+ let arg_array = [ this ];
+ // arguments[0] should be signal name so skip it
+ length = arguments.length;
+ for (i = 1; i < length; ++i) {
+ arg_array.push(arguments[i]);
+ }
+
+ length = handlers.length;
+ for (i = 0; i < length; ++i) {
+ let connection = handlers[i];
+ if (!connection.disconnected) {
+ try {
+ // since we pass "null" for this, the global object will be used.
+ let ret = connection.callback.apply(null, arg_array);
+
+ // if the callback returns true, we don't call the next
+ // signal handlers
+ if (ret === true) {
+ break;
+ }
+ } catch(e) {
+ // just log any exceptions so that callbacks can't disrupt
+ // signal emission
+ logError(e, "Exception in callback for signal: "+name);
+ }
+ }
+ }
+}
+
+function addSignalMethods(proto) {
+ proto.connect = _connect;
+ proto.disconnect = _disconnect;
+ proto.emit = _emit;
+ // this one is not in GObject, but useful
+ proto.disconnectAll = _disconnectAll;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]