diff --git a/storage/variant/src/bag.rs b/storage/variant/src/bag.rs new file mode 100644 index 000000000000..9ab28892ae10 --- /dev/null +++ b/storage/variant/src/bag.rs @@ -0,0 +1,94 @@ +/* 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_OK}; +use nsstring::nsString; +use xpcom::{getter_addrefs, interfaces::nsIWritablePropertyBag, RefPtr}; + +use crate::VariantType; + +extern "C" { + fn NS_NewHashPropertyBag(bag: *mut *const nsIWritablePropertyBag) -> libc::c_void; +} + +/// A hash property bag backed by storage variant values. +pub struct HashPropertyBag(RefPtr); + +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() -> HashPropertyBag { + HashPropertyBag::default() + } + + /// 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(&self, name: K) -> Result + where + K: AsRef, + 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(&self, name: K) -> V + where + K: AsRef, + 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(&mut self, name: K, value: V) + where + K: AsRef, + 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) -> 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 index 6912ad411029..0ddac4dca1cf 100644 --- a/storage/variant/src/lib.rs +++ b/storage/variant/src/lib.rs @@ -7,11 +7,15 @@ extern crate nserror; extern crate nsstring; extern crate xpcom; +mod bag; + use libc::{c_double, int64_t, uint16_t}; use nserror::{nsresult, 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) -> uint16_t; fn NS_NewStorageNullVariant(result: *mut *const nsIVariant); diff --git a/xpcom/ds/nsHashPropertyBag.cpp b/xpcom/ds/nsHashPropertyBag.cpp index 064fd7664e56..11b06aa522cf 100644 --- a/xpcom/ds/nsHashPropertyBag.cpp +++ b/xpcom/ds/nsHashPropertyBag.cpp @@ -14,6 +14,16 @@ #include "mozilla/Attributes.h" #include "mozilla/Move.h" +extern "C" { + +// This function uses C linkage because it's exposed to Rust to support the +// `HashPropertyBag` wrapper in the `storage_variant` crate. +void NS_NewHashPropertyBag(nsIWritablePropertyBag** aBag) { + MakeRefPtr().forget(aBag); +} + +} // extern "C" + /* * nsHashPropertyBagBase implementation. */