script/dom/
domtokenlist.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 dom_struct::dom_struct;
6use html5ever::{LocalName, ns};
7use style::str::HTML_SPACE_CHARACTERS;
8use stylo_atoms::Atom;
9
10use crate::dom::attr::Attr;
11use crate::dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenListMethods;
12use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
13use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
14use crate::dom::bindings::root::{Dom, DomRoot};
15use crate::dom::bindings::str::DOMString;
16use crate::dom::element::Element;
17use crate::dom::node::NodeTraits;
18use crate::script_runtime::CanGc;
19
20#[dom_struct]
21pub(crate) struct DOMTokenList {
22    reflector_: Reflector,
23    element: Dom<Element>,
24    #[no_trace]
25    local_name: LocalName,
26    #[no_trace]
27    supported_tokens: Option<Vec<Atom>>,
28}
29
30impl DOMTokenList {
31    pub(crate) fn new_inherited(
32        element: &Element,
33        local_name: LocalName,
34        supported_tokens: Option<Vec<Atom>>,
35    ) -> DOMTokenList {
36        DOMTokenList {
37            reflector_: Reflector::new(),
38            element: Dom::from_ref(element),
39            local_name,
40            supported_tokens,
41        }
42    }
43
44    pub(crate) fn new(
45        element: &Element,
46        local_name: &LocalName,
47        supported_tokens: Option<Vec<Atom>>,
48        can_gc: CanGc,
49    ) -> DomRoot<DOMTokenList> {
50        reflect_dom_object(
51            Box::new(DOMTokenList::new_inherited(
52                element,
53                local_name.clone(),
54                supported_tokens,
55            )),
56            &*element.owner_window(),
57            can_gc,
58        )
59    }
60
61    fn attribute(&self) -> Option<DomRoot<Attr>> {
62        self.element.get_attribute(&ns!(), &self.local_name)
63    }
64
65    fn check_token_exceptions(&self, token: &DOMString) -> Fallible<Atom> {
66        let token = token.str();
67        match &*token {
68            "" => Err(Error::Syntax(None)),
69            slice if slice.find(HTML_SPACE_CHARACTERS).is_some() => Err(Error::InvalidCharacter),
70            slice => Ok(Atom::from(slice)),
71        }
72    }
73
74    /// <https://dom.spec.whatwg.org/#concept-dtl-update>
75    fn perform_update_steps(&self, atoms: Vec<Atom>, can_gc: CanGc) {
76        // Step 1
77        if !self.element.has_attribute(&self.local_name) && atoms.is_empty() {
78            return;
79        }
80        // step 2
81        self.element
82            .set_atomic_tokenlist_attribute(&self.local_name, atoms, can_gc)
83    }
84
85    /// <https://dom.spec.whatwg.org/#concept-domtokenlist-validation>
86    fn validation_steps(&self, token: &str) -> Fallible<bool> {
87        match &self.supported_tokens {
88            None => Err(Error::Type(
89                "This attribute has no supported tokens".to_owned(),
90            )),
91            Some(supported_tokens) => {
92                let token = Atom::from(token).to_ascii_lowercase();
93                if supported_tokens.contains(&token) {
94                    return Ok(true);
95                }
96                Ok(false)
97            },
98        }
99    }
100}
101
102/// <https://dom.spec.whatwg.org/#domtokenlist>
103impl DOMTokenListMethods<crate::DomTypeHolder> for DOMTokenList {
104    /// <https://dom.spec.whatwg.org/#dom-domtokenlist-length>
105    fn Length(&self) -> u32 {
106        self.attribute()
107            .map_or(0, |attr| attr.value().as_tokens().len()) as u32
108    }
109
110    /// <https://dom.spec.whatwg.org/#dom-domtokenlist-item>
111    fn Item(&self, index: u32) -> Option<DOMString> {
112        self.attribute().and_then(|attr| {
113            // FIXME(ajeffrey): Convert directly from Atom to DOMString
114            attr.value()
115                .as_tokens()
116                .get(index as usize)
117                .map(|token| DOMString::from(&**token))
118        })
119    }
120
121    /// <https://dom.spec.whatwg.org/#dom-domtokenlist-contains>
122    fn Contains(&self, token: DOMString) -> bool {
123        let token = Atom::from(token);
124        self.attribute()
125            .is_some_and(|attr| attr.value().as_tokens().contains(&token))
126    }
127
128    /// <https://dom.spec.whatwg.org/#dom-domtokenlist-add>
129    fn Add(&self, tokens: Vec<DOMString>, can_gc: CanGc) -> ErrorResult {
130        let mut atoms = self.element.get_tokenlist_attribute(&self.local_name);
131        for token in &tokens {
132            let token = self.check_token_exceptions(token)?;
133            if !atoms.contains(&token) {
134                atoms.push(token);
135            }
136        }
137        self.perform_update_steps(atoms, can_gc);
138        Ok(())
139    }
140
141    /// <https://dom.spec.whatwg.org/#dom-domtokenlist-remove>
142    fn Remove(&self, tokens: Vec<DOMString>, can_gc: CanGc) -> ErrorResult {
143        let mut atoms = self.element.get_tokenlist_attribute(&self.local_name);
144        for token in &tokens {
145            let token = self.check_token_exceptions(token)?;
146            atoms
147                .iter()
148                .position(|atom| *atom == token)
149                .map(|index| atoms.remove(index));
150        }
151        self.perform_update_steps(atoms, can_gc);
152        Ok(())
153    }
154
155    /// <https://dom.spec.whatwg.org/#dom-domtokenlist-toggle>
156    fn Toggle(&self, token: DOMString, force: Option<bool>, can_gc: CanGc) -> Fallible<bool> {
157        let mut atoms = self.element.get_tokenlist_attribute(&self.local_name);
158        let token = self.check_token_exceptions(&token)?;
159        match atoms.iter().position(|atom| *atom == token) {
160            Some(index) => match force {
161                Some(true) => Ok(true),
162                _ => {
163                    atoms.remove(index);
164                    self.perform_update_steps(atoms, can_gc);
165                    Ok(false)
166                },
167            },
168            None => match force {
169                Some(false) => Ok(false),
170                _ => {
171                    atoms.push(token);
172                    self.perform_update_steps(atoms, can_gc);
173                    Ok(true)
174                },
175            },
176        }
177    }
178
179    /// <https://dom.spec.whatwg.org/#dom-domtokenlist-value>
180    fn Value(&self) -> DOMString {
181        self.element.get_string_attribute(&self.local_name)
182    }
183
184    /// <https://dom.spec.whatwg.org/#dom-domtokenlist-value>
185    fn SetValue(&self, value: DOMString, can_gc: CanGc) {
186        self.element
187            .set_tokenlist_attribute(&self.local_name, value, can_gc);
188    }
189
190    /// <https://dom.spec.whatwg.org/#dom-domtokenlist-replace>
191    fn Replace(&self, token: DOMString, new_token: DOMString, can_gc: CanGc) -> Fallible<bool> {
192        if token.is_empty() || new_token.is_empty() {
193            // Step 1.
194            return Err(Error::Syntax(None));
195        }
196        if token.contains_html_space_characters() || new_token.contains_html_space_characters() {
197            // Step 2.
198            return Err(Error::InvalidCharacter);
199        }
200        // Steps 3-4.
201        let token = Atom::from(token);
202        let new_token = Atom::from(new_token);
203        let mut atoms = self.element.get_tokenlist_attribute(&self.local_name);
204        let mut result = false;
205        if let Some(pos) = atoms.iter().position(|atom| *atom == token) {
206            match atoms.iter().position(|atom| *atom == new_token) {
207                Some(redundant_pos) if redundant_pos > pos => {
208                    // The replacement is already in the list, later,
209                    // so we perform the replacement and remove the
210                    // later copy.
211                    atoms[pos] = new_token;
212                    atoms.remove(redundant_pos);
213                },
214                Some(redundant_pos) if redundant_pos < pos => {
215                    // The replacement is already in the list, earlier,
216                    // so we remove the index where we'd be putting the
217                    // later copy.
218                    atoms.remove(pos);
219                },
220                Some(_) => {
221                    // Else we are replacing the token with itself, nothing to change
222                },
223                None => {
224                    // The replacement is not in the list already
225                    atoms[pos] = new_token;
226                },
227            }
228
229            // Step 5.
230            self.perform_update_steps(atoms, can_gc);
231            result = true;
232        }
233        Ok(result)
234    }
235
236    /// <https://dom.spec.whatwg.org/#dom-domtokenlist-supports>
237    fn Supports(&self, token: DOMString) -> Fallible<bool> {
238        self.validation_steps(&token.str())
239    }
240
241    // check-tidy: no specs after this line
242    fn IndexedGetter(&self, index: u32) -> Option<DOMString> {
243        self.Item(index)
244    }
245}