summaryrefslogtreecommitdiffstats
path: root/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /toolkit/components/uniffi-bindgen-gecko-js/src/templates/js
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/uniffi-bindgen-gecko-js/src/templates/js')
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Boolean.sys.mjs22
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/CallbackInterface.sys.mjs24
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/CallbackInterfaceHandler.sys.mjs19
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/CallbackInterfaceRuntime.sys.mjs195
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/CustomType.sys.mjs23
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Enum.sys.mjs113
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Error.sys.mjs79
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/ExternalType.sys.mjs7
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Float32.sys.mjs18
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Float64.sys.mjs18
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Helpers.sys.mjs234
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Int16.sys.mjs27
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Int32.sys.mjs27
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Int64.sys.mjs24
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Int8.sys.mjs27
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Map.sys.mjs54
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Object.sys.mjs68
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Optional.sys.mjs36
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Record.sys.mjs67
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Sequence.sys.mjs43
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/String.sys.mjs32
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/TopLevelFunctions.sys.mjs6
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Types.sys.mjs94
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/UInt16.sys.mjs27
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/UInt32.sys.mjs27
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/UInt64.sys.mjs27
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/UInt8.sys.mjs27
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/macros.sys.mjs62
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/wrapper.sys.mjs15
29 files changed, 1442 insertions, 0 deletions
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Boolean.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Boolean.sys.mjs
new file mode 100644
index 0000000000..a38b6bdd94
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Boolean.sys.mjs
@@ -0,0 +1,22 @@
+// Export the FFIConverter object to make external types work.
+export class {{ ffi_converter }} extends FfiConverter {
+ static computeSize() {
+ return 1;
+ }
+ static lift(value) {
+ return value == 1;
+ }
+ static lower(value) {
+ if (value) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ static write(dataStream, value) {
+ dataStream.writeUint8(this.lower(value))
+ }
+ static read(dataStream) {
+ return this.lift(dataStream.readUint8())
+ }
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/CallbackInterface.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/CallbackInterface.sys.mjs
new file mode 100644
index 0000000000..0b24cbbe0b
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/CallbackInterface.sys.mjs
@@ -0,0 +1,24 @@
+{%- let cbi = ci.get_callback_interface_definition(name).unwrap() %}
+{#- See CallbackInterfaceRuntime.sys.mjs and CallbackInterfaceHandler.sys.mjs for the callback interface handler definition, referenced here as `{{ cbi.handler() }}` #}
+// Export the FFIConverter object to make external types work.
+export class {{ ffi_converter }} extends FfiConverter {
+ static lower(callbackObj) {
+ return {{ cbi.handler() }}.storeCallbackObj(callbackObj)
+ }
+
+ static lift(handleId) {
+ return {{ cbi.handler() }}.getCallbackObj(handleId)
+ }
+
+ static read(dataStream) {
+ return this.lift(dataStream.readInt64())
+ }
+
+ static write(dataStream, callbackObj) {
+ dataStream.writeInt64(this.lower(callbackObj))
+ }
+
+ static computeSize(callbackObj) {
+ return 8;
+ }
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/CallbackInterfaceHandler.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/CallbackInterfaceHandler.sys.mjs
new file mode 100644
index 0000000000..c062d64e0c
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/CallbackInterfaceHandler.sys.mjs
@@ -0,0 +1,19 @@
+const {{ cbi.handler() }} = new UniFFICallbackHandler(
+ "{{ callback_ids.name(ci, cbi) }}",
+ {{ callback_ids.get(ci, cbi) }},
+ [
+ {%- for method in cbi.methods() %}
+ new UniFFICallbackMethodHandler(
+ "{{ method.nm() }}",
+ [
+ {%- for arg in method.arguments() %}
+ {{ arg.ffi_converter() }},
+ {%- endfor %}
+ ],
+ ),
+ {%- endfor %}
+ ]
+);
+
+// Allow the shutdown-related functionality to be tested in the unit tests
+UnitTestObjs.{{ cbi.handler() }} = {{ cbi.handler() }};
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/CallbackInterfaceRuntime.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/CallbackInterfaceRuntime.sys.mjs
new file mode 100644
index 0000000000..a4d88136ab
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/CallbackInterfaceRuntime.sys.mjs
@@ -0,0 +1,195 @@
+
+/**
+ * Handler for a single UniFFI CallbackInterface
+ *
+ * This class stores objects that implement a callback interface in a handle
+ * map, allowing them to be referenced by the Rust code using an integer
+ * handle.
+ *
+ * While the callback object is stored in the map, it allows the Rust code to
+ * call methods on the object using the callback object handle, a method id,
+ * and an ArrayBuffer packed with the method arguments.
+ *
+ * When the Rust code drops its reference, it sends a call with the methodId=0,
+ * which causes callback object to be removed from the map.
+ */
+class UniFFICallbackHandler {
+ #name;
+ #interfaceId;
+ #handleCounter;
+ #handleMap;
+ #methodHandlers;
+ #allowNewCallbacks
+
+ /**
+ * Create a UniFFICallbackHandler
+ * @param {string} name - Human-friendly name for this callback interface
+ * @param {int} interfaceId - Interface ID for this CallbackInterface.
+ * @param {UniFFICallbackMethodHandler[]} methodHandlers -- UniFFICallbackHandler for each method, in the same order as the UDL file
+ */
+ constructor(name, interfaceId, methodHandlers) {
+ this.#name = name;
+ this.#interfaceId = interfaceId;
+ this.#handleCounter = 0;
+ this.#handleMap = new Map();
+ this.#methodHandlers = methodHandlers;
+ this.#allowNewCallbacks = true;
+
+ UniFFIScaffolding.registerCallbackHandler(this.#interfaceId, this.invokeCallback.bind(this));
+ Services.obs.addObserver(this, "xpcom-shutdown");
+ }
+
+ /**
+ * Store a callback object in the handle map and return the handle
+ *
+ * @param {obj} callbackObj - Object that implements the callback interface
+ * @returns {int} - Handle for this callback object, this is what gets passed back to Rust.
+ */
+ storeCallbackObj(callbackObj) {
+ if (!this.#allowNewCallbacks) {
+ throw new UniFFIError(`No new callbacks allowed for ${this.#name}`);
+ }
+ const handle = this.#handleCounter;
+ this.#handleCounter += 1;
+ this.#handleMap.set(handle, new UniFFICallbackHandleMapEntry(callbackObj, Components.stack.caller.formattedStack.trim()));
+ return handle;
+ }
+
+ /**
+ * Get a previously stored callback object
+ *
+ * @param {int} handle - Callback object handle, returned from `storeCallbackObj()`
+ * @returns {obj} - Callback object
+ */
+ getCallbackObj(handle) {
+ return this.#handleMap.get(handle).callbackObj;
+ }
+
+ /**
+ * Set if new callbacks are allowed for this handler
+ *
+ * This is called with false during shutdown to ensure the callback maps don't
+ * prevent JS objects from being GCed.
+ */
+ setAllowNewCallbacks(allow) {
+ this.#allowNewCallbacks = allow
+ }
+
+ /**
+ * Check that no callbacks are currently registered
+ *
+ * If there are callbacks registered a UniFFIError will be thrown. This is
+ * called during shutdown to generate an alert if there are leaked callback
+ * interfaces.
+ */
+ assertNoRegisteredCallbacks() {
+ if (this.#handleMap.size > 0) {
+ const entry = this.#handleMap.values().next().value;
+ throw new UniFFIError(`UniFFI interface ${this.#name} has ${this.#handleMap.size} registered callbacks at xpcom-shutdown. This likely indicates a UniFFI callback leak.\nStack trace for the first leaked callback:\n${entry.stackTrace}.`);
+ }
+ }
+
+ /**
+ * Invoke a method on a stored callback object
+ * @param {int} handle - Object handle
+ * @param {int} methodId - Method identifier. This the 1-based index of
+ * the method from the UDL file. 0 is the special drop method, which
+ * removes the callback object from the handle map.
+ * @param {ArrayBuffer} argsArrayBuffer - Arguments to pass to the method, packed in an ArrayBuffer
+ */
+ invokeCallback(handle, methodId, argsArrayBuffer) {
+ try {
+ this.#invokeCallbackInner(handle, methodId, argsArrayBuffer);
+ } catch (e) {
+ console.error(`internal error invoking callback: ${e}`)
+ }
+ }
+
+ #invokeCallbackInner(handle, methodId, argsArrayBuffer) {
+ const callbackObj = this.getCallbackObj(handle);
+ if (callbackObj === undefined) {
+ throw new UniFFIError(`${this.#name}: invalid callback handle id: ${handle}`);
+ }
+
+ // Special-cased drop method, remove the object from the handle map and
+ // return an empty array buffer
+ if (methodId == 0) {
+ this.#handleMap.delete(handle);
+ return;
+ }
+
+ // Get the method data, converting from 1-based indexing
+ const methodHandler = this.#methodHandlers[methodId - 1];
+ if (methodHandler === undefined) {
+ throw new UniFFIError(`${this.#name}: invalid method id: ${methodId}`)
+ }
+
+ methodHandler.call(callbackObj, argsArrayBuffer);
+ }
+
+ /**
+ * xpcom-shutdown observer method
+ *
+ * This handles:
+ * - Deregistering ourselves as the UniFFI callback handler
+ * - Checks for any leftover stored callbacks which indicate memory leaks
+ */
+ observe(aSubject, aTopic, aData) {
+ if (aTopic == "xpcom-shutdown") {
+ try {
+ this.setAllowNewCallbacks(false);
+ this.assertNoRegisteredCallbacks();
+ UniFFIScaffolding.deregisterCallbackHandler(this.#interfaceId);
+ } catch (ex) {
+ console.error(`UniFFI Callback interface error during xpcom-shutdown: ${ex}`);
+ Cc["@mozilla.org/xpcom/debug;1"]
+ .getService(Ci.nsIDebug2)
+ .abort(ex.filename, ex.lineNumber);
+ }
+ }
+ }
+}
+
+/**
+ * Handles calling a single method for a callback interface
+ */
+class UniFFICallbackMethodHandler {
+ #name;
+ #argsConverters;
+
+ /**
+ * Create a UniFFICallbackMethodHandler
+
+ * @param {string} name -- Name of the method to call on the callback object
+ * @param {FfiConverter[]} argsConverters - FfiConverter for each argument type
+ */
+ constructor(name, argsConverters) {
+ this.#name = name;
+ this.#argsConverters = argsConverters;
+ }
+
+ /**
+ * Invoke the method
+ *
+ * @param {obj} callbackObj -- Object implementing the callback interface for this method
+ * @param {ArrayBuffer} argsArrayBuffer -- Arguments for the method, packed in an ArrayBuffer
+ */
+ call(callbackObj, argsArrayBuffer) {
+ const argsStream = new ArrayBufferDataStream(argsArrayBuffer);
+ const args = this.#argsConverters.map(converter => converter.read(argsStream));
+ callbackObj[this.#name](...args);
+ }
+}
+
+/**
+ * UniFFICallbackHandler.handleMap entry
+ *
+ * @property callbackObj - Callback object, this must implement the callback interface.
+ * @property {string} stackTrace - Stack trace from when the callback object was registered. This is used to proved extra context when debugging leaked callback objects.
+ */
+class UniFFICallbackHandleMapEntry {
+ constructor(callbackObj, stackTrace) {
+ this.callbackObj = callbackObj;
+ this.stackTrace = stackTrace
+ }
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/CustomType.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/CustomType.sys.mjs
new file mode 100644
index 0000000000..4ce4dc31af
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/CustomType.sys.mjs
@@ -0,0 +1,23 @@
+// Export the FFIConverter object to make external types work.
+export class {{ ffi_converter }} extends FfiConverter {
+ static lift(buf) {
+ return {{ builtin.ffi_converter() }}.lift(buf);
+ }
+
+ static lower(buf) {
+ return {{ builtin.ffi_converter() }}.lower(buf);
+ }
+
+ static write(dataStream, value) {
+ {{ builtin.ffi_converter() }}.write(dataStream, value);
+ }
+
+ static read(buf) {
+ return {{ builtin.ffi_converter() }}.read(buf);
+ }
+
+ static computeSize(value) {
+ return {{ builtin.ffi_converter() }}.computeSize(value);
+ }
+}
+// TODO: We should also allow JS to customize the type eventually.
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Enum.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Enum.sys.mjs
new file mode 100644
index 0000000000..f7716ac6d8
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Enum.sys.mjs
@@ -0,0 +1,113 @@
+{%- if enum_.is_flat() -%}
+
+export const {{ enum_.nm() }} = {
+ {%- for variant in enum_.variants() %}
+ {{ variant.name().to_shouty_snake_case() }}: {{loop.index}},
+ {%- endfor %}
+};
+
+Object.freeze({{ enum_.nm() }});
+// Export the FFIConverter object to make external types work.
+export class {{ ffi_converter }} extends FfiConverterArrayBuffer {
+ static read(dataStream) {
+ switch (dataStream.readInt32()) {
+ {%- for variant in enum_.variants() %}
+ case {{ loop.index }}:
+ return {{ enum_.nm() }}.{{ variant.name().to_shouty_snake_case() }}
+ {%- endfor %}
+ default:
+ return new Error("Unknown {{ enum_.nm() }} variant");
+ }
+ }
+
+ static write(dataStream, value) {
+ {%- for variant in enum_.variants() %}
+ if (value === {{ enum_.nm() }}.{{ variant.name().to_shouty_snake_case() }}) {
+ dataStream.writeInt32({{ loop.index }});
+ return;
+ }
+ {%- endfor %}
+ return new Error("Unknown {{ enum_.nm() }} variant");
+ }
+
+ static computeSize(value) {
+ return 4;
+ }
+
+ static checkType(value) {
+ if (!Number.isInteger(value) || value < 1 || value > {{ enum_.variants().len() }}) {
+ throw new UniFFITypeError(`${value} is not a valid value for {{ enum_.nm() }}`);
+ }
+ }
+}
+
+{%- else -%}
+
+export class {{ enum_.nm() }} {}
+{%- for variant in enum_.variants() %}
+{{enum_.nm()}}.{{variant.name().to_upper_camel_case() }} = class extends {{ enum_.nm() }}{
+ constructor(
+ {% for field in variant.fields() -%}
+ {{ field.nm() }}{%- if loop.last %}{%- else %}, {%- endif %}
+ {% endfor -%}
+ ) {
+ super();
+ {%- for field in variant.fields() %}
+ this.{{field.nm()}} = {{ field.nm() }};
+ {%- endfor %}
+ }
+}
+{%- endfor %}
+
+// Export the FFIConverter object to make external types work.
+export class {{ ffi_converter }} extends FfiConverterArrayBuffer {
+ static read(dataStream) {
+ switch (dataStream.readInt32()) {
+ {%- for variant in enum_.variants() %}
+ case {{ loop.index }}:
+ return new {{ enum_.nm() }}.{{ variant.name().to_upper_camel_case() }}(
+ {%- for field in variant.fields() %}
+ {{ field.ffi_converter() }}.read(dataStream){%- if loop.last %}{% else %}, {%- endif %}
+ {%- endfor %}
+ );
+ {%- endfor %}
+ default:
+ return new Error("Unknown {{ enum_.nm() }} variant");
+ }
+ }
+
+ static write(dataStream, value) {
+ {%- for variant in enum_.variants() %}
+ if (value instanceof {{enum_.nm()}}.{{ variant.name().to_upper_camel_case() }}) {
+ dataStream.writeInt32({{ loop.index }});
+ {%- for field in variant.fields() %}
+ {{ field.ffi_converter() }}.write(dataStream, value.{{ field.nm() }});
+ {%- endfor %}
+ return;
+ }
+ {%- endfor %}
+ return new Error("Unknown {{ enum_.nm() }} variant");
+ }
+
+ static computeSize(value) {
+ // Size of the Int indicating the variant
+ let totalSize = 4;
+ {%- for variant in enum_.variants() %}
+ if (value instanceof {{enum_.nm()}}.{{ variant.name().to_upper_camel_case() }}) {
+ {%- for field in variant.fields() %}
+ totalSize += {{ field.ffi_converter() }}.computeSize(value.{{ field.nm() }});
+ {%- endfor %}
+ return totalSize;
+ }
+ {%- endfor %}
+ return new Error("Unknown {{ enum_.nm() }} variant");
+ }
+
+ static checkType(value) {
+ if (!(value instanceof {{ enum_.nm() }})) {
+ throw new UniFFITypeError(`${value} is not a subclass instance of {{ enum_.nm() }}`);
+ }
+ }
+}
+
+{%- endif %}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Error.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Error.sys.mjs
new file mode 100644
index 0000000000..b140d908da
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Error.sys.mjs
@@ -0,0 +1,79 @@
+{%- let string_type = Type::String %}
+{%- let string_ffi_converter = string_type.ffi_converter() %}
+
+export class {{ error.nm() }} extends Error {}
+{% for variant in error.variants() %}
+
+export class {{ variant.name().to_upper_camel_case() }} extends {{ error.nm() }} {
+{% if error.is_flat() %}
+ constructor(message, ...params) {
+ super(...params);
+ this.message = message;
+ }
+{%- else %}
+ constructor(
+ {% for field in variant.fields() -%}
+ {{field.nm()}},
+ {% endfor -%}
+ ...params
+ ) {
+ super(...params);
+ {%- for field in variant.fields() %}
+ this.{{field.nm()}} = {{ field.nm() }};
+ {%- endfor %}
+ }
+{%- endif %}
+ toString() {
+ return `{{ variant.name().to_upper_camel_case() }}: ${super.toString()}`
+ }
+}
+{%- endfor %}
+
+// Export the FFIConverter object to make external types work.
+export class {{ ffi_converter }} extends FfiConverterArrayBuffer {
+ static read(dataStream) {
+ switch (dataStream.readInt32()) {
+ {%- for variant in error.variants() %}
+ case {{ loop.index }}:
+ {%- if error.is_flat() %}
+ return new {{ variant.name().to_upper_camel_case() }}({{ string_ffi_converter }}.read(dataStream));
+ {%- else %}
+ return new {{ variant.name().to_upper_camel_case() }}(
+ {%- for field in variant.fields() %}
+ {{ field.ffi_converter() }}.read(dataStream){%- if loop.last %}{% else %}, {%- endif %}
+ {%- endfor %}
+ );
+ {%- endif %}
+ {%- endfor %}
+ default:
+ throw new Error("Unknown {{ error.nm() }} variant");
+ }
+ }
+ static computeSize(value) {
+ // Size of the Int indicating the variant
+ let totalSize = 4;
+ {%- for variant in error.variants() %}
+ if (value instanceof {{ variant.name().to_upper_camel_case() }}) {
+ {%- for field in variant.fields() %}
+ totalSize += {{ field.ffi_converter() }}.computeSize(value.{{ field.nm() }});
+ {%- endfor %}
+ return totalSize;
+ }
+ {%- endfor %}
+ throw new Error("Unknown {{ error.nm() }} variant");
+ }
+ static write(dataStream, value) {
+ {%- for variant in error.variants() %}
+ if (value instanceof {{ variant.name().to_upper_camel_case() }}) {
+ dataStream.writeInt32({{ loop.index }});
+ {%- for field in variant.fields() %}
+ {{ field.ffi_converter() }}.write(dataStream, value.{{ field.nm() }});
+ {%- endfor %}
+ return;
+ }
+ {%- endfor %}
+ throw new Error("Unknown {{ error.nm() }} variant");
+ }
+
+ static errorClass = {{ error.nm() }};
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/ExternalType.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/ExternalType.sys.mjs
new file mode 100644
index 0000000000..4661b23bf6
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/ExternalType.sys.mjs
@@ -0,0 +1,7 @@
+import {
+ {{ ffi_converter }},
+ {{ name }},
+} from "{{ self.external_type_module(module_path) }}";
+
+// Export the FFIConverter object to make external types work.
+export { {{ ffi_converter }}, {{ name }} };
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Float32.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Float32.sys.mjs
new file mode 100644
index 0000000000..1030efa226
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Float32.sys.mjs
@@ -0,0 +1,18 @@
+// Export the FFIConverter object to make external types work.
+export class {{ ffi_converter }} extends FfiConverter {
+ static computeSize() {
+ return 4;
+ }
+ static lift(value) {
+ return value;
+ }
+ static lower(value) {
+ return value;
+ }
+ static write(dataStream, value) {
+ dataStream.writeFloat32(value)
+ }
+ static read(dataStream) {
+ return dataStream.readFloat32()
+ }
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Float64.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Float64.sys.mjs
new file mode 100644
index 0000000000..fc49046691
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Float64.sys.mjs
@@ -0,0 +1,18 @@
+// Export the FFIConverter object to make external types work.
+export class {{ ffi_converter }} extends FfiConverter {
+ static computeSize() {
+ return 8;
+ }
+ static lift(value) {
+ return value;
+ }
+ static lower(value) {
+ return value;
+ }
+ static write(dataStream, value) {
+ dataStream.writeFloat64(value)
+ }
+ static read(dataStream) {
+ return dataStream.readFloat64()
+ }
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Helpers.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Helpers.sys.mjs
new file mode 100644
index 0000000000..0daaa983ac
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Helpers.sys.mjs
@@ -0,0 +1,234 @@
+// Write/Read data to/from an ArrayBuffer
+class ArrayBufferDataStream {
+ constructor(arrayBuffer) {
+ this.dataView = new DataView(arrayBuffer);
+ this.pos = 0;
+ }
+
+ readUint8() {
+ let rv = this.dataView.getUint8(this.pos);
+ this.pos += 1;
+ return rv;
+ }
+
+ writeUint8(value) {
+ this.dataView.setUint8(this.pos, value);
+ this.pos += 1;
+ }
+
+ readUint16() {
+ let rv = this.dataView.getUint16(this.pos);
+ this.pos += 2;
+ return rv;
+ }
+
+ writeUint16(value) {
+ this.dataView.setUint16(this.pos, value);
+ this.pos += 2;
+ }
+
+ readUint32() {
+ let rv = this.dataView.getUint32(this.pos);
+ this.pos += 4;
+ return rv;
+ }
+
+ writeUint32(value) {
+ this.dataView.setUint32(this.pos, value);
+ this.pos += 4;
+ }
+
+ readUint64() {
+ let rv = this.dataView.getBigUint64(this.pos);
+ this.pos += 8;
+ return Number(rv);
+ }
+
+ writeUint64(value) {
+ this.dataView.setBigUint64(this.pos, BigInt(value));
+ this.pos += 8;
+ }
+
+
+ readInt8() {
+ let rv = this.dataView.getInt8(this.pos);
+ this.pos += 1;
+ return rv;
+ }
+
+ writeInt8(value) {
+ this.dataView.setInt8(this.pos, value);
+ this.pos += 1;
+ }
+
+ readInt16() {
+ let rv = this.dataView.getInt16(this.pos);
+ this.pos += 2;
+ return rv;
+ }
+
+ writeInt16(value) {
+ this.dataView.setInt16(this.pos, value);
+ this.pos += 2;
+ }
+
+ readInt32() {
+ let rv = this.dataView.getInt32(this.pos);
+ this.pos += 4;
+ return rv;
+ }
+
+ writeInt32(value) {
+ this.dataView.setInt32(this.pos, value);
+ this.pos += 4;
+ }
+
+ readInt64() {
+ let rv = this.dataView.getBigInt64(this.pos);
+ this.pos += 8;
+ return Number(rv);
+ }
+
+ writeInt64(value) {
+ this.dataView.setBigInt64(this.pos, BigInt(value));
+ this.pos += 8;
+ }
+
+ readFloat32() {
+ let rv = this.dataView.getFloat32(this.pos);
+ this.pos += 4;
+ return rv;
+ }
+
+ writeFloat32(value) {
+ this.dataView.setFloat32(this.pos, value);
+ this.pos += 4;
+ }
+
+ readFloat64() {
+ let rv = this.dataView.getFloat64(this.pos);
+ this.pos += 8;
+ return rv;
+ }
+
+ writeFloat64(value) {
+ this.dataView.setFloat64(this.pos, value);
+ this.pos += 8;
+ }
+
+
+ writeString(value) {
+ const encoder = new TextEncoder();
+ // Note: in order to efficiently write this data, we first write the
+ // string data, reserving 4 bytes for the size.
+ const dest = new Uint8Array(this.dataView.buffer, this.pos + 4);
+ const encodeResult = encoder.encodeInto(value, dest);
+ if (encodeResult.read != value.length) {
+ throw new UniFFIError(
+ "writeString: out of space when writing to ArrayBuffer. Did the computeSize() method returned the wrong result?"
+ );
+ }
+ const size = encodeResult.written;
+ // Next, go back and write the size before the string data
+ this.dataView.setUint32(this.pos, size);
+ // Finally, advance our position past both the size and string data
+ this.pos += size + 4;
+ }
+
+ readString() {
+ const decoder = new TextDecoder();
+ const size = this.readUint32();
+ const source = new Uint8Array(this.dataView.buffer, this.pos, size)
+ const value = decoder.decode(source);
+ this.pos += size;
+ return value;
+ }
+
+ {%- for object in ci.object_definitions() %}
+
+ // Reads a {{ object.nm() }} pointer from the data stream
+ // UniFFI Pointers are **always** 8 bytes long. That is enforced
+ // by the C++ and Rust Scaffolding code.
+ readPointer{{ object.nm() }}() {
+ const pointerId = {{ object_ids.get(ci, object) }}; // {{ object_ids.name(ci, object) }}
+ const res = UniFFIScaffolding.readPointer(pointerId, this.dataView.buffer, this.pos);
+ this.pos += 8;
+ return res;
+ }
+
+ // Writes a {{ object.nm() }} pointer into the data stream
+ // UniFFI Pointers are **always** 8 bytes long. That is enforced
+ // by the C++ and Rust Scaffolding code.
+ writePointer{{ object.nm() }}(value) {
+ const pointerId = {{ object_ids.get(ci, object) }}; // {{ object_ids.name(ci, object) }}
+ UniFFIScaffolding.writePointer(pointerId, value, this.dataView.buffer, this.pos);
+ this.pos += 8;
+ }
+ {% endfor %}
+}
+
+function handleRustResult(result, liftCallback, liftErrCallback) {
+ switch (result.code) {
+ case "success":
+ return liftCallback(result.data);
+
+ case "error":
+ throw liftErrCallback(result.data);
+
+ case "internal-error":
+ let message = result.internalErrorMessage;
+ if (message) {
+ throw new UniFFIInternalError(message);
+ } else {
+ throw new UniFFIInternalError("Unknown error");
+ }
+
+ default:
+ throw new UniFFIError(`Unexpected status code: ${result.code}`);
+ }
+}
+
+class UniFFIError {
+ constructor(message) {
+ this.message = message;
+ }
+
+ toString() {
+ return `UniFFIError: ${this.message}`
+ }
+}
+
+class UniFFIInternalError extends UniFFIError {}
+
+// Base class for FFI converters
+class FfiConverter {
+ // throw `UniFFITypeError` if a value to be converted has an invalid type
+ static checkType(value) {
+ if (value === undefined ) {
+ throw new UniFFITypeError(`undefined`);
+ }
+ if (value === null ) {
+ throw new UniFFITypeError(`null`);
+ }
+ }
+}
+
+// Base class for FFI converters that lift/lower by reading/writing to an ArrayBuffer
+class FfiConverterArrayBuffer extends FfiConverter {
+ static lift(buf) {
+ return this.read(new ArrayBufferDataStream(buf));
+ }
+
+ static lower(value) {
+ const buf = new ArrayBuffer(this.computeSize(value));
+ const dataStream = new ArrayBufferDataStream(buf);
+ this.write(dataStream, value);
+ return buf;
+ }
+}
+
+// Symbols that are used to ensure that Object constructors
+// can only be used with a proper UniFFI pointer
+const uniffiObjectPtr = Symbol("uniffiObjectPtr");
+const constructUniffiObject = Symbol("constructUniffiObject");
+UnitTestObjs.uniffiObjectPtr = uniffiObjectPtr;
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Int16.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Int16.sys.mjs
new file mode 100644
index 0000000000..63c26bce8a
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Int16.sys.mjs
@@ -0,0 +1,27 @@
+// Export the FFIConverter object to make external types work.
+export class {{ ffi_converter }} extends FfiConverter {
+ static checkType(value) {
+ super.checkType(value);
+ if (!Number.isInteger(value)) {
+ throw new UniFFITypeError(`${value} is not an integer`);
+ }
+ if (value < -32768 || value > 32767) {
+ throw new UniFFITypeError(`${value} exceeds the I16 bounds`);
+ }
+ }
+ static computeSize() {
+ return 2;
+ }
+ static lift(value) {
+ return value;
+ }
+ static lower(value) {
+ return value;
+ }
+ static write(dataStream, value) {
+ dataStream.writeInt16(value)
+ }
+ static read(dataStream) {
+ return dataStream.readInt16()
+ }
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Int32.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Int32.sys.mjs
new file mode 100644
index 0000000000..502092eb16
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Int32.sys.mjs
@@ -0,0 +1,27 @@
+// Export the FFIConverter object to make external types work.
+export class {{ ffi_converter }} extends FfiConverter {
+ static checkType(value) {
+ super.checkType(value);
+ if (!Number.isInteger(value)) {
+ throw new UniFFITypeError(`${value} is not an integer`);
+ }
+ if (value < -2147483648 || value > 2147483647) {
+ throw new UniFFITypeError(`${value} exceeds the I32 bounds`);
+ }
+ }
+ static computeSize() {
+ return 4;
+ }
+ static lift(value) {
+ return value;
+ }
+ static lower(value) {
+ return value;
+ }
+ static write(dataStream, value) {
+ dataStream.writeInt32(value)
+ }
+ static read(dataStream) {
+ return dataStream.readInt32()
+ }
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Int64.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Int64.sys.mjs
new file mode 100644
index 0000000000..d56296712d
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Int64.sys.mjs
@@ -0,0 +1,24 @@
+// Export the FFIConverter object to make external types work.
+export class {{ ffi_converter }} extends FfiConverter {
+ static checkType(value) {
+ super.checkType(value);
+ if (!Number.isSafeInteger(value)) {
+ throw new UniFFITypeError(`${value} exceeds the safe integer bounds`);
+ }
+ }
+ static computeSize() {
+ return 8;
+ }
+ static lift(value) {
+ return value;
+ }
+ static lower(value) {
+ return value;
+ }
+ static write(dataStream, value) {
+ dataStream.writeInt64(value)
+ }
+ static read(dataStream) {
+ return dataStream.readInt64()
+ }
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Int8.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Int8.sys.mjs
new file mode 100644
index 0000000000..63e543b1fa
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Int8.sys.mjs
@@ -0,0 +1,27 @@
+// Export the FFIConverter object to make external types work.
+export class {{ ffi_converter }} extends FfiConverter {
+ static checkType(value) {
+ super.checkType(value);
+ if (!Number.isInteger(value)) {
+ throw new UniFFITypeError(`${value} is not an integer`);
+ }
+ if (value < -128 || value > 127) {
+ throw new UniFFITypeError(`${value} exceeds the I8 bounds`);
+ }
+ }
+ static computeSize() {
+ return 1;
+ }
+ static lift(value) {
+ return value;
+ }
+ static lower(value) {
+ return value;
+ }
+ static write(dataStream, value) {
+ dataStream.writeInt8(value)
+ }
+ static read(dataStream) {
+ return dataStream.readInt8()
+ }
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Map.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Map.sys.mjs
new file mode 100644
index 0000000000..5b6e6dc172
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Map.sys.mjs
@@ -0,0 +1,54 @@
+// Export the FFIConverter object to make external types work.
+export class {{ ffi_converter }} extends FfiConverterArrayBuffer {
+ static read(dataStream) {
+ const len = dataStream.readInt32();
+ const map = {};
+ for (let i = 0; i < len; i++) {
+ const key = {{ key_type.ffi_converter() }}.read(dataStream);
+ const value = {{ value_type.ffi_converter() }}.read(dataStream);
+ map[key] = value;
+ }
+
+ return map;
+ }
+
+ static write(dataStream, value) {
+ dataStream.writeInt32(Object.keys(value).length);
+ for (const key in value) {
+ {{ key_type.ffi_converter() }}.write(dataStream, key);
+ {{ value_type.ffi_converter() }}.write(dataStream, value[key]);
+ }
+ }
+
+ static computeSize(value) {
+ // The size of the length
+ let size = 4;
+ for (const key in value) {
+ size += {{ key_type.ffi_converter() }}.computeSize(key);
+ size += {{ value_type.ffi_converter() }}.computeSize(value[key]);
+ }
+ return size;
+ }
+
+ static checkType(value) {
+ for (const key in value) {
+ try {
+ {{ key_type.ffi_converter() }}.checkType(key);
+ } catch (e) {
+ if (e instanceof UniFFITypeError) {
+ e.addItemDescriptionPart("(key)");
+ }
+ throw e;
+ }
+
+ try {
+ {{ value_type.ffi_converter() }}.checkType(value[key]);
+ } catch (e) {
+ if (e instanceof UniFFITypeError) {
+ e.addItemDescriptionPart(`[${key}]`);
+ }
+ throw e;
+ }
+ }
+ }
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Object.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Object.sys.mjs
new file mode 100644
index 0000000000..e03291089e
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Object.sys.mjs
@@ -0,0 +1,68 @@
+{%- let object = ci.get_object_definition(name).unwrap() -%}
+export class {{ object.nm() }} {
+ // Use `init` to instantiate this class.
+ // DO NOT USE THIS CONSTRUCTOR DIRECTLY
+ constructor(opts) {
+ if (!Object.prototype.hasOwnProperty.call(opts, constructUniffiObject)) {
+ throw new UniFFIError("Attempting to construct an object using the JavaScript constructor directly" +
+ "Please use a UDL defined constructor, or the init function for the primary constructor")
+ }
+ if (!opts[constructUniffiObject] instanceof UniFFIPointer) {
+ throw new UniFFIError("Attempting to create a UniFFI object with a pointer that is not an instance of UniFFIPointer")
+ }
+ this[uniffiObjectPtr] = opts[constructUniffiObject];
+ }
+
+ {%- for cons in object.constructors() %}
+ {%- if object.is_constructor_async(config) %}
+ /**
+ * An async constructor for {{ object.nm() }}.
+ *
+ * @returns {Promise<{{ object.nm() }}>}: A promise that resolves
+ * to a newly constructed {{ object.nm() }}
+ */
+ {%- else %}
+ /**
+ * A constructor for {{ object.nm() }}.
+ *
+ * @returns { {{ object.nm() }} }
+ */
+ {%- endif %}
+ static {{ cons.nm() }}({{cons.arg_names()}}) {
+ {%- call js::call_constructor(cons, type_, object.is_constructor_async(config)) -%}
+ }
+ {%- endfor %}
+
+ {%- for meth in object.methods() %}
+
+ {{ meth.nm() }}({{ meth.arg_names() }}) {
+ {%- call js::call_method(meth, type_, object.is_method_async(meth, config)) %}
+ }
+ {%- endfor %}
+
+}
+
+// Export the FFIConverter object to make external types work.
+export class {{ ffi_converter }} extends FfiConverter {
+ static lift(value) {
+ const opts = {};
+ opts[constructUniffiObject] = value;
+ return new {{ object.nm() }}(opts);
+ }
+
+ static lower(value) {
+ return value[uniffiObjectPtr];
+ }
+
+ static read(dataStream) {
+ return this.lift(dataStream.readPointer{{ object.nm() }}());
+ }
+
+ static write(dataStream, value) {
+ dataStream.writePointer{{ object.nm() }}(value[uniffiObjectPtr]);
+ }
+
+ static computeSize(value) {
+ return 8;
+ }
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Optional.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Optional.sys.mjs
new file mode 100644
index 0000000000..836ea81b89
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Optional.sys.mjs
@@ -0,0 +1,36 @@
+// Export the FFIConverter object to make external types work.
+export class {{ ffi_converter }} extends FfiConverterArrayBuffer {
+ static checkType(value) {
+ if (value !== undefined && value !== null) {
+ {{ inner_type.ffi_converter() }}.checkType(value)
+ }
+ }
+
+ static read(dataStream) {
+ const code = dataStream.readUint8(0);
+ switch (code) {
+ case 0:
+ return null
+ case 1:
+ return {{ inner_type.ffi_converter() }}.read(dataStream)
+ default:
+ throw UniFFIError(`Unexpected code: ${code}`);
+ }
+ }
+
+ static write(dataStream, value) {
+ if (value === null || value === undefined) {
+ dataStream.writeUint8(0);
+ return;
+ }
+ dataStream.writeUint8(1);
+ {{ inner_type.ffi_converter() }}.write(dataStream, value)
+ }
+
+ static computeSize(value) {
+ if (value === null || value === undefined) {
+ return 1;
+ }
+ return 1 + {{ inner_type.ffi_converter() }}.computeSize(value)
+ }
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Record.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Record.sys.mjs
new file mode 100644
index 0000000000..2f54160b9e
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Record.sys.mjs
@@ -0,0 +1,67 @@
+{%- let record = ci.get_record_definition(name).unwrap() -%}
+export class {{ record.nm() }} {
+ constructor({{ record.constructor_field_list() }} = {}) {
+ {%- for field in record.fields() %}
+ try {
+ {{ field.ffi_converter() }}.checkType({{ field.nm() }})
+ } catch (e) {
+ if (e instanceof UniFFITypeError) {
+ e.addItemDescriptionPart("{{ field.nm() }}");
+ }
+ throw e;
+ }
+ {%- endfor %}
+
+ {%- for field in record.fields() %}
+ this.{{field.nm()}} = {{ field.nm() }};
+ {%- endfor %}
+ }
+ equals(other) {
+ return (
+ {%- for field in record.fields() %}
+ {{ field.as_type().equals("this.{}"|format(field.nm()), "other.{}"|format(field.nm())) }}{% if !loop.last %} &&{% endif %}
+ {%- endfor %}
+ )
+ }
+}
+
+// Export the FFIConverter object to make external types work.
+export class {{ ffi_converter }} extends FfiConverterArrayBuffer {
+ static read(dataStream) {
+ return new {{record.nm()}}({
+ {%- for field in record.fields() %}
+ {{ field.nm() }}: {{ field.read_datastream_fn() }}(dataStream),
+ {%- endfor %}
+ });
+ }
+ static write(dataStream, value) {
+ {%- for field in record.fields() %}
+ {{ field.write_datastream_fn() }}(dataStream, value.{{field.nm()}});
+ {%- endfor %}
+ }
+
+ static computeSize(value) {
+ let totalSize = 0;
+ {%- for field in record.fields() %}
+ totalSize += {{ field.ffi_converter() }}.computeSize(value.{{ field.nm() }});
+ {%- endfor %}
+ return totalSize
+ }
+
+ static checkType(value) {
+ super.checkType(value);
+ if (!(value instanceof {{ record.nm() }})) {
+ throw new TypeError(`Expected '{{ record.nm() }}', found '${typeof value}'`);
+ }
+ {%- for field in record.fields() %}
+ try {
+ {{ field.ffi_converter() }}.checkType(value.{{ field.nm() }});
+ } catch (e) {
+ if (e instanceof UniFFITypeError) {
+ e.addItemDescriptionPart(".{{ field.nm() }}");
+ }
+ throw e;
+ }
+ {%- endfor %}
+ }
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Sequence.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Sequence.sys.mjs
new file mode 100644
index 0000000000..4c1034a182
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Sequence.sys.mjs
@@ -0,0 +1,43 @@
+// Export the FFIConverter object to make external types work.
+export class {{ ffi_converter }} extends FfiConverterArrayBuffer {
+ static read(dataStream) {
+ const len = dataStream.readInt32();
+ const arr = [];
+ for (let i = 0; i < len; i++) {
+ arr.push({{ inner_type.ffi_converter() }}.read(dataStream));
+ }
+ return arr;
+ }
+
+ static write(dataStream, value) {
+ dataStream.writeInt32(value.length);
+ value.forEach((innerValue) => {
+ {{ inner_type.ffi_converter() }}.write(dataStream, innerValue);
+ })
+ }
+
+ static computeSize(value) {
+ // The size of the length
+ let size = 4;
+ for (const innerValue of value) {
+ size += {{ inner_type.ffi_converter() }}.computeSize(innerValue);
+ }
+ return size;
+ }
+
+ static checkType(value) {
+ if (!Array.isArray(value)) {
+ throw new UniFFITypeError(`${value} is not an array`);
+ }
+ value.forEach((innerValue, idx) => {
+ try {
+ {{ inner_type.ffi_converter() }}.checkType(innerValue);
+ } catch (e) {
+ if (e instanceof UniFFITypeError) {
+ e.addItemDescriptionPart(`[${idx}]`);
+ }
+ throw e;
+ }
+ })
+ }
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/String.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/String.sys.mjs
new file mode 100644
index 0000000000..d016e3f21b
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/String.sys.mjs
@@ -0,0 +1,32 @@
+// Export the FFIConverter object to make external types work.
+export class {{ ffi_converter }} extends FfiConverter {
+ static checkType(value) {
+ super.checkType(value);
+ if (typeof value !== "string") {
+ throw new UniFFITypeError(`${value} is not a string`);
+ }
+ }
+
+ static lift(buf) {
+ const decoder = new TextDecoder();
+ const utf8Arr = new Uint8Array(buf);
+ return decoder.decode(utf8Arr);
+ }
+ static lower(value) {
+ const encoder = new TextEncoder();
+ return encoder.encode(value).buffer;
+ }
+
+ static write(dataStream, value) {
+ dataStream.writeString(value);
+ }
+
+ static read(dataStream) {
+ return dataStream.readString();
+ }
+
+ static computeSize(value) {
+ const encoder = new TextEncoder();
+ return 4 + encoder.encode(value).length
+ }
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/TopLevelFunctions.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/TopLevelFunctions.sys.mjs
new file mode 100644
index 0000000000..601eb74d7c
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/TopLevelFunctions.sys.mjs
@@ -0,0 +1,6 @@
+{%- for func in ci.function_definitions() %}
+
+export function {{ func.nm() }}({{ func.arg_names() }}) {
+{% call js::call_scaffolding_function(func) %}
+}
+{%- endfor %}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Types.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Types.sys.mjs
new file mode 100644
index 0000000000..a5dfd9c0c7
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/Types.sys.mjs
@@ -0,0 +1,94 @@
+{%- if !ci.callback_interface_definitions().is_empty() %}
+{%- include "CallbackInterfaceRuntime.sys.mjs" %}
+
+{% endif %}
+
+{%- for type_ in ci.iter_types() %}
+{%- let ffi_converter = type_.ffi_converter() %}
+{%- match type_ %}
+
+{%- when Type::Boolean %}
+{%- include "Boolean.sys.mjs" %}
+
+{%- when Type::UInt8 %}
+{%- include "UInt8.sys.mjs" %}
+
+{%- when Type::UInt16 %}
+{%- include "UInt16.sys.mjs" %}
+
+{%- when Type::UInt32 %}
+{%- include "UInt32.sys.mjs" %}
+
+{%- when Type::UInt64 %}
+{%- include "UInt64.sys.mjs" %}
+
+{%- when Type::Int8 %}
+{%- include "Int8.sys.mjs" %}
+
+{%- when Type::Int16 %}
+{%- include "Int16.sys.mjs" %}
+
+{%- when Type::Int32 %}
+{%- include "Int32.sys.mjs" %}
+
+{%- when Type::Int64 %}
+{%- include "Int64.sys.mjs" %}
+
+{%- when Type::Float32 %}
+{%- include "Float32.sys.mjs" %}
+
+{%- when Type::Float64 %}
+{%- include "Float64.sys.mjs" %}
+
+{%- when Type::Record { name, module_path } %}
+{%- include "Record.sys.mjs" %}
+
+{%- when Type::Optional { inner_type } %}
+{%- include "Optional.sys.mjs" %}
+
+{%- when Type::String %}
+{%- include "String.sys.mjs" %}
+
+{%- when Type::Sequence { inner_type } %}
+{%- include "Sequence.sys.mjs" %}
+
+{%- when Type::Map { key_type, value_type } %}
+{%- include "Map.sys.mjs" %}
+
+{%- when Type::Enum { name, module_path } %}
+{%- let e = ci.get_enum_definition(name).unwrap() %}
+{# For enums, there are either an error *or* an enum, they can't be both. #}
+{%- if ci.is_name_used_as_error(name) %}
+{%- let error = e %}
+{%- include "Error.sys.mjs" %}
+{%- else %}
+{%- let enum_ = e %}
+{%- include "Enum.sys.mjs" %}
+{% endif %}
+
+{%- when Type::Object { name, imp, module_path } %}
+{%- include "Object.sys.mjs" %}
+
+{%- when Type::Custom { name, builtin, module_path } %}
+{%- include "CustomType.sys.mjs" %}
+
+{%- when Type::External { name, module_path, kind, namespace, tagged } %}
+{%- include "ExternalType.sys.mjs" %}
+
+{%- when Type::CallbackInterface { name, module_path } %}
+{%- include "CallbackInterface.sys.mjs" %}
+
+{%- else %}
+{#- TODO implement the other types #}
+
+{%- endmatch %}
+
+{% endfor %}
+
+{%- if !ci.callback_interface_definitions().is_empty() %}
+// Define callback interface handlers, this must come after the type loop since they reference the FfiConverters defined above.
+
+{% for cbi in ci.callback_interface_definitions() %}
+{%- include "CallbackInterfaceHandler.sys.mjs" %}
+{% endfor %}
+{% endif %}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/UInt16.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/UInt16.sys.mjs
new file mode 100644
index 0000000000..569d6d2ebd
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/UInt16.sys.mjs
@@ -0,0 +1,27 @@
+// Export the FFIConverter object to make external types work.
+export class {{ ffi_converter }} extends FfiConverter {
+ static checkType(value) {
+ super.checkType(value);
+ if (!Number.isInteger(value)) {
+ throw new UniFFITypeError(`${value} is not an integer`);
+ }
+ if (value < 0 || value > 65535) {
+ throw new UniFFITypeError(`${value} exceeds the U16 bounds`);
+ }
+ }
+ static computeSize() {
+ return 2;
+ }
+ static lift(value) {
+ return value;
+ }
+ static lower(value) {
+ return value;
+ }
+ static write(dataStream, value) {
+ dataStream.writeUint16(value)
+ }
+ static read(dataStream) {
+ return dataStream.readUint16()
+ }
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/UInt32.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/UInt32.sys.mjs
new file mode 100644
index 0000000000..cfeffb1ecb
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/UInt32.sys.mjs
@@ -0,0 +1,27 @@
+// Export the FFIConverter object to make external types work.
+export class {{ ffi_converter }} extends FfiConverter {
+ static checkType(value) {
+ super.checkType(value);
+ if (!Number.isInteger(value)) {
+ throw new UniFFITypeError(`${value} is not an integer`);
+ }
+ if (value < 0 || value > 4294967295) {
+ throw new UniFFITypeError(`${value} exceeds the U32 bounds`);
+ }
+ }
+ static computeSize() {
+ return 4;
+ }
+ static lift(value) {
+ return value;
+ }
+ static lower(value) {
+ return value;
+ }
+ static write(dataStream, value) {
+ dataStream.writeUint32(value)
+ }
+ static read(dataStream) {
+ return dataStream.readUint32()
+ }
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/UInt64.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/UInt64.sys.mjs
new file mode 100644
index 0000000000..a62a0b7e6c
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/UInt64.sys.mjs
@@ -0,0 +1,27 @@
+// Export the FFIConverter object to make external types work.
+export class {{ ffi_converter }} extends FfiConverter {
+ static checkType(value) {
+ super.checkType(value);
+ if (!Number.isSafeInteger(value)) {
+ throw new UniFFITypeError(`${value} exceeds the safe integer bounds`);
+ }
+ if (value < 0) {
+ throw new UniFFITypeError(`${value} exceeds the U64 bounds`);
+ }
+ }
+ static computeSize() {
+ return 8;
+ }
+ static lift(value) {
+ return value;
+ }
+ static lower(value) {
+ return value;
+ }
+ static write(dataStream, value) {
+ dataStream.writeUint64(value)
+ }
+ static read(dataStream) {
+ return dataStream.readUint64()
+ }
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/UInt8.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/UInt8.sys.mjs
new file mode 100644
index 0000000000..2f08aeee1b
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/UInt8.sys.mjs
@@ -0,0 +1,27 @@
+// Export the FFIConverter object to make external types work.
+export class {{ ffi_converter }} extends FfiConverter {
+ static checkType(value) {
+ super.checkType(value);
+ if (!Number.isInteger(value)) {
+ throw new UniFFITypeError(`${value} is not an integer`);
+ }
+ if (value < 0 || value > 256) {
+ throw new UniFFITypeError(`${value} exceeds the U8 bounds`);
+ }
+ }
+ static computeSize() {
+ return 1;
+ }
+ static lift(value) {
+ return value;
+ }
+ static lower(value) {
+ return value;
+ }
+ static write(dataStream, value) {
+ dataStream.writeUint8(value)
+ }
+ static read(dataStream) {
+ return dataStream.readUint8()
+ }
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/macros.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/macros.sys.mjs
new file mode 100644
index 0000000000..d0dfed6c85
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/macros.sys.mjs
@@ -0,0 +1,62 @@
+{%- macro call_scaffolding_function(func) %}
+{%- call _call_scaffolding_function(func, func.return_type(), "", func.is_js_async(config)) -%}
+{%- endmacro %}
+
+{%- macro call_constructor(cons, object_type, is_async) %}
+{%- call _call_scaffolding_function(cons, Some(object_type), "", is_async) -%}
+{%- endmacro %}
+
+{%- macro call_method(method, object_type, is_async) %}
+{%- call _call_scaffolding_function(method, method.return_type(), object_type.ffi_converter(), is_async) -%}
+{%- endmacro %}
+
+{%- macro _call_scaffolding_function(func, return_type, receiver_ffi_converter, is_async) %}
+ {%- match return_type %}
+ {%- when Some with (return_type) %}
+ const liftResult = (result) => {{ return_type.ffi_converter() }}.lift(result);
+ {%- else %}
+ const liftResult = (result) => undefined;
+ {%- endmatch %}
+ {%- match func.throws_type() %}
+ {%- when Some with (err_type) %}
+ const liftError = (data) => {{ err_type.ffi_converter() }}.lift(data);
+ {%- else %}
+ const liftError = null;
+ {%- endmatch %}
+ const functionCall = () => {
+ {%- for arg in func.arguments() %}
+ try {
+ {{ arg.ffi_converter() }}.checkType({{ arg.nm() }})
+ } catch (e) {
+ if (e instanceof UniFFITypeError) {
+ e.addItemDescriptionPart("{{ arg.nm() }}");
+ }
+ throw e;
+ }
+ {%- endfor %}
+
+ {%- if is_async %}
+ return UniFFIScaffolding.callAsync(
+ {%- else %}
+ return UniFFIScaffolding.callSync(
+ {%- endif %}
+ {{ function_ids.get(ci, func.ffi_func()) }}, // {{ function_ids.name(ci, func.ffi_func()) }}
+ {%- if receiver_ffi_converter != "" %}
+ {{ receiver_ffi_converter }}.lower(this),
+ {%- endif %}
+ {%- for arg in func.arguments() %}
+ {{ arg.lower_fn() }}({{ arg.nm() }}),
+ {%- endfor %}
+ )
+ }
+
+ {%- if is_async %}
+ try {
+ return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
+ } catch (error) {
+ return Promise.reject(error)
+ }
+ {%- else %}
+ return handleRustResult(functionCall(), liftResult, liftError);
+ {%- endif %}
+{%- endmacro %}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/wrapper.sys.mjs b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/wrapper.sys.mjs
new file mode 100644
index 0000000000..0c33c05e4f
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/wrapper.sys.mjs
@@ -0,0 +1,15 @@
+// This file was autogenerated by the `uniffi-bindgen-gecko-js` crate.
+// Trust me, you don't want to mess with it!
+
+import { UniFFITypeError } from "resource://gre/modules/UniFFI.sys.mjs";
+
+{% import "macros.sys.mjs" as js %}
+
+// Objects intended to be used in the unit tests
+export var UnitTestObjs = {};
+
+{% include "Helpers.sys.mjs" %}
+
+{% include "Types.sys.mjs" %}
+
+{% include "TopLevelFunctions.sys.mjs" %}