diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /toolkit/components/uniffi-bindgen-gecko-js/src/templates | |
parent | Initial commit. (diff) | |
download | firefox-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')
30 files changed, 1597 insertions, 0 deletions
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/templates/UniFFIScaffolding.cpp b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/UniFFIScaffolding.cpp new file mode 100644 index 0000000000..5c4ed8c2f5 --- /dev/null +++ b/toolkit/components/uniffi-bindgen-gecko-js/src/templates/UniFFIScaffolding.cpp @@ -0,0 +1,155 @@ +// Generated by uniffi-bindgen-gecko-js. DO NOT EDIT. + +#include "nsString.h" +#include "nsPrintfCString.h" +#include "mozilla/Logging.h" +#include "mozilla/Maybe.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/dom/UniFFICallbacks.h" +#include "mozilla/dom/UniFFIScaffolding.h" +#include "mozilla/dom/ScaffoldingCall.h" + +namespace mozilla::uniffi { + +using dom::ArrayBuffer; +using dom::AutoEntryScript; +using dom::GlobalObject; +using dom::RootedDictionary; +using dom::Promise; +using dom::ScaffoldingType; +using dom::Sequence; +using dom::UniFFICallbackHandler; +using dom::UniFFIPointer; +using dom::UniFFIScaffoldingCallResult; + +// Define scaffolding functions from UniFFI +extern "C" { + {%- for (ci, config) in components %} + {%- for func in ci.iter_user_ffi_function_definitions() %} + {{ func.rust_return_type() }} {{ func.rust_name() }}({{ func.rust_arg_list() }}); + {%- endfor %} + {%- endfor %} +} + +// Define pointer types +{%- for (ci, config) in components %} +{%- for object in ci.object_definitions() %} +{%- let pointer_type = ci.pointer_type(object) %} +const static mozilla::uniffi::UniFFIPointerType {{ pointer_type }} { + "{{ "{}::{}"|format(ci.namespace(), object.name()) }}"_ns, + {{ object.ffi_object_free().rust_name() }} +}; +{%- endfor %} +{%- endfor %} + +// Define the data we need per-callback interface +{%- for (ci, config) in components %} +{%- for cbi in ci.callback_interface_definitions() %} +MOZ_CAN_RUN_SCRIPT +extern "C" int {{ cbi.c_handler(prefix) }}(uint64_t aHandle, uint32_t aMethod, const uint8_t* aArgsData, int32_t aArgsLen, RustBuffer* aOutBuffer) { + // Currently, we only support "fire-and-forget" async callbacks. These are + // callbacks that run asynchronously without returning anything. The main + // use case for callbacks is logging, which fits very well with this model. + // + // So, here we simple queue the callback and return immediately. + mozilla::uniffi::QueueCallback({{ callback_ids.get(ci, cbi) }}, aHandle, aMethod, aArgsData, aArgsLen); + return CALLBACK_INTERFACE_SUCCESS; +} +static StaticRefPtr<dom::UniFFICallbackHandler> {{ cbi.js_handler() }}; +{%- endfor %} +{%- endfor %} + +// Define a lookup function for our callback interface info +Maybe<CallbackInterfaceInfo> {{ prefix }}GetCallbackInterfaceInfo(uint64_t aInterfaceId) { + switch(aInterfaceId) { + {%- for (ci, config) in components %} + {%- for cbi in ci.callback_interface_definitions() %} + case {{ callback_ids.get(ci, cbi) }}: { // {{ callback_ids.name(ci, cbi) }} + return Some(CallbackInterfaceInfo { + "{{ cbi.name() }}", + &{{ cbi.js_handler() }}, + {{ cbi.c_handler(prefix) }}, + {{ cbi.ffi_init_callback().name() }}, + }); + } + {%- endfor %} + {%- endfor %} + + default: + return Nothing(); + } +} + +Maybe<already_AddRefed<Promise>> {{ prefix }}CallAsync(const GlobalObject& aGlobal, uint64_t aId, const Sequence<ScaffoldingType>& aArgs, ErrorResult& aError) { + switch (aId) { + {%- for (ci, config) in components %} + {%- for func in ci.exposed_functions() %} + case {{ function_ids.get(ci, func) }}: { // {{ function_ids.name(ci, func) }} + using CallHandler = {{ ci.scaffolding_call_handler(func) }}; + return Some(CallHandler::CallAsync({{ func.rust_name() }}, aGlobal, aArgs, "{{ func.name() }}: "_ns, aError)); + } + {%- endfor %} + {%- endfor %} + } + return Nothing(); +} + +bool {{ prefix }}CallSync(const GlobalObject& aGlobal, uint64_t aId, const Sequence<ScaffoldingType>& aArgs, RootedDictionary<UniFFIScaffoldingCallResult>& aReturnValue, ErrorResult& aError) { + switch (aId) { + {%- for (ci, config) in components %} + {%- for func in ci.exposed_functions() %} + case {{ function_ids.get(ci, func) }}: { // {{ function_ids.name(ci, func) }} + using CallHandler = {{ ci.scaffolding_call_handler(func) }}; + CallHandler::CallSync({{ func.rust_name() }}, aGlobal, aArgs, aReturnValue, "{{ func.name() }}: "_ns, aError); + return true; + } + {%- endfor %} + {%- endfor %} + } + return false; +} + +Maybe<already_AddRefed<UniFFIPointer>> {{ prefix }}ReadPointer(const GlobalObject& aGlobal, uint64_t aId, const ArrayBuffer& aArrayBuff, long aPosition, ErrorResult& aError) { + {%- if self.has_any_objects() %} + const UniFFIPointerType* type; + switch (aId) { + {%- for (ci, config) in components %} + {%- for object in ci.object_definitions() %} + case {{ object_ids.get(ci, object) }}: { // {{ object_ids.name(ci, object) }} + type = &{{ ci.pointer_type(object) }}; + break; + } + {%- endfor %} + {%- endfor %} + default: + return Nothing(); + } + return Some(UniFFIPointer::Read(aArrayBuff, aPosition, type, aError)); + {%- else %} + return Nothing(); + {%- endif %} +} + +bool {{ prefix }}WritePointer(const GlobalObject& aGlobal, uint64_t aId, const UniFFIPointer& aPtr, const ArrayBuffer& aArrayBuff, long aPosition, ErrorResult& aError) { + {%- if self.has_any_objects() %} + const UniFFIPointerType* type; + switch (aId) { + {%- for (ci, config) in components %} + {%- for object in ci.object_definitions() %} + case {{ object_ids.get(ci, object) }}: { // {{ object_ids.name(ci, object) }} + type = &{{ ci.pointer_type(object) }}; + break; + } + {%- endfor %} + {%- endfor %} + default: + return false; + } + aPtr.Write(aArrayBuff, aPosition, type, aError); + return true; + {%- else %} + return false; + {%- endif %} +} + +} // namespace mozilla::uniffi 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" %} |