diff --git a/components/script/dom/bindings/js.rs b/components/script/dom/bindings/js.rs index 36a3ce19198e..a91be04e424f 100644 --- a/components/script/dom/bindings/js.rs +++ b/components/script/dom/bindings/js.rs @@ -35,6 +35,7 @@ use script_task::STACK_ROOTS; use core::nonzero::NonZero; use std::cell::{Cell, UnsafeCell}; use std::default::Default; +use std::hash::{Hash, Hasher}; use std::ops::Deref; /// A traced reference to a DOM object. Must only be used as a field in other @@ -102,6 +103,14 @@ impl PartialEq for JS { } } +impl Eq for JS { } + +impl Hash for JS { + fn hash(&self, state: &mut H) where H: Hasher { + self.ptr.hash(state); + } +} + impl PartialEq for LayoutJS { fn eq(&self, other: &LayoutJS) -> bool { self.ptr == other.ptr diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index f7f8c527d9af..9b5a796973f3 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -37,6 +37,7 @@ use script_task::ScriptChan; use canvas_traits::{CanvasGradientStop, LinearGradientStyle, RadialGradientStyle}; use canvas_traits::{LineCapStyle, LineJoinStyle, CompositionOrBlending, RepetitionStyle}; use canvas_traits::WebGLError; +use devtools_traits::TimelineMarkerType; use cssparser::RGBA; use encoding::types::EncodingRef; use euclid::matrix2d::Matrix2D; @@ -260,6 +261,20 @@ impl JSTraceable for HashMap } } +impl JSTraceable for HashSet + where T: Hash + Eq + JSTraceable, + S: HashState, + ::Hasher: Hasher, +{ + #[inline] + fn trace(&self, trc: *mut JSTracer) { + for v in self.iter() { + v.trace(trc); + } + } +} + + impl JSTraceable for (A, B) { #[inline] fn trace(&self, trc: *mut JSTracer) { @@ -282,7 +297,6 @@ no_jsmanaged_fields!(Image, ImageCacheChan, ImageCacheTask, ScriptControlChan); no_jsmanaged_fields!(Atom, Namespace); no_jsmanaged_fields!(Trusted); no_jsmanaged_fields!(PropertyDeclarationBlock); -no_jsmanaged_fields!(HashSet); // These three are interdependent, if you plan to put jsmanaged data // in one of these make sure it is propagated properly to containing structs no_jsmanaged_fields!(SubpageId, WindowSizeData, PipelineId); @@ -303,6 +317,7 @@ no_jsmanaged_fields!(LineCapStyle, LineJoinStyle, CompositionOrBlending); no_jsmanaged_fields!(RepetitionStyle); no_jsmanaged_fields!(WebGLError); no_jsmanaged_fields!(ProfilerChan); +no_jsmanaged_fields!(TimelineMarkerType); impl JSTraceable for Box { #[inline] diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 69575884afe4..6b2ea6d9f0dd 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -47,6 +47,7 @@ use dom::eventtarget::{EventTarget, EventTargetTypeId, EventTargetHelpers}; use dom::htmlanchorelement::HTMLAnchorElement; use dom::htmlcollection::{HTMLCollection, CollectionFilter}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; +use dom::htmlformelement::{FormControl, FormControlElementHelpers}; use dom::htmlheadelement::HTMLHeadElement; use dom::htmlhtmlelement::HTMLHtmlElement; use dom::htmlscriptelement::HTMLScriptElement; @@ -55,6 +56,7 @@ use dom::mouseevent::MouseEvent; use dom::keyboardevent::KeyboardEvent; use dom::messageevent::MessageEvent; use dom::node::{self, Node, NodeHelpers, NodeTypeId, CloneChildrenFlag, NodeDamage, window_from_node}; +use dom::node::VecPreOrderInsertionHelper; use dom::nodelist::NodeList; use dom::nodeiterator::NodeIterator; use dom::text::Text; @@ -90,7 +92,7 @@ use js::jsapi::{JSContext, JSObject, JSRuntime}; use num::ToPrimitive; use std::iter::FromIterator; use std::borrow::ToOwned; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::ascii::AsciiExt; use std::cell::{Cell, Ref, RefMut, RefCell}; @@ -149,6 +151,12 @@ pub struct Document { current_parser: MutNullableHeap>, /// When we should kick off a reflow. This happens during parsing. reflow_timeout: Cell>, + /// 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>>>, } impl PartialEq for Document { @@ -287,6 +295,8 @@ pub trait DocumentHelpers<'a> { fn finish_load(self, load: LoadType); fn set_current_parser(self, script: Option<&ServoHTMLParser>); fn get_current_parser(self) -> Option>; + fn register_form_id_listener(self, id: DOMString, listener: &T); + fn unregister_form_id_listener(self, id: DOMString, listener: &T); } impl<'a> DocumentHelpers<'a> for &'a Document { @@ -400,21 +410,27 @@ impl<'a> DocumentHelpers<'a> for &'a Document { to_unregister: &Element, id: Atom) { debug!("Removing named element from document {:p}: {:p} id={}", self, to_unregister, id); - let mut idmap = self.idmap.borrow_mut(); - let is_empty = match idmap.get_mut(&id) { - None => false, - Some(elements) => { - let position = elements.iter() - .map(|elem| elem.root()) - .position(|element| element.r() == to_unregister) - .expect("This element should be in registered."); - elements.remove(position); - elements.is_empty() + // Limit the scope of the borrow because idmap might be borrowed again by + // GetElementById through the following sequence of calls + // reset_form_owner_for_listeners -> reset_form_owner -> GetElementById + { + let mut idmap = self.idmap.borrow_mut(); + let is_empty = match idmap.get_mut(&id) { + None => false, + Some(elements) => { + let position = elements.iter() + .map(|elem| elem.root()) + .position(|element| element.r() == to_unregister) + .expect("This element should be in registered."); + elements.remove(position); + elements.is_empty() + } + }; + if is_empty { + idmap.remove(&id); } - }; - if is_empty { - idmap.remove(&id); } + self.reset_form_owner_for_listeners(&id); } /// Associate an element present in this document with the provided id. @@ -428,34 +444,38 @@ impl<'a> DocumentHelpers<'a> for &'a Document { }); assert!(!id.is_empty()); - let mut idmap = self.idmap.borrow_mut(); - let root = self.GetDocumentElement().expect( "The element is in the document, so there must be a document element."); - match idmap.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 idmap might be borrowed again by + // GetElementById through the following sequence of calls + // reset_form_owner_for_listeners -> reset_form_owner -> GetElementById + { + let mut idmap = self.idmap.borrow_mut(); + let mut elements = idmap.entry(id.clone()).or_insert(Vec::new()); + elements.insert_pre_order(element, NodeCast::from_ref(root.r())); + } - let new_node = NodeCast::from_ref(element); - let mut head: usize = 0; - let root = NodeCast::from_ref(root.r()); - for node in root.traverse_preorder() { - if let Some(elem) = ElementCast::to_ref(node.r()) { - if (*elements)[head].root().r() == elem { - head += 1; - } - if new_node == node.r() || head == elements.len() { - break; - } - } - } + self.reset_form_owner_for_listeners(&id); + } - elements.insert(head, JS::from_ref(element)); + 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_slice(&id)).or_insert(HashSet::new()); + set.insert(JS::from_ref(listener)); + } + + fn unregister_form_id_listener(self, id: DOMString, listener: &T) { + let mut map = self.form_id_listener_map.borrow_mut(); + match map.entry(Atom::from_slice(&id)) { + Occupied(mut entry) => { + entry.get_mut().remove(&JS::from_ref(listener.to_element())); + if entry.get().is_empty() { + entry.remove(); + } } + Vacant(_) => () } } @@ -1073,6 +1093,7 @@ impl Document { loader: DOMRefCell::new(doc_loader), current_parser: Default::default(), reflow_timeout: Cell::new(None), + form_id_listener_map: DOMRefCell::new(HashMap::new()), } } @@ -1110,6 +1131,7 @@ impl Document { trait PrivateDocumentHelpers { fn create_node_list bool>(self, callback: F) -> Root; fn get_html_element(self) -> Option>; + fn reset_form_owner_for_listeners(self, id: &Atom); } impl<'a> PrivateDocumentHelpers for &'a Document { @@ -1128,6 +1150,18 @@ impl<'a> PrivateDocumentHelpers for &'a Document { .and_then(HTMLHtmlElementCast::to_ref) .map(Root::from_ref) } + + 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 { + let listener = listener.root(); + listener.r().as_maybe_form_control() + .expect("Element must be a form control") + .reset_form_owner(); + } + } + } } trait PrivateClickEventHelpers { diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index ff9864c88b8f..2d1cc748407c 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -16,7 +16,8 @@ use dom::bindings::codegen::Bindings::EventBinding::EventMethods; use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; use dom::bindings::codegen::Bindings::NamedNodeMapBinding::NamedNodeMapMethods; use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::codegen::InheritTypes::{ElementCast, ElementDerived, EventTargetCast}; +use dom::bindings::codegen::InheritTypes::{ElementBase, ElementCast, ElementDerived}; +use dom::bindings::codegen::InheritTypes::{EventTargetCast}; use dom::bindings::codegen::InheritTypes::{HTMLBodyElementDerived, HTMLFontElementDerived}; use dom::bindings::codegen::InheritTypes::{HTMLIFrameElementDerived, HTMLInputElementCast}; use dom::bindings::codegen::InheritTypes::{HTMLInputElementDerived, HTMLTableElementCast}; @@ -32,6 +33,7 @@ use dom::bindings::error::Error::NoModificationAllowed; use dom::bindings::js::{JS, LayoutJS, MutNullableHeap}; use dom::bindings::js::{Root, RootedReference}; use dom::bindings::trace::RootedVec; +use dom::bindings::utils::Reflectable; use dom::bindings::utils::{namespace_from_domstring, xml_name_type, validate_and_extract}; use dom::bindings::utils::XMLName::InvalidXMLName; use dom::create::create_element; @@ -554,6 +556,8 @@ pub trait ElementHelpers<'a> { fn serialize(self, traversal_scope: TraversalScope) -> Fallible; fn get_root_element(self) -> Root; fn lookup_prefix(self, namespace: Namespace) -> Option; + fn is_in_same_home_subtree(self, other: &T) -> bool + where T: ElementBase + Reflectable; } impl<'a> ElementHelpers<'a> for &'a Element { @@ -740,6 +744,15 @@ impl<'a> ElementHelpers<'a> for &'a Element { } None } + + // https://html.spec.whatwg.org/multipage/#home-subtree + fn is_in_same_home_subtree(self, other: &T) -> bool + where T: ElementBase + Reflectable + { + let other = ElementCast::from_ref(other); + self.get_root_element() == other.get_root_element() + } + } pub trait FocusElementHelpers { diff --git a/components/script/dom/htmlbuttonelement.rs b/components/script/dom/htmlbuttonelement.rs index 0ba1cb740e40..e69976570f5a 100644 --- a/components/script/dom/htmlbuttonelement.rs +++ b/components/script/dom/htmlbuttonelement.rs @@ -9,23 +9,27 @@ use dom::bindings::codegen::Bindings::HTMLButtonElementBinding; use dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElementMethods; use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, HTMLButtonElementCast, NodeCast}; use dom::bindings::codegen::InheritTypes::{HTMLButtonElementDerived, HTMLFieldSetElementDerived}; -use dom::bindings::js::Root; +use dom::bindings::js::{JS, MutNullableHeap, Root}; use dom::document::Document; use dom::element::{AttributeHandlers, Element, ElementTypeId}; use dom::element::ActivationElementHelpers; use dom::event::Event; use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; -use dom::htmlformelement::{FormSubmitter, FormControl, HTMLFormElementHelpers}; +use dom::htmlformelement::{FormSubmitter, FormControl}; +use dom::htmlformelement::{HTMLFormElement, HTMLFormElementHelpers}; use dom::htmlformelement::{SubmittedFrom}; use dom::node::{DisabledStateHelpers, Node, NodeHelpers, NodeTypeId, document_from_node, window_from_node}; use dom::validitystate::ValidityState; use dom::virtualmethods::VirtualMethods; +use util::str::DOMString; +use string_cache::Atom; + use std::ascii::OwnedAsciiExt; use std::borrow::ToOwned; -use util::str::DOMString; use std::cell::Cell; +use std::default::Default; #[derive(JSTraceable, PartialEq, Copy, Clone)] #[allow(dead_code)] @@ -39,7 +43,8 @@ enum ButtonType { #[dom_struct] pub struct HTMLButtonElement { htmlelement: HTMLElement, - button_type: Cell + button_type: Cell, + form_owner: MutNullableHeap> } impl HTMLButtonElementDerived for EventTarget { @@ -58,7 +63,8 @@ impl HTMLButtonElement { htmlelement: HTMLElement::new_inherited(HTMLElementTypeId::HTMLButtonElement, localName, prefix, document), //TODO: implement button_type in after_set_attr - button_type: Cell::new(ButtonType::ButtonSubmit) + button_type: Cell::new(ButtonType::ButtonSubmit), + form_owner: Default::default(), } } @@ -97,6 +103,11 @@ impl<'a> HTMLButtonElementMethods for &'a HTMLButtonElement { // https://html.spec.whatwg.org/multipage/#dom-button-type make_setter!(SetType, "type"); + // https://html.spec.whatwg.org/multipage/#dom-fae-form + fn GetForm(self) -> Option> { + self.form_owner() + } + // https://html.spec.whatwg.org/multipage/#htmlbuttonelement make_url_or_base_getter!(FormAction); @@ -145,6 +156,9 @@ impl<'a> VirtualMethods for &'a HTMLButtonElement { node.set_disabled_state(true); node.set_enabled_state(false); }, + &atom!("form") => { + self.after_set_form_attr(); + } _ => () } } @@ -161,6 +175,22 @@ impl<'a> VirtualMethods for &'a HTMLButtonElement { node.set_enabled_state(true); node.check_ancestors_disabled_state_for_form_control(); }, + &atom!("form") => { + self.before_remove_form_attr(); + } + _ => () + } + } + + fn after_remove_attr(&self, attr: &Atom) { + if let Some(ref s) = self.super_type() { + s.after_remove_attr(attr); + } + + match attr { + &atom!("form") => { + self.after_remove_form_attr(); + } _ => () } } @@ -170,6 +200,8 @@ impl<'a> VirtualMethods for &'a HTMLButtonElement { s.bind_to_tree(tree_in_doc); } + self.bind_form_control_to_tree(); + let node = NodeCast::from_ref(*self); node.check_ancestors_disabled_state_for_form_control(); } @@ -179,6 +211,8 @@ impl<'a> VirtualMethods for &'a HTMLButtonElement { s.unbind_from_tree(tree_in_doc); } + self.unbind_form_control_from_tree(); + let node = NodeCast::from_ref(*self); if node.ancestors().any(|ancestor| ancestor.r().is_htmlfieldsetelement()) { node.check_ancestors_disabled_state_for_form_control(); @@ -188,9 +222,17 @@ impl<'a> VirtualMethods for &'a HTMLButtonElement { } } -impl<'a> FormControl<'a> for &'a HTMLButtonElement { - fn to_element(self) -> &'a Element { - ElementCast::from_ref(self) +impl<'a> FormControl for &'a HTMLButtonElement { + fn form_owner(&self) -> Option> { + self.form_owner.get().map(Root::from_rooted) + } + + fn set_form_owner(&self, form: Option<&HTMLFormElement>) { + self.form_owner.set(form.map(JS::from_ref)); + } + + fn to_element<'b>(&'b self) -> &'b Element { + ElementCast::from_ref(*self) } } diff --git a/components/script/dom/htmlfieldsetelement.rs b/components/script/dom/htmlfieldsetelement.rs index 706f892c07b0..be4227ed6155 100644 --- a/components/script/dom/htmlfieldsetelement.rs +++ b/components/script/dom/htmlfieldsetelement.rs @@ -6,24 +6,29 @@ use dom::attr::Attr; use dom::attr::AttrHelpers; use dom::bindings::codegen::Bindings::HTMLFieldSetElementBinding; use dom::bindings::codegen::Bindings::HTMLFieldSetElementBinding::HTMLFieldSetElementMethods; -use dom::bindings::codegen::InheritTypes::{HTMLFieldSetElementDerived, NodeCast}; +use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLFieldSetElementDerived, NodeCast}; use dom::bindings::codegen::InheritTypes::{HTMLElementCast, HTMLLegendElementDerived}; -use dom::bindings::js::{Root, RootedReference}; +use dom::bindings::js::{JS, MutNullableHeap, Root, RootedReference}; use dom::document::Document; use dom::element::{AttributeHandlers, Element, ElementHelpers}; use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::htmlcollection::{HTMLCollection, CollectionFilter}; use dom::element::ElementTypeId; use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; +use dom::htmlformelement::{HTMLFormElement, FormControl}; use dom::node::{DisabledStateHelpers, Node, NodeHelpers, NodeTypeId, window_from_node}; use dom::validitystate::ValidityState; use dom::virtualmethods::VirtualMethods; +use string_cache::Atom; use util::str::{DOMString, StaticStringVec}; +use std::default::Default; + #[dom_struct] pub struct HTMLFieldSetElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, + form_owner: MutNullableHeap>, } impl HTMLFieldSetElementDerived for EventTarget { @@ -40,7 +45,8 @@ impl HTMLFieldSetElement { document: &Document) -> HTMLFieldSetElement { HTMLFieldSetElement { htmlelement: - HTMLElement::new_inherited(HTMLElementTypeId::HTMLFieldSetElement, localName, prefix, document) + HTMLElement::new_inherited(HTMLElementTypeId::HTMLFieldSetElement, localName, prefix, document), + form_owner: Default::default(), } } @@ -76,6 +82,11 @@ impl<'a> HTMLFieldSetElementMethods for &'a HTMLFieldSetElement { ValidityState::new(window.r()) } + // https://html.spec.whatwg.org/multipage/#dom-fae-form + fn GetForm(self) -> Option> { + self.form_owner() + } + // https://www.whatwg.org/html/#dom-fieldset-disabled make_bool_getter!(Disabled); @@ -125,6 +136,9 @@ impl<'a> VirtualMethods for &'a HTMLFieldSetElement { } } }, + &atom!("form") => { + self.after_set_form_attr(); + }, _ => () } } @@ -165,8 +179,53 @@ impl<'a> VirtualMethods for &'a HTMLFieldSetElement { } } }, + &atom!("form") => { + self.before_remove_form_attr(); + }, _ => () } } + + fn after_remove_attr(&self, attr: &Atom) { + if let Some(ref s) = self.super_type() { + s.after_remove_attr(attr); + } + + match attr { + &atom!("form") => { + self.after_remove_form_attr(); + } + _ => () + } + } + + 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, tree_in_doc: bool) { + if let Some(ref s) = self.super_type() { + s.unbind_from_tree(tree_in_doc); + } + + self.unbind_form_control_from_tree(); + } } +impl<'a> FormControl for &'a HTMLFieldSetElement { + fn form_owner(&self) -> Option> { + self.form_owner.get().map(Root::from_rooted) + } + + fn set_form_owner(&self, form: Option<&HTMLFormElement>) { + self.form_owner.set(form.map(JS::from_ref)); + } + + fn to_element<'b>(&'b self) -> &'b Element { + ElementCast::from_ref(*self) + } +} diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index bfa5792327ad..f008beac9541 100644 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -3,23 +3,33 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use dom::attr::AttrValue; +use dom::bindings::cell::DOMRefCell; use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; use dom::bindings::codegen::Bindings::EventBinding::EventMethods; 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::HTMLButtonElementBinding::HTMLButtonElementMethods; +use dom::bindings::codegen::InheritTypes::ElementCast; use dom::bindings::codegen::InheritTypes::EventTargetCast; +use dom::bindings::codegen::InheritTypes::HTMLButtonElementCast; use dom::bindings::codegen::InheritTypes::HTMLDataListElementCast; use dom::bindings::codegen::InheritTypes::HTMLElementCast; +use dom::bindings::codegen::InheritTypes::HTMLFieldSetElementCast; use dom::bindings::codegen::InheritTypes::HTMLFormElementCast; use dom::bindings::codegen::InheritTypes::HTMLFormElementDerived; +use dom::bindings::codegen::InheritTypes::HTMLImageElementCast; use dom::bindings::codegen::InheritTypes::HTMLInputElementCast; +use dom::bindings::codegen::InheritTypes::HTMLLabelElementCast; +use dom::bindings::codegen::InheritTypes::HTMLObjectElementCast; +use dom::bindings::codegen::InheritTypes::HTMLOutputElementCast; +use dom::bindings::codegen::InheritTypes::HTMLSelectElementCast; use dom::bindings::codegen::InheritTypes::{HTMLTextAreaElementCast, NodeCast}; use dom::bindings::global::GlobalRef; -use dom::bindings::js::{Root}; +use dom::bindings::js::{JS, Root, RootedReference}; +use dom::bindings::trace::RootedVec; use dom::document::{Document, DocumentHelpers}; -use dom::element::{Element, AttributeHandlers}; +use dom::element::{Element, ElementHelpers, AttributeHandlers}; use dom::event::{Event, EventHelpers, EventBubbles, EventCancelable}; use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::element::ElementTypeId; @@ -28,6 +38,7 @@ use dom::htmlinputelement::{HTMLInputElement, HTMLInputElementHelpers}; use dom::htmlbuttonelement::{HTMLButtonElement}; use dom::htmltextareaelement::HTMLTextAreaElementHelpers; use dom::node::{Node, NodeHelpers, NodeTypeId, document_from_node, window_from_node}; +use dom::node::{VecPreOrderInsertionHelper, PARSER_ASSOCIATED_FORM_OWNER}; use dom::virtualmethods::VirtualMethods; use hyper::method::Method; use hyper::header::ContentType; @@ -47,6 +58,7 @@ use std::cell::Cell; pub struct HTMLFormElement { htmlelement: HTMLElement, marked_for_reset: Cell, + controls: DOMRefCell>>, } impl PartialEq for HTMLFormElement { @@ -70,6 +82,7 @@ impl HTMLFormElement { HTMLFormElement { htmlelement: HTMLElement::new_inherited(HTMLElementTypeId::HTMLFormElement, localName, prefix, document), marked_for_reset: Cell::new(false), + controls: DOMRefCell::new(Vec::new()), } } @@ -169,6 +182,9 @@ pub trait HTMLFormElementHelpers { fn get_form_dataset(self, submitter: Option) -> Vec; // https://html.spec.whatwg.org/multipage/#dom-form-reset fn reset(self, submit_method_flag: ResetFrom); + + fn add_control(self, control: &T); + fn remove_control(self, control: &T); } impl<'a> HTMLFormElementHelpers for &'a HTMLFormElement { @@ -268,21 +284,21 @@ impl<'a> HTMLFormElementHelpers for &'a HTMLFormElement { buf } - let node = NodeCast::from_ref(self); - // TODO: This is an incorrect way of getting controls owned - // by the form, but good enough until html5ever lands - let data_set = node.traverse_preorder().filter_map(|child| { - if child.r().get_disabled_state() { + let controls = self.controls.borrow(); + let data_set = controls.iter().filter_map(|child| { + let child = child.root(); + let child = NodeCast::from_ref(child.r()); + if child.get_disabled_state() { return None; } - if child.r().ancestors() - .any(|a| HTMLDataListElementCast::to_root(a).is_some()) { + if child.ancestors() + .any(|a| HTMLDataListElementCast::to_root(a).is_some()) { return None; } // XXXManishearth don't include it if it is a button but not the submitter - match child.r().type_id() { + match child.type_id() { NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) => { - let input = HTMLInputElementCast::to_ref(child.r()).unwrap(); + let input = HTMLInputElementCast::to_ref(child).unwrap(); let ty = input.Type(); let name = input.Name(); match &*ty { @@ -381,14 +397,13 @@ impl<'a> HTMLFormElementHelpers for &'a HTMLFormElement { return; } - let node = NodeCast::from_ref(self); - - // TODO: This is an incorrect way of getting controls owned - // by the form, but good enough until html5ever lands - for child in node.traverse_preorder() { - match child.r().type_id() { + let controls = self.controls.borrow(); + for child in controls.iter() { + let child = child.root(); + let child = NodeCast::from_ref(child.r()); + match child.type_id() { NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) => { - let input = HTMLInputElementCast::to_ref(child.r()).unwrap(); + let input = HTMLInputElementCast::to_ref(child).unwrap(); input.reset() } // TODO HTMLKeygenElement unimplemented @@ -401,7 +416,7 @@ impl<'a> HTMLFormElementHelpers for &'a HTMLFormElement { {} } NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) => { - let textarea = HTMLTextAreaElementCast::to_ref(child.r()).unwrap(); + let textarea = HTMLTextAreaElementCast::to_ref(child).unwrap(); textarea.reset() } NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOutputElement)) => { @@ -413,6 +428,23 @@ impl<'a> HTMLFormElementHelpers for &'a HTMLFormElement { }; self.marked_for_reset.set(false); } + + fn add_control(self, control: &T) { + let elem = ElementCast::from_ref(self); + let root = elem.get_root_element(); + let root = NodeCast::from_ref(root.r()); + + 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().map(|c| c.root()) + .position(|c| c.r() == control) + .map(|idx| controls.remove(idx)); + } } // TODO: add file support @@ -526,33 +558,125 @@ impl<'a> FormSubmitter<'a> { } } -pub trait FormControl<'a> : Copy + Sized { - // 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 { + + 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(&atom!("form")); - if !owner.is_empty() { - let doc = document_from_node(elem); - let owner = doc.r().GetElementById(owner); - match owner { - Some(ref o) => { - let maybe_form = HTMLFormElementCast::to_ref(o.r()); - if maybe_form.is_some() { - return maybe_form.map(Root::from_ref); - } - }, - _ => () + let node = NodeCast::from_ref(elem); + 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 = NodeCast::from_ref(elem); + let old_owner = self.form_owner(); + let has_form_id = elem.has_attribute(&atom!(form)); + let nearest_form_ancestor = node.ancestors() + .filter_map(HTMLFormElementCast::to_root) + .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(&atom!(form)); + doc.GetElementById(form_id).and_then(HTMLFormElementCast::to_root) + } else { + // Step 4 + nearest_form_ancestor + }; + + if old_owner != new_owner { + old_owner.r().map(|o| o.remove_control(self)); + new_owner.r().map(|o| o.add_control(self)); + self.set_form_owner(new_owner.r()); + } + } + + // https://html.spec.whatwg.org/multipage/#form-owner + fn after_set_form_attr(&self) { + let elem = self.to_element(); + let form_id = elem.get_string_attribute(&atom!(form)); let node = NodeCast::from_ref(elem); - for ancestor in node.ancestors() { - if let Some(ancestor) = HTMLFormElementCast::to_ref(ancestor.r()) { - return Some(Root::from_ref(ancestor)) - } + + 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(); + } + + fn before_remove_form_attr(&self) { + let elem = self.to_element(); + let form_id = elem.get_string_attribute(&atom!(form)); + + if self.is_reassociatable() && !form_id.is_empty() { + let doc = document_from_node(NodeCast::from_ref(elem)); + doc.unregister_form_id_listener(form_id, self); + } + } + + fn after_remove_form_attr(&self) { + self.reset_form_owner(); + } + + fn bind_form_control_to_tree(&self) { + let elem = self.to_element(); + let node = NodeCast::from_ref(elem); + + // 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.after_set_form_attr(); + } + } + + fn unbind_form_control_from_tree(&self) { + let elem = self.to_element(); + let has_form_attr = elem.has_attribute(&atom!(form)); + let same_subtree = self.form_owner().map_or(true, |form| { + elem.is_in_same_home_subtree(form.r()) + }); + + self.before_remove_form_attr(); + + // 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(); } - None } fn get_form_attribute(self, @@ -561,7 +685,8 @@ pub trait FormControl<'a> : Copy + Sized { 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) @@ -569,8 +694,6 @@ pub trait FormControl<'a> : Copy + Sized { self.form_owner().map_or("".to_owned(), |t| owner(t.r())) } } - - fn to_element(self) -> &'a Element; } impl<'a> VirtualMethods for &'a HTMLFormElement { @@ -584,4 +707,76 @@ impl<'a> VirtualMethods for &'a HTMLFormElement { _ => self.super_type().unwrap().parse_plain_attribute(name, value), } } + + fn unbind_from_tree(&self, tree_in_doc: bool) { + if let Some(ref s) = self.super_type() { + s.unbind_from_tree(tree_in_doc); + } + + // Collect the controls to reset because reset_form_owner + // will mutably borrow self.controls + let mut to_reset: RootedVec> = RootedVec::new(); + to_reset.extend(self.controls.borrow().iter() + .filter(|c| !c.root().is_in_same_home_subtree(*self)) + .map(|c| c.clone())); + + for control in to_reset.iter() { + let control = control.root(); + control.r().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<'a> FormControlElementHelpers for &'a Element { + fn as_maybe_form_control<'b>(&'b self) -> Option<&'b FormControl> { + let node = NodeCast::from_ref(*self); + + match node.type_id() { + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLButtonElement)) => { + let element = HTMLButtonElementCast::to_borrowed_ref(self).unwrap(); + Some(element as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLFieldSetElement)) => { + let element = HTMLFieldSetElementCast::to_borrowed_ref(self).unwrap(); + Some(element as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLImageElement)) => { + let element = HTMLImageElementCast::to_borrowed_ref(self).unwrap(); + Some(element as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) => { + let element = HTMLInputElementCast::to_borrowed_ref(self).unwrap(); + Some(element as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLabelElement)) => { + let element = HTMLLabelElementCast::to_borrowed_ref(self).unwrap(); + Some(element as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLObjectElement)) => { + let element = HTMLObjectElementCast::to_borrowed_ref(self).unwrap(); + Some(element as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOutputElement)) => { + let element = HTMLOutputElementCast::to_borrowed_ref(self).unwrap(); + Some(element as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) => { + let element = HTMLSelectElementCast::to_borrowed_ref(self).unwrap(); + Some(element as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) => { + let element = HTMLTextAreaElementCast::to_borrowed_ref(self).unwrap(); + Some(element as &FormControl) + }, + _ => { + None + } + } + } } diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs index 6580e4f42a5f..51efb5124a36 100644 --- a/components/script/dom/htmlimageelement.rs +++ b/components/script/dom/htmlimageelement.rs @@ -12,14 +12,15 @@ use dom::bindings::codegen::InheritTypes::{NodeCast, ElementCast, EventTargetCas HTMLImageElementDerived}; use dom::bindings::error::Fallible; use dom::bindings::global::GlobalRef; -use dom::bindings::js::{LayoutJS, Root}; +use dom::bindings::js::{JS, LayoutJS, MutNullableHeap, Root}; use dom::bindings::refcounted::Trusted; use dom::document::{Document, DocumentHelpers}; use dom::element::AttributeHandlers; use dom::eventtarget::{EventTarget, EventTargetTypeId}; -use dom::element::ElementTypeId; +use dom::element::{Element, ElementTypeId}; use dom::event::{Event, EventBubbles, EventCancelable, EventHelpers}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; +use dom::htmlformelement::{FormControl, HTMLFormElement}; use dom::node::{document_from_node, Node, NodeTypeId, NodeHelpers, NodeDamage, window_from_node}; use dom::virtualmethods::VirtualMethods; use dom::window::WindowHelpers; @@ -38,6 +39,7 @@ pub struct HTMLImageElement { htmlelement: HTMLElement, url: DOMRefCell>, image: DOMRefCell>>, + form_owner: MutNullableHeap>, } impl HTMLImageElementDerived for EventTarget { @@ -143,6 +145,7 @@ impl HTMLImageElement { htmlelement: HTMLElement::new_inherited(HTMLElementTypeId::HTMLImageElement, localName, prefix, document), url: DOMRefCell::new(None), image: DOMRefCell::new(None), + form_owner: Default::default(), } } @@ -320,5 +323,38 @@ impl<'a> VirtualMethods for &'a HTMLImageElement { _ => self.super_type().unwrap().parse_plain_attribute(name, value), } } + + 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, tree_in_doc: bool) { + if let Some(ref s) = self.super_type() { + s.unbind_from_tree(tree_in_doc); + } + + self.unbind_form_control_from_tree(); + } } +impl<'a> FormControl for &'a HTMLImageElement { + fn form_owner(&self) -> Option> { + self.form_owner.get().map(Root::from_rooted) + } + + fn set_form_owner(&self, form: Option<&HTMLFormElement>) { + self.form_owner.set(form.map(JS::from_ref)); + } + + fn to_element<'b>(&'b self) -> &'b Element { + ElementCast::from_ref(*self) + } + + fn is_reassociatable(&self) -> bool { + false + } +} diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 2f3db69b43dc..e66f28fdb755 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -15,7 +15,7 @@ use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, HTMLInp use dom::bindings::codegen::InheritTypes::{HTMLInputElementDerived, HTMLFieldSetElementDerived, EventTargetCast}; use dom::bindings::codegen::InheritTypes::KeyboardEventCast; use dom::bindings::global::GlobalRef; -use dom::bindings::js::{JS, LayoutJS, Root, RootedReference}; +use dom::bindings::js::{JS, LayoutJS, MutNullableHeap, Root, RootedReference}; use dom::document::{Document, DocumentHelpers}; use dom::element::{AttributeHandlers, Element}; use dom::element::{RawLayoutElementHelpers, ActivationElementHelpers}; @@ -24,7 +24,8 @@ use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::element::ElementTypeId; use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::keyboardevent::KeyboardEvent; -use dom::htmlformelement::{FormSubmitter, FormControl, HTMLFormElement, HTMLFormElementHelpers}; +use dom::htmlformelement::{FormSubmitter, FormControl}; +use dom::htmlformelement::{HTMLFormElement, HTMLFormElementHelpers}; use dom::htmlformelement::{SubmittedFrom, ResetFrom}; use dom::node::{DisabledStateHelpers, Node, NodeHelpers, NodeDamage, NodeTypeId}; use dom::node::{document_from_node, window_from_node}; @@ -71,6 +72,7 @@ pub struct HTMLInputElement { size: Cell, textinput: DOMRefCell>, activation_state: DOMRefCell, + form_owner: MutNullableHeap> } impl PartialEq for HTMLInputElement { @@ -128,7 +130,8 @@ impl HTMLInputElement { value_changed: Cell::new(false), size: Cell::new(DEFAULT_INPUT_SIZE), textinput: DOMRefCell::new(TextInput::new(Single, "".to_owned(), chan)), - activation_state: DOMRefCell::new(InputActivationState::new()) + activation_state: DOMRefCell::new(InputActivationState::new()), + form_owner: Default::default(), } } @@ -294,6 +297,11 @@ impl<'a> HTMLInputElementMethods for &'a HTMLInputElement { // https://html.spec.whatwg.org/multipage/#attr-input-placeholder make_setter!(SetPlaceholder, "placeholder"); + // https://html.spec.whatwg.org/multipage/#dom-fae-form + fn GetForm(self) -> Option> { + self.form_owner() + } + // https://html.spec.whatwg.org/multipage/#dom-input-formaction make_url_or_base_getter!(FormAction); @@ -512,6 +520,9 @@ impl<'a> VirtualMethods for &'a HTMLInputElement { self.radio_group_updated(Some(&value)); } } + &atom!("form") => { + self.after_set_form_attr(); + } _ if attr.local_name() == &Atom::from_slice("placeholder") => { let value = attr.value(); let stripped = value.chars() @@ -563,6 +574,9 @@ impl<'a> VirtualMethods for &'a HTMLInputElement { self.radio_group_updated(None); } } + &atom!("form") => { + self.before_remove_form_attr(); + } _ if attr.local_name() == &Atom::from_slice("placeholder") => { self.placeholder.borrow_mut().clear(); } @@ -570,6 +584,19 @@ impl<'a> VirtualMethods for &'a HTMLInputElement { } } + fn after_remove_attr(&self, attr: &Atom) { + if let Some(ref s) = self.super_type() { + s.after_remove_attr(attr); + } + + match attr { + &atom!("form") => { + self.after_remove_form_attr(); + } + _ => () + } + } + fn parse_plain_attribute(&self, name: &Atom, value: DOMString) -> AttrValue { match name { &atom!("size") => AttrValue::from_limited_u32(value, DEFAULT_INPUT_SIZE), @@ -582,6 +609,8 @@ impl<'a> VirtualMethods for &'a HTMLInputElement { s.bind_to_tree(tree_in_doc); } + self.bind_form_control_to_tree(); + let node = NodeCast::from_ref(*self); node.check_ancestors_disabled_state_for_form_control(); } @@ -591,6 +620,8 @@ impl<'a> VirtualMethods for &'a HTMLInputElement { s.unbind_from_tree(tree_in_doc); } + self.unbind_form_control_from_tree(); + let node = NodeCast::from_ref(*self); if node.ancestors().any(|ancestor| ancestor.r().is_htmlfieldsetelement()) { node.check_ancestors_disabled_state_for_form_control(); @@ -644,9 +675,17 @@ impl<'a> VirtualMethods for &'a HTMLInputElement { } } -impl<'a> FormControl<'a> for &'a HTMLInputElement { - fn to_element(self) -> &'a Element { - ElementCast::from_ref(self) +impl<'a> FormControl for &'a HTMLInputElement { + fn form_owner(&self) -> Option> { + self.form_owner.get().map(Root::from_rooted) + } + + fn set_form_owner(&self, form: Option<&HTMLFormElement>) { + self.form_owner.set(form.map(JS::from_ref)); + } + + fn to_element<'b>(&'b self) -> &'b Element { + ElementCast::from_ref(*self) } } diff --git a/components/script/dom/htmllabelelement.rs b/components/script/dom/htmllabelelement.rs index 45d10e9bf890..38e6239d25e5 100644 --- a/components/script/dom/htmllabelelement.rs +++ b/components/script/dom/htmllabelelement.rs @@ -2,19 +2,28 @@ * 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::attr::AttrHelpers; use dom::bindings::codegen::Bindings::HTMLLabelElementBinding; +use dom::bindings::codegen::Bindings::HTMLLabelElementBinding::HTMLLabelElementMethods; +use dom::bindings::codegen::InheritTypes::ElementCast; +use dom::bindings::codegen::InheritTypes::HTMLElementCast; use dom::bindings::codegen::InheritTypes::HTMLLabelElementDerived; -use dom::bindings::js::Root; +use dom::bindings::js::{JS, MutNullableHeap, Root}; use dom::document::Document; use dom::eventtarget::{EventTarget, EventTargetTypeId}; -use dom::element::ElementTypeId; +use dom::element::{Element, ElementTypeId}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; +use dom::htmlformelement::{HTMLFormElement, FormControl}; use dom::node::{Node, NodeTypeId}; +use dom::virtualmethods::VirtualMethods; +use string_cache::Atom; use util::str::DOMString; #[dom_struct] pub struct HTMLLabelElement { htmlelement: HTMLElement, + form_owner: MutNullableHeap>, } impl HTMLLabelElementDerived for EventTarget { @@ -31,7 +40,8 @@ impl HTMLLabelElement { document: &Document) -> HTMLLabelElement { HTMLLabelElement { htmlelement: - HTMLElement::new_inherited(HTMLElementTypeId::HTMLLabelElement, localName, prefix, document) + HTMLElement::new_inherited(HTMLElementTypeId::HTMLLabelElement, localName, prefix, document), + form_owner: Default::default(), } } @@ -44,3 +54,85 @@ impl HTMLLabelElement { } } +impl<'a> HTMLLabelElementMethods for &'a HTMLLabelElement { + // https://html.spec.whatwg.org/multipage/#dom-fae-form + fn GetForm(self) -> Option> { + self.form_owner() + } +} + +impl<'a> VirtualMethods for &'a HTMLLabelElement { + fn super_type<'b>(&'b self) -> Option<&'b VirtualMethods> { + let htmlelement: &&HTMLElement = HTMLElementCast::from_borrowed_ref(self); + Some(htmlelement as &VirtualMethods) + } + + fn after_set_attr(&self, attr: &Attr) { + if let Some(ref s) = self.super_type() { + s.after_set_attr(attr); + } + + match attr.local_name() { + &atom!("form") => { + self.after_set_form_attr(); + }, + _ => () + } + } + + fn before_remove_attr(&self, attr: &Attr) { + if let Some(ref s) = self.super_type() { + s.before_remove_attr(attr); + } + + match attr.local_name() { + &atom!("form") => { + self.before_remove_form_attr(); + }, + _ => () + } + } + + fn after_remove_attr(&self, attr: &Atom) { + if let Some(ref s) = self.super_type() { + s.after_remove_attr(attr); + } + + match attr { + &atom!("form") => { + self.after_remove_form_attr(); + } + _ => () + } + } + + 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, tree_in_doc: bool) { + if let Some(ref s) = self.super_type() { + s.unbind_from_tree(tree_in_doc); + } + + self.unbind_form_control_from_tree(); + } +} + +impl<'a> FormControl for &'a HTMLLabelElement { + fn form_owner(&self) -> Option> { + self.form_owner.get().map(Root::from_rooted) + } + + fn set_form_owner(&self, form: Option<&HTMLFormElement>) { + self.form_owner.set(form.map(JS::from_ref)); + } + + fn to_element<'b>(&'b self) -> &'b Element { + ElementCast::from_ref(*self) + } +} diff --git a/components/script/dom/htmlobjectelement.rs b/components/script/dom/htmlobjectelement.rs index 3ea6db1bd4ea..ddbdcece9d0c 100644 --- a/components/script/dom/htmlobjectelement.rs +++ b/components/script/dom/htmlobjectelement.rs @@ -10,24 +10,30 @@ use dom::bindings::codegen::Bindings::HTMLObjectElementBinding; use dom::bindings::codegen::Bindings::HTMLObjectElementBinding::HTMLObjectElementMethods; use dom::bindings::codegen::InheritTypes::HTMLObjectElementDerived; use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast}; -use dom::bindings::js::Root; +use dom::bindings::js::{JS, MutNullableHeap, Root}; use dom::document::Document; +use dom::element::Element; use dom::element::AttributeHandlers; use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::element::ElementTypeId; use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; +use dom::htmlformelement::{HTMLFormElement, FormControl}; use dom::node::{Node, NodeTypeId, NodeHelpers, window_from_node}; use dom::validitystate::ValidityState; use dom::virtualmethods::VirtualMethods; use net_traits::image::base::Image; +use string_cache::Atom; use util::str::DOMString; + use std::sync::Arc; +use std::default::Default; #[dom_struct] pub struct HTMLObjectElement { htmlelement: HTMLElement, image: DOMRefCell>>, + form_owner: MutNullableHeap>, } impl HTMLObjectElementDerived for EventTarget { @@ -46,6 +52,7 @@ impl HTMLObjectElement { htmlelement: HTMLElement::new_inherited(HTMLElementTypeId::HTMLObjectElement, localName, prefix, document), image: DOMRefCell::new(None), + form_owner: Default::default(), } } @@ -90,6 +97,11 @@ impl<'a> HTMLObjectElementMethods for &'a HTMLObjectElement { ValidityState::new(window.r()) } + // https://html.spec.whatwg.org/multipage/#dom-fae-form + fn GetForm(self) -> Option> { + self.form_owner() + } + // https://html.spec.whatwg.org/multipage/#dom-object-type make_getter!(Type); @@ -112,8 +124,66 @@ impl<'a> VirtualMethods for &'a HTMLObjectElement { &atom!("data") => { self.process_data_url(); }, + &atom!("form") => { + self.after_set_form_attr(); + }, _ => () } } + + fn before_remove_attr(&self, attr: &Attr) { + if let Some(ref s) = self.super_type() { + s.before_remove_attr(attr); + } + + match attr.local_name() { + &atom!("form") => { + self.before_remove_form_attr(); + }, + _ => () + } + } + + fn after_remove_attr(&self, attr: &Atom) { + if let Some(ref s) = self.super_type() { + s.after_remove_attr(attr); + } + + match attr { + &atom!("form") => { + self.after_remove_form_attr(); + } + _ => () + } + } + + 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, tree_in_doc: bool) { + if let Some(ref s) = self.super_type() { + s.unbind_from_tree(tree_in_doc); + } + + self.unbind_form_control_from_tree(); + } } +impl<'a> FormControl for &'a HTMLObjectElement { + fn form_owner(&self) -> Option> { + self.form_owner.get().map(Root::from_rooted) + } + + fn set_form_owner(&self, form: Option<&HTMLFormElement>) { + self.form_owner.set(form.map(JS::from_ref)); + } + + fn to_element<'b>(&'b self) -> &'b Element { + ElementCast::from_ref(*self) + } +} diff --git a/components/script/dom/htmloutputelement.rs b/components/script/dom/htmloutputelement.rs index 9b6ac86e365c..936681f0baad 100644 --- a/components/script/dom/htmloutputelement.rs +++ b/components/script/dom/htmloutputelement.rs @@ -2,21 +2,28 @@ * 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, AttrHelpers}; use dom::bindings::codegen::Bindings::HTMLOutputElementBinding; use dom::bindings::codegen::Bindings::HTMLOutputElementBinding::HTMLOutputElementMethods; +use dom::bindings::codegen::InheritTypes::ElementCast; +use dom::bindings::codegen::InheritTypes::HTMLElementCast; use dom::bindings::codegen::InheritTypes::HTMLOutputElementDerived; -use dom::bindings::js::Root; +use dom::bindings::js::{JS, MutNullableHeap, Root}; use dom::document::Document; use dom::eventtarget::{EventTarget, EventTargetTypeId}; -use dom::element::ElementTypeId; +use dom::element::{Element, ElementTypeId}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; +use dom::htmlformelement::{HTMLFormElement, FormControl}; use dom::node::{Node, NodeTypeId, window_from_node}; use dom::validitystate::ValidityState; +use dom::virtualmethods::VirtualMethods; +use string_cache::Atom; use util::str::DOMString; #[dom_struct] pub struct HTMLOutputElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, + form_owner: MutNullableHeap>, } impl HTMLOutputElementDerived for EventTarget { @@ -33,7 +40,8 @@ impl HTMLOutputElement { document: &Document) -> HTMLOutputElement { HTMLOutputElement { htmlelement: - HTMLElement::new_inherited(HTMLElementTypeId::HTMLOutputElement, localName, prefix, document) + HTMLElement::new_inherited(HTMLElementTypeId::HTMLOutputElement, localName, prefix, document), + form_owner: Default::default(), } } @@ -51,5 +59,87 @@ impl<'a> HTMLOutputElementMethods for &'a HTMLOutputElement { let window = window_from_node(self); ValidityState::new(window.r()) } + + // https://html.spec.whatwg.org/multipage/#dom-fae-form + fn GetForm(self) -> Option> { + self.form_owner() + } +} + +impl<'a> VirtualMethods for &'a HTMLOutputElement { + fn super_type<'b>(&'b self) -> Option<&'b VirtualMethods> { + let htmlelement: &&HTMLElement = HTMLElementCast::from_borrowed_ref(self); + Some(htmlelement as &VirtualMethods) + } + + fn after_set_attr(&self, attr: &Attr) { + if let Some(ref s) = self.super_type() { + s.after_set_attr(attr); + } + + match attr.local_name() { + &atom!("form") => { + self.after_set_form_attr(); + }, + _ => () + } + } + + fn before_remove_attr(&self, attr: &Attr) { + if let Some(ref s) = self.super_type() { + s.before_remove_attr(attr); + } + + match attr.local_name() { + &atom!("form") => { + self.before_remove_form_attr(); + }, + _ => () + } + } + + fn after_remove_attr(&self, attr: &Atom) { + if let Some(ref s) = self.super_type() { + s.after_remove_attr(attr); + } + + match attr { + &atom!("form") => { + self.after_remove_form_attr(); + } + _ => () + } + } + + 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, tree_in_doc: bool) { + if let Some(ref s) = self.super_type() { + s.unbind_from_tree(tree_in_doc); + } + + self.unbind_form_control_from_tree(); + } + +} + +impl<'a> FormControl for &'a HTMLOutputElement { + fn form_owner(&self) -> Option> { + self.form_owner.get().map(Root::from_rooted) + } + + fn set_form_owner(&self, form: Option<&HTMLFormElement>) { + self.form_owner.set(form.map(JS::from_ref)); + } + + fn to_element<'b>(&'b self) -> &'b Element { + ElementCast::from_ref(*self) + } } diff --git a/components/script/dom/htmlselectelement.rs b/components/script/dom/htmlselectelement.rs index a33e8d419ba6..af76e96c089f 100644 --- a/components/script/dom/htmlselectelement.rs +++ b/components/script/dom/htmlselectelement.rs @@ -5,16 +5,17 @@ use dom::attr::{Attr, AttrHelpers, AttrValue}; use dom::bindings::codegen::Bindings::HTMLSelectElementBinding; use dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElementMethods; -use dom::bindings::codegen::InheritTypes::{HTMLElementCast, NodeCast}; +use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast}; use dom::bindings::codegen::InheritTypes::{HTMLSelectElementDerived, HTMLFieldSetElementDerived}; use dom::bindings::codegen::UnionTypes::HTMLElementOrLong; use dom::bindings::codegen::UnionTypes::HTMLOptionElementOrHTMLOptGroupElement; -use dom::bindings::js::Root; +use dom::bindings::js::{JS, MutNullableHeap, Root}; use dom::document::Document; -use dom::element::AttributeHandlers; +use dom::element::{Element, AttributeHandlers}; use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::element::ElementTypeId; use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; +use dom::htmlformelement::{HTMLFormElement, FormControl}; use dom::node::{DisabledStateHelpers, Node, NodeHelpers, NodeTypeId, window_from_node}; use dom::validitystate::ValidityState; use dom::virtualmethods::VirtualMethods; @@ -23,10 +24,12 @@ use util::str::DOMString; use string_cache::Atom; use std::borrow::ToOwned; +use std::default::Default; #[dom_struct] pub struct HTMLSelectElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, + form_owner: MutNullableHeap>, } impl HTMLSelectElementDerived for EventTarget { @@ -45,7 +48,8 @@ impl HTMLSelectElement { document: &Document) -> HTMLSelectElement { HTMLSelectElement { htmlelement: - HTMLElement::new_inherited(HTMLElementTypeId::HTMLSelectElement, localName, prefix, document) + HTMLElement::new_inherited(HTMLElementTypeId::HTMLSelectElement, localName, prefix, document), + form_owner: Default::default(), } } @@ -100,6 +104,11 @@ impl<'a> HTMLSelectElementMethods for &'a HTMLSelectElement { "select-one".to_owned() } } + + // https://html.spec.whatwg.org/multipage/#dom-fae-form + fn GetForm(self) -> Option> { + self.form_owner() + } } impl<'a> VirtualMethods for &'a HTMLSelectElement { @@ -119,6 +128,9 @@ impl<'a> VirtualMethods for &'a HTMLSelectElement { node.set_disabled_state(true); node.set_enabled_state(false); }, + &atom!("form") => { + self.after_set_form_attr(); + }, _ => () } } @@ -135,6 +147,22 @@ impl<'a> VirtualMethods for &'a HTMLSelectElement { node.set_enabled_state(true); node.check_ancestors_disabled_state_for_form_control(); }, + &atom!("form") => { + self.before_remove_form_attr(); + }, + _ => () + } + } + + fn after_remove_attr(&self, attr: &Atom) { + if let Some(ref s) = self.super_type() { + s.after_remove_attr(attr); + } + + match attr { + &atom!("form") => { + self.after_remove_form_attr(); + } _ => () } } @@ -144,6 +172,8 @@ impl<'a> VirtualMethods for &'a HTMLSelectElement { s.bind_to_tree(tree_in_doc); } + self.bind_form_control_to_tree(); + let node = NodeCast::from_ref(*self); node.check_ancestors_disabled_state_for_form_control(); } @@ -153,6 +183,8 @@ impl<'a> VirtualMethods for &'a HTMLSelectElement { s.unbind_from_tree(tree_in_doc); } + self.unbind_form_control_from_tree(); + let node = NodeCast::from_ref(*self); if node.ancestors().any(|ancestor| ancestor.r().is_htmlfieldsetelement()) { node.check_ancestors_disabled_state_for_form_control(); @@ -169,3 +201,16 @@ impl<'a> VirtualMethods for &'a HTMLSelectElement { } } +impl<'a> FormControl for &'a HTMLSelectElement { + fn form_owner(&self) -> Option> { + self.form_owner.get().map(Root::from_rooted) + } + + fn set_form_owner(&self, form: Option<&HTMLFormElement>) { + self.form_owner.set(form.map(JS::from_ref)); + } + + fn to_element<'b>(&'b self) -> &'b Element { + ElementCast::from_ref(*self) + } +} diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index 17e646d71edf..477e4aa87120 100644 --- a/components/script/dom/htmltextareaelement.rs +++ b/components/script/dom/htmltextareaelement.rs @@ -13,7 +13,7 @@ use dom::bindings::codegen::InheritTypes::{ElementCast, EventTargetCast, HTMLEle use dom::bindings::codegen::InheritTypes::{HTMLTextAreaElementDerived, HTMLFieldSetElementDerived}; use dom::bindings::codegen::InheritTypes::{KeyboardEventCast, TextDerived}; use dom::bindings::global::GlobalRef; -use dom::bindings::js::{LayoutJS, Root}; +use dom::bindings::js::{JS, LayoutJS, MutNullableHeap, Root}; use dom::bindings::refcounted::Trusted; use dom::document::{Document, DocumentHelpers}; use dom::element::{Element, AttributeHandlers}; @@ -21,7 +21,7 @@ use dom::event::{Event, EventBubbles, EventCancelable}; use dom::eventtarget::{EventTarget, EventTargetHelpers, EventTargetTypeId}; use dom::element::ElementTypeId; use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; -use dom::htmlformelement::FormControl; +use dom::htmlformelement::{HTMLFormElement, FormControl}; use dom::keyboardevent::KeyboardEvent; use dom::node::{DisabledStateHelpers, Node, NodeHelpers, NodeDamage, NodeTypeId}; use dom::node::{document_from_node, window_from_node}; @@ -36,6 +36,7 @@ use string_cache::Atom; use std::borrow::ToOwned; use std::cell::Cell; +use std::default::Default; #[dom_struct] pub struct HTMLTextAreaElement { @@ -45,6 +46,7 @@ pub struct HTMLTextAreaElement { rows: Cell, // https://html.spec.whatwg.org/multipage/#concept-textarea-dirty value_changed: Cell, + form_owner: MutNullableHeap> } impl HTMLTextAreaElementDerived for EventTarget { @@ -104,6 +106,7 @@ impl HTMLTextAreaElement { cols: Cell::new(DEFAULT_COLS), rows: Cell::new(DEFAULT_ROWS), value_changed: Cell::new(false), + form_owner: Default::default(), } } @@ -200,6 +203,11 @@ impl<'a> HTMLTextAreaElementMethods for &'a HTMLTextAreaElement { self.force_relayout(); } + + // https://html.spec.whatwg.org/multipage/#dom-fae-form + fn GetForm(self) -> Option> { + self.form_owner() + } } pub trait HTMLTextAreaElementHelpers { @@ -274,6 +282,9 @@ impl<'a> VirtualMethods for &'a HTMLTextAreaElement { _ => panic!("Expected an AttrValue::UInt"), } }, + &atom!("form") => { + self.after_set_form_attr(); + }, _ => () } } @@ -296,6 +307,22 @@ impl<'a> VirtualMethods for &'a HTMLTextAreaElement { &atom!("rows") => { self.rows.set(DEFAULT_ROWS); }, + &atom!("form") => { + self.before_remove_form_attr(); + } + _ => () + } + } + + fn after_remove_attr(&self, attr: &Atom) { + if let Some(ref s) = self.super_type() { + s.after_remove_attr(attr); + } + + match attr { + &atom!("form") => { + self.after_remove_form_attr(); + } _ => () } } @@ -305,6 +332,8 @@ impl<'a> VirtualMethods for &'a HTMLTextAreaElement { s.bind_to_tree(tree_in_doc); } + self.bind_form_control_to_tree(); + let node = NodeCast::from_ref(*self); node.check_ancestors_disabled_state_for_form_control(); } @@ -322,6 +351,8 @@ impl<'a> VirtualMethods for &'a HTMLTextAreaElement { s.unbind_from_tree(tree_in_doc); } + self.unbind_form_control_from_tree(); + let node = NodeCast::from_ref(*self); if node.ancestors().any(|ancestor| ancestor.r().is_htmlfieldsetelement()) { node.check_ancestors_disabled_state_for_form_control(); @@ -379,9 +410,17 @@ impl<'a> VirtualMethods for &'a HTMLTextAreaElement { } } -impl<'a> FormControl<'a> for &'a HTMLTextAreaElement { - fn to_element(self) -> &'a Element { - ElementCast::from_ref(self) +impl<'a> FormControl for &'a HTMLTextAreaElement { + fn form_owner(&self) -> Option> { + self.form_owner.get().map(Root::from_rooted) + } + + fn set_form_owner(&self, form: Option<&HTMLFormElement>) { + self.form_owner.set(form.map(JS::from_ref)); + } + + fn to_element<'b>(&'b self) -> &'b Element { + ElementCast::from_ref(*self) } } diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index d7432c570b0b..c40dd524a753 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -165,6 +165,9 @@ bitflags! { #[doc = "Specifies whether this node is focusable and whether it is supposed \ to be reachable with using sequential focus navigation."] const SEQUENTIALLY_FOCUSABLE = 0x400, + #[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 = 0x800, } } @@ -309,6 +312,8 @@ impl<'a> PrivateNodeHelpers for &'a Node { assert!(self.parent_node.get().is_none()); for node in self.traverse_preorder() { node.r().set_flag(IS_IN_DOC, false); + } + for node in self.traverse_preorder() { vtable_for(&node.r()).unbind_from_tree(parent_in_doc); } self.layout_data.dispose(self); @@ -2674,3 +2679,33 @@ pub enum NodeDamage { /// Other parts of a node changed; attributes, text content, etc. OtherNodeDamage, } + +/// 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: NodeBase + Reflectable +{ + fn insert_pre_order(&mut self, elem: &T, tree_root: &Node) { + if self.len() == 0 { + self.push(JS::from_ref(elem)); + return; + } + + let elem_node = NodeCast::from_ref(elem); + let mut head: usize = 0; + for node in tree_root.traverse_preorder() { + let head_node = NodeCast::from_root(self[head].root()); + 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/virtualmethods.rs b/components/script/dom/virtualmethods.rs index 8bb62637ac6f..c312fba4ae17 100644 --- a/components/script/dom/virtualmethods.rs +++ b/components/script/dom/virtualmethods.rs @@ -19,7 +19,9 @@ use dom::bindings::codegen::InheritTypes::HTMLIFrameElementCast; use dom::bindings::codegen::InheritTypes::HTMLImageElementCast; use dom::bindings::codegen::InheritTypes::HTMLInputElementCast; use dom::bindings::codegen::InheritTypes::HTMLLinkElementCast; +use dom::bindings::codegen::InheritTypes::HTMLLabelElementCast; use dom::bindings::codegen::InheritTypes::HTMLObjectElementCast; +use dom::bindings::codegen::InheritTypes::HTMLOutputElementCast; use dom::bindings::codegen::InheritTypes::HTMLOptGroupElementCast; use dom::bindings::codegen::InheritTypes::HTMLOptionElementCast; use dom::bindings::codegen::InheritTypes::HTMLScriptElementCast; @@ -177,6 +179,10 @@ pub fn vtable_for<'a>(node: &'a &'a Node) -> &'a (VirtualMethods + 'a) { let element = HTMLInputElementCast::to_borrowed_ref(node).unwrap(); element as &'a (VirtualMethods + 'a) } + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLabelElement)) => { + let element = HTMLLabelElementCast::to_borrowed_ref(node).unwrap(); + element as &'a (VirtualMethods + 'a) + } NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLinkElement)) => { let element = HTMLLinkElementCast::to_borrowed_ref(node).unwrap(); element as &'a (VirtualMethods + 'a) @@ -185,6 +191,10 @@ pub fn vtable_for<'a>(node: &'a &'a Node) -> &'a (VirtualMethods + 'a) { let element = HTMLObjectElementCast::to_borrowed_ref(node).unwrap(); element as &'a (VirtualMethods + 'a) } + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOutputElement)) => { + let element = HTMLOutputElementCast::to_borrowed_ref(node).unwrap(); + element as &'a (VirtualMethods + 'a) + } NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOptGroupElement)) => { let element = HTMLOptGroupElementCast::to_borrowed_ref(node).unwrap(); element as &'a (VirtualMethods + 'a) diff --git a/components/script/dom/webidls/HTMLButtonElement.webidl b/components/script/dom/webidls/HTMLButtonElement.webidl index 7613bd566872..73eec85a1980 100644 --- a/components/script/dom/webidls/HTMLButtonElement.webidl +++ b/components/script/dom/webidls/HTMLButtonElement.webidl @@ -7,7 +7,7 @@ interface HTMLButtonElement : HTMLElement { // attribute boolean autofocus; attribute boolean disabled; - //readonly attribute HTMLFormElement? form; + readonly attribute HTMLFormElement? form; attribute DOMString formAction; attribute DOMString formEnctype; attribute DOMString formMethod; diff --git a/components/script/dom/webidls/HTMLFieldSetElement.webidl b/components/script/dom/webidls/HTMLFieldSetElement.webidl index eb5f4fcc8407..1c65475a8892 100644 --- a/components/script/dom/webidls/HTMLFieldSetElement.webidl +++ b/components/script/dom/webidls/HTMLFieldSetElement.webidl @@ -6,7 +6,7 @@ // https://www.whatwg.org/html/#htmlfieldsetelement interface HTMLFieldSetElement : HTMLElement { attribute boolean disabled; - //readonly attribute HTMLFormElement? form; + readonly attribute HTMLFormElement? form; // attribute DOMString name; //readonly attribute DOMString type; diff --git a/components/script/dom/webidls/HTMLInputElement.webidl b/components/script/dom/webidls/HTMLInputElement.webidl index 81ff5f97426b..b972500fbbad 100644 --- a/components/script/dom/webidls/HTMLInputElement.webidl +++ b/components/script/dom/webidls/HTMLInputElement.webidl @@ -13,7 +13,7 @@ interface HTMLInputElement : HTMLElement { attribute boolean checked; // attribute DOMString dirName; attribute boolean disabled; - //readonly attribute HTMLFormElement? form; + readonly attribute HTMLFormElement? form; //readonly attribute FileList? files; attribute DOMString formAction; attribute DOMString formEnctype; diff --git a/components/script/dom/webidls/HTMLLabelElement.webidl b/components/script/dom/webidls/HTMLLabelElement.webidl index 6794b4673af3..049ee7524fd3 100644 --- a/components/script/dom/webidls/HTMLLabelElement.webidl +++ b/components/script/dom/webidls/HTMLLabelElement.webidl @@ -5,7 +5,7 @@ // https://www.whatwg.org/html/#htmllabelelement interface HTMLLabelElement : HTMLElement { - //readonly attribute HTMLFormElement? form; + readonly attribute HTMLFormElement? form; // attribute DOMString htmlFor; //readonly attribute HTMLElement? control; }; diff --git a/components/script/dom/webidls/HTMLObjectElement.webidl b/components/script/dom/webidls/HTMLObjectElement.webidl index 13f4a5ee285b..2b4e24e61f3b 100644 --- a/components/script/dom/webidls/HTMLObjectElement.webidl +++ b/components/script/dom/webidls/HTMLObjectElement.webidl @@ -10,7 +10,7 @@ interface HTMLObjectElement : HTMLElement { // attribute boolean typeMustMatch; // attribute DOMString name; // attribute DOMString useMap; - //readonly attribute HTMLFormElement? form; + readonly attribute HTMLFormElement? form; // attribute DOMString width; // attribute DOMString height; //readonly attribute Document? contentDocument; diff --git a/components/script/dom/webidls/HTMLOutputElement.webidl b/components/script/dom/webidls/HTMLOutputElement.webidl index 8fd7acb2d229..d618f92e0a97 100644 --- a/components/script/dom/webidls/HTMLOutputElement.webidl +++ b/components/script/dom/webidls/HTMLOutputElement.webidl @@ -6,7 +6,7 @@ // https://www.whatwg.org/html/#htmloutputelement interface HTMLOutputElement : HTMLElement { //[PutForwards=value] readonly attribute DOMSettableTokenList htmlFor; - //readonly attribute HTMLFormElement? form; + readonly attribute HTMLFormElement? form; // attribute DOMString name; //readonly attribute DOMString type; diff --git a/components/script/dom/webidls/HTMLSelectElement.webidl b/components/script/dom/webidls/HTMLSelectElement.webidl index 09f36334e7b8..eb53cf948159 100644 --- a/components/script/dom/webidls/HTMLSelectElement.webidl +++ b/components/script/dom/webidls/HTMLSelectElement.webidl @@ -7,7 +7,7 @@ interface HTMLSelectElement : HTMLElement { // attribute boolean autofocus; attribute boolean disabled; - //readonly attribute HTMLFormElement? form; + readonly attribute HTMLFormElement? form; attribute boolean multiple; attribute DOMString name; // attribute boolean required; diff --git a/components/script/dom/webidls/HTMLTextAreaElement.webidl b/components/script/dom/webidls/HTMLTextAreaElement.webidl index fa70cb4d47a2..8872d12e113b 100644 --- a/components/script/dom/webidls/HTMLTextAreaElement.webidl +++ b/components/script/dom/webidls/HTMLTextAreaElement.webidl @@ -11,7 +11,7 @@ interface HTMLTextAreaElement : HTMLElement { attribute unsigned long cols; // attribute DOMString dirName; attribute boolean disabled; - //readonly attribute HTMLFormElement? form; + readonly attribute HTMLFormElement? form; // attribute DOMString inputMode; // attribute long maxLength; // attribute long minLength; diff --git a/components/script/parse/html.rs b/components/script/parse/html.rs index 6ea2dc443f87..d2db4c5c30dc 100644 --- a/components/script/parse/html.rs +++ b/components/script/parse/html.rs @@ -10,7 +10,8 @@ use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use dom::bindings::codegen::InheritTypes::{CharacterDataCast, DocumentTypeCast}; use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLScriptElementCast}; -use dom::bindings::codegen::InheritTypes::{HTMLFormElementDerived, NodeCast}; +use dom::bindings::codegen::InheritTypes::{HTMLFormElementCast, HTMLFormElementDerived}; +use dom::bindings::codegen::InheritTypes::{NodeCast}; use dom::bindings::codegen::InheritTypes::ProcessingInstructionCast; use dom::bindings::js::{JS, Root}; use dom::bindings::js::{RootedReference}; @@ -20,6 +21,7 @@ use dom::document::{Document, DocumentHelpers}; use dom::document::{DocumentSource, IsHTMLDocument}; use dom::documenttype::DocumentType; use dom::element::{Element, AttributeHandlers, ElementHelpers, ElementCreator}; +use dom::htmlformelement::FormControlElementHelpers; use dom::htmlscriptelement::HTMLScriptElement; use dom::htmlscriptelement::HTMLScriptElementHelpers; use dom::node::{Node, NodeHelpers, NodeTypeId}; @@ -84,6 +86,16 @@ impl<'a> TreeSink for servohtmlparser::Sink { } } + fn same_home_subtree(&self, x: JS, y: JS) -> bool { + let x = x.root(); + let y = y.root(); + + let x = ElementCast::to_root(x).expect("Element node expected"); + let y = ElementCast::to_root(y).expect("Element node expected"); + + x.is_in_same_home_subtree(y.r()) + } + fn create_element(&mut self, name: QualName, attrs: Vec) -> JS { let doc = self.document.root(); @@ -105,19 +117,37 @@ impl<'a> TreeSink for servohtmlparser::Sink { JS::from_rooted(&node) } + fn has_parent_node(&self, node: JS) -> bool { + node.root().GetParentNode().is_some() + } + + + fn associate_with_form(&mut self, target: JS, form: JS) { + let node = target.root(); + let form = form.root(); + let form = HTMLFormElementCast::to_root(form) + .expect("Owner must be a form element"); + + let elem = ElementCast::to_ref(node.r()); + 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.r()); + } 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 sibling: Root = sibling.root(); - let parent = match sibling.r().GetParentNode() { - Some(p) => p, - None => return Err(new_node), - }; + new_node: NodeOrText>) { + let sibling = sibling.root(); + let parent = sibling.GetParentNode() + .expect("append_before_sibling called on node without parent"); let child = self.get_or_create(new_node); - assert!(parent.r().InsertBefore(child.r(), Some(sibling.r())).is_ok()); - Ok(()) + assert!(parent.InsertBefore(child.r(), Some(sibling.r())).is_ok()); } fn parse_error(&mut self, msg: Cow<'static, str>) { diff --git a/tests/wpt/metadata/html/dom/interfaces.html.ini b/tests/wpt/metadata/html/dom/interfaces.html.ini index d000553cacc0..d5176017830e 100644 --- a/tests/wpt/metadata/html/dom/interfaces.html.ini +++ b/tests/wpt/metadata/html/dom/interfaces.html.ini @@ -2856,9 +2856,6 @@ [HTMLObjectElement interface: attribute useMap] expected: FAIL - [HTMLObjectElement interface: attribute form] - expected: FAIL - [HTMLObjectElement interface: attribute width] expected: FAIL @@ -5187,18 +5184,12 @@ [HTMLLabelElement interface: existence and properties of interface object] expected: FAIL - [HTMLLabelElement interface: attribute form] - expected: FAIL - [HTMLLabelElement interface: attribute htmlFor] expected: FAIL [HTMLLabelElement interface: attribute control] expected: FAIL - [HTMLLabelElement interface: document.createElement("label") must inherit property "form" with the proper type (0)] - expected: FAIL - [HTMLLabelElement interface: document.createElement("label") must inherit property "htmlFor" with the proper type (1)] expected: FAIL @@ -5223,9 +5214,6 @@ [HTMLInputElement interface: attribute dirName] expected: FAIL - [HTMLInputElement interface: attribute form] - expected: FAIL - [HTMLInputElement interface: attribute files] expected: FAIL @@ -5352,9 +5340,6 @@ [HTMLInputElement interface: document.createElement("input") must inherit property "dirName" with the proper type (6)] expected: FAIL - [HTMLInputElement interface: document.createElement("input") must inherit property "form" with the proper type (8)] - expected: FAIL - [HTMLInputElement interface: document.createElement("input") must inherit property "files" with the proper type (9)] expected: FAIL @@ -5490,9 +5475,6 @@ [HTMLButtonElement interface: attribute autofocus] expected: FAIL - [HTMLButtonElement interface: attribute form] - expected: FAIL - [HTMLButtonElement interface: attribute formNoValidate] expected: FAIL @@ -5520,9 +5502,6 @@ [HTMLButtonElement interface: document.createElement("button") must inherit property "autofocus" with the proper type (0)] expected: FAIL - [HTMLButtonElement interface: document.createElement("button") must inherit property "form" with the proper type (2)] - expected: FAIL - [HTMLButtonElement interface: document.createElement("button") must inherit property "formNoValidate" with the proper type (6)] expected: FAIL @@ -5559,9 +5538,6 @@ [HTMLSelectElement interface: attribute autofocus] expected: FAIL - [HTMLSelectElement interface: attribute form] - expected: FAIL - [HTMLSelectElement interface: attribute required] expected: FAIL @@ -5616,9 +5592,6 @@ [HTMLSelectElement interface: document.createElement("select") must inherit property "autofocus" with the proper type (1)] expected: FAIL - [HTMLSelectElement interface: document.createElement("select") must inherit property "form" with the proper type (3)] - expected: FAIL - [HTMLSelectElement interface: document.createElement("select") must inherit property "required" with the proper type (6)] expected: FAIL @@ -5721,9 +5694,6 @@ [HTMLTextAreaElement interface: attribute dirName] expected: FAIL - [HTMLTextAreaElement interface: attribute form] - expected: FAIL - [HTMLTextAreaElement interface: attribute inputMode] expected: FAIL @@ -5787,9 +5757,6 @@ [HTMLTextAreaElement interface: document.createElement("textarea") must inherit property "dirName" with the proper type (3)] expected: FAIL - [HTMLTextAreaElement interface: document.createElement("textarea") must inherit property "form" with the proper type (5)] - expected: FAIL - [HTMLTextAreaElement interface: document.createElement("textarea") must inherit property "inputMode" with the proper type (6)] expected: FAIL @@ -5967,9 +5934,6 @@ [HTMLOutputElement interface: attribute htmlFor] expected: FAIL - [HTMLOutputElement interface: attribute form] - expected: FAIL - [HTMLOutputElement interface: attribute name] expected: FAIL @@ -6003,9 +5967,6 @@ [HTMLOutputElement interface: document.createElement("output") must inherit property "htmlFor" with the proper type (0)] expected: FAIL - [HTMLOutputElement interface: document.createElement("output") must inherit property "form" with the proper type (1)] - expected: FAIL - [HTMLOutputElement interface: document.createElement("output") must inherit property "name" with the proper type (2)] expected: FAIL @@ -6114,9 +6075,6 @@ [HTMLFieldSetElement interface: existence and properties of interface object] expected: FAIL - [HTMLFieldSetElement interface: attribute form] - expected: FAIL - [HTMLFieldSetElement interface: attribute name] expected: FAIL 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 4ba65533c662..fc4b144051ed 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 @@ -1,29 +1,5 @@ [form.html] type: testharness - [button.form] - expected: FAIL - - [fieldset.form] - expected: FAIL - - [input.form] - expected: FAIL - [keygen.form] expected: FAIL - [label.form] - expected: FAIL - - [object.form] - expected: FAIL - - [output.form] - expected: FAIL - - [select.form] - expected: FAIL - - [textarea.form] - expected: FAIL - diff --git a/tests/wpt/metadata/html/semantics/forms/the-fieldset-element/HTMLFieldSetElement.html.ini b/tests/wpt/metadata/html/semantics/forms/the-fieldset-element/HTMLFieldSetElement.html.ini index 14a930e34733..57d011290252 100644 --- a/tests/wpt/metadata/html/semantics/forms/the-fieldset-element/HTMLFieldSetElement.html.ini +++ b/tests/wpt/metadata/html/semantics/forms/the-fieldset-element/HTMLFieldSetElement.html.ini @@ -3,9 +3,6 @@ [The type attribute must return 'fieldset'] expected: FAIL - [The form attribute must return the fieldset's form owner] - expected: FAIL - [The elements must return an HTMLFormControlsCollection object] 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 - 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 84e573b74ecd..147e09170a5e 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 @@ -27,9 +27,3 @@ [A form control has no label 2.] expected: FAIL - [A label's form attribute should return its form owner.] - expected: FAIL - - [Check that the labels property of a form control with no label returns a zero-length NodeList.] - expected: FAIL - diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 02ec1797a79e..93875766f965 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -503,6 +503,24 @@ "url": "/_mozilla/mozilla/focus_blur.html" } ], + "mozilla/form_attribute.html": [ + { + "path": "mozilla/form_attribute.html", + "url": "/_mozilla/mozilla/form_attribute.html" + } + ], + "mozilla/form_owner_and_table.html": [ + { + "path": "mozilla/form_owner_and_table.html", + "url": "/_mozilla/mozilla/form_owner_and_table.html" + } + ], + "mozilla/form_owner_and_table_2.html": [ + { + "path": "mozilla/form_owner_and_table_2.html", + "url": "/_mozilla/mozilla/form_owner_and_table_2.html" + } + ], "mozilla/getBoundingClientRect.html": [ { "path": "mozilla/getBoundingClientRect.html", @@ -950,4 +968,4 @@ "rev": null, "url_base": "/_mozilla/", "version": 2 -} \ No newline at end of file +} 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..a3efc3689303 --- /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 @@ + + + + + + + +
+
+ + + + + +
+ + + +
+ + + +