servo: Merge #15938 - Implement the form owner concept (from servo:form-owner); r=nox,jdm
authorMukilan Thiyagarajan <mukilanthiagarajan@gmail.com>
Wed, 15 Mar 2017 09:16:08 -0700
changeset 347766 cc663a2c94b2221026b617f6bbb4db94a7adf350
parent 347765 6e491b215d2d67db6ea82700e210cf59031392a1
child 347767 0198076d288f13190574ffef00f06507cfb11957
push id31504
push userkwierso@gmail.com
push dateWed, 15 Mar 2017 21:05:46 +0000
treeherdermozilla-central@1c4d97d1bc61 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnox, jdm
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #15938 - Implement the form owner concept (from servo:form-owner); r=nox,jdm Source-Repo: https://github.com/servo/servo Source-Revision: ed98cb9c0a0ce66e2580773c0716ce86fa1f5377
servo/Cargo.lock
servo/components/script/Cargo.toml
servo/components/script/dom/document.rs
servo/components/script/dom/element.rs
servo/components/script/dom/htmlbuttonelement.rs
servo/components/script/dom/htmlfieldsetelement.rs
servo/components/script/dom/htmlformelement.rs
servo/components/script/dom/htmlimageelement.rs
servo/components/script/dom/htmlinputelement.rs
servo/components/script/dom/htmllabelelement.rs
servo/components/script/dom/htmllegendelement.rs
servo/components/script/dom/htmlobjectelement.rs
servo/components/script/dom/htmloutputelement.rs
servo/components/script/dom/htmlselectelement.rs
servo/components/script/dom/htmltextareaelement.rs
servo/components/script/dom/node.rs
servo/components/script/dom/servoparser/html.rs
servo/components/script/dom/virtualmethods.rs
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -1128,17 +1128,17 @@ name = "hpack"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "html5ever"
-version = "0.13.1"
+version = "0.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "html5ever-atoms 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2247,17 +2247,17 @@ dependencies = [
  "dom_struct 0.0.1",
  "domobject_derive 0.0.1",
  "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "gfx_traits 0.0.1",
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "html5ever 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "html5ever 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "html5ever-atoms 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "hyper 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)",
  "hyper_serde 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "image 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "js 0.1.4 (git+https://github.com/servo/rust-mozjs)",
  "jstraceable_derive 0.0.1",
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3407,17 +3407,17 @@ dependencies = [
 "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
 "checksum glx 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b280007fa9c7442cfd1e0b1addb8d1a59240267110e8705f8f7e2c7bfb7e2f72"
 "checksum harfbuzz-sys 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6b76113246f5c089dcf272cf89c3f61168a4d77b50ec5b2c1fab8c628c9ea762"
 "checksum heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "5a376f7402b85be6e0ba504243ecbc0709c48019ecc6286d0540c2e359050c88"
 "checksum heapsize_derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "46f96d52fb1564059fc97b85ef6165728cc30198ab60073bf114c66c4c89bb5d"
 "checksum heartbeats-simple 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9ad003ce233955e9d95f2c69cde84e68302ba9ba4a673d351c9bff93c738aadc"
 "checksum heartbeats-simple-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e1a408c0011427cc0e0049f7861c70377819aedfc006e8c901b1c70fd98fb1a4"
 "checksum hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2da7d3a34cf6406d9d700111b8eafafe9a251de41ae71d8052748259343b58"
-"checksum html5ever 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d60508177ec4e5774a112efcf4d4d5f123cb00a43476fa5940b7da568371a165"
+"checksum html5ever 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a3b2e982006a000535c1976213cd1baa3f455cd19335d50992ab219ffe1d3c06"
 "checksum html5ever-atoms 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f9bd86e3b6a5a7933a272cc0a854f24e371f31576e585c0b41e8f857270c5134"
 "checksum httparse 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a6e7a63e511f9edffbab707141fbb8707d1a3098615fb2adbd5769cdfcc9b17d"
 "checksum hyper 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)" = "1b9bf64f730d6ee4b0528a5f0a316363da9d8104318731509d4ccc86248f82b3"
 "checksum hyper_serde 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d602a93073c250f49b2e2d931cc1755a5f447824154dc3c711716dee29bd7486"
 "checksum idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1053236e00ce4f668aeca4a769a09b3bf5a682d802abd6f3cb39374f6b162c11"
 "checksum image 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "979bad0502082fd60053a490282e87d6c89650942e3a270e0d4c83569c7f5899"
 "checksum immeta 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0b9260463a221bfe3f02100c56e2d14c050d5ffe7e44a43d0a1b2b1f2b523502"
 "checksum inflate 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e7e0062d2dc2f17d2f13750d95316ae8a2ff909af0fda957084f5defd87c43bb"
--- a/servo/components/script/Cargo.toml
+++ b/servo/components/script/Cargo.toml
@@ -40,17 +40,17 @@ devtools_traits = {path = "../devtools_t
 dom_struct = {path = "../dom_struct"}
 domobject_derive = {path = "../domobject_derive"}
 encoding = "0.2"
 euclid = "0.11"
 fnv = "1.0"
 gfx_traits = {path = "../gfx_traits"}
 heapsize = "0.3.6"
 heapsize_derive = "0.1"
-html5ever = {version = "0.13", features = ["heap_size", "unstable"]}
+html5ever = {version = "0.14", features = ["heap_size", "unstable"]}
 html5ever-atoms = {version = "0.2", features = ["heap_size"]}
 hyper = "0.9.9"
 hyper_serde = "0.5"
 image = "0.12"
 ipc-channel = "0.7"
 js = {git = "https://github.com/servo/rust-mozjs", features = ["promises"]}
 jstraceable_derive = {path = "../jstraceable_derive"}
 libc = "0.2"
--- a/servo/components/script/dom/document.rs
+++ b/servo/components/script/dom/document.rs
@@ -51,28 +51,29 @@ use dom::hashchangeevent::HashChangeEven
 use dom::htmlanchorelement::HTMLAnchorElement;
 use dom::htmlappletelement::HTMLAppletElement;
 use dom::htmlareaelement::HTMLAreaElement;
 use dom::htmlbaseelement::HTMLBaseElement;
 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;
 use dom::htmlimageelement::HTMLImageElement;
 use dom::htmlscriptelement::{HTMLScriptElement, ScriptResult};
 use dom::htmltitleelement::HTMLTitleElement;
 use dom::keyboardevent::KeyboardEvent;
 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;
 use dom::popstateevent::PopStateEvent;
 use dom::processinginstruction::ProcessingInstruction;
 use dom::progressevent::ProgressEvent;
 use dom::promise::Promise;
 use dom::range::Range;
@@ -116,17 +117,17 @@ use script_traits::{MsDuration, ScriptMs
 use script_traits::{TouchEventType, TouchId};
 use script_traits::UntrustedNodeAddress;
 use servo_atoms::Atom;
 use servo_config::prefs::PREFS;
 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;
 use std::mem;
 use std::rc::Rc;
 use std::sync::Arc;
 use std::time::{Duration, Instant};
 use style::attr::AttrValue;
@@ -309,16 +310,22 @@ pub struct Document {
     /// Track the total number of elements in this DOM's tree.
     /// This is sent to the layout thread every time a reflow is done;
     /// layout uses this to determine if the gains from parallel layout will be worth the overhead.
     ///
     /// See also: https://github.com/servo/servo/issues/10110
     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)]
 struct ImagesFilter;
 impl CollectionFilter for ImagesFilter {
     fn filter(&self, elem: &Element, _root: &Node) -> bool {
         elem.is::<HTMLImageElement>()
     }
@@ -572,69 +579,75 @@ impl Document {
     }
 
     /// Remove any existing association between the provided id and any elements in this document.
     pub fn unregister_named_element(&self, to_unregister: &Element, id: Atom) {
         debug!("Removing named element from document {:p}: {:p} id={}",
                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.
     pub fn register_named_element(&self, element: &Element, id: Atom) {
         debug!("Adding named element to document {:p}: {:p} id={}",
                self,
                element,
                id);
         assert!(element.upcast::<Node>().is_in_doc());
         assert!(!id.is_empty());
 
-        let mut id_map = self.id_map.borrow_mut();
-
         let root = self.GetDocumentElement()
                        .expect("The element is in the document, so there must be a document \
                                 element.");
 
-        match id_map.entry(id) {
-            Vacant(entry) => {
-                entry.insert(vec![JS::from_ref(element)]);
-            }
-            Occupied(entry) => {
-                let elements = entry.into_mut();
-
-                let new_node = element.upcast::<Node>();
-                let mut head: usize = 0;
-                let root = root.upcast::<Node>();
-                for node in root.traverse_preorder() {
-                    if let Some(elem) = node.downcast() {
-                        if &*(*elements)[head] == elem {
-                            head += 1;
-                        }
-                        if new_node == &*node || head == elements.len() {
-                            break;
-                        }
-                    }
-                }
-
-                elements.insert(head, JS::from_ref(element));
+        // 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);
+    }
+
+    pub fn register_form_id_listener<T: ?Sized + FormControl>(&self, id: DOMString, listener: &T) {
+        let mut map = self.form_id_listener_map.borrow_mut();
+        let listener = listener.to_element();
+        let mut set = map.entry(Atom::from(id)).or_insert(HashSet::new());
+        set.insert(JS::from_ref(listener));
+    }
+
+    pub fn unregister_form_id_listener<T: ?Sized + FormControl>(&self, id: DOMString, listener: &T) {
+        let mut map = self.form_id_listener_map.borrow_mut();
+        if let Occupied(mut entry) = map.entry(Atom::from(id)) {
+            entry.get_mut().remove(&JS::from_ref(listener.to_element()));
+            if entry.get().is_empty() {
+                entry.remove();
             }
         }
     }
 
     /// Attempt to find a named element in this page's document.
     /// https://html.spec.whatwg.org/multipage/#the-indicated-part-of-the-document
     pub fn find_fragment_node(&self, fragid: &str) -> Option<Root<Element>> {
         // Step 1 is not handled here; the fragid is already obtained by the calling function
@@ -2146,16 +2159,17 @@ impl Document {
             referrer: referrer,
             referrer_policy: Cell::new(referrer_policy),
             target_element: MutNullableJS::new(None),
             last_click_info: DOMRefCell::new(None),
             ignore_destructive_writes_counter: Default::default(),
             spurious_animation_frames: Cell::new(0),
             dom_count: Cell::new(1),
             fullscreen_element: MutNullableJS::new(None),
+            form_id_listener_map: Default::default(),
         }
     }
 
     // https://dom.spec.whatwg.org/#dom-document-document
     pub fn Constructor(window: &Window) -> Fallible<Root<Document>> {
         let doc = window.Document();
         let docloader = DocumentLoader::new(&*doc.loader());
         Ok(Document::new(window,
@@ -2459,16 +2473,27 @@ impl Document {
                     true
                 } else {
                     // Step 3
                     window.GetFrameElement().map_or(false, |el| el.has_attribute(&local_name!("allowfullscreen")))
                 }
             }
         }
     }
+
+    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();
+            }
+        }
+    }
 }
 
 
 impl Element {
     fn click_event_filter_by_disabled_state(&self) -> bool {
         let node = self.upcast::<Node>();
         match node.type_id() {
             NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLButtonElement)) |
--- a/servo/components/script/dom/element.rs
+++ b/servo/components/script/dom/element.rs
@@ -14,16 +14,17 @@ use dom::bindings::codegen::Bindings::Do
 use dom::bindings::codegen::Bindings::ElementBinding;
 use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
 use dom::bindings::codegen::Bindings::EventBinding::EventMethods;
 use dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods;
 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};
 use dom::bindings::js::{Root, RootedReference};
 use dom::bindings::refcounted::{Trusted, TrustedPromise};
 use dom::bindings::reflector::DomObject;
 use dom::bindings::str::{DOMString, extended_filtering};
 use dom::bindings::xmlname::{namespace_from_domstring, validate_and_extract, xml_name_type};
@@ -39,16 +40,17 @@ use dom::event::Event;
 use dom::eventtarget::EventTarget;
 use dom::htmlanchorelement::HTMLAnchorElement;
 use dom::htmlbodyelement::{HTMLBodyElement, HTMLBodyElementLayoutHelpers};
 use dom::htmlbuttonelement::HTMLButtonElement;
 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};
 use dom::htmlinputelement::{HTMLInputElement, LayoutHTMLInputElementHelpers};
 use dom::htmllabelelement::HTMLLabelElement;
 use dom::htmllegendelement::HTMLLegendElement;
 use dom::htmllinkelement::HTMLLinkElement;
 use dom::htmlobjectelement::HTMLObjectElement;
@@ -1355,16 +1357,24 @@ impl Element {
     // https://fullscreen.spec.whatwg.org/#fullscreen-element-ready-check
     pub fn fullscreen_element_ready_check(&self) -> bool {
         if !self.is_connected() {
             return false
         }
         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 {
     // https://dom.spec.whatwg.org/#dom-element-namespaceuri
     fn GetNamespaceURI(&self) -> Option<DOMString> {
         Node::namespace_to_string(self.namespace.clone())
     }
 
@@ -2235,31 +2245,39 @@ impl VirtualMethods for Element {
         }
     }
 
     fn bind_to_tree(&self, tree_in_doc: bool) {
         if let Some(ref s) = self.super_type() {
             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;
         }
 
         let doc = document_from_node(self);
         if let Some(ref value) = *self.id_attribute.borrow() {
             doc.register_named_element(self, value.clone());
         }
         // This is used for layout optimization.
         doc.increment_dom_count();
     }
 
     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;
         }
 
         let doc = document_from_node(self);
         let fullscreen = doc.GetFullscreenElement();
         if fullscreen.r() == Some(self) {
             doc.exit_fullscreen();
--- a/servo/components/script/dom/htmlbuttonelement.rs
+++ b/servo/components/script/dom/htmlbuttonelement.rs
@@ -2,17 +2,17 @@
  * 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::activation::{Activatable, ActivationSource, synthetic_click_activation};
 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};
 use dom::event::Event;
 use dom::eventtarget::EventTarget;
 use dom::htmlelement::HTMLElement;
 use dom::htmlfieldsetelement::HTMLFieldSetElement;
 use dom::htmlformelement::{FormControl, FormDatum, FormDatumValue};
@@ -21,42 +21,45 @@ use dom::htmlformelement::HTMLFormElemen
 use dom::node::{Node, UnbindContext, document_from_node, window_from_node};
 use dom::nodelist::NodeList;
 use dom::validation::Validatable;
 use dom::validitystate::{ValidityState, ValidationFlags};
 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)]
 #[derive(HeapSizeOf)]
 enum ButtonType {
     Submit,
     Reset,
     Button,
     Menu
 }
 
 #[dom_struct]
 pub struct HTMLButtonElement {
     htmlelement: HTMLElement,
-    button_type: Cell<ButtonType>
+    button_type: Cell<ButtonType>,
+    form_owner: MutNullableJS<HTMLFormElement>,
 }
 
 impl HTMLButtonElement {
     fn new_inherited(local_name: LocalName,
                      prefix: Option<DOMString>,
                      document: &Document) -> HTMLButtonElement {
         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(),
         }
     }
 
     #[allow(unrooted_must_root)]
     pub fn new(local_name: LocalName,
                prefix: Option<DOMString>,
                document: &Document) -> Root<HTMLButtonElement> {
         Node::reflect_node(box HTMLButtonElement::new_inherited(local_name, prefix, document),
@@ -206,16 +209,19 @@ impl VirtualMethods for HTMLButtonElemen
                             _ => ButtonType::Submit,
                         };
                         self.button_type.set(value);
                     }
                     AttributeMutation::Removed => {
                         self.button_type.set(ButtonType::Submit);
                     }
                 }
