summaryrefslogtreecommitdiffstats
path: root/toolkit/components/uniffi-bindgen-gecko-js/src/render
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/uniffi-bindgen-gecko-js/src/render')
-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
4 files changed, 582 insertions, 0 deletions
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()))
+ }
+}