Implement form-associated custom elements and their ElementInternals (#31980)

* FACEs work, setFormValue test is awful so now has _mozilla backup

* 1. Impl Validatable in ElementInternals instead of HTMLElement. 2. Reuse the code in Validatable trait. 3. The form associated custom element is not a customized built-in element.

* add some comments

* support readonly attribute and complete barred from constraint validation

* Addressed the code review comments

* Updated the legacy-layout results

* Fixed the WPT failures in ElementInternals-validation.html

* Addressed the code review comments

* Review suggestions

* Fixed silly mistakes and update the test result outside elementinternals

* update the test results

---------

Co-authored-by: Patrick Shaughnessy <pshaughn@comcast.net>
Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
cathiechen 2024-04-11 15:17:11 +02:00 committed by GitHub
parent 2eb959a159
commit 4e4a4c0a28
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
67 changed files with 1641 additions and 619 deletions

View file

@ -12,7 +12,7 @@ use html5ever::{namespace_url, ns, LocalName, Namespace, Prefix};
use js::conversions::ToJSValConvertible;
use js::glue::UnwrapObjectStatic;
use js::jsapi::{HandleValueArray, Heap, IsCallable, IsConstructor, JSAutoRealm, JSObject};
use js::jsval::{JSVal, NullValue, ObjectValue, UndefinedValue};
use js::jsval::{BooleanValue, JSVal, NullValue, ObjectValue, UndefinedValue};
use js::rust::wrappers::{Construct1, JS_GetProperty, SameValue};
use js::rust::{HandleObject, HandleValue, MutableHandleValue};
@ -41,6 +41,7 @@ use crate::dom::domexception::{DOMErrorName, DOMException};
use crate::dom::element::Element;
use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlelement::HTMLElement;
use crate::dom::htmlformelement::{FormControl, HTMLFormElement};
use crate::dom::node::{document_from_node, window_from_node, Node, ShadowIncluding};
use crate::dom::promise::Promise;
use crate::dom::window::Window;
@ -164,7 +165,8 @@ impl CustomElementRegistry {
}
/// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define>
/// Steps 10.3, 10.4
/// This function includes both steps 14.3 and 14.4 which add the callbacks to a map and
/// process them.
#[allow(unsafe_code)]
unsafe fn get_callbacks(&self, prototype: HandleObject) -> Fallible<LifecycleCallbacks> {
let cx = GlobalScope::get_cx();
@ -175,11 +177,34 @@ impl CustomElementRegistry {
disconnected_callback: get_callback(cx, prototype, b"disconnectedCallback\0")?,
adopted_callback: get_callback(cx, prototype, b"adoptedCallback\0")?,
attribute_changed_callback: get_callback(cx, prototype, b"attributeChangedCallback\0")?,
form_associated_callback: None,
form_disabled_callback: None,
form_reset_callback: None,
form_state_restore_callback: None,
})
}
/// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define>
/// Step 10.6
/// Step 14.13: Add form associated callbacks to LifecycleCallbacks
#[allow(unsafe_code)]
unsafe fn add_form_associated_callbacks(
&self,
prototype: HandleObject,
callbacks: &mut LifecycleCallbacks,
) -> ErrorResult {
let cx = self.window.get_cx();
callbacks.form_associated_callback =
get_callback(cx, prototype, b"formAssociatedCallback\0")?;
callbacks.form_reset_callback = get_callback(cx, prototype, b"formResetCallback\0")?;
callbacks.form_disabled_callback = get_callback(cx, prototype, b"formDisabledCallback\0")?;
callbacks.form_state_restore_callback =
get_callback(cx, prototype, b"formStateRestoreCallback\0")?;
Ok(())
}
#[allow(unsafe_code)]
fn get_observed_attributes(&self, constructor: HandleObject) -> Fallible<Vec<DOMString>> {
let cx = GlobalScope::get_cx();
@ -212,10 +237,75 @@ impl CustomElementRegistry {
_ => Err(Error::JSFailed),
}
}
/// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define>
/// Step 14.11: Get the value of `formAssociated`.
#[allow(unsafe_code)]
fn get_form_associated_value(&self, constructor: HandleObject) -> Fallible<bool> {
let cx = self.window.get_cx();
rooted!(in(*cx) let mut form_associated_value = UndefinedValue());
if unsafe {
!JS_GetProperty(
*cx,
constructor,
b"formAssociated\0".as_ptr() as *const _,
form_associated_value.handle_mut(),
)
} {
return Err(Error::JSFailed);
}
if form_associated_value.is_undefined() {
return Ok(false);
}
let conversion =
unsafe { FromJSValConvertible::from_jsval(*cx, form_associated_value.handle(), ()) };
match conversion {
Ok(ConversionResult::Success(flag)) => Ok(flag),
Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.into())),
_ => Err(Error::JSFailed),
}
}
/// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define>
/// Step 14.7: Get `disabledFeatures` value
#[allow(unsafe_code)]
fn get_disabled_features(&self, constructor: HandleObject) -> Fallible<Vec<DOMString>> {
let cx = self.window.get_cx();
rooted!(in(*cx) let mut disabled_features = UndefinedValue());
if unsafe {
!JS_GetProperty(
*cx,
constructor,
b"disabledFeatures\0".as_ptr() as *const _,
disabled_features.handle_mut(),
)
} {
return Err(Error::JSFailed);
}
if disabled_features.is_undefined() {
return Ok(Vec::new());
}
let conversion = unsafe {
FromJSValConvertible::from_jsval(
*cx,
disabled_features.handle(),
StringificationBehavior::Default,
)
};
match conversion {
Ok(ConversionResult::Success(attributes)) => Ok(attributes),
Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.into())),
_ => Err(Error::JSFailed),
}
}
}
/// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define>
/// Step 10.4
/// Step 14.4: Get `callbackValue` for all `callbackName` in `lifecycleCallbacks`.
#[allow(unsafe_code)]
fn get_callback(
cx: JSContext,
@ -323,7 +413,10 @@ impl CustomElementRegistryMethods for CustomElementRegistry {
// Step 9
self.element_definition_is_running.set(true);
// Steps 10.1 - 10.2
// Steps 10-13: Initialize `formAssociated`, `disableInternals`, `disableShadow`, and
// `observedAttributes` with default values, but this is done later.
// Steps 14.1 - 14.2: Get the value of the prototype.
rooted!(in(*cx) let mut prototype = UndefinedValue());
{
let _ac = JSAutoRealm::new(*cx, constructor.get());
@ -334,8 +427,12 @@ impl CustomElementRegistryMethods for CustomElementRegistry {
};
// Steps 10.3 - 10.4
// It would be easier to get all the callbacks in one pass after
// we know whether this definition is going to be form-associated,
// but the order of operations is specified and it's observable
// if one of the callback getters throws an exception.
rooted!(in(*cx) let proto_object = prototype.to_object());
let callbacks = {
let mut callbacks = {
let _ac = JSAutoRealm::new(*cx, proto_object.get());
match unsafe { self.get_callbacks(proto_object.handle()) } {
Ok(callbacks) => callbacks,
@ -346,7 +443,8 @@ impl CustomElementRegistryMethods for CustomElementRegistry {
}
};
// Step 10.5 - 10.6
// Step 14.5: Handle the case where with `attributeChangedCallback` on `lifecycleCallbacks`
// is not null.
let observed_attributes = if callbacks.attribute_changed_callback.is_some() {
let _ac = JSAutoRealm::new(*cx, constructor.get());
match self.get_observed_attributes(constructor.handle()) {
@ -360,26 +458,71 @@ impl CustomElementRegistryMethods for CustomElementRegistry {
Vec::new()
};
// Steps 14.6 - 14.10: Handle `disabledFeatures`.
let (disable_internals, disable_shadow) = {
let _ac = JSAutoRealm::new(*cx, constructor.get());
match self.get_disabled_features(constructor.handle()) {
Ok(sequence) => (
sequence.iter().any(|s| *s == "internals"),
sequence.iter().any(|s| *s == "shadow"),
),
Err(error) => {
self.element_definition_is_running.set(false);
return Err(error);
},
}
};
// Step 14.11 - 14.12: Handle `formAssociated`.
let form_associated = {
let _ac = JSAutoRealm::new(*cx, constructor.get());
match self.get_form_associated_value(constructor.handle()) {
Ok(flag) => flag,
Err(error) => {
self.element_definition_is_running.set(false);
return Err(error);
},
}
};
// Steps 14.13: Add the `formAssociated` callbacks.
if form_associated {
let _ac = JSAutoRealm::new(*cx, proto_object.get());
unsafe {
match self.add_form_associated_callbacks(proto_object.handle(), &mut callbacks) {
Err(error) => {
self.element_definition_is_running.set(false);
return Err(error);
},
Ok(()) => {},
}
}
}
self.element_definition_is_running.set(false);
// Step 11
// Step 15: Set up the new custom element definition.
let definition = Rc::new(CustomElementDefinition::new(
name.clone(),
local_name.clone(),
constructor_,
observed_attributes,
callbacks,
form_associated,
disable_internals,
disable_shadow,
));
// Step 12
// Step 16: Add definition to this CustomElementRegistry.
self.definitions
.borrow_mut()
.insert(name.clone(), definition.clone());
// Step 13
// Step 17: Let document be this CustomElementRegistry's relevant global object's
// associated Document.
let document = self.window.Document();
// Steps 14-15
// Steps 18-19: Enqueue custom elements upgrade reaction for upgrade candidates.
for candidate in document
.upcast::<Node>()
.traverse_preorder(ShadowIncluding::Yes)
@ -489,6 +632,18 @@ pub struct LifecycleCallbacks {
#[ignore_malloc_size_of = "Rc"]
attribute_changed_callback: Option<Rc<Function>>,
#[ignore_malloc_size_of = "Rc"]
form_associated_callback: Option<Rc<Function>>,
#[ignore_malloc_size_of = "Rc"]
form_reset_callback: Option<Rc<Function>>,
#[ignore_malloc_size_of = "Rc"]
form_disabled_callback: Option<Rc<Function>>,
#[ignore_malloc_size_of = "Rc"]
form_state_restore_callback: Option<Rc<Function>>,
}
#[derive(Clone, JSTraceable, MallocSizeOf)]
@ -514,6 +669,12 @@ pub struct CustomElementDefinition {
pub callbacks: LifecycleCallbacks,
pub construction_stack: DomRefCell<Vec<ConstructionStackEntry>>,
pub form_associated: bool,
pub disable_internals: bool,
pub disable_shadow: bool,
}
impl CustomElementDefinition {
@ -523,6 +684,9 @@ impl CustomElementDefinition {
constructor: Rc<CustomElementConstructor>,
observed_attributes: Vec<DOMString>,
callbacks: LifecycleCallbacks,
form_associated: bool,
disable_internals: bool,
disable_shadow: bool,
) -> CustomElementDefinition {
CustomElementDefinition {
name,
@ -531,6 +695,9 @@ impl CustomElementDefinition {
observed_attributes,
callbacks,
construction_stack: Default::default(),
form_associated: form_associated,
disable_internals: disable_internals,
disable_shadow: disable_shadow,
}
}
@ -676,7 +843,43 @@ pub fn upgrade_element(definition: Rc<CustomElementDefinition>, element: &Elemen
return;
}
// TODO Step 9: "If element is a form-associated custom element..."
// Step 9: handle with form-associated custom element
if let Some(html_element) = element.downcast::<HTMLElement>() {
if html_element.is_form_associated_custom_element() {
// We know this element is is form-associated, so we can use the implementation of
// `FormControl` for HTMLElement, which makes that assumption.
// Step 9.1: Reset the form owner of element
html_element.reset_form_owner();
if let Some(form) = html_element.form_owner() {
// Even though the tree hasn't structurally mutated,
// HTMLCollections need to be invalidated.
form.upcast::<Node>().rev_version();
// The spec tells us specifically to enqueue a formAssociated reaction
// here, but it also says to do that for resetting form owner in general,
// and we don't need two reactions.
}
// Either enabled_state or disabled_state needs to be set,
// and the possibility of a disabled fieldset ancestor needs
// to be accounted for. (In the spec, being disabled is
// a fact that's true or false about a node at a given time,
// not a flag that belongs to the node and is updated,
// so it doesn't describe this check as an action.)
element.check_disabled_attribute();
element.check_ancestors_disabled_state_for_form_control();
element.update_read_write_state_from_readonly_attribute();
// Step 9.2: If element is disabled, then enqueue a custom element callback reaction
// with element.
if element.disabled_state() {
ScriptThread::enqueue_callback_reaction(
element,
CallbackReaction::FormDisabled(true),
Some(definition.clone()),
)
}
}
}
// Step 10
element.set_custom_element_state(CustomElementState::Custom);
@ -796,6 +999,9 @@ pub enum CallbackReaction {
Disconnected,
Adopted(DomRoot<Document>, DomRoot<Document>),
AttributeChanged(LocalName, Option<DOMString>, Option<DOMString>, Namespace),
FormAssociated(Option<DomRoot<HTMLFormElement>>),
FormDisabled(bool),
FormReset,
}
/// <https://html.spec.whatwg.org/multipage/#processing-the-backup-element-queue>
@ -963,6 +1169,25 @@ impl CustomElementReactionStack {
args,
)
},
CallbackReaction::FormAssociated(form) => {
let args = vec![Heap::default()];
if let Some(form) = form {
args[0].set(ObjectValue(form.reflector().get_jsobject().get()));
} else {
args[0].set(NullValue());
}
(definition.callbacks.form_associated_callback.clone(), args)
},
CallbackReaction::FormDisabled(disabled) => {
let cx = GlobalScope::get_cx();
rooted!(in(*cx) let mut disabled_value = BooleanValue(disabled));
let args = vec![Heap::default()];
args[0].set(disabled_value.get());
(definition.callbacks.form_disabled_callback.clone(), args)
},
CallbackReaction::FormReset => {
(definition.callbacks.form_reset_callback.clone(), Vec::new())
},
};
// Step 3

View file

@ -1256,7 +1256,8 @@ impl Document {
debug!("{} on {:?}", mouse_event_type_string, node.debug_str());
// Prevent click event if form control element is disabled.
if let MouseEventType::Click = mouse_event_type {
if el.click_event_filter_by_disabled_state() {
// The click event is filtered by the disabled state.
if el.is_actually_disabled() {
return;
}
@ -3975,27 +3976,6 @@ impl Document {
}
}
impl Element {
fn click_event_filter_by_disabled_state(&self) -> bool {
let node = self.upcast::<Node>();
matches!(node.type_id(), NodeTypeId::Element(ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLButtonElement,
)) |
NodeTypeId::Element(ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLInputElement,
)) |
NodeTypeId::Element(ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLOptionElement,
)) |
NodeTypeId::Element(ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLSelectElement,
)) |
NodeTypeId::Element(ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLTextAreaElement,
)) if self.disabled_state())
}
}
impl ProfilerMetadataFactory for Document {
fn new_metadata(&self) -> Option<TimerMetadata> {
Some(TimerMetadata {

View file

@ -102,6 +102,7 @@ use crate::dom::document::{
use crate::dom::documentfragment::DocumentFragment;
use crate::dom::domrect::DOMRect;
use crate::dom::domtokenlist::DOMTokenList;
use crate::dom::elementinternals::ElementInternals;
use crate::dom::eventtarget::EventTarget;
use crate::dom::htmlanchorelement::HTMLAnchorElement;
use crate::dom::htmlbodyelement::{HTMLBodyElement, HTMLBodyElementLayoutHelpers};
@ -145,6 +146,7 @@ use crate::dom::servoparser::ServoParser;
use crate::dom::shadowroot::{IsUserAgentWidget, ShadowRoot};
use crate::dom::text::Text;
use crate::dom::validation::Validatable;
use crate::dom::validitystate::ValidationFlags;
use crate::dom::virtualmethods::{vtable_for, VirtualMethods};
use crate::dom::window::ReflowReason;
use crate::script_thread::ScriptThread;
@ -1419,6 +1421,12 @@ impl Element {
NodeTypeId::Element(ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLOptionElement,
)) => self.disabled_state(),
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLElement)) => {
self.downcast::<HTMLElement>()
.unwrap()
.is_form_associated_custom_element() &&
self.disabled_state()
},
// TODO:
// an optgroup element that has a disabled attribute
// a menuitem element that has a disabled attribute
@ -1857,10 +1865,17 @@ impl Element {
// https://w3c.github.io/DOM-Parsing/#parsing
pub fn parse_fragment(&self, markup: DOMString) -> Fallible<DomRoot<DocumentFragment>> {
// Steps 1-2.
let context_document = document_from_node(self);
// TODO(#11995): XML case.
let new_children = ServoParser::parse_html_fragment(self, markup);
// Step 3.
// See https://github.com/w3c/DOM-Parsing/issues/61.
let context_document = {
if let Some(template) = self.downcast::<HTMLTemplateElement>() {
template.Content().upcast::<Node>().owner_doc()
} else {
document_from_node(self)
}
};
let fragment = DocumentFragment::new(&context_document);
// Step 4.
for child in new_children {
@ -1973,6 +1988,24 @@ impl Element {
document.perform_focus_fixup_rule(self);
}
}
pub fn get_element_internals(&self) -> Option<DomRoot<ElementInternals>> {
self.rare_data()
.as_ref()?
.element_internals
.as_ref()
.map(|sr| DomRoot::from_ref(&**sr))
}
pub fn ensure_element_internals(&self) -> DomRoot<ElementInternals> {
let mut rare_data = self.ensure_rare_data();
DomRoot::from_ref(rare_data.element_internals.get_or_insert_with(|| {
let elem = self
.downcast::<HTMLElement>()
.expect("ensure_element_internals should only be called for an HTMLElement");
Dom::from_ref(&*ElementInternals::new(elem))
}))
}
}
impl ElementMethods for Element {
@ -3098,6 +3131,9 @@ impl VirtualMethods for Element {
self.super_type().unwrap().unbind_from_tree(context);
if let Some(f) = self.as_maybe_form_control() {
// TODO: The valid state of ancestors might be wrong if the form control element
// has a fieldset ancestor, for instance: `<form><fieldset><input>`,
// if `<input>` is unbound, `<form><fieldset>` should trigger a call to `update_validity()`.
f.unbind_form_control_from_tree();
}
@ -3543,6 +3579,38 @@ impl Element {
element
}
pub fn is_invalid(&self, needs_update: bool) -> bool {
if let Some(validatable) = self.as_maybe_validatable() {
if needs_update {
validatable
.validity_state()
.perform_validation_and_update(ValidationFlags::all());
}
return validatable.is_instance_validatable() && !validatable.satisfies_constraints();
}
if let Some(internals) = self.get_element_internals() {
return internals.is_invalid();
}
false
}
pub fn is_instance_validatable(&self) -> bool {
if let Some(validatable) = self.as_maybe_validatable() {
return validatable.is_instance_validatable();
}
if let Some(internals) = self.get_element_internals() {
return internals.is_instance_validatable();
}
false
}
pub fn init_state_for_internals(&self) {
self.set_enabled_state(true);
self.set_state(ElementState::VALID, true);
self.set_state(ElementState::INVALID, false);
}
pub fn click_in_progress(&self) -> bool {
self.upcast::<Node>().get_flag(NodeFlags::CLICK_IN_PROGRESS)
}
@ -3743,6 +3811,11 @@ impl Element {
self.set_disabled_state(has_disabled_attrib);
self.set_enabled_state(!has_disabled_attrib);
}
pub fn update_read_write_state_from_readonly_attribute(&self) {
let has_readonly_attribute = self.has_attribute(&local_name!("readonly"));
self.set_read_write_state(has_readonly_attribute);
}
}
#[derive(Clone, Copy)]

View file

@ -0,0 +1,366 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
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};
#[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,
/// If `attached` is false, we're using this to hold form-related state
/// on an element for which `attachInternals()` wasn't called yet; this is
/// necessary because it might have a form owner.
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)
}
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().map(|d| d.clone()));
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: name,
value: FormDatumValue::String(DOMString::from(string.to_string())),
});
},
SubmissionValue::File(file) => {
entry_list.push(FormDatum {
ty: DOMString::from("file"),
name: 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 for ElementInternals {
/// <https://html.spec.whatwg.org/multipage#dom-elementinternals-setformvalue>
fn SetFormValue(
&self,
value: Option<FileOrUSVStringOrFormData>,
maybe_state: Option<Option<FileOrUSVStringOrFormData>>,
) -> ErrorResult {
// Steps 1-2: If element is not a form-associated custom element, then throw a "NotSupportedError" DOMException
if !self.is_target_form_associated() {
return Err(Error::NotSupported);
}
// Step 3: Set target element's submission value
self.set_submission_value(value.as_ref().into());
match maybe_state {
// Step 4: If the state argument of the function is omitted, set element's state to its submission value
None => self.set_state(value.as_ref().into()),
// Steps 5-6: Otherwise, set element's state to state
Some(state) => self.set_state(state.as_ref().into()),
}
Ok(())
}
/// <https://html.spec.whatwg.org/multipage#dom-elementinternals-setvalidity>
fn SetValidity(
&self,
flags: &ValidityStateFlags,
message: Option<DOMString>,
anchor: Option<&HTMLElement>,
) -> ErrorResult {
// Steps 1-2: Check form-associated custom element
if !self.is_target_form_associated() {
return Err(Error::NotSupported);
}
// Step 3: If flags contains one or more true values and message is not given or is the empty
// string, then throw a TypeError.
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(),
));
}
// Step 4: For each entry `flag` → `value` of `flags`, set element's validity flag with the name
// `flag` to `value`.
self.validity_state().update_invalid_flags(bits);
self.validity_state().update_pseudo_classes();
// Step 5: Set element's validation message to the empty string if message is not given
// or all of element's validity flags are false, or to message otherwise.
if bits.is_empty() {
self.set_validation_message(DOMString::new());
} else {
self.set_validation_message(message.unwrap_or_else(|| DOMString::new()));
}
// Step 6: If element's customError validity flag is true, then set element's custom validity error
// message to element's validation message. Otherwise, set element's custom validity error
// message to the empty string.
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());
}
// Step 7: Set element's validation anchor to null if anchor is not given.
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(())
}
/// <https://html.spec.whatwg.org/multipage#dom-elementinternals-validationmessage>
fn GetValidationMessage(&self) -> Fallible<DOMString> {
// This check isn't in the spec but it's in WPT tests and it maintains
// consistency with other methods that do specify it
if !self.is_target_form_associated() {
return Err(Error::NotSupported);
}
Ok(self.validation_message.borrow().clone())
}
/// <https://html.spec.whatwg.org/multipage#dom-elementinternals-validity>
fn GetValidity(&self) -> Fallible<DomRoot<ValidityState>> {
if !self.is_target_form_associated() {
return Err(Error::NotSupported);
}
Ok(self.validity_state())
}
/// <https://html.spec.whatwg.org/multipage#dom-elementinternals-labels>
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,
)
}))
}
/// <https://html.spec.whatwg.org/multipage#dom-elementinternals-willvalidate>
fn GetWillValidate(&self) -> Fallible<bool> {
if !self.is_target_form_associated() {
return Err(Error::NotSupported);
}
Ok(self.is_instance_validatable())
}
/// <https://html.spec.whatwg.org/multipage#dom-elementinternals-form>
fn GetForm(&self) -> Fallible<Option<DomRoot<HTMLFormElement>>> {
if !self.is_target_form_associated() {
return Err(Error::NotSupported);
}
Ok(self.form_owner.get())
}
/// <https://html.spec.whatwg.org/multipage#dom-elementinternals-checkvalidity>
fn CheckValidity(&self) -> Fallible<bool> {
if !self.is_target_form_associated() {
return Err(Error::NotSupported);
}
Ok(self.check_validity())
}
/// <https://html.spec.whatwg.org/multipage#dom-elementinternals-reportvalidity>
fn ReportValidity(&self) -> Fallible<bool> {
if !self.is_target_form_associated() {
return Err(Error::NotSupported);
}
Ok(self.report_validity())
}
}
// Form-associated custom elements also need the Validatable trait.
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(),
)
})
}
/// <https://html.spec.whatwg.org/multipage#candidate-for-constraint-validation>
fn is_instance_validatable(&self) -> bool {
debug_assert!(self.is_target_form_associated());
if !self.target_element.is_submittable_element() {
return false;
}
// The form-associated custom element is barred from constraint validation,
// if the readonly attribute is specified, the element is disabled,
// or the element has a datalist element ancestor.
!self.as_element().read_write_state() &&
!self.as_element().disabled_state() &&
!is_barred_by_datalist_ancestor(self.target_element.upcast::<Node>())
}
}

