summaryrefslogtreecommitdiffstats
path: root/toolkit/components/osfile/modules/osfile_shared_allthreads.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/osfile/modules/osfile_shared_allthreads.jsm')
-rw-r--r--toolkit/components/osfile/modules/osfile_shared_allthreads.jsm1315
1 files changed, 1315 insertions, 0 deletions
diff --git a/toolkit/components/osfile/modules/osfile_shared_allthreads.jsm b/toolkit/components/osfile/modules/osfile_shared_allthreads.jsm
new file mode 100644
index 000000000..c5c505102
--- /dev/null
+++ b/toolkit/components/osfile/modules/osfile_shared_allthreads.jsm
@@ -0,0 +1,1315 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * OS.File utilities used by all threads.
+ *
+ * This module defines:
+ * - logging;
+ * - the base constants;
+ * - base types and primitives for declaring new types;
+ * - primitives for importing C functions;
+ * - primitives for dealing with integers, pointers, typed arrays;
+ * - the base class OSError;
+ * - a few additional utilities.
+ */
+
+// Boilerplate used to be able to import this module both from the main
+// thread and from worker threads.
+
+// Since const is lexically scoped, hoist the
+// conditionally-useful definition ourselves.
+const Cu = typeof Components != "undefined" ? Components.utils : undefined;
+const Ci = typeof Components != "undefined" ? Components.interfaces : undefined;
+const Cc = typeof Components != "undefined" ? Components.classes : undefined;
+
+/**
+ * A constructor for messages that require transfers instead of copies.
+ *
+ * See BasePromiseWorker.Meta.
+ *
+ * @constructor
+ */
+var Meta;
+if (typeof Components != "undefined") {
+ // Global definition of |exports|, to keep everybody happy.
+ // In non-main thread, |exports| is provided by the module
+ // loader.
+ this.exports = {};
+
+ Cu.import("resource://gre/modules/Services.jsm", this);
+ Meta = Cu.import("resource://gre/modules/PromiseWorker.jsm", {}).BasePromiseWorker.Meta;
+} else {
+ importScripts("resource://gre/modules/workers/require.js");
+ Meta = require("resource://gre/modules/workers/PromiseWorker.js").Meta;
+}
+
+var EXPORTED_SYMBOLS = [
+ "LOG",
+ "clone",
+ "Config",
+ "Constants",
+ "Type",
+ "HollowStructure",
+ "OSError",
+ "Library",
+ "declareFFI",
+ "declareLazy",
+ "declareLazyFFI",
+ "normalizeBufferArgs",
+ "projectValue",
+ "isArrayBuffer",
+ "isTypedArray",
+ "defineLazyGetter",
+ "OS" // Warning: this exported symbol will disappear
+];
+
+////////////////////// Configuration of OS.File
+
+var Config = {
+ /**
+ * If |true|, calls to |LOG| are shown. Otherwise, they are hidden.
+ *
+ * This configuration option is controlled by preference "toolkit.osfile.log".
+ */
+ DEBUG: false,
+
+ /**
+ * TEST
+ */
+ TEST: false
+};
+exports.Config = Config;
+
+////////////////////// OS Constants
+
+if (typeof Components != "undefined") {
+ // On the main thread, OS.Constants is defined by a xpcom
+ // component. On other threads, it is available automatically
+ Cu.import("resource://gre/modules/ctypes.jsm");
+ Cc["@mozilla.org/net/osfileconstantsservice;1"].
+ getService(Ci.nsIOSFileConstantsService).init();
+}
+
+exports.Constants = OS.Constants;
+
+///////////////////// Utilities
+
+// Define a lazy getter for a property
+var defineLazyGetter = function defineLazyGetter(object, name, getter) {
+ Object.defineProperty(object, name, {
+ configurable: true,
+ get: function lazy() {
+ delete this[name];
+ let value = getter.call(this);
+ Object.defineProperty(object, name, {
+ value: value
+ });
+ return value;
+ }
+ });
+};
+exports.defineLazyGetter = defineLazyGetter;
+
+
+///////////////////// Logging
+
+/**
+ * The default implementation of the logger.
+ *
+ * The choice of logger can be overridden with Config.TEST.
+ */
+var gLogger;
+if (typeof window != "undefined" && window.console && console.log) {
+ gLogger = console.log.bind(console, "OS");
+} else {
+ gLogger = function(...args) {
+ dump("OS " + args.join(" ") + "\n");
+ };
+}
+
+/**
+ * Attempt to stringify an argument into something useful for
+ * debugging purposes, by using |.toString()| or |JSON.stringify|
+ * if available.
+ *
+ * @param {*} arg An argument to be stringified if possible.
+ * @return {string} A stringified version of |arg|.
+ */
+var stringifyArg = function stringifyArg(arg) {
+ if (typeof arg === "string") {
+ return arg;
+ }
+ if (arg && typeof arg === "object") {
+ let argToString = "" + arg;
+
+ /**
+ * The only way to detect whether this object has a non-default
+ * implementation of |toString| is to check whether it returns
+ * '[object Object]'. Unfortunately, we cannot simply compare |arg.toString|
+ * and |Object.prototype.toString| as |arg| typically comes from another
+ * compartment.
+ */
+ if (argToString === "[object Object]") {
+ return JSON.stringify(arg, function(key, value) {
+ if (isTypedArray(value)) {
+ return "["+ value.constructor.name + " " + value.byteOffset + " " + value.byteLength + "]";
+ }
+ if (isArrayBuffer(arg)) {
+ return "[" + value.constructor.name + " " + value.byteLength + "]";
+ }
+ return value;
+ });
+ } else {
+ return argToString;
+ }
+ }
+ return arg;
+};
+
+var LOG = function (...args) {
+ if (!Config.DEBUG) {
+ // If logging is deactivated, don't log
+ return;
+ }
+
+ let logFunc = gLogger;
+ if (Config.TEST && typeof Components != "undefined") {
+ // If _TESTING_LOGGING is set, and if we are on the main thread,
+ // redirect logs to Services.console, for testing purposes
+ logFunc = function logFunc(...args) {
+ let message = ["TEST", "OS"].concat(args).join(" ");
+ Services.console.logStringMessage(message + "\n");
+ };
+ }
+ logFunc.apply(null, args.map(stringifyArg));
+};
+
+exports.LOG = LOG;
+
+/**
+ * Return a shallow clone of the enumerable properties of an object.
+ *
+ * Utility used whenever normalizing options requires making (shallow)
+ * changes to an option object. The copy ensures that we do not modify
+ * a client-provided object by accident.
+ *
+ * Note: to reference and not copy specific fields, provide an optional
+ * |refs| argument containing their names.
+ *
+ * @param {JSON} object Options to be cloned.
+ * @param {Array} refs An optional array of field names to be passed by
+ * reference instead of copying.
+ */
+var clone = function (object, refs = []) {
+ let result = {};
+ // Make a reference between result[key] and object[key].
+ let refer = function refer(result, key, object) {
+ Object.defineProperty(result, key, {
+ enumerable: true,
+ get: function() {
+ return object[key];
+ },
+ set: function(value) {
+ object[key] = value;
+ }
+ });
+ };
+ for (let k in object) {
+ if (refs.indexOf(k) < 0) {
+ result[k] = object[k];
+ } else {
+ refer(result, k, object);
+ }
+ }
+ return result;
+};
+
+exports.clone = clone;
+
+///////////////////// Abstractions above js-ctypes
+
+/**
+ * Abstraction above js-ctypes types.
+ *
+ * Use values of this type to register FFI functions. In addition to the
+ * usual features of js-ctypes, values of this type perform the necessary
+ * transformations to ensure that C errors are handled nicely, to connect
+ * resources with their finalizer, etc.
+ *
+ * @param {string} name The name of the type. Must be unique.
+ * @param {CType} implementation The js-ctypes implementation of the type.
+ *
+ * @constructor
+ */
+function Type(name, implementation) {
+ if (!(typeof name == "string")) {
+ throw new TypeError("Type expects as first argument a name, got: "
+ + name);
+ }
+ if (!(implementation instanceof ctypes.CType)) {
+ throw new TypeError("Type expects as second argument a ctypes.CType"+
+ ", got: " + implementation);
+ }
+ Object.defineProperty(this, "name", { value: name });
+ Object.defineProperty(this, "implementation", { value: implementation });
+}
+Type.prototype = {
+ /**
+ * Serialize a value of |this| |Type| into a format that can
+ * be transmitted as a message (not necessarily a string).
+ *
+ * In the default implementation, the method returns the
+ * value unchanged.
+ */
+ toMsg: function default_toMsg(value) {
+ return value;
+ },
+ /**
+ * Deserialize a message to a value of |this| |Type|.
+ *
+ * In the default implementation, the method returns the
+ * message unchanged.
+ */
+ fromMsg: function default_fromMsg(msg) {
+ return msg;
+ },
+ /**
+ * Import a value from C.
+ *
+ * In this default implementation, return the value
+ * unchanged.
+ */
+ importFromC: function default_importFromC(value) {
+ return value;
+ },
+
+ /**
+ * A pointer/array used to pass data to the foreign function.
+ */
+ get in_ptr() {
+ delete this.in_ptr;
+ let ptr_t = new PtrType(
+ "[in] " + this.name + "*",
+ this.implementation.ptr,
+ this);
+ Object.defineProperty(this, "in_ptr",
+ {
+ get: function() {
+ return ptr_t;
+ }
+ });
+ return ptr_t;
+ },
+
+ /**
+ * A pointer/array used to receive data from the foreign function.
+ */
+ get out_ptr() {
+ delete this.out_ptr;
+ let ptr_t = new PtrType(
+ "[out] " + this.name + "*",
+ this.implementation.ptr,
+ this);
+ Object.defineProperty(this, "out_ptr",
+ {
+ get: function() {
+ return ptr_t;
+ }
+ });
+ return ptr_t;
+ },
+
+ /**
+ * A pointer/array used to both pass data to the foreign function
+ * and receive data from the foreign function.
+ *
+ * Whenever possible, prefer using |in_ptr| or |out_ptr|, which
+ * are generally faster.
+ */
+ get inout_ptr() {
+ delete this.inout_ptr;
+ let ptr_t = new PtrType(
+ "[inout] " + this.name + "*",
+ this.implementation.ptr,
+ this);
+ Object.defineProperty(this, "inout_ptr",
+ {
+ get: function() {
+ return ptr_t;
+ }
+ });
+ return ptr_t;
+ },
+
+ /**
+ * Attach a finalizer to a type.
+ */
+ releaseWith: function releaseWith(finalizer) {
+ let parent = this;
+ let type = this.withName("[auto " + this.name + ", " + finalizer + "] ");
+ type.importFromC = function importFromC(value, operation) {
+ return ctypes.CDataFinalizer(
+ parent.importFromC(value, operation),
+ finalizer);
+ };
+ return type;
+ },
+
+ /**
+ * Lazy variant of releaseWith.
+ * Attach a finalizer lazily to a type.
+ *
+ * @param {function} getFinalizer The function that
+ * returns finalizer lazily.
+ */
+ releaseWithLazy: function releaseWithLazy(getFinalizer) {
+ let parent = this;
+ let type = this.withName("[auto " + this.name + ", (lazy)] ");
+ type.importFromC = function importFromC(value, operation) {
+ return ctypes.CDataFinalizer(
+ parent.importFromC(value, operation),
+ getFinalizer());
+ };
+ return type;
+ },
+
+ /**
+ * Return an alias to a type with a different name.
+ */
+ withName: function withName(name) {
+ return Object.create(this, {name: {value: name}});
+ },
+
+ /**
+ * Cast a C value to |this| type.
+ *
+ * Throw an error if the value cannot be casted.
+ */
+ cast: function cast(value) {
+ return ctypes.cast(value, this.implementation);
+ },
+
+ /**
+ * Return the number of bytes in a value of |this| type.
+ *
+ * This may not be defined, e.g. for |void_t|, array types
+ * without length, etc.
+ */
+ get size() {
+ return this.implementation.size;
+ }
+};
+
+/**
+ * Utility function used to determine whether an object is a typed array
+ */
+var isTypedArray = function isTypedArray(obj) {
+ return obj != null && typeof obj == "object"
+ && "byteOffset" in obj;
+};
+exports.isTypedArray = isTypedArray;
+
+/**
+ * Utility function used to determine whether an object is an ArrayBuffer.
+ */
+var isArrayBuffer = function(obj) {
+ return obj != null && typeof obj == "object" &&
+ obj.constructor.name == "ArrayBuffer";
+};
+exports.isArrayBuffer = isArrayBuffer;
+
+/**
+ * A |Type| of pointers.
+ *
+ * @param {string} name The name of this type.
+ * @param {CType} implementation The type of this pointer.
+ * @param {Type} targetType The target type.
+ */
+function PtrType(name, implementation, targetType) {
+ Type.call(this, name, implementation);
+ if (targetType == null || !targetType instanceof Type) {
+ throw new TypeError("targetType must be an instance of Type");
+ }
+ /**
+ * The type of values targeted by this pointer type.
+ */
+ Object.defineProperty(this, "targetType", {
+ value: targetType
+ });
+}
+PtrType.prototype = Object.create(Type.prototype);
+
+/**
+ * Convert a value to a pointer.
+ *
+ * Protocol:
+ * - |null| returns |null|
+ * - a string returns |{string: value}|
+ * - a typed array returns |{ptr: address_of_buffer}|
+ * - a C array returns |{ptr: address_of_buffer}|
+ * everything else raises an error
+ */
+PtrType.prototype.toMsg = function ptr_toMsg(value) {
+ if (value == null) {
+ return null;
+ }
+ if (typeof value == "string") {
+ return { string: value };
+ }
+ if (isTypedArray(value)) {
+ // Automatically transfer typed arrays
+ return new Meta({data: value}, {transfers: [value.buffer]});
+ }
+ if (isArrayBuffer(value)) {
+ // Automatically transfer array buffers
+ return new Meta({data: value}, {transfers: [value]});
+ }
+ let normalized;
+ if ("addressOfElement" in value) { // C array
+ normalized = value.addressOfElement(0);
+ } else if ("isNull" in value) { // C pointer
+ normalized = value;
+ } else {
+ throw new TypeError("Value " + value +
+ " cannot be converted to a pointer");
+ }
+ let cast = Type.uintptr_t.cast(normalized);
+ return {ptr: cast.value.toString()};
+};
+
+/**
+ * Convert a message back to a pointer.
+ */
+PtrType.prototype.fromMsg = function ptr_fromMsg(msg) {
+ if (msg == null) {
+ return null;
+ }
+ if ("string" in msg) {
+ return msg.string;
+ }
+ if ("data" in msg) {
+ return msg.data;
+ }
+ if ("ptr" in msg) {
+ let address = ctypes.uintptr_t(msg.ptr);
+ return this.cast(address);
+ }
+ throw new TypeError("Message " + msg.toSource() +
+ " does not represent a pointer");
+};
+
+exports.Type = Type;
+
+
+/*
+ * Some values are large integers on 64 bit platforms. Unfortunately,
+ * in practice, 64 bit integers cannot be manipulated in JS. We
+ * therefore project them to regular numbers whenever possible.
+ */
+
+var projectLargeInt = function projectLargeInt(x) {
+ let str = x.toString();
+ let rv = parseInt(str, 10);
+ if (rv.toString() !== str) {
+ throw new TypeError("Number " + str + " cannot be projected to a double");
+ }
+ return rv;
+};
+var projectLargeUInt = function projectLargeUInt(x) {
+ return projectLargeInt(x);
+};
+var projectValue = function projectValue(x) {
+ if (!(x instanceof ctypes.CData)) {
+ return x;
+ }
+ if (!("value" in x)) { // Sanity check
+ throw new TypeError("Number " + x.toSource() + " has no field |value|");
+ }
+ return x.value;
+};
+
+function projector(type, signed) {
+ LOG("Determining best projection for", type,
+ "(size: ", type.size, ")", signed?"signed":"unsigned");
+ if (type instanceof Type) {
+ type = type.implementation;
+ }
+ if (!type.size) {
+ throw new TypeError("Argument is not a proper C type");
+ }
+ // Determine if type is projected to Int64/Uint64
+ if (type.size == 8 // Usual case
+ // The following cases have special treatment in js-ctypes
+ // Regardless of their size, the value getter returns
+ // a Int64/Uint64
+ || type == ctypes.size_t // Special cases
+ || type == ctypes.ssize_t
+ || type == ctypes.intptr_t
+ || type == ctypes.uintptr_t
+ || type == ctypes.off_t) {
+ if (signed) {
+ LOG("Projected as a large signed integer");
+ return projectLargeInt;
+ } else {
+ LOG("Projected as a large unsigned integer");
+ return projectLargeUInt;
+ }
+ }
+ LOG("Projected as a regular number");
+ return projectValue;
+};
+exports.projectValue = projectValue;
+
+/**
+ * Get the appropriate type for an unsigned int of the given size.
+ *
+ * This function is useful to define types such as |mode_t| whose
+ * actual width depends on the OS/platform.
+ *
+ * @param {number} size The number of bytes requested.
+ */
+Type.uintn_t = function uintn_t(size) {
+ switch (size) {
+ case 1: return Type.uint8_t;
+ case 2: return Type.uint16_t;
+ case 4: return Type.uint32_t;
+ case 8: return Type.uint64_t;
+ default:
+ throw new Error("Cannot represent unsigned integers of " + size + " bytes");
+ }
+};
+
+/**
+ * Get the appropriate type for an signed int of the given size.
+ *
+ * This function is useful to define types such as |mode_t| whose
+ * actual width depends on the OS/platform.
+ *
+ * @param {number} size The number of bytes requested.
+ */
+Type.intn_t = function intn_t(size) {
+ switch (size) {
+ case 1: return Type.int8_t;
+ case 2: return Type.int16_t;
+ case 4: return Type.int32_t;
+ case 8: return Type.int64_t;
+ default:
+ throw new Error("Cannot represent integers of " + size + " bytes");
+ }
+};
+
+/**
+ * Actual implementation of common C types.
+ */
+
+/**
+ * The void value.
+ */
+Type.void_t =
+ new Type("void",
+ ctypes.void_t);
+
+/**
+ * Shortcut for |void*|.
+ */
+Type.voidptr_t =
+ new PtrType("void*",
+ ctypes.voidptr_t,
+ Type.void_t);
+
+// void* is a special case as we can cast any pointer to/from it
+// so we have to shortcut |in_ptr|/|out_ptr|/|inout_ptr| and
+// ensure that js-ctypes' casting mechanism is invoked directly
+["in_ptr", "out_ptr", "inout_ptr"].forEach(function(key) {
+ Object.defineProperty(Type.void_t, key,
+ {
+ value: Type.voidptr_t
+ });
+});
+
+/**
+ * A Type of integers.
+ *
+ * @param {string} name The name of this type.
+ * @param {CType} implementation The underlying js-ctypes implementation.
+ * @param {bool} signed |true| if this is a type of signed integers,
+ * |false| otherwise.
+ *
+ * @constructor
+ */
+function IntType(name, implementation, signed) {
+ Type.call(this, name, implementation);
+ this.importFromC = projector(implementation, signed);
+ this.project = this.importFromC;
+};
+IntType.prototype = Object.create(Type.prototype);
+IntType.prototype.toMsg = function toMsg(value) {
+ if (typeof value == "number") {
+ return value;
+ }
+ return this.project(value);
+};
+
+/**
+ * A C char (one byte)
+ */
+Type.char =
+ new Type("char",
+ ctypes.char);
+
+/**
+ * A C wide char (two bytes)
+ */
+Type.char16_t =
+ new Type("char16_t",
+ ctypes.char16_t);
+
+ /**
+ * Base string types.
+ */
+Type.cstring = Type.char.in_ptr.withName("[in] C string");
+Type.wstring = Type.char16_t.in_ptr.withName("[in] wide string");
+Type.out_cstring = Type.char.out_ptr.withName("[out] C string");
+Type.out_wstring = Type.char16_t.out_ptr.withName("[out] wide string");
+
+/**
+ * A C integer (8-bits).
+ */
+Type.int8_t =
+ new IntType("int8_t", ctypes.int8_t, true);
+
+Type.uint8_t =
+ new IntType("uint8_t", ctypes.uint8_t, false);
+
+/**
+ * A C integer (16-bits).
+ *
+ * Also known as WORD under Windows.
+ */
+Type.int16_t =
+ new IntType("int16_t", ctypes.int16_t, true);
+
+Type.uint16_t =
+ new IntType("uint16_t", ctypes.uint16_t, false);
+
+/**
+ * A C integer (32-bits).
+ *
+ * Also known as DWORD under Windows.
+ */
+Type.int32_t =
+ new IntType("int32_t", ctypes.int32_t, true);
+
+Type.uint32_t =
+ new IntType("uint32_t", ctypes.uint32_t, false);
+
+/**
+ * A C integer (64-bits).
+ */
+Type.int64_t =
+ new IntType("int64_t", ctypes.int64_t, true);
+
+Type.uint64_t =
+ new IntType("uint64_t", ctypes.uint64_t, false);
+
+ /**
+ * A C integer
+ *
+ * Size depends on the platform.
+ */
+Type.int = Type.intn_t(ctypes.int.size).
+ withName("int");
+
+Type.unsigned_int = Type.intn_t(ctypes.unsigned_int.size).
+ withName("unsigned int");
+
+/**
+ * A C long integer.
+ *
+ * Size depends on the platform.
+ */
+Type.long =
+ Type.intn_t(ctypes.long.size).withName("long");
+
+Type.unsigned_long =
+ Type.intn_t(ctypes.unsigned_long.size).withName("unsigned long");
+
+/**
+ * An unsigned integer with the same size as a pointer.
+ *
+ * Used to cast a pointer to an integer, whenever necessary.
+ */
+Type.uintptr_t =
+ Type.uintn_t(ctypes.uintptr_t.size).withName("uintptr_t");
+
+/**
+ * A boolean.
+ * Implemented as a C integer.
+ */
+Type.bool = Type.int.withName("bool");
+Type.bool.importFromC = function projectBool(x) {
+ return !!(x.value);
+};
+
+/**
+ * A user identifier.
+ *
+ * Implemented as a C integer.
+ */
+Type.uid_t =
+ Type.int.withName("uid_t");
+
+/**
+ * A group identifier.
+ *
+ * Implemented as a C integer.
+ */
+Type.gid_t =
+ Type.int.withName("gid_t");
+
+/**
+ * An offset (positive or negative).
+ *
+ * Implemented as a C integer.
+ */
+Type.off_t =
+ new IntType("off_t", ctypes.off_t, true);
+
+/**
+ * A size (positive).
+ *
+ * Implemented as a C size_t.
+ */
+Type.size_t =
+ new IntType("size_t", ctypes.size_t, false);
+
+/**
+ * An offset (positive or negative).
+ * Implemented as a C integer.
+ */
+Type.ssize_t =
+ new IntType("ssize_t", ctypes.ssize_t, true);
+
+/**
+ * Encoding/decoding strings
+ */
+Type.uencoder =
+ new Type("uencoder", ctypes.StructType("uencoder"));
+Type.udecoder =
+ new Type("udecoder", ctypes.StructType("udecoder"));
+
+/**
+ * Utility class, used to build a |struct| type
+ * from a set of field names, types and offsets.
+ *
+ * @param {string} name The name of the |struct| type.
+ * @param {number} size The total size of the |struct| type in bytes.
+ */
+function HollowStructure(name, size) {
+ if (!name) {
+ throw new TypeError("HollowStructure expects a name");
+ }
+ if (!size || size < 0) {
+ throw new TypeError("HollowStructure expects a (positive) size");
+ }
+
+ // A mapping from offsets in the struct to name/type pairs
+ // (or nothing if no field starts at that offset).
+ this.offset_to_field_info = [];
+
+ // The name of the struct
+ this.name = name;
+
+ // The size of the struct, in bytes
+ this.size = size;
+
+ // The number of paddings inserted so far.
+ // Used to give distinct names to padding fields.
+ this._paddings = 0;
+}
+HollowStructure.prototype = {
+ /**
+ * Add a field at a given offset.
+ *
+ * @param {number} offset The offset at which to insert the field.
+ * @param {string} name The name of the field.
+ * @param {CType|Type} type The type of the field.
+ */
+ add_field_at: function add_field_at(offset, name, type) {
+ if (offset == null) {
+ throw new TypeError("add_field_at requires a non-null offset");
+ }
+ if (!name) {
+ throw new TypeError("add_field_at requires a non-null name");
+ }
+ if (!type) {
+ throw new TypeError("add_field_at requires a non-null type");
+ }
+ if (type instanceof Type) {
+ type = type.implementation;
+ }
+ if (this.offset_to_field_info[offset]) {
+ throw new Error("HollowStructure " + this.name +
+ " already has a field at offset " + offset);
+ }
+ if (offset + type.size > this.size) {
+ throw new Error("HollowStructure " + this.name +
+ " cannot place a value of type " + type +
+ " at offset " + offset +
+ " without exceeding its size of " + this.size);
+ }
+ let field = {name: name, type:type};
+ this.offset_to_field_info[offset] = field;
+ },
+
+ /**
+ * Create a pseudo-field that will only serve as padding.
+ *
+ * @param {number} size The number of bytes in the field.
+ * @return {Object} An association field-name => field-type,
+ * as expected by |ctypes.StructType|.
+ */
+ _makePaddingField: function makePaddingField(size) {
+ let field = ({});
+ field["padding_" + this._paddings] =
+ ctypes.ArrayType(ctypes.uint8_t, size);
+ this._paddings++;
+ return field;
+ },
+
+ /**
+ * Convert this |HollowStructure| into a |Type|.
+ */
+ getType: function getType() {
+ // Contents of the structure, in the format expected
+ // by ctypes.StructType.
+ let struct = [];
+
+ let i = 0;
+ while (i < this.size) {
+ let currentField = this.offset_to_field_info[i];
+ if (!currentField) {
+ // No field was specified at this offset, we need to
+ // introduce some padding.
+
+ // Firstly, determine how many bytes of padding
+ let padding_length = 1;
+ while (i + padding_length < this.size
+ && !this.offset_to_field_info[i + padding_length]) {
+ ++padding_length;
+ }
+
+ // Then add the padding
+ struct.push(this._makePaddingField(padding_length));
+
+ // And proceed
+ i += padding_length;
+ } else {
+ // We have a field at this offset.
+
+ // Firstly, ensure that we do not have two overlapping fields
+ for (let j = 1; j < currentField.type.size; ++j) {
+ let candidateField = this.offset_to_field_info[i + j];
+ if (candidateField) {
+ throw new Error("Fields " + currentField.name +
+ " and " + candidateField.name +
+ " overlap at position " + (i + j));
+ }
+ }
+
+ // Then add the field
+ let field = ({});
+ field[currentField.name] = currentField.type;
+ struct.push(field);
+
+ // And proceed
+ i += currentField.type.size;
+ }
+ }
+ let result = new Type(this.name, ctypes.StructType(this.name, struct));
+ if (result.implementation.size != this.size) {
+ throw new Error("Wrong size for type " + this.name +
+ ": expected " + this.size +
+ ", found " + result.implementation.size +
+ " (" + result.implementation.toSource() + ")");
+ }
+ return result;
+ }
+};
+exports.HollowStructure = HollowStructure;
+
+/**
+ * Representation of a native library.
+ *
+ * The native library is opened lazily, during the first call to its
+ * field |library| or whenever accessing one of the methods imported
+ * with declareLazyFFI.
+ *
+ * @param {string} name A human-readable name for the library. Used
+ * for debugging and error reporting.
+ * @param {string...} candidates A list of system libraries that may
+ * represent this library. Used e.g. to try different library names
+ * on distinct operating systems ("libxul", "XUL", etc.).
+ *
+ * @constructor
+ */
+function Library(name, ...candidates) {
+ this.name = name;
+ this._candidates = candidates;
+};
+Library.prototype = Object.freeze({
+ /**
+ * The native library as a js-ctypes object.
+ *
+ * @throws {Error} If none of the candidate libraries could be opened.
+ */
+ get library() {
+ let library;
+ delete this.library;
+ for (let candidate of this._candidates) {
+ try {
+ library = ctypes.open(candidate);
+ break;
+ } catch (ex) {
+ LOG("Could not open library", candidate, ex);
+ }
+ }
+ this._candidates = null;
+ if (library) {
+ Object.defineProperty(this, "library", {
+ value: library
+ });
+ Object.freeze(this);
+ return library;
+ }
+ let error = new Error("Could not open library " + this.name);
+ Object.defineProperty(this, "library", {
+ get: function() {
+ throw error;
+ }
+ });
+ Object.freeze(this);
+ throw error;
+ },
+
+ /**
+ * Declare a function, lazily.
+ *
+ * @param {object} The object containing the function as a field.
+ * @param {string} The name of the field containing the function.
+ * @param {string} symbol The name of the function, as defined in the
+ * library.
+ * @param {ctypes.abi} abi The abi to use, or |null| for default.
+ * @param {Type} returnType The type of values returned by the function.
+ * @param {...Type} argTypes The type of arguments to the function.
+ */
+ declareLazyFFI: function(object, field, ...args) {
+ let lib = this;
+ Object.defineProperty(object, field, {
+ get: function() {
+ delete this[field];
+ let ffi = declareFFI(lib.library, ...args);
+ if (ffi) {
+ return this[field] = ffi;
+ }
+ return undefined;
+ },
+ configurable: true,
+ enumerable: true
+ });
+ },
+
+ /**
+ * Define a js-ctypes function lazily using ctypes method declare.
+ *
+ * @param {object} The object containing the function as a field.
+ * @param {string} The name of the field containing the function.
+ * @param {string} symbol The name of the function, as defined in the
+ * library.
+ * @param {ctypes.abi} abi The abi to use, or |null| for default.
+ * @param {ctypes.CType} returnType The type of values returned by the function.
+ * @param {...ctypes.CType} argTypes The type of arguments to the function.
+ */
+ declareLazy: function(object, field, ...args) {
+ let lib = this;
+ Object.defineProperty(object, field, {
+ get: function() {
+ delete this[field];
+ let ffi = lib.library.declare(...args);
+ if (ffi) {
+ return this[field] = ffi;
+ }
+ return undefined;
+ },
+ configurable: true,
+ enumerable: true
+ });
+ },
+
+ /**
+ * Define a js-ctypes function lazily using ctypes method declare,
+ * with a fallback library to use if this library can't be opened
+ * or the function cannot be declared.
+ *
+ * @param {fallbacklibrary} The fallback Library object.
+ * @param {object} The object containing the function as a field.
+ * @param {string} The name of the field containing the function.
+ * @param {string} symbol The name of the function, as defined in the
+ * library.
+ * @param {ctypes.abi} abi The abi to use, or |null| for default.
+ * @param {ctypes.CType} returnType The type of values returned by the function.
+ * @param {...ctypes.CType} argTypes The type of arguments to the function.
+ */
+ declareLazyWithFallback: function(fallbacklibrary, object, field, ...args) {
+ let lib = this;
+ Object.defineProperty(object, field, {
+ get: function() {
+ delete this[field];
+ try {
+ let ffi = lib.library.declare(...args);
+ if (ffi) {
+ return this[field] = ffi;
+ }
+ } catch (ex) {
+ // Use the fallback library and get the symbol from there.
+ fallbacklibrary.declareLazy(object, field, ...args);
+ return object[field];
+ }
+ return undefined;
+ },
+ configurable: true,
+ enumerable: true
+ });
+ },
+
+ toString: function() {
+ return "[Library " + this.name + "]";
+ }
+});
+exports.Library = Library;
+
+/**
+ * Declare a function through js-ctypes
+ *
+ * @param {ctypes.library} lib The ctypes library holding the function.
+ * @param {string} symbol The name of the function, as defined in the
+ * library.
+ * @param {ctypes.abi} abi The abi to use, or |null| for default.
+ * @param {Type} returnType The type of values returned by the function.
+ * @param {...Type} argTypes The type of arguments to the function.
+ *
+ * @return null if the function could not be defined (generally because
+ * it does not exist), or a JavaScript wrapper performing the call to C
+ * and any type conversion required.
+ */
+var declareFFI = function declareFFI(lib, symbol, abi,
+ returnType /*, argTypes ...*/) {
+ LOG("Attempting to declare FFI ", symbol);
+ // We guard agressively, to avoid any late surprise
+ if (typeof symbol != "string") {
+ throw new TypeError("declareFFI expects as first argument a string");
+ }
+ abi = abi || ctypes.default_abi;
+ if (Object.prototype.toString.call(abi) != "[object CABI]") {
+ // Note: This is the only known manner of checking whether an object
+ // is an abi.
+ throw new TypeError("declareFFI expects as second argument an abi or null");
+ }
+ if (!returnType.importFromC) {
+ throw new TypeError("declareFFI expects as third argument an instance of Type");
+ }
+ let signature = [symbol, abi];
+ let argtypes = [];
+ for (let i = 3; i < arguments.length; ++i) {
+ let current = arguments[i];
+ if (!current) {
+ throw new TypeError("Missing type for argument " + ( i - 3 ) +
+ " of symbol " + symbol);
+ }
+ if (!current.implementation) {
+ throw new TypeError("Missing implementation for argument " + (i - 3)
+ + " of symbol " + symbol
+ + " ( " + current.name + " )" );
+ }
+ signature.push(current.implementation);
+ }
+ try {
+ let fun = lib.declare.apply(lib, signature);
+ let result = function ffi(...args) {
+ for (let i = 0; i < args.length; i++) {
+ if (typeof args[i] == "undefined") {
+ throw new TypeError("Argument " + i + " of " + symbol + " is undefined");
+ }
+ }
+ let result = fun.apply(fun, args);
+ return returnType.importFromC(result, symbol);
+ };
+ LOG("Function", symbol, "declared");
+ return result;
+ } catch (x) {
+ // Note: Not being able to declare a function is normal.
+ // Some functions are OS (or OS version)-specific.
+ LOG("Could not declare function ", symbol, x);
+ return null;
+ }
+};
+exports.declareFFI = declareFFI;
+
+/**
+ * Define a lazy getter to a js-ctypes function using declareFFI.
+ *
+ * @param {object} The object containing the function as a field.
+ * @param {string} The name of the field containing the function.
+ * @param {ctypes.library} lib The ctypes library holding the function.
+ * @param {string} symbol The name of the function, as defined in the
+ * library.
+ * @param {ctypes.abi} abi The abi to use, or |null| for default.
+ * @param {Type} returnType The type of values returned by the function.
+ * @param {...Type} argTypes The type of arguments to the function.
+ */
+function declareLazyFFI(object, field, ...declareFFIArgs) {
+ Object.defineProperty(object, field, {
+ get: function() {
+ delete this[field];
+ let ffi = declareFFI(...declareFFIArgs);
+ if (ffi) {
+ return this[field] = ffi;
+ }
+ return undefined;
+ },
+ configurable: true,
+ enumerable: true
+ });
+}
+exports.declareLazyFFI = declareLazyFFI;
+
+/**
+ * Define a lazy getter to a js-ctypes function using ctypes method declare.
+ *
+ * @param {object} The object containing the function as a field.
+ * @param {string} The name of the field containing the function.
+ * @param {ctypes.library} lib The ctypes library holding the function.
+ * @param {string} symbol The name of the function, as defined in the
+ * library.
+ * @param {ctypes.abi} abi The abi to use, or |null| for default.
+ * @param {ctypes.CType} returnType The type of values returned by the function.
+ * @param {...ctypes.CType} argTypes The type of arguments to the function.
+ */
+function declareLazy(object, field, lib, ...declareArgs) {
+ Object.defineProperty(object, field, {
+ get: function() {
+ delete this[field];
+ try {
+ let ffi = lib.declare(...declareArgs);
+ return this[field] = ffi;
+ } catch (ex) {
+ // The symbol doesn't exist
+ return undefined;
+ }
+ },
+ configurable: true
+ });
+}
+exports.declareLazy = declareLazy;
+
+/**
+ * Utility function used to sanity check buffer and length arguments. The
+ * buffer must be a Typed Array.
+ *
+ * @param {Typed array} candidate The buffer.
+ * @param {number} bytes The number of bytes that |candidate| should contain.
+ *
+ * @return number The bytes argument clamped to the length of the buffer.
+ */
+function normalizeBufferArgs(candidate, bytes) {
+ if (!candidate) {
+ throw new TypeError("Expecting a Typed Array");
+ }
+ if (!isTypedArray(candidate)) {
+ throw new TypeError("Expecting a Typed Array");
+ }
+ if (bytes == null) {
+ bytes = candidate.byteLength;
+ } else if (candidate.byteLength < bytes) {
+ throw new TypeError("Buffer is too short. I need at least " +
+ bytes +
+ " bytes but I have only " +
+ candidate.byteLength +
+ "bytes");
+ }
+ return bytes;
+};
+exports.normalizeBufferArgs = normalizeBufferArgs;
+
+///////////////////// OS interactions
+
+/**
+ * An OS error.
+ *
+ * This class is provided mostly for type-matching. If you need more
+ * details about an error, you should use the platform-specific error
+ * codes provided by subclasses of |OS.Shared.Error|.
+ *
+ * @param {string} operation The operation that failed.
+ * @param {string=} path The path of the file on which the operation failed,
+ * or nothing if there was no file involved in the failure.
+ *
+ * @constructor
+ */
+function OSError(operation, path = "") {
+ Error.call(this);
+ this.operation = operation;
+ this.path = path;
+}
+OSError.prototype = Object.create(Error.prototype);
+exports.OSError = OSError;
+
+
+///////////////////// Temporary boilerplate
+// Boilerplate, to simplify the transition to require()
+// Do not rely upon this symbol, it will disappear with
+// bug 883050.
+exports.OS = {
+ Constants: exports.Constants,
+ Shared: {
+ LOG: LOG,
+ clone: clone,
+ Type: Type,
+ HollowStructure: HollowStructure,
+ Error: OSError,
+ declareFFI: declareFFI,
+ projectValue: projectValue,
+ isTypedArray: isTypedArray,
+ defineLazyGetter: defineLazyGetter
+ }
+};
+
+Object.defineProperty(exports.OS.Shared, "DEBUG", {
+ get: function() {
+ return Config.DEBUG;
+ },
+ set: function(x) {
+ return Config.DEBUG = x;
+ }
+});
+Object.defineProperty(exports.OS.Shared, "TEST", {
+ get: function() {
+ return Config.TEST;
+ },
+ set: function(x) {
+ return Config.TEST = x;
+ }
+});
+
+
+///////////////////// Permanent boilerplate
+if (typeof Components != "undefined") {
+ this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS;
+ for (let symbol of EXPORTED_SYMBOLS) {
+ this[symbol] = exports[symbol];
+ }
+}