diff options
author | Mukilan Thiyagarajan <mukilanthiagarajan@gmail.com> | 2017-01-28 17:20:01 +0300 |
---|---|---|
committer | Anthony Ramine <n.oxyde@gmail.com> | 2017-03-15 16:39:55 +0100 |
commit | 38a61712e41ddeebb52cace6a9787735947964a4 (patch) | |
tree | f9949282e968e9a8fd57a8c8aa1fd63a59d0a89c /components/script/dom | |
parent | f90e19f7055387a14cabdf11f77335c7763e3fb7 (diff) | |
download | servo-38a61712e41ddeebb52cace6a9787735947964a4.tar.gz servo-38a61712e41ddeebb52cace6a9787735947964a4.zip |
Implement the form owner concept
Diffstat (limited to 'components/script/dom')
-rw-r--r-- | components/script/dom/document.rs | 97 | ||||
-rw-r--r-- | components/script/dom/element.rs | 18 | ||||
-rwxr-xr-x | components/script/dom/htmlbuttonelement.rs | 26 | ||||
-rw-r--r-- | components/script/dom/htmlfieldsetelement.rs | 26 | ||||
-rwxr-xr-x | components/script/dom/htmlformelement.rs | 270 | ||||
-rw-r--r-- | components/script/dom/htmlimageelement.rs | 23 | ||||
-rwxr-xr-x | components/script/dom/htmlinputelement.rs | 21 | ||||
-rw-r--r-- | components/script/dom/htmllabelelement.rs | 36 | ||||
-rw-r--r-- | components/script/dom/htmllegendelement.rs | 22 | ||||
-rwxr-xr-x | components/script/dom/htmlobjectelement.rs | 22 | ||||
-rw-r--r-- | components/script/dom/htmloutputelement.rs | 41 | ||||
-rwxr-xr-x | components/script/dom/htmlselectelement.rs | 49 | ||||
-rwxr-xr-x | components/script/dom/htmltextareaelement.rs | 24 | ||||
-rw-r--r-- | components/script/dom/node.rs | 49 | ||||
-rw-r--r-- | components/script/dom/servoparser/html.rs | 40 | ||||
-rw-r--r-- | components/script/dom/virtualmethods.rs | 4 |
16 files changed, 642 insertions, 126 deletions
diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 29d7227dee9..f0be35377d7 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; @@ -313,6 +314,12 @@ pub struct Document { dom_count: Cell<u32>, /// Entry node for fullscreen. fullscreen_element: MutNullableJS<Element>, + /// Map from ID to set of form control elements that have that ID as + /// their 'form' content attribute. Used to reset form controls + /// whenever any element with the same ID as the form attribute + /// is inserted or removed from the document. + /// See https://html.spec.whatwg.org/multipage/#form-owner + form_id_listener_map: DOMRefCell<HashMap<Atom, HashSet<JS<Element>>>>, } #[derive(JSTraceable, HeapSizeOf)] @@ -576,20 +583,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. @@ -601,34 +614,34 @@ impl Document { assert!(element.upcast::<Node>().is_in_doc()); assert!(!id.is_empty()); - let mut id_map = self.id_map.borrow_mut(); - let root = self.GetDocumentElement() .expect("The element is in the document, so there must be a document \ element."); - match id_map.entry(id) { - Vacant(entry) => { - entry.insert(vec![JS::from_ref(element)]); - } - Occupied(entry) => { - let elements = entry.into_mut(); + // Limit the scope of the borrow because id_map might be borrowed again by + // GetElementById through the following sequence of calls + // reset_form_owner_for_listeners -> reset_form_owner -> GetElementById + { + let mut id_map = self.id_map.borrow_mut(); + let mut elements = id_map.entry(id.clone()).or_insert(Vec::new()); + elements.insert_pre_order(element, root.r().upcast::<Node>()); + } + self.reset_form_owner_for_listeners(&id); + } - let new_node = element.upcast::<Node>(); - let mut head: usize = 0; - let root = root.upcast::<Node>(); - for node in root.traverse_preorder() { - if let Some(elem) = node.downcast() { - if &*(*elements)[head] == elem { - head += 1; - } - if new_node == &*node || head == elements.len() { - break; - } - } - } + pub fn register_form_id_listener<T: ?Sized + FormControl>(&self, id: DOMString, listener: &T) { + let mut map = self.form_id_listener_map.borrow_mut(); + let listener = listener.to_element(); + let mut set = map.entry(Atom::from(id)).or_insert(HashSet::new()); + set.insert(JS::from_ref(listener)); + } - elements.insert(head, JS::from_ref(element)); + pub fn unregister_form_id_listener<T: ?Sized + FormControl>(&self, id: DOMString, listener: &T) { + let mut map = self.form_id_listener_map.borrow_mut(); + if let Occupied(mut entry) = map.entry(Atom::from(id)) { + entry.get_mut().remove(&JS::from_ref(listener.to_element())); + if entry.get().is_empty() { + entry.remove(); } } } @@ -2101,6 +2114,7 @@ impl Document { spurious_animation_frames: Cell::new(0), dom_count: Cell::new(1), fullscreen_element: MutNullableJS::new(None), + form_id_listener_map: Default::default(), } } @@ -2414,6 +2428,17 @@ impl Document { } } } + + fn reset_form_owner_for_listeners(&self, id: &Atom) { + let map = self.form_id_listener_map.borrow(); + if let Some(listeners) = map.get(id) { + for listener in listeners { + listener.r().as_maybe_form_control() + .expect("Element must be a form control") + .reset_form_owner(); + } + } + } } diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index a0fb1d0f179..33b2ccb9b7f 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}; @@ -44,6 +45,7 @@ use dom::htmlcollection::HTMLCollection; use dom::htmlelement::HTMLElement; use dom::htmlfieldsetelement::HTMLFieldSetElement; use dom::htmlfontelement::{HTMLFontElement, HTMLFontElementLayoutHelpers}; +use dom::htmlformelement::FormControlElementHelpers; use dom::htmlhrelement::{HTMLHRElement, HTMLHRLayoutHelpers}; use dom::htmliframeelement::{HTMLIFrameElement, HTMLIFrameElementLayoutMethods}; use dom::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelpers}; @@ -1360,6 +1362,14 @@ impl Element { let document = document_from_node(self); document.get_allow_fullscreen() } + + // https://html.spec.whatwg.org/multipage/#home-subtree + pub fn is_in_same_home_subtree<T>(&self, other: &T) -> bool + where T: DerivedFrom<Element> + DomObject + { + let other = other.upcast::<Element>(); + self.root_element() == other.root_element() + } } impl ElementMethods for Element { @@ -2240,6 +2250,10 @@ impl VirtualMethods for Element { s.bind_to_tree(tree_in_doc); } + if let Some(f) = self.as_maybe_form_control() { + f.bind_form_control_to_tree(); + } + if !tree_in_doc { return; } @@ -2255,6 +2269,10 @@ impl VirtualMethods for Element { fn unbind_from_tree(&self, context: &UnbindContext) { self.super_type().unwrap().unbind_from_tree(context); + if let Some(f) = self.as_maybe_form_control() { + f.unbind_form_control_from_tree(); + } + if !context.tree_in_doc { return; } diff --git a/components/script/dom/htmlbuttonelement.rs b/components/script/dom/htmlbuttonelement.rs index 56a614cf608..b36b276bfd7 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<ButtonType> + button_type: Cell<ButtonType>, + form_owner: MutNullableJS<HTMLFormElement>, } impl HTMLButtonElement { @@ -51,7 +53,8 @@ impl HTMLButtonElement { htmlelement: HTMLElement::new_inherited_with_state(IN_ENABLED_STATE, local_name, prefix, document), - button_type: Cell::new(ButtonType::Submit) + button_type: Cell::new(ButtonType::Submit), + form_owner: Default::default(), } } @@ -211,6 +214,9 @@ impl VirtualMethods for HTMLButtonElement { self.button_type.set(ButtonType::Submit); } } + }, + &local_name!("form") => { + self.form_attribute_mutated(mutation); } _ => {}, } @@ -237,7 +243,19 @@ impl VirtualMethods for HTMLButtonElement { } } -impl FormControl for HTMLButtonElement {} +impl FormControl for HTMLButtonElement { + fn form_owner(&self) -> Option<Root<HTMLFormElement>> { + self.form_owner.get() + } + + fn set_form_owner(&self, form: Option<&HTMLFormElement>) { + self.form_owner.set(form); + } + + fn to_element<'a>(&'a self) -> &'a Element { + self.upcast::<Element>() + } +} impl Validatable for HTMLButtonElement { fn is_instance_validatable(&self) -> bool { diff --git a/components/script/dom/htmlfieldsetelement.rs b/components/script/dom/htmlfieldsetelement.rs index 19dbc8d4f1b..9fdc65a513c 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}; @@ -19,11 +19,13 @@ use dom::validitystate::ValidityState; use dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; use html5ever_atoms::LocalName; +use std::default::Default; use style::element_state::*; #[dom_struct] pub struct HTMLFieldSetElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, + form_owner: MutNullableJS<HTMLFormElement>, } impl HTMLFieldSetElement { @@ -33,7 +35,8 @@ impl HTMLFieldSetElement { HTMLFieldSetElement { htmlelement: HTMLElement::new_inherited_with_state(IN_ENABLED_STATE, - local_name, prefix, document) + local_name, prefix, document), + form_owner: Default::default(), } } @@ -148,9 +151,24 @@ impl VirtualMethods for HTMLFieldSetElement { } } }, + &local_name!("form") => { + self.form_attribute_mutated(mutation); + }, _ => {}, } } } -impl FormControl for HTMLFieldSetElement {} +impl FormControl for HTMLFieldSetElement { + fn form_owner(&self) -> Option<Root<HTMLFormElement>> { + self.form_owner.get() + } + + fn set_form_owner(&self, form: Option<&HTMLFormElement>) { + self.form_owner.set(form); + } + + fn to_element<'a>(&'a self) -> &'a Element { + self.upcast::<Element>() + } +} diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index 67d80acdcab..b958e8f1225 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,15 +12,14 @@ use dom::bindings::codegen::Bindings::HTMLFormElementBinding; use dom::bindings::codegen::Bindings::HTMLFormElementBinding::HTMLFormElementMethods; use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; use dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods; -use dom::bindings::conversions::DerivedFrom; use dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId}; -use dom::bindings::js::{MutNullableJS, Root}; +use dom::bindings::js::{JS, MutNullableJS, Root, RootedReference}; use dom::bindings::refcounted::Trusted; use dom::bindings::reflector::DomObject; use dom::bindings::str::DOMString; use dom::blob::Blob; use dom::document::Document; -use dom::element::Element; +use dom::element::{AttributeMutation, Element}; use dom::eventtarget::EventTarget; use dom::file::File; use dom::globalscope::GlobalScope; @@ -29,12 +29,16 @@ use dom::htmldatalistelement::HTMLDataListElement; use dom::htmlelement::HTMLElement; use dom::htmlfieldsetelement::HTMLFieldSetElement; use dom::htmlformcontrolscollection::HTMLFormControlsCollection; +use dom::htmlimageelement::HTMLImageElement; use dom::htmlinputelement::HTMLInputElement; +use dom::htmllabelelement::HTMLLabelElement; +use dom::htmllegendelement::HTMLLegendElement; use dom::htmlobjectelement::HTMLObjectElement; use dom::htmloutputelement::HTMLOutputElement; use dom::htmlselectelement::HTMLSelectElement; use dom::htmltextareaelement::HTMLTextAreaElement; -use dom::node::{Node, document_from_node, window_from_node}; +use dom::node::{Node, PARSER_ASSOCIATED_FORM_OWNER, UnbindContext, VecPreOrderInsertionHelper}; +use dom::node::{document_from_node, window_from_node}; use dom::validitystate::ValidationFlags; use dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; @@ -63,7 +67,8 @@ pub struct HTMLFormElement { htmlelement: HTMLElement, marked_for_reset: Cell<bool>, elements: MutNullableJS<HTMLFormControlsCollection>, - generation_id: Cell<GenerationId> + generation_id: Cell<GenerationId>, + controls: DOMRefCell<Vec<JS<Element>>>, } impl HTMLFormElement { @@ -74,7 +79,8 @@ impl HTMLFormElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), marked_for_reset: Cell::new(false), elements: Default::default(), - generation_id: Cell::new(GenerationId(0)) + generation_id: Cell::new(GenerationId(0)), + controls: DOMRefCell::new(Vec::new()), } } @@ -504,16 +510,14 @@ impl HTMLFormElement { /// https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set /// Steps range from 1 to 3 fn get_unclean_dataset(&self, submitter: Option<FormSubmitter>) -> Vec<FormDatum> { - let node = self.upcast::<Node>(); - // FIXME(#3553): This is an incorrect way of getting controls owned - // by the form, but good enough until html5ever lands + let controls = self.controls.borrow(); let mut data_set = Vec::new(); - for child in node.traverse_preorder() { + for child in controls.iter() { // Step 3.1: The field element is disabled. - match child.downcast::<Element>() { - Some(el) if !el.disabled_state() => (), - _ => continue, + if child.disabled_state() { + continue; } + let child = child.upcast::<Node>(); // Step 3.1: The field element has a datalist element ancestor. if child.ancestors() @@ -627,9 +631,10 @@ impl HTMLFormElement { return; } - // TODO: This is an incorrect way of getting controls owned - // by the form, but good enough until html5ever lands - for child in self.upcast::<Node>().traverse_preorder() { + let controls = self.controls.borrow(); + for child in controls.iter() { + let child = child.upcast::<Node>(); + match child.type_id() { NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) => { child.downcast::<HTMLInputElement>().unwrap().reset(); @@ -647,14 +652,27 @@ impl HTMLFormElement { } NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOutputElement)) => { // Unimplemented - {} } _ => {} } - }; + } self.marked_for_reset.set(false); } + fn add_control<T: ?Sized + FormControl>(&self, control: &T) { + let root = self.upcast::<Element>().root_element(); + let root = root.r().upcast::<Node>(); + + let mut controls = self.controls.borrow_mut(); + controls.insert_pre_order(control.to_element(), root); + } + + fn remove_control<T: ?Sized + FormControl>(&self, control: &T) { + let control = control.to_element(); + let mut controls = self.controls.borrow_mut(); + controls.iter().position(|c| c.r() == control) + .map(|idx| controls.remove(idx)); + } } #[derive(JSTraceable, HeapSizeOf, Clone)] @@ -844,24 +862,139 @@ impl<'a> FormSubmitter<'a> { } } -pub trait FormControl: DerivedFrom<Element> + DomObject { - // FIXME: This is wrong (https://github.com/servo/servo/issues/3553) - // but we need html5ever to do it correctly - fn form_owner(&self) -> Option<Root<HTMLFormElement>> { - // https://html.spec.whatwg.org/multipage/#reset-the-form-owner +pub trait FormControl: DomObject { + fn form_owner(&self) -> Option<Root<HTMLFormElement>>; + + fn set_form_owner(&self, form: Option<&HTMLFormElement>); + + fn to_element<'a>(&'a self) -> &'a Element; + + fn is_listed(&self) -> bool { + true + } + + // https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token + // Part of step 12. + // '..suppress the running of the reset the form owner algorithm + // when the parser subsequently attempts to insert the element..' + fn set_form_owner_from_parser(&self, form: &HTMLFormElement) { let elem = self.to_element(); - let owner = elem.get_string_attribute(&local_name!("form")); - if !owner.is_empty() { - let doc = document_from_node(elem); - let owner = doc.GetElementById(owner); - if let Some(ref o) = owner { - let maybe_form = o.downcast::<HTMLFormElement>(); - if maybe_form.is_some() { - return maybe_form.map(Root::from_ref); - } + let node = elem.upcast::<Node>(); + node.set_flag(PARSER_ASSOCIATED_FORM_OWNER, true); + form.add_control(self); + self.set_form_owner(Some(form)); + } + + // https://html.spec.whatwg.org/multipage/#reset-the-form-owner + fn reset_form_owner(&self) { + let elem = self.to_element(); + let node = elem.upcast::<Node>(); + let old_owner = self.form_owner(); + let has_form_id = elem.has_attribute(&local_name!("form")); + let nearest_form_ancestor = node.ancestors() + .filter_map(Root::downcast::<HTMLFormElement>) + .next(); + + // Step 1 + if old_owner.is_some() && !(self.is_listed() && has_form_id) { + if nearest_form_ancestor == old_owner { + return; } } - elem.upcast::<Node>().ancestors().filter_map(Root::downcast).next() + + let new_owner = if self.is_listed() && has_form_id && elem.is_connected() { + // Step 3 + let doc = document_from_node(node); + let form_id = elem.get_string_attribute(&local_name!("form")); + doc.GetElementById(form_id).and_then(Root::downcast::<HTMLFormElement>) + } else { + // Step 4 + nearest_form_ancestor + }; + + if old_owner != new_owner { + if let Some(o) = old_owner { + o.remove_control(self); + } + let new_owner = new_owner.as_ref().map(|o| { + o.add_control(self); + o.r() + }); + self.set_form_owner(new_owner); + } + } + + // https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms + fn form_attribute_mutated(&self, mutation: AttributeMutation) { + match mutation { + AttributeMutation::Set(_) => { + self.register_if_necessary(); + }, + AttributeMutation::Removed => { + self.unregister_if_necessary(); + }, + } + + self.reset_form_owner(); + } + + // https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms + fn register_if_necessary(&self) { + let elem = self.to_element(); + let form_id = elem.get_string_attribute(&local_name!("form")); + let node = elem.upcast::<Node>(); + + if self.is_listed() && !form_id.is_empty() && node.is_in_doc() { + let doc = document_from_node(node); + doc.register_form_id_listener(form_id, self); + } + } + + fn unregister_if_necessary(&self) { + let elem = self.to_element(); + let form_id = elem.get_string_attribute(&local_name!("form")); + + if self.is_listed() && !form_id.is_empty() { + let doc = document_from_node(elem.upcast::<Node>()); + doc.unregister_form_id_listener(form_id, self); + } + } + + // https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms + fn bind_form_control_to_tree(&self) { + let elem = self.to_element(); + let node = elem.upcast::<Node>(); + + // https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token + // Part of step 12. + // '..suppress the running of the reset the form owner algorithm + // when the parser subsequently attempts to insert the element..' + let must_skip_reset = node.get_flag(PARSER_ASSOCIATED_FORM_OWNER); + node.set_flag(PARSER_ASSOCIATED_FORM_OWNER, false); + + if !must_skip_reset { + self.form_attribute_mutated(AttributeMutation::Set(None)); + } + } + + // https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms + fn unbind_form_control_from_tree(&self) { + let elem = self.to_element(); + let has_form_attr = elem.has_attribute(&local_name!("form")); + let same_subtree = self.form_owner().map_or(true, |form| { + elem.is_in_same_home_subtree(&*form) + }); + + self.unregister_if_necessary(); + + // Since this control has been unregistered from the id->listener map + // in the previous step, reset_form_owner will not be invoked on it + // when the form owner element is unbound (i.e it is in the same + // subtree) if it appears later in the tree order. Hence invoke + // reset from here if this control has the form attribute set. + if !same_subtree || (self.is_listed() && has_form_attr) { + self.reset_form_owner(); + } } fn get_form_attribute<InputFn, OwnerFn>(&self, @@ -870,7 +1003,7 @@ pub trait FormControl: DerivedFrom<Element> + DomObject { owner: OwnerFn) -> DOMString where InputFn: Fn(&Self) -> DOMString, - OwnerFn: Fn(&HTMLFormElement) -> DOMString + OwnerFn: Fn(&HTMLFormElement) -> DOMString, Self: Sized { if self.to_element().has_attribute(attr) { input(self) @@ -885,7 +1018,7 @@ pub trait FormControl: DerivedFrom<Element> + DomObject { owner: OwnerFn) -> bool where InputFn: Fn(&Self) -> bool, - OwnerFn: Fn(&HTMLFormElement) -> bool + OwnerFn: Fn(&HTMLFormElement) -> bool, Self: Sized { if self.to_element().has_attribute(attr) { input(self) @@ -894,10 +1027,6 @@ pub trait FormControl: DerivedFrom<Element> + DomObject { } } - fn to_element(&self) -> &Element { - self.upcast() - } - // XXXKiChjang: Implement these on inheritors // fn candidate_for_validation(&self) -> bool; // fn satisfies_constraints(&self) -> bool; @@ -914,6 +1043,69 @@ impl VirtualMethods for HTMLFormElement { _ => self.super_type().unwrap().parse_plain_attribute(name, value), } } + + fn unbind_from_tree(&self, context: &UnbindContext) { + self.super_type().unwrap().unbind_from_tree(context); + + // Collect the controls to reset because reset_form_owner + // will mutably borrow self.controls + rooted_vec!(let mut to_reset); + to_reset.extend(self.controls.borrow().iter() + .filter(|c| !c.is_in_same_home_subtree(self)) + .map(|c| c.clone())); + + for control in to_reset.iter() { + control.as_maybe_form_control() + .expect("Element must be a form control") + .reset_form_owner(); + } + } +} + +pub trait FormControlElementHelpers { + fn as_maybe_form_control<'a>(&'a self) -> Option<&'a FormControl>; +} + +impl FormControlElementHelpers for Element { + fn as_maybe_form_control<'a>(&'a self) -> Option<&'a FormControl> { + let node = self.upcast::<Node>(); + + match node.type_id() { + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLButtonElement)) => { + Some(self.downcast::<HTMLButtonElement>().unwrap() as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLFieldSetElement)) => { + Some(self.downcast::<HTMLFieldSetElement>().unwrap() as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLImageElement)) => { + Some(self.downcast::<HTMLImageElement>().unwrap() as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) => { + Some(self.downcast::<HTMLInputElement>().unwrap() as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLabelElement)) => { + Some(self.downcast::<HTMLLabelElement>().unwrap() as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLegendElement)) => { + Some(self.downcast::<HTMLLegendElement>().unwrap() as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLObjectElement)) => { + Some(self.downcast::<HTMLObjectElement>().unwrap() as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOutputElement)) => { + Some(self.downcast::<HTMLOutputElement>().unwrap() as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) => { + Some(self.downcast::<HTMLSelectElement>().unwrap() as &FormControl) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) => { + Some(self.downcast::<HTMLTextAreaElement>().unwrap() as &FormControl) + }, + _ => { + None + } + } + } } struct PlannedNavigation { diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs index 321b5041553..610b0a3fae0 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,6 +26,7 @@ use dom::event::Event; use dom::eventtarget::EventTarget; use dom::htmlareaelement::HTMLAreaElement; use dom::htmlelement::HTMLElement; +use dom::htmlformelement::{FormControl, HTMLFormElement}; use dom::htmlmapelement::HTMLMapElement; use dom::mouseevent::MouseEvent; use dom::node::{Node, NodeDamage, document_from_node, window_from_node}; @@ -78,6 +79,7 @@ pub struct HTMLImageElement { htmlelement: HTMLElement, current_request: DOMRefCell<ImageRequest>, pending_request: DOMRefCell<ImageRequest>, + form_owner: MutNullableJS<HTMLFormElement>, generation: Cell<u32>, } @@ -384,6 +386,7 @@ impl HTMLImageElement { metadata: None, blocker: None, }), + form_owner: Default::default(), generation: Default::default(), } } @@ -689,6 +692,24 @@ impl VirtualMethods for HTMLImageElement { } } +impl FormControl for HTMLImageElement { + fn form_owner(&self) -> Option<Root<HTMLFormElement>> { + self.form_owner.get() + } + + fn set_form_owner(&self, form: Option<&HTMLFormElement>) { + self.form_owner.set(form); + } + + fn to_element<'a>(&'a self) -> &'a Element { + self.upcast::<Element>() + } + + fn is_listed(&self) -> bool { + false + } +} + fn image_dimension_setter(element: &Element, attr: LocalName, value: u32) { // This setter is a bit weird: the IDL type is unsigned long, but it's parsed as // a dimension for rendering. diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index f2e9f983300..db26905b3b4 100755 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -100,6 +100,7 @@ pub struct HTMLInputElement { value_dirty: Cell<bool>, filelist: MutNullableJS<FileList>, + form_owner: MutNullableJS<HTMLFormElement>, } #[derive(JSTraceable)] @@ -156,6 +157,7 @@ impl HTMLInputElement { activation_state: DOMRefCell::new(InputActivationState::new()), value_dirty: Cell::new(false), filelist: MutNullableJS::new(None), + form_owner: Default::default(), } } @@ -1044,7 +1046,10 @@ impl VirtualMethods for HTMLInputElement { el.set_read_write_state(!el.disabled_state()); } } - } + }, + &local_name!("form") => { + self.form_attribute_mutated(mutation); + }, _ => {}, } } @@ -1163,7 +1168,19 @@ impl VirtualMethods for HTMLInputElement { } } -impl FormControl for HTMLInputElement {} +impl FormControl for HTMLInputElement { + fn form_owner(&self) -> Option<Root<HTMLFormElement>> { + self.form_owner.get() + } + + fn set_form_owner(&self, form: Option<&HTMLFormElement>) { + self.form_owner.set(form); + } + + fn to_element<'a>(&'a self) -> &'a Element { + self.upcast::<Element>() + } +} impl Validatable for HTMLInputElement { fn is_instance_validatable(&self) -> bool { diff --git a/components/script/dom/htmllabelelement.rs b/components/script/dom/htmllabelelement.rs index 02345ad1f20..73a62af5570 100644 --- a/components/script/dom/htmllabelelement.rs +++ b/components/script/dom/htmllabelelement.rs @@ -3,17 +3,18 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use dom::activation::{Activatable, ActivationSource, synthetic_click_activation}; +use dom::attr::Attr; use dom::bindings::codegen::Bindings::HTMLLabelElementBinding; use dom::bindings::codegen::Bindings::HTMLLabelElementBinding::HTMLLabelElementMethods; use dom::bindings::inheritance::Castable; use dom::bindings::js::Root; use dom::bindings::str::DOMString; use dom::document::Document; -use dom::element::Element; +use dom::element::{AttributeMutation, Element}; use dom::event::Event; use dom::eventtarget::EventTarget; use dom::htmlelement::HTMLElement; -use dom::htmlformelement::{FormControl, HTMLFormElement}; +use dom::htmlformelement::{FormControl, FormControlElementHelpers, HTMLFormElement}; use dom::node::{document_from_node, Node}; use dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; @@ -22,7 +23,7 @@ use style::attr::AttrValue; #[dom_struct] pub struct HTMLLabelElement { - htmlelement: HTMLElement, + htmlelement: HTMLElement } impl HTMLLabelElement { @@ -31,7 +32,7 @@ impl HTMLLabelElement { document: &Document) -> HTMLLabelElement { HTMLLabelElement { htmlelement: - HTMLElement::new_inherited(local_name, prefix, document) + HTMLElement::new_inherited(local_name, prefix, document), } } @@ -128,6 +129,16 @@ impl VirtualMethods for HTMLLabelElement { _ => self.super_type().unwrap().parse_plain_attribute(name, value), } } + + fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { + self.super_type().unwrap().attribute_mutated(attr, mutation); + match attr.local_name() { + &local_name!("form") => { + self.form_attribute_mutated(mutation); + }, + _ => {}, + } + } } impl HTMLLabelElement { @@ -140,4 +151,19 @@ impl HTMLLabelElement { } } -impl FormControl for HTMLLabelElement {} +impl FormControl for HTMLLabelElement { + fn form_owner(&self) -> Option<Root<HTMLFormElement>> { + self.GetControl().map(Root::upcast::<Element>).and_then(|elem| { + elem.as_maybe_form_control().and_then(|control| control.form_owner()) + }) + } + + fn set_form_owner(&self, _: Option<&HTMLFormElement>) { + // Label is a special case for form owner, it reflects its control's + // form owner. Therefore it doesn't hold form owner itself. + } + + fn to_element<'a>(&'a self) -> &'a Element { + self.upcast::<Element>() + } +} diff --git a/components/script/dom/htmllegendelement.rs b/components/script/dom/htmllegendelement.rs index f05ec6bfd3b..86b34c27dd0 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<HTMLFormElement>, } impl HTMLLegendElement { @@ -28,7 +29,10 @@ impl HTMLLegendElement { prefix: Option<DOMString>, document: &Document) -> HTMLLegendElement { - HTMLLegendElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document) } + HTMLLegendElement { + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), + form_owner: Default::default(), + } } #[allow(unrooted_must_root)] @@ -83,4 +87,16 @@ impl HTMLLegendElementMethods for HTMLLegendElement { } } -impl FormControl for HTMLLegendElement {} +impl FormControl for HTMLLegendElement { + fn form_owner(&self) -> Option<Root<HTMLFormElement>> { + self.form_owner.get() + } + + fn set_form_owner(&self, form: Option<&HTMLFormElement>) { + self.form_owner.set(form); + } + + fn to_element<'a>(&'a self) -> &'a Element { + self.upcast::<Element>() + } +} diff --git a/components/script/dom/htmlobjectelement.rs b/components/script/dom/htmlobjectelement.rs index 6352ea5c862..c80a7838198 100755 --- a/components/script/dom/htmlobjectelement.rs +++ b/components/script/dom/htmlobjectelement.rs @@ -7,7 +7,7 @@ use dom::bindings::cell::DOMRefCell; use dom::bindings::codegen::Bindings::HTMLObjectElementBinding; use dom::bindings::codegen::Bindings::HTMLObjectElementBinding::HTMLObjectElementMethods; use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; +use dom::bindings::js::{MutNullableJS, Root}; use dom::bindings::str::DOMString; use dom::document::Document; use dom::element::{AttributeMutation, Element}; @@ -20,6 +20,7 @@ use dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; use html5ever_atoms::LocalName; use net_traits::image::base::Image; +use std::default::Default; use std::sync::Arc; #[dom_struct] @@ -27,6 +28,7 @@ pub struct HTMLObjectElement { htmlelement: HTMLElement, #[ignore_heap_size_of = "Arc"] image: DOMRefCell<Option<Arc<Image>>>, + form_owner: MutNullableJS<HTMLFormElement>, } impl HTMLObjectElement { @@ -37,6 +39,7 @@ impl HTMLObjectElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), image: DOMRefCell::new(None), + form_owner: Default::default(), } } @@ -114,9 +117,24 @@ impl VirtualMethods for HTMLObjectElement { self.process_data_url(); } }, + &local_name!("form") => { + self.form_attribute_mutated(mutation); + }, _ => {}, } } } -impl FormControl for HTMLObjectElement {} +impl FormControl for HTMLObjectElement { + fn form_owner(&self) -> Option<Root<HTMLFormElement>> { + self.form_owner.get() + } + + fn set_form_owner(&self, form: Option<&HTMLFormElement>) { + self.form_owner.set(form); + } + + fn to_element<'a>(&'a self) -> &'a Element { + self.upcast::<Element>() + } +} diff --git a/components/script/dom/htmloutputelement.rs b/components/script/dom/htmloutputelement.rs index 6f3154057ea..719efad95d3 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::nodelist::NodeList; use dom::validitystate::ValidityState; +use dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; use html5ever_atoms::LocalName; #[dom_struct] pub struct HTMLOutputElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, + form_owner: MutNullableJS<HTMLFormElement>, } impl HTMLOutputElement { @@ -27,7 +31,8 @@ impl HTMLOutputElement { document: &Document) -> HTMLOutputElement { HTMLOutputElement { htmlelement: - HTMLElement::new_inherited(local_name, prefix, document) + HTMLElement::new_inherited(local_name, prefix, document), + form_owner: Default::default(), } } @@ -59,4 +64,32 @@ impl HTMLOutputElementMethods for HTMLOutputElement { } } -impl FormControl for HTMLOutputElement {} +impl VirtualMethods for HTMLOutputElement { + fn super_type<'b>(&'b self) -> Option<&'b VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &VirtualMethods) + } + + fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { + self.super_type().unwrap().attribute_mutated(attr, mutation); + match attr.local_name() { + &local_name!("form") => { + self.form_attribute_mutated(mutation); + }, + _ => {}, + } + } +} + +impl FormControl for HTMLOutputElement { + fn form_owner(&self) -> Option<Root<HTMLFormElement>> { + self.form_owner.get() + } + + fn set_form_owner(&self, form: Option<&HTMLFormElement>) { + self.form_owner.set(form); + } + + fn to_element<'a>(&'a self) -> &'a Element { + self.upcast::<Element>() + } +} diff --git a/components/script/dom/htmlselectelement.rs b/components/script/dom/htmlselectelement.rs index 1f4a8f029b0..2d4dc4758b6 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<HTMLOptionsCollection>, + form_owner: MutNullableJS<HTMLFormElement>, } static DEFAULT_SELECT_SIZE: u32 = 0; @@ -73,7 +75,8 @@ impl HTMLSelectElement { htmlelement: HTMLElement::new_inherited_with_state(IN_ENABLED_STATE, local_name, prefix, document), - options: Default::default() + options: Default::default(), + form_owner: Default::default(), } } @@ -344,19 +347,25 @@ 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::<Element>(); - 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::<Element>(); + 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") => { + self.form_attribute_mutated(mutation); + }, + _ => {}, } } @@ -388,7 +397,19 @@ impl VirtualMethods for HTMLSelectElement { } } -impl FormControl for HTMLSelectElement {} +impl FormControl for HTMLSelectElement { + fn form_owner(&self) -> Option<Root<HTMLFormElement>> { + self.form_owner.get() + } + + fn set_form_owner(&self, form: Option<&HTMLFormElement>) { + self.form_owner.set(form); + } + + fn to_element<'a>(&'a self) -> &'a Element { + self.upcast::<Element>() + } +} impl Validatable for HTMLSelectElement { fn is_instance_validatable(&self) -> bool { diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index e7db7565adf..8a4e9f41cd3 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<DOMString>, // https://html.spec.whatwg.org/multipage/#concept-textarea-dirty value_changed: Cell<bool>, + form_owner: MutNullableJS<HTMLFormElement>, } pub trait LayoutHTMLTextAreaElementHelpers { @@ -116,6 +118,7 @@ impl HTMLTextAreaElement { textinput: DOMRefCell::new(TextInput::new( Lines::Multiple, DOMString::new(), chan, None, None, SelectionDirection::None)), value_changed: Cell::new(false), + form_owner: Default::default(), } } @@ -342,7 +345,10 @@ impl VirtualMethods for HTMLTextAreaElement { el.set_read_write_state(!el.disabled_state()); } } - } + }, + local_name!("form") => { + self.form_attribute_mutated(mutation); + }, _ => {}, } } @@ -435,7 +441,19 @@ impl VirtualMethods for HTMLTextAreaElement { } } -impl FormControl for HTMLTextAreaElement {} +impl FormControl for HTMLTextAreaElement { + fn form_owner(&self) -> Option<Root<HTMLFormElement>> { + self.form_owner.get() + } + + fn set_form_owner(&self, form: Option<&HTMLFormElement>) { + self.form_owner.set(form); + } + + fn to_element<'a>(&'a self) -> &'a Element { + self.upcast::<Element>() + } +} impl Validatable for HTMLTextAreaElement {} diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index fce1ce4826d..e21dfa2bee0 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,11 @@ impl Node { for node in child.traverse_preorder() { // Out-of-document elements never have the descendants flag set. node.set_flag(IS_IN_DOC | HAS_DIRTY_DESCENDANTS, false); + } + for node in child.traverse_preorder() { + // This needs to be in its own loop, because unbind_from_tree may + // rely on the state of IS_IN_DOC of the context node's descendants, + // e.g. when removing a <form>. vtable_for(&&*node).unbind_from_tree(&context); node.style_and_layout_data.get().map(|d| node.dispose(d)); } @@ -2656,3 +2665,41 @@ impl Into<LayoutElementType> for ElementTypeId { } } +/// Helper trait to insert an element into vector whose elements +/// are maintained in tree order +pub trait VecPreOrderInsertionHelper<T> { + fn insert_pre_order(&mut self, elem: &T, tree_root: &Node); +} + +impl<T> VecPreOrderInsertionHelper<T> for Vec<JS<T>> + where T: DerivedFrom<Node> + DomObject +{ + /// This algorithm relies on the following assumptions: + /// * any elements inserted in this vector share the same tree root + /// * any time an element is removed from the tree root, it is also removed from this array + /// * any time an element is moved within the tree, it is removed from this array and re-inserted + /// + /// Under these assumptions, an element's tree-order position in this array can be determined by + /// performing a [preorder traversal](https://dom.spec.whatwg.org/#concept-tree-order) of the tree root's children, + /// and increasing the destination index in the array every time a node in the array is encountered during + /// the traversal. + fn insert_pre_order(&mut self, elem: &T, tree_root: &Node) { + if self.is_empty() { + self.push(JS::from_ref(elem)); + return; + } + + let elem_node = elem.upcast::<Node>(); + let mut head: usize = 0; + for node in tree_root.traverse_preorder() { + let head_node = Root::upcast::<Node>(Root::from_ref(&*self[head])); + if head_node == node { + head += 1; + } + if elem_node == node.r() || head == self.len() { + break; + } + } + self.insert(head, JS::from_ref(elem)); + } +} diff --git a/components/script/dom/servoparser/html.rs b/components/script/dom/servoparser/html.rs index a6270eaaf26..688e2aba788 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<Node>, y: JS<Node>) -> bool { + let x = x.downcast::<Element>().expect("Element node expected"); + let y = y.downcast::<Element>().expect("Element node expected"); + + x.is_in_same_home_subtree(y) + } + fn create_element(&mut self, name: QualName, attrs: Vec<Attribute>) -> JS<Node> { let elem = Element::create(name, None, &*self.document, @@ -176,17 +184,33 @@ impl TreeSink for Sink { JS::from_ref(comment.upcast()) } + fn has_parent_node(&self, node: JS<Node>) -> bool { + node.GetParentNode().is_some() + } + + fn associate_with_form(&mut self, target: JS<Node>, form: JS<Node>) { + let node = target; + let form = Root::downcast::<HTMLFormElement>(Root::from_ref(&*form)) + .expect("Owner must be a form element"); + + let elem = node.downcast::<Element>(); + let control = elem.as_ref().and_then(|e| e.as_maybe_form_control()); + + if let Some(control) = control { + control.set_form_owner_from_parser(&form); + } else { + // TODO remove this code when keygen is implemented. + assert!(node.NodeName() == "KEYGEN", "Unknown form-associatable element"); + } + } + fn append_before_sibling(&mut self, sibling: JS<Node>, - new_node: NodeOrText<JS<Node>>) -> Result<(), NodeOrText<JS<Node>>> { - // If there is no parent, return the node to the parser. - let parent = match sibling.GetParentNode() { - Some(p) => p, - None => return Err(new_node), - }; + new_node: NodeOrText<JS<Node>>) { + let parent = sibling.GetParentNode() + .expect("append_before_sibling called on node without parent"); super::insert(&parent, Some(&*sibling), new_node); - Ok(()) } fn parse_error(&mut self, msg: Cow<'static, str>) { diff --git a/components/script/dom/virtualmethods.rs b/components/script/dom/virtualmethods.rs index d6b77b31256..8ff47f9ced5 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::<HTMLOptionElement>().unwrap() as &VirtualMethods } + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOutputElement)) => { + node.downcast::<HTMLOutputElement>().unwrap() as &VirtualMethods + } NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLScriptElement)) => { node.downcast::<HTMLScriptElement>().unwrap() as &VirtualMethods } |