+            },
+            &local_name!("form") => {
+                self.form_attribute_mutated(mutation);
             }
             _ => {},
         }
     }
 
     fn bind_to_tree(&self, tree_in_doc: bool) {
         if let Some(ref s) = self.super_type() {
             s.bind_to_tree(tree_in_doc);
@@ -232,17 +238,29 @@ impl VirtualMethods for HTMLButtonElemen
         if node.ancestors().any(|ancestor| ancestor.is::<HTMLFieldSetElement>()) {
             el.check_ancestors_disabled_state_for_form_control();
         } else {
             el.check_disabled_attribute();
         }
     }
 }
 
-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 {
         true
     }
     fn validate(&self, validate_flags: ValidationFlags) -> bool {
         if validate_flags.is_empty() {}
         // Need more flag check for different validation types later
--- a/servo/components/script/dom/htmlfieldsetelement.rs
+++ b/servo/components/script/dom/htmlfieldsetelement.rs
@@ -1,44 +1,47 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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::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};
 use dom::htmlcollection::{CollectionFilter, HTMLCollection};
 use dom::htmlelement::HTMLElement;
 use dom::htmlformelement::{FormControl, HTMLFormElement};
 use dom::htmllegendelement::HTMLLegendElement;
 use dom::node::{Node, window_from_node};
 use dom::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 {
     fn new_inherited(local_name: LocalName,
                      prefix: Option<DOMString>,
                      document: &Document) -> HTMLFieldSetElement {
         HTMLFieldSetElement {
             htmlelement:
                 HTMLElement::new_inherited_with_state(IN_ENABLED_STATE,
-                                                      local_name, prefix, document)
+                                                      local_name, prefix, document),
+            form_owner: Default::default(),
         }
     }
 
     #[allow(unrooted_must_root)]
     pub fn new(local_name: LocalName,
                prefix: Option<DOMString>,
                document: &Document) -> Root<HTMLFieldSetElement> {
         Node::reflect_node(box HTMLFieldSetElement::new_inherited(local_name, prefix, document),
@@ -143,14 +146,29 @@ impl VirtualMethods for HTMLFieldSetElem
                 } else {
                     for field in fields {
                         let el = field.downcast::<Element>().unwrap();
                         el.check_disabled_attribute();
                         el.check_ancestors_disabled_state_for_form_control();
                     }
                 }
             },
