1use std::ffi::c_void;
6use std::ptr;
7use std::ptr::NonNull;
8
9use js::context::{JSContext, RawJSContext};
10use js::conversions::ToJSValConvertible;
11use js::glue::{
12 AppendToIdVector, CreateForwardingProxyHandler, GetProxyHandlerExtra, GetProxyPrivate,
13 GetProxyReservedSlot, JS_GetReservedSlot, ProxyTraps, SetProxyReservedSlot,
14};
15use js::jsapi::{
16 Handle as RawHandle, HandleId as RawHandleId, HandleObject as RawHandleObject,
17 HandleValue as RawHandleValue, JS_SetReservedSlot, JSITER_HIDDEN, JSITER_OWNONLY,
18 JSITER_SYMBOLS, JSObject, JSPROP_ENUMERATE, JSPROP_PERMANENT,
19 MutableHandle as RawMutableHandle, MutableHandleIdVector as RawMutableHandleIdVector,
20 MutableHandleValue as RawMutableHandleValue, ObjectOpResult, PropertyDescriptor, jsid,
21};
22use js::jsval::{ObjectValue, PrivateValue, UndefinedValue};
23use js::rust::wrappers2::{
24 GetArrayLength, GetPropertyKeys, JS_DefinePropertyById, JS_DeletePropertyById,
25 JS_ForwardGetPropertyTo, JS_ForwardSetPropertyTo, JS_GetElement,
26 JS_GetOwnPropertyDescriptorById, JS_HasPropertyById, JS_SetPrototype, NewArrayObject1,
27 SetArrayLength, int_to_jsid,
28};
29use js::rust::{
30 Handle, HandleId, HandleObject, HandleValue, MutableHandle, MutableHandleObject,
31 MutableHandleValue, ToNumber, ToUint32,
32};
33
34use crate::conversions::jsid_to_string;
35use crate::proxyhandler::{is_accessor_descriptor, set_property_descriptor};
36use crate::utils::get_array_index_from_id;
37
38pub const OBSERVABLE_ARRAY_OWNER_SLOT: u32 = 0;
41pub const OBSERVABLE_ARRAY_BACKING_LIST_OBJECT_SLOT: u32 = 1;
42
43pub struct ObservableArrayProxyHandlerConfig {
44 pub on_delete_item: unsafe fn(&mut JSContext, HandleObject, HandleValue, u32) -> bool,
45 pub set_indexed_value: unsafe fn(
46 &mut JSContext,
47 HandleObject,
48 HandleObject,
49 u32,
50 HandleValue,
51 *mut ObjectOpResult,
52 ) -> bool,
53}
54
55static PROXY_TRAPS: ProxyTraps = ProxyTraps {
56 enter: None,
57 getOwnPropertyDescriptor: Some(get_own_property_descriptor),
58 defineProperty: Some(define_property),
59 ownPropertyKeys: Some(own_property_keys),
60 delete_: Some(delete_),
61 enumerate: None,
62 getPrototypeIfOrdinary: None,
63 getPrototype: None,
64 setPrototype: None,
65 setImmutablePrototype: None,
66 preventExtensions: Some(prevent_extensions),
67 isExtensible: None,
68 has: Some(has),
69 get: Some(get),
70 set: Some(set),
71 call: None,
72 construct: None,
73 hasOwn: None,
74 getOwnEnumerablePropertyKeys: None,
75 nativeCall: None,
76 objectClassIs: None,
77 className: None,
78 fun_toString: None,
79 boxedValue_unbox: None,
80 defaultValue: None,
81 trace: None,
82 finalize: None,
83 objectMoved: None,
84 isCallable: None,
85 isConstructor: None,
86};
87
88pub unsafe fn create_proxy_handler(
91 config: *const ObservableArrayProxyHandlerConfig,
92) -> *const c_void {
93 unsafe { CreateForwardingProxyHandler(&PROXY_TRAPS, config as *const c_void) }
94}
95
96pub unsafe fn new_proxy_object(
99 cx: &mut JSContext,
100 handler: *const c_void,
101 owner: *const c_void,
102) -> *mut JSObject {
103 rooted!(&in(cx) let target = unsafe { NewArrayObject1(cx, 0) });
104 if target.is_null() {
105 return ptr::null_mut();
106 }
107
108 rooted!(&in(cx) let target_value = ObjectValue(target.get()));
109 let proxy = unsafe {
110 js::rust::wrappers2::NewProxyObject(
111 cx,
112 handler,
113 target_value.handle(),
114 ptr::null_mut(),
115 ptr::null(),
116 true,
117 )
118 };
119 if proxy.is_null() {
120 return ptr::null_mut();
121 }
122
123 unsafe {
124 SetProxyReservedSlot(proxy, OBSERVABLE_ARRAY_OWNER_SLOT, &PrivateValue(owner));
125 }
126 proxy
127}
128
129pub unsafe fn get_or_create_proxy_object(
132 cx: &mut JSContext,
133 obj: HandleObject,
134 is_proxy: bool,
135 slot: u32,
136 handler: *const c_void,
137 owner: *const c_void,
138 mut proxy: MutableHandleObject,
139) -> bool {
140 let mut slot_value = UndefinedValue();
141 unsafe {
142 if is_proxy {
143 GetProxyReservedSlot(obj.get(), slot, &mut slot_value);
144 } else {
145 JS_GetReservedSlot(obj.get(), slot, &mut slot_value);
146 }
147 }
148
149 if slot_value.is_undefined() {
150 let new_proxy = unsafe { new_proxy_object(cx, handler, owner) };
151 if new_proxy.is_null() {
152 return false;
153 }
154
155 let value = ObjectValue(new_proxy);
156 unsafe {
157 if is_proxy {
158 SetProxyReservedSlot(obj.get(), slot, &value);
159 } else {
160 JS_SetReservedSlot(obj.get(), slot, &value);
161 }
162 }
163 proxy.set(new_proxy);
164 return true;
165 }
166
167 proxy.set(slot_value.to_object());
168 true
169}
170
171pub unsafe fn clear_owner_slot(proxy: *mut JSObject) {
174 unsafe {
175 SetProxyReservedSlot(proxy, OBSERVABLE_ARRAY_OWNER_SLOT, &UndefinedValue());
176 }
177}
178
179pub fn set_length(cx: &mut JSContext, proxy: HandleObject, length: u32) -> bool {
180 let backing_list: *mut JSObject = ptr::null_mut();
181 rooted!(&in(cx) let mut rooted_backing_list = backing_list);
182 if !get_backing_list_object(cx, proxy, rooted_backing_list.handle_mut()) {
183 return false;
184 }
185
186 set_length_internal(cx, proxy, rooted_backing_list.handle(), length)
187}
188
189fn config_for_proxy(proxy: HandleObject) -> &'static ObservableArrayProxyHandlerConfig {
190 let extra = unsafe { GetProxyHandlerExtra(proxy.get()) };
191 unsafe { &*(extra as *const ObservableArrayProxyHandlerConfig) }
192}
193
194fn is_length_id(cx: &JSContext, id: HandleId) -> bool {
195 matches!(jsid_to_string(cx, id), Some(ref name) if name == "length")
196}
197
198fn get_proxy_target(proxy: HandleObject, mut target: MutableHandleObject) {
199 let mut slot = UndefinedValue();
200 unsafe { GetProxyPrivate(proxy.get(), &mut slot) };
201 debug_assert!(slot.is_object());
202 target.set(slot.to_object());
203}
204
205fn get_backing_list_object(
206 cx: &mut JSContext,
207 proxy: HandleObject,
208 mut backing_list: MutableHandleObject,
209) -> bool {
210 let mut slot_value = UndefinedValue();
211 unsafe {
212 GetProxyReservedSlot(
213 proxy.get(),
214 OBSERVABLE_ARRAY_BACKING_LIST_OBJECT_SLOT,
215 &mut slot_value,
216 );
217 }
218
219 if slot_value.is_undefined() {
220 rooted!(&in(cx) let new_backing_list = unsafe { NewArrayObject1(cx, 0) });
221 if new_backing_list.is_null() {
222 return false;
223 }
224 if unsafe { !JS_SetPrototype(cx, new_backing_list.handle(), HandleObject::null()) } {
225 return false;
226 }
227
228 let value = ObjectValue(new_backing_list.get());
229 unsafe {
230 SetProxyReservedSlot(
231 proxy.get(),
232 OBSERVABLE_ARRAY_BACKING_LIST_OBJECT_SLOT,
233 &value,
234 );
235 }
236 backing_list.set(new_backing_list.get());
237 return true;
238 }
239
240 backing_list.set(slot_value.to_object());
241 true
242}
243
244fn get_backing_list_length(cx: &mut JSContext, proxy: HandleObject, length: &mut u32) -> bool {
245 rooted!(&in(cx) let mut backing_list = ptr::null_mut::<JSObject>());
246 if !get_backing_list_object(cx, proxy, backing_list.handle_mut()) {
247 return false;
248 }
249 unsafe { GetArrayLength(cx, backing_list.handle(), length) }
250}
251
252fn set_length_internal(
253 cx: &mut JSContext,
254 proxy: HandleObject,
255 backing_list: HandleObject,
256 length: u32,
257) -> bool {
258 let mut old_len = 0;
259 if unsafe { !GetArrayLength(cx, backing_list, &mut old_len) } {
260 return false;
261 }
262
263 if length > old_len {
264 return false;
265 }
266
267 let config = config_for_proxy(proxy);
268 let mut current_len = old_len;
269 while current_len > length {
270 let index_to_delete = current_len - 1;
271 rooted!(&in(cx) let mut value = UndefinedValue());
272 if unsafe { !JS_GetElement(cx, backing_list, index_to_delete, value.handle_mut()) } {
273 return false;
274 }
275 if unsafe { !(config.on_delete_item)(cx, proxy, value.handle(), index_to_delete) } {
276 return false;
277 }
278 current_len -= 1;
279 }
280
281 unsafe { SetArrayLength(cx, backing_list, current_len) }
282}
283
284fn set_length_from_value(
285 cx: &mut JSContext,
286 proxy: HandleObject,
287 backing_list: HandleObject,
288 value: HandleValue,
289 result: *mut ObjectOpResult,
290) -> bool {
291 let uint32_len = match unsafe { ToUint32(cx, value) } {
292 Ok(length) => length,
293 Err(()) => return false,
294 };
295 let number_len = match unsafe { ToNumber(cx, value) } {
296 Ok(length) => length,
297 Err(()) => return false,
298 };
299
300 if f64::from(uint32_len) != number_len {
301 unsafe {
302 (*result).fail_bad_index();
303 }
304 return true;
305 }
306
307 let mut old_len = 0;
308 if unsafe { !GetArrayLength(cx, backing_list, &mut old_len) } {
309 return false;
310 }
311 if uint32_len > old_len {
312 unsafe {
313 (*result).fail_bad_array_length();
314 }
315 return true;
316 }
317
318 if !set_length_internal(cx, proxy, backing_list, uint32_len) {
319 return false;
320 }
321
322 unsafe { (*result).succeed() }
323}
324
325unsafe extern "C" fn define_property(
327 cx: *mut RawJSContext,
328 proxy: RawHandleObject,
329 id: RawHandleId,
330 descriptor: RawHandle<PropertyDescriptor>,
331 result: *mut ObjectOpResult,
332) -> bool {
333 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
334 let proxy = unsafe { HandleObject::from_raw(proxy) };
335 let id = unsafe { Handle::from_raw(id) };
336 let desc = unsafe { Handle::from_raw(descriptor) };
337 let mut result_ptr = unsafe { *result };
338
339 if is_length_id(&cx, id) {
340 if is_accessor_descriptor(&descriptor) {
341 return (result_ptr).fail_not_data_descriptor();
342 }
343 if desc.hasConfigurable_() && desc.configurable_() {
344 return (result_ptr).fail_invalid_descriptor();
345 }
346 if desc.hasEnumerable_() && desc.enumerable_() {
347 return (result_ptr).fail_invalid_descriptor();
348 }
349 if desc.hasWritable_() && !desc.writable_() {
350 return (result_ptr).fail_invalid_descriptor();
351 }
352 if desc.hasValue_() {
353 rooted!(&in(cx) let mut backing_list = ptr::null_mut::<JSObject>());
354 if !get_backing_list_object(&mut cx, proxy, backing_list.handle_mut()) {
355 return false;
356 }
357 rooted!(&in(cx) let desc_value = desc.value_);
358 return set_length_from_value(
359 &mut cx,
360 proxy,
361 backing_list.handle(),
362 desc_value.handle(),
363 result,
364 );
365 }
366 return (result_ptr).succeed();
367 }
368
369 if let Some(index) = get_array_index_from_id(id) {
370 if is_accessor_descriptor(&descriptor) {
371 return (result_ptr).fail_not_data_descriptor();
372 }
373 if desc.hasConfigurable_() && !desc.configurable_() {
374 return (result_ptr).fail_invalid_descriptor();
375 }
376 if desc.hasEnumerable_() && !desc.enumerable_() {
377 return (result_ptr).fail_invalid_descriptor();
378 }
379 if desc.hasWritable_() && !desc.writable_() {
380 return (result_ptr).fail_invalid_descriptor();
381 }
382 if desc.hasValue_() {
383 rooted!(&in(cx) let mut backing_list = ptr::null_mut::<JSObject>());
384 if !get_backing_list_object(&mut cx, proxy, backing_list.handle_mut()) {
385 return false;
386 }
387 rooted!(&in(cx) let desc_value = desc.value_);
388 return unsafe {
389 (config_for_proxy(proxy).set_indexed_value)(
390 &mut cx,
391 proxy,
392 backing_list.handle(),
393 index,
394 desc_value.handle(),
395 result,
396 )
397 };
398 }
399 return (result_ptr).succeed();
400 }
401
402 rooted!(&in(cx) let mut target = ptr::null_mut::<JSObject>());
403 get_proxy_target(proxy, target.handle_mut());
404 unsafe { JS_DefinePropertyById(&mut cx, target.handle(), id, desc, result) }
405}
406
407unsafe extern "C" fn delete_(
409 cx: *mut RawJSContext,
410 proxy: RawHandleObject,
411 id: RawHandleId,
412 result: *mut ObjectOpResult,
413) -> bool {
414 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
415 let proxy = unsafe { HandleObject::from_raw(proxy) };
416 let id = unsafe { Handle::from_raw(id) };
417 let result_ptr = unsafe { &mut *result };
418
419 if is_length_id(&cx, id) {
420 return result_ptr.fail_cant_delete();
421 }
422
423 if let Some(index) = get_array_index_from_id(id) {
424 rooted!(&in(cx) let mut backing_list = ptr::null_mut::<JSObject>());
425 if !get_backing_list_object(&mut cx, proxy, backing_list.handle_mut()) {
426 return false;
427 }
428
429 let mut old_len = 0;
430 if unsafe { !GetArrayLength(&mut cx, backing_list.handle(), &mut old_len) } {
431 return false;
432 }
433
434 if old_len != index + 1 {
435 return result_ptr.fail_bad_index();
436 }
437
438 rooted!(&in(cx) let mut value = UndefinedValue());
439 if unsafe { !JS_GetElement(&mut cx, backing_list.handle(), index, value.handle_mut()) } {
440 return false;
441 }
442
443 if unsafe {
444 !(config_for_proxy(proxy).on_delete_item)(&mut cx, proxy, value.handle(), index)
445 } {
446 return false;
447 }
448
449 if unsafe { !SetArrayLength(&mut cx, backing_list.handle(), index) } {
450 return false;
451 }
452
453 return result_ptr.succeed();
454 }
455
456 rooted!(&in(cx) let mut target = ptr::null_mut::<JSObject>());
457 get_proxy_target(proxy, target.handle_mut());
458 unsafe { JS_DeletePropertyById(&mut cx, target.handle(), id, result) }
459}
460
461unsafe extern "C" fn get(
463 cx: *mut RawJSContext,
464 proxy: RawHandleObject,
465 receiver: RawHandleValue,
466 id: RawHandleId,
467 vp: RawMutableHandleValue,
468) -> bool {
469 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
470 let proxy = unsafe { HandleObject::from_raw(proxy) };
471 let receiver = unsafe { HandleValue::from_raw(receiver) };
472 let id = unsafe { Handle::from_raw(id) };
473 let mut vp = unsafe { MutableHandleValue::from_raw(vp) };
474
475 rooted!(&in(cx) let mut backing_list = ptr::null_mut::<JSObject>());
476 if !get_backing_list_object(&mut cx, proxy, backing_list.handle_mut()) {
477 return false;
478 }
479
480 let mut length = 0;
481 if unsafe { !GetArrayLength(&mut cx, backing_list.handle(), &mut length) } {
482 return false;
483 }
484
485 if is_length_id(&cx, id) {
486 length.to_jsval(&mut cx, vp);
487 return true;
488 }
489
490 if let Some(index) = get_array_index_from_id(id) {
491 if index >= length {
492 vp.set(UndefinedValue());
493 return true;
494 }
495 return unsafe { JS_GetElement(&mut cx, backing_list.handle(), index, vp) };
496 }
497
498 rooted!(&in(cx) let mut target = ptr::null_mut::<JSObject>());
499 get_proxy_target(proxy, target.handle_mut());
500 unsafe { JS_ForwardGetPropertyTo(&mut cx, target.handle(), id, receiver, vp) }
501}
502
503unsafe extern "C" fn get_own_property_descriptor(
505 cx: *mut RawJSContext,
506 proxy: RawHandleObject,
507 id: RawHandleId,
508 desc: RawMutableHandle<PropertyDescriptor>,
509 is_none: *mut bool,
510) -> bool {
511 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
512 let proxy = unsafe { HandleObject::from_raw(proxy) };
513 let id = unsafe { Handle::from_raw(id) };
514 let desc = unsafe { MutableHandle::from_raw(desc) };
515 let is_none = unsafe { &mut *is_none };
516
517 rooted!(&in(cx) let mut backing_list = ptr::null_mut::<JSObject>());
518 if !get_backing_list_object(&mut cx, proxy, backing_list.handle_mut()) {
519 return false;
520 }
521
522 let mut length = 0;
523 if unsafe { !GetArrayLength(&mut cx, backing_list.handle(), &mut length) } {
524 return false;
525 }
526
527 if is_length_id(&cx, id) {
528 rooted!(&in(cx) let mut value = UndefinedValue());
529 length.to_jsval(&mut cx, value.handle_mut());
530 set_property_descriptor(desc, value.handle(), JSPROP_PERMANENT.into(), is_none);
531 return true;
532 }
533
534 if let Some(index) = get_array_index_from_id(id) {
535 if index >= length {
536 return true;
537 }
538
539 rooted!(&in(cx) let mut value = UndefinedValue());
540 if unsafe { !JS_GetElement(&mut cx, backing_list.handle(), index, value.handle_mut()) } {
541 return false;
542 }
543 set_property_descriptor(desc, value.handle(), JSPROP_ENUMERATE.into(), is_none);
544 return true;
545 }
546
547 rooted!(&in(cx) let mut target = ptr::null_mut::<JSObject>());
548 get_proxy_target(proxy, target.handle_mut());
549 unsafe { JS_GetOwnPropertyDescriptorById(&mut cx, target.handle(), id, desc, is_none) }
550}
551
552unsafe extern "C" fn has(
554 cx: *mut RawJSContext,
555 proxy: RawHandleObject,
556 id: RawHandleId,
557 bp: *mut bool,
558) -> bool {
559 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
560 let proxy = unsafe { HandleObject::from_raw(proxy) };
561 let id = unsafe { Handle::from_raw(id) };
562
563 if is_length_id(&cx, id) {
564 unsafe { *bp = true };
565 return true;
566 }
567
568 if let Some(index) = get_array_index_from_id(id) {
569 let mut length = 0;
570 if !get_backing_list_length(&mut cx, proxy, &mut length) {
571 return false;
572 }
573 unsafe { *bp = index < length };
574 return true;
575 }
576
577 rooted!(&in(cx) let mut target = ptr::null_mut::<JSObject>());
578 get_proxy_target(proxy, target.handle_mut());
579 unsafe { JS_HasPropertyById(&mut cx, target.handle(), id, bp) }
580}
581
582unsafe extern "C" fn own_property_keys(
584 cx: *mut RawJSContext,
585 proxy: RawHandleObject,
586 props: RawMutableHandleIdVector,
587) -> bool {
588 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
589 let proxy = unsafe { HandleObject::from_raw(proxy) };
590
591 let mut length = 0;
592 if !get_backing_list_length(&mut cx, proxy, &mut length) {
593 return false;
594 }
595
596 rooted!(&in(cx) let mut rooted_jsid: jsid);
597 for i in 0..length {
598 unsafe { int_to_jsid(i as i32, rooted_jsid.handle_mut()) };
599 unsafe { AppendToIdVector(props, rooted_jsid.handle().into()) };
600 }
601
602 rooted!(&in(cx) let mut target = ptr::null_mut::<JSObject>());
603 get_proxy_target(proxy, target.handle_mut());
604 unsafe {
605 GetPropertyKeys(
606 &mut cx,
607 target.handle(),
608 JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS,
609 props,
610 )
611 }
612}
613
614unsafe extern "C" fn prevent_extensions(
616 _cx: *mut RawJSContext,
617 _proxy: RawHandleObject,
618 result: *mut ObjectOpResult,
619) -> bool {
620 (unsafe { *result }).fail_cant_prevent_extensions()
621}
622
623unsafe extern "C" fn set(
625 cx: *mut RawJSContext,
626 proxy: RawHandleObject,
627 id: RawHandleId,
628 value: RawHandleValue,
629 receiver: RawHandleValue,
630 result: *mut ObjectOpResult,
631) -> bool {
632 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
633 let proxy = unsafe { HandleObject::from_raw(proxy) };
634 let id = unsafe { Handle::from_raw(id) };
635 let value = unsafe { HandleValue::from_raw(value) };
636 let receiver = unsafe { HandleValue::from_raw(receiver) };
637
638 if is_length_id(&cx, id) {
639 rooted!(&in(cx) let mut backing_list = ptr::null_mut::<JSObject>());
640 if !get_backing_list_object(&mut cx, proxy, backing_list.handle_mut()) {
641 return false;
642 }
643 return set_length_from_value(&mut cx, proxy, backing_list.handle(), value, result);
644 }
645
646 if let Some(index) = get_array_index_from_id(id) {
647 rooted!(&in(cx) let mut backing_list = ptr::null_mut::<JSObject>());
648 if !get_backing_list_object(&mut cx, proxy, backing_list.handle_mut()) {
649 return false;
650 }
651 return unsafe {
652 (config_for_proxy(proxy).set_indexed_value)(
653 &mut cx,
654 proxy,
655 backing_list.handle(),
656 index,
657 value,
658 result,
659 )
660 };
661 }
662
663 rooted!(&in(cx) let mut target = ptr::null_mut::<JSObject>());
664 get_proxy_target(proxy, target.handle_mut());
665 unsafe { JS_ForwardSetPropertyTo(&mut cx, target.handle(), id, value, receiver, result) }
666}