Skip to main content

script_bindings/
observablearray.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
5use 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
38// Adapted from https://searchfox.org/firefox-main/rev/c681e91369f59d0efae43bdc465872b855e8b269/dom/bindings/ObservableArrayProxyHandler.cpp
39
40pub 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
88/// # Safety
89/// config must outlive the proxy handler.
90pub 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
96/// # Safety
97/// The caller must ensure that the returned pointer is not used after the `JSContext` is deallocated.
98pub 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
129/// # Safety
130/// handler and owner pointers must outlive the created proxy object.
131pub 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
171/// # Safety
172/// `proxy` must be a valid, non-null pointer.
173pub 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
325// https://searchfox.org/firefox-main/rev/c681e91369f59d0efae43bdc465872b855e8b269/dom/bindings/ObservableArrayProxyHandler.cpp#22
326unsafe 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
407// https://searchfox.org/firefox-main/rev/c681e91369f59d0efae43bdc465872b855e8b269/dom/bindings/ObservableArrayProxyHandler.cpp#79
408unsafe 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
461// https://searchfox.org/firefox-main/rev/c681e91369f59d0efae43bdc465872b855e8b269/dom/bindings/ObservableArrayProxyHandler.cpp#125
462unsafe 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
503// https://searchfox.org/firefox-main/rev/c681e91369f59d0efae43bdc465872b855e8b269/dom/bindings/ObservableArrayProxyHandler.cpp#154
504unsafe 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
552// https://searchfox.org/firefox-main/rev/c681e91369f59d0efae43bdc465872b855e8b269/dom/bindings/ObservableArrayProxyHandler.cpp#195
553unsafe 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
582// https://searchfox.org/firefox-main/rev/c681e91369f59d0efae43bdc465872b855e8b269/dom/bindings/ObservableArrayProxyHandler.cpp#216
583unsafe 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
614// https://searchfox.org/firefox-main/rev/c681e91369f59d0efae43bdc465872b855e8b269/dom/bindings/ObservableArrayProxyHandler.cpp#232
615unsafe 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
623// https://searchfox.org/firefox-main/rev/c681e91369f59d0efae43bdc465872b855e8b269/dom/bindings/ObservableArrayProxyHandler.cpp#238
624unsafe 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}