+            &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>()
+    }
+}
--- a/servo/components/script/dom/htmlformelement.rs
+++ b/servo/components/script/dom/htmlformelement.rs
@@ -1,45 +1,49 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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;
 use dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElementMethods;
 use dom::bindings::codegen::Bindings::HTMLFormControlsCollectionBinding::HTMLFormControlsCollectionMethods;
 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;
 use dom::htmlbuttonelement::HTMLButtonElement;
 use dom::htmlcollection::CollectionFilter;
 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;
 use encoding::EncodingRef;
 use encoding::all::UTF_8;
 use encoding::label::encoding_from_whatwg_label;
 use html5ever_atoms::LocalName;
 use hyper::header::{Charset, ContentDisposition, ContentType, DispositionParam, DispositionType};
@@ -58,28 +62,30 @@ use task_source::TaskSource;
 #[derive(JSTraceable, PartialEq, Clone, Copy, HeapSizeOf)]
 pub struct GenerationId(u32);
 
 #[dom_struct]
 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 {
     fn new_inherited(local_name: LocalName,
                      prefix: Option<DOMString>,
                      document: &Document) -> HTMLFormElement {
         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()),
         }
     }
 
     #[allow(unrooted_must_root)]
     pub fn new(local_name: LocalName,
                prefix: Option<DOMString>,
                document: &Document) -> Root<HTMLFormElement> {
         Node::reflect_node(box HTMLFormElement::new_inherited(local_name, prefix, document),
@@ -499,26 +505,24 @@ impl HTMLFormElement {
         }).collect::<Vec<FormSubmittableElement>>();
         // Step 7
         Err(unhandled_invalid_controls)
     }
 
     /// 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()
                     .any(|a| Root::downcast::<HTMLDataListElement>(a).is_some()) {
                 continue;
             }
             if let NodeTypeId::Element(ElementTypeId::HTMLElement(element)) = child.type_id() {
                 match element {
@@ -622,19 +626,20 @@ impl HTMLFormElement {
         }
 
         let event = self.upcast::<EventTarget>()
             .fire_bubbling_cancelable_event(atom!("reset"));
         if event.DefaultPrevented() {
             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();
                 }
                 // TODO HTMLKeygenElement unimplemented
                 //NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLKeygenElement)) => {
                 //    // Unimplemented
                 //    {}
@@ -642,24 +647,37 @@ impl HTMLFormElement {
                 NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) => {
                     child.downcast::<HTMLSelectElement>().unwrap().reset();
                 }
                 NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) => {
                     child.downcast::<HTMLTextAreaElement>().unwrap().reset();
                 }
                 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)]
 pub enum FormDatumValue {
     #[allow(dead_code)]
     File(Root<File>),
     String(DOMString)
 }
