script/dom/
domtokenlist.rs1use 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 fn perform_update_steps(&self, cx: &mut JSContext, atoms: Vec<Atom>) {
73 if !self.element.has_attribute(&self.local_name) && atoms.is_empty() {
75 return;
76 }
77 self.element
79 .set_attribute(cx, &self.local_name, atoms.into())
80 }
81
82 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
99impl DOMTokenListMethods<crate::DomTypeHolder> for DOMTokenList {
101 fn Length(&self) -> u32 {
103 self.element.get_tokenlist_attribute(&self.local_name).len() as u32
104 }
105
106 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 fn Contains(&self, token: DOMString) -> bool {
116 self.element
117 .get_tokenlist_attribute(&self.local_name)
118 .contains(&Atom::from(token))
119 }
120
121 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 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 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 fn Value(&self) -> DOMString {
174 self.element.get_string_attribute(&self.local_name)
175 }
176
177 fn SetValue(&self, cx: &mut JSContext, value: DOMString) {
179 self.element
180 .set_tokenlist_attribute(cx, &self.local_name, value);
181 }
182
183 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 return Err(Error::Syntax(None));
193 }
194 if token.contains_html_space_characters() || new_token.contains_html_space_characters() {
195 return Err(Error::InvalidCharacter(None));
197 }
198 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 atoms[pos] = new_token;
210 atoms.remove(redundant_pos);
211 },
212 Some(redundant_pos) if redundant_pos < pos => {
213 atoms.remove(pos);
217 },
218 Some(_) => {
219 },
221 None => {
222 atoms[pos] = new_token;
224 },
225 }
226
227 self.perform_update_steps(cx, atoms);
229 result = true;
230 }
231 Ok(result)
232 }
233
234 fn Supports(&self, token: DOMString) -> Fallible<bool> {
236 self.validation_steps(&token.str())
237 }
238
239 fn IndexedGetter(&self, index: u32) -> Option<DOMString> {
241 self.Item(index)
242 }
243}