1use 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 #[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#[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 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 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 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}