@@ -839,86 +857,260 @@ impl<'a> FormSubmitter<'a> {
                 button_element.get_form_boolean_attribute(&local_name!("formnovalidate"),
                                                   |i| i.FormNoValidate(),
                                                   |f| f.NoValidate())
             }
         }
     }
 }
 
-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,
                                             attr: &LocalName,
                                             input: InputFn,
                                             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)
         } else {
             self.form_owner().map_or(DOMString::new(), |t| owner(&t))
         }
     }
 
     fn get_form_boolean_attribute<InputFn, OwnerFn>(&self,
                                             attr: &LocalName,
                                             input: InputFn,
                                             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)
         } else {
             self.form_owner().map_or(false, |t| owner(&t))
         }
     }
 
-    fn to_element(&self) -> &Element {
-        self.upcast()
-    }
-
     // XXXKiChjang: Implement these on inheritors
     // fn candidate_for_validation(&self) -> bool;
     // fn satisfies_constraints(&self) -> bool;
 }
 
 impl VirtualMethods for HTMLFormElement {
     fn super_type(&self) -> Option<&VirtualMethods> {
         Some(self.upcast::<HTMLElement>() as &VirtualMethods)
     }
 
     fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
         match name {
             &local_name!("name") => AttrValue::from_atomic(value.into()),
             _ => 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 {
     load_data: LoadData,
     pipeline_id: PipelineId,
     script_chan: Sender<MainThreadScriptMsg>,
     generation_id: GenerationId,
     form: Trusted<HTMLFormElement>
--- a/servo/components/script/dom/htmlimageelement.rs
+++ b/servo/components/script/dom/htmlimageelement.rs
@@ -10,27 +10,28 @@ use dom::bindings::cell::DOMRefCell;
 use dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectBinding::DOMRectMethods;
 use dom::bindings::codegen::Bindings::ElementBinding::ElementBinding::ElementMethods;
 use dom::bindings::codegen::Bindings::HTMLImageElementBinding;
 use dom::bindings::codegen::Bindings::HTMLImageElementBinding::HTMLImageElementMethods;
 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;
 use dom::document::Document;
 use dom::element::{AttributeMutation, Element, RawLayoutElementHelpers};
 use dom::element::{reflect_cross_origin_attribute, set_cross_origin_attribute};
 use dom::event::Event;
 use dom::eventtarget::EventTarget;
 use dom::htmlareaelement::HTMLAreaElement;
 use dom::htmlelement::HTMLElement;
+use dom::htmlformelement::{FormControl, HTMLFormElement};
 use dom::htmlmapelement::HTMLMapElement;
 use dom::mouseevent::MouseEvent;
 use dom::node::{Node, NodeDamage, document_from_node, window_from_node};
 use dom::values::UNSIGNED_LONG_MAX;
 use dom::virtualmethods::VirtualMethods;
 use dom::window::Window;
 use dom_struct::dom_struct;
 use euclid::point::Point2D;
@@ -73,16 +74,17 @@ struct ImageRequest {
     image: Option<Arc<Image>>,
     metadata: Option<ImageMetadata>,
 }
 #[dom_struct]
 pub struct HTMLImageElement {
     htmlelement: HTMLElement,
     current_request: DOMRefCell<ImageRequest>,
     pending_request: DOMRefCell<ImageRequest>,
+    form_owner: MutNullableJS<HTMLFormElement>,
     generation: Cell<u32>,
 }
 
 impl HTMLImageElement {
     pub fn get_url(&self) -> Option<ServoUrl> {
         self.current_request.borrow().parsed_url.clone()
     }
 }
@@ -379,16 +381,17 @@ impl HTMLImageElement {
             pending_request: DOMRefCell::new(ImageRequest {
                 state: State::Unavailable,
                 parsed_url: None,
                 source_url: None,
                 image: None,
                 metadata: None,
                 blocker: None,
             }),
+            form_owner: Default::default(),
             generation: Default::default(),
         }
     }
 
     #[allow(unrooted_must_root)]
     pub fn new(local_name: LocalName,
                prefix: Option<DOMString>,
                document: &Document) -> Root<HTMLImageElement> {
@@ -684,16 +687,34 @@ impl VirtualMethods for HTMLImageElement
                    element.activation_behavior(event, self.upcast());
                    return
                }
            }
        }
     }
 }
 