View file

@ -22,28 +22,34 @@ use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMeth
use crate::dom::bindings::codegen::Bindings::HTMLLabelElementBinding::HTMLLabelElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::error::{Error, ErrorResult};
use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner};
use crate::dom::customelementregistry::CallbackReaction;
use crate::dom::document::{Document, FocusType};
use crate::dom::documentfragment::DocumentFragment;
use crate::dom::domstringmap::DOMStringMap;
use crate::dom::element::{AttributeMutation, Element};
use crate::dom::elementinternals::ElementInternals;
use crate::dom::event::Event;
use crate::dom::eventtarget::EventTarget;
use crate::dom::htmlbodyelement::HTMLBodyElement;
use crate::dom::htmlbrelement::HTMLBRElement;
use crate::dom::htmldetailselement::HTMLDetailsElement;
use crate::dom::htmlformelement::{FormControl, HTMLFormElement};
use crate::dom::htmlframesetelement::HTMLFrameSetElement;
use crate::dom::htmlhtmlelement::HTMLHtmlElement;
use crate::dom::htmlinputelement::{HTMLInputElement, InputType};
use crate::dom::htmllabelelement::HTMLLabelElement;
use crate::dom::htmltextareaelement::HTMLTextAreaElement;
use crate::dom::node::{document_from_node, window_from_node, Node, ShadowIncluding};
use crate::dom::node::{
document_from_node, window_from_node, BindContext, Node, ShadowIncluding, UnbindContext,
};
use crate::dom::text::Text;
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_thread::ScriptThread;
#[dom_struct]
pub struct HTMLElement {
@ -352,7 +358,7 @@ impl HTMLElementMethods for HTMLElement {
// https://html.spec.whatwg.org/multipage/#dom-click
fn Click(&self) {
let element = self.upcast::<Element>();
let element = self.as_element();
if element.disabled_state() {
return;
}
@ -377,7 +383,7 @@ impl HTMLElementMethods for HTMLElement {
// https://html.spec.whatwg.org/multipage/#dom-blur
fn Blur(&self) {
// TODO: Run the unfocusing steps.
if !self.upcast::<Element>().focus_state() {
if !self.as_element().focus_state() {
return;
}
// https://html.spec.whatwg.org/multipage/#unfocusing-steps
@ -446,7 +452,7 @@ impl HTMLElementMethods for HTMLElement {
fn InnerText(&self) -> DOMString {
let node = self.upcast::<Node>();
let window = window_from_node(node);
let element = self.upcast::<Element>();
let element = self.as_element();
// Step 1.
let element_not_rendered = !node.is_connected() || !element.has_css_layout_box();
@ -511,12 +517,12 @@ impl HTMLElementMethods for HTMLElement {
// https://html.spec.whatwg.org/multipage/#dom-translate
fn Translate(&self) -> bool {
self.upcast::<Element>().is_translate_enabled()
self.as_element().is_translate_enabled()
}
// https://html.spec.whatwg.org/multipage/#dom-translate
fn SetTranslate(&self, yesno: bool) {
self.upcast::<Element>().set_string_attribute(
self.as_element().set_string_attribute(
&html5ever::local_name!("translate"),
match yesno {
true => DOMString::from("yes"),
@ -528,7 +534,7 @@ impl HTMLElementMethods for HTMLElement {
// https://html.spec.whatwg.org/multipage/#dom-contenteditable
fn ContentEditable(&self) -> DOMString {
// TODO: https://github.com/servo/servo/issues/12776
self.upcast::<Element>()
self.as_element()
.get_attribute(&ns!(), &local_name!("contenteditable"))
.map(|attr| DOMString::from(&**attr.value()))
.unwrap_or_else(|| DOMString::from("inherit"))
@ -545,6 +551,46 @@ impl HTMLElementMethods for HTMLElement {
// TODO: https://github.com/servo/servo/issues/12776
false
}
/// <https://html.spec.whatwg.org/multipage#dom-attachinternals>
fn AttachInternals(&self) -> Fallible<DomRoot<ElementInternals>> {
let element = self.as_element();
// Step 1: If this's is value is not null, then throw a "NotSupportedError" DOMException
if element.get_is().is_some() {
return Err(Error::NotSupported);
}
// Step 2: Let definition be the result of looking up a custom element definition
// Note: the element can pass this check without yet being a custom
// element, as long as there is a registered definition
// that could upgrade it to one later.
let registry = document_from_node(self).window().CustomElements();
let definition = registry.lookup_definition(self.as_element().local_name(), None);
// Step 3: If definition is null, then throw an "NotSupportedError" DOMException
let definition = match definition {
Some(definition) => definition,
None => return Err(Error::NotSupported),
};
// Step 4: If definition's disable internals is true, then throw a "NotSupportedError" DOMException
if definition.disable_internals {
return Err(Error::NotSupported);
}
// Step 5: If this's attached internals is non-null, then throw an "NotSupportedError" DOMException
let internals = element.ensure_element_internals();
if internals.attached() {
return Err(Error::NotSupported);
}
if self.is_form_associated_custom_element() {
element.init_state_for_internals();
}
// Step 6-7: Set this's attached internals to a new ElementInternals instance
internals.set_attached();
Ok(internals)
}
}
fn append_text_node_to_fragment(document: &Document, fragment: &DocumentFragment, text: String) {
@ -620,14 +666,14 @@ impl HTMLElement {
{
return Err(Error::Syntax);
}
self.upcast::<Element>()
self.as_element()
.set_custom_attribute(to_snake_case(name), value)
}
pub fn get_custom_attr(&self, local_name: DOMString) -> Option<DOMString> {
// FIXME(ajeffrey): Convert directly from DOMString to LocalName
let local_name = LocalName::from(to_snake_case(local_name));
self.upcast::<Element>()
self.as_element()
.get_attribute(&ns!(), &local_name)
.map(|attr| {
DOMString::from(&**attr.value()) // FIXME(ajeffrey): Convert directly from AttrValue to DOMString
@ -637,11 +683,10 @@ impl HTMLElement {
pub fn delete_custom_attr(&self, local_name: DOMString) {
// FIXME(ajeffrey): Convert directly from DOMString to LocalName
let local_name = LocalName::from(to_snake_case(local_name));
self.upcast::<Element>()
.remove_attribute(&ns!(), &local_name);
self.as_element().remove_attribute(&ns!(), &local_name);
}
// https://html.spec.whatwg.org/multipage/#category-label
/// <https://html.spec.whatwg.org/multipage/#category-label>
pub fn is_labelable_element(&self) -> bool {
match self.upcast::<Node>().type_id() {
NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
@ -654,31 +699,54 @@ impl HTMLElement {
HTMLElementTypeId::HTMLProgressElement |
HTMLElementTypeId::HTMLSelectElement |
HTMLElementTypeId::HTMLTextAreaElement => true,
_ => false,
_ => self.is_form_associated_custom_element(),
},
_ => false,
}
}
// https://html.spec.whatwg.org/multipage/#category-listed
/// <https://html.spec.whatwg.org/multipage/#form-associated-custom-element>
pub fn is_form_associated_custom_element(&self) -> bool {
if let Some(definition) = self.as_element().get_custom_element_definition() {
definition.is_autonomous() && definition.form_associated
} else {
false
}
}
/// <https://html.spec.whatwg.org/multipage/#category-listed>
pub fn is_listed_element(&self) -> bool {
match self.upcast::<Node>().type_id() {
NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => matches!(
type_id,
NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
HTMLElementTypeId::HTMLButtonElement |
HTMLElementTypeId::HTMLFieldSetElement |
HTMLElementTypeId::HTMLInputElement |
HTMLElementTypeId::HTMLObjectElement |
HTMLElementTypeId::HTMLOutputElement |
HTMLElementTypeId::HTMLSelectElement |
HTMLElementTypeId::HTMLTextAreaElement
),
HTMLElementTypeId::HTMLTextAreaElement => true,
_ => self.is_form_associated_custom_element(),
},
_ => false,
}
}
/// <https://html.spec.whatwg.org/multipage/#category-submit>
pub fn is_submittable_element(&self) -> bool {
match self.upcast::<Node>().type_id() {
NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id {
HTMLElementTypeId::HTMLButtonElement |
HTMLElementTypeId::HTMLInputElement |
HTMLElementTypeId::HTMLSelectElement |
HTMLElementTypeId::HTMLTextAreaElement => true,
_ => self.is_form_associated_custom_element(),
},
_ => false,
}
}
pub fn supported_prop_names_custom_attr(&self) -> Vec<DOMString> {
let element = self.upcast::<Element>();
let element = self.as_element();
element
.attrs()
.iter()
@ -692,7 +760,7 @@ impl HTMLElement {
// https://html.spec.whatwg.org/multipage/#dom-lfe-labels
// This gets the nth label in tree order.
pub fn label_at(&self, index: u32) -> Option<DomRoot<Node>> {
let element = self.upcast::<Element>();
let element = self.as_element();
// Traverse entire tree for <label> elements that have
// this as their control.
@ -721,7 +789,7 @@ impl HTMLElement {
// This counts the labels of the element, to support NodeList::Length
pub fn labels_count(&self) -> u32 {
// see label_at comments about performance
let element = self.upcast::<Element>();
let element = self.as_element();
let root_element = element.root_element();
let root_node = root_element.upcast::<Node>();
root_node
@ -814,7 +882,7 @@ impl HTMLElement {
.child_elements()
.find(|el| el.local_name() == &local_name!("summary"));
match first_summary_element {
Some(first_summary) => &*first_summary == self.upcast::<Element>(),
Some(first_summary) => &*first_summary == self.as_element(),
None => false,
}
}
@ -822,11 +890,12 @@ impl HTMLElement {
impl VirtualMethods for HTMLElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<Element>() as &dyn VirtualMethods)
Some(self.as_element() as &dyn VirtualMethods)
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
self.super_type().unwrap().attribute_mutated(attr, mutation);
let element = self.as_element();
match (attr.local_name(), mutation) {
(name, AttributeMutation::Set(_)) if name.starts_with("on") => {
let evtarget = self.upcast::<EventTarget>();
@ -839,10 +908,95 @@ impl VirtualMethods for HTMLElement {
DOMString::from(&**attr.value()),
);
},
(&local_name!("form"), mutation) if self.is_form_associated_custom_element() => {
self.form_attribute_mutated(mutation);
},
// Adding a "disabled" attribute disables an enabled form element.
(&local_name!("disabled"), AttributeMutation::Set(_))
if self.is_form_associated_custom_element() && element.enabled_state() =>
{
element.set_disabled_state(true);
element.set_enabled_state(false);
ScriptThread::enqueue_callback_reaction(
element,
CallbackReaction::FormDisabled(true),
None,
);
},
// Removing the "disabled" attribute may enable a disabled
// form element, but a fieldset ancestor may keep it disabled.
(&local_name!("disabled"), AttributeMutation::Removed)
if self.is_form_associated_custom_element() && element.disabled_state() =>
{
element.set_disabled_state(false);
element.set_enabled_state(true);
element.check_ancestors_disabled_state_for_form_control();
if element.enabled_state() {
ScriptThread::enqueue_callback_reaction(
element,
CallbackReaction::FormDisabled(false),
None,
);
}
},
(&local_name!("readonly"), mutation) if self.is_form_associated_custom_element() => {
match mutation {
AttributeMutation::Set(_) => {
element.set_read_write_state(true);
},
AttributeMutation::Removed => {
element.set_read_write_state(false);
},
}
},
_ => {},
}
}
fn bind_to_tree(&self, context: &BindContext) {
if let Some(ref super_type) = self.super_type() {
super_type.bind_to_tree(context);
}
let element = self.as_element();
element.update_sequentially_focusable_status();
// Binding to a tree can disable a form control if one of the new
// ancestors is a fieldset.
if self.is_form_associated_custom_element() && element.enabled_state() {
element.check_ancestors_disabled_state_for_form_control();
if element.disabled_state() {
ScriptThread::enqueue_callback_reaction(
element,
CallbackReaction::FormDisabled(true),
None,
);
}
}
}
fn unbind_from_tree(&self, context: &UnbindContext) {
if let Some(ref super_type) = self.super_type() {
super_type.unbind_from_tree(context);
}
// Unbinding from a tree might enable a form control, if a
// fieldset ancestor is the only reason it was disabled.
// (The fact that it's enabled doesn't do much while it's
// disconnected, but it is an observable fact to keep track of.)
let element = self.as_element();
if self.is_form_associated_custom_element() && element.disabled_state() {
element.check_disabled_attribute();
element.check_ancestors_disabled_state_for_form_control();
if element.enabled_state() {
ScriptThread::enqueue_callback_reaction(
element,
CallbackReaction::FormDisabled(false),
None,
);
}
}
}
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
match *name {
local_name!("itemprop") => AttrValue::from_serialized_tokenlist(value.into()),
@ -869,3 +1023,35 @@ impl Activatable for HTMLElement {
self.summary_activation_behavior();
}
}
// Form-associated custom elements are the same interface type as
// normal HTMLElements, so HTMLElement needs to have the FormControl trait
// even though it's usually more specific trait implementations, like the
// HTMLInputElement one, that we really want. (Alternately we could put
// the FormControl trait on ElementInternals, but that raises lifetime issues.)
impl FormControl for HTMLElement {
fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
debug_assert!(self.is_form_associated_custom_element());
self.as_element()
.get_element_internals()
.and_then(|e| e.form_owner())
}
fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
debug_assert!(self.is_form_associated_custom_element());
self.as_element()
.ensure_element_internals()
.set_form_owner(form);
}
fn to_element<'a>(&'a self) -> &'a Element {
debug_assert!(self.is_form_associated_custom_element());
self.as_element()
}
fn is_listed(&self) -> bool {
debug_assert!(self.is_form_associated_custom_element());
true
}
// TODO candidate_for_validation, satisfies_constraints traits
}

View file

@ -14,6 +14,7 @@ use crate::dom::bindings::codegen::Bindings::HTMLFieldSetElementBinding::HTMLFie
use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::customelementregistry::CallbackReaction;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element};
use crate::dom::htmlcollection::{CollectionFilter, HTMLCollection};
@ -24,6 +25,7 @@ use crate::dom::node::{window_from_node, Node, ShadowIncluding};
use crate::dom::validation::Validatable;
use crate::dom::validitystate::ValidityState;
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_thread::ScriptThread;
#[dom_struct]
pub struct HTMLFieldSetElement {
@ -71,14 +73,7 @@ impl HTMLFieldSetElement {
.upcast::<Node>()
.traverse_preorder(ShadowIncluding::No)
.flat_map(DomRoot::downcast::<Element>)
.any(|element| {
if let Some(validatable) = element.as_maybe_validatable() {
validatable.is_instance_validatable() &&
!validatable.validity_state().invalid_flags().is_empty()
} else {
false
}
});
.any(|element| element.is_invalid(false));
self.upcast::<Element>()
.set_state(ElementState::VALID, !has_invalid_child);
@ -169,9 +164,9 @@ impl VirtualMethods for HTMLFieldSetElement {
AttributeMutation::Removed => false,
};
let node = self.upcast::<Node>();
let el = self.upcast::<Element>();
el.set_disabled_state(disabled_state);
el.set_enabled_state(!disabled_state);
let element = self.upcast::<Element>();
element.set_disabled_state(disabled_state);
element.set_enabled_state(!disabled_state);
let mut found_legend = false;
let children = node.children().filter(|node| {
if found_legend {
@ -186,37 +181,64 @@ impl VirtualMethods for HTMLFieldSetElement {
let fields = children.flat_map(|child| {
child
.traverse_preorder(ShadowIncluding::No)
.filter(|descendant| {
matches!(
descendant.type_id(),
.filter(|descendant| match descendant.type_id() {
NodeTypeId::Element(ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLButtonElement,
)) | NodeTypeId::Element(ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLInputElement,
)) | NodeTypeId::Element(ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLSelectElement,
)) | NodeTypeId::Element(ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLButtonElement |
HTMLElementTypeId::HTMLInputElement |
HTMLElementTypeId::HTMLSelectElement |
HTMLElementTypeId::HTMLTextAreaElement,
))
)
)) => true,
NodeTypeId::Element(ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLElement,
)) => descendant
.downcast::<HTMLElement>()
.unwrap()
.is_form_associated_custom_element(),
_ => false,
})
});
if disabled_state {
for field in fields {
let el = field.downcast::<Element>().unwrap();
el.set_disabled_state(true);
el.set_enabled_state(false);
el.update_sequentially_focusable_status();
let element = field.downcast::<Element>().unwrap();
if element.enabled_state() {
element.set_disabled_state(true);
element.set_enabled_state(false);
if element
.downcast::<HTMLElement>()
.map_or(false, |h| h.is_form_associated_custom_element())
{
ScriptThread::enqueue_callback_reaction(
element,
CallbackReaction::FormDisabled(true),
None,
);
}
}
element.update_sequentially_focusable_status();
}
} else {
for field in fields {
let el = field.downcast::<Element>().unwrap();
el.check_disabled_attribute();
el.check_ancestors_disabled_state_for_form_control();
el.update_sequentially_focusable_status();
let element = field.downcast::<Element>().unwrap();
if element.disabled_state() {
element.check_disabled_attribute();
element.check_ancestors_disabled_state_for_form_control();
// Fire callback only if this has actually enabled the custom element
if element.enabled_state() &&
element
.downcast::<HTMLElement>()
.map_or(false, |h| h.is_form_associated_custom_element())
{
ScriptThread::enqueue_callback_reaction(
element,
CallbackReaction::FormDisabled(false),
None,
);
}
}
el.update_sequentially_focusable_status();
element.update_sequentially_focusable_status();
}
}
element.update_sequentially_focusable_status();
},
local_name!("form") => {
self.form_attribute_mutated(mutation);

View file

@ -46,6 +46,7 @@ use crate::dom::bindings::reflector::DomObject;
use crate::dom::bindings::root::{Dom, DomOnceCell, DomRoot, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::blob::Blob;
use crate::dom::customelementregistry::CallbackReaction;
use crate::dom::document::Document;
use crate::dom::domtokenlist::DOMTokenList;
use crate::dom::element::{AttributeMutation, Element};
@ -77,9 +78,9 @@ use crate::dom::node::{
use crate::dom::nodelist::{NodeList, RadioListMode};
use crate::dom::radionodelist::RadioNodeList;
use crate::dom::submitevent::SubmitEvent;
use crate::dom::validitystate::ValidationFlags;
use crate::dom::virtualmethods::VirtualMethods;
use crate::dom::window::Window;
use crate::script_thread::ScriptThread;
use crate::task_source::TaskSource;
#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
@ -363,6 +364,14 @@ impl HTMLFormElementMethods for HTMLFormElement {
HTMLElementTypeId::HTMLTextAreaElement => {
elem.downcast::<HTMLTextAreaElement>().unwrap().form_owner()
},
HTMLElementTypeId::HTMLElement => {
let html_element = elem.downcast::<HTMLElement>().unwrap();
if html_element.is_form_associated_custom_element() {
html_element.form_owner()
} else {
return false;
}
},
_ => {
debug_assert!(!elem
.downcast::<HTMLElement>()
@ -673,14 +682,7 @@ impl HTMLFormElement {
pub fn update_validity(&self) {
let controls = self.controls.borrow();
let is_any_invalid = controls
.iter()
.filter_map(|control| control.as_maybe_validatable())
.any(|validatable| {
validatable.is_instance_validatable() &&
!validatable.validity_state().invalid_flags().is_empty()
});
let is_any_invalid = controls.iter().any(|control| control.is_invalid(false));
self.upcast::<Element>()
.set_state(ElementState::VALID, !is_any_invalid);
@ -1027,6 +1029,8 @@ impl HTMLFormElement {
}
}
// If it's form-associated and has a validation anchor, point the
// user there instead of the element itself.
// Step 4
Err(())
}
@ -1039,20 +1043,11 @@ impl HTMLFormElement {
let invalid_controls = controls
.iter()
.filter_map(|field| {
if let Some(el) = field.downcast::<Element>() {
let validatable = match el.as_maybe_validatable() {
Some(v) => v,
None => return None,
};
validatable
.validity_state()
.perform_validation_and_update(ValidationFlags::all());
if !validatable.is_instance_validatable() ||
validatable.validity_state().invalid_flags().is_empty()
{
None
if let Some(element) = field.downcast::<Element>() {
if element.is_invalid(true) {
Some(DomRoot::from_ref(element))
} else {
Some(DomRoot::from_ref(el))
None
}
} else {
None
@ -1135,6 +1130,15 @@ impl HTMLFormElement {
});
}
},
HTMLElementTypeId::HTMLElement => {
let custom = child.downcast::<HTMLElement>().unwrap();
if custom.is_form_associated_custom_element() {
// https://html.spec.whatwg.org/multipage/#face-entry-construction
let internals = custom.upcast::<Element>().ensure_element_internals();
internals.perform_entry_construction(&mut data_set);
// Otherwise no form value has been set so there is nothing to do.
}
},
_ => (),
}
}
@ -1287,6 +1291,16 @@ impl HTMLFormElement {
)) => {
child.downcast::<HTMLOutputElement>().unwrap().reset();
},
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLElement)) => {
let html_element = child.downcast::<HTMLElement>().unwrap();
if html_element.is_form_associated_custom_element() {
ScriptThread::enqueue_callback_reaction(
html_element.upcast::<Element>(),
CallbackReaction::FormReset,
None,
)
}
},
_ => {},
}
}
@ -1550,6 +1564,19 @@ pub trait FormControl: DomObject {
if let Some(ref new_owner) = new_owner {
new_owner.add_control(self);
}
// https://html.spec.whatwg.org/multipage/#custom-element-reactions:reset-the-form-owner
if let Some(html_elem) = elem.downcast::<HTMLElement>() {
if html_elem.is_form_associated_custom_element() {
ScriptThread::enqueue_callback_reaction(
elem,
CallbackReaction::FormAssociated(match new_owner {
None => None,
Some(ref form) => Some(DomRoot::from_ref(&**form)),
}),
None,
)
}
}
self.set_form_owner(new_owner.as_deref());
}
}
@ -1745,7 +1772,13 @@ impl FormControlElementHelpers for Element {
NodeTypeId::Element(ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLTextAreaElement,
)) => Some(self.downcast::<HTMLTextAreaElement>().unwrap() as &dyn FormControl),
_ => None,
_ => self.downcast::<HTMLElement>().and_then(|elem| {
if elem.is_form_associated_custom_element() {
Some(elem as &dyn FormControl)
} else {
None
}
}),
}
}
}

View file

@ -299,6 +299,7 @@ pub mod domstringmap;
pub mod domtokenlist;
pub mod dynamicmoduleowner;
pub mod element;
pub mod elementinternals;
pub mod errorevent;
pub mod event;
pub mod eventsource;

View file

@ -738,7 +738,7 @@ impl Node {
parent.ancestors().any(|ancestor| &*ancestor == self)
}
fn is_shadow_including_inclusive_ancestor_of(&self, node: &Node) -> bool {
pub fn is_shadow_including_inclusive_ancestor_of(&self, node: &Node) -> bool {
node.inclusive_ancestors(ShadowIncluding::Yes)
.any(|ancestor| &*ancestor == self)
}
@ -2092,19 +2092,17 @@ impl Node {
.traverse_preorder(ShadowIncluding::Yes)
.filter_map(DomRoot::downcast::<Element>)
{
// Step 7.7.2.
if descendant.is_connected() {
// Step 7.7.2, whatwg/dom#833
if descendant.get_custom_element_definition().is_some() {
// Step 7.7.2.1.
if descendant.is_connected() {
ScriptThread::enqueue_callback_reaction(
&descendant,
CallbackReaction::Connected,
None,
);
} else {
// Step 7.7.2.2.
try_upgrade_element(&descendant);
}
} else {
try_upgrade_element(&*descendant);
}
}
}

View file

@ -11,6 +11,7 @@ use crate::dom::bindings::root::Dom;
use crate::dom::customelementregistry::{
CustomElementDefinition, CustomElementReaction, CustomElementState,
};
use crate::dom::elementinternals::ElementInternals;
use crate::dom::mutationobserver::RegisteredObserver;
use crate::dom::node::UniqueId;
use crate::dom::shadowroot::ShadowRoot;
@ -54,4 +55,6 @@ pub struct ElementRareData {
/// The client rect reported by layout.
#[no_trace]
pub client_rect: Option<LayoutValue<Rect<i32>>>,
/// <https://html.spec.whatwg.org/multipage#elementinternals>
pub element_internals: Option<Dom<ElementInternals>>,
}

View file

@ -17,10 +17,10 @@ use crate::dom::validitystate::{ValidationFlags, ValidityState};
pub trait Validatable {
fn as_element(&self) -> &Element;
// https://html.spec.whatwg.org/multipage/#dom-cva-validity
/// <https://html.spec.whatwg.org/multipage/#dom-cva-validity>
fn validity_state(&self) -> DomRoot<ValidityState>;
// https://html.spec.whatwg.org/multipage/#candidate-for-constraint-validation
/// <https://html.spec.whatwg.org/multipage/#candidate-for-constraint-validation>
fn is_instance_validatable(&self) -> bool;
// Check if element satisfies its constraints, excluding custom errors
@ -28,9 +28,14 @@ pub trait Validatable {
ValidationFlags::empty()
}
// https://html.spec.whatwg.org/multipage/#check-validity-steps
/// <https://html.spec.whatwg.org/multipage/#concept-fv-valid>
fn satisfies_constraints(&self) -> bool {
self.validity_state().invalid_flags().is_empty()
}
/// <https://html.spec.whatwg.org/multipage/#check-validity-steps>
fn check_validity(&self) -> bool {
if self.is_instance_validatable() && !self.validity_state().invalid_flags().is_empty() {
if self.is_instance_validatable() && !self.satisfies_constraints() {
self.as_element()
.upcast::<EventTarget>()
.fire_cancelable_event(atom!("invalid"));
@ -40,15 +45,14 @@ pub trait Validatable {
}
}
// https://html.spec.whatwg.org/multipage/#report-validity-steps
/// <https://html.spec.whatwg.org/multipage/#report-validity-steps>
fn report_validity(&self) -> bool {
// Step 1.
if !self.is_instance_validatable() {
return true;
}
let flags = self.validity_state().invalid_flags();
if flags.is_empty() {
if self.satisfies_constraints() {
return true;
}
@ -60,6 +64,7 @@ pub trait Validatable {
// Step 1.2.
if !event.DefaultPrevented() {
let flags = self.validity_state().invalid_flags();
println!(
"Validation error: {}",
validation_message_for_flags(&self.validity_state(), flags)
@ -73,7 +78,7 @@ pub trait Validatable {
false
}
// https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage
/// <https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage>
fn validation_message(&self) -> DOMString {
if self.is_instance_validatable() {
let flags = self.validity_state().invalid_flags();
@ -84,7 +89,7 @@ pub trait Validatable {
}
}
// https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation
/// <https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation>
pub fn is_barred_by_datalist_ancestor(elem: &Node) -> bool {
elem.upcast::<Node>()
.ancestors()

View file

@ -10,6 +10,7 @@ use dom_struct::dom_struct;
use itertools::Itertools;
use style_traits::dom::ElementState;
use super::bindings::codegen::Bindings::ElementInternalsBinding::ValidityStateFlags;
use crate::dom::bindings::cell::{DomRefCell, Ref};
use crate::dom::bindings::codegen::Bindings::ValidityStateBinding::ValidityStateMethods;
use crate::dom::bindings::inheritance::Castable;
@ -129,13 +130,16 @@ impl ValidityState {
self.update_pseudo_classes();
}
pub fn update_invalid_flags(&self, update_flags: ValidationFlags) {
self.invalid_flags.set(update_flags);
}
pub fn invalid_flags(&self) -> ValidationFlags {
self.invalid_flags.get()
}
fn update_pseudo_classes(&self) {
if let Some(validatable) = self.element.as_maybe_validatable() {
if validatable.is_instance_validatable() {
pub fn update_pseudo_classes(&self) {
if self.element.is_instance_validatable() {
let is_valid = self.invalid_flags.get().is_empty();
self.element.set_state(ElementState::VALID, is_valid);
self.element.set_state(ElementState::INVALID, !is_valid);
@ -143,7 +147,6 @@ impl ValidityState {
self.element.set_state(ElementState::VALID, false);
self.element.set_state(ElementState::INVALID, false);
}
}
if let Some(form_control) = self.element.as_maybe_form_control() {
if let Some(form_owner) = form_control.form_owner() {
@ -225,3 +228,40 @@ impl ValidityStateMethods for ValidityState {
self.invalid_flags().is_empty()
}
}
impl From<&ValidityStateFlags> for ValidationFlags {
fn from(flags: &ValidityStateFlags) -> Self {
let mut bits = ValidationFlags::empty();
if flags.valueMissing {
bits |= ValidationFlags::VALUE_MISSING;
}
if flags.typeMismatch {
bits |= ValidationFlags::TYPE_MISMATCH;
}
if flags.patternMismatch {
bits |= ValidationFlags::PATTERN_MISMATCH;
}
if flags.tooLong {
bits |= ValidationFlags::TOO_LONG;
}
if flags.tooShort {
bits |= ValidationFlags::TOO_SHORT;
}
if flags.rangeUnderflow {
bits |= ValidationFlags::RANGE_UNDERFLOW;
}
if flags.rangeOverflow {
bits |= ValidationFlags::RANGE_OVERFLOW;
}
if flags.stepMismatch {
bits |= ValidationFlags::STEP_MISMATCH;
}
if flags.badInput {
bits |= ValidationFlags::BAD_INPUT;
}
if flags.customError {
bits |= ValidationFlags::CUSTOM_ERROR;
}
bits
}
}

View file

@ -0,0 +1,41 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
// https://html.spec.whatwg.org/multipage/#elementinternals
[Exposed=Window]
interface ElementInternals {
// Form-associated custom elements
[Throws] undefined setFormValue((File or USVString or FormData)? value,
optional (File or USVString or FormData)? state);
[Throws] readonly attribute HTMLFormElement? form;
// flags shouldn't be optional here, #25704
[Throws] undefined setValidity(optional ValidityStateFlags flags = {},
optional DOMString message,
optional HTMLElement anchor);
[Throws] readonly attribute boolean willValidate;
[Throws] readonly attribute ValidityState validity;
[Throws] readonly attribute DOMString validationMessage;
[Throws] boolean checkValidity();
[Throws] boolean reportValidity();
[Throws] readonly attribute NodeList labels;
};
// https://html.spec.whatwg.org/multipage/#elementinternals
dictionary ValidityStateFlags {
boolean valueMissing = false;
boolean typeMismatch = false;
boolean patternMismatch = false;
boolean tooLong = false;
boolean tooShort = false;
boolean rangeUnderflow = false;
boolean rangeOverflow = false;
boolean stepMismatch = false;
boolean badInput = false;
boolean customError = false;
};

View file

@ -50,6 +50,8 @@ interface HTMLElement : Element {
attribute [LegacyNullToEmptyString] DOMString innerText;
[Throws] ElementInternals attachInternals();
// command API
// readonly attribute DOMString? commandType;
// readonly attribute DOMString? commandLabel;

View file

@ -1,4 +1,3 @@
[state-in-has.html]
expected: ERROR
[Test :has() invalidation with :state() pseudo-classes]
expected: FAIL

View file

@ -2,42 +2,3 @@
type: testharness
[customElements.define must upgrade elements in the shadow-including tree order]
expected: FAIL
[customElements.define must get "observedAttributes" property on the constructor prototype when "attributeChangedCallback" is present]
expected: FAIL
[customElements.define must rethrow an exception thrown while retrieving Symbol.iterator on disabledFeatures]
expected: FAIL
[customElements.define must rethrow an exception thrown while getting disabledFeatures on the constructor prototype]
expected: FAIL
[customElements.define must rethrow an exception thrown while iterating over disabledFeatures to sequence<DOMString>]
expected: FAIL
[customElements.define must rethrow an exception thrown while converting the value of disabledFeatures to sequence<DOMString>]
expected: FAIL
[customElements.define must rethrow an exception thrown while getting formAssociated on the constructor prototype]
expected: FAIL
[customElements.define must rethrow an exception thrown while getting additional formAssociated callbacks on the constructor prototype]
expected: FAIL
[customElements.define must get four additional callbacks on the prototype if formAssociated is converted to true]
expected: FAIL
[customElements.define must get "prototype", "disabledFeatures", and "formAssociated" property of the constructor]
expected: FAIL
[customElements.define must not throw when defining another custom element in a different global object during Get(constructor, "prototype")]
expected: FAIL
[customElements.getName must return null when the registry does not contain an entry with the given constructor]
expected: FAIL
[customElements.getName returns the name of the entry with the given constructor when there is a matching entry.]
expected: FAIL
[customElements.getName returns the name of the entry with the given customized built in constructor when there is a matching entry.]
expected: FAIL

View file

@ -1,2 +1,138 @@
[ElementInternals-accessibility.html]
expected: ERROR
[role is defined in ElementInternals]
expected: FAIL
[ariaActiveDescendantElement is defined in ElementInternals]
expected: FAIL
[ariaAtomic is defined in ElementInternals]
expected: FAIL
[ariaAutoComplete is defined in ElementInternals]
expected: FAIL
[ariaBusy is defined in ElementInternals]
expected: FAIL
[ariaChecked is defined in ElementInternals]
expected: FAIL
[ariaColCount is defined in ElementInternals]
expected: FAIL
[ariaColIndex is defined in ElementInternals]
expected: FAIL
[ariaColSpan is defined in ElementInternals]
expected: FAIL
[ariaControlsElements is defined in ElementInternals]
expected: FAIL
[ariaCurrent is defined in ElementInternals]
expected: FAIL
[ariaDescribedByElements is defined in ElementInternals]
expected: FAIL
[ariaDetailsElements is defined in ElementInternals]
expected: FAIL
[ariaDisabled is defined in ElementInternals]
expected: FAIL
[ariaErrorMessageElements is defined in ElementInternals]
expected: FAIL
[ariaExpanded is defined in ElementInternals]
expected: FAIL
[ariaFlowToElements is defined in ElementInternals]
expected: FAIL
[ariaHasPopup is defined in ElementInternals]
expected: FAIL
[ariaHidden is defined in ElementInternals]
expected: FAIL
[ariaInvalid is defined in ElementInternals]
expected: FAIL
[ariaKeyShortcuts is defined in ElementInternals]
expected: FAIL
[ariaLabel is defined in ElementInternals]
expected: FAIL
[ariaLabelledByElements is defined in ElementInternals]
expected: FAIL
[ariaLevel is defined in ElementInternals]
expected: FAIL
[ariaLive is defined in ElementInternals]
expected: FAIL
[ariaModal is defined in ElementInternals]
expected: FAIL
[ariaMultiLine is defined in ElementInternals]
expected: FAIL
[ariaMultiSelectable is defined in ElementInternals]
expected: FAIL
[ariaOrientation is defined in ElementInternals]
expected: FAIL
[ariaOwnsElements is defined in ElementInternals]
expected: FAIL
[ariaPlaceholder is defined in ElementInternals]
expected: FAIL
[ariaPosInSet is defined in ElementInternals]
expected: FAIL
[ariaPressed is defined in ElementInternals]
expected: FAIL
[ariaReadOnly is defined in ElementInternals]
expected: FAIL
[ariaRelevant is defined in ElementInternals]
expected: FAIL
[ariaRequired is defined in ElementInternals]
expected: FAIL
[ariaRoleDescription is defined in ElementInternals]
expected: FAIL
[ariaRowCount is defined in ElementInternals]
expected: FAIL
[ariaRowIndex is defined in ElementInternals]
expected: FAIL
[ariaRowSpan is defined in ElementInternals]
expected: FAIL
[ariaSelected is defined in ElementInternals]
expected: FAIL
[ariaSort is defined in ElementInternals]
expected: FAIL
[ariaValueMax is defined in ElementInternals]
expected: FAIL
[ariaValueMin is defined in ElementInternals]
expected: FAIL
[ariaValueNow is defined in ElementInternals]
expected: FAIL
[ariaValueText is defined in ElementInternals]
expected: FAIL

View file

@ -1,16 +0,0 @@
[HTMLElement-attachInternals.html]
[Successful attachInternals() and the second call.]
expected: FAIL
[attachInternals() throws a NotSupportedError if it is called for a customized built-in element]
expected: FAIL
[If a custom element definition for the local name of the element has disable internals flag, throw a NotSupportedError]
expected: FAIL
[If a custom element definition for the local name of the element doesn't exist, throw an InvalidStateError]
expected: FAIL
[If a custom element definition for the local name of the element doesn't exist, throw an NotSupportedError]
expected: FAIL

View file

@ -1,2 +1,3 @@
[element-internals-aria-element-reflection.html]
expected: ERROR
[Getting previously-unset ARIA element reflection properties on ElementInternals should return null.]
expected: FAIL

View file

@ -1,8 +1,5 @@
[element-internals-shadowroot.html]
expected: ERROR
[ElementInternals cannot be called after constructor calls it, create case]
expected: FAIL
[ElementInternals cannot be called before constructor, upgrade case]
expected: FAIL
@ -15,8 +12,5 @@
[ElementInternals *can* be called after constructor, upgrade case]
expected: FAIL
[ElementInternals disabled by disabledFeatures]
expected: FAIL
[ElementInternals.shadowRoot doesn't reveal pre-attached closed shadowRoot]
expected: FAIL

View file

@ -1,4 +0,0 @@
[ElementInternals-NotSupportedError.html]
[Form-related operations and attributes should throw NotSupportedErrors for non-form-associated custom elements.]
expected: FAIL

View file

@ -1,2 +0,0 @@
[ElementInternals-form.html]
expected: ERROR

View file

@ -1,10 +0,0 @@
[ElementInternals-labels.html]
expected: ERROR
[LABEL click]
expected: FAIL
[LABEL association]
expected: FAIL
[ElementInternals.labels should throw NotSupportedError if the target element is not a form-associated custom element]
expected: FAIL

View file

@ -1,2 +0,0 @@
[ElementInternals-setFormValue-nullish-value.html]
expected: ERROR

View file

@ -1,5 +1,4 @@
[ElementInternals-setFormValue.html]
expected: ERROR
[Single value - Non-empty name exists]
expected: FAIL
@ -219,8 +218,5 @@
[Newline normalization - \\n\\r in FormData filename (formdata)]
expected: FAIL
[ElementInternals.setFormValue() should throw NotSupportedError if the target element is not a form-associated custom element]
expected: FAIL
[Single value - name is missing]
expected: FAIL

View file

@ -1,37 +1,6 @@
[ElementInternals-validation.html]
expected: ERROR
[validity and setValidity()]
expected: FAIL
[reportValidity()]
expected: FAIL
["anchor" argument of setValidity()]
expected: FAIL
[checkValidity()]
expected: FAIL
[Custom control affects validation at the owner form]
expected: FAIL
[willValidate]
expected: FAIL
[Custom control affects :valid :invalid for FORM and FIELDSET]
expected: FAIL
[willValidate after upgrade]
expected: FAIL
[willValidate should throw NotSupportedError if the target element is not a form-associated custom element]
expected: FAIL
[checkValidity() should throw NotSupportedError if the target element is not a form-associated custom element]
expected: FAIL
[reportValidity() should throw NotSupportedError if the target element is not a form-associated custom element]
expected: FAIL
[willValidate after upgrade (document.createElement)]
expected: FAIL

View file

@ -1,3 +0,0 @@
[fieldset-elements.html]
[Form associated custom elements should work with fieldset.elements]
expected: FAIL

View file

@ -1,3 +0,0 @@
[focusability.html]
[Focusability of form-associated custom elements]
expected: FAIL

View file

@ -1,5 +1,3 @@
[form-associated-callback.html]
expected: ERROR
[formAssociatedCallback, and form IDL attribute of ElementInternals]
[Updating "id" attribute of form element]
expected: FAIL

View file

@ -1,28 +1,3 @@
[form-disabled-callback.html]
expected: ERROR
[Upgrading an element with disabled content attribute]
expected: FAIL
[Disabled attribute affects focus-capability]
expected: FAIL
[Relationship with FIELDSET]
expected: FAIL
[A disabled form-associated custom element should not submit an entry for it]
expected: FAIL
[Adding/removing disabled content attribute]
expected: FAIL
[Toggling "disabled" attribute on a custom element inside disabled <fieldset> does not trigger a callback]
expected: FAIL
[Toggling "disabled" attribute on a <fieldset> does not trigger a callback on disabled custom element descendant]
expected: FAIL
[Callback triggered during a clone/append operation, with disabled state provided by ancestor]
expected: FAIL
[Callback triggered during a clone operation, with disabled state provided by ancestor]
expected: FAIL

View file

@ -1,9 +0,0 @@
[form-elements-namedItem.html]
[Form associated custom elements should work with document.forms.elements.namedItem()]
expected: FAIL
[Form associated custom elements should work with document.forms.elements.namedItem() after upgrading]
expected: FAIL
[Form associated custom elements should work with document.forms.elements.namedItem() after updating the name attribute]
expected: FAIL

View file

@ -1,10 +0,0 @@
[form-reset-callback.html]
[form.reset(): formResetCallback is called after reset of the last built-in form control and before the next statement.]
expected: FAIL
[Clicking a reset button invokes formResetCallback in a microtask]
expected: FAIL
[form.reset() should trigger formResetCallback]
expected: FAIL

View file

@ -1,14 +0,0 @@
[HTMLTableElement.html]
type: testharness
[caption on HTMLTableElement must enqueue connectedCallback when inserting a custom element]
expected: FAIL
[tHead on HTMLTableElement must enqueue connectedCallback when inserting a custom element]
expected: FAIL
[tFoot on HTMLTableElement must enqueue connectedCallback when inserting a custom element]
expected: FAIL
[Custom Elements: CEReactions on HTMLTableElement interface]
expected: FAIL

View file

@ -1,5 +0,0 @@
[Range.html]
type: testharness
[createContextualFragment on Range must construct a custom element]
expected: FAIL

View file

@ -1,2 +1,3 @@
[custom-state-set-strong-ref.html]
expected: ERROR
[customstateset doesn't crash after GC on detached node]
expected: FAIL

View file

@ -1,2 +1,10 @@
[state-css-selector-nth-of.html]
expected: ERROR
[state selector has influence on nth-of when state is applied]
expected: FAIL
[state selector only applies on given ident]
expected: NOTRUN
[style is invalided on clear()]
expected: NOTRUN

View file

@ -1,2 +1,30 @@
[state-css-selector.html]
expected: ERROR
[state selector has no influence when state is not applied]
expected: FAIL
[state selector has no influence on sibling selectors when not applied]
expected: FAIL
[state selector has influence when state is applied]
expected: FAIL
[state selector influences siblings when state is applied]
expected: FAIL
[state selector influences has() when state is applied]
expected: FAIL
[state selector only applies on given ident]
expected: FAIL
[state selector only applies to siblings on given ident]
expected: FAIL
[state selector only applies to has() on given ident]
expected: FAIL
[states added multiple times counts as one]
expected: FAIL
[style is invalided on clear()]
expected: FAIL

View file

@ -32,9 +32,6 @@
[OffscreenCanvasRenderingContext2D interface: operation measureText(DOMString)]
expected: FAIL
[ElementInternals interface: existence and properties of interface object]
expected: FAIL
[CanvasRenderingContext2D interface: document.createElement("canvas").getContext("2d") must inherit property "imageSmoothingQuality" with the proper type]
expected: FAIL
@ -209,9 +206,6 @@
[OffscreenCanvasRenderingContext2D interface: operation beginPath()]
expected: FAIL
[ElementInternals interface object length]
expected: FAIL
[SVGElement interface: attribute onchange]
expected: FAIL
@ -221,9 +215,6 @@
[CanvasRenderingContext2D interface: operation strokeText(DOMString, unrestricted double, unrestricted double, unrestricted double)]
expected: FAIL
[ElementInternals interface: operation reportValidity()]
expected: FAIL
[SharedWorker interface object name]
expected: FAIL
@ -239,9 +230,6 @@
[OffscreenCanvasRenderingContext2D interface: attribute shadowColor]
expected: FAIL
[ElementInternals interface: operation checkValidity()]
expected: FAIL
[SVGElement interface: attribute tabIndex]
expected: FAIL
@ -650,12 +638,6 @@
[OffscreenCanvasRenderingContext2D interface object name]
expected: FAIL
[ElementInternals interface object name]
expected: FAIL
[ElementInternals interface: attribute labels]
expected: FAIL
[SVGAElement interface: attribute host]
expected: FAIL
@ -689,9 +671,6 @@
[ApplicationCache interface: attribute oncached]
expected: FAIL
[ElementInternals interface: existence and properties of interface prototype object]
expected: FAIL
[OffscreenCanvasRenderingContext2D interface: operation commit()]
expected: FAIL
@ -737,9 +716,6 @@
[SVGSVGElement interface: attribute onrejectionhandled]
expected: FAIL
[ElementInternals interface: attribute form]
expected: FAIL
[ApplicationCache interface: attribute onerror]
expected: FAIL
@ -818,12 +794,6 @@
[OffscreenCanvasRenderingContext2D interface: operation putImageData(ImageData, long, long, long, long, long, long)]
expected: FAIL
[ElementInternals interface: existence and properties of interface prototype object's @@unscopables property]
expected: FAIL
[ElementInternals interface: existence and properties of interface prototype object's "constructor" property]
expected: FAIL
[SharedWorker interface object length]
expected: FAIL
@ -836,9 +806,6 @@
[SVGElement interface: attribute onmouseleave]
expected: FAIL
[ElementInternals interface: attribute willValidate]
expected: FAIL
[OffscreenCanvasRenderingContext2D interface: operation moveTo(unrestricted double, unrestricted double)]
expected: FAIL
@ -1187,9 +1154,6 @@
[OffscreenCanvas interface: operation convertToBlob(ImageEncodeOptions)]
expected: FAIL
[ElementInternals interface: attribute validationMessage]
expected: FAIL
[SVGElement interface: attribute onformdata]
expected: FAIL
@ -1220,9 +1184,6 @@
[SVGElement interface: attribute ondrag]
expected: FAIL
[ElementInternals interface: attribute validity]
expected: FAIL
[SVGElement interface: attribute autofocus]
expected: FAIL
@ -1334,9 +1295,6 @@
[OffscreenCanvasRenderingContext2D interface: operation setTransform(optional DOMMatrix2DInit)]
expected: FAIL
[ElementInternals interface: operation setFormValue((File or USVString or FormData)?, optional (File or USVString or FormData)?)]
expected: FAIL
[Navigator interface: operation registerProtocolHandler(DOMString, USVString)]
expected: FAIL
@ -1346,9 +1304,6 @@
[Navigator interface: calling registerProtocolHandler(DOMString, USVString) on window.navigator with too few arguments must throw TypeError]
expected: FAIL
[ElementInternals interface: operation setValidity(optional ValidityStateFlags, optional DOMString, optional HTMLElement)]
expected: FAIL
[ElementInternals interface: attribute shadowRoot]
expected: FAIL
@ -2862,9 +2817,6 @@
[HTMLInputElement interface: createInput("image") must inherit property "useMap" with the proper type]
expected: FAIL
[HTMLElement interface: operation attachInternals()]
expected: FAIL
[HTMLVideoElement interface: attribute playsInline]
expected: FAIL
@ -4050,9 +4002,6 @@
[HTMLMarqueeElement interface: attribute direction]
expected: FAIL
[HTMLElement interface: document.createElement("noscript") must inherit property "attachInternals()" with the proper type]
expected: FAIL
[HTMLFormElement interface: document.createElement("form") must inherit property "requestSubmit(HTMLElement)" with the proper type]
expected: FAIL

View file

@ -563171,7 +563171,7 @@
]
],
"form-associated-callback.html": [
"7feec50fef89103326e404efc2287767ee0981fb",
"329c4d75931896e7e6a2a8e4499b6ff67fc22cab",
[
null,
{}

View file

@ -1,4 +1,3 @@
[state-in-has.html]
expected: ERROR
[Test :has() invalidation with :state() pseudo-classes]
expected: FAIL

View file

@ -1,33 +1,3 @@
[CustomElementRegistry.html]
[customElements.define must get "observedAttributes" property on the constructor prototype when "attributeChangedCallback" is present]
expected: FAIL
[customElements.define must rethrow an exception thrown while getting formAssociated on the constructor prototype]
expected: FAIL
[customElements.define must rethrow an exception thrown while getting additional formAssociated callbacks on the constructor prototype]
expected: FAIL
[customElements.define must rethrow an exception thrown while retrieving Symbol.iterator on disabledFeatures]
expected: FAIL
[customElements.define must upgrade elements in the shadow-including tree order]
expected: FAIL
[customElements.define must get four additional callbacks on the prototype if formAssociated is converted to true]
expected: FAIL
[customElements.define must rethrow an exception thrown while getting disabledFeatures on the constructor prototype]
expected: FAIL
[customElements.define must rethrow an exception thrown while iterating over disabledFeatures to sequence<DOMString>]
expected: FAIL
[customElements.define must get "prototype", "disabledFeatures", and "formAssociated" property of the constructor]
expected: FAIL
[customElements.define must rethrow an exception thrown while converting the value of disabledFeatures to sequence<DOMString>]
expected: FAIL
[customElements.define must not throw when defining another custom element in a different global object during Get(constructor, "prototype")]
expected: FAIL

View file

@ -1,2 +1,144 @@
[ElementInternals-accessibility.html]
expected: ERROR
[ElementInternals-accessibility]
expected: FAIL
[ariaDescribedByElements is defined in ElementInternals]
expected: FAIL
[ariaFlowToElements is defined in ElementInternals]
expected: FAIL
[role is defined in ElementInternals]
expected: FAIL
[ariaPosInSet is defined in ElementInternals]
expected: FAIL
[ariaValueMax is defined in ElementInternals]
expected: FAIL
[ariaDisabled is defined in ElementInternals]
expected: FAIL
[ariaRoleDescription is defined in ElementInternals]
expected: FAIL
[ariaValueMin is defined in ElementInternals]
expected: FAIL
[ariaOrientation is defined in ElementInternals]
expected: FAIL
[ariaLabel is defined in ElementInternals]
expected: FAIL
[ariaMultiSelectable is defined in ElementInternals]
expected: FAIL
[ariaExpanded is defined in ElementInternals]
expected: FAIL
[ariaDetailsElements is defined in ElementInternals]
expected: FAIL
[ariaColIndex is defined in ElementInternals]
expected: FAIL
[ariaRowCount is defined in ElementInternals]
expected: FAIL
[ariaBusy is defined in ElementInternals]
expected: FAIL
[ariaChecked is defined in ElementInternals]
expected: FAIL
[ariaControlsElements is defined in ElementInternals]
expected: FAIL
[ariaAutoComplete is defined in ElementInternals]
expected: FAIL
[ariaHasPopup is defined in ElementInternals]
expected: FAIL
[ariaLevel is defined in ElementInternals]
expected: FAIL
[ariaAtomic is defined in ElementInternals]
expected: FAIL
[ariaErrorMessageElement is defined in ElementInternals]
expected: FAIL
[ariaHidden is defined in ElementInternals]
expected: FAIL
[ariaSort is defined in ElementInternals]
expected: FAIL
[ariaRowSpan is defined in ElementInternals]
expected: FAIL
[ariaRowIndex is defined in ElementInternals]
expected: FAIL
[ariaPlaceholder is defined in ElementInternals]
expected: FAIL
[ariaReadOnly is defined in ElementInternals]
expected: FAIL
[ariaOwnsElements is defined in ElementInternals]
expected: FAIL
[ariaActiveDescendantElement is defined in ElementInternals]
expected: FAIL
[ariaColCount is defined in ElementInternals]
expected: FAIL
[ariaRelevant is defined in ElementInternals]
expected: FAIL
[ariaLive is defined in ElementInternals]
expected: FAIL
[ariaValueNow is defined in ElementInternals]
expected: FAIL
[ariaRequired is defined in ElementInternals]
expected: FAIL
[ariaValueText is defined in ElementInternals]
expected: FAIL
[ariaLabelledByElements is defined in ElementInternals]
expected: FAIL
[ariaColSpan is defined in ElementInternals]
expected: FAIL
[ariaModal is defined in ElementInternals]
expected: FAIL
[ariaSelected is defined in ElementInternals]
expected: FAIL
[ariaKeyShortcuts is defined in ElementInternals]
expected: FAIL
[ariaCurrent is defined in ElementInternals]
expected: FAIL
[ariaMultiLine is defined in ElementInternals]
expected: FAIL
[ariaPressed is defined in ElementInternals]
expected: FAIL
[ariaErrorMessageElements is defined in ElementInternals]
expected: FAIL
[ariaInvalid is defined in ElementInternals]
expected: FAIL

View file

@ -1,12 +0,0 @@
[HTMLElement-attachInternals.html]
[Successful attachInternals() and the second call.]
expected: FAIL
[attachInternals() throws a NotSupportedError if it is called for a customized built-in element]
expected: FAIL
[If a custom element definition for the local name of the element doesn't exist, throw an NotSupportedError]
expected: FAIL
[If a custom element definition for the local name of the element has disable internals flag, throw a NotSupportedError]
expected: FAIL

View file

@ -1,2 +1,3 @@
[element-internals-aria-element-reflection.html]
expected: ERROR
[Getting previously-unset ARIA element reflection properties on ElementInternals should return null.]
expected: FAIL

View file

@ -12,11 +12,5 @@
[ElementInternals *can* be called after constructor, upgrade case]
expected: FAIL
[ElementInternals cannot be called after constructor calls it, create case]
expected: FAIL
[ElementInternals disabled by disabledFeatures]
expected: FAIL
[ElementInternals.shadowRoot doesn't reveal pre-attached closed shadowRoot]
expected: FAIL

View file

@ -1,3 +0,0 @@
[ElementInternals-NotSupportedError.html]
[Form-related operations and attributes should throw NotSupportedErrors for non-form-associated custom elements.]
expected: FAIL

View file

@ -1,2 +0,0 @@
[ElementInternals-form.html]
expected: ERROR

View file

@ -1,10 +0,0 @@
[ElementInternals-labels.html]
expected: ERROR
[LABEL association]
expected: FAIL
[LABEL click]
expected: FAIL
[ElementInternals.labels should throw NotSupportedError if the target element is not a form-associated custom element]
expected: FAIL

View file

@ -1,2 +0,0 @@
[ElementInternals-setFormValue-nullish-value.html]
expected: ERROR

View file

@ -1,5 +1,4 @@
[ElementInternals-setFormValue.html]
expected: ERROR
[Newline normalization - \\n\\r in value]
expected: FAIL
@ -221,6 +220,3 @@
[Newline normalization - \\n\\r in FormData filename (formdata)]
expected: FAIL
[ElementInternals.setFormValue() should throw NotSupportedError if the target element is not a form-associated custom element]
expected: FAIL

View file

@ -1,37 +1,6 @@
[ElementInternals-validation.html]
expected: ERROR
[willValidate]
expected: FAIL
[willValidate after upgrade]
expected: FAIL
[willValidate should throw NotSupportedError if the target element is not a form-associated custom element]
expected: FAIL
[validity and setValidity()]
expected: FAIL
["anchor" argument of setValidity()]
expected: FAIL
[checkValidity() should throw NotSupportedError if the target element is not a form-associated custom element]
expected: FAIL
[checkValidity()]
expected: FAIL
[reportValidity() should throw NotSupportedError if the target element is not a form-associated custom element]
expected: FAIL
[reportValidity()]
expected: FAIL
[Custom control affects validation at the owner form]
expected: FAIL
[Custom control affects :valid :invalid for FORM and FIELDSET]
expected: FAIL
[willValidate after upgrade (document.createElement)]
expected: FAIL

View file

@ -1,3 +0,0 @@
[fieldset-elements.html]
[Form associated custom elements should work with fieldset.elements]
expected: FAIL

View file

@ -1,3 +0,0 @@
[focusability.html]
[Focusability of form-associated custom elements]
expected: FAIL

View file

@ -1,2 +1,3 @@
[form-associated-callback.html]
expected: ERROR
[Updating "id" attribute of form element]
expected: FAIL

View file

@ -1,28 +1,3 @@
[form-disabled-callback.html]
expected: ERROR
[Adding/removing disabled content attribute]
expected: FAIL
[Relationship with FIELDSET]
expected: FAIL
[A disabled form-associated custom element should not submit an entry for it]
expected: FAIL
[Disabled attribute affects focus-capability]
expected: FAIL
[Upgrading an element with disabled content attribute]
expected: FAIL
[Toggling "disabled" attribute on a custom element inside disabled <fieldset> does not trigger a callback]
expected: FAIL
[Toggling "disabled" attribute on a <fieldset> does not trigger a callback on disabled custom element descendant]
expected: FAIL
[Callback triggered during a clone/append operation, with disabled state provided by ancestor]
expected: FAIL
[Callback triggered during a clone operation, with disabled state provided by ancestor]
expected: FAIL

View file

@ -1,9 +0,0 @@
[form-elements-namedItem.html]
[Form associated custom elements should work with document.forms.elements.namedItem()]
expected: FAIL
[Form associated custom elements should work with document.forms.elements.namedItem() after upgrading]
expected: FAIL
[Form associated custom elements should work with document.forms.elements.namedItem() after updating the name attribute]
expected: FAIL

View file

@ -1,9 +0,0 @@
[form-reset-callback.html]
[form.reset() should trigger formResetCallback]
expected: FAIL
[form.reset(): formResetCallback is called after reset of the last built-in form control and before the next statement.]
expected: FAIL
[Clicking a reset button invokes formResetCallback in a microtask]
expected: FAIL

View file

@ -1,9 +0,0 @@
[HTMLTableElement.html]
[caption on HTMLTableElement must enqueue connectedCallback when inserting a custom element]
expected: FAIL
[tHead on HTMLTableElement must enqueue connectedCallback when inserting a custom element]
expected: FAIL
[tFoot on HTMLTableElement must enqueue connectedCallback when inserting a custom element]
expected: FAIL

View file

@ -1,3 +0,0 @@
[Range.html]
[createContextualFragment on Range must construct a custom element]
expected: FAIL

View file

@ -1,2 +1,3 @@
[custom-state-set-strong-ref.html]
expected: ERROR
[customstateset doesn't crash after GC on detached node]
expected: FAIL

View file

@ -1,2 +1,10 @@
[state-css-selector-nth-of.html]
expected: ERROR
[state selector has influence on nth-of when state is applied]
expected: FAIL
[state selector only applies on given ident]
expected: NOTRUN
[style is invalided on clear()]
expected: NOTRUN

View file

@ -1,2 +1,30 @@
[state-css-selector.html]
expected: ERROR
[state selector has no influence when state is not applied]
expected: FAIL
[state selector has no influence on sibling selectors when not applied]
expected: FAIL
[state selector has influence when state is applied]
expected: FAIL
[state selector influences siblings when state is applied]
expected: FAIL
[state selector influences has() when state is applied]
expected: FAIL
[state selector only applies on given ident]
expected: FAIL
[state selector only applies to siblings on given ident]
expected: FAIL
[state selector only applies to has() on given ident]
expected: FAIL
[states added multiple times counts as one]
expected: FAIL
[style is invalided on clear()]
expected: FAIL

View file

@ -62,12 +62,6 @@
[SVGElement interface: attribute onended]
expected: FAIL
[ElementInternals interface object length]
expected: FAIL
[ElementInternals interface: operation reportValidity()]
expected: FAIL
[SVGSVGElement interface: attribute onbeforeprint]
expected: FAIL
@ -92,9 +86,6 @@
[OffscreenCanvas interface: operation convertToBlob(optional ImageEncodeOptions)]
expected: FAIL
[ElementInternals interface: operation setValidity(ValidityStateFlags, optional DOMString, optional HTMLElement)]
expected: FAIL
[DOMStringList interface: calling item(unsigned long) on location.ancestorOrigins with too few arguments must throw TypeError]
expected: FAIL
@ -167,9 +158,6 @@
[OffscreenCanvasRenderingContext2D interface: attribute strokeStyle]
expected: FAIL
[ElementInternals interface: attribute labels]
expected: FAIL
[SVGElement interface: attribute oncopy]
expected: FAIL
@ -323,9 +311,6 @@
[CanvasRenderingContext2D interface: document.createElement("canvas").getContext("2d") must inherit property "getLineDash()" with the proper type]
expected: FAIL
[ElementInternals interface: existence and properties of interface object]
expected: FAIL
[OffscreenCanvasRenderingContext2D interface: attribute imageSmoothingEnabled]
expected: FAIL
@ -389,9 +374,6 @@
[Navigator interface: calling unregisterProtocolHandler(DOMString, USVString) on window.navigator with too few arguments must throw TypeError]
expected: FAIL
[ElementInternals interface: existence and properties of interface prototype object's "constructor" property]
expected: FAIL
[SVGElement interface: attribute ondurationchange]
expected: FAIL
@ -461,9 +443,6 @@
[Path2D interface: operation rect(unrestricted double, unrestricted double, unrestricted double, unrestricted double)]
expected: FAIL
[ElementInternals interface: attribute validationMessage]
expected: FAIL
[DataTransfer interface: attribute dropEffect]
expected: FAIL
@ -806,15 +785,9 @@
[DragEvent interface: existence and properties of interface object]
expected: FAIL
[ElementInternals interface: existence and properties of interface prototype object's @@unscopables property]
expected: FAIL
[Path2D interface: operation arc(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, optional boolean)]
expected: FAIL
[ElementInternals interface: attribute willValidate]
expected: FAIL
[OffscreenCanvas interface object length]
expected: FAIL
@ -851,9 +824,6 @@
[Navigator interface: window.navigator must inherit property "registerProtocolHandler(DOMString, USVString)" with the proper type]
expected: FAIL
[ElementInternals interface: existence and properties of interface prototype object]
expected: FAIL
[OffscreenCanvas interface object name]
expected: FAIL
@ -959,9 +929,6 @@
[SVGSVGElement interface: attribute onmessage]
expected: FAIL
[ElementInternals interface object name]
expected: FAIL
[SVGElement interface: attribute onmouseup]
expected: FAIL
@ -971,9 +938,6 @@
[OffscreenCanvasRenderingContext2D interface: operation isPointInPath(unrestricted double, unrestricted double, optional CanvasFillRule)]
expected: FAIL
[ElementInternals interface: attribute validity]
expected: FAIL
[OffscreenCanvasRenderingContext2D interface: operation restore()]
expected: FAIL
@ -983,9 +947,6 @@
[DataTransferItem interface: existence and properties of interface object]
expected: FAIL
[ElementInternals interface: operation checkValidity()]
expected: FAIL
[Location interface: stringifier]
expected: FAIL
@ -1112,9 +1073,6 @@
[SVGSVGElement interface: attribute onrejectionhandled]
expected: FAIL
[ElementInternals interface: attribute form]
expected: FAIL
[SVGElement interface: attribute onratechange]
expected: FAIL
@ -1205,15 +1163,6 @@
[ApplicationCache interface: attribute onobsolete]
expected: FAIL
[ElementInternals interface: operation setFormValue((File or USVString or FormData)?, optional (File or USVString or FormData)?)]
expected: FAIL
[ElementInternals interface: operation setValidity(optional ValidityStateFlags, optional DOMString, optional HTMLElement)]
expected: FAIL
[ElementInternals interface: attribute shadowRoot]
expected: FAIL
[Worklet interface object length]
expected: FAIL
@ -1838,9 +1787,6 @@
[Element interface: operation setHTMLUnsafe(DOMString)]
expected: FAIL
[ElementInternals interface: attribute states]
expected: FAIL
[CustomStateSet interface: existence and properties of interface object]
expected: FAIL
@ -2021,6 +1967,12 @@
[Element interface: operation setHTMLUnsafe(HTMLString)]
expected: FAIL
[ElementInternals interface: attribute shadowRoot]
expected: FAIL
[ElementInternals interface: attribute states]
expected: FAIL
[idlharness.https.html?include=(Document|Window)]
[Document interface: documentWithHandlers must inherit property "queryCommandEnabled(DOMString)" with the proper type]
@ -2718,9 +2670,6 @@
[HTMLInputElement interface: createInput("image") must inherit property "useMap" with the proper type]
expected: FAIL
[HTMLElement interface: operation attachInternals()]
expected: FAIL
[HTMLVideoElement interface: attribute playsInline]
expected: FAIL
@ -3879,9 +3828,6 @@
[HTMLMarqueeElement interface: attribute direction]
expected: FAIL
[HTMLElement interface: document.createElement("noscript") must inherit property "attachInternals()" with the proper type]
expected: FAIL
[HTMLInputElement interface: createInput("radio") must inherit property "width" with the proper type]
expected: FAIL

View file

@ -12615,6 +12615,13 @@
{}
]
],
"ElementInternals-setFormValue.html": [
"d553dd1a90262700323a714359df60906ac057cf",
[
null,
{}
]
],
"Event.html": [
"3947b286122ee47f2f874232763ceeff3c2b661e",
[
@ -13410,7 +13417,7 @@
]
],
"interfaces.html": [
"5461ff50d836f872fdf09defd98575d08fda0061",
"063495d90f464f161c52a8d099a298e914b9b082",
[
null,
{}

View file

@ -0,0 +1,137 @@
<!DOCTYPE html>
<!-- Like custom-elements/form-associated/ElementInternals-setFormValue,
but without any ordering assumptions about iframe loads and promise
microtasks, to test the form submissions despite Servo's racey iframe.
Includes web-platform-tests/wpt#21747 change to initial values. -->
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="container1"></div>
<div id="container2"></div>
<div id="container3"></div>
<div id="container4"></div>
<div id="container5"></div>
<div id="container6"></div>
<script>
class MyControl extends HTMLElement {
static get formAssociated() { return true; }
constructor() {
super();
this.internals_ = this.attachInternals();
this.value_ = '';
}
get value() {
return this.value_;
}
set value(v) {
this.internals_.setFormValue(v);
this.value_ = v;
}
setValues(nameValues) {
const formData = new FormData();
for (let p of nameValues) {
formData.append(p[0], p[1]);
}
this.internals_.setFormValue(formData);
}
}
customElements.define('my-control', MyControl);
const $ = document.querySelector.bind(document);
function submitPromise(t, n) {
return new Promise((resolve, reject) => {
const iframe = $('#container'+n+' iframe');
iframe.onload = () => {
if(iframe.contentWindow.location.href == "about:blank") { return; }
resolve(iframe.contentWindow.location.search);
}
iframe.onerror = () => reject(new Error('iframe onerror fired'));
$('#container'+n+' form').submit();
});
}
promise_test(t => {
$('#container1').innerHTML = '<form action="/common/blank.html" target="if1">' +
'<input name=name-pd1 value="value-pd1">' +
'<my-control></my-control>' +
'</form>' +
'<iframe name="if1"></iframe>';
return submitPromise(t,1).then(query => {
assert_equals(query, '?name-pd1=value-pd1');
});
}, 'Single value - name is missing');
promise_test(t => {
$('#container2').innerHTML = '<form action="/common/blank.html" target="if2">' +
'<input name=name-pd1 value="value-pd1">' +
'<my-control name=""></my-control>' +
'<input name=name-pd2 value="value-pd2">' +
'</form>' +
'<iframe name="if2"></iframe>';
$('#container2 my-control').value = 'value-ce1';
return submitPromise(t,2).then(query => {
assert_equals(query, '?name-pd1=value-pd1&name-pd2=value-pd2');
});
}, 'Single value - empty name exists');
promise_test(t => {
$('#container3').innerHTML = '<form action="/common/blank.html" target="if3" accept-charset=utf-8>' +
'<input name=name-pd1 value="value-pd1">' +
'<my-control name="name-ce1"></my-control>' +
'<my-control name="name-usv"></my-control>' +
'<my-control name="name-file"></my-control>' +
'</form>' +
'<iframe name="if3"></iframe>';
const USV_INPUT = 'abc\uDC00\uD800def';
const USV_OUTPUT = 'abc\uFFFD\uFFFDdef';
const FILE_NAME = 'test_file.txt';
$('#container3 [name=name-usv]').value = USV_INPUT;
$('#container3 [name=name-file]').value = new File(['file content'], FILE_NAME);
return submitPromise(t,3).then(query => {
assert_equals(query, `?name-pd1=value-pd1&name-usv=${encodeURIComponent(USV_OUTPUT)}&name-file=${FILE_NAME}`);
});
}, 'Single value - Non-empty name exists');
promise_test(t => {
$('#container4').innerHTML = '<form action="/common/blank.html" target="if4">' +
'<input name=name-pd1 value="value-pd1">' +
'<my-control name="name-ce1"></my-control>' +
'<my-control name="name-ce2"></my-control>' +
'</form>' +
'<iframe name="if4"></iframe>';
$('#container4 my-control').value = null;
return submitPromise(t,4).then(query => {
assert_equals(query, '?name-pd1=value-pd1');
});
}, 'Null value should submit nothing');
promise_test(t => {
$('#container5').innerHTML = '<form action="/common/blank.html" target="if5">' +
'<input name=name-pd1 value="value-pd1">' +
'<my-control name=name-ce1></my-control>' +
'</form>' +
'<iframe name="if5"></iframe>';
$('#container5 my-control').value = 'value-ce1';
$('#container5 my-control').setValues([]);
$('#container5 my-control').setValues([['sub1', 'subvalue1'],
['sub2', 'subvalue2'],
['sub2', 'subvalue3']]);
return submitPromise(t,5).then(query => {
assert_equals(query, '?name-pd1=value-pd1&sub1=subvalue1&sub2=subvalue2&sub2=subvalue3');
});
}, 'Multiple values - name content attribute is ignored');
promise_test(t => {
$('#container6').innerHTML = '<form action="/common/blank.html" target="if6">' +
'<input name=name-pd1 value="value-pd1">' +
'<my-control name=name-ce1></my-control>' +
'</form>' +
'<iframe name="if6"></iframe>';
$('#container6 my-control').value = 'value-ce1';
$('#container6 my-control').setValues([]);
return submitPromise(t,6).then(query => {
assert_equals(query, '?name-pd1=value-pd1');
});
}, 'setFormValue with an empty FormData should submit nothing');
</script>

View file

@ -77,6 +77,7 @@ test_interfaces([
"DOMStringMap",
"DOMTokenList",
"Element",
"ElementInternals",
"ErrorEvent",
"Event",
"EventSource",

View file

@ -122,7 +122,7 @@ test(() => {
}
}
customElements.define('will-be-defined', WillBeDefined);
customElements.upgrade(container);
customElements.upgrade($('#container'));
controls = $('#form1').elements;
assert_equals(controls.length, 3, 'form.elements.length');