script/dom/
domtokenlist.rs1use 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() => {
70 Err(Error::InvalidCharacter(None))
71 },
72 slice => Ok(Atom::from(slice)),
73 }
74 }
75
76 fn perform_update_steps(&self, atoms: Vec<Atom>, can_gc: CanGc) {
78 if !self.element.has_attribute(&self.local_name) && atoms.is_empty() {
80 return;
81 }
82 self.element
84 .set_atomic_tokenlist_attribute(&self.local_name, atoms, can_gc)
85 }
86
87 fn validation_steps(&self, token: &str) -> Fallible<bool> {
89 match &self.supported_tokens {
90 None => Err(Error::Type(
91 "This attribute has no supported tokens".to_owned(),
92 )),
93 Some(supported_tokens) => {
94 let token = Atom::from(token).to_ascii_lowercase();
95 if supported_tokens.contains(&token) {
96 return Ok(true);
97 }
98 Ok(false)
99 },
100 }
101 }
102}
103
104impl DOMTokenListMethods<crate::DomTypeHolder> for DOMTokenList {
106 fn Length(&self) -> u32 {
108 self.attribute()
109 .map_or(0, |attr| attr.value().as_tokens().len()) as u32
110 }
111
112 fn Item(&self, index: u32) -> Option<DOMString> {
114 self.attribute().and_then(|attr| {
115 attr.value()
117 .as_tokens()
118 .get(index as usize)
119 .map(|token| DOMString::from(&**token))
120 })
121 }
122
123 fn Contains(&self, token: DOMString) -> bool {
125 let token = Atom::from(token);
126 self.attribute()
127 .is_some_and(|attr| attr.value().as_tokens().contains(&token))
128 }
129
130 fn Add(&self, tokens: Vec<DOMString>, can_gc: CanGc) -> ErrorResult {
132 let mut atoms = self.element.get_tokenlist_attribute(&self.local_name);
133 for token in &tokens {
134 let token = self.check_token_exceptions(token)?;
135 if !atoms.contains(&token) {
136 atoms.push(token);
137 }
138 }
139 self.perform_update_steps(atoms, can_gc);
140 Ok(())
141 }
142
143 fn Remove(&self, tokens: Vec<DOMString>, can_gc: CanGc) -> ErrorResult {
145 let mut atoms = self.element.get_tokenlist_attribute(&self.local_name);
146 for token in &tokens {
147 let token = self.check_token_exceptions(token)?;
148 atoms
149 .iter()
150 .position(|atom| *atom == token)
151 .map(|index| atoms.remove(index));
152 }
153 self.perform_update_steps(atoms, can_gc);
154 Ok(())
155 }
156
157 fn Toggle(&self, token: DOMString, force: Option<bool>, can_gc: CanGc) -> Fallible<bool> {
159 let mut atoms = self.element.get_tokenlist_attribute(&self.local_name);
160 let token = self.check_token_exceptions(&token)?;
161 match atoms.iter().position(|atom| *atom == token) {
162 Some(index) => match force {
163 Some(true) => Ok(true),
164 _ => {
165 atoms.remove(index);
166 self.perform_update_steps(atoms, can_gc);
167 Ok(false)
168 },
169 },
170 None => match force {
171 Some(false) => Ok(false),
172 _ => {
173 atoms.push(token);
174 self.perform_update_steps(atoms, can_gc);
175 Ok(true)
176 },
177 },
178 }
179 }
180
181 fn Value(&self) -> DOMString {
183 self.element.get_string_attribute(&self.local_name)
184 }
185
186 fn SetValue(&self, value: DOMString, can_gc: CanGc) {
188 self.element
189 .set_tokenlist_attribute(&self.local_name, value, can_gc);
190 }
191
192 fn Replace(&self, token: DOMString, new_token: DOMString, can_gc: CanGc) -> Fallible<bool> {
194 if token.is_empty() || new_token.is_empty() {
195 return Err(Error::Syntax(None));
197 }
198 if token.contains_html_space_characters() || new_token.contains_html_space_characters() {
199 return Err(Error::InvalidCharacter(None));
201 }
202 let token = Atom::from(token);
204 let new_token = Atom::from(new_token);
205 let mut atoms = self.element.get_tokenlist_attribute(&self.local_name);
206 let mut result = false;
207 if let Some(pos) = atoms.iter().position(|atom| *atom == token) {
208 match atoms.iter().position(|atom| *atom == new_token) {
209 Some(redundant_pos) if redundant_pos > pos => {
210 atoms[pos] = new_token;
214 atoms.remove(redundant_pos);
215 },
216 Some(redundant_pos) if redundant_pos < pos => {
217 atoms.remove(pos);
221 },
222 Some(_) => {
223 },
225 None => {
226 atoms[pos] = new_token;
228 },
229 }
230
231 self.perform_update_steps(atoms, can_gc);
233 result = true;
234 }
235 Ok(result)
236 }
237
238 fn Supports(&self, token: DOMString) -> Fallible<bool> {
240 self.validation_steps(&token.str())
241 }
242
243 fn IndexedGetter(&self, index: u32) -> Option<DOMString> {
245 self.Item(index)
246 }
247}