Implement the form owner concept

This commit is contained in:
Mukilan Thiyagarajan 2017-01-28 17:20:01 +03:00 committed by Anthony Ramine
parent f90e19f705
commit 38a61712e4
25 changed files with 1004 additions and 165 deletions

6
Cargo.lock generated
View file

@ -1133,7 +1133,7 @@ dependencies = [
[[package]]
name = "html5ever"
version = "0.13.1"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2252,7 +2252,7 @@ dependencies = [
"gfx_traits 0.0.1",
"heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"heapsize_derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"html5ever 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
"html5ever 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
"html5ever-atoms 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper_serde 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -3412,7 +3412,7 @@ dependencies = [
"checksum heartbeats-simple 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9ad003ce233955e9d95f2c69cde84e68302ba9ba4a673d351c9bff93c738aadc"
"checksum heartbeats-simple-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e1a408c0011427cc0e0049f7861c70377819aedfc006e8c901b1c70fd98fb1a4"
"checksum hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2da7d3a34cf6406d9d700111b8eafafe9a251de41ae71d8052748259343b58"
"checksum html5ever 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d60508177ec4e5774a112efcf4d4d5f123cb00a43476fa5940b7da568371a165"
"checksum html5ever 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a3b2e982006a000535c1976213cd1baa3f455cd19335d50992ab219ffe1d3c06"
"checksum html5ever-atoms 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f9bd86e3b6a5a7933a272cc0a854f24e371f31576e585c0b41e8f857270c5134"
"checksum httparse 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a6e7a63e511f9edffbab707141fbb8707d1a3098615fb2adbd5769cdfcc9b17d"
"checksum hyper 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)" = "1b9bf64f730d6ee4b0528a5f0a316363da9d8104318731509d4ccc86248f82b3"

View file

@ -45,7 +45,7 @@ fnv = "1.0"
gfx_traits = {path = "../gfx_traits"}
heapsize = "0.3.6"
heapsize_derive = "0.1"
html5ever = {version = "0.13", features = ["heap_size", "unstable"]}
html5ever = {version = "0.14", features = ["heap_size", "unstable"]}
html5ever-atoms = {version = "0.2", features = ["heap_size"]}
hyper = "0.9.9"
hyper_serde = "0.5"

View file

@ -56,7 +56,7 @@ use dom::htmlbodyelement::HTMLBodyElement;
use dom::htmlcollection::{CollectionFilter, HTMLCollection};
use dom::htmlelement::HTMLElement;
use dom::htmlembedelement::HTMLEmbedElement;
use dom::htmlformelement::HTMLFormElement;
use dom::htmlformelement::{FormControl, FormControlElementHelpers, HTMLFormElement};
use dom::htmlheadelement::HTMLHeadElement;
use dom::htmlhtmlelement::HTMLHtmlElement;
use dom::htmliframeelement::HTMLIFrameElement;
@ -68,6 +68,7 @@ use dom::location::Location;
use dom::messageevent::MessageEvent;
use dom::mouseevent::MouseEvent;
use dom::node::{self, CloneChildrenFlag, Node, NodeDamage, window_from_node, IS_IN_DOC, LayoutNodeHelpers};
use dom::node::VecPreOrderInsertionHelper;
use dom::nodeiterator::NodeIterator;
use dom::nodelist::NodeList;
use dom::pagetransitionevent::PageTransitionEvent;
@ -120,7 +121,7 @@ use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
use std::ascii::AsciiExt;
use std::borrow::ToOwned;
use std::cell::{Cell, Ref, RefMut};
use std::collections::{HashMap, VecDeque};
use std::collections::{HashMap, HashSet, VecDeque};
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::default::Default;
use std::iter::once;
@ -313,6 +314,12 @@ pub struct Document {
dom_count: Cell<u32>,
/// Entry node for fullscreen.
fullscreen_element: MutNullableJS<Element>,
/// Map from ID to set of form control elements that have that ID as
/// their 'form' content attribute. Used to reset form controls
/// whenever any element with the same ID as the form attribute
/// is inserted or removed from the document.
/// See https://html.spec.whatwg.org/multipage/#form-owner
form_id_listener_map: DOMRefCell<HashMap<Atom, HashSet<JS<Element>>>>,
}
#[derive(JSTraceable, HeapSizeOf)]
@ -576,6 +583,10 @@ impl Document {
self,
to_unregister,
id);
// Limit the scope of the borrow because id_map might be borrowed again by
// GetElementById through the following sequence of calls
// reset_form_owner_for_listeners -> reset_form_owner -> GetElementById
{
let mut id_map = self.id_map.borrow_mut();
let is_empty = match id_map.get_mut(&id) {
None => false,
@ -591,6 +602,8 @@ impl Document {
id_map.remove(&id);
}
}
self.reset_form_owner_for_listeners(&id);
}
/// Associate an element present in this document with the provided id.
pub fn register_named_element(&self, element: &Element, id: Atom) {
@ -601,34 +614,34 @@ impl Document {
assert!(element.upcast::<Node>().is_in_doc());
assert!(!id.is_empty());
let mut id_map = self.id_map.borrow_mut();
let root = self.GetDocumentElement()
.expect("The element is in the document, so there must be a document \
element.");
match id_map.entry(id) {
Vacant(entry) => {
entry.insert(vec![JS::from_ref(element)]);
}
Occupied(entry) => {
let elements = entry.into_mut();
let new_node = element.upcast::<Node>();
let mut head: usize = 0;
let root = root.upcast::<Node>();
for node in root.traverse_preorder() {
if let Some(elem) = node.downcast() {
if &*(*elements)[head] == elem {
head += 1;
}
if new_node == &*node || head == elements.len() {
break;
}
// Limit the scope of the borrow because id_map might be borrowed again by
// GetElementById through the following sequence of calls
// reset_form_owner_for_listeners -> reset_form_owner -> GetElementById
{
let mut id_map = self.id_map.borrow_mut();
let mut elements = id_map.entry(id.clone()).or_insert(Vec::new());
elements.insert_pre_order(element, root.r().upcast::<Node>());
}
self.reset_form_owner_for_listeners(&id);
}
elements.insert(head, JS::from_ref(element));
pub fn register_form_id_listener<T: ?Sized + FormControl>(&self, id: DOMString, listener: &T) {
let mut map = self.form_id_listener_map.borrow_mut();
let listener = listener.to_element();
let mut set = map.entry(Atom::from(id)).or_insert(HashSet::new());
set.insert(JS::from_ref(listener));
}
pub fn unregister_form_id_listener<T: ?Sized + FormControl>(&self, id: DOMString, listener: &T) {
let mut map = self.form_id_listener_map.borrow_mut();
if let Occupied(mut entry) = map.entry(Atom::from(id)) {
entry.get_mut().remove(&JS::from_ref(listener.to_element()));
if entry.get().is_empty() {
entry.remove();
}
}
}
@ -2101,6 +2114,7 @@ impl Document {
spurious_animation_frames: Cell::new(0),
dom_count: Cell::new(1),
fullscreen_element: MutNullableJS::new(None),
form_id_listener_map: Default::default(),
}
}
@ -2414,6 +2428,17 @@ impl Document {
}
}
}
fn reset_form_owner_for_listeners(&self, id: &Atom) {
let map = self.form_id_listener_map.borrow();
if let Some(listeners) = map.get(id) {
for listener in listeners {
listener.r().as_maybe_form_control()
.expect("Element must be a form control")
.reset_form_owner();
}
}
}
}

View file

@ -19,6 +19,7 @@ use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use dom::bindings::codegen::Bindings::WindowBinding::{ScrollBehavior, ScrollToOptions};
use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use dom::bindings::codegen::UnionTypes::NodeOrString;
use dom::bindings::conversions::DerivedFrom;
use dom::bindings::error::{Error, ErrorResult, Fallible};
use dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
use dom::bindings::js::{JS, LayoutJS, MutNullableJS};
@ -44,6 +45,7 @@ use dom::htmlcollection::HTMLCollection;
use dom::htmlelement::HTMLElement;
use dom::htmlfieldsetelement::HTMLFieldSetElement;
use dom::htmlfontelement::{HTMLFontElement, HTMLFontElementLayoutHelpers};
use dom::htmlformelement::FormControlElementHelpers;
use dom::htmlhrelement::{HTMLHRElement, HTMLHRLayoutHelpers};
use dom::htmliframeelement::{HTMLIFrameElement, HTMLIFrameElementLayoutMethods};
use dom::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelpers};
@ -1360,6 +1362,14 @@ impl Element {
let document = document_from_node(self);
document.get_allow_fullscreen()
}
// https://html.spec.whatwg.org/multipage/#home-subtree
pub fn is_in_same_home_subtree<T>(&self, other: &T) -> bool
where T: DerivedFrom<Element> + DomObject
{
let other = other.upcast::<Element>();
self.root_element() == other.root_element()
}
}
impl ElementMethods for Element {
@ -2240,6 +2250,10 @@ impl VirtualMethods for Element {
s.bind_to_tree(tree_in_doc);
}
if let Some(f) = self.as_maybe_form_control() {
f.bind_form_control_to_tree();
}
if !tree_in_doc {
return;
}
@ -2255,6 +2269,10 @@ impl VirtualMethods for Element {
fn unbind_from_tree(&self, context: &UnbindContext) {
self.super_type().unwrap().unbind_from_tree(context);
if let Some(f) = self.as_maybe_form_control() {
f.unbind_form_control_from_tree();
}
if !context.tree_in_doc {
return;
}

View file

@ -7,7 +7,7 @@ use dom::attr::Attr;
use dom::bindings::codegen::Bindings::HTMLButtonElementBinding;
use dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElementMethods;
use dom::bindings::inheritance::Castable;
use dom::bindings::js::Root;
use dom::bindings::js::{MutNullableJS, Root};
use dom::bindings::str::DOMString;
use dom::document::Document;
use dom::element::{AttributeMutation, Element};
@ -26,6 +26,7 @@ use dom::virtualmethods::VirtualMethods;
use dom_struct::dom_struct;
use html5ever_atoms::LocalName;
use std::cell::Cell;
use std::default::Default;
use style::element_state::*;
#[derive(JSTraceable, PartialEq, Copy, Clone)]
@ -40,7 +41,8 @@ enum ButtonType {
#[dom_struct]
pub struct HTMLButtonElement {
htmlelement: HTMLElement,
button_type: Cell<ButtonType>
button_type: Cell<ButtonType>,
form_owner: MutNullableJS<HTMLFormElement>,
}
impl HTMLButtonElement {
@ -51,7 +53,8 @@ impl HTMLButtonElement {
htmlelement:
HTMLElement::new_inherited_with_state(IN_ENABLED_STATE,
local_name, prefix, document),
button_type: Cell::new(ButtonType::Submit)
button_type: Cell::new(ButtonType::Submit),
form_owner: Default::default(),
}
}
@ -211,6 +214,9 @@ impl VirtualMethods for HTMLButtonElement {
self.button_type.set(ButtonType::Submit);
}
}
},
&local_name!("form") => {
self.form_attribute_mutated(mutation);
}
_ => {},
}
@ -237,7 +243,19 @@ impl VirtualMethods for HTMLButtonElement {
}
}
impl FormControl for HTMLButtonElement {}
impl FormControl for HTMLButtonElement {
fn form_owner(&self) -> Option<Root<HTMLFormElement>> {
self.form_owner.get()
}
fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
self.form_owner.set(form);
}
fn to_element<'a>(&'a self) -> &'a Element {
self.upcast::<Element>()
}
}
impl Validatable for HTMLButtonElement {
fn is_instance_validatable(&self) -> bool {

View file

@ -6,7 +6,7 @@ use dom::attr::Attr;
use dom::bindings::codegen::Bindings::HTMLFieldSetElementBinding;
use dom::bindings::codegen::Bindings::HTMLFieldSetElementBinding::HTMLFieldSetElementMethods;
use dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
use dom::bindings::js::Root;
use dom::bindings::js::{MutNullableJS, Root};
use dom::bindings::str::DOMString;
use dom::document::Document;
use dom::element::{AttributeMutation, Element};
@ -19,11 +19,13 @@ use dom::validitystate::ValidityState;
use dom::virtualmethods::VirtualMethods;
use dom_struct::dom_struct;
use html5ever_atoms::LocalName;
use std::default::Default;
use style::element_state::*;
#[dom_struct]
pub struct HTMLFieldSetElement {
htmlelement: HTMLElement
htmlelement: HTMLElement,
form_owner: MutNullableJS<HTMLFormElement>,
}
impl HTMLFieldSetElement {
@ -33,7 +35,8 @@ impl HTMLFieldSetElement {
HTMLFieldSetElement {
htmlelement:
HTMLElement::new_inherited_with_state(IN_ENABLED_STATE,
local_name, prefix, document)
local_name, prefix, document),
form_owner: Default::default(),
}
}
@ -148,9 +151,24 @@ impl VirtualMethods for HTMLFieldSetElement {
}
}
},
&local_name!("form") => {
self.form_attribute_mutated(mutation);
},
_ => {},
}
}
}
impl FormControl for HTMLFieldSetElement {}
impl FormControl for HTMLFieldSetElement {
fn form_owner(&self) -> Option<Root<HTMLFormElement>> {
self.form_owner.get()
}
fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
self.form_owner.set(form);
}
fn to_element<'a>(&'a self) -> &'a Element {
self.upcast::<Element>()
}
}

