use std::cell::Cell;
use dom_struct::dom_struct;
use html5ever::local_name;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::ElementInternalsBinding::{
ElementInternalsMethods, ValidityStateFlags,
};
use crate::dom::bindings::codegen::UnionTypes::FileOrUSVStringOrFormData;
use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::element::Element;
use crate::dom::file::File;
use crate::dom::htmlelement::HTMLElement;
use crate::dom::htmlformelement::{FormDatum, FormDatumValue, HTMLFormElement};
use crate::dom::node::{window_from_node, Node};
use crate::dom::nodelist::NodeList;
use crate::dom::validation::{is_barred_by_datalist_ancestor, Validatable};
use crate::dom::validitystate::{ValidationFlags, ValidityState};
use crate::script_runtime::CanGc;
#[derive(Clone, JSTraceable, MallocSizeOf)]
enum SubmissionValue {
File(DomRoot<File>),
FormData(Vec<FormDatum>),
USVString(USVString),
None,
}
impl From<Option<&FileOrUSVStringOrFormData>> for SubmissionValue {
fn from(value: Option<&FileOrUSVStringOrFormData>) -> Self {
match value {
None => SubmissionValue::None,
Some(FileOrUSVStringOrFormData::File(file)) => {
SubmissionValue::File(DomRoot::from_ref(file))
},
Some(FileOrUSVStringOrFormData::USVString(usv_string)) => {
SubmissionValue::USVString(usv_string.clone())
},
Some(FileOrUSVStringOrFormData::FormData(form_data)) => {
SubmissionValue::FormData(form_data.datums())
},
}
}
}
#[dom_struct]
pub struct ElementInternals {
reflector_: Reflector,
attached: Cell<bool>,
target_element: Dom<HTMLElement>,
validity_state: MutNullableDom<ValidityState>,
validation_message: DomRefCell<DOMString>,
custom_validity_error_message: DomRefCell<DOMString>,
validation_anchor: MutNullableDom<HTMLElement>,
submission_value: DomRefCell<SubmissionValue>,
state: DomRefCell<SubmissionValue>,
form_owner: MutNullableDom<HTMLFormElement>,
labels_node_list: MutNullableDom<NodeList>,
}
impl ElementInternals {
fn new_inherited(target_element: &HTMLElement) -> ElementInternals {
ElementInternals {
reflector_: Reflector::new(),
attached: Cell::new(false),
target_element: Dom::from_ref(target_element),
validity_state: Default::default(),
validation_message: DomRefCell::new(DOMString::new()),
custom_validity_error_message: DomRefCell::new(DOMString::new()),
validation_anchor: MutNullableDom::new(None),
submission_value: DomRefCell::new(SubmissionValue::None),
state: DomRefCell::new(SubmissionValue::None),
form_owner: MutNullableDom::new(None),
labels_node_list: MutNullableDom::new(None),
}
}
pub fn new(element: &HTMLElement) -> DomRoot<ElementInternals> {
let global = window_from_node(element);
reflect_dom_object(
Box::new(ElementInternals::new_inherited(element)),
&*global,
CanGc::note(),
)
}
fn is_target_form_associated(&self) -> bool {
self.target_element.is_form_associated_custom_element()
}
fn set_validation_message(&self, message: DOMString) {
*self.validation_message.borrow_mut() = message;
}
fn set_custom_validity_error_message(&self, message: DOMString) {
*self.custom_validity_error_message.borrow_mut() = message;
}
fn set_submission_value(&self, value: SubmissionValue) {
*self.submission_value.borrow_mut() = value;
}
fn set_state(&self, value: SubmissionValue) {
*self.state.borrow_mut() = value;
}
pub fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
self.form_owner.set(form);
}
pub fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
self.form_owner.get()
}
pub fn set_attached(&self) {
self.attached.set(true);
}
pub fn attached(&self) -> bool {
self.attached.get()
}
pub fn perform_entry_construction(&self, entry_list: &mut Vec<FormDatum>) {
if self
.target_element
.upcast::<Element>()
.has_attribute(&local_name!("disabled"))
{
warn!("We are in perform_entry_construction on an element with disabled attribute!");
}
if self.target_element.upcast::<Element>().disabled_state() {
warn!("We are in perform_entry_construction on an element with disabled bit!");
}
if !self.target_element.upcast::<Element>().enabled_state() {
warn!("We are in perform_entry_construction on an element without enabled bit!");
}
if let SubmissionValue::FormData(datums) = &*self.submission_value.borrow() {
entry_list.extend(datums.iter().cloned());
return;
}
let name = self
.target_element
.upcast::<Element>()
.get_string_attribute(&local_name!("name"));
if name.is_empty() {
return;
}
match &*self.submission_value.borrow() {
SubmissionValue::FormData(_) => unreachable!(
"The FormData submission value has been handled before name empty checking"
),
SubmissionValue::None => {},
SubmissionValue::USVString(string) => {
entry_list.push(FormDatum {
ty: DOMString::from("string"),
name,
value: FormDatumValue::String(DOMString::from(string.to_string())),
});
},
SubmissionValue::File(file) => {
entry_list.push(FormDatum {
ty: DOMString::from("file"),
name,
value: FormDatumValue::File(DomRoot::from_ref(file)),
});
},
}
}
pub fn is_invalid(&self) -> bool {
self.is_target_form_associated() &&
self.is_instance_validatable() &&
!self.satisfies_constraints()
}
}
impl ElementInternalsMethods<crate::DomTypeHolder> for ElementInternals {
fn SetFormValue(
&self,
value: Option<FileOrUSVStringOrFormData>,
maybe_state: Option<Option<FileOrUSVStringOrFormData>>,
) -> ErrorResult {
if !self.is_target_form_associated() {
return Err(Error::NotSupported);
}
self.set_submission_value(value.as_ref().into());
match maybe_state {
None => self.set_state(value.as_ref().into()),
Some(state) => self.set_state(state.as_ref().into()),
}
Ok(())
}
fn SetValidity(
&self,
flags: &ValidityStateFlags,
message: Option<DOMString>,
anchor: Option<&HTMLElement>,
) -> ErrorResult {
if !self.is_target_form_associated() {
return Err(Error::NotSupported);
}
let bits: ValidationFlags = flags.into();
if !bits.is_empty() && !message.as_ref().map_or_else(|| false, |m| !m.is_empty()) {
return Err(Error::Type(
"Setting an element to invalid requires a message string as the second argument."
.to_string(),
));
}
self.validity_state().update_invalid_flags(bits);
self.validity_state().update_pseudo_classes();
if bits.is_empty() {
self.set_validation_message(DOMString::new());
} else {
self.set_validation_message(message.unwrap_or_default());
}
if bits.contains(ValidationFlags::CUSTOM_ERROR) {
self.set_custom_validity_error_message(self.validation_message.borrow().clone());
} else {
self.set_custom_validity_error_message(DOMString::new());
}
match anchor {
None => self.validation_anchor.set(None),
Some(a) => {
if a == &*self.target_element ||
!self
.target_element
.upcast::<Node>()
.is_shadow_including_inclusive_ancestor_of(a.upcast::<Node>())
{
return Err(Error::NotFound);
}
self.validation_anchor.set(Some(a));
},
}
Ok(())
}
fn GetValidationMessage(&self) -> Fallible<DOMString> {
if !self.is_target_form_associated() {
return Err(Error::NotSupported);
}
Ok(self.validation_message.borrow().clone())
}
fn GetValidity(&self) -> Fallible<DomRoot<ValidityState>> {
if !self.is_target_form_associated() {
return Err(Error::NotSupported);
}
Ok(self.validity_state())
}
fn GetLabels(&self) -> Fallible<DomRoot<NodeList>> {
if !self.is_target_form_associated() {
return Err(Error::NotSupported);
}
Ok(self.labels_node_list.or_init(|| {
NodeList::new_labels_list(
self.target_element.upcast::<Node>().owner_doc().window(),
&self.target_element,
)
}))
}
fn GetWillValidate(&self) -> Fallible<bool> {
if !self.is_target_form_associated() {
return Err(Error::NotSupported);
}
Ok(self.is_instance_validatable())
}
fn GetForm(&self) -> Fallible<Option<DomRoot<HTMLFormElement>>> {
if !self.is_target_form_associated() {
return Err(Error::NotSupported);
}
Ok(self.form_owner.get())
}
fn CheckValidity(&self, can_gc: CanGc) -> Fallible<bool> {
if !self.is_target_form_associated() {
return Err(Error::NotSupported);
}
Ok(self.check_validity(can_gc))
}
fn ReportValidity(&self, can_gc: CanGc) -> Fallible<bool> {
if !self.is_target_form_associated() {
return Err(Error::NotSupported);
}
Ok(self.report_validity(can_gc))
}
}
impl Validatable for ElementInternals {
fn as_element(&self) -> &Element {
debug_assert!(self.is_target_form_associated());
self.target_element.upcast::<Element>()
}
fn validity_state(&self) -> DomRoot<ValidityState> {
debug_assert!(self.is_target_form_associated());
self.validity_state.or_init(|| {
ValidityState::new(
&window_from_node(self.target_element.upcast::<Node>()),
self.target_element.upcast(),
)
})
}
fn is_instance_validatable(&self) -> bool {
debug_assert!(self.is_target_form_associated());
if !self.target_element.is_submittable_element() {
return false;
}
!self.as_element().read_write_state() &&
!self.as_element().disabled_state() &&
!is_barred_by_datalist_ancestor(self.target_element.upcast::<Node>())
}
}