+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.
     let value = if value > UNSIGNED_LONG_MAX {
         0
     } else {
         value
     };
--- a/servo/components/script/dom/htmlinputelement.rs
+++ b/servo/components/script/dom/htmlinputelement.rs
@@ -95,16 +95,17 @@ pub struct HTMLInputElement {
     minlength: Cell<i32>,
     #[ignore_heap_size_of = "#7193"]
     textinput: DOMRefCell<TextInput<IpcSender<ConstellationMsg>>>,
     activation_state: DOMRefCell<InputActivationState>,
     // https://html.spec.whatwg.org/multipage/#concept-input-value-dirty-flag
     value_dirty: Cell<bool>,
 
     filelist: MutNullableJS<FileList>,
+    form_owner: MutNullableJS<HTMLFormElement>,
 }
 
 #[derive(JSTraceable)]
 #[must_root]
 #[derive(HeapSizeOf)]
 struct InputActivationState {
     indeterminate: bool,
     checked: bool,
@@ -151,16 +152,17 @@ impl HTMLInputElement {
                                                       DOMString::new(),
                                                       chan,
                                                       None,
                                                       None,
                                                       SelectionDirection::None)),
             activation_state: DOMRefCell::new(InputActivationState::new()),
             value_dirty: Cell::new(false),
             filelist: MutNullableJS::new(None),
+            form_owner: Default::default(),
         }
     }
 
     #[allow(unrooted_must_root)]
     pub fn new(local_name: LocalName,
                prefix: Option<DOMString>,
                document: &Document) -> Root<HTMLInputElement> {
         Node::reflect_node(box HTMLInputElement::new_inherited(local_name, prefix, document),
@@ -1039,17 +1041,20 @@ impl VirtualMethods for HTMLInputElement
                 match mutation {
                     AttributeMutation::Set(_) => {
                         el.set_read_write_state(false);
                     },
                     AttributeMutation::Removed => {
                         el.set_read_write_state(!el.disabled_state());
                     }
                 }
-            }
+            },
+            &local_name!("form") => {
+                self.form_attribute_mutated(mutation);
+            },
             _ => {},
         }
     }
 
     fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
         match name {
             &local_name!("accept") => AttrValue::from_comma_separated_tokenlist(value.into()),
             &local_name!("name") => AttrValue::from_atomic(value.into()),
@@ -1158,17 +1163,29 @@ impl VirtualMethods for HTMLInputElement
                                                EventBubbles::Bubbles,
                                                EventCancelable::NotCancelable,
                                                &window);
                 }
             }
     }
 }
 
