Skip to main content

script_bindings/
iterable.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//! Implementation of `iterable<...>` and `iterable<..., ...>` WebIDL declarations.
6
7use std::cell::Cell;
8use std::marker::PhantomData;
9use std::ptr;
10
11use dom_struct::dom_struct;
12use js::context::JSContext;
13use js::conversions::ToJSValConvertible;
14use js::jsapi::Heap;
15use js::jsval::UndefinedValue;
16use js::realm::CurrentRealm;
17use js::rust::wrappers2::JS_NewObject;
18use js::rust::{HandleObject, HandleValue, MutableHandleObject};
19
20use crate::codegen::GenericBindings::IterableIteratorBinding::{
21    IterableKeyAndValueResult, IterableKeyOrValueResult,
22};
23use crate::conversions::IDLInterface;
24use crate::error::Fallible;
25use crate::interfaces::{DomHelpers, GlobalScopeHelpers};
26use crate::reflector::{DomGlobalGeneric, DomObjectIteratorWrap, DomObjectWrap, Reflector};
27use crate::root::{Dom, DomRoot, Root};
28use crate::trace::{NoTrace, RootedTraceableBox};
29use crate::utils::DOMClass;
30use crate::{DomTypes, JSTraceable};
31
32/// The values that an iterator will iterate over.
33#[derive(JSTraceable, MallocSizeOf)]
34pub(crate) enum IteratorType {
35    /// The keys of the iterable object.
36    Keys,
37    /// The values of the iterable object.
38    Values,
39    /// The keys and values of the iterable object combined.
40    Entries,
41}
42
43/// A DOM object that can be iterated over using a pair value iterator.
44pub trait Iterable {
45    /// The type of the key of the iterator pair.
46    type Key: ToJSValConvertible;
47    /// The type of the value of the iterator pair.
48    type Value: ToJSValConvertible;
49    /// Return the number of entries that can be iterated over.
50    fn get_iterable_length(&self) -> u32;
51    /// Return the value at the provided index.
52    fn get_value_at_index(&self, index: u32) -> Self::Value;
53    /// Return the key at the provided index.
54    fn get_key_at_index(&self, index: u32) -> Self::Key;
55}
56
57/// A version of the [IDLInterface] trait that is specific to types that have
58/// iterators defined for them. This allows the `script` crate to define the
59/// derives check for the concrete interface type, while the [IteratableIterator]
60/// type defined in this module can be parameterized over an unknown generic.
61pub trait IteratorDerives {
62    fn derives(class: &'static DOMClass) -> bool;
63}
64
65/// An iterator over the iterable entries of a given DOM interface.
66#[dom_struct]
67pub struct IterableIterator<
68    D: DomTypes,
69    T: DomObjectIteratorWrap<D> + JSTraceable + Iterable + DomGlobalGeneric<D>,
70> {
71    reflector: Reflector,
72    iterable: Dom<T>,
73    type_: IteratorType,
74    index: Cell<u32>,
75    _marker: NoTrace<PhantomData<D>>,
76}
77
78impl<
79    D: DomTypes,
80    T: DomObjectIteratorWrap<D>
81        + JSTraceable
82        + Iterable
83        + DomGlobalGeneric<D>
84        + IDLInterface
85        + IteratorDerives,
86> IDLInterface for IterableIterator<D, T>
87{
88    fn derives(class: &'static DOMClass) -> bool {
89        <T as IteratorDerives>::derives(class)
90    }
91}
92
93impl<D: DomTypes, T: DomObjectIteratorWrap<D> + JSTraceable + Iterable + DomGlobalGeneric<D>>
94    IterableIterator<D, T>
95{
96    /// Create a new iterator instance for the provided iterable DOM interface.
97    pub(crate) fn new(
98        realm: &mut CurrentRealm,
99        iterable: &T,
100        type_: IteratorType,
101    ) -> DomRoot<Self> {
102        let iterator = Box::new(IterableIterator {
103            reflector: Reflector::new(),
104            type_,
105            iterable: Dom::from_ref(iterable),
106            index: Cell::new(0),
107            _marker: NoTrace(PhantomData),
108        });
109        let global = D::GlobalScope::from_current_realm(realm);
110        <D as DomHelpers<D>>::reflect_dom_object_with_cx(realm, iterator, &*global)
111    }
112
113    /// Return the next value from the iterable object.
114    #[expect(non_snake_case)]
115    pub fn Next(&self, cx: &mut JSContext, return_value: MutableHandleObject) -> Fallible<()> {
116        let index = self.index.get();
117        rooted!(&in(cx) let mut value = UndefinedValue());
118        let result = if index >= self.iterable.get_iterable_length() {
119            dict_return(cx, return_value, true, value.handle())
120        } else {
121            match self.type_ {
122                IteratorType::Keys => {
123                    self.iterable
124                        .get_key_at_index(index)
125                        .safe_to_jsval(cx, value.handle_mut());
126                    dict_return(cx, return_value, false, value.handle())
127                },
128                IteratorType::Values => {
129                    self.iterable
130                        .get_value_at_index(index)
131                        .safe_to_jsval(cx, value.handle_mut());
132                    dict_return(cx, return_value, false, value.handle())
133                },
134                IteratorType::Entries => {
135                    rooted!(&in(cx) let mut key = UndefinedValue());
136                    self.iterable
137                        .get_key_at_index(index)
138                        .safe_to_jsval(cx, key.handle_mut());
139                    self.iterable
140                        .get_value_at_index(index)
141                        .safe_to_jsval(cx, value.handle_mut());
142                    key_and_value_return(cx, return_value, key.handle(), value.handle())
143                },
144            }
145        };
146        self.index.set(index + 1);
147        result
148    }
149}
150
151impl<D: DomTypes, T: DomObjectIteratorWrap<D> + JSTraceable + Iterable + DomGlobalGeneric<D>>
152    DomObjectWrap<D> for IterableIterator<D, T>
153{
154    const WRAP: unsafe fn(
155        &mut JSContext,
156        &D::GlobalScope,
157        Option<HandleObject>,
158        Box<Self>,
159    ) -> Root<Dom<Self>> = T::ITER_WRAP;
160}
161
162fn dict_return(
163    cx: &mut JSContext,
164    mut result: MutableHandleObject,
165    done: bool,
166    value: HandleValue,
167) -> Fallible<()> {
168    let mut dict = IterableKeyOrValueResult::empty();
169    dict.done = done;
170    dict.value.set(value.get());
171
172    unsafe { result.set(JS_NewObject(cx, ptr::null())) };
173    dict.to_jsobject(cx, result);
174    Ok(())
175}
176
177fn key_and_value_return(
178    cx: &mut JSContext,
179    mut result: MutableHandleObject,
180    key: HandleValue,
181    value: HandleValue,
182) -> Fallible<()> {
183    let mut dict = IterableKeyAndValueResult::empty();
184    dict.done = false;
185    dict.value = Some(
186        vec![key, value]
187            .into_iter()
188            .map(|handle| RootedTraceableBox::from_box(Heap::boxed(handle.get())))
189            .collect(),
190    );
191
192    unsafe { result.set(JS_NewObject(cx, ptr::null())) };
193    dict.to_jsobject(cx, result);
194    Ok(())
195}