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::conversions::{ConversionResult, FromJSValConvertible, ToJSValConvertible};
14use js::jsapi::glue::JS_GetOwnPropertyDescriptorById;
15use js::jsapi::{
16    HandleId as RawHandleId, JS_NewPlainObject, JSContext, JSITER_HIDDEN, JSITER_OWNONLY,
17    JSITER_SYMBOLS, JSPROP_ENUMERATE, PropertyDescriptor,
18};
19use js::jsval::{ObjectValue, UndefinedValue};
20use js::rooted;
21use js::rust::wrappers::{GetPropertyKeys, JS_DefineUCProperty2, JS_GetPropertyById, JS_IdToValue};
22use js::rust::{HandleId, HandleValue, IdVector, MutableHandleValue};
23
24use crate::conversions::jsid_to_string;
25use crate::str::{ByteString, DOMString, USVString};
26
27pub trait RecordKey: Eq + Hash + Sized {
28    fn to_utf16_vec(&self) -> Vec<u16>;
29
30    /// Attempt to extract a key from a JS id.
31    /// # Safety
32    /// - cx must point to a non-null, valid JSContext.
33    #[allow(clippy::result_unit_err)]
34    unsafe fn from_id(cx: *mut JSContext, id: HandleId) -> Result<ConversionResult<Self>, ()>;
35}
36
37impl RecordKey for DOMString {
38    fn to_utf16_vec(&self) -> Vec<u16> {
39        self.encode_utf16().collect::<Vec<_>>()
40    }
41
42    unsafe fn from_id(cx: *mut JSContext, id: HandleId) -> Result<ConversionResult<Self>, ()> {
43        match jsid_to_string(cx, id) {
44            Some(s) => Ok(ConversionResult::Success(s)),
45            None => Ok(ConversionResult::Failure("Failed to get DOMString".into())),
46        }
47    }
48}
49
50impl RecordKey for USVString {
51    fn to_utf16_vec(&self) -> Vec<u16> {
52        self.0.encode_utf16().collect::<Vec<_>>()
53    }
54
55    unsafe fn from_id(cx: *mut JSContext, id: HandleId) -> Result<ConversionResult<Self>, ()> {
56        rooted!(in(cx) let mut jsid_value = UndefinedValue());
57        let raw_id: RawHandleId = id.into();
58        JS_IdToValue(cx, *raw_id.ptr, jsid_value.handle_mut());
59
60        USVString::from_jsval(cx, jsid_value.handle(), ())
61    }
62}
63
64impl RecordKey for ByteString {
65    fn to_utf16_vec(&self) -> Vec<u16> {
66        self.iter().map(|&x| x as u16).collect::<Vec<u16>>()
67    }
68
69    unsafe fn from_id(cx: *mut JSContext, id: HandleId) -> Result<ConversionResult<Self>, ()> {
70        rooted!(in(cx) let mut jsid_value = UndefinedValue());
71        let raw_id: RawHandleId = id.into();
72        JS_IdToValue(cx, *raw_id.ptr, jsid_value.handle_mut());
73
74        ByteString::from_jsval(cx, jsid_value.handle(), ())
75    }
76}
77
78/// The `Record` (open-ended dictionary) type.
79#[derive(Clone, JSTraceable)]
80pub struct Record<K: RecordKey, V> {
81    #[custom_trace]
82    map: IndexMap<K, V>,
83}
84
85impl<K: RecordKey, V> Record<K, V> {
86    /// Create an empty `Record`.
87    pub fn new() -> Self {
88        Record {
89            map: IndexMap::new(),
90        }
91    }
92}
93
94impl<K: RecordKey, V> Deref for Record<K, V> {
95    type Target = IndexMap<K, V>;
96
97    fn deref(&self) -> &Self::Target {
98        &self.map
99    }
100}
101
102impl<K: RecordKey, V> DerefMut for Record<K, V> {
103    fn deref_mut(&mut self) -> &mut Self::Target {
104        &mut self.map
105    }
106}
107
108impl<K, V, C> FromJSValConvertible for Record<K, V>
109where
110    K: RecordKey,
111    V: FromJSValConvertible<Config = C>,
112    C: Clone,
113{
114    type Config = C;
115    unsafe fn from_jsval(
116        cx: *mut JSContext,
117        value: HandleValue,
118        config: C,
119    ) -> Result<ConversionResult<Self>, ()> {
120        if !value.is_object() {
121            return Ok(ConversionResult::Failure(
122                "Record value was not an object".into(),
123            ));
124        }
125
126        rooted!(in(cx) let object = value.to_object());
127        let mut ids = IdVector::new(cx);
128        if !GetPropertyKeys(
129            cx,
130            object.handle(),
131            JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS,
132            ids.handle_mut(),
133        ) {
134            return Err(());
135        }
136
137        let mut map = IndexMap::new();
138        for id in &*ids {
139            rooted!(in(cx) let id = *id);
140            rooted!(in(cx) let mut desc = PropertyDescriptor::default());
141
142            let mut is_none = false;
143            if !JS_GetOwnPropertyDescriptorById(
144                cx,
145                object.handle().into(),
146                id.handle().into(),
147                desc.handle_mut().into(),
148                &mut is_none,
149            ) {
150                return Err(());
151            }
152
153            if !desc.enumerable_() {
154                continue;
155            }
156
157            let key = match K::from_id(cx, id.handle())? {
158                ConversionResult::Success(key) => key,
159                ConversionResult::Failure(message) => {
160                    return Ok(ConversionResult::Failure(message));
161                },
162            };
163
164            rooted!(in(cx) let mut property = UndefinedValue());
165            if !JS_GetPropertyById(cx, object.handle(), id.handle(), property.handle_mut()) {
166                return Err(());
167            }
168
169            let property = match V::from_jsval(cx, property.handle(), config.clone())? {
170                ConversionResult::Success(property) => property,
171                ConversionResult::Failure(message) => {
172                    return Ok(ConversionResult::Failure(message));
173                },
174            };
175            map.insert(key, property);
176        }
177
178        Ok(ConversionResult::Success(Record { map }))
179    }
180}
181
182impl<K, V> ToJSValConvertible for Record<K, V>
183where
184    K: RecordKey,
185    V: ToJSValConvertible,
186{
187    #[inline]
188    unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
189        rooted!(in(cx) let js_object = JS_NewPlainObject(cx));
190        assert!(!js_object.handle().is_null());
191
192        rooted!(in(cx) let mut js_value = UndefinedValue());
193        for (key, value) in &self.map {
194            let key = key.to_utf16_vec();
195            value.to_jsval(cx, js_value.handle_mut());
196
197            assert!(JS_DefineUCProperty2(
198                cx,
199                js_object.handle(),
200                key.as_ptr(),
201                key.len(),
202                js_value.handle(),
203                JSPROP_ENUMERATE as u32
204            ));
205        }
206
207        rval.set(ObjectValue(js_object.handle().get()));
208    }
209}
210
211impl<K: RecordKey, V> Default for Record<K, V> {
212    fn default() -> Self {
213        Self::new()
214    }
215}