From c9ef305e603efd19ba9428aa6d7be24e0bfe2755 Mon Sep 17 00:00:00 2001 From: Mukilan Thiyagarajan Date: Sat, 28 Jan 2017 17:20:01 +0300 Subject: [PATCH 01/10] Implement the form owner concept --- components/script/Cargo.toml | 2 +- components/script/dom/document.rs | 98 ++++--- components/script/dom/element.rs | 9 + components/script/dom/htmlbuttonelement.rs | 37 ++- components/script/dom/htmlfieldsetelement.rs | 47 +++- components/script/dom/htmlformelement.rs | 251 +++++++++++++++--- components/script/dom/htmlimageelement.rs | 38 ++- components/script/dom/htmlinputelement.rs | 31 ++- components/script/dom/htmllabelelement.rs | 55 +++- components/script/dom/htmllegendelement.rs | 22 +- components/script/dom/htmlobjectelement.rs | 44 ++- components/script/dom/htmloutputelement.rs | 63 ++++- components/script/dom/htmlselectelement.rs | 59 +++- components/script/dom/htmltextareaelement.rs | 34 ++- components/script/dom/node.rs | 37 ++- components/script/dom/servoparser/html.rs | 40 ++- components/script/dom/virtualmethods.rs | 4 + .../script_plugins/unrooted_must_root.rs | 3 +- 18 files changed, 746 insertions(+), 128 deletions(-) diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml index 9c5044a91e36..0bd50f4aa14a 100644 --- a/components/script/Cargo.toml +++ b/components/script/Cargo.toml @@ -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 = {git = "https://github.com/canaltinova/html5ever", branch = "form-owner-test", features = ["heap_size", "unstable"]} html5ever-atoms = {version = "0.2", features = ["heap_size"]} hyper = "0.9.9" hyper_serde = "0.5" diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index d70976f236e2..7115d4efb530 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -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; @@ -298,6 +299,12 @@ pub struct Document { dom_count: Cell, /// Entry node for fullscreen. fullscreen_element: MutNullableJS, + /// 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>>>, } #[derive(JSTraceable, HeapSizeOf)] @@ -561,20 +568,26 @@ impl Document { self, to_unregister, id); - let mut id_map = self.id_map.borrow_mut(); - let is_empty = match id_map.get_mut(&id) { - None => false, - Some(elements) => { - let position = elements.iter() - .position(|element| &**element == to_unregister) - .expect("This element should be in registered."); - elements.remove(position); - elements.is_empty() + // 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, + Some(elements) => { + let position = elements.iter() + .position(|element| &**element == to_unregister) + .expect("This element should be in registered."); + elements.remove(position); + elements.is_empty() + } + }; + if is_empty { + id_map.remove(&id); } - }; - if is_empty { - id_map.remove(&id); } + self.reset_form_owner_for_listeners(&id); } /// Associate an element present in this document with the provided id. @@ -586,38 +599,39 @@ impl Document { assert!(element.upcast::().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(); + // 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::()); + } + self.reset_form_owner_for_listeners(&id); + } - let new_node = element.upcast::(); - let mut head: usize = 0; - let root = root.upcast::(); - 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; - } - } - } + pub fn register_form_id_listener(&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)); + } - elements.insert(head, JS::from_ref(element)); + pub fn unregister_form_id_listener(&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(); } } } + /// Attempt to find a named element in this page's document. /// https://html.spec.whatwg.org/multipage/#the-indicated-part-of-the-document pub fn find_fragment_node(&self, fragid: &str) -> Option> { @@ -2044,6 +2058,7 @@ impl Document { ignore_destructive_writes_counter: Default::default(), dom_count: Cell::new(1), fullscreen_element: MutNullableJS::new(None), + form_id_listener_map: DOMRefCell::new(HashMap::new()), } } @@ -2351,6 +2366,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(); + } + } + } } diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 471771728957..4fded0576ca4 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -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}; @@ -1333,6 +1334,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(&self, other: &T) -> bool + where T: DerivedFrom + DomObject + { + let other = other.upcast::(); + self.root_element() == other.root_element() + } } impl ElementMethods for Element { diff --git a/components/script/dom/htmlbuttonelement.rs b/components/script/dom/htmlbuttonelement.rs index 56a614cf6088..1447605de605 100755 --- a/components/script/dom/htmlbuttonelement.rs +++ b/components/script/dom/htmlbuttonelement.rs @@ -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 + button_type: Cell, + form_owner: MutNullableJS, } 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,16 @@ impl VirtualMethods for HTMLButtonElement { self.button_type.set(ButtonType::Submit); } } + }, + &local_name!("form") => { + match mutation { + AttributeMutation::Set(_) => { + self.form_attribute_set(); + } + AttributeMutation::Removed => { + self.form_attribute_removed(); + } + } } _ => {}, } @@ -221,12 +234,16 @@ impl VirtualMethods for HTMLButtonElement { s.bind_to_tree(tree_in_doc); } + self.bind_form_control_to_tree(); + self.upcast::().check_ancestors_disabled_state_for_form_control(); } fn unbind_from_tree(&self, context: &UnbindContext) { self.super_type().unwrap().unbind_from_tree(context); + self.unbind_form_control_from_tree(); + let node = self.upcast::(); let el = self.upcast::(); if node.ancestors().any(|ancestor| ancestor.is::()) { @@ -237,7 +254,19 @@ impl VirtualMethods for HTMLButtonElement { } } -impl FormControl for HTMLButtonElement {} +impl FormControl for HTMLButtonElement { + fn form_owner(&self) -> Option> { + 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::() + } +} impl Validatable for HTMLButtonElement { fn is_instance_validatable(&self) -> bool { diff --git a/components/script/dom/htmlfieldsetelement.rs b/components/script/dom/htmlfieldsetelement.rs index 19dbc8d4f1bf..bc302cc64707 100644 --- a/components/script/dom/htmlfieldsetelement.rs +++ b/components/script/dom/htmlfieldsetelement.rs @@ -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}; @@ -14,16 +14,18 @@ use dom::htmlcollection::{CollectionFilter, HTMLCollection}; use dom::htmlelement::HTMLElement; use dom::htmlformelement::{FormControl, HTMLFormElement}; use dom::htmllegendelement::HTMLLegendElement; -use dom::node::{Node, window_from_node}; +use dom::node::{Node, UnbindContext, window_from_node}; 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, } 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,43 @@ impl VirtualMethods for HTMLFieldSetElement { } } }, + &local_name!("form") => { + match mutation { + AttributeMutation::Set(_) => { + self.form_attribute_set(); + }, + AttributeMutation::Removed => { + self.form_attribute_removed(); + }, + } + }, _ => {}, } } + + fn bind_to_tree(&self, tree_in_doc: bool) { + if let Some(ref s) = self.super_type() { + s.bind_to_tree(tree_in_doc); + } + self.bind_form_control_to_tree(); + } + + fn unbind_from_tree(&self, context: &UnbindContext) { + self.super_type().unwrap().unbind_from_tree(context); + self.unbind_form_control_from_tree(); + } } -impl FormControl for HTMLFieldSetElement {} +impl FormControl for HTMLFieldSetElement { + fn form_owner(&self) -> Option> { + 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::() + } +} diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index 67d80acdcab6..8680c8e36a56 100755 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -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,9 +12,8 @@ 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; @@ -29,12 +29,15 @@ 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::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 +66,8 @@ pub struct HTMLFormElement { htmlelement: HTMLElement, marked_for_reset: Cell, elements: MutNullableJS, - generation_id: Cell + generation_id: Cell, + controls: DOMRefCell>>, } impl HTMLFormElement { @@ -74,7 +78,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,11 +509,10 @@ 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) -> Vec { - let node = self.upcast::(); - // 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() { + let child = child.upcast::(); // Step 3.1: The field element is disabled. match child.downcast::() { Some(el) if !el.disabled_state() => (), @@ -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::().traverse_preorder() { + let controls = self.controls.borrow(); + for child in controls.iter() { + let child = child.upcast::(); + match child.type_id() { NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) => { child.downcast::().unwrap().reset(); @@ -647,14 +652,28 @@ impl HTMLFormElement { } NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOutputElement)) => { // Unimplemented - {} + unimplemented!() } _ => {} } - }; + } self.marked_for_reset.set(false); } + fn add_control(&self, control: &T) { + let root = self.upcast::().root_element(); + let root = root.r().upcast::(); + + let mut controls = self.controls.borrow_mut(); + controls.insert_pre_order(control.to_element(), root); + } + + fn remove_control(&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 +863,129 @@ impl<'a> FormSubmitter<'a> { } } -pub trait FormControl: DerivedFrom + 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> { - // https://html.spec.whatwg.org/multipage/#reset-the-form-owner +pub trait FormControl: DomObject { + fn form_owner(&self) -> Option>; + + fn set_form_owner(&self, form: Option<&HTMLFormElement>); + + fn to_element<'a>(&'a self) -> &'a Element; + + fn is_reassociatable(&self) -> bool { + true + } + + // https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token + // Part of step 4. + // '..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::(); - if maybe_form.is_some() { - return maybe_form.map(Root::from_ref); - } + let node = elem.upcast::(); + 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::(); + 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::) + .next(); + + // Step 1 + if old_owner.is_some() && !(self.is_reassociatable() && has_form_id) { + if nearest_form_ancestor == old_owner { + return; + } + } + + let new_owner = if self.is_reassociatable() && has_form_id && node.is_in_doc() { + // 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::) + } 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_set(&self) { + let elem = self.to_element(); + let form_id = elem.get_string_attribute(&local_name!("form")); + let node = elem.upcast::(); + + if self.is_reassociatable() && !form_id.is_empty() && node.is_in_doc() { + let doc = document_from_node(node); + doc.register_form_id_listener(form_id, self); + } + + self.reset_form_owner(); + } + + // https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms + fn form_attribute_removed(&self) { + let elem = self.to_element(); + let form_id = elem.get_string_attribute(&local_name!("form")); + + if self.is_reassociatable() && !form_id.is_empty() { + let doc = document_from_node(elem.upcast::()); + doc.unregister_form_id_listener(form_id, self); + } + self.reset_form_owner(); + } + + // 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::(); + + // https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token + // Part of step 4. + // '..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_set(); + } + } + + // 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.form_attribute_removed(); + + // 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_reassociatable() && has_form_attr) { + self.reset_form_owner(); } - elem.upcast::().ancestors().filter_map(Root::downcast).next() } fn get_form_attribute(&self, @@ -870,7 +994,7 @@ pub trait FormControl: DerivedFrom + 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 +1009,7 @@ pub trait FormControl: DerivedFrom + 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 +1018,6 @@ pub trait FormControl: DerivedFrom + 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 +1034,67 @@ 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 + //let mut to_reset: RootedVec> = RootedVec::new(); + 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::(); + + match node.type_id() { + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLButtonElement)) => { + Some(self.downcast::().unwrap() as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLFieldSetElement)) => { + Some(self.downcast::().unwrap() as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLImageElement)) => { + Some(self.downcast::().unwrap() as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) => { + Some(self.downcast::().unwrap() as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLabelElement)) => { + Some(self.downcast::().unwrap() as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLObjectElement)) => { + Some(self.downcast::().unwrap() as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOutputElement)) => { + Some(self.downcast::().unwrap() as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) => { + Some(self.downcast::().unwrap() as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) => { + Some(self.downcast::().unwrap() as &FormControl) + }, + _ => { + None + } + } + } } struct PlannedNavigation { diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs index 8f7497c55aca..fd119f47eded 100644 --- a/components/script/dom/htmlimageelement.rs +++ b/components/script/dom/htmlimageelement.rs @@ -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,9 +26,10 @@ 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}; +use dom::node::{Node, NodeDamage, UnbindContext, document_from_node, window_from_node}; use dom::values::UNSIGNED_LONG_MAX; use dom::virtualmethods::VirtualMethods; use dom::window::Window; @@ -76,6 +77,7 @@ pub struct HTMLImageElement { htmlelement: HTMLElement, current_request: DOMRefCell, pending_request: DOMRefCell, + form_owner: MutNullableJS, } impl HTMLImageElement { @@ -381,6 +383,7 @@ impl HTMLImageElement { metadata: None, blocker: None, }), + form_owner: Default::default(), } } @@ -644,6 +647,19 @@ impl VirtualMethods for HTMLImageElement { } } + fn bind_to_tree(&self, tree_in_doc: bool) { + if let Some(ref s) = self.super_type() { + s.bind_to_tree(tree_in_doc); + } + + self.bind_form_control_to_tree(); + } + + fn unbind_from_tree(&self, context: &UnbindContext) { + self.super_type().unwrap().unbind_from_tree(context); + self.unbind_form_control_from_tree(); + } + fn handle_event(&self, event: &Event) { if (event.type_() == atom!("click")) { let area_elements = self.areas(); @@ -682,6 +698,24 @@ impl VirtualMethods for HTMLImageElement { } } +impl FormControl for HTMLImageElement { + fn form_owner(&self) -> Option> { + 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::() + } + + fn is_reassociatable(&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. diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index f2e9f9833003..a7d308f0a908 100755 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -100,6 +100,7 @@ pub struct HTMLInputElement { value_dirty: Cell, filelist: MutNullableJS, + form_owner: MutNullableJS, } #[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,17 @@ impl VirtualMethods for HTMLInputElement { el.set_read_write_state(!el.disabled_state()); } } - } + }, + &local_name!("form") => { + match mutation { + AttributeMutation::Set(_) => { + self.form_attribute_set(); + }, + AttributeMutation::Removed => { + self.form_attribute_removed(); + } + } + }, _ => {}, } } @@ -1066,12 +1078,15 @@ impl VirtualMethods for HTMLInputElement { s.bind_to_tree(tree_in_doc); } + self.bind_form_control_to_tree(); self.upcast::().check_ancestors_disabled_state_for_form_control(); } fn unbind_from_tree(&self, context: &UnbindContext) { self.super_type().unwrap().unbind_from_tree(context); + self.unbind_form_control_from_tree(); + let node = self.upcast::(); let el = self.upcast::(); if node.ancestors().any(|ancestor| ancestor.is::()) { @@ -1163,7 +1178,19 @@ impl VirtualMethods for HTMLInputElement { } } -impl FormControl for HTMLInputElement {} +impl FormControl for HTMLInputElement { + fn form_owner(&self) -> Option> { + 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::() + } +} impl Validatable for HTMLInputElement { fn is_instance_validatable(&self) -> bool { diff --git a/components/script/dom/htmllabelelement.rs b/components/script/dom/htmllabelelement.rs index 02345ad1f20c..9bc37af9877b 100644 --- a/components/script/dom/htmllabelelement.rs +++ b/components/script/dom/htmllabelelement.rs @@ -3,18 +3,19 @@ * 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::js::{MutNullableJS, 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::node::{document_from_node, Node}; +use dom::node::{document_from_node, Node, UnbindContext}; use dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; use html5ever_atoms::LocalName; @@ -23,6 +24,7 @@ use style::attr::AttrValue; #[dom_struct] pub struct HTMLLabelElement { htmlelement: HTMLElement, + form_owner: MutNullableJS, } impl HTMLLabelElement { @@ -31,7 +33,8 @@ impl HTMLLabelElement { document: &Document) -> HTMLLabelElement { HTMLLabelElement { htmlelement: - HTMLElement::new_inherited(local_name, prefix, document) + HTMLElement::new_inherited(local_name, prefix, document), + form_owner: Default::default(), } } @@ -128,6 +131,36 @@ 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") => { + match mutation { + AttributeMutation::Set(_) => { + self.form_attribute_set(); + }, + AttributeMutation::Removed => { + self.form_attribute_removed(); + }, + } + }, + _ => {}, + } + } + + fn bind_to_tree(&self, tree_in_doc: bool) { + if let Some(ref s) = self.super_type() { + s.bind_to_tree(tree_in_doc); + } + + self.bind_form_control_to_tree(); + } + + fn unbind_from_tree(&self, context: &UnbindContext) { + self.super_type().unwrap().unbind_from_tree(context); + self.unbind_form_control_from_tree(); + } } impl HTMLLabelElement { @@ -140,4 +173,16 @@ impl HTMLLabelElement { } } -impl FormControl for HTMLLabelElement {} +impl FormControl for HTMLLabelElement { + fn form_owner(&self) -> Option> { + 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::() + } +} diff --git a/components/script/dom/htmllegendelement.rs b/components/script/dom/htmllegendelement.rs index f05ec6bfd3b2..86b34c27dd02 100644 --- a/components/script/dom/htmllegendelement.rs +++ b/components/script/dom/htmllegendelement.rs @@ -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, } impl HTMLLegendElement { @@ -28,7 +29,10 @@ impl HTMLLegendElement { prefix: Option, 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> { + 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::() + } +} diff --git a/components/script/dom/htmlobjectelement.rs b/components/script/dom/htmlobjectelement.rs index 6352ea5c862d..f522a793068c 100755 --- a/components/script/dom/htmlobjectelement.rs +++ b/components/script/dom/htmlobjectelement.rs @@ -7,19 +7,20 @@ 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}; use dom::htmlelement::HTMLElement; use dom::htmlformelement::{FormControl, HTMLFormElement}; -use dom::node::{Node, window_from_node}; +use dom::node::{Node, UnbindContext, window_from_node}; use dom::validation::Validatable; use dom::validitystate::{ValidityState, ValidationFlags}; 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>>, + form_owner: MutNullableJS, } 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,44 @@ impl VirtualMethods for HTMLObjectElement { self.process_data_url(); } }, + &local_name!("form") => { + match mutation { + AttributeMutation::Set(_) => { + self.form_attribute_set(); + }, + AttributeMutation::Removed => { + self.form_attribute_removed(); + }, + } + }, _ => {}, } } + + fn bind_to_tree(&self, tree_in_doc: bool) { + if let Some(ref s) = self.super_type() { + s.bind_to_tree(tree_in_doc); + } + + self.bind_form_control_to_tree(); + } + + fn unbind_from_tree(&self, context: &UnbindContext) { + self.super_type().unwrap().unbind_from_tree(context); + self.unbind_form_control_from_tree(); + } } -impl FormControl for HTMLObjectElement {} +impl FormControl for HTMLObjectElement { + fn form_owner(&self) -> Option> { + 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::() + } +} diff --git a/components/script/dom/htmloutputelement.rs b/components/script/dom/htmloutputelement.rs index 6f3154057ea4..52d521edd3c5 100644 --- a/components/script/dom/htmloutputelement.rs +++ b/components/script/dom/htmloutputelement.rs @@ -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::node::{Node, UnbindContext, 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, } 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,52 @@ impl HTMLOutputElementMethods for HTMLOutputElement { } } -impl FormControl for HTMLOutputElement {} +impl VirtualMethods for HTMLOutputElement { + fn super_type<'b>(&'b self) -> Option<&'b VirtualMethods> { + Some(self.upcast::() 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") => { + match mutation { + AttributeMutation::Set(_) => { + self.form_attribute_set(); + }, + AttributeMutation::Removed => { + self.form_attribute_removed(); + }, + } + }, + _ => {}, + } + } + + fn bind_to_tree(&self, tree_in_doc: bool) { + if let Some(ref s) = self.super_type() { + s.bind_to_tree(tree_in_doc); + } + + self.bind_form_control_to_tree(); + } + + fn unbind_from_tree(&self, context: &UnbindContext) { + self.super_type().unwrap().unbind_from_tree(context); + self.unbind_form_control_from_tree(); + } +} + +impl FormControl for HTMLOutputElement { + fn form_owner(&self) -> Option> { + 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::() + } +} diff --git a/components/script/dom/htmlselectelement.rs b/components/script/dom/htmlselectelement.rs index 1f4a8f029b09..fa46b32e6c98 100755 --- a/components/script/dom/htmlselectelement.rs +++ b/components/script/dom/htmlselectelement.rs @@ -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, + form_owner: MutNullableJS, } 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,19 +347,32 @@ 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") { - let el = self.upcast::(); - match mutation { - AttributeMutation::Set(_) => { - el.set_disabled_state(true); - el.set_enabled_state(false); - }, - AttributeMutation::Removed => { - el.set_disabled_state(false); - el.set_enabled_state(true); - el.check_ancestors_disabled_state_for_form_control(); + match attr.local_name() { + &local_name!("disabled") => { + let el = self.upcast::(); + match mutation { + AttributeMutation::Set(_) => { + el.set_disabled_state(true); + el.set_enabled_state(false); + }, + AttributeMutation::Removed => { + el.set_disabled_state(false); + el.set_enabled_state(true); + el.check_ancestors_disabled_state_for_form_control(); + } } - } + }, + &local_name!("form") => { + match mutation { + AttributeMutation::Set(_) => { + self.form_attribute_set(); + }, + AttributeMutation::Removed => { + self.form_attribute_removed(); + } + } + }, + _ => {}, } } @@ -365,12 +381,15 @@ impl VirtualMethods for HTMLSelectElement { s.bind_to_tree(tree_in_doc); } + self.bind_form_control_to_tree(); self.upcast::().check_ancestors_disabled_state_for_form_control(); } fn unbind_from_tree(&self, context: &UnbindContext) { self.super_type().unwrap().unbind_from_tree(context); + self.unbind_form_control_from_tree(); + let node = self.upcast::(); let el = self.upcast::(); if node.ancestors().any(|ancestor| ancestor.is::()) { @@ -388,7 +407,19 @@ impl VirtualMethods for HTMLSelectElement { } } -impl FormControl for HTMLSelectElement {} +impl FormControl for HTMLSelectElement { + fn form_owner(&self) -> Option> { + 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::() + } +} impl Validatable for HTMLSelectElement { fn is_instance_validatable(&self) -> bool { diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index e7db7565adfa..c131324e0421 100755 --- a/components/script/dom/htmltextareaelement.rs +++ b/components/script/dom/htmltextareaelement.rs @@ -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, // https://html.spec.whatwg.org/multipage/#concept-textarea-dirty value_changed: Cell, + form_owner: MutNullableJS, } 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,17 @@ impl VirtualMethods for HTMLTextAreaElement { el.set_read_write_state(!el.disabled_state()); } } - } + }, + local_name!("form") => { + match mutation { + AttributeMutation::Set(_) => { + self.form_attribute_set(); + }, + AttributeMutation::Removed => { + self.form_attribute_removed(); + } + } + }, _ => {}, } } @@ -352,6 +365,7 @@ impl VirtualMethods for HTMLTextAreaElement { s.bind_to_tree(tree_in_doc); } + self.bind_form_control_to_tree(); self.upcast::().check_ancestors_disabled_state_for_form_control(); } @@ -366,6 +380,8 @@ impl VirtualMethods for HTMLTextAreaElement { fn unbind_from_tree(&self, context: &UnbindContext) { self.super_type().unwrap().unbind_from_tree(context); + self.unbind_form_control_from_tree(); + let node = self.upcast::(); let el = self.upcast::(); if node.ancestors().any(|ancestor| ancestor.is::()) { @@ -435,7 +451,19 @@ impl VirtualMethods for HTMLTextAreaElement { } } -impl FormControl for HTMLTextAreaElement {} +impl FormControl for HTMLTextAreaElement { + fn form_owner(&self) -> Option> { + 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::() + } +} impl Validatable for HTMLTextAreaElement {} diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index fce1ce4826df..efd9b4cadf83 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -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,8 @@ 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() { vtable_for(&&*node).unbind_from_tree(&context); node.style_and_layout_data.get().map(|d| node.dispose(d)); } @@ -2656,3 +2662,32 @@ impl Into for ElementTypeId { } } +/// Helper trait to insert an element into vector whose elements +/// are maintained in tree order +pub trait VecPreOrderInsertionHelper { + fn insert_pre_order(&mut self, elem: &T, tree_root: &Node); +} + +impl VecPreOrderInsertionHelper for Vec> + where T: DerivedFrom + DomObject +{ + 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::(); + let mut head: usize = 0; + for node in tree_root.traverse_preorder() { + let head_node = Root::upcast::(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)); + } +} diff --git a/components/script/dom/servoparser/html.rs b/components/script/dom/servoparser/html.rs index a6270eaaf262..688e2aba788d 100644 --- a/components/script/dom/servoparser/html.rs +++ b/components/script/dom/servoparser/html.rs @@ -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, y: JS) -> bool { + let x = x.downcast::().expect("Element node expected"); + let y = y.downcast::().expect("Element node expected"); + + x.is_in_same_home_subtree(y) + } + fn create_element(&mut self, name: QualName, attrs: Vec) -> JS { 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) -> bool { + node.GetParentNode().is_some() + } + + fn associate_with_form(&mut self, target: JS, form: JS) { + let node = target; + let form = Root::downcast::(Root::from_ref(&*form)) + .expect("Owner must be a form element"); + + let elem = node.downcast::(); + 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, - new_node: NodeOrText>) -> Result<(), NodeOrText>> { - // 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>) { + 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>) { diff --git a/components/script/dom/virtualmethods.rs b/components/script/dom/virtualmethods.rs index d6b77b31256a..8ff47f9ced54 100644 --- a/components/script/dom/virtualmethods.rs +++ b/components/script/dom/virtualmethods.rs @@ -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::().unwrap() as &VirtualMethods } + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOutputElement)) => { + node.downcast::().unwrap() as &VirtualMethods + } NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLScriptElement)) => { node.downcast::().unwrap() as &VirtualMethods } diff --git a/components/script_plugins/unrooted_must_root.rs b/components/script_plugins/unrooted_must_root.rs index ad74f6c4b8fe..c62796b7a90f 100644 --- a/components/script_plugins/unrooted_must_root.rs +++ b/components/script_plugins/unrooted_must_root.rs @@ -54,7 +54,8 @@ fn is_unrooted_ty(cx: &LateContext, ty: &ty::TyS, in_new_function: bool) -> bool || match_def_path(cx, did.did, &["core", "slice", "Iter"]) || match_def_path(cx, did.did, &["std", "collections", "hash", "map", "Entry"]) || match_def_path(cx, did.did, &["std", "collections", "hash", "map", "OccupiedEntry"]) - || match_def_path(cx, did.did, &["std", "collections", "hash", "map", "VacantEntry"]) { + || match_def_path(cx, did.did, &["std", "collections", "hash", "map", "VacantEntry"]) + || match_def_path(cx, did.did, &["std", "collections", "hash", "set", "Iter"]) { // Structures which are semantically similar to an &ptr. false } else if did.is_box() && in_new_function { From d1e0cf45a142eac0b874472627ea509dc26a13c0 Mon Sep 17 00:00:00 2001 From: Mukilan Thiyagarajan Date: Thu, 2 Jul 2015 20:33:14 +0530 Subject: [PATCH 02/10] Update web-platform-tests expected data --- .../forms/form-control-infrastructure/form.html.ini | 3 --- .../html/semantics/forms/the-input-element/reset.html.ini | 6 ------ 2 files changed, 9 deletions(-) diff --git a/tests/wpt/metadata/html/semantics/forms/form-control-infrastructure/form.html.ini b/tests/wpt/metadata/html/semantics/forms/form-control-infrastructure/form.html.ini index 2182e7efee34..f470086cf0db 100644 --- a/tests/wpt/metadata/html/semantics/forms/form-control-infrastructure/form.html.ini +++ b/tests/wpt/metadata/html/semantics/forms/form-control-infrastructure/form.html.ini @@ -3,9 +3,6 @@ [keygen.form] expected: FAIL - [label.form] - expected: FAIL - [label-form.form] expected: FAIL diff --git a/tests/wpt/metadata/html/semantics/forms/the-input-element/reset.html.ini b/tests/wpt/metadata/html/semantics/forms/the-input-element/reset.html.ini index 20a3f17079a3..4f2efff5bd4b 100644 --- a/tests/wpt/metadata/html/semantics/forms/the-input-element/reset.html.ini +++ b/tests/wpt/metadata/html/semantics/forms/the-input-element/reset.html.ini @@ -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 - From 4a746f723647259c05ad3035f40ce9e11e798a10 Mon Sep 17 00:00:00 2001 From: Mukilan Thiyagarajan Date: Fri, 3 Jul 2015 20:54:28 +0530 Subject: [PATCH 03/10] Add tests for form owner concept --- tests/wpt/mozilla/meta/MANIFEST.json | 30 +++ .../mozilla/tests/mozilla/form_attribute.html | 234 ++++++++++++++++++ .../tests/mozilla/form_owner_and_table.html | 50 ++++ .../tests/mozilla/form_owner_and_table_2.html | 45 ++++ 4 files changed, 359 insertions(+) create mode 100644 tests/wpt/mozilla/tests/mozilla/form_attribute.html create mode 100644 tests/wpt/mozilla/tests/mozilla/form_owner_and_table.html create mode 100644 tests/wpt/mozilla/tests/mozilla/form_owner_and_table_2.html diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 434afa7e5a21..bb3818161c88 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -12754,6 +12754,24 @@ {} ] ], + "mozilla/form_attribute.html": [ + [ + "/_mozilla/mozilla/form_attribute.html", + {} + ] + ], + "mozilla/form_owner_and_table.html": [ + [ + "/_mozilla/mozilla/form_owner_and_table.html", + {} + ] + ], + "mozilla/form_owner_and_table_2.html": [ + [ + "/_mozilla/mozilla/form_owner_and_table_2.html", + {} + ] + ], "mozilla/form_submit_about.html": [ [ "/_mozilla/mozilla/form_submit_about.html", @@ -25235,6 +25253,18 @@ "3407915dc9912d4d681c47ec5227278a198139ab", "testharness" ], + "mozilla/form_attribute.html": [ + "46f783ddbe21c914d452e175083a2ad266475654", + "testharness" + ], + "mozilla/form_owner_and_table.html": [ + "7172d74bb5572091ee09abf30a4f6892b85aca3a", + "testharness" + ], + "mozilla/form_owner_and_table_2.html": [ + "3b1dea35bb4af384125904d69bf569109965714f", + "testharness" + ], "mozilla/form_submit_about.html": [ "06ca608a11dd01908c8040c0060cb4473574acd1", "testharness" diff --git a/tests/wpt/mozilla/tests/mozilla/form_attribute.html b/tests/wpt/mozilla/tests/mozilla/form_attribute.html new file mode 100644 index 000000000000..53171b1a1b0f --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/form_attribute.html @@ -0,0 +1,234 @@ + + + + + + + +
+
+
+ + + +
+ +
+ +
+
+ + + + diff --git a/tests/wpt/mozilla/tests/mozilla/form_owner_and_table.html b/tests/wpt/mozilla/tests/mozilla/form_owner_and_table.html new file mode 100644 index 000000000000..1aa75c27b355 --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/form_owner_and_table.html @@ -0,0 +1,50 @@ + + + + + + + +
+
+ + + + +
+ + + +
+ + + + diff --git a/tests/wpt/mozilla/tests/mozilla/form_owner_and_table_2.html b/tests/wpt/mozilla/tests/mozilla/form_owner_and_table_2.html new file mode 100644 index 000000000000..d9aee12b5f35 --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/form_owner_and_table_2.html @@ -0,0 +1,45 @@ + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + From 945fda175d43d64b61b650b70ab6b3beb70a2747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Mon, 30 Jan 2017 21:58:57 +0300 Subject: [PATCH 04/10] Fix failing tests --- components/script/dom/htmlformelement.rs | 6 +++++- components/script/dom/htmllabelelement.rs | 21 ++++++++++++------- components/script/dom/htmllegendelement.rs | 3 +++ .../form-control-infrastructure/form.html.ini | 19 ----------------- 4 files changed, 21 insertions(+), 28 deletions(-) diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index 8680c8e36a56..3dad73ba17a6 100755 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -32,6 +32,7 @@ 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; @@ -652,7 +653,7 @@ impl HTMLFormElement { } NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOutputElement)) => { // Unimplemented - unimplemented!() + {} } _ => {} } @@ -1078,6 +1079,9 @@ impl FormControlElementHelpers for Element { NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLabelElement)) => { Some(self.downcast::().unwrap() as &FormControl) }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLegendElement)) => { + Some(self.downcast::().unwrap() as &FormControl) + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLObjectElement)) => { Some(self.downcast::().unwrap() as &FormControl) }, diff --git a/components/script/dom/htmllabelelement.rs b/components/script/dom/htmllabelelement.rs index 9bc37af9877b..a80c90511f16 100644 --- a/components/script/dom/htmllabelelement.rs +++ b/components/script/dom/htmllabelelement.rs @@ -7,14 +7,14 @@ 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::{MutNullableJS, Root}; +use dom::bindings::js::Root; use dom::bindings::str::DOMString; use dom::document::Document; 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, UnbindContext}; use dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; @@ -23,8 +23,7 @@ use style::attr::AttrValue; #[dom_struct] pub struct HTMLLabelElement { - htmlelement: HTMLElement, - form_owner: MutNullableJS, + htmlelement: HTMLElement } impl HTMLLabelElement { @@ -34,7 +33,6 @@ impl HTMLLabelElement { HTMLLabelElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), - form_owner: Default::default(), } } @@ -175,11 +173,18 @@ impl HTMLLabelElement { impl FormControl for HTMLLabelElement { fn form_owner(&self) -> Option> { - self.form_owner.get() + if let Some(e) = self.GetControl() { + let form_control = e.upcast::().as_maybe_form_control() + .expect("Element must be a form control"); + form_control.form_owner() + } else { + None + } } - fn set_form_owner(&self, form: Option<&HTMLFormElement>) { - self.form_owner.set(form); + 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 { diff --git a/components/script/dom/htmllegendelement.rs b/components/script/dom/htmllegendelement.rs index 86b34c27dd02..0590114a2f6b 100644 --- a/components/script/dom/htmllegendelement.rs +++ b/components/script/dom/htmllegendelement.rs @@ -56,12 +56,15 @@ impl VirtualMethods for HTMLLegendElement { s.bind_to_tree(tree_in_doc); } + self.bind_form_control_to_tree(); self.upcast::().check_ancestors_disabled_state_for_form_control(); } fn unbind_from_tree(&self, context: &UnbindContext) { self.super_type().unwrap().unbind_from_tree(context); + self.unbind_form_control_from_tree(); + let node = self.upcast::(); let el = self.upcast::(); if node.ancestors().any(|ancestor| ancestor.is::()) { diff --git a/tests/wpt/metadata/html/semantics/forms/form-control-infrastructure/form.html.ini b/tests/wpt/metadata/html/semantics/forms/form-control-infrastructure/form.html.ini index f470086cf0db..352bc7f2d347 100644 --- a/tests/wpt/metadata/html/semantics/forms/form-control-infrastructure/form.html.ini +++ b/tests/wpt/metadata/html/semantics/forms/form-control-infrastructure/form.html.ini @@ -3,27 +3,8 @@ [keygen.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 - From 0c9edb6c8df591d73a4c6ca56dfe1762724da266 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Tue, 31 Jan 2017 18:01:52 +0300 Subject: [PATCH 05/10] Use one form_attribute_mutated method instead of set and remoted --- components/script/dom/htmlbuttonelement.rs | 9 +---- components/script/dom/htmlfieldsetelement.rs | 9 +---- components/script/dom/htmlformelement.rs | 42 ++++++++++---------- components/script/dom/htmlinputelement.rs | 9 +---- components/script/dom/htmllabelelement.rs | 9 +---- components/script/dom/htmlobjectelement.rs | 9 +---- components/script/dom/htmloutputelement.rs | 9 +---- components/script/dom/htmlselectelement.rs | 9 +---- components/script/dom/htmltextareaelement.rs | 9 +---- 9 files changed, 28 insertions(+), 86 deletions(-) diff --git a/components/script/dom/htmlbuttonelement.rs b/components/script/dom/htmlbuttonelement.rs index 1447605de605..34008a0aa8ea 100755 --- a/components/script/dom/htmlbuttonelement.rs +++ b/components/script/dom/htmlbuttonelement.rs @@ -216,14 +216,7 @@ impl VirtualMethods for HTMLButtonElement { } }, &local_name!("form") => { - match mutation { - AttributeMutation::Set(_) => { - self.form_attribute_set(); - } - AttributeMutation::Removed => { - self.form_attribute_removed(); - } - } + self.form_attribute_mutated(mutation); } _ => {}, } diff --git a/components/script/dom/htmlfieldsetelement.rs b/components/script/dom/htmlfieldsetelement.rs index bc302cc64707..9f5029ec251e 100644 --- a/components/script/dom/htmlfieldsetelement.rs +++ b/components/script/dom/htmlfieldsetelement.rs @@ -152,14 +152,7 @@ impl VirtualMethods for HTMLFieldSetElement { } }, &local_name!("form") => { - match mutation { - AttributeMutation::Set(_) => { - self.form_attribute_set(); - }, - AttributeMutation::Removed => { - self.form_attribute_removed(); - }, - } + self.form_attribute_mutated(mutation); }, _ => {}, } diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index 3dad73ba17a6..a654b03ded23 100755 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -19,7 +19,7 @@ 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; @@ -876,7 +876,7 @@ pub trait FormControl: DomObject { } // https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token - // Part of step 4. + // 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) { @@ -927,45 +927,43 @@ pub trait FormControl: DomObject { } // https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms - fn form_attribute_set(&self) { + fn form_attribute_mutated(&self, mutation: AttributeMutation) { let elem = self.to_element(); let form_id = elem.get_string_attribute(&local_name!("form")); - let node = elem.upcast::(); - if self.is_reassociatable() && !form_id.is_empty() && node.is_in_doc() { - let doc = document_from_node(node); - doc.register_form_id_listener(form_id, self); + match mutation { + AttributeMutation::Set(_) => { + let node = elem.upcast::(); + if self.is_reassociatable() && !form_id.is_empty() && node.is_in_doc() { + let doc = document_from_node(node); + doc.register_form_id_listener(form_id, self); + } + }, + AttributeMutation::Removed => { + if self.is_reassociatable() && !form_id.is_empty() { + let doc = document_from_node(elem.upcast::()); + doc.unregister_form_id_listener(form_id, self); + } + }, } self.reset_form_owner(); } - // https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms - fn form_attribute_removed(&self) { - let elem = self.to_element(); - let form_id = elem.get_string_attribute(&local_name!("form")); - - if self.is_reassociatable() && !form_id.is_empty() { - let doc = document_from_node(elem.upcast::()); - doc.unregister_form_id_listener(form_id, self); - } - self.reset_form_owner(); - } - // 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::(); // https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token - // Part of step 4. + // 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_set(); + self.form_attribute_mutated(AttributeMutation::Set(None)); } } @@ -977,7 +975,7 @@ pub trait FormControl: DomObject { elem.is_in_same_home_subtree(&*form) }); - self.form_attribute_removed(); + self.form_attribute_mutated(AttributeMutation::Removed); // Since this control has been unregistered from the id->listener map // in the previous step, reset_form_owner will not be invoked on it diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index a7d308f0a908..3ee70640fa59 100755 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -1048,14 +1048,7 @@ impl VirtualMethods for HTMLInputElement { } }, &local_name!("form") => { - match mutation { - AttributeMutation::Set(_) => { - self.form_attribute_set(); - }, - AttributeMutation::Removed => { - self.form_attribute_removed(); - } - } + self.form_attribute_mutated(mutation); }, _ => {}, } diff --git a/components/script/dom/htmllabelelement.rs b/components/script/dom/htmllabelelement.rs index a80c90511f16..556294f7e8fb 100644 --- a/components/script/dom/htmllabelelement.rs +++ b/components/script/dom/htmllabelelement.rs @@ -134,14 +134,7 @@ impl VirtualMethods for HTMLLabelElement { self.super_type().unwrap().attribute_mutated(attr, mutation); match attr.local_name() { &local_name!("form") => { - match mutation { - AttributeMutation::Set(_) => { - self.form_attribute_set(); - }, - AttributeMutation::Removed => { - self.form_attribute_removed(); - }, - } + self.form_attribute_mutated(mutation); }, _ => {}, } diff --git a/components/script/dom/htmlobjectelement.rs b/components/script/dom/htmlobjectelement.rs index f522a793068c..1c1eaab23432 100755 --- a/components/script/dom/htmlobjectelement.rs +++ b/components/script/dom/htmlobjectelement.rs @@ -118,14 +118,7 @@ impl VirtualMethods for HTMLObjectElement { } }, &local_name!("form") => { - match mutation { - AttributeMutation::Set(_) => { - self.form_attribute_set(); - }, - AttributeMutation::Removed => { - self.form_attribute_removed(); - }, - } + self.form_attribute_mutated(mutation); }, _ => {}, } diff --git a/components/script/dom/htmloutputelement.rs b/components/script/dom/htmloutputelement.rs index 52d521edd3c5..77d7cd192ace 100644 --- a/components/script/dom/htmloutputelement.rs +++ b/components/script/dom/htmloutputelement.rs @@ -73,14 +73,7 @@ impl VirtualMethods for HTMLOutputElement { self.super_type().unwrap().attribute_mutated(attr, mutation); match attr.local_name() { &local_name!("form") => { - match mutation { - AttributeMutation::Set(_) => { - self.form_attribute_set(); - }, - AttributeMutation::Removed => { - self.form_attribute_removed(); - }, - } + self.form_attribute_mutated(mutation); }, _ => {}, } diff --git a/components/script/dom/htmlselectelement.rs b/components/script/dom/htmlselectelement.rs index fa46b32e6c98..5f233c7cda1b 100755 --- a/components/script/dom/htmlselectelement.rs +++ b/components/script/dom/htmlselectelement.rs @@ -363,14 +363,7 @@ impl VirtualMethods for HTMLSelectElement { } }, &local_name!("form") => { - match mutation { - AttributeMutation::Set(_) => { - self.form_attribute_set(); - }, - AttributeMutation::Removed => { - self.form_attribute_removed(); - } - } + self.form_attribute_mutated(mutation); }, _ => {}, } diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index c131324e0421..fa50827b7512 100755 --- a/components/script/dom/htmltextareaelement.rs +++ b/components/script/dom/htmltextareaelement.rs @@ -347,14 +347,7 @@ impl VirtualMethods for HTMLTextAreaElement { } }, local_name!("form") => { - match mutation { - AttributeMutation::Set(_) => { - self.form_attribute_set(); - }, - AttributeMutation::Removed => { - self.form_attribute_removed(); - } - } + self.form_attribute_mutated(mutation); }, _ => {}, } From c64c38e205933c0fb827fdf122d84f59ee83d695 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Tue, 31 Jan 2017 20:29:50 +0300 Subject: [PATCH 06/10] Fix test by not calling reset_form_owner() in unbind_form_control_from_tree when it shouldn't --- components/script/dom/htmlformelement.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index a654b03ded23..5a8d134f2c80 100755 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -975,7 +975,15 @@ pub trait FormControl: DomObject { elem.is_in_same_home_subtree(&*form) }); - self.form_attribute_mutated(AttributeMutation::Removed); + // FIXME: We should call `form_attribute_mutated` method here, + // but also we don't want to call reset_form_owner() method in + // `form_attribute_mutated`. We should fix this. + // self.form_attribute_mutated(AttributeMutation::Removed); + let form_id = elem.get_string_attribute(&local_name!("form")); + if self.is_reassociatable() && !form_id.is_empty() { + let doc = document_from_node(elem.upcast::()); + doc.unregister_form_id_listener(form_id, self); + } // Since this control has been unregistered from the id->listener map // in the previous step, reset_form_owner will not be invoked on it From 701dca6a10bb43df9bc55a0f23af5f850e8d1da2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Tue, 31 Jan 2017 20:33:42 +0300 Subject: [PATCH 07/10] Exclude label element from form_attribute.html Because label has a different form owner concept. It should reflect its control's form owner. When it doesn't have a control it should print null. --- tests/wpt/mozilla/tests/mozilla/form_attribute.html | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/wpt/mozilla/tests/mozilla/form_attribute.html b/tests/wpt/mozilla/tests/mozilla/form_attribute.html index 53171b1a1b0f..07bf0b66b119 100644 --- a/tests/wpt/mozilla/tests/mozilla/form_attribute.html +++ b/tests/wpt/mozilla/tests/mozilla/form_attribute.html @@ -48,7 +48,6 @@ "button", "fieldset", "input", - "label", "object", "output", "select", From 8d80757d54d22b9069f6c6e3ce4ba36f741144f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Tue, 31 Jan 2017 21:31:33 +0300 Subject: [PATCH 08/10] Update test expectations --- .../forms/the-label-element/label-attributes.html.ini | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/wpt/metadata/html/semantics/forms/the-label-element/label-attributes.html.ini b/tests/wpt/metadata/html/semantics/forms/the-label-element/label-attributes.html.ini index 26a7bbdc4f79..77caa827250e 100644 --- a/tests/wpt/metadata/html/semantics/forms/the-label-element/label-attributes.html.ini +++ b/tests/wpt/metadata/html/semantics/forms/the-label-element/label-attributes.html.ini @@ -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 - From 0d04420eb7ae0ab356cd4924d4c6fa9c4274c3e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Thu, 9 Feb 2017 18:00:55 +0300 Subject: [PATCH 09/10] Move control's tree bind/unbind calls to Element's tree bind/unbind methods --- components/script/dom/element.rs | 9 +++++++++ components/script/dom/htmlbuttonelement.rs | 4 ---- components/script/dom/htmlfieldsetelement.rs | 14 +------------- components/script/dom/htmlimageelement.rs | 15 +-------------- components/script/dom/htmlinputelement.rs | 3 --- components/script/dom/htmllabelelement.rs | 15 +-------------- components/script/dom/htmllegendelement.rs | 3 --- components/script/dom/htmlobjectelement.rs | 15 +-------------- components/script/dom/htmloutputelement.rs | 15 +-------------- components/script/dom/htmlselectelement.rs | 3 --- components/script/dom/htmltextareaelement.rs | 3 --- 11 files changed, 14 insertions(+), 85 deletions(-) diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 4fded0576ca4..69dbeb4f6166 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -45,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}; @@ -2222,6 +2223,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; } @@ -2237,6 +2242,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; } diff --git a/components/script/dom/htmlbuttonelement.rs b/components/script/dom/htmlbuttonelement.rs index 34008a0aa8ea..b36b276bfd7e 100755 --- a/components/script/dom/htmlbuttonelement.rs +++ b/components/script/dom/htmlbuttonelement.rs @@ -227,16 +227,12 @@ impl VirtualMethods for HTMLButtonElement { s.bind_to_tree(tree_in_doc); } - self.bind_form_control_to_tree(); - self.upcast::().check_ancestors_disabled_state_for_form_control(); } fn unbind_from_tree(&self, context: &UnbindContext) { self.super_type().unwrap().unbind_from_tree(context); - self.unbind_form_control_from_tree(); - let node = self.upcast::(); let el = self.upcast::(); if node.ancestors().any(|ancestor| ancestor.is::()) { diff --git a/components/script/dom/htmlfieldsetelement.rs b/components/script/dom/htmlfieldsetelement.rs index 9f5029ec251e..9fdc65a513cd 100644 --- a/components/script/dom/htmlfieldsetelement.rs +++ b/components/script/dom/htmlfieldsetelement.rs @@ -14,7 +14,7 @@ use dom::htmlcollection::{CollectionFilter, HTMLCollection}; use dom::htmlelement::HTMLElement; use dom::htmlformelement::{FormControl, HTMLFormElement}; use dom::htmllegendelement::HTMLLegendElement; -use dom::node::{Node, UnbindContext, window_from_node}; +use dom::node::{Node, window_from_node}; use dom::validitystate::ValidityState; use dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; @@ -157,18 +157,6 @@ impl VirtualMethods for HTMLFieldSetElement { _ => {}, } } - - fn bind_to_tree(&self, tree_in_doc: bool) { - if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); - } - self.bind_form_control_to_tree(); - } - - fn unbind_from_tree(&self, context: &UnbindContext) { - self.super_type().unwrap().unbind_from_tree(context); - self.unbind_form_control_from_tree(); - } } impl FormControl for HTMLFieldSetElement { diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs index fd119f47eded..106fbb3a609d 100644 --- a/components/script/dom/htmlimageelement.rs +++ b/components/script/dom/htmlimageelement.rs @@ -29,7 +29,7 @@ use dom::htmlelement::HTMLElement; use dom::htmlformelement::{FormControl, HTMLFormElement}; use dom::htmlmapelement::HTMLMapElement; use dom::mouseevent::MouseEvent; -use dom::node::{Node, NodeDamage, UnbindContext, document_from_node, window_from_node}; +use dom::node::{Node, NodeDamage, document_from_node, window_from_node}; use dom::values::UNSIGNED_LONG_MAX; use dom::virtualmethods::VirtualMethods; use dom::window::Window; @@ -647,19 +647,6 @@ impl VirtualMethods for HTMLImageElement { } } - fn bind_to_tree(&self, tree_in_doc: bool) { - if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); - } - - self.bind_form_control_to_tree(); - } - - fn unbind_from_tree(&self, context: &UnbindContext) { - self.super_type().unwrap().unbind_from_tree(context); - self.unbind_form_control_from_tree(); - } - fn handle_event(&self, event: &Event) { if (event.type_() == atom!("click")) { let area_elements = self.areas(); diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 3ee70640fa59..db26905b3b40 100755 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -1071,15 +1071,12 @@ impl VirtualMethods for HTMLInputElement { s.bind_to_tree(tree_in_doc); } - self.bind_form_control_to_tree(); self.upcast::().check_ancestors_disabled_state_for_form_control(); } fn unbind_from_tree(&self, context: &UnbindContext) { self.super_type().unwrap().unbind_from_tree(context); - self.unbind_form_control_from_tree(); - let node = self.upcast::(); let el = self.upcast::(); if node.ancestors().any(|ancestor| ancestor.is::()) { diff --git a/components/script/dom/htmllabelelement.rs b/components/script/dom/htmllabelelement.rs index 556294f7e8fb..49bb10eba395 100644 --- a/components/script/dom/htmllabelelement.rs +++ b/components/script/dom/htmllabelelement.rs @@ -15,7 +15,7 @@ use dom::event::Event; use dom::eventtarget::EventTarget; use dom::htmlelement::HTMLElement; use dom::htmlformelement::{FormControl, FormControlElementHelpers, HTMLFormElement}; -use dom::node::{document_from_node, Node, UnbindContext}; +use dom::node::{document_from_node, Node}; use dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; use html5ever_atoms::LocalName; @@ -139,19 +139,6 @@ impl VirtualMethods for HTMLLabelElement { _ => {}, } } - - fn bind_to_tree(&self, tree_in_doc: bool) { - if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); - } - - self.bind_form_control_to_tree(); - } - - fn unbind_from_tree(&self, context: &UnbindContext) { - self.super_type().unwrap().unbind_from_tree(context); - self.unbind_form_control_from_tree(); - } } impl HTMLLabelElement { diff --git a/components/script/dom/htmllegendelement.rs b/components/script/dom/htmllegendelement.rs index 0590114a2f6b..86b34c27dd02 100644 --- a/components/script/dom/htmllegendelement.rs +++ b/components/script/dom/htmllegendelement.rs @@ -56,15 +56,12 @@ impl VirtualMethods for HTMLLegendElement { s.bind_to_tree(tree_in_doc); } - self.bind_form_control_to_tree(); self.upcast::().check_ancestors_disabled_state_for_form_control(); } fn unbind_from_tree(&self, context: &UnbindContext) { self.super_type().unwrap().unbind_from_tree(context); - self.unbind_form_control_from_tree(); - let node = self.upcast::(); let el = self.upcast::(); if node.ancestors().any(|ancestor| ancestor.is::()) { diff --git a/components/script/dom/htmlobjectelement.rs b/components/script/dom/htmlobjectelement.rs index 1c1eaab23432..c80a78381983 100755 --- a/components/script/dom/htmlobjectelement.rs +++ b/components/script/dom/htmlobjectelement.rs @@ -13,7 +13,7 @@ use dom::document::Document; use dom::element::{AttributeMutation, Element}; use dom::htmlelement::HTMLElement; use dom::htmlformelement::{FormControl, HTMLFormElement}; -use dom::node::{Node, UnbindContext, window_from_node}; +use dom::node::{Node, window_from_node}; use dom::validation::Validatable; use dom::validitystate::{ValidityState, ValidationFlags}; use dom::virtualmethods::VirtualMethods; @@ -123,19 +123,6 @@ impl VirtualMethods for HTMLObjectElement { _ => {}, } } - - fn bind_to_tree(&self, tree_in_doc: bool) { - if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); - } - - self.bind_form_control_to_tree(); - } - - fn unbind_from_tree(&self, context: &UnbindContext) { - self.super_type().unwrap().unbind_from_tree(context); - self.unbind_form_control_from_tree(); - } } impl FormControl for HTMLObjectElement { diff --git a/components/script/dom/htmloutputelement.rs b/components/script/dom/htmloutputelement.rs index 77d7cd192ace..719efad95d3f 100644 --- a/components/script/dom/htmloutputelement.rs +++ b/components/script/dom/htmloutputelement.rs @@ -12,7 +12,7 @@ use dom::document::Document; use dom::element::{AttributeMutation, Element}; use dom::htmlelement::HTMLElement; use dom::htmlformelement::{FormControl, HTMLFormElement}; -use dom::node::{Node, UnbindContext, window_from_node}; +use dom::node::{Node, window_from_node}; use dom::nodelist::NodeList; use dom::validitystate::ValidityState; use dom::virtualmethods::VirtualMethods; @@ -78,19 +78,6 @@ impl VirtualMethods for HTMLOutputElement { _ => {}, } } - - fn bind_to_tree(&self, tree_in_doc: bool) { - if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); - } - - self.bind_form_control_to_tree(); - } - - fn unbind_from_tree(&self, context: &UnbindContext) { - self.super_type().unwrap().unbind_from_tree(context); - self.unbind_form_control_from_tree(); - } } impl FormControl for HTMLOutputElement { diff --git a/components/script/dom/htmlselectelement.rs b/components/script/dom/htmlselectelement.rs index 5f233c7cda1b..2d4dc4758b60 100755 --- a/components/script/dom/htmlselectelement.rs +++ b/components/script/dom/htmlselectelement.rs @@ -374,15 +374,12 @@ impl VirtualMethods for HTMLSelectElement { s.bind_to_tree(tree_in_doc); } - self.bind_form_control_to_tree(); self.upcast::().check_ancestors_disabled_state_for_form_control(); } fn unbind_from_tree(&self, context: &UnbindContext) { self.super_type().unwrap().unbind_from_tree(context); - self.unbind_form_control_from_tree(); - let node = self.upcast::(); let el = self.upcast::(); if node.ancestors().any(|ancestor| ancestor.is::()) { diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index fa50827b7512..8a4e9f41cd37 100755 --- a/components/script/dom/htmltextareaelement.rs +++ b/components/script/dom/htmltextareaelement.rs @@ -358,7 +358,6 @@ impl VirtualMethods for HTMLTextAreaElement { s.bind_to_tree(tree_in_doc); } - self.bind_form_control_to_tree(); self.upcast::().check_ancestors_disabled_state_for_form_control(); } @@ -373,8 +372,6 @@ impl VirtualMethods for HTMLTextAreaElement { fn unbind_from_tree(&self, context: &UnbindContext) { self.super_type().unwrap().unbind_from_tree(context); - self.unbind_form_control_from_tree(); - let node = self.upcast::(); let el = self.upcast::(); if node.ancestors().any(|ancestor| ancestor.is::()) { From 438c0f85c3385eb0832a5754e2240f5f7a032bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Wed, 15 Feb 2017 17:32:14 +0300 Subject: [PATCH 10/10] Address review comments --- Cargo.lock | 8 +-- components/script/dom/document.rs | 3 +- components/script/dom/htmlformelement.rs | 63 ++++++++++--------- components/script/dom/htmlimageelement.rs | 2 +- components/script/dom/htmllabelelement.rs | 6 +- components/script/dom/node.rs | 11 +++- tests/wpt/metadata/MANIFEST.json | 30 +++++++++ tests/wpt/mozilla/meta/MANIFEST.json | 30 --------- .../form_attribute.html | 16 ++--- .../form_owner_and_table.html | 0 .../form_owner_and_table_2.html | 0 11 files changed, 88 insertions(+), 81 deletions(-) rename tests/wpt/{mozilla/tests/mozilla => web-platform-tests/html/semantics/forms/form-control-infrastructure}/form_attribute.html (95%) rename tests/wpt/{mozilla/tests/mozilla => web-platform-tests/html/semantics/forms/form-control-infrastructure}/form_owner_and_table.html (100%) rename tests/wpt/{mozilla/tests/mozilla => web-platform-tests/html/semantics/forms/form-control-infrastructure}/form_owner_and_table_2.html (100%) diff --git a/Cargo.lock b/Cargo.lock index a16e0e9cbd68..8b868cf3e9e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1117,8 +1117,8 @@ dependencies = [ [[package]] name = "html5ever" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.13.1" +source = "git+https://github.com/canaltinova/html5ever?branch=form-owner-test#3d2febfc3a8c8e9b0f56f4ca328a7452ff53cdce" dependencies = [ "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2219,7 +2219,7 @@ dependencies = [ "gfx_traits 0.0.1", "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "html5ever 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "html5ever 0.13.1 (git+https://github.com/canaltinova/html5ever?branch=form-owner-test)", "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)", @@ -3424,7 +3424,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.0 (registry+https://github.com/rust-lang/crates.io-index)" = "43c1b50a0c2b4f697c025699ae33b2b4072fff5b6cbd7c184e25eea2beccb3ef" +"checksum html5ever 0.13.1 (git+https://github.com/canaltinova/html5ever?branch=form-owner-test)" = "" "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" diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 7115d4efb530..09e58a5e88bf 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -631,7 +631,6 @@ impl Document { } } - /// Attempt to find a named element in this page's document. /// https://html.spec.whatwg.org/multipage/#the-indicated-part-of-the-document pub fn find_fragment_node(&self, fragid: &str) -> Option> { @@ -2058,7 +2057,7 @@ impl Document { ignore_destructive_writes_counter: Default::default(), dom_count: Cell::new(1), fullscreen_element: MutNullableJS::new(None), - form_id_listener_map: DOMRefCell::new(HashMap::new()), + form_id_listener_map: Default::default(), } } diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index 5a8d134f2c80..b958e8f12252 100755 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -513,12 +513,11 @@ impl HTMLFormElement { let controls = self.controls.borrow(); let mut data_set = Vec::new(); for child in controls.iter() { - let child = child.upcast::(); // Step 3.1: The field element is disabled. - match child.downcast::() { - Some(el) if !el.disabled_state() => (), - _ => continue, + if child.disabled_state() { + continue; } + let child = child.upcast::(); // Step 3.1: The field element has a datalist element ancestor. if child.ancestors() @@ -653,7 +652,6 @@ impl HTMLFormElement { } NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOutputElement)) => { // Unimplemented - {} } _ => {} } @@ -871,7 +869,7 @@ pub trait FormControl: DomObject { fn to_element<'a>(&'a self) -> &'a Element; - fn is_reassociatable(&self) -> bool { + fn is_listed(&self) -> bool { true } @@ -898,13 +896,13 @@ pub trait FormControl: DomObject { .next(); // Step 1 - if old_owner.is_some() && !(self.is_reassociatable() && has_form_id) { + if old_owner.is_some() && !(self.is_listed() && has_form_id) { if nearest_form_ancestor == old_owner { return; } } - let new_owner = if self.is_reassociatable() && has_form_id && node.is_in_doc() { + 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")); @@ -928,28 +926,40 @@ pub trait FormControl: DomObject { // https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms fn form_attribute_mutated(&self, mutation: AttributeMutation) { - let elem = self.to_element(); - let form_id = elem.get_string_attribute(&local_name!("form")); - match mutation { AttributeMutation::Set(_) => { - let node = elem.upcast::(); - if self.is_reassociatable() && !form_id.is_empty() && node.is_in_doc() { - let doc = document_from_node(node); - doc.register_form_id_listener(form_id, self); - } + self.register_if_necessary(); }, AttributeMutation::Removed => { - if self.is_reassociatable() && !form_id.is_empty() { - let doc = document_from_node(elem.upcast::()); - doc.unregister_form_id_listener(form_id, self); - } + 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::(); + + 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::()); + 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(); @@ -975,22 +985,14 @@ pub trait FormControl: DomObject { elem.is_in_same_home_subtree(&*form) }); - // FIXME: We should call `form_attribute_mutated` method here, - // but also we don't want to call reset_form_owner() method in - // `form_attribute_mutated`. We should fix this. - // self.form_attribute_mutated(AttributeMutation::Removed); - let form_id = elem.get_string_attribute(&local_name!("form")); - if self.is_reassociatable() && !form_id.is_empty() { - let doc = document_from_node(elem.upcast::()); - doc.unregister_form_id_listener(form_id, self); - } + 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_reassociatable() && has_form_attr) { + if !same_subtree || (self.is_listed() && has_form_attr) { self.reset_form_owner(); } } @@ -1047,7 +1049,6 @@ impl VirtualMethods for HTMLFormElement { // Collect the controls to reset because reset_form_owner // will mutably borrow self.controls - //let mut to_reset: RootedVec> = RootedVec::new(); rooted_vec!(let mut to_reset); to_reset.extend(self.controls.borrow().iter() .filter(|c| !c.is_in_same_home_subtree(self)) diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs index 106fbb3a609d..40fcc54ffa4e 100644 --- a/components/script/dom/htmlimageelement.rs +++ b/components/script/dom/htmlimageelement.rs @@ -698,7 +698,7 @@ impl FormControl for HTMLImageElement { self.upcast::() } - fn is_reassociatable(&self) -> bool { + fn is_listed(&self) -> bool { false } } diff --git a/components/script/dom/htmllabelelement.rs b/components/script/dom/htmllabelelement.rs index 49bb10eba395..be1cda2d8d0f 100644 --- a/components/script/dom/htmllabelelement.rs +++ b/components/script/dom/htmllabelelement.rs @@ -153,13 +153,11 @@ impl HTMLLabelElement { impl FormControl for HTMLLabelElement { fn form_owner(&self) -> Option> { - if let Some(e) = self.GetControl() { + self.GetControl().and_then(|e| { let form_control = e.upcast::().as_maybe_form_control() .expect("Element must be a form control"); form_control.form_owner() - } else { - None - } + }) } fn set_form_owner(&self, _: Option<&HTMLFormElement>) { diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index efd9b4cadf83..bbeb8fc55cc8 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -165,7 +165,7 @@ bitflags! { 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."] + this element. Only applicable for form-associatable elements."] const PARSER_ASSOCIATED_FORM_OWNER = 0x90, } } @@ -2671,6 +2671,15 @@ pub trait VecPreOrderInsertionHelper { impl VecPreOrderInsertionHelper for Vec> where T: DerivedFrom + 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)); diff --git a/tests/wpt/metadata/MANIFEST.json b/tests/wpt/metadata/MANIFEST.json index b7a485acf495..100d5d80dca3 100644 --- a/tests/wpt/metadata/MANIFEST.json +++ b/tests/wpt/metadata/MANIFEST.json @@ -93553,6 +93553,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", @@ -176690,6 +176708,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" diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index bb3818161c88..434afa7e5a21 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -12754,24 +12754,6 @@ {} ] ], - "mozilla/form_attribute.html": [ - [ - "/_mozilla/mozilla/form_attribute.html", - {} - ] - ], - "mozilla/form_owner_and_table.html": [ - [ - "/_mozilla/mozilla/form_owner_and_table.html", - {} - ] - ], - "mozilla/form_owner_and_table_2.html": [ - [ - "/_mozilla/mozilla/form_owner_and_table_2.html", - {} - ] - ], "mozilla/form_submit_about.html": [ [ "/_mozilla/mozilla/form_submit_about.html", @@ -25253,18 +25235,6 @@ "3407915dc9912d4d681c47ec5227278a198139ab", "testharness" ], - "mozilla/form_attribute.html": [ - "46f783ddbe21c914d452e175083a2ad266475654", - "testharness" - ], - "mozilla/form_owner_and_table.html": [ - "7172d74bb5572091ee09abf30a4f6892b85aca3a", - "testharness" - ], - "mozilla/form_owner_and_table_2.html": [ - "3b1dea35bb4af384125904d69bf569109965714f", - "testharness" - ], "mozilla/form_submit_about.html": [ "06ca608a11dd01908c8040c0060cb4473574acd1", "testharness" diff --git a/tests/wpt/mozilla/tests/mozilla/form_attribute.html b/tests/wpt/web-platform-tests/html/semantics/forms/form-control-infrastructure/form_attribute.html similarity index 95% rename from tests/wpt/mozilla/tests/mozilla/form_attribute.html rename to tests/wpt/web-platform-tests/html/semantics/forms/form-control-infrastructure/form_attribute.html index 07bf0b66b119..dde3250dbf7c 100644 --- a/tests/wpt/mozilla/tests/mozilla/form_attribute.html +++ b/tests/wpt/web-platform-tests/html/semantics/forms/form-control-infrastructure/form_attribute.html @@ -60,19 +60,19 @@ reassociateableElements.forEach(function(localName) { function testControl(test_, desc) { - var control = document.createElement(localName); + 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); + form1 = document.createElement("form"); + form2 = document.createElement("form"); + form1.id = "form1"; + form2.id = "form2"; + placeholder.appendChild(form1); + placeholder.appendChild(form2); - test(function() { test_.call(control); }, "[" + localName.toUpperCase() + "] " + desc); } diff --git a/tests/wpt/mozilla/tests/mozilla/form_owner_and_table.html b/tests/wpt/web-platform-tests/html/semantics/forms/form-control-infrastructure/form_owner_and_table.html similarity index 100% rename from tests/wpt/mozilla/tests/mozilla/form_owner_and_table.html rename to tests/wpt/web-platform-tests/html/semantics/forms/form-control-infrastructure/form_owner_and_table.html diff --git a/tests/wpt/mozilla/tests/mozilla/form_owner_and_table_2.html b/tests/wpt/web-platform-tests/html/semantics/forms/form-control-infrastructure/form_owner_and_table_2.html similarity index 100% rename from tests/wpt/mozilla/tests/mozilla/form_owner_and_table_2.html rename to tests/wpt/web-platform-tests/html/semantics/forms/form-control-infrastructure/form_owner_and_table_2.html