View file

@ -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;
}
}
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();
}
elem.upcast::<Node>().ancestors().filter_map(Root::downcast).next()
}
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 {

View file

@ -15,7 +15,7 @@ use dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods;
use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use dom::bindings::error::Fallible;
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{LayoutJS, Root};
use dom::bindings::js::{LayoutJS, MutNullableJS, Root};
use dom::bindings::refcounted::Trusted;
use dom::bindings::reflector::DomObject;
use dom::bindings::str::DOMString;
@ -26,6 +26,7 @@ use dom::event::Event;
use dom::eventtarget::EventTarget;
use dom::htmlareaelement::HTMLAreaElement;
use dom::htmlelement::HTMLElement;
use dom::htmlformelement::{FormControl, HTMLFormElement};
use dom::htmlmapelement::HTMLMapElement;
use dom::mouseevent::MouseEvent;
use dom::node::{Node, NodeDamage, document_from_node, window_from_node};
@ -78,6 +79,7 @@ pub struct HTMLImageElement {
htmlelement: HTMLElement,
current_request: DOMRefCell<ImageRequest>,
pending_request: DOMRefCell<ImageRequest>,
form_owner: MutNullableJS<HTMLFormElement>,
generation: Cell<u32>,
}
@ -384,6 +386,7 @@ impl HTMLImageElement {
metadata: None,
blocker: None,
}),
form_owner: Default::default(),
generation: Default::default(),
}
}
@ -689,6 +692,24 @@ impl VirtualMethods for HTMLImageElement {
}
}
impl FormControl for HTMLImageElement {
fn form_owner(&self) -> Option<Root<HTMLFormElement>> {
self.form_owner.get()
}
fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
self.form_owner.set(form);
}
fn to_element<'a>(&'a self) -> &'a Element {
self.upcast::<Element>()
}
fn is_listed(&self) -> bool {
false
}
}
fn image_dimension_setter(element: &Element, attr: LocalName, value: u32) {
// This setter is a bit weird: the IDL type is unsigned long, but it's parsed as
// a dimension for rendering.

View file

@ -100,6 +100,7 @@ pub struct HTMLInputElement {
value_dirty: Cell<bool>,
filelist: MutNullableJS<FileList>,
form_owner: MutNullableJS<HTMLFormElement>,
}
#[derive(JSTraceable)]
@ -156,6 +157,7 @@ impl HTMLInputElement {
activation_state: DOMRefCell::new(InputActivationState::new()),
value_dirty: Cell::new(false),
filelist: MutNullableJS::new(None),
form_owner: Default::default(),
}
}
@ -1044,7 +1046,10 @@ impl VirtualMethods for HTMLInputElement {
el.set_read_write_state(!el.disabled_state());
}
}
}
},
&local_name!("form") => {
self.form_attribute_mutated(mutation);
},
_ => {},
}
}
@ -1163,7 +1168,19 @@ impl VirtualMethods for HTMLInputElement {
}
}
impl FormControl for HTMLInputElement {}
impl FormControl for HTMLInputElement {
fn form_owner(&self) -> Option<Root<HTMLFormElement>> {
self.form_owner.get()
}
fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
self.form_owner.set(form);
}
fn to_element<'a>(&'a self) -> &'a Element {
self.upcast::<Element>()
}
}
impl Validatable for HTMLInputElement {
fn is_instance_validatable(&self) -> bool {

View file

@ -3,17 +3,18 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use dom::activation::{Activatable, ActivationSource, synthetic_click_activation};
use dom::attr::Attr;
use dom::bindings::codegen::Bindings::HTMLLabelElementBinding;
use dom::bindings::codegen::Bindings::HTMLLabelElementBinding::HTMLLabelElementMethods;
use dom::bindings::inheritance::Castable;
use dom::bindings::js::Root;
use dom::bindings::str::DOMString;
use dom::document::Document;
use dom::element::Element;
use dom::element::{AttributeMutation, Element};
use dom::event::Event;
use dom::eventtarget::EventTarget;
use dom::htmlelement::HTMLElement;
use dom::htmlformelement::{FormControl, HTMLFormElement};
use dom::htmlformelement::{FormControl, FormControlElementHelpers, HTMLFormElement};
use dom::node::{document_from_node, Node};
use dom::virtualmethods::VirtualMethods;
use dom_struct::dom_struct;
@ -22,7 +23,7 @@ use style::attr::AttrValue;
#[dom_struct]
pub struct HTMLLabelElement {
htmlelement: HTMLElement,
htmlelement: HTMLElement
}
impl HTMLLabelElement {
@ -31,7 +32,7 @@ impl HTMLLabelElement {
document: &Document) -> HTMLLabelElement {
HTMLLabelElement {
htmlelement:
HTMLElement::new_inherited(local_name, prefix, document)
HTMLElement::new_inherited(local_name, prefix, document),
}
}
@ -128,6 +129,16 @@ impl VirtualMethods for HTMLLabelElement {
_ => self.super_type().unwrap().parse_plain_attribute(name, value),
}
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
self.super_type().unwrap().attribute_mutated(attr, mutation);
match attr.local_name() {
&local_name!("form") => {
self.form_attribute_mutated(mutation);
},
_ => {},
}
}
}
impl HTMLLabelElement {
@ -140,4 +151,19 @@ impl HTMLLabelElement {
}
}
impl FormControl for HTMLLabelElement {}
impl FormControl for HTMLLabelElement {
fn form_owner(&self) -> Option<Root<HTMLFormElement>> {
self.GetControl().map(Root::upcast::<Element>).and_then(|elem| {
elem.as_maybe_form_control().and_then(|control| control.form_owner())
})
}
fn set_form_owner(&self, _: Option<&HTMLFormElement>) {
// Label is a special case for form owner, it reflects its control's
// form owner. Therefore it doesn't hold form owner itself.
}
fn to_element<'a>(&'a self) -> &'a Element {
self.upcast::<Element>()
}
}

