Skip to main content

script_bindings/
record.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! The `Record` (open-ended dictionary) type.
6
7use std::cmp::Eq;
8use std::hash::Hash;
9use std::marker::Sized;
10use std::ops::{Deref, DerefMut};
11
12use indexmap::IndexMap;
13use js::context::{JSContext, RawJSContext};
14use js::conversions::{ConversionResult, FromJSValConvertible, ToJSValConvertible};
15use js::jsapi::{
16    JSITER_HIDDEN, JSITER_OWNONLY, JSITER_SYMBOLS, JSPROP_ENUMERATE, PropertyDescriptor,
17};
18use js::jsval::{ObjectValue, UndefinedValue};
19use js::rust::wrappers2::{
20    GetPropertyKeys, JS_DefineUCProperty2, JS_GetOwnPropertyDescriptorById, JS_GetPropertyById,
21    JS_IdToValue, JS_NewPlainObject,
22};
23use js::rust::{HandleId, HandleValue, IdVector, MutableHandleValue};
24
25use crate::conversions::jsid_to_string;
26use crate::str::{ByteString, DOMString, USVString};
27
28pub trait RecordKey: Eq + Hash + Sized {
29    fn to_utf16_vec(&self) -> Vec<u16>;
30
31    /// Attempt to extract a key from a JS id.
32    #[allow(clippy::result_unit_err)]
33    fn from_id(cx: &mut JSContext, id: HandleId) -> Result<ConversionResult<Self>, ()>;
34}
35
36impl RecordKey for DOMString {
37    fn to_utf16_vec(&self) -> Vec<u16> {
38        self.str().encode_utf16().collect::<Vec<_>>()
39    }
40
41    fn from_id(cx: &mut JSContext, id: HandleId) -> Result<ConversionResult<Self>, ()> {
42        match jsid_to_string(cx, id) {
43            Some(s) => Ok(ConversionResult::Success(s)),
44            None => Ok(ConversionResult::Failure(c"Failed to get DOMString".into())),
45        }
46    }
47}
48
49impl RecordKey for USVString {
50    fn to_utf16_vec(&self) -> Vec<u16> {
51        self.0.encode_utf16().collect::<Vec<_>>()
52    }
53
54    fn from_id(cx: &mut JSContext, id: HandleId) -> Result<ConversionResult<Self>, ()> {
55        rooted!(&in(cx) let mut jsid_value = UndefinedValue());
56        unsafe { JS_IdToValue(cx, *id.as_ref(cx), jsid_value.handle_mut()) };
57
58        USVString::safe_from_jsval(cx, jsid_value.handle(), ())
59    }
60}
61
62impl RecordKey for ByteString {
63    fn to_utf16_vec(&self) -> Vec<u16> {
64        self.iter().map(|&x| x as u16).collect::<Vec<u16>>()
65    }
66
67    fn from_id(cx: &mut JSContext, id: HandleId) -> Result<ConversionResult<Self>, ()> {
68        rooted!(&in(cx) let mut jsid_value = UndefinedValue());
69        unsafe { JS_IdToValue(cx, *id.as_ref(cx), jsid_value.handle_mut()) };
70
71        ByteString::safe_from_jsval(cx, jsid_value.handle(), ())
72    }
73}
74
75/// The `Record` (open-ended dictionary) type.
76#[derive(Clone, JSTraceable)]
77pub struct Record<K: RecordKey, V> {
78    #[custom_trace]
79    map: IndexMap<K, V>,
80}
81
82impl<K: RecordKey, V> Record<K, V> {
83    /// Create an empty `Record`.
84    pub fn new() -> Self {
85        Record {
86            map: IndexMap::new(),
87        }
88    }
89}
90
91impl<K: RecordKey, V> Deref for Record<K, V> {
92    type Target = IndexMap<K, V>;
93
94    fn deref(&self) -> &Self::Target {
95        &self.map
96    }
97}
98
99impl<K: RecordKey, V> DerefMut for Record<K, V> {
100    fn deref_mut(&mut self) -> &mut Self::Target {
101        &mut self.map
102    }
103}
104
105impl<K, V, C> FromJSValConvertible for Record<K, V>
106where
107    K: RecordKey,
108    V: FromJSValConvertible<Config = C>,
109    C: Clone,
110{
111    type Config = C;
112    unsafe fn from_jsval(
113        _cx: *mut RawJSContext,
114        value: HandleValue,
115        config: C,
116    ) -> Result<ConversionResult<Self>, ()> {
117        // TODO https://github.com/servo/mozjs/issues/749
118        let mut cx = unsafe { crate::script_runtime::temp_cx() };
119        FromJSValConvertible::safe_from_jsval(&mut cx, value, config)
120    }
121
122    fn safe_from_jsval(
123        cx: &mut JSContext,
124        value: HandleValue,
125        config: C,
126    ) -> Result<ConversionResult<Self>, ()> {
127        if !value.is_object() {
128            return Ok(ConversionResult::Failure(
129                c"Record value was not an object".into(),
130            ));
131        }
132
133        rooted!(&in(cx) let object = value.to_object());
134        let mut ids = unsafe { IdVector::new(cx.raw_cx()) };
135        if unsafe {
136            !GetPropertyKeys(
137                cx,
138                object.handle(),
139                JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS,
140                ids.handle_mut(),
141            )
142        } {
143            return Err(());
144        }
145
146        let mut map = IndexMap::new();
147        for id in &*ids {
148            rooted!(&in(cx) let id = *id);
149            rooted!(&in(cx) let mut desc = PropertyDescriptor::default());
150
151            let mut is_none = false;
152            if unsafe {
153                !JS_GetOwnPropertyDescriptorById(
154                    cx,
155                    object.handle(),
156                    id.handle(),
157                    desc.handle_mut(),
158                    &mut is_none,
159                )
160            } {
161                return Err(());
162            }
163
164            if !desc.enumerable_() {
165                continue;
166            }
167
168            let key = match K::from_id(cx, id.handle())? {
169                ConversionResult::Success(key) => key,
170                ConversionResult::Failure(message) => {
171                    return Ok(ConversionResult::Failure(message));
172                },
173            };
174
175            rooted!(&in(cx) let mut property = UndefinedValue());
176            if unsafe {
177                !JS_GetPropertyById(cx, object.handle(), id.handle(), property.handle_mut())
178            } {
179                return Err(());
180            }
181
182            let property = match V::safe_from_jsval(cx, property.handle(), config.clone())? {
183                ConversionResult::Success(property) => property,
184                ConversionResult::Failure(message) => {
185                    return Ok(ConversionResult::Failure(message));
186                },
187            };
188            map.insert(key, property);
189        }
190
191        Ok(ConversionResult::Success(Record { map }))
192    }
193}
194
195impl<K, V> ToJSValConvertible for Record<K, V>
196where
197    K: RecordKey,
198    V: ToJSValConvertible,
199{
200    #[inline]
201    unsafe fn to_jsval(&self, _cx: *mut RawJSContext, rval: MutableHandleValue) {
202        // TODO: https://github.com/servo/mozjs/issues/764
203        // This is needed until the `RawJSContext` version is removed from the trait.
204        let mut cx = unsafe { crate::script_runtime::temp_cx() };
205        ToJSValConvertible::safe_to_jsval(self, &mut cx, rval);
206    }
207
208    fn safe_to_jsval(&self, cx: &mut JSContext, mut rval: MutableHandleValue) {
209        rooted!(&in(cx) let js_object = unsafe { JS_NewPlainObject(cx) });
210        assert!(!js_object.handle().is_null());
211
212        rooted!(&in(cx) let mut js_value = UndefinedValue());
213        for (key, value) in &self.map {
214            let key = key.to_utf16_vec();
215            value.safe_to_jsval(cx, js_value.handle_mut());
216
217            assert!(unsafe {
218                JS_DefineUCProperty2(
219                    cx,
220                    js_object.handle(),
221                    key.as_ptr(),
222                    key.len(),
223                    js_value.handle(),
224                    JSPROP_ENUMERATE as u32,
225                )
226            });
227        }
228
229        rval.set(ObjectValue(js_object.handle().get()));
230    }
231}
232
233impl<K: RecordKey, V> Default for Record<K, V> {
234    fn default() -> Self {
235        Self::new()
236    }
237}