-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 {
         // https://html.spec.whatwg.org/multipage/#candidate-for-constraint-validation
         true
     }
     fn validate(&self, _validate_flags: ValidationFlags) -> bool {
         // call stub methods defined in validityState.rs file here according to the flags set in validate_flags
--- a/servo/components/script/dom/htmllabelelement.rs
+++ b/servo/components/script/dom/htmllabelelement.rs
@@ -1,42 +1,43 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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::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;
 use html5ever_atoms::LocalName;
 use style::attr::AttrValue;
 
 #[dom_struct]
 pub struct HTMLLabelElement {
-    htmlelement: HTMLElement,
+    htmlelement: HTMLElement
 }
 
 impl HTMLLabelElement {
     fn new_inherited(local_name: LocalName,
                      prefix: Option<DOMString>,
                      document: &Document) -> HTMLLabelElement {
         HTMLLabelElement {
             htmlelement:
-                HTMLElement::new_inherited(local_name, prefix, document)
+                HTMLElement::new_inherited(local_name, prefix, document),
         }
     }
 
     #[allow(unrooted_must_root)]
     pub fn new(local_name: LocalName,
                prefix: Option<DOMString>,
                document: &Document) -> Root<HTMLLabelElement> {
         Node::reflect_node(box HTMLLabelElement::new_inherited(local_name, prefix, document),
@@ -123,21 +124,46 @@ impl VirtualMethods for HTMLLabelElement
     }
 
     fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
         match name {
             &local_name!("for") => AttrValue::from_atomic(value.into()),
             _ => 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 {
     pub fn first_labelable_descendant(&self) -> Option<Root<HTMLElement>> {
         self.upcast::<Node>()
             .traverse_preorder()
             .filter_map(Root::downcast::<HTMLElement>)
             .filter(|elem| elem.is_labelable_element())
             .next()
     }
 }
 
-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>()
+    }
+}
--- a/servo/components/script/dom/htmllegendelement.rs
+++ b/servo/components/script/dom/htmllegendelement.rs
@@ -1,39 +1,43 @@
 // This Source Code Form is subject to the terms of the Mozilla Public
 // 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::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;
 use dom::htmlelement::HTMLElement;
 use dom::htmlfieldsetelement::HTMLFieldSetElement;
 use dom::htmlformelement::{HTMLFormElement, FormControl};
 use dom::node::{Node, UnbindContext};
 use dom::virtualmethods::VirtualMethods;
 use dom_struct::dom_struct;
 use html5ever_atoms::LocalName;
 
 #[dom_struct]
 pub struct HTMLLegendElement {
     htmlelement: HTMLElement,
+    form_owner: MutNullableJS<HTMLFormElement>,
 }
 
 impl HTMLLegendElement {
     fn new_inherited(local_name: LocalName,
                      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)]
     pub fn new(local_name: LocalName,
                prefix: Option<DOMString>,
                document: &Document)
                -> Root<HTMLLegendElement> {
         Node::reflect_node(box HTMLLegendElement::new_inherited(local_name, prefix, document),
@@ -78,9 +82,21 @@ impl HTMLLegendElementMethods for HTMLLe
         };
         if parent.is::<HTMLFieldSetElement>() {
             return self.form_owner();
         }
         None
     }
 }
 
-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>()
+    }
+}
--- a/servo/components/script/dom/htmlobjectelement.rs
+++ b/servo/components/script/dom/htmlobjectelement.rs
@@ -2,46 +2,49 @@
  * 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::cell::DOMRefCell;
 use dom::bindings::codegen::Bindings::HTMLObjectElementBinding;
 use dom::bindings::codegen::Bindings::HTMLObjectElementBinding::HTMLObjectElementMethods;
 use dom::bindings::inheritance::Castable;
-use dom::bindings::js::Root;
+use dom::bindings::js::{MutNullableJS, Root};
 use dom::bindings::str::DOMString;
 use dom::document::Document;
 use dom::element::{AttributeMutation, Element};
 use dom::htmlelement::HTMLElement;
 use dom::htmlformelement::{FormControl, HTMLFormElement};
 use dom::node::{Node, window_from_node};
 use dom::validation::Validatable;
 use dom::validitystate::{ValidityState, ValidationFlags};
 use dom::virtualmethods::VirtualMethods;
 use dom_struct::dom_struct;
 use html5ever_atoms::LocalName;
 use net_traits::image::base::Image;
+use std::default::Default;
 use std::sync::Arc;
 
 #[dom_struct]
 pub struct HTMLObjectElement {
     htmlelement: HTMLElement,
     #[ignore_heap_size_of = "Arc"]
     image: DOMRefCell<Option<Arc<Image>>>,
+    form_owner: MutNullableJS<HTMLFormElement>,
 }
 
 impl HTMLObjectElement {
     fn new_inherited(local_name: LocalName,
                      prefix: Option<DOMString>,
                      document: &Document) -> HTMLObjectElement {
         HTMLObjectElement {
             htmlelement:
                 HTMLElement::new_inherited(local_name, prefix, document),
             image: DOMRefCell::new(None),
+            form_owner: Default::default(),
         }
     }
 
     #[allow(unrooted_must_root)]
     pub fn new(local_name: LocalName,
                prefix: Option<DOMString>,
                document: &Document) -> Root<HTMLObjectElement> {
         Node::reflect_node(box HTMLObjectElement::new_inherited(local_name, prefix, document),
@@ -109,14 +112,29 @@ impl VirtualMethods for HTMLObjectElemen
     fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
         self.super_type().unwrap().attribute_mutated(attr, mutation);
         match attr.local_name() {
             &local_name!("data") => {
                 if let AttributeMutation::Set(_) = mutation {
                     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>()
+    }
+}
--- a/servo/components/script/dom/htmloutputelement.rs
+++ b/servo/components/script/dom/htmloutputelement.rs
@@ -1,38 +1,43 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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 {
     fn new_inherited(local_name: LocalName,
                      prefix: Option<DOMString>,
                      document: &Document) -> HTMLOutputElement {
         HTMLOutputElement {
             htmlelement:
-                HTMLElement::new_inherited(local_name, prefix, document)
+                HTMLElement::new_inherited(local_name, prefix, document),
+            form_owner: Default::default(),
         }
     }
 
     #[allow(unrooted_must_root)]
     pub fn new(local_name: LocalName,
                prefix: Option<DOMString>,
                document: &Document) -> Root<HTMLOutputElement> {
         Node::reflect_node(box HTMLOutputElement::new_inherited(local_name, prefix, document),
@@ -54,9 +59,37 @@ impl HTMLOutputElementMethods for HTMLOu
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-lfe-labels
     fn Labels(&self) -> Root<NodeList> {
         self.upcast::<HTMLElement>().labels()
     }
 }
 
-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>()
+    }
+}
--- a/servo/components/script/dom/htmlselectelement.rs
+++ b/servo/components/script/dom/htmlselectelement.rs
@@ -27,16 +27,17 @@ use dom::htmloptionelement::HTMLOptionEl
 use dom::htmloptionscollection::HTMLOptionsCollection;
 use dom::node::{Node, UnbindContext, window_from_node};
 use dom::nodelist::NodeList;
 use dom::validation::Validatable;
 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::*;
 
 #[derive(JSTraceable, HeapSizeOf)]
 struct OptionsFilter;
 impl CollectionFilter for OptionsFilter {
     fn filter<'a>(&self, elem: &'a Element, root: &'a Node) -> bool {
@@ -56,29 +57,31 @@ impl CollectionFilter for OptionsFilter 
         }
     }
 }
 
 #[dom_struct]
 pub struct HTMLSelectElement {
     htmlelement: HTMLElement,
     options: MutNullableJS<HTMLOptionsCollection>,
+    form_owner: MutNullableJS<HTMLFormElement>,
 }
 
 static DEFAULT_SELECT_SIZE: u32 = 0;
 
 impl HTMLSelectElement {
     fn new_inherited(local_name: LocalName,
                      prefix: Option<DOMString>,
                      document: &Document) -> HTMLSelectElement {
         HTMLSelectElement {
             htmlelement:
                 HTMLElement::new_inherited_with_state(IN_ENABLED_STATE,
                                                       local_name, prefix, document),
-                options: Default::default()
+                options: Default::default(),
+                form_owner: Default::default(),
         }
     }
 
     #[allow(unrooted_must_root)]
     pub fn new(local_name: LocalName,
                prefix: Option<DOMString>,
                document: &Document) -> Root<HTMLSelectElement> {
         Node::reflect_node(box HTMLSelectElement::new_inherited(local_name, prefix, document),
@@ -339,29 +342,35 @@ impl HTMLSelectElementMethods for HTMLSe
 
 impl VirtualMethods for HTMLSelectElement {
     fn super_type(&self) -> Option<&VirtualMethods> {
         Some(self.upcast::<HTMLElement>() as &VirtualMethods)
     }
 
     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);
+            },
+            _ => {},
         }
     }
 
     fn bind_to_tree(&self, tree_in_doc: bool) {
         if let Some(ref s) = self.super_type() {
             s.bind_to_tree(tree_in_doc);
         }
 
@@ -383,17 +392,29 @@ impl VirtualMethods for HTMLSelectElemen
     fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue {
         match *local_name {
             local_name!("size") => AttrValue::from_u32(value.into(), DEFAULT_SELECT_SIZE),
             _ => self.super_type().unwrap().parse_plain_attribute(local_name, value),
         }
     }
 }
 
-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 {
         true
     }
     fn validate(&self, validate_flags: ValidationFlags) -> bool {
         if validate_flags.is_empty() {}
         // Need more flag check for different validation types later
--- a/servo/components/script/dom/htmltextareaelement.rs
+++ b/servo/components/script/dom/htmltextareaelement.rs
@@ -4,17 +4,17 @@
 
 use dom::attr::Attr;
 use dom::bindings::cell::DOMRefCell;
 use dom::bindings::codegen::Bindings::EventBinding::EventMethods;
 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};
 use dom::element::RawLayoutElementHelpers;
 use dom::event::{Event, EventBubbles, EventCancelable};
 use dom::globalscope::GlobalScope;
 use dom::htmlelement::HTMLElement;
 use dom::htmlfieldsetelement::HTMLFieldSetElement;
@@ -25,29 +25,31 @@ use dom::node::{document_from_node, wind
 use dom::nodelist::NodeList;
 use dom::validation::Validatable;
 use dom::virtualmethods::VirtualMethods;
 use dom_struct::dom_struct;
 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::*;
 use textinput::{KeyReaction, Lines, SelectionDirection, TextInput};
 
 #[dom_struct]
 pub struct HTMLTextAreaElement {
     htmlelement: HTMLElement,
     #[ignore_heap_size_of = "#7193"]
     textinput: DOMRefCell<TextInput<IpcSender<ConstellationMsg>>>,
     placeholder: DOMRefCell<DOMString>,
     // https://html.spec.whatwg.org/multipage/#concept-textarea-dirty
     value_changed: Cell<bool>,
+    form_owner: MutNullableJS<HTMLFormElement>,
 }
 
 pub trait LayoutHTMLTextAreaElementHelpers {
     #[allow(unsafe_code)]
     unsafe fn get_value_for_layout(self) -> String;
     #[allow(unsafe_code)]
     unsafe fn selection_for_layout(self) -> Option<Range<usize>>;
     #[allow(unsafe_code)]
@@ -111,16 +113,17 @@ impl HTMLTextAreaElement {
         HTMLTextAreaElement {
             htmlelement:
                 HTMLElement::new_inherited_with_state(IN_ENABLED_STATE | IN_READ_WRITE_STATE,
                                                       local_name, prefix, document),
             placeholder: DOMRefCell::new(DOMString::new()),
             textinput: DOMRefCell::new(TextInput::new(
                     Lines::Multiple, DOMString::new(), chan, None, None, SelectionDirection::None)),
             value_changed: Cell::new(false),
+            form_owner: Default::default(),
         }
     }
 
     #[allow(unrooted_must_root)]
     pub fn new(local_name: LocalName,
                prefix: Option<DOMString>,
                document: &Document) -> Root<HTMLTextAreaElement> {
         Node::reflect_node(box HTMLTextAreaElement::new_inherited(local_name, prefix, document),
@@ -337,17 +340,20 @@ impl VirtualMethods for HTMLTextAreaElem
                 match mutation {
                     AttributeMutation::Set(_) => {
                         el.set_read_write_state(false);
                     },
                     AttributeMutation::Removed => {
                         el.set_read_write_state(!el.disabled_state());
                     }
                 }
-            }
+            },
+            local_name!("form") => {
+                self.form_attribute_mutated(mutation);
+            },
             _ => {},
         }
     }
 
     fn bind_to_tree(&self, tree_in_doc: bool) {
         if let Some(ref s) = self.super_type() {
             s.bind_to_tree(tree_in_doc);
         }
@@ -430,12 +436,24 @@ impl VirtualMethods for HTMLTextAreaElem
     fn pop(&self) {
         self.super_type().unwrap().pop();
 
         // https://html.spec.whatwg.org/multipage/#the-textarea-element:stack-of-open-elements
         self.reset();
     }
 }
 
-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 {}
--- a/servo/components/script/dom/node.rs
+++ b/servo/components/script/dom/node.rs
@@ -157,17 +157,21 @@ bitflags! {
         const CLICK_IN_PROGRESS = 0x10,
         #[doc = "Specifies whether this node is focusable and whether it is supposed \
                  to be reachable with using sequential focus navigation."]
         const SEQUENTIALLY_FOCUSABLE = 0x20,
 
         /// 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,
     }
 }
 
 impl NodeFlags {
     pub fn new() -> NodeFlags {
         NodeFlags::empty()
     }
 }
@@ -281,16 +285,21 @@ impl Node {
         child.prev_sibling.set(None);
         child.next_sibling.set(None);
         child.parent_node.set(None);
         self.children_count.set(self.children_count.get() - 1);
 
         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));
         }
 
         self.owner_doc().content_and_heritage_changed(self, NodeDamage::OtherNodeDamage);
         child.owner_doc().content_and_heritage_changed(child, NodeDamage::OtherNodeDamage);
     }
 
