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