aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom
diff options
context:
space:
mode:
authorMukilan Thiyagarajan <mukilanthiagarajan@gmail.com>2017-01-28 17:20:01 +0300
committerAnthony Ramine <n.oxyde@gmail.com>2017-03-15 16:39:55 +0100
commit38a61712e41ddeebb52cace6a9787735947964a4 (patch)
treef9949282e968e9a8fd57a8c8aa1fd63a59d0a89c /components/script/dom
parentf90e19f7055387a14cabdf11f77335c7763e3fb7 (diff)
downloadservo-38a61712e41ddeebb52cace6a9787735947964a4.tar.gz
servo-38a61712e41ddeebb52cace6a9787735947964a4.zip
Implement the form owner concept
Diffstat (limited to 'components/script/dom')
-rw-r--r--components/script/dom/document.rs97
-rw-r--r--components/script/dom/element.rs18
-rwxr-xr-xcomponents/script/dom/htmlbuttonelement.rs26
-rw-r--r--components/script/dom/htmlfieldsetelement.rs26
-rwxr-xr-xcomponents/script/dom/htmlformelement.rs270
-rw-r--r--components/script/dom/htmlimageelement.rs23
-rwxr-xr-xcomponents/script/dom/htmlinputelement.rs21
-rw-r--r--components/script/dom/htmllabelelement.rs36
-rw-r--r--components/script/dom/htmllegendelement.rs22
-rwxr-xr-xcomponents/script/dom/htmlobjectelement.rs22
-rw-r--r--components/script/dom/htmloutputelement.rs41
-rwxr-xr-xcomponents/script/dom/htmlselectelement.rs49
-rwxr-xr-xcomponents/script/dom/htmltextareaelement.rs24
-rw-r--r--components/script/dom/node.rs49
-rw-r--r--components/script/dom/servoparser/html.rs40
-rw-r--r--components/script/dom/virtualmethods.rs4
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
}