Auto merge of #25070 - cagandhi:named-form-getter, r=jdm

Named form getter

This PR contains changes related to adding named getter in Servo for getting the list of all meaningful property names for a HTMLFormElement object, and getting the value of a specific property name. The following changes have been made:

* uncomment the [named getter](f63b404e0c/components/script/dom/webidls/HTMLFormElement.webidl (L30)) from HTMLFormElement.webidl
* add the missing `NamedGetter` and `SupportedPropertyNames` methods to [HTMLFormElement](f63b404e0c/components/script/dom/htmlformelement.rs (L113))
* implement `SupportedPropertyNames` according to [the specification](https://html.spec.whatwg.org/multipage/forms.html#the-form-element:supported-property-names):
  * create an enum to represent the `id`, `name`, and `past` states for the sourced names
  * create a vector of `(SourcedName, DomRoot<HTMLElement>)` by iterating over `self.controls` and checking the element type and calling methods like `HTMLElement::is_listed_element`
---
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [x] These changes fix #16479 (GitHub issue number if applicable)
This commit is contained in:
bors-servo 2019-12-16 18:10:12 -05:00 committed by GitHub
commit d205194618
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 231 additions and 22 deletions

View file

@ -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`.

222
components/script/dom/htmlformelement.rs Executable file → Normal file
View file

@ -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)]

View file

@ -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]

View file

@ -1,5 +1,5 @@
[form-double-submit-2.html]
expected: ERROR
expected: TIMEOUT
[preventDefault should allow onclick submit() to succeed]
expected: FAIL

View file

@ -1,5 +1,5 @@
[form-double-submit.html]
expected: ERROR
expected: TIMEOUT
[default submit action should supersede onclick submit()]
expected: FAIL

View file

@ -14,3 +14,6 @@
[firing an event named submit; form.requestSubmit()]
expected: FAIL
[Cannot navigate (after constructing the entry list)]
expected: FAIL

View file

@ -6,6 +6,3 @@
[The elements must return an HTMLFormControlsCollection object]
expected: FAIL
[The controls must root at the fieldset element]
expected: FAIL

View file

@ -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