mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Implement the form owner concept
This commit is contained in:
parent
f90e19f705
commit
38a61712e4
25 changed files with 1004 additions and 165 deletions
|
@ -2,6 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom::bindings::cell::DOMRefCell;
|
||||
use dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
|
||||
use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
|
||||
use dom::bindings::codegen::Bindings::EventBinding::EventMethods;
|
||||
|
@ -11,15 +12,14 @@ use dom::bindings::codegen::Bindings::HTMLFormElementBinding;
|
|||
use dom::bindings::codegen::Bindings::HTMLFormElementBinding::HTMLFormElementMethods;
|
||||
use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
|
||||
use dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods;
|
||||
use dom::bindings::conversions::DerivedFrom;
|
||||
use dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
|
||||
use dom::bindings::js::{MutNullableJS, Root};
|
||||
use dom::bindings::js::{JS, MutNullableJS, Root, RootedReference};
|
||||
use dom::bindings::refcounted::Trusted;
|
||||
use dom::bindings::reflector::DomObject;
|
||||
use dom::bindings::str::DOMString;
|
||||
use dom::blob::Blob;
|
||||
use dom::document::Document;
|
||||
use dom::element::Element;
|
||||
use dom::element::{AttributeMutation, Element};
|
||||
use dom::eventtarget::EventTarget;
|
||||
use dom::file::File;
|
||||
use dom::globalscope::GlobalScope;
|
||||
|
@ -29,12 +29,16 @@ use dom::htmldatalistelement::HTMLDataListElement;
|
|||
use dom::htmlelement::HTMLElement;
|
||||
use dom::htmlfieldsetelement::HTMLFieldSetElement;
|
||||
use dom::htmlformcontrolscollection::HTMLFormControlsCollection;
|
||||
use dom::htmlimageelement::HTMLImageElement;
|
||||
use dom::htmlinputelement::HTMLInputElement;
|
||||
use dom::htmllabelelement::HTMLLabelElement;
|
||||
use dom::htmllegendelement::HTMLLegendElement;
|
||||
use dom::htmlobjectelement::HTMLObjectElement;
|
||||
use dom::htmloutputelement::HTMLOutputElement;
|
||||
use dom::htmlselectelement::HTMLSelectElement;
|
||||
use dom::htmltextareaelement::HTMLTextAreaElement;
|
||||
use dom::node::{Node, document_from_node, window_from_node};
|
||||
use dom::node::{Node, PARSER_ASSOCIATED_FORM_OWNER, UnbindContext, VecPreOrderInsertionHelper};
|
||||
use dom::node::{document_from_node, window_from_node};
|
||||
use dom::validitystate::ValidationFlags;
|
||||
use dom::virtualmethods::VirtualMethods;
|
||||
use dom_struct::dom_struct;
|
||||
|
@ -63,7 +67,8 @@ pub struct HTMLFormElement {
|
|||
htmlelement: HTMLElement,
|
||||
marked_for_reset: Cell<bool>,
|
||||
elements: MutNullableJS<HTMLFormControlsCollection>,
|
||||
generation_id: Cell<GenerationId>
|
||||
generation_id: Cell<GenerationId>,
|
||||
controls: DOMRefCell<Vec<JS<Element>>>,
|
||||
}
|
||||
|
||||
impl HTMLFormElement {
|
||||
|
@ -74,7 +79,8 @@ impl HTMLFormElement {
|
|||
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
|
||||
marked_for_reset: Cell::new(false),
|
||||
elements: Default::default(),
|
||||
generation_id: Cell::new(GenerationId(0))
|
||||
generation_id: Cell::new(GenerationId(0)),
|
||||
controls: DOMRefCell::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -504,16 +510,14 @@ impl HTMLFormElement {
|
|||
/// https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set
|
||||
/// Steps range from 1 to 3
|
||||
fn get_unclean_dataset(&self, submitter: Option<FormSubmitter>) -> Vec<FormDatum> {
|
||||
let node = self.upcast::<Node>();
|
||||
// FIXME(#3553): This is an incorrect way of getting controls owned
|
||||
// by the form, but good enough until html5ever lands
|
||||
let controls = self.controls.borrow();
|
||||
let mut data_set = Vec::new();
|
||||
for child in node.traverse_preorder() {
|
||||
for child in controls.iter() {
|
||||
// Step 3.1: The field element is disabled.
|
||||
match child.downcast::<Element>() {
|
||||
Some(el) if !el.disabled_state() => (),
|
||||
_ => continue,
|
||||
if child.disabled_state() {
|
||||
continue;
|
||||
}
|
||||
let child = child.upcast::<Node>();
|
||||
|
||||
// Step 3.1: The field element has a datalist element ancestor.
|
||||
if child.ancestors()
|
||||
|
@ -627,9 +631,10 @@ impl HTMLFormElement {
|
|||
return;
|
||||
}
|
||||
|
||||
// TODO: This is an incorrect way of getting controls owned
|
||||
// by the form, but good enough until html5ever lands
|
||||
for child in self.upcast::<Node>().traverse_preorder() {
|
||||
let controls = self.controls.borrow();
|
||||
for child in controls.iter() {
|
||||
let child = child.upcast::<Node>();
|
||||
|
||||
match child.type_id() {
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) => {
|
||||
child.downcast::<HTMLInputElement>().unwrap().reset();
|
||||
|
@ -647,14 +652,27 @@ impl HTMLFormElement {
|
|||
}
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOutputElement)) => {
|
||||
// Unimplemented
|
||||
{}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
}
|
||||
self.marked_for_reset.set(false);
|
||||
}
|
||||
|
||||
fn add_control<T: ?Sized + FormControl>(&self, control: &T) {
|
||||
let root = self.upcast::<Element>().root_element();
|
||||
let root = root.r().upcast::<Node>();
|
||||
|
||||
let mut controls = self.controls.borrow_mut();
|
||||
controls.insert_pre_order(control.to_element(), root);
|
||||
}
|
||||
|
||||
fn remove_control<T: ?Sized + FormControl>(&self, control: &T) {
|
||||
let control = control.to_element();
|
||||
let mut controls = self.controls.borrow_mut();
|
||||
controls.iter().position(|c| c.r() == control)
|
||||
.map(|idx| controls.remove(idx));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(JSTraceable, HeapSizeOf, Clone)]
|
||||
|
@ -844,24 +862,139 @@ impl<'a> FormSubmitter<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait FormControl: DerivedFrom<Element> + DomObject {
|
||||
// FIXME: This is wrong (https://github.com/servo/servo/issues/3553)
|
||||
// but we need html5ever to do it correctly
|
||||
fn form_owner(&self) -> Option<Root<HTMLFormElement>> {
|
||||
// https://html.spec.whatwg.org/multipage/#reset-the-form-owner
|
||||
pub trait FormControl: DomObject {
|
||||
fn form_owner(&self) -> Option<Root<HTMLFormElement>>;
|
||||
|
||||
fn set_form_owner(&self, form: Option<&HTMLFormElement>);
|
||||
|
||||
fn to_element<'a>(&'a self) -> &'a Element;
|
||||
|
||||
fn is_listed(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token
|
||||
// Part of step 12.
|
||||
// '..suppress the running of the reset the form owner algorithm
|
||||
// when the parser subsequently attempts to insert the element..'
|
||||
fn set_form_owner_from_parser(&self, form: &HTMLFormElement) {
|
||||
let elem = self.to_element();
|
||||
let owner = elem.get_string_attribute(&local_name!("form"));
|
||||
if !owner.is_empty() {
|
||||
let doc = document_from_node(elem);
|
||||
let owner = doc.GetElementById(owner);
|
||||
if let Some(ref o) = owner {
|
||||
let maybe_form = o.downcast::<HTMLFormElement>();
|
||||
if maybe_form.is_some() {
|
||||
return maybe_form.map(Root::from_ref);
|
||||
}
|
||||
let node = elem.upcast::<Node>();
|
||||
node.set_flag(PARSER_ASSOCIATED_FORM_OWNER, true);
|
||||
form.add_control(self);
|
||||
self.set_form_owner(Some(form));
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#reset-the-form-owner
|
||||
fn reset_form_owner(&self) {
|
||||
let elem = self.to_element();
|
||||
let node = elem.upcast::<Node>();
|
||||
let old_owner = self.form_owner();
|
||||
let has_form_id = elem.has_attribute(&local_name!("form"));
|
||||
let nearest_form_ancestor = node.ancestors()
|
||||
.filter_map(Root::downcast::<HTMLFormElement>)
|
||||
.next();
|
||||
|
||||
// Step 1
|
||||
if old_owner.is_some() && !(self.is_listed() && has_form_id) {
|
||||
if nearest_form_ancestor == old_owner {
|
||||
return;
|
||||
}
|
||||
}
|
||||
elem.upcast::<Node>().ancestors().filter_map(Root::downcast).next()
|
||||
|
||||
let new_owner = if self.is_listed() && has_form_id && elem.is_connected() {
|
||||
// Step 3
|
||||
let doc = document_from_node(node);
|
||||
let form_id = elem.get_string_attribute(&local_name!("form"));
|
||||
doc.GetElementById(form_id).and_then(Root::downcast::<HTMLFormElement>)
|
||||
} else {
|
||||
// Step 4
|
||||
nearest_form_ancestor
|
||||
};
|
||||
|
||||
if old_owner != new_owner {
|
||||
if let Some(o) = old_owner {
|
||||
o.remove_control(self);
|
||||
}
|
||||
let new_owner = new_owner.as_ref().map(|o| {
|
||||
o.add_control(self);
|
||||
o.r()
|
||||
});
|
||||
self.set_form_owner(new_owner);
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms
|
||||
fn form_attribute_mutated(&self, mutation: AttributeMutation) {
|
||||
match mutation {
|
||||
AttributeMutation::Set(_) => {
|
||||
self.register_if_necessary();
|
||||
},
|
||||
AttributeMutation::Removed => {
|
||||
self.unregister_if_necessary();
|
||||
},
|
||||
}
|
||||
|
||||
self.reset_form_owner();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms
|
||||
fn register_if_necessary(&self) {
|
||||
let elem = self.to_element();
|
||||
let form_id = elem.get_string_attribute(&local_name!("form"));
|
||||
let node = elem.upcast::<Node>();
|
||||
|
||||
if self.is_listed() && !form_id.is_empty() && node.is_in_doc() {
|
||||
let doc = document_from_node(node);
|
||||
doc.register_form_id_listener(form_id, self);
|
||||
}
|
||||
}
|
||||
|
||||
fn unregister_if_necessary(&self) {
|
||||
let elem = self.to_element();
|
||||
let form_id = elem.get_string_attribute(&local_name!("form"));
|
||||
|
||||
if self.is_listed() && !form_id.is_empty() {
|
||||
let doc = document_from_node(elem.upcast::<Node>());
|
||||
doc.unregister_form_id_listener(form_id, self);
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms
|
||||
fn bind_form_control_to_tree(&self) {
|
||||
let elem = self.to_element();
|
||||
let node = elem.upcast::<Node>();
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token
|
||||
// Part of step 12.
|
||||
// '..suppress the running of the reset the form owner algorithm
|
||||
// when the parser subsequently attempts to insert the element..'
|
||||
let must_skip_reset = node.get_flag(PARSER_ASSOCIATED_FORM_OWNER);
|
||||
node.set_flag(PARSER_ASSOCIATED_FORM_OWNER, false);
|
||||
|
||||
if !must_skip_reset {
|
||||
self.form_attribute_mutated(AttributeMutation::Set(None));
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms
|
||||
fn unbind_form_control_from_tree(&self) {
|
||||
let elem = self.to_element();
|
||||
let has_form_attr = elem.has_attribute(&local_name!("form"));
|
||||
let same_subtree = self.form_owner().map_or(true, |form| {
|
||||
elem.is_in_same_home_subtree(&*form)
|
||||
});
|
||||
|
||||
self.unregister_if_necessary();
|
||||
|
||||
// Since this control has been unregistered from the id->listener map
|
||||
// in the previous step, reset_form_owner will not be invoked on it
|
||||
// when the form owner element is unbound (i.e it is in the same
|
||||
// subtree) if it appears later in the tree order. Hence invoke
|
||||
// reset from here if this control has the form attribute set.
|
||||
if !same_subtree || (self.is_listed() && has_form_attr) {
|
||||
self.reset_form_owner();
|
||||
}
|
||||
}
|
||||
|
||||
fn get_form_attribute<InputFn, OwnerFn>(&self,
|
||||
|
@ -870,7 +1003,7 @@ pub trait FormControl: DerivedFrom<Element> + DomObject {
|
|||
owner: OwnerFn)
|
||||
-> DOMString
|
||||
where InputFn: Fn(&Self) -> DOMString,
|
||||
OwnerFn: Fn(&HTMLFormElement) -> DOMString
|
||||
OwnerFn: Fn(&HTMLFormElement) -> DOMString, Self: Sized
|
||||
{
|
||||
if self.to_element().has_attribute(attr) {
|
||||
input(self)
|
||||
|
@ -885,7 +1018,7 @@ pub trait FormControl: DerivedFrom<Element> + DomObject {
|
|||
owner: OwnerFn)
|
||||
-> bool
|
||||
where InputFn: Fn(&Self) -> bool,
|
||||
OwnerFn: Fn(&HTMLFormElement) -> bool
|
||||
OwnerFn: Fn(&HTMLFormElement) -> bool, Self: Sized
|
||||
{
|
||||
if self.to_element().has_attribute(attr) {
|
||||
input(self)
|
||||
|
@ -894,10 +1027,6 @@ pub trait FormControl: DerivedFrom<Element> + DomObject {
|
|||
}
|
||||
}
|
||||
|
||||
fn to_element(&self) -> &Element {
|
||||
self.upcast()
|
||||
}
|
||||
|
||||
// XXXKiChjang: Implement these on inheritors
|
||||
// fn candidate_for_validation(&self) -> bool;
|
||||
// fn satisfies_constraints(&self) -> bool;
|
||||
|
@ -914,6 +1043,69 @@ impl VirtualMethods for HTMLFormElement {
|
|||
_ => self.super_type().unwrap().parse_plain_attribute(name, value),
|
||||
}
|
||||
}
|
||||
|
||||
fn unbind_from_tree(&self, context: &UnbindContext) {
|
||||
self.super_type().unwrap().unbind_from_tree(context);
|
||||
|
||||
// Collect the controls to reset because reset_form_owner
|
||||
// will mutably borrow self.controls
|
||||
rooted_vec!(let mut to_reset);
|
||||
to_reset.extend(self.controls.borrow().iter()
|
||||
.filter(|c| !c.is_in_same_home_subtree(self))
|
||||
.map(|c| c.clone()));
|
||||
|
||||
for control in to_reset.iter() {
|
||||
control.as_maybe_form_control()
|
||||
.expect("Element must be a form control")
|
||||
.reset_form_owner();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FormControlElementHelpers {
|
||||
fn as_maybe_form_control<'a>(&'a self) -> Option<&'a FormControl>;
|
||||
}
|
||||
|
||||
impl FormControlElementHelpers for Element {
|
||||
fn as_maybe_form_control<'a>(&'a self) -> Option<&'a FormControl> {
|
||||
let node = self.upcast::<Node>();
|
||||
|
||||
match node.type_id() {
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLButtonElement)) => {
|
||||
Some(self.downcast::<HTMLButtonElement>().unwrap() as &FormControl)
|
||||
},
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLFieldSetElement)) => {
|
||||
Some(self.downcast::<HTMLFieldSetElement>().unwrap() as &FormControl)
|
||||
},
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLImageElement)) => {
|
||||
Some(self.downcast::<HTMLImageElement>().unwrap() as &FormControl)
|
||||
},
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) => {
|
||||
Some(self.downcast::<HTMLInputElement>().unwrap() as &FormControl)
|
||||
},
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLabelElement)) => {
|
||||
Some(self.downcast::<HTMLLabelElement>().unwrap() as &FormControl)
|
||||
},
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLegendElement)) => {
|
||||
Some(self.downcast::<HTMLLegendElement>().unwrap() as &FormControl)
|
||||
},
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLObjectElement)) => {
|
||||
Some(self.downcast::<HTMLObjectElement>().unwrap() as &FormControl)
|
||||
},
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOutputElement)) => {
|
||||
Some(self.downcast::<HTMLOutputElement>().unwrap() as &FormControl)
|
||||
},
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) => {
|
||||
Some(self.downcast::<HTMLSelectElement>().unwrap() as &FormControl)
|
||||
},
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) => {
|
||||
Some(self.downcast::<HTMLTextAreaElement>().unwrap() as &FormControl)
|
||||
},
|
||||
_ => {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PlannedNavigation {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue