use dom_struct::dom_struct;
use html5ever::{namespace_url, ns, LocalName};
use servo_atoms::Atom;
use style::str::HTML_SPACE_CHARACTERS;
use crate::dom::attr::Attr;
use crate::dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenListMethods;
use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::element::Element;
use crate::dom::node::window_from_node;
use crate::script_runtime::CanGc;
#[dom_struct]
pub struct DOMTokenList {
reflector_: Reflector,
element: Dom<Element>,
#[no_trace]
local_name: LocalName,
#[no_trace]
supported_tokens: Option<Vec<Atom>>,
}
impl DOMTokenList {
pub fn new_inherited(
element: &Element,
local_name: LocalName,
supported_tokens: Option<Vec<Atom>>,
) -> DOMTokenList {
DOMTokenList {
reflector_: Reflector::new(),
element: Dom::from_ref(element),
local_name,
supported_tokens,
}
}
pub fn new(
element: &Element,
local_name: &LocalName,
supported_tokens: Option<Vec<Atom>>,
) -> DomRoot<DOMTokenList> {
let window = window_from_node(element);
reflect_dom_object(
Box::new(DOMTokenList::new_inherited(
element,
local_name.clone(),
supported_tokens,
)),
&*window,
)
}
fn attribute(&self) -> Option<DomRoot<Attr>> {
self.element.get_attribute(&ns!(), &self.local_name)
}
fn check_token_exceptions(&self, token: &str) -> Fallible<Atom> {
match token {
"" => Err(Error::Syntax),
slice if slice.find(HTML_SPACE_CHARACTERS).is_some() => Err(Error::InvalidCharacter),
slice => Ok(Atom::from(slice)),
}
}
fn perform_update_steps(&self, atoms: Vec<Atom>, can_gc: CanGc) {
if !self.element.has_attribute(&self.local_name) && atoms.is_empty() {
return;
}
self.element
.set_atomic_tokenlist_attribute(&self.local_name, atoms, can_gc)
}
fn validation_steps(&self, token: &str) -> Fallible<bool> {
match &self.supported_tokens {
None => Err(Error::Type(
"This attribute has no supported tokens".to_owned(),
)),
Some(supported_tokens) => {
let token = Atom::from(token).to_ascii_lowercase();
if supported_tokens
.iter()
.any(|supported_token| *supported_token == token)
{
return Ok(true);
}
Ok(false)
},
}
}
}
impl DOMTokenListMethods for DOMTokenList {
fn Length(&self) -> u32 {
self.attribute()
.map_or(0, |attr| attr.value().as_tokens().len()) as u32
}
fn Item(&self, index: u32) -> Option<DOMString> {
self.attribute().and_then(|attr| {
attr.value()
.as_tokens()
.get(index as usize)
.map(|token| DOMString::from(&**token))
})
}
fn Contains(&self, token: DOMString) -> bool {
let token = Atom::from(token);
self.attribute().is_some_and(|attr| {
attr.value()
.as_tokens()
.iter()
.any(|atom: &Atom| *atom == token)
})
}
fn Add(&self, tokens: Vec<DOMString>, can_gc: CanGc) -> ErrorResult {
let mut atoms = self.element.get_tokenlist_attribute(&self.local_name);
for token in &tokens {
let token = self.check_token_exceptions(token)?;
if !atoms.iter().any(|atom| *atom == token) {
atoms.push(token);
}
}
self.perform_update_steps(atoms, can_gc);
Ok(())
}
fn Remove(&self, tokens: Vec<DOMString>, can_gc: CanGc) -> ErrorResult {
let mut atoms = self.element.get_tokenlist_attribute(&self.local_name);
for token in &tokens {
let token = self.check_token_exceptions(token)?;
atoms
.iter()
.position(|atom| *atom == token)
.map(|index| atoms.remove(index));
}
self.perform_update_steps(atoms, can_gc);
Ok(())
}
fn Toggle(&self, token: DOMString, force: Option<bool>, can_gc: CanGc) -> Fallible<bool> {
let mut atoms = self.element.get_tokenlist_attribute(&self.local_name);
let token = self.check_token_exceptions(&token)?;
match atoms.iter().position(|atom| *atom == token) {
Some(index) => match force {
Some(true) => Ok(true),
_ => {
atoms.remove(index);
self.perform_update_steps(atoms, can_gc);
Ok(false)
},
},
None => match force {
Some(false) => Ok(false),
_ => {
atoms.push(token);
self.perform_update_steps(atoms, can_gc);
Ok(true)
},
},
}
}
fn Value(&self) -> DOMString {
self.element.get_string_attribute(&self.local_name)
}
fn SetValue(&self, value: DOMString, can_gc: CanGc) {
self.element
.set_tokenlist_attribute(&self.local_name, value, can_gc);
}
fn Replace(&self, token: DOMString, new_token: DOMString, can_gc: CanGc) -> Fallible<bool> {
if token.is_empty() || new_token.is_empty() {
return Err(Error::Syntax);
}
if token.contains(HTML_SPACE_CHARACTERS) || new_token.contains(HTML_SPACE_CHARACTERS) {
return Err(Error::InvalidCharacter);
}
let token = Atom::from(token);
let new_token = Atom::from(new_token);
let mut atoms = self.element.get_tokenlist_attribute(&self.local_name);
let mut result = false;
if let Some(pos) = atoms.iter().position(|atom| *atom == token) {
match atoms.iter().position(|atom| *atom == new_token) {
Some(redundant_pos) if redundant_pos > pos => {
atoms[pos] = new_token;
atoms.remove(redundant_pos);
},
Some(redundant_pos) if redundant_pos < pos => {
atoms.remove(pos);
},
Some(_) => {
},
None => {
atoms[pos] = new_token;
},
}
self.perform_update_steps(atoms, can_gc);
result = true;
}
Ok(result)
}
fn Supports(&self, token: DOMString) -> Fallible<bool> {
self.validation_steps(&token)
}
fn IndexedGetter(&self, index: u32) -> Option<DOMString> {
self.Item(index)
}
}