@@ -2651,8 +2660,46 @@ impl Into<LayoutElementType> for Element
                 LayoutElementType::HTMLTextAreaElement,
             ElementTypeId::SVGElement(SVGElementTypeId::SVGGraphicsElement(SVGGraphicsElementTypeId::SVGSVGElement)) =>
                 LayoutElementType::SVGSVGElement,
             _ => LayoutElementType::Element,
         }
     }
 }
 
+/// 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));
+    }
+}
--- a/servo/components/script/dom/servoparser/html.rs
+++ b/servo/components/script/dom/servoparser/html.rs
@@ -10,31 +10,32 @@ use dom::bindings::inheritance::{Castabl
 use dom::bindings::js::{JS, Root};
 use dom::bindings::str::DOMString;
 use dom::bindings::trace::JSTraceable;
 use dom::characterdata::CharacterData;
 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};
 use html5ever::tendril::StrTendril;
 use html5ever::tokenizer::{Tokenizer as HtmlTokenizer, TokenizerOpts, TokenizerResult};
 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;
 use std::io::{self, Write};
 use style::context::QuirksMode as ServoQuirksMode;
 
 #[derive(HeapSizeOf, JSTraceable)]
 #[must_root]
@@ -154,16 +155,23 @@ impl TreeSink for Sink {
         let elem = target.downcast::<Element>()
             .expect("tried to get name of non-Element in HTML parsing");
         QualName {
             ns: elem.namespace().clone(),
             local: elem.local_name().clone(),
         }
     }
 
+    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,
                                    ElementCreator::ParserCreated(self.current_line));
 
         for attr in attrs {
             elem.set_attribute_from_parser(attr.name, DOMString::from(String::from(attr.value)), None);
         }
@@ -171,27 +179,43 @@ impl TreeSink for Sink {
         JS::from_ref(elem.upcast())
     }
 
     fn create_comment(&mut self, text: StrTendril) -> JS<Node> {
         let comment = Comment::new(DOMString::from(String::from(text)), &*self.document);
         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>) {
         debug!("Parse error: {}", msg);
     }
 
     fn set_quirks_mode(&mut self, mode: QuirksMode) {
         let mode = match mode {
--- a/servo/components/script/dom/virtualmethods.rs
+++ b/servo/components/script/dom/virtualmethods.rs
@@ -33,16 +33,17 @@ use dom::htmlinputelement::HTMLInputElem
 use dom::htmllabelelement::HTMLLabelElement;
 use dom::htmllielement::HTMLLIElement;
 use dom::htmllinkelement::HTMLLinkElement;
 use dom::htmlmediaelement::HTMLMediaElement;
 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;
 use dom::htmltablecellelement::HTMLTableCellElement;
 use dom::htmltableelement::HTMLTableElement;
 use dom::htmltablerowelement::HTMLTableRowElement;
 use dom::htmltablesectionelement::HTMLTableSectionElement;
 use dom::htmltemplateelement::HTMLTemplateElement;
@@ -207,16 +208,19 @@ pub fn vtable_for(node: &Node) -> &Virtu
             node.downcast::<HTMLObjectElement>().unwrap() as &VirtualMethods
         }
         NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOptGroupElement)) => {
             node.downcast::<HTMLOptGroupElement>().unwrap() as &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
         }
         NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) => {
             node.downcast::<HTMLSelectElement>().unwrap() as &VirtualMethods
         }
         NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLStyleElement)) => {
             node.downcast::<HTMLStyleElement>().unwrap() as &VirtualMethods