summaryrefslogtreecommitdiffstats
path: root/storage/variant/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--storage/variant/src/bag.rs135
-rw-r--r--storage/variant/src/lib.rs230
2 files changed, 365 insertions, 0 deletions
diff --git a/storage/variant/src/bag.rs b/storage/variant/src/bag.rs
new file mode 100644
index 0000000000..91653ba226
--- /dev/null
+++ b/storage/variant/src/bag.rs
@@ -0,0 +1,135 @@
+/* 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 nserror::{nsresult, NS_ERROR_CANNOT_CONVERT_DATA, NS_OK};
+use nsstring::nsString;
+use xpcom::{
+ getter_addrefs,
+ interfaces::{nsIProperty, nsIPropertyBag, nsIWritablePropertyBag},
+ RefPtr, XpCom,
+};
+
+use crate::{NsIVariantExt, VariantType};
+
+extern "C" {
+ fn NS_NewHashPropertyBag(bag: *mut *const nsIWritablePropertyBag);
+}
+
+/// A hash property bag backed by storage variant values.
+pub struct HashPropertyBag(RefPtr<nsIWritablePropertyBag>);
+
+// This is safe as long as our `nsIWritablePropertyBag` is an instance of
+// `mozilla::nsHashPropertyBag`, which is atomically reference counted, and
+// all properties are backed by `Storage*Variant`s, all of which are
+// thread-safe.
+unsafe impl Send for HashPropertyBag {}
+unsafe impl Sync for HashPropertyBag {}
+
+impl Default for HashPropertyBag {
+ fn default() -> HashPropertyBag {
+ // This is safe to unwrap because `NS_NewHashPropertyBag` is infallible.
+ let bag = getter_addrefs(|p| {
+ unsafe { NS_NewHashPropertyBag(p) };
+ NS_OK
+ })
+ .unwrap();
+ HashPropertyBag(bag)
+ }
+}
+
+impl HashPropertyBag {
+ /// Creates an empty property bag.
+ #[inline]
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Creates a property bag from an instance of `nsIPropertyBag`, cloning its
+ /// contents. The `source` bag can only contain primitive values for which
+ /// the `VariantType` trait is implemented. Attempting to clone a bag with
+ /// unsupported types, such as arrays, interface pointers, and `jsval`s,
+ /// fails with `NS_ERROR_CANNOT_CONVERT_DATA`.
+ ///
+ /// `clone_from_bag` can be used to clone a thread-unsafe `nsIPropertyBag`,
+ /// like one passed from JavaScript via XPConnect, into one that can be
+ /// shared across threads.
+ pub fn clone_from_bag(source: &nsIPropertyBag) -> Result<Self, nsresult> {
+ let enumerator = getter_addrefs(|p| unsafe { source.GetEnumerator(p) })?;
+ let b = HashPropertyBag::new();
+ while {
+ let mut has_more = false;
+ unsafe { enumerator.HasMoreElements(&mut has_more) }.to_result()?;
+ has_more
+ } {
+ let element = getter_addrefs(|p| unsafe { enumerator.GetNext(p) })?;
+ let property = element
+ .query_interface::<nsIProperty>()
+ .ok_or(NS_ERROR_CANNOT_CONVERT_DATA)?;
+ let mut name = nsString::new();
+ unsafe { property.GetName(&mut *name) }.to_result()?;
+ let value = getter_addrefs(|p| unsafe { property.GetValue(p) })?;
+ unsafe { b.0.SetProperty(&*name, value.try_clone()?.coerce()) }.to_result()?;
+ }
+ Ok(b)
+ }
+
+ /// Returns the value for a property name. Fails with `NS_ERROR_FAILURE`
+ /// if the property doesn't exist, or `NS_ERROR_CANNOT_CONVERT_DATA` if the
+ /// property exists, but is not of the value type `V`.
+ pub fn get<K, V>(&self, name: K) -> Result<V, nsresult>
+ where
+ K: AsRef<str>,
+ V: VariantType,
+ {
+ getter_addrefs(|p| unsafe { self.0.GetProperty(&*nsString::from(name.as_ref()), p) })
+ .and_then(|v| V::from_variant(v.coerce()))
+ }
+
+ /// Returns the value for a property name, or the default if not set or
+ /// not of the value type `V`.
+ #[inline]
+ pub fn get_or_default<K, V>(&self, name: K) -> V
+ where
+ K: AsRef<str>,
+ V: VariantType + Default,
+ {
+ self.get(name).unwrap_or_default()
+ }
+
+ /// Sets a property with the name to the value, overwriting any previous
+ /// value.
+ pub fn set<K, V>(&mut self, name: K, value: V)
+ where
+ K: AsRef<str>,
+ V: VariantType,
+ {
+ let v = value.into_variant();
+ unsafe {
+ // This is safe to unwrap because
+ // `nsHashPropertyBagBase::SetProperty` only returns an error if `v`
+ // is a null pointer.
+ self.0
+ .SetProperty(&*nsString::from(name.as_ref()), v.coerce())
+ .to_result()
+ .unwrap()
+ }
+ }
+
+ /// Deletes a property with the name. Returns `true` if the property
+ /// was previously in the bag, `false` if not.
+ pub fn delete(&mut self, name: impl AsRef<str>) -> bool {
+ unsafe {
+ self.0
+ .DeleteProperty(&*nsString::from(name.as_ref()))
+ .to_result()
+ .is_ok()
+ }
+ }
+
+ /// Returns a reference to the backing `nsIWritablePropertyBag`.
+ #[inline]
+ pub fn bag(&self) -> &nsIWritablePropertyBag {
+ &self.0
+ }
+}
diff --git a/storage/variant/src/lib.rs b/storage/variant/src/lib.rs
new file mode 100644
index 0000000000..4734705299
--- /dev/null
+++ b/storage/variant/src/lib.rs
@@ -0,0 +1,230 @@
+/* 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<u16> 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<Self, Self::Error> {
+ 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<RefPtr<nsIVariant>, nsresult>;
+}
+
+impl NsIVariantExt for nsIVariant {
+ fn get_data_type(&self) -> u16 {
+ unsafe { NS_GetDataType(self) }
+ }
+
+ fn try_clone(&self) -> Result<RefPtr<nsIVariant>, 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<nsIVariant>;
+ fn from_variant(variant: &nsIVariant) -> Result<Self, nsresult>
+ 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<nsIVariant> {
+ // getter_addrefs returns a Result<RefPtr<T>, 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<nsIVariant> {
+ // getter_addrefs returns a Result<RefPtr<T>, 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<nsIVariant> {
+ // getter_addrefs returns a Result<RefPtr<T>, 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<Self, nsresult> {
+ Ok(())
+ }
+}
+
+impl<T> VariantType for Option<T>
+where
+ T: VariantType,
+{
+ fn type_name() -> Cow<'static, str> {
+ format!("Option<{}>", T::type_name()).into()
+ }
+ fn into_variant(self) -> RefPtr<nsIVariant> {
+ match self {
+ Some(v) => v.into_variant(),
+ None => ().into_variant(),
+ }
+ }
+ fn from_variant(variant: &nsIVariant) -> Result<Self, nsresult> {
+ 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);