diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /toolkit/components/uniffi-bindgen-gecko-js/src/render | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/uniffi-bindgen-gecko-js/src/render')
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())) + } +} |