Skip to main content

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