summaryrefslogtreecommitdiffstats
path: root/toolkit/components/uniffi-bindgen-gecko-js/src
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/uniffi-bindgen-gecko-js/src')
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/ci_list.rs198
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/lib.rs157
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/main.rs9
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/render/cpp.rs199
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/render/js.rs333
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/render/mod.rs7
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/render/shared.rs43
-rw-r--r--toolkit/components/uniffi-bindgen-gecko-js/src/templates/UniFFIScaffolding.cpp155
-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
37 files changed, 2543 insertions, 0 deletions
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/ci_list.rs b/toolkit/components/uniffi-bindgen-gecko-js/src/ci_list.rs
new file mode 100644
index 0000000000..e79283cd57
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/ci_list.rs
@@ -0,0 +1,198 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Manage the universe of ComponentInterfaces / Configs
+//!
+//! uniffi-bindgen-gecko-js is unique because it generates bindings over a set of UDL files rather
+//! than just one. This is because we want to generate the WebIDL statically rather than generate
+//! it. To accomplish that, each WebIDL function inputs an opaque integer id that identifies which
+//! version of it should run, for example `CallSync` inputs a function id. Operating on all UDL
+//! files at once simplifies the task of ensuring those ids are to be unique and consistent between
+//! the JS and c++ code.
+//!
+//! This module manages the list of ComponentInterface and the object ids.
+
+use crate::render::cpp::ComponentInterfaceCppExt;
+use crate::{Config, ConfigMap};
+use anyhow::{bail, Context, Result};
+use camino::Utf8PathBuf;
+use std::collections::{BTreeSet, HashMap, HashSet};
+use uniffi_bindgen::interface::{CallbackInterface, ComponentInterface, FfiFunction, Object};
+
+pub struct ComponentUniverse {
+ pub components: Vec<(ComponentInterface, Config)>,
+ pub fixture_components: Vec<(ComponentInterface, Config)>,
+}
+
+impl ComponentUniverse {
+ pub fn new(config_map: ConfigMap) -> Result<Self> {
+ let universe = Self {
+ components: parse_udl_files(&config_map, false)?,
+ fixture_components: parse_udl_files(&config_map, true)?,
+ };
+ universe.check_udl_namespaces_unique()?;
+ universe.check_callback_interfaces()?;
+ Ok(universe)
+ }
+
+ fn check_udl_namespaces_unique(&self) -> Result<()> {
+ let mut set = HashSet::new();
+ for ci in self.iter_cis() {
+ if !set.insert(ci.namespace()) {
+ bail!("UDL files have duplicate namespace: {}", ci.namespace());
+ }
+ }
+ Ok(())
+ }
+
+ fn check_callback_interfaces(&self) -> Result<()> {
+ // We don't currently support callback interfaces returning values or throwing errors.
+ for ci in self.iter_cis() {
+ for cbi in ci.callback_interface_definitions() {
+ for method in cbi.methods() {
+ if method.return_type().is_some() {
+ bail!("Callback interface method {}.{} throws an error, which is not yet supported", cbi.name(), method.name())
+ }
+ if method.throws_type().is_some() {
+ bail!("Callback interface method {}.{} returns a value, which is not yet supported", cbi.name(), method.name())
+ }
+ }
+ }
+ }
+ Ok(())
+ }
+
+ pub fn iter_cis(&self) -> impl Iterator<Item = &ComponentInterface> {
+ self.components
+ .iter()
+ .chain(self.fixture_components.iter())
+ .map(|(ci, _)| ci)
+ }
+}
+
+fn parse_udl_files(
+ config_map: &ConfigMap,
+ fixture: bool,
+) -> Result<Vec<(ComponentInterface, Config)>> {
+ // Sort config entries to ensure consistent output
+ let mut entries: Vec<_> = config_map.iter().collect();
+ entries.sort_by_key(|(key, _)| *key);
+ entries
+ .into_iter()
+ .filter_map(|(_, config)| {
+ if config.fixture == fixture {
+ Some(parse_udl_file(&config).map(|ci| (ci, config.clone())))
+ } else {
+ None
+ }
+ })
+ .collect()
+}
+
+fn parse_udl_file(config: &Config) -> Result<ComponentInterface> {
+ let udl_file = Utf8PathBuf::from(&config.udl_file);
+ let udl = std::fs::read_to_string(udl_file)
+ .context(format!("Error reading UDL file '{}'", config.udl_file))?;
+ ComponentInterface::from_webidl(&udl, &config.crate_name)
+ .context(format!("Failed to parse UDL '{}'", config.udl_file))
+}
+
+pub struct FunctionIds<'a> {
+ // Map (CI namespace, func name) -> Ids
+ map: HashMap<(&'a str, &'a str), usize>,
+}
+
+impl<'a> FunctionIds<'a> {
+ pub fn new(cis: &'a ComponentUniverse) -> Self {
+ Self {
+ map: cis
+ .iter_cis()
+ .flat_map(|ci| {
+ ci.exposed_functions()
+ .into_iter()
+ .map(move |f| (ci.namespace(), f.name()))
+ })
+ .enumerate()
+ .map(|(i, (namespace, name))| ((namespace, name), i))
+ // Sort using BTreeSet to guarantee the IDs remain stable across runs
+ .collect::<BTreeSet<_>>()
+ .into_iter()
+ .collect(),
+ }
+ }
+
+ pub fn get(&self, ci: &ComponentInterface, func: &FfiFunction) -> usize {
+ return *self.map.get(&(ci.namespace(), func.name())).unwrap();
+ }
+
+ pub fn name(&self, ci: &ComponentInterface, func: &FfiFunction) -> String {
+ format!("{}:{}", ci.namespace(), func.name())
+ }
+}
+
+pub struct ObjectIds<'a> {
+ // Map (CI namespace, object name) -> Ids
+ map: HashMap<(&'a str, &'a str), usize>,
+}
+
+impl<'a> ObjectIds<'a> {
+ pub fn new(cis: &'a ComponentUniverse) -> Self {
+ Self {
+ map: cis
+ .iter_cis()
+ .flat_map(|ci| {
+ ci.object_definitions()
+ .iter()
+ .map(move |o| (ci.namespace(), o.name()))
+ })
+ .enumerate()
+ .map(|(i, (namespace, name))| ((namespace, name), i))
+ // Sort using BTreeSet to guarantee the IDs remain stable across runs
+ .collect::<BTreeSet<_>>()
+ .into_iter()
+ .collect(),
+ }
+ }
+
+ pub fn get(&self, ci: &ComponentInterface, obj: &Object) -> usize {
+ return *self.map.get(&(ci.namespace(), obj.name())).unwrap();
+ }
+
+ pub fn name(&self, ci: &ComponentInterface, obj: &Object) -> String {
+ format!("{}:{}", ci.namespace(), obj.name())
+ }
+}
+
+pub struct CallbackIds<'a> {
+ // Map (CI namespace, callback name) -> Ids
+ map: HashMap<(&'a str, &'a str), usize>,
+}
+
+impl<'a> CallbackIds<'a> {
+ pub fn new(cis: &'a ComponentUniverse) -> Self {
+ Self {
+ map: cis
+ .iter_cis()
+ .flat_map(|ci| {
+ ci.callback_interface_definitions()
+ .iter()
+ .map(move |cb| (ci.namespace(), cb.name()))
+ })
+ .enumerate()
+ .map(|(i, (namespace, name))| ((namespace, name), i))
+ // Sort using BTreeSet to guarantee the IDs remain stable across runs
+ .collect::<BTreeSet<_>>()
+ .into_iter()
+ .collect(),
+ }
+ }
+
+ pub fn get(&self, ci: &ComponentInterface, cb: &CallbackInterface) -> usize {
+ return *self.map.get(&(ci.namespace(), cb.name())).unwrap();
+ }
+
+ pub fn name(&self, ci: &ComponentInterface, cb: &CallbackInterface) -> String {
+ format!("{}:{}", ci.namespace(), cb.name())
+ }
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/lib.rs b/toolkit/components/uniffi-bindgen-gecko-js/src/lib.rs
new file mode 100644
index 0000000000..32b7cb438b
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/lib.rs
@@ -0,0 +1,157 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use anyhow::{Context, Result};
+use askama::Template;
+use camino::Utf8PathBuf;
+use clap::Parser;
+use serde::{Deserialize, Serialize};
+use std::collections::{HashMap, HashSet};
+use std::fs::File;
+use std::io::Write;
+use uniffi_bindgen::ComponentInterface;
+
+mod ci_list;
+mod render;
+
+use ci_list::{CallbackIds, ComponentUniverse, FunctionIds, ObjectIds};
+use render::cpp::CPPScaffoldingTemplate;
+use render::js::JSBindingsTemplate;
+
+#[derive(Debug, Parser)]
+#[clap(name = "uniffi-bindgen-gecko-js")]
+#[clap(version = clap::crate_version!())]
+#[clap(about = "JS bindings generator for Rust")]
+#[clap(propagate_version = true)]
+struct CliArgs {
+ // This is a really convoluted set of arguments, but we're only expecting to be called by
+ // `mach_commands.py`
+ #[clap(long, value_name = "FILE")]
+ js_dir: Utf8PathBuf,
+
+ #[clap(long, value_name = "FILE")]
+ fixture_js_dir: Utf8PathBuf,
+
+ #[clap(long, value_name = "FILE")]
+ cpp_path: Utf8PathBuf,
+
+ #[clap(long, value_name = "FILE")]
+ fixture_cpp_path: Utf8PathBuf,
+}
+
+/// Configuration for all components, read from `uniffi.toml`
+type ConfigMap = HashMap<String, Config>;
+
+/// Configuration for a single Component
+#[derive(Clone, Debug, Default, Deserialize, Serialize)]
+pub struct Config {
+ crate_name: String,
+ udl_file: String,
+ #[serde(default)]
+ fixture: bool,
+ #[serde(default)]
+ receiver_thread: ReceiverThreadConfig,
+}
+
+#[derive(Clone, Debug, Default, Deserialize, Serialize)]
+struct ReceiverThreadConfig {
+ #[serde(default)]
+ default: Option<String>,
+ #[serde(default)]
+ main: HashSet<String>,
+ #[serde(default)]
+ worker: HashSet<String>,
+}
+
+fn render(out_path: Utf8PathBuf, template: impl Template) -> Result<()> {
+ println!("rendering {}", out_path);
+ let contents = template.render()?;
+ let mut f =
+ File::create(&out_path).context(format!("Failed to create {:?}", out_path.file_name()))?;
+ write!(f, "{}\n", contents).context(format!("Failed to write to {}", out_path))
+}
+
+fn render_cpp(
+ path: Utf8PathBuf,
+ prefix: &str,
+ components: &Vec<(ComponentInterface, Config)>,
+ function_ids: &FunctionIds,
+ object_ids: &ObjectIds,
+ callback_ids: &CallbackIds,
+) -> Result<()> {
+ render(
+ path,
+ CPPScaffoldingTemplate {
+ prefix,
+ components,
+ function_ids,
+ object_ids,
+ callback_ids,
+ },
+ )
+}
+
+fn render_js(
+ out_dir: Utf8PathBuf,
+ components: &Vec<(ComponentInterface, Config)>,
+ function_ids: &FunctionIds,
+ object_ids: &ObjectIds,
+ callback_ids: &CallbackIds,
+) -> Result<()> {
+ for (ci, config) in components {
+ let template = JSBindingsTemplate {
+ ci,
+ config,
+ function_ids,
+ object_ids,
+ callback_ids,
+ };
+ let path = out_dir.join(template.js_module_name());
+ render(path, template)?;
+ }
+ Ok(())
+}
+
+pub fn run_main() -> Result<()> {
+ let args = CliArgs::parse();
+ let config_map: ConfigMap =
+ toml::from_str(include_str!("../config.toml")).expect("Error parsing config.toml");
+ let components = ComponentUniverse::new(config_map)?;
+ let function_ids = FunctionIds::new(&components);
+ let object_ids = ObjectIds::new(&components);
+ let callback_ids = CallbackIds::new(&components);
+
+ render_cpp(
+ args.cpp_path,
+ "UniFFI",
+ &components.components,
+ &function_ids,
+ &object_ids,
+ &callback_ids,
+ )?;
+ render_cpp(
+ args.fixture_cpp_path,
+ "UniFFIFixtures",
+ &components.fixture_components,
+ &function_ids,
+ &object_ids,
+ &callback_ids,
+ )?;
+ render_js(
+ args.js_dir,
+ &components.components,
+ &function_ids,
+ &object_ids,
+ &callback_ids,
+ )?;
+ render_js(
+ args.fixture_js_dir,
+ &components.fixture_components,
+ &function_ids,
+ &object_ids,
+ &callback_ids,
+ )?;
+
+ Ok(())
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/main.rs b/toolkit/components/uniffi-bindgen-gecko-js/src/main.rs
new file mode 100644
index 0000000000..eefe72ba66
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/main.rs
@@ -0,0 +1,9 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use anyhow::Result;
+
+fn main() -> Result<()> {
+ uniffi_bindgen_gecko_js::run_main()
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/render/cpp.rs b/toolkit/components/uniffi-bindgen-gecko-js/src/render/cpp.rs
new file mode 100644
index 0000000000..685c3c2bf3
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/render/cpp.rs
@@ -0,0 +1,199 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{CallbackIds, Config, FunctionIds, ObjectIds};
+use askama::Template;
+use extend::ext;
+use heck::{ToShoutySnakeCase, ToUpperCamelCase};
+use std::collections::HashSet;
+use std::iter;
+use uniffi_bindgen::interface::{
+ CallbackInterface, ComponentInterface, FfiArgument, FfiFunction, FfiType, Object,
+};
+
+#[derive(Template)]
+#[template(path = "UniFFIScaffolding.cpp", escape = "none")]
+pub struct CPPScaffoldingTemplate<'a> {
+ // Prefix for each function name in. This is related to how we handle the test fixtures. For
+ // each function defined in the UniFFI namespace in UniFFI.webidl we:
+ // - Generate a function in to handle it using the real UDL files
+ // - Generate a different function in for handle it using the fixture UDL files
+ // - Have a hand-written stub function that always calls the first function and only calls
+ // the second function in if MOZ_UNIFFI_FIXTURES is defined.
+ pub prefix: &'a str,
+ pub components: &'a Vec<(ComponentInterface, Config)>,
+ pub function_ids: &'a FunctionIds<'a>,
+ pub object_ids: &'a ObjectIds<'a>,
+ pub callback_ids: &'a CallbackIds<'a>,
+}
+
+impl<'a> CPPScaffoldingTemplate<'a> {
+ fn has_any_objects(&self) -> bool {
+ self.components
+ .iter()
+ .any(|(ci, _)| ci.object_definitions().len() > 0)
+ }
+}
+
+// Define extension traits with methods used in our template code
+
+#[ext(name=ComponentInterfaceCppExt)]
+pub impl ComponentInterface {
+ // C++ pointer type name. This needs to be a valid C++ type name and unique across all UDL
+ // files.
+ fn pointer_type(&self, object: &Object) -> String {
+ self._pointer_type(object.name())
+ }
+
+ fn _pointer_type(&self, name: &str) -> String {
+ format!(
+ "k{}{}PointerType",
+ self.namespace().to_upper_camel_case(),
+ name.to_upper_camel_case()
+ )
+ }
+
+ // Iterate over all functions to expose via the UniFFIScaffolding class
+ //
+ // This is basically all the user functions, except we don't expose the free methods for
+ // objects. Freeing is handled by the UniFFIPointer class.
+ //
+ // Note: this function should return `impl Iterator<&FfiFunction>`, but that's not currently
+ // allowed for traits.
+ fn exposed_functions(&self) -> Vec<&FfiFunction> {
+ let excluded: HashSet<_> = self
+ .object_definitions()
+ .iter()
+ .map(|o| o.ffi_object_free().name())
+ .chain(
+ self.callback_interface_definitions()
+ .iter()
+ .map(|cbi| cbi.ffi_init_callback().name()),
+ )
+ .collect();
+ self.iter_user_ffi_function_definitions()
+ .filter(move |f| !excluded.contains(f.name()))
+ .collect()
+ }
+
+ // ScaffoldingConverter class
+ //
+ // This is used to convert types between the JS code and Rust
+ fn scaffolding_converter(&self, ffi_type: &FfiType) -> String {
+ match ffi_type {
+ FfiType::RustArcPtr(name) => {
+ format!("ScaffoldingObjectConverter<&{}>", self._pointer_type(name),)
+ }
+ _ => format!("ScaffoldingConverter<{}>", ffi_type.rust_type()),
+ }
+ }
+
+ // ScaffoldingCallHandler class
+ fn scaffolding_call_handler(&self, func: &FfiFunction) -> String {
+ let return_param = match func.return_type() {
+ Some(return_type) => self.scaffolding_converter(return_type),
+ None => "ScaffoldingConverter<void>".to_string(),
+ };
+ let all_params = iter::once(return_param)
+ .chain(
+ func.arguments()
+ .into_iter()
+ .map(|a| self.scaffolding_converter(&a.type_())),
+ )
+ .collect::<Vec<_>>()
+ .join(", ");
+ return format!("ScaffoldingCallHandler<{}>", all_params);
+ }
+}
+
+#[ext(name=FFIFunctionCppExt)]
+pub impl FfiFunction {
+ fn nm(&self) -> String {
+ self.name().to_upper_camel_case()
+ }
+
+ fn rust_name(&self) -> String {
+ self.name().to_string()
+ }
+
+ fn rust_return_type(&self) -> String {
+ match self.return_type() {
+ Some(t) => t.rust_type(),
+ None => "void".to_owned(),
+ }
+ }
+
+ fn rust_arg_list(&self) -> String {
+ let mut parts: Vec<String> = self.arguments().iter().map(|a| a.rust_type()).collect();
+ parts.push("RustCallStatus*".to_owned());
+ parts.join(", ")
+ }
+}
+
+#[ext(name=FFITypeCppExt)]
+pub impl FfiType {
+ // Type for the Rust scaffolding code
+ fn rust_type(&self) -> String {
+ match self {
+ FfiType::UInt8 => "uint8_t",
+ FfiType::Int8 => "int8_t",
+ FfiType::UInt16 => "uint16_t",
+ FfiType::Int16 => "int16_t",
+ FfiType::UInt32 => "uint32_t",
+ FfiType::Int32 => "int32_t",
+ FfiType::UInt64 => "uint64_t",
+ FfiType::Int64 => "int64_t",
+ FfiType::Float32 => "float",
+ FfiType::Float64 => "double",
+ FfiType::RustBuffer(_) => "RustBuffer",
+ FfiType::RustArcPtr(_) => "void *",
+ FfiType::ForeignCallback => "ForeignCallback",
+ FfiType::ForeignBytes => unimplemented!("ForeignBytes not supported"),
+ FfiType::ForeignExecutorHandle => unimplemented!("ForeignExecutorHandle not supported"),
+ FfiType::ForeignExecutorCallback => {
+ unimplemented!("ForeignExecutorCallback not supported")
+ }
+ FfiType::RustFutureHandle
+ | FfiType::RustFutureContinuationCallback
+ | FfiType::RustFutureContinuationData => {
+ unimplemented!("Rust async functions not supported")
+ }
+ }
+ .to_owned()
+ }
+}
+
+#[ext(name=FFIArgumentCppExt)]
+pub impl FfiArgument {
+ fn rust_type(&self) -> String {
+ self.type_().rust_type()
+ }
+}
+
+#[ext(name=ObjectCppExt)]
+pub impl Object {
+ fn nm(&self) -> String {
+ self.name().to_upper_camel_case()
+ }
+}
+
+#[ext(name=CallbackInterfaceCppExt)]
+pub impl CallbackInterface {
+ fn nm(&self) -> String {
+ self.name().to_upper_camel_case()
+ }
+
+ /// Name of the static pointer to the JS callback handler
+ fn js_handler(&self) -> String {
+ format!("JS_CALLBACK_HANDLER_{}", self.name().to_shouty_snake_case())
+ }
+
+ /// Name of the C function handler
+ fn c_handler(&self, prefix: &str) -> String {
+ format!(
+ "{prefix}CallbackHandler{}",
+ self.name().to_upper_camel_case()
+ )
+ }
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/render/js.rs b/toolkit/components/uniffi-bindgen-gecko-js/src/render/js.rs
new file mode 100644
index 0000000000..efd7b42456
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/render/js.rs
@@ -0,0 +1,333 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::shared::*;
+use crate::{CallbackIds, Config, FunctionIds, ObjectIds};
+use askama::Template;
+use extend::ext;
+use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase};
+use uniffi_bindgen::interface::{
+ Argument, AsType, CallbackInterface, ComponentInterface, Constructor, Enum, Field, Function,
+ Literal, Method, Object, Radix, Record, Type,
+};
+
+fn arg_names(args: &[&Argument]) -> String {
+ args.iter()
+ .map(|arg| {
+ if let Some(default_value) = arg.default_value() {
+ format!("{} = {}", arg.nm(), default_value.render())
+ } else {
+ arg.nm()
+ }
+ })
+ .collect::<Vec<String>>()
+ .join(",")
+}
+
+fn render_enum_literal(typ: &Type, variant_name: &str) -> String {
+ if let Type::Enum { name, .. } = typ {
+ // TODO: This does not support complex enum literals yet.
+ return format!(
+ "{}.{}",
+ name.to_upper_camel_case(),
+ variant_name.to_shouty_snake_case()
+ );
+ } else {
+ panic!("Rendering an enum literal on a type that is not an enum")
+ }
+}
+
+#[derive(Template)]
+#[template(path = "js/wrapper.sys.mjs", escape = "none")]
+pub struct JSBindingsTemplate<'a> {
+ pub ci: &'a ComponentInterface,
+ pub config: &'a Config,
+ pub function_ids: &'a FunctionIds<'a>,
+ pub object_ids: &'a ObjectIds<'a>,
+ pub callback_ids: &'a CallbackIds<'a>,
+}
+
+impl<'a> JSBindingsTemplate<'a> {
+ pub fn js_module_name(&self) -> String {
+ self.js_module_name_for_ci_namespace(self.ci.namespace())
+ }
+
+ fn external_type_module(&self, crate_name: &str) -> String {
+ format!(
+ "resource://gre/modules/{}",
+ self.js_module_name_for_crate_name(crate_name),
+ )
+ }
+
+ // TODO: Once https://phabricator.services.mozilla.com/D156116 is merged maybe the next two
+ // functions should use a map from the config file
+
+ fn js_module_name_for_ci_namespace(&self, namespace: &str) -> String {
+ // The plain namespace name is a bit too generic as a module name for m-c, so we
+ // prefix it with "Rust". Later we'll probably allow this to be customized.
+ format!("Rust{}.sys.mjs", namespace.to_upper_camel_case())
+ }
+
+ fn js_module_name_for_crate_name(&self, crate_name: &str) -> String {
+ let namespace = match crate_name {
+ "uniffi_geometry" => "geometry",
+ s => s,
+ };
+ self.js_module_name_for_ci_namespace(namespace)
+ }
+}
+
+// Define extension traits with methods used in our template code
+
+#[ext(name=LiteralJSExt)]
+pub impl Literal {
+ fn render(&self) -> String {
+ match self {
+ Literal::Boolean(inner) => inner.to_string(),
+ Literal::String(inner) => format!("\"{}\"", inner),
+ Literal::UInt(num, radix, _) => format!("{}", radix.render_num(num)),
+ Literal::Int(num, radix, _) => format!("{}", radix.render_num(num)),
+ Literal::Float(num, _) => num.clone(),
+ Literal::Enum(name, typ) => render_enum_literal(typ, name),
+ Literal::EmptyMap => "{}".to_string(),
+ Literal::EmptySequence => "[]".to_string(),
+ Literal::Null => "null".to_string(),
+ }
+ }
+}
+
+#[ext(name=RadixJSExt)]
+pub impl Radix {
+ fn render_num(
+ &self,
+ num: impl std::fmt::Display + std::fmt::LowerHex + std::fmt::Octal,
+ ) -> String {
+ match self {
+ Radix::Decimal => format!("{}", num),
+ Radix::Hexadecimal => format!("{:#x}", num),
+ Radix::Octal => format!("{:#o}", num),
+ }
+ }
+}
+
+#[ext(name=RecordJSExt)]
+pub impl Record {
+ fn nm(&self) -> String {
+ self.name().to_upper_camel_case()
+ }
+
+ fn constructor_field_list(&self) -> String {
+ let o = self
+ .fields()
+ .iter()
+ .map(|field| {
+ if let Some(default_value) = field.default_value() {
+ format!("{} = {}", field.nm(), default_value.render())
+ } else {
+ field.nm()
+ }
+ })
+ .collect::<Vec<String>>()
+ .join(", ");
+ format!("{{ {o} }}")
+ }
+}
+
+#[ext(name=CallbackInterfaceJSExt)]
+pub impl CallbackInterface {
+ fn nm(&self) -> String {
+ self.name().to_upper_camel_case()
+ }
+
+ fn handler(&self) -> String {
+ format!("callbackHandler{}", self.nm())
+ }
+}
+
+#[ext(name=FieldJSExt)]
+pub impl Field {
+ fn nm(&self) -> String {
+ self.name().to_lower_camel_case()
+ }
+
+ fn lower_fn(&self) -> String {
+ self.as_type().lower_fn()
+ }
+
+ fn lift_fn(&self) -> String {
+ self.as_type().lift_fn()
+ }
+
+ fn write_datastream_fn(&self) -> String {
+ self.as_type().write_datastream_fn()
+ }
+
+ fn read_datastream_fn(&self) -> String {
+ self.as_type().read_datastream_fn()
+ }
+
+ fn compute_size_fn(&self) -> String {
+ self.as_type().compute_size_fn()
+ }
+
+ fn ffi_converter(&self) -> String {
+ self.as_type().ffi_converter()
+ }
+}
+
+#[ext(name=ArgumentJSExt)]
+pub impl Argument {
+ fn nm(&self) -> String {
+ self.name().to_lower_camel_case()
+ }
+
+ fn lower_fn(&self) -> String {
+ self.as_type().lower_fn()
+ }
+
+ fn lift_fn(&self) -> String {
+ self.as_type().lift_fn()
+ }
+
+ fn write_datastream_fn(&self) -> String {
+ self.as_type().write_datastream_fn()
+ }
+
+ fn read_datastream_fn(&self) -> String {
+ self.as_type().read_datastream_fn()
+ }
+
+ fn compute_size_fn(&self) -> String {
+ self.as_type().compute_size_fn()
+ }
+
+ fn ffi_converter(&self) -> String {
+ self.as_type().ffi_converter()
+ }
+}
+
+#[ext(name=TypeJSExt)]
+pub impl Type {
+ // Render an expression to check if two instances of this type are equal
+ fn equals(&self, first: &str, second: &str) -> String {
+ match self {
+ Type::Record { .. } => format!("{}.equals({})", first, second),
+ _ => format!("{} == {}", first, second),
+ }
+ }
+
+ fn lower_fn(&self) -> String {
+ format!("{}.lower", self.ffi_converter())
+ }
+
+ fn lift_fn(&self) -> String {
+ format!("{}.lift", self.ffi_converter())
+ }
+
+ fn write_datastream_fn(&self) -> String {
+ format!("{}.write", self.ffi_converter())
+ }
+
+ fn read_datastream_fn(&self) -> String {
+ format!("{}.read", self.ffi_converter())
+ }
+
+ fn compute_size_fn(&self) -> String {
+ format!("{}.computeSize", self.ffi_converter())
+ }
+
+ fn canonical_name(&self) -> String {
+ match self {
+ Type::Int8 => "i8".into(),
+ Type::UInt8 => "u8".into(),
+ Type::Int16 => "i16".into(),
+ Type::UInt16 => "u16".into(),
+ Type::Int32 => "i32".into(),
+ Type::UInt32 => "u32".into(),
+ Type::Int64 => "i64".into(),
+ Type::UInt64 => "u64".into(),
+ Type::Float32 => "f32".into(),
+ Type::Float64 => "f64".into(),
+ Type::String => "string".into(),
+ Type::Bytes => "bytes".into(),
+ Type::Boolean => "bool".into(),
+ Type::Object { name, .. }
+ | Type::Enum { name, .. }
+ | Type::Record { name, .. }
+ | Type::CallbackInterface { name, .. } => format!("Type{name}"),
+ Type::Timestamp => "Timestamp".into(),
+ Type::Duration => "Duration".into(),
+ Type::ForeignExecutor => "ForeignExecutor".into(),
+ Type::Optional { inner_type } => format!("Optional{}", inner_type.canonical_name()),
+ Type::Sequence { inner_type } => format!("Sequence{}", inner_type.canonical_name()),
+ Type::Map {
+ key_type,
+ value_type,
+ } => format!(
+ "Map{}{}",
+ key_type.canonical_name().to_upper_camel_case(),
+ value_type.canonical_name().to_upper_camel_case()
+ ),
+ Type::External { name, .. } | Type::Custom { name, .. } => format!("Type{name}"),
+ }
+ }
+
+ fn ffi_converter(&self) -> String {
+ format!(
+ "FfiConverter{}",
+ self.canonical_name().to_upper_camel_case()
+ )
+ }
+}
+
+#[ext(name=EnumJSExt)]
+pub impl Enum {
+ fn nm(&self) -> String {
+ self.name().to_upper_camel_case()
+ }
+}
+
+#[ext(name=FunctionJSExt)]
+pub impl Function {
+ fn arg_names(&self) -> String {
+ arg_names(self.arguments().as_slice())
+ }
+
+ fn nm(&self) -> String {
+ self.name().to_lower_camel_case()
+ }
+}
+
+#[ext(name=ObjectJSExt)]
+pub impl Object {
+ fn nm(&self) -> String {
+ self.name().to_upper_camel_case()
+ }
+}
+
+#[ext(name=ConstructorJSExt)]
+pub impl Constructor {
+ fn nm(&self) -> String {
+ if self.is_primary_constructor() {
+ "init".to_string()
+ } else {
+ self.name().to_lower_camel_case()
+ }
+ }
+
+ fn arg_names(&self) -> String {
+ arg_names(&self.arguments().as_slice())
+ }
+}
+
+#[ext(name=MethodJSExt)]
+pub impl Method {
+ fn arg_names(&self) -> String {
+ arg_names(self.arguments().as_slice())
+ }
+
+ fn nm(&self) -> String {
+ self.name().to_lower_camel_case()
+ }
+}
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/render/mod.rs b/toolkit/components/uniffi-bindgen-gecko-js/src/render/mod.rs
new file mode 100644
index 0000000000..f9ceeb9872
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/render/mod.rs
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub mod cpp;
+pub mod js;
+pub mod shared;
diff --git a/toolkit/components/uniffi-bindgen-gecko-js/src/render/shared.rs b/toolkit/components/uniffi-bindgen-gecko-js/src/render/shared.rs
new file mode 100644
index 0000000000..7b2d2e19ad
--- /dev/null
+++ b/toolkit/components/uniffi-bindgen-gecko-js/src/render/shared.rs
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/// Extension traits that are shared across multiple render targets
+use crate::Config;
+use extend::ext;
+use uniffi_bindgen::interface::{Function, Method, Object};
+
+/// Check if a JS function should be async.
+///
+/// `uniffi-bindgen-gecko-js` has special async handling. Many non-async Rust functions end up
+/// being async in js
+fn is_js_async(config: &Config, spec: &str) -> bool {
+ if config.receiver_thread.main.contains(spec) {
+ false
+ } else if config.receiver_thread.worker.contains(spec) {
+ true
+ } else {
+ match &config.receiver_thread.default {
+ Some(t) => t != "main",
+ _ => true,
+ }
+ }
+}
+
+#[ext]
+pub impl Function {
+ fn is_js_async(&self, config: &Config) -> bool {
+ is_js_async(config, self.name())
+ }
+}
+
+#[ext]
+pub impl Object {
+ fn is_constructor_async(&self, config: &Config) -> bool {
+ is_js_async(config, self.name())
+ }
+
+ fn is_method_async(&self, method: &Method, config: &Config) -> bool {
+ is_js_async(config, &format!("{}.{}", self.name(), method.name()))
+ }
+}
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" %}