View file

@ -6,7 +6,7 @@ use dom::bindings::codegen::Bindings::HTMLLegendElementBinding;
use dom::bindings::codegen::Bindings::HTMLLegendElementBinding::HTMLLegendElementMethods;
use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use dom::bindings::inheritance::Castable;
use dom::bindings::js::Root;
use dom::bindings::js::{MutNullableJS, Root};
use dom::bindings::str::DOMString;
use dom::document::Document;
use dom::element::Element;
@ -21,6 +21,7 @@ use html5ever_atoms::LocalName;
#[dom_struct]
pub struct HTMLLegendElement {
htmlelement: HTMLElement,
form_owner: MutNullableJS<HTMLFormElement>,
}
impl HTMLLegendElement {
@ -28,7 +29,10 @@ impl HTMLLegendElement {
prefix: Option<DOMString>,
document: &Document)
-> HTMLLegendElement {
HTMLLegendElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document) }
HTMLLegendElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
form_owner: Default::default(),
}
}
#[allow(unrooted_must_root)]
@ -83,4 +87,16 @@ impl HTMLLegendElementMethods for HTMLLegendElement {
}
}
impl FormControl for HTMLLegendElement {}
impl FormControl for HTMLLegendElement {
fn form_owner(&self) -> Option<Root<HTMLFormElement>> {
self.form_owner.get()
}
fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
self.form_owner.set(form);
}
fn to_element<'a>(&'a self) -> &'a Element {
self.upcast::<Element>()
}
}

