summaryrefslogtreecommitdiffstats
path: root/storage/variant/src/bag.rs
blob: 91653ba2260b1afafb4177baced61ebb614e39d2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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
    }
}