diff options
8 files changed, 231 insertions, 22 deletions
diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 9dad0da9cd3..8f019f9a37f 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -145,13 +145,15 @@ use style::values::specified::Length; use tendril::fmt::UTF8; use tendril::stream::LossyDecoder; use tendril::{StrTendril, TendrilSink}; -use time::{Duration, Timespec}; +use time::{Duration, Timespec, Tm}; use uuid::Uuid; use webgpu::{WebGPU, WebGPUAdapter, WebGPUDevice}; use webrender_api::{DocumentId, ImageKey}; use webvr_traits::{WebVRGamepadData, WebVRGamepadHand, WebVRGamepadState}; use webxr_api::SwapChainId as WebXRSwapChainId; +unsafe_no_jsmanaged_fields!(Tm); + /// A trait to allow tracing (only) DOM objects. pub unsafe trait JSTraceable { /// Trace `self`. diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index f4135ac3e0a..959bca74b54 100755..100644 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -64,6 +64,13 @@ use std::cell::Cell; use style::attr::AttrValue; use style::str::split_html_space_chars; +use crate::dom::bindings::codegen::UnionTypes::RadioNodeListOrElement; +use crate::dom::radionodelist::RadioNodeList; +use std::collections::HashMap; +use time::{now, Duration, Tm}; + +use crate::dom::bindings::codegen::Bindings::NodeBinding::{NodeConstants, NodeMethods}; + #[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] pub struct GenerationId(u32); @@ -76,6 +83,7 @@ pub struct HTMLFormElement { elements: DomOnceCell<HTMLFormControlsCollection>, generation_id: Cell<GenerationId>, controls: DomRefCell<Vec<Dom<Element>>>, + past_names_map: DomRefCell<HashMap<DOMString, (Dom<Element>, Tm)>>, } impl HTMLFormElement { @@ -91,6 +99,7 @@ impl HTMLFormElement { elements: Default::default(), generation_id: Cell::new(GenerationId(0)), controls: DomRefCell::new(Vec::new()), + past_names_map: DomRefCell::new(HashMap::new()), } } @@ -253,6 +262,219 @@ impl HTMLFormElementMethods for HTMLFormElement { let elements = self.Elements(); elements.IndexedGetter(index) } + + // https://html.spec.whatwg.org/multipage/#the-form-element%3Adetermine-the-value-of-a-named-property + fn NamedGetter(&self, name: DOMString) -> Option<RadioNodeListOrElement> { + let mut candidates: Vec<DomRoot<Node>> = Vec::new(); + + let controls = self.controls.borrow(); + // Step 1 + for child in controls.iter() { + if child + .downcast::<HTMLElement>() + .map_or(false, |c| c.is_listed_element()) + { + if (child.has_attribute(&local_name!("id")) && + child.get_string_attribute(&local_name!("id")) == name) || + (child.has_attribute(&local_name!("name")) && + child.get_string_attribute(&local_name!("name")) == name) + { + candidates.push(DomRoot::from_ref(&*child.upcast::<Node>())); + } + } + } + // Step 2 + if candidates.len() == 0 { + for child in controls.iter() { + if child.is::<HTMLImageElement>() { + if (child.has_attribute(&local_name!("id")) && + child.get_string_attribute(&local_name!("id")) == name) || + (child.has_attribute(&local_name!("name")) && + child.get_string_attribute(&local_name!("name")) == name) + { + candidates.push(DomRoot::from_ref(&*child.upcast::<Node>())); + } + } + } + } + + let mut past_names_map = self.past_names_map.borrow_mut(); + + // Step 3 + if candidates.len() == 0 { + if past_names_map.contains_key(&name) { + return Some(RadioNodeListOrElement::Element(DomRoot::from_ref( + &*past_names_map.get(&name).unwrap().0, + ))); + } + return None; + } + + // Step 4 + if candidates.len() > 1 { + let window = window_from_node(self); + + return Some(RadioNodeListOrElement::RadioNodeList( + RadioNodeList::new_simple_list(&window, candidates.into_iter()), + )); + } + + // Step 5 + let element_node = &candidates[0]; + past_names_map.insert( + name, + ( + Dom::from_ref(&*element_node.downcast::<Element>().unwrap()), + now(), + ), + ); + + // Step 6 + return Some(RadioNodeListOrElement::Element(DomRoot::from_ref( + &*element_node.downcast::<Element>().unwrap(), + ))); + } + + // https://html.spec.whatwg.org/multipage/#the-form-element:supported-property-names + fn SupportedPropertyNames(&self) -> Vec<DOMString> { + // Step 1 + #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] + enum SourcedNameSource { + Id, + Name, + Past(Duration), + } + + impl SourcedNameSource { + fn is_past(&self) -> bool { + match self { + SourcedNameSource::Past(..) => true, + _ => false, + } + } + } + + struct SourcedName { + name: DOMString, + element: DomRoot<Element>, + source: SourcedNameSource, + } + + let mut sourcedNamesVec: Vec<SourcedName> = Vec::new(); + + let controls = self.controls.borrow(); + + // Step 2 + for child in controls.iter() { + if child + .downcast::<HTMLElement>() + .map_or(false, |c| c.is_listed_element()) + { + if child.has_attribute(&local_name!("id")) { + let entry = SourcedName { + name: child.get_string_attribute(&local_name!("id")), + element: DomRoot::from_ref(&*child), + source: SourcedNameSource::Id, + }; + sourcedNamesVec.push(entry); + } + if child.has_attribute(&local_name!("name")) { + let entry = SourcedName { + name: child.get_string_attribute(&local_name!("name")), + element: DomRoot::from_ref(&*child), + source: SourcedNameSource::Name, + }; + sourcedNamesVec.push(entry); + } + } + } + + // Step 3 + for child in controls.iter() { + if child.is::<HTMLImageElement>() { + if child.has_attribute(&local_name!("id")) { + let entry = SourcedName { + name: child.get_string_attribute(&local_name!("id")), + element: DomRoot::from_ref(&*child), + source: SourcedNameSource::Id, + }; + sourcedNamesVec.push(entry); + } + if child.has_attribute(&local_name!("name")) { + let entry = SourcedName { + name: child.get_string_attribute(&local_name!("name")), + element: DomRoot::from_ref(&*child), + source: SourcedNameSource::Name, + }; + sourcedNamesVec.push(entry); + } + } + } + + // Step 4 + let past_names_map = self.past_names_map.borrow(); + for (key, val) in past_names_map.iter() { + let entry = SourcedName { + name: key.clone(), + element: DomRoot::from_ref(&*val.0), + source: SourcedNameSource::Past(now() - val.1), // calculate difference now()-val.1 to find age + }; + sourcedNamesVec.push(entry); + } + + // Step 5 + // TODO need to sort as per spec. + // if a.CompareDocumentPosition(b) returns 0 that means a=b in which case + // the remaining part where sorting is to be done by putting entries whose source is id first, + // then entries whose source is name, and finally entries whose source is past, + // and sorting entries with the same element and source by their age, oldest first. + + // if a.CompareDocumentPosition(b) has set NodeConstants::DOCUMENT_POSITION_FOLLOWING + // (this can be checked by bitwise operations) then b would follow a in tree order and + // Ordering::Less should be returned in the closure else Ordering::Greater + + sourcedNamesVec.sort_by(|a, b| { + if a.element + .upcast::<Node>() + .CompareDocumentPosition(b.element.upcast::<Node>()) == + 0 + { + if a.source.is_past() && b.source.is_past() { + b.source.cmp(&a.source) + } else { + a.source.cmp(&b.source) + } + } else { + if a.element + .upcast::<Node>() + .CompareDocumentPosition(b.element.upcast::<Node>()) & + NodeConstants::DOCUMENT_POSITION_FOLLOWING == + NodeConstants::DOCUMENT_POSITION_FOLLOWING + { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Greater + } + } + }); + + // Step 6 + sourcedNamesVec.retain(|sn| !sn.name.to_string().is_empty()); + + // Step 7-8 + let mut namesVec: Vec<DOMString> = Vec::new(); + for elem in sourcedNamesVec.iter() { + if namesVec + .iter() + .find(|name| name.to_string() == elem.name.to_string()) + .is_none() + { + namesVec.push(elem.name.clone()); + } + } + + return namesVec; + } } #[derive(Clone, Copy, MallocSizeOf, PartialEq)] diff --git a/components/script/dom/webidls/HTMLFormElement.webidl b/components/script/dom/webidls/HTMLFormElement.webidl index f48726c343a..13ffbf4dfe8 100644 --- a/components/script/dom/webidls/HTMLFormElement.webidl +++ b/components/script/dom/webidls/HTMLFormElement.webidl @@ -29,7 +29,7 @@ interface HTMLFormElement : HTMLElement { [SameObject] readonly attribute HTMLFormControlsCollection elements; readonly attribute unsigned long length; getter Element? (unsigned long index); - //getter (RadioNodeList or Element) (DOMString name); + getter (RadioNodeList or Element) (DOMString name); void submit(); [CEReactions] diff --git a/tests/wpt/metadata/html/semantics/forms/form-submission-0/form-double-submit-2.html.ini b/tests/wpt/metadata/html/semantics/forms/form-submission-0/form-double-submit-2.html.ini index 61799e4c935..6d6e5b37510 100644 --- a/tests/wpt/metadata/html/semantics/forms/form-submission-0/form-double-submit-2.html.ini +++ b/tests/wpt/metadata/html/semantics/forms/form-submission-0/form-double-submit-2.html.ini @@ -1,5 +1,5 @@ [form-double-submit-2.html] - expected: ERROR + expected: TIMEOUT [preventDefault should allow onclick submit() to succeed] expected: FAIL diff --git a/tests/wpt/metadata/html/semantics/forms/form-submission-0/form-double-submit.html.ini b/tests/wpt/metadata/html/semantics/forms/form-submission-0/form-double-submit.html.ini index dce74c6dd71..a951013eb1e 100644 --- a/tests/wpt/metadata/html/semantics/forms/form-submission-0/form-double-submit.html.ini +++ b/tests/wpt/metadata/html/semantics/forms/form-submission-0/form-double-submit.html.ini @@ -1,5 +1,5 @@ [form-double-submit.html] - expected: ERROR + expected: TIMEOUT [default submit action should supersede onclick submit()] expected: FAIL diff --git a/tests/wpt/metadata/html/semantics/forms/form-submission-0/form-submission-algorithm.html.ini b/tests/wpt/metadata/html/semantics/forms/form-submission-0/form-submission-algorithm.html.ini index 52cb32ee092..1183ab27af2 100644 --- a/tests/wpt/metadata/html/semantics/forms/form-submission-0/form-submission-algorithm.html.ini +++ b/tests/wpt/metadata/html/semantics/forms/form-submission-0/form-submission-algorithm.html.ini @@ -14,3 +14,6 @@ [firing an event named submit; form.requestSubmit()] expected: FAIL + [Cannot navigate (after constructing the entry list)] + expected: FAIL + diff --git a/tests/wpt/metadata/html/semantics/forms/the-fieldset-element/HTMLFieldSetElement.html.ini b/tests/wpt/metadata/html/semantics/forms/the-fieldset-element/HTMLFieldSetElement.html.ini index 57d01129025..9bdcfee9aeb 100644 --- a/tests/wpt/metadata/html/semantics/forms/the-fieldset-element/HTMLFieldSetElement.html.ini +++ b/tests/wpt/metadata/html/semantics/forms/the-fieldset-element/HTMLFieldSetElement.html.ini @@ -6,6 +6,3 @@ [The elements must return an HTMLFormControlsCollection object] expected: FAIL - [The controls must root at the fieldset element] - expected: FAIL - diff --git a/tests/wpt/metadata/html/semantics/forms/the-form-element/form-nameditem.html.ini b/tests/wpt/metadata/html/semantics/forms/the-form-element/form-nameditem.html.ini index 3ebc134b386..2a540f30348 100644 --- a/tests/wpt/metadata/html/semantics/forms/the-form-element/form-nameditem.html.ini +++ b/tests/wpt/metadata/html/semantics/forms/the-form-element/form-nameditem.html.ini @@ -3,33 +3,18 @@ [Name for a single element should work] expected: FAIL - [Calling item() on the NodeList returned from the named getter should work] - expected: FAIL - - [Indexed getter on the NodeList returned from the named getter should work] - expected: FAIL - [All listed elements except input type=image should be present in the form] expected: FAIL [Named elements should override builtins] expected: FAIL - [The form attribute should be taken into account for named getters (single element)] - expected: FAIL - [The form attribute should be taken into account for named getters (multiple elements)] expected: FAIL - [Input should only be a named property on the innermost form that contains it] - expected: FAIL - [Trying to set an expando that would shadow an already-existing named property] expected: FAIL - [Trying to set an expando that shadows a named property that gets added later] - expected: FAIL - [Past names map should work correctly] expected: FAIL |