/* 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/. */ extern crate libc; extern crate nserror; extern crate nsstring; extern crate xpcom; mod bag; use std::{ borrow::Cow, convert::{TryFrom, TryInto}, }; use libc::c_double; use nserror::{nsresult, NS_ERROR_CANNOT_CONVERT_DATA, NS_OK}; use nsstring::{nsACString, nsAString, nsCString, nsString}; use xpcom::{getter_addrefs, interfaces::nsIVariant, RefPtr}; pub use crate::bag::HashPropertyBag; extern "C" { fn NS_GetDataType(variant: *const nsIVariant) -> u16; fn NS_NewStorageNullVariant(result: *mut *const nsIVariant); fn NS_NewStorageBooleanVariant(value: bool, result: *mut *const nsIVariant); fn NS_NewStorageIntegerVariant(value: i64, result: *mut *const nsIVariant); fn NS_NewStorageFloatVariant(value: c_double, result: *mut *const nsIVariant); fn NS_NewStorageTextVariant(value: *const nsAString, result: *mut *const nsIVariant); fn NS_NewStorageUTF8TextVariant(value: *const nsACString, result: *mut *const nsIVariant); } // These are the relevant parts of the nsXPTTypeTag enum in xptinfo.h, // which nsIVariant.idl reflects into the nsIDataType struct class and uses // to constrain the values of nsIVariant::dataType. #[repr(u16)] #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] pub enum DataType { Int32 = 2, Int64 = 3, Double = 9, Bool = 10, Void = 13, CharStr = 15, WCharStr = 16, StringSizeIs = 20, WStringSizeIs = 21, Utf8String = 24, CString = 25, AString = 26, EmptyArray = 254, Empty = 255, } impl TryFrom for DataType { type Error = nsresult; /// Converts a raw type tag for an `nsIVariant` into a `DataType` variant. /// Returns `NS_ERROR_CANNOT_CONVERT_DATA` if the type isn't one that we /// support. fn try_from(raw: u16) -> Result { Ok(match raw { 2 => DataType::Int32, 3 => DataType::Int64, 9 => DataType::Double, 10 => DataType::Bool, 13 => DataType::Void, 15 => DataType::CharStr, 16 => DataType::WCharStr, 20 => DataType::StringSizeIs, 21 => DataType::WStringSizeIs, 24 => DataType::Utf8String, 25 => DataType::CString, 26 => DataType::AString, 254 => DataType::EmptyArray, 255 => DataType::Empty, _ => Err(NS_ERROR_CANNOT_CONVERT_DATA)?, }) } } /// Extension methods implemented on `nsIVariant` types, to make them easier /// to work with. pub trait NsIVariantExt { /// Returns the raw type tag for this variant. Call /// `DataType::try_from()` on this tag to turn it into a `DataType`. fn get_data_type(&self) -> u16; /// Tries to clone this variant, failing with `NS_ERROR_CANNOT_CONVERT_DATA` /// if its type is unsupported. fn try_clone(&self) -> Result, nsresult>; } impl NsIVariantExt for nsIVariant { fn get_data_type(&self) -> u16 { unsafe { NS_GetDataType(self) } } fn try_clone(&self) -> Result, nsresult> { Ok(match self.get_data_type().try_into()? { DataType::Bool => bool::from_variant(self)?.into_variant(), DataType::Int32 => i32::from_variant(self)?.into_variant(), DataType::Int64 => i64::from_variant(self)?.into_variant(), DataType::Double => f64::from_variant(self)?.into_variant(), DataType::AString | DataType::WCharStr | DataType::WStringSizeIs => { nsString::from_variant(self)?.into_variant() } DataType::CString | DataType::CharStr | DataType::StringSizeIs | DataType::Utf8String => nsCString::from_variant(self)?.into_variant(), DataType::Void | DataType::EmptyArray | DataType::Empty => ().into_variant(), }) } } pub trait VariantType { fn type_name() -> Cow<'static, str>; fn into_variant(self) -> RefPtr; fn from_variant(variant: &nsIVariant) -> Result where Self: Sized; } /// Implements traits to convert between variants and their types. macro_rules! variant { ($typ:ident, $constructor:ident, $getter:ident) => { impl VariantType for $typ { fn type_name() -> Cow<'static, str> { stringify!($typ).into() } fn into_variant(self) -> RefPtr { // getter_addrefs returns a Result, nsresult>, // but we know that our $constructor is infallible, so we can // safely unwrap and return the RefPtr. getter_addrefs(|p| { unsafe { $constructor(self.into(), p) }; NS_OK }) .unwrap() } fn from_variant(variant: &nsIVariant) -> Result<$typ, nsresult> { let mut result = $typ::default(); let rv = unsafe { variant.$getter(&mut result) }; if rv.succeeded() { Ok(result) } else { Err(rv) } } } }; (* $typ:ident, $constructor:ident, $getter:ident) => { impl VariantType for $typ { fn type_name() -> Cow<'static, str> { stringify!($typ).into() } fn into_variant(self) -> RefPtr { // getter_addrefs returns a Result, nsresult>, // but we know that our $constructor is infallible, so we can // safely unwrap and return the RefPtr. getter_addrefs(|p| { unsafe { $constructor(&*self, p) }; NS_OK }) .unwrap() } fn from_variant(variant: &nsIVariant) -> Result<$typ, nsresult> { let mut result = $typ::new(); let rv = unsafe { variant.$getter(&mut *result) }; if rv.succeeded() { Ok(result) } else { Err(rv) } } } }; } // The unit type (()) is a reasonable equivalation of the null variant. // The macro can't produce its implementations of VariantType, however, // so we implement them concretely. impl VariantType for () { fn type_name() -> Cow<'static, str> { "()".into() } fn into_variant(self) -> RefPtr { // getter_addrefs returns a Result, nsresult>, // but we know that NS_NewStorageNullVariant is infallible, so we can // safely unwrap and return the RefPtr. getter_addrefs(|p| { unsafe { NS_NewStorageNullVariant(p) }; NS_OK }) .unwrap() } fn from_variant(_variant: &nsIVariant) -> Result { Ok(()) } } impl VariantType for Option where T: VariantType, { fn type_name() -> Cow<'static, str> { format!("Option<{}>", T::type_name()).into() } fn into_variant(self) -> RefPtr { match self { Some(v) => v.into_variant(), None => ().into_variant(), } } fn from_variant(variant: &nsIVariant) -> Result { Ok(match variant.get_data_type().try_into() { Ok(DataType::Void) | Ok(DataType::EmptyArray) | Ok(DataType::Empty) => None, _ => Some(VariantType::from_variant(variant)?), }) } } variant!(bool, NS_NewStorageBooleanVariant, GetAsBool); variant!(i32, NS_NewStorageIntegerVariant, GetAsInt32); variant!(i64, NS_NewStorageIntegerVariant, GetAsInt64); variant!(f64, NS_NewStorageFloatVariant, GetAsDouble); variant!(*nsString, NS_NewStorageTextVariant, GetAsAString); variant!(*nsCString, NS_NewStorageUTF8TextVariant, GetAsAUTF8String);