View file

@ -7,7 +7,7 @@ use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::HTMLObjectElementBinding;
use dom::bindings::codegen::Bindings::HTMLObjectElementBinding::HTMLObjectElementMethods;
use dom::bindings::inheritance::Castable;
use dom::bindings::js::Root;
use dom::bindings::js::{MutNullableJS, Root};
use dom::bindings::str::DOMString;
use dom::document::Document;
use dom::element::{AttributeMutation, Element};
@ -20,6 +20,7 @@ use dom::virtualmethods::VirtualMethods;
use dom_struct::dom_struct;
use html5ever_atoms::LocalName;
use net_traits::image::base::Image;
use std::default::Default;
use std::sync::Arc;
#[dom_struct]
@ -27,6 +28,7 @@ pub struct HTMLObjectElement {
htmlelement: HTMLElement,
#[ignore_heap_size_of = "Arc"]
image: DOMRefCell<Option<Arc<Image>>>,
form_owner: MutNullableJS<HTMLFormElement>,
}
impl HTMLObjectElement {
@ -37,6 +39,7 @@ impl HTMLObjectElement {
htmlelement:
HTMLElement::new_inherited(local_name, prefix, document),
image: DOMRefCell::new(None),
form_owner: Default::default(),
}
}
@ -114,9 +117,24 @@ impl VirtualMethods for HTMLObjectElement {
self.process_data_url();
}
},
&local_name!("form") => {
self.form_attribute_mutated(mutation);
},
_ => {},
}
}
}
impl FormControl for HTMLObjectElement {}
impl FormControl for HTMLObjectElement {
fn form_owner(&self) -> Option<Root<HTMLFormElement>> {
self.form_owner.get()
}
fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
self.form_owner.set(form);
}
fn to_element<'a>(&'a self) -> &'a Element {
self.upcast::<Element>()
}
}

View file

