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;
9
10use dom_struct::dom_struct;
11use js::context::JSContext;
12use js::conversions::ToJSValConvertible;
13use js::jsapi::Heap;
14use js::jsval::UndefinedValue;
15use js::realm::CurrentRealm;
16use js::rust::{HandleObject, HandleValue, MutableHandleObject};
17
18use crate::codegen::GenericBindings::IterableIteratorBinding::{
19    IterableKeyAndValueResult, IterableKeyOrValueResult,
20};
21use crate::conversions::IDLInterface;
22use crate::error::Fallible;
23use crate::interfaces::{DomHelpers, GlobalScopeHelpers};
24use crate::js::JS_NewObject;
25use crate::reflector::{DomGlobalGeneric, DomObjectIteratorWrap, DomObjectWrap, Reflector};
26use crate::root::{Dom, DomRoot, Root};
27use crate::trace::NoTrace;
28use crate::utils::DOMClass;
29use crate::{DomTypes, JSTraceable};
30
31/// The values that an iterator will iterate over.
32#[derive(JSTraceable, MallocSizeOf)]
33pub(crate) enum IteratorType {
34    /// The keys of the iterable object.
35    Keys,
36    /// The values of the iterable object.
37    Values,
38    /// The keys and values of the iterable object combined.
39    Entries,
40}
41
42/// A DOM object that can be iterated over using a pair value iterator.
43pub trait Iterable {
44    /// The type of the key of the iterator pair.
45    type Key: ToJSValConvertible;
46    /// The type of the value of the iterator pair.
47    type Value: ToJSValConvertible;
48
49    /// Return the number of entries that can be iterated over.
50    fn get_iterable_length(&self, cx: &mut JSContext) -> u32;
51    /// Return the value at the provided index.
52    fn get_value_at_index(&self, cx: &mut JSContext, index: u32) -> Self::Value;
53    /// Return the key at the provided index.
54    fn get_key_at_index(&self, cx: &mut JSContext, 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(no_has_parent)]
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    const PROTO_ID: crate::codegen::PrototypeList::ID = crate::codegen::PrototypeList::ID::Last;
93}
94
95impl<D: DomTypes, T: DomObjectIteratorWrap<D> + JSTraceable + Iterable + DomGlobalGeneric<D>>
96    IterableIterator<D, T>
97{
98    /// Create a new iterator instance for the provided iterable DOM interface.
99    pub(crate) fn new(
100        realm: &mut CurrentRealm,
101        iterable: &T,
102        type_: IteratorType,
103    ) -> DomRoot<Self> {
104        let iterator = Box::new(IterableIterator {
105            reflector: Reflector::new(),
106            type_,
107            iterable: Dom::from_ref(iterable),
108            index: Cell::new(0),
109            _marker: NoTrace(PhantomData),
110        });
111        let global = D::GlobalScope::from_current_realm(realm);
112        <D as DomHelpers<D>>::reflect_dom_object(realm, iterator, &*global)
113    }
114
115    /// Return the next value from the iterable object.
116    #[expect(non_snake_case)]
117    pub fn Next(&self, cx: &mut JSContext, return_value: MutableHandleObject) -> Fallible<()> {
118        let index = self.index.get();
119        rooted!(&in(cx) let mut value = UndefinedValue());
120        let result = if index >= self.iterable.get_iterable_length(cx) {
121            dict_return(cx, return_value, true, value.handle())
122        } else {
123            match self.type_ {
124                IteratorType::Keys => {
125                    self.iterable
126                        .get_key_at_index(cx, index)
127                        .to_jsval(cx, value.handle_mut());
128                    dict_return(cx, return_value, false, value.handle())
129                },
130                IteratorType::Values => {
131                    self.iterable
132                        .get_value_at_index(cx, index)
133                        .to_jsval(cx, value.handle_mut());
134                    dict_return(cx, return_value, false, value.handle())
135                },
136                IteratorType::Entries => {
137                    rooted!(&in(cx) let mut key = UndefinedValue());
138                    self.iterable
139                        .get_key_at_index(cx, index)
140                        .to_jsval(cx, key.handle_mut());
141                    self.iterable
142                        .get_value_at_index(cx, index)
143                        .to_jsval(cx, value.handle_mut());
144                    key_and_value_return(cx, return_value, key.handle(), value.handle())
145                },
146            }
147        };
148        self.index.set(index + 1);
149        result
150    }
151}
152
153impl<D: DomTypes, T: DomObjectIteratorWrap<D> + JSTraceable + Iterable + DomGlobalGeneric<D>>
154    DomObjectWrap<D> for IterableIterator<D, T>
155{
156    const WRAP: unsafe fn(
157        &mut JSContext,
158        &D::GlobalScope,
159        Option<HandleObject>,
160        Box<Self>,
161    ) -> Root<Dom<Self>> = T::ITER_WRAP;
162}
163
164fn dict_return(
165    cx: &mut JSContext,
166    mut result: MutableHandleObject,
167    done: bool,
168    value: HandleValue,
169) -> Fallible<()> {
170    let mut dict = IterableKeyOrValueResult::empty();
171    dict.done = done;
172    dict.value.set(value.get());
173
174    result.set(JS_NewObject(cx, None));
175    dict.to_jsobject(cx, result);
176    Ok(())
177}
178
179fn key_and_value_return(
180    cx: &mut JSContext,
181    mut result: MutableHandleObject,
182    key: HandleValue,
183    value: HandleValue,
184) -> Fallible<()> {
185    let mut dict = IterableKeyAndValueResult::empty();
186    dict.done = false;
187    dict.value = Some(
188        vec![key, value]
189            .into_iter()
190            .map(|handle| Heap::boxed(handle.get()))
191            .collect(),
192    );
193
194    result.set(JS_NewObject(cx, None));
195    dict.to_jsobject(cx, result);
196    Ok(())
197}