@ -2,23 +2,27 @@
* 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::attr::Attr;
use dom::bindings::codegen::Bindings::HTMLOutputElementBinding;
use dom::bindings::codegen::Bindings::HTMLOutputElementBinding::HTMLOutputElementMethods;
use dom::bindings::inheritance::Castable;
use dom::bindings::js::Root;
use dom::bindings::js::{MutNullableJS, Root};
use dom::bindings::str::DOMString;
use dom::document::Document;
use dom::element::{AttributeMutation, Element};
use dom::htmlelement::HTMLElement;
use dom::htmlformelement::{FormControl, HTMLFormElement};
use dom::node::{Node, window_from_node};
use dom::nodelist::NodeList;
use dom::validitystate::ValidityState;
use dom::virtualmethods::VirtualMethods;
use dom_struct::dom_struct;
use html5ever_atoms::LocalName;
#[dom_struct]
pub struct HTMLOutputElement {
htmlelement: HTMLElement
htmlelement: HTMLElement,
form_owner: MutNullableJS<HTMLFormElement>,
}
impl HTMLOutputElement {
@ -27,7 +31,8 @@ impl HTMLOutputElement {
document: &Document) -> HTMLOutputElement {
HTMLOutputElement {
htmlelement:
HTMLElement::new_inherited(local_name, prefix, document)
HTMLElement::new_inherited(local_name, prefix, document),
form_owner: Default::default(),
}
}
@ -59,4 +64,32 @@ impl HTMLOutputElementMethods for HTMLOutputElement {
}
}
impl FormControl for HTMLOutputElement {}
impl VirtualMethods for HTMLOutputElement {
fn super_type<'b>(&'b self) -> Option<&'b VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &VirtualMethods)
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
self.super_type().unwrap().attribute_mutated(attr, mutation);
match attr.local_name() {
&local_name!("form") => {
self.form_attribute_mutated(mutation);
},
_ => {},
}
}
}
impl FormControl for HTMLOutputElement {
fn form_owner(&self) -> Option<Root<HTMLFormElement>> {
self.form_owner.get()
}
fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
self.form_owner.set(form);
}
fn to_element<'a>(&'a self) -> &'a Element {
self.upcast::<Element>()
}
}

View file

@ -32,6 +32,7 @@ use dom::validitystate::{ValidityState, ValidationFlags};
use dom::virtualmethods::VirtualMethods;
use dom_struct::dom_struct;
use html5ever_atoms::LocalName;
use std::default::Default;
use std::iter;
use style::attr::AttrValue;
use style::element_state::*;
@ -61,6 +62,7 @@ impl CollectionFilter for OptionsFilter {
pub struct HTMLSelectElement {
htmlelement: HTMLElement,
options: MutNullableJS<HTMLOptionsCollection>,
form_owner: MutNullableJS<HTMLFormElement>,
}
static DEFAULT_SELECT_SIZE: u32 = 0;
@ -73,7 +75,8 @@ impl HTMLSelectElement {
htmlelement:
HTMLElement::new_inherited_with_state(IN_ENABLED_STATE,
local_name, prefix, document),
options: Default::default()
options: Default::default(),
form_owner: Default::default(),
}
}
@ -344,7 +347,8 @@ impl VirtualMethods for HTMLSelectElement {
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
self.super_type().unwrap().attribute_mutated(attr, mutation);
if attr.local_name() == &local_name!("disabled") {
match attr.local_name() {
&local_name!("disabled") => {
let el = self.upcast::<Element>();
match mutation {
AttributeMutation::Set(_) => {
@ -357,6 +361,11 @@ impl VirtualMethods for HTMLSelectElement {
el.check_ancestors_disabled_state_for_form_control();
}
}
},
&local_name!("form") => {
self.form_attribute_mutated(mutation);
},
_ => {},
}
}
@ -388,7 +397,19 @@ impl VirtualMethods for HTMLSelectElement {
}
}
impl FormControl for HTMLSelectElement {}
impl FormControl for HTMLSelectElement {
fn form_owner(&self) -> Option<Root<HTMLFormElement>> {
self.form_owner.get()
}
fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
self.form_owner.set(form);
}
fn to_element<'a>(&'a self) -> &'a Element {
self.upcast::<Element>()
}
}
impl Validatable for HTMLSelectElement {
fn is_instance_validatable(&self) -> bool {

View file

@ -9,7 +9,7 @@ use dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding;
use dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods;
use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{LayoutJS, Root};
use dom::bindings::js::{LayoutJS, MutNullableJS, Root};
use dom::bindings::str::DOMString;
use dom::document::Document;
use dom::element::{AttributeMutation, Element};
@ -30,6 +30,7 @@ use html5ever_atoms::LocalName;
use ipc_channel::ipc::IpcSender;
use script_traits::ScriptMsg as ConstellationMsg;
use std::cell::Cell;
use std::default::Default;
use std::ops::Range;
use style::attr::AttrValue;
use style::element_state::*;
@ -43,6 +44,7 @@ pub struct HTMLTextAreaElement {
placeholder: DOMRefCell<DOMString>,
// https://html.spec.whatwg.org/multipage/#concept-textarea-dirty
value_changed: Cell<bool>,
form_owner: MutNullableJS<HTMLFormElement>,
}
pub trait LayoutHTMLTextAreaElementHelpers {
@ -116,6 +118,7 @@ impl HTMLTextAreaElement {
textinput: DOMRefCell::new(TextInput::new(
Lines::Multiple, DOMString::new(), chan, None, None, SelectionDirection::None)),
value_changed: Cell::new(false),
form_owner: Default::default(),
}
}
@ -342,7 +345,10 @@ impl VirtualMethods for HTMLTextAreaElement {
el.set_read_write_state(!el.disabled_state());
}
}
}
},
local_name!("form") => {
self.form_attribute_mutated(mutation);
},
_ => {},
}
}
@ -435,7 +441,19 @@ impl VirtualMethods for HTMLTextAreaElement {
}
}
impl FormControl for HTMLTextAreaElement {}
impl FormControl for HTMLTextAreaElement {
fn form_owner(&self) -> Option<Root<HTMLFormElement>> {
self.form_owner.get()
}
fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
self.form_owner.set(form);
}
fn to_element<'a>(&'a self) -> &'a Element {
self.upcast::<Element>()
}
}
impl Validatable for HTMLTextAreaElement {}

View file

@ -162,7 +162,11 @@ bitflags! {
/// Whether any ancestor is a fragmentation container
const CAN_BE_FRAGMENTED = 0x40,
#[doc = "Specifies whether this node needs to be dirted when viewport size changed."]
const DIRTY_ON_VIEWPORT_SIZE_CHANGE = 0x80
const DIRTY_ON_VIEWPORT_SIZE_CHANGE = 0x80,
#[doc = "Specifies whether the parser has set an associated form owner for \
this element. Only applicable for form-associatable elements."]
const PARSER_ASSOCIATED_FORM_OWNER = 0x90,
}
}
@ -286,6 +290,11 @@ impl Node {
for node in child.traverse_preorder() {
// Out-of-document elements never have the descendants flag set.
node.set_flag(IS_IN_DOC | HAS_DIRTY_DESCENDANTS, false);
}
for node in child.traverse_preorder() {
// This needs to be in its own loop, because unbind_from_tree may
// rely on the state of IS_IN_DOC of the context node's descendants,
// e.g. when removing a <form>.
vtable_for(&&*node).unbind_from_tree(&context);
node.style_and_layout_data.get().map(|d| node.dispose(d));
}
@ -2656,3 +2665,41 @@ impl Into<LayoutElementType> for ElementTypeId {
}
}
/// Helper trait to insert an element into vector whose elements
/// are maintained in tree order
pub trait VecPreOrderInsertionHelper<T> {
fn insert_pre_order(&mut self, elem: &T, tree_root: &Node);
}
impl<T> VecPreOrderInsertionHelper<T> for Vec<JS<T>>
where T: DerivedFrom<Node> + DomObject
{
/// This algorithm relies on the following assumptions:
/// * any elements inserted in this vector share the same tree root
/// * any time an element is removed from the tree root, it is also removed from this array
/// * any time an element is moved within the tree, it is removed from this array and re-inserted
///
/// Under these assumptions, an element's tree-order position in this array can be determined by
/// performing a [preorder traversal](https://dom.spec.whatwg.org/#concept-tree-order) of the tree root's children,
/// and increasing the destination index in the array every time a node in the array is encountered during
/// the traversal.
fn insert_pre_order(&mut self, elem: &T, tree_root: &Node) {
if self.is_empty() {
self.push(JS::from_ref(elem));
return;
}
let elem_node = elem.upcast::<Node>();
let mut head: usize = 0;
for node in tree_root.traverse_preorder() {
let head_node = Root::upcast::<Node>(Root::from_ref(&*self[head]));
if head_node == node {
head += 1;
}
if elem_node == node.r() || head == self.len() {
break;
}
}
self.insert(head, JS::from_ref(elem));
}
}

View file

@ -15,12 +15,14 @@ use dom::comment::Comment;
use dom::document::Document;
use dom::documenttype::DocumentType;
use dom::element::{Element, ElementCreator};
use dom::htmlformelement::{FormControlElementHelpers, HTMLFormElement};
use dom::htmlscriptelement::HTMLScriptElement;
use dom::htmltemplateelement::HTMLTemplateElement;
use dom::node::Node;
use dom::processinginstruction::ProcessingInstruction;
use dom::virtualmethods::vtable_for;
use html5ever::Attribute;
use html5ever::QualName;
use html5ever::serialize::{AttrRef, Serializable, Serializer};
use html5ever::serialize::TraversalScope;
use html5ever::serialize::TraversalScope::{ChildrenOnly, IncludeNode};
@ -29,7 +31,6 @@ use html5ever::tokenizer::{Tokenizer as HtmlTokenizer, TokenizerOpts, TokenizerR
use html5ever::tokenizer::buffer_queue::BufferQueue;
use html5ever::tree_builder::{NodeOrText, QuirksMode};
use html5ever::tree_builder::{Tracer as HtmlTracer, TreeBuilder, TreeBuilderOpts, TreeSink};
use html5ever_atoms::QualName;
use js::jsapi::JSTracer;
use servo_url::ServoUrl;
use std::borrow::Cow;
@ -159,6 +160,13 @@ impl TreeSink for Sink {
}
}
fn same_tree(&self, x: JS<Node>, y: JS<Node>) -> bool {
let x = x.downcast::<Element>().expect("Element node expected");
let y = y.downcast::<Element>().expect("Element node expected");
x.is_in_same_home_subtree(y)
}
fn create_element(&mut self, name: QualName, attrs: Vec<Attribute>)
-> JS<Node> {
let elem = Element::create(name, None, &*self.document,
@ -176,17 +184,33 @@ impl TreeSink for Sink {
JS::from_ref(comment.upcast())
}
fn has_parent_node(&self, node: JS<Node>) -> bool {
node.GetParentNode().is_some()
}
fn associate_with_form(&mut self, target: JS<Node>, form: JS<Node>) {
let node = target;
let form = Root::downcast::<HTMLFormElement>(Root::from_ref(&*form))
.expect("Owner must be a form element");
let elem = node.downcast::<Element>();
let control = elem.as_ref().and_then(|e| e.as_maybe_form_control());
if let Some(control) = control {
control.set_form_owner_from_parser(&form);
} else {
// TODO remove this code when keygen is implemented.
assert!(node.NodeName() == "KEYGEN", "Unknown form-associatable element");
}
}
fn append_before_sibling(&mut self,
sibling: JS<Node>,
new_node: NodeOrText<JS<Node>>) -> Result<(), NodeOrText<JS<Node>>> {
// If there is no parent, return the node to the parser.
let parent = match sibling.GetParentNode() {
Some(p) => p,
None => return Err(new_node),
};
new_node: NodeOrText<JS<Node>>) {
let parent = sibling.GetParentNode()
.expect("append_before_sibling called on node without parent");
super::insert(&parent, Some(&*sibling), new_node);
Ok(())
}
fn parse_error(&mut self, msg: Cow<'static, str>) {

View file

@ -38,6 +38,7 @@ use dom::htmlmetaelement::HTMLMetaElement;
use dom::htmlobjectelement::HTMLObjectElement;
use dom::htmloptgroupelement::HTMLOptGroupElement;
use dom::htmloptionelement::HTMLOptionElement;
use dom::htmloutputelement::HTMLOutputElement;
use dom::htmlscriptelement::HTMLScriptElement;
use dom::htmlselectelement::HTMLSelectElement;
use dom::htmlstyleelement::HTMLStyleElement;
@ -212,6 +213,9 @@ pub fn vtable_for(node: &Node) -> &VirtualMethods {
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOptionElement)) => {
node.downcast::<HTMLOptionElement>().unwrap() as &VirtualMethods
}
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOutputElement)) => {
node.downcast::<HTMLOutputElement>().unwrap() as &VirtualMethods
}
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLScriptElement)) => {
node.downcast::<HTMLScriptElement>().unwrap() as &VirtualMethods
}

View file

@ -93571,6 +93571,24 @@
{}
]
],
"html/semantics/forms/form-control-infrastructure/form_attribute.html": [
[
"/html/semantics/forms/form-control-infrastructure/form_attribute.html",
{}
]
],
"html/semantics/forms/form-control-infrastructure/form_owner_and_table.html": [
[
"/html/semantics/forms/form-control-infrastructure/form_owner_and_table.html",
{}
]
],
"html/semantics/forms/form-control-infrastructure/form_owner_and_table_2.html": [
[
"/html/semantics/forms/form-control-infrastructure/form_owner_and_table_2.html",
{}
]
],
"html/semantics/forms/form-submission-0/form-data-set-usv.html": [
[
"/html/semantics/forms/form-submission-0/form-data-set-usv.html",
@ -176716,6 +176734,18 @@
"bfd11561a2e568668b6aaf94bc6da9e42fccdf55",
"testharness"
],
"html/semantics/forms/form-control-infrastructure/form_attribute.html": [
"b19b882091ff523e71397bfcad5dcf505e0a8f88",
"testharness"
],
"html/semantics/forms/form-control-infrastructure/form_owner_and_table.html": [
"7172d74bb5572091ee09abf30a4f6892b85aca3a",
"testharness"
],
"html/semantics/forms/form-control-infrastructure/form_owner_and_table_2.html": [
"3b1dea35bb4af384125904d69bf569109965714f",
"testharness"
],
"html/semantics/forms/form-submission-0/.gitkeep": [
"da39a3ee5e6b4b0d3255bfef95601890afd80709",
"support"

View file

@ -3,30 +3,8 @@
[keygen.form]
expected: FAIL
[label.form]
expected: FAIL
[label-form.form]
expected: FAIL
[label-form-form2.form]
expected: FAIL
[label-with-progress.form]
expected: FAIL
[label-with-meter.form]
expected: FAIL
[label-for-control-form-in-form.form]
expected: FAIL
[label-for-control-form.form]
expected: FAIL
[label-in-table-with-control.form]
expected: FAIL
[label-in-table-for.form]
expected: FAIL

View file

@ -3,9 +3,3 @@
[the element is barred from constraint validation]
expected: FAIL
[reset button resets controls associated with their form using the form element pointer]
expected: FAIL
[reset button resets controls associated with a form using the form attribute]
expected: FAIL

View file

@ -2,10 +2,3 @@
type: testharness
[A non-control follows by a control with same ID.]
expected: FAIL
[A label in a form without a control]
expected: FAIL
[A label outside a form with a control inside the form]
expected: FAIL

View file

@ -0,0 +1,233 @@
<!DOCTYPE html>
<html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<div>
<form id="f1"></form>
<form id="f2">
<input id="i1" />
<input id="i2" form="f1" />
<input id="i3" />
</form>
<script>
test(function() {
var i1 = document.getElementById("i1");
var i2 = document.getElementById("i2");
var i3 = document.getElementById("i3");
var f1 = document.getElementById("f1");
var f2 = document.getElementById("f2");
assert_equals(i1.form, f2,
"i1 must be associated with f2 by the parser");
assert_equals(i2.form, f1,
"i2 is not associated with f2 by the parser " +
"since it has the form attribute set to f1");
f1.appendChild(i1);
i3.setAttribute("form", "f1");
assert_equals(i1.form, f1,
"i1's form owner must be reset when parent changes");
assert_equals(i3.form, f1,
"i3's form owner must be reset when the form" +
"attribute is set");
assert_equals(i2.form, f1);
}, "Tests for parser inserted controls");
</script>
</div>
<div id="placeholder">
</div>
<script>
var reassociateableElements = [
"button",
"fieldset",
"input",
"object",
"output",
"select",
"textarea",
];
var form1 = null;
var form2 = null;
var placeholder = document.getElementById("placeholder");
reassociateableElements.forEach(function(localName) {
function testControl(test_, desc) {
test(function() {
var control = document.createElement(localName);
while(placeholder.firstChild)
placeholder.removeChild(placeholder.firstChild);
form1 = document.createElement("form");
form2 = document.createElement("form");
form1.id = "form1";
form2.id = "form2";
placeholder.appendChild(form1);
placeholder.appendChild(form2);
test_.call(control);
}, "[" + localName.toUpperCase() + "] " + desc);
}
testControl(function() {
form1.appendChild(this);
assert_equals(this.form, form1);
}, "Basic form association - control with no form attribute is associated with ancestor");
testControl(function() {
this.setAttribute("form", "form1");
form1.appendChild(this);
assert_equals(this.form, form1);
form1.id = "form-one";
assert_equals(this.form, null);
}, "Form owner is reset to null when control's form attribute is set to an ID " +
"that does not exist in the document");
testControl(function() {
this.setAttribute("form", "");
form1.appendChild(this);
assert_equals(this.form, null);
}, "Control whose form attribute is an empty string has no form owner");
testControl(function() {
form1.id = "";
this.setAttribute("form", "");
form1.appendChild(this);
assert_equals(this.form, null);
}, "Control whose form attribute is an empty string has no form owner " +
"even when form with empty attribute is present");
testControl(function() {
form1.id = "FORM1";
this.setAttribute("form", "form1");
form1.appendChild(this);
assert_equals(this.form, null);
}, "Control's form attribute must be a case sensitive match for the form's id");
testControl(function() {
form1.appendChild(this);
assert_equals(this.form, form1);
this.setAttribute("form", "form2");
assert_equals(this.form, form2);
}, "Setting the form attribute of a control to the id of a non-ancestor form works");
testControl(function() {
this.setAttribute("form", "form1");
form2.appendChild(this);
assert_equals(this.form, form1);
this.removeAttribute("form");
assert_equals(this.form, form2);
}, "Removing form id from a control resets the form owner to ancestor");
testControl(function() {
this.setAttribute("form", "form1");
form2.appendChild(this);
assert_equals(this.form, form1);
placeholder.removeChild(form1);
assert_equals(this.form, null);
}, "Removing the form owner of a control with form attribute resets " +
"the form owner to null");
testControl(function() {
var form3 = document.createElement("form");
form3.id = "form3";
placeholder.appendChild(form3);
form3.appendChild(this);
assert_equals(this.form, form3);
this.setAttribute("form", "form2");
assert_equals(this.form, form2);
this.setAttribute("form", "form1");
assert_equals(this.form, form1);
}, "Changing form attibute of control resets form owner to correct form");
testControl(function() {
this.setAttribute("form", "form1");
var form3 = document.createElement("form");
form3.id = "form3";
placeholder.appendChild(form3);
placeholder.appendChild(this);
assert_equals(this.form, form1);
form1.appendChild(this);
assert_equals(this.form, form1);
form2.appendChild(this);
assert_equals(this.form, form1);
}, "Moving a control with form attribute within the document " +
"does not change the form owner");
testControl(function() {
form1.id = "form-one";
this.setAttribute("form", "form1");
form2.appendChild(this);
assert_equals(this.form, null);
form1.id = "form1";
assert_equals(this.form, form1);
}, "When the id of a non-ancestor form changes from not being a match for the " +
"form attribute to being a match, the control's form owner is reset");
testControl(function() {
this.setAttribute("form", "form1");
form1.appendChild(this);
assert_equals(this.form, form1);
form2.id = "form1";
form1.parentNode.insertBefore(form2, form1);
assert_equals(this.form, form2);
form2.parentNode.removeChild(form2);
assert_equals(this.form, form1);
form1.parentNode.appendChild(form2);
assert_equals(this.form, form1);
}, "When form element with same ID as the control's form attribute is inserted " +
"earlier in tree order, the form owner is changed to the inserted form");
testControl(function() {
this.setAttribute("form", "form1");
form2.appendChild(this);
assert_equals(this.form, form1);
var span = document.createElement("span");
span.id = "form1";
form1.parentNode.insertBefore(span, form1);
assert_equals(this.form, null);
form1.parentNode.appendChild(span);
assert_equals(this.form, form1);
}, "When non-form element with same ID as the control's form attribute is " +
"inserted earlier in tree order, the control does not have a form owner");
testControl(function() {
this.setAttribute("form", "form1");
form1.appendChild(this);
assert_equals(this.form, form1);
form1.parentNode.removeChild(form1);
assert_equals(this.form, form1);
}, "A control that is not in the document but has the form attribute set " +
"is associated with the nearest ancestor form if one exists");
});
</script>
</body>
</html>

View file

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<div id="root">
<form id='form1'></form>
<table id='table1'>
<form id='form2'>
<tr><td><input id='input1'></td></tr>
<tr><td><input id='input2' form='form1'></td></tr>
</table>
<form id="form3">
<input id="input3" />
</form>
</div>
<script>
test(function() {
var input1 = document.getElementById('input1');
var input2 = document.getElementById('input2');
var input3 = document.getElementById('input3');
var form1 = document.getElementById('form1');
var form2 = document.getElementById('form2');
var form3 = document.getElementById('form3');
var root = document.getElementById('root');
assert_equals(input1.form, form2,
"input1's form owner must be form2 as per the parsing rules");
assert_equals(input2.form, form1,
"input2's form owner must be the form with id 'form1'");
assert_equals(input3.form, form2,
"input3's form owner must be form2 as per the parsing rules");
root.parentNode.removeChild(root);
assert_equals(input1.form, form2,
"input1's form owner must not have changed since they are both in the same subtree");
assert_equals(input2.form, null,
"input2 must not have a form owner since it has the form attribute set");
assert_equals(input3.form, form2,
"input3's form owner must not have changed since they are both in the same subtree");
}, "Form element and form controls nested inside a table are correctly handled");
</script>
</body>
</html>

View file

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<div>
<form id='form1'></form>
<table id='table1'>
<form id='form2'>
<script>
var t = document.getElementById('table1');
var f = document.getElementById('form2');
t.removeChild(f);
</script>
<tr><td><input id='input1'></td></tr>
<tr><td><input id='input2' form='form1'></td></tr>
</table>
<form id="form3">
<input id="input3" />
</form>
</div>
<script>
test(function() {
var form1 = document.getElementById('form1');
assert_equals(document.getElementById('input1').form, null,
"input1's form owner must be null since form2 is not in the" +
"same home subtree");
assert_equals(document.getElementById('input2').form, form1,
"input2's form owner must be the form with id 'form1'");
assert_equals(document.getElementById('input3').form, null,
"input3's form owner must be null instead of form2 (as per parsing rules)" +
"since form2 is not in the same home subtree");
}, "Controls nested in tables are not associated with form element inside the " +
"table if the form had been removed by script before the controls were " +
"inserted by the parser");
</script>
</body>
</html>