mirror of
https://github.com/servo/servo.git
synced 2025-07-24 15:50:21 +01:00
Implement the form owner concept
This commit is contained in:
parent
f90e19f705
commit
38a61712e4
25 changed files with 1004 additions and 165 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
@ -1133,7 +1133,7 @@ dependencies = [
|
|||
|
||||
[[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)",
|
||||
|
@ -2252,7 +2252,7 @@ dependencies = [
|
|||
"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)",
|
||||
|
@ -3412,7 +3412,7 @@ dependencies = [
|
|||
"checksum heartbeats-simple 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9ad003ce233955e9d95f2c69cde84e68302ba9ba4a673d351c9bff93c738aadc"
|
||||
"checksum heartbeats-simple-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e1a408c0011427cc0e0049f7861c70377819aedfc006e8c901b1c70fd98fb1a4"
|
||||
"checksum hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2da7d3a34cf6406d9d700111b8eafafe9a251de41ae71d8052748259343b58"
|
||||
"checksum html5ever 0.13.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"
|
||||
|
|
|
@ -45,7 +45,7 @@ fnv = "1.0"
|
|||
gfx_traits = {path = "../gfx_traits"}
|
||||
heapsize = "0.3.6"
|
||||
heapsize_derive = "0.1"
|
||||
html5ever = {version = "0.13", features = ["heap_size", "unstable"]}
|
||||
html5ever = {version = "0.14", features = ["heap_size", "unstable"]}
|
||||
html5ever-atoms = {version = "0.2", features = ["heap_size"]}
|
||||
hyper = "0.9.9"
|
||||
hyper_serde = "0.5"
|
||||
|
|
|
@ -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,6 +583,10 @@ impl Document {
|
|||
self,
|
||||
to_unregister,
|
||||
id);
|
||||
// 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,
|
||||
|
@ -591,6 +602,8 @@ impl Document {
|
|||
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) {
|
||||
|
@ -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();
|
||||
|
||||
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;
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
|
||||
elements.insert(head, JS::from_ref(element));
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
elem.upcast::<Node>().ancestors().filter_map(Root::downcast).next()
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,7 +347,8 @@ 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") {
|
||||
match attr.local_name() {
|
||||
&local_name!("disabled") => {
|
||||
let el = self.upcast::<Element>();
|
||||
match mutation {
|
||||
AttributeMutation::Set(_) => {
|
||||
|
@ -357,6 +361,11 @@ impl VirtualMethods for HTMLSelectElement {
|
|||
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 {
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -93571,6 +93571,24 @@
|
|||
{}
|
||||
]
|
||||
],
|
||||
"html/semantics/forms/form-control-infrastructure/form_attribute.html": [
|
||||
[
|
||||
"/html/semantics/forms/form-control-infrastructure/form_attribute.html",
|
||||
{}
|
||||
]
|
||||
],
|
||||
"html/semantics/forms/form-control-infrastructure/form_owner_and_table.html": [
|
||||
[
|
||||
"/html/semantics/forms/form-control-infrastructure/form_owner_and_table.html",
|
||||
{}
|
||||
]
|
||||
],
|
||||
"html/semantics/forms/form-control-infrastructure/form_owner_and_table_2.html": [
|
||||
[
|
||||
"/html/semantics/forms/form-control-infrastructure/form_owner_and_table_2.html",
|
||||
{}
|
||||
]
|
||||
],
|
||||
"html/semantics/forms/form-submission-0/form-data-set-usv.html": [
|
||||
[
|
||||
"/html/semantics/forms/form-submission-0/form-data-set-usv.html",
|
||||
|
@ -176716,6 +176734,18 @@
|
|||
"bfd11561a2e568668b6aaf94bc6da9e42fccdf55",
|
||||
"testharness"
|
||||
],
|
||||
"html/semantics/forms/form-control-infrastructure/form_attribute.html": [
|
||||
"b19b882091ff523e71397bfcad5dcf505e0a8f88",
|
||||
"testharness"
|
||||
],
|
||||
"html/semantics/forms/form-control-infrastructure/form_owner_and_table.html": [
|
||||
"7172d74bb5572091ee09abf30a4f6892b85aca3a",
|
||||
"testharness"
|
||||
],
|
||||
"html/semantics/forms/form-control-infrastructure/form_owner_and_table_2.html": [
|
||||
"3b1dea35bb4af384125904d69bf569109965714f",
|
||||
"testharness"
|
||||
],
|
||||
"html/semantics/forms/form-submission-0/.gitkeep": [
|
||||
"da39a3ee5e6b4b0d3255bfef95601890afd80709",
|
||||
"support"
|
||||
|
|
|
@ -3,30 +3,8 @@
|
|||
[keygen.form]
|
||||
expected: FAIL
|
||||
|
||||
[label.form]
|
||||
expected: FAIL
|
||||
|
||||
[label-form.form]
|
||||
expected: FAIL
|
||||
|
||||
[label-form-form2.form]
|
||||
expected: FAIL
|
||||
|
||||
[label-with-progress.form]
|
||||
expected: FAIL
|
||||
|
||||
[label-with-meter.form]
|
||||
expected: FAIL
|
||||
|
||||
[label-for-control-form-in-form.form]
|
||||
expected: FAIL
|
||||
|
||||
[label-for-control-form.form]
|
||||
expected: FAIL
|
||||
|
||||
[label-in-table-with-control.form]
|
||||
expected: FAIL
|
||||
|
||||
[label-in-table-for.form]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -3,9 +3,3 @@
|
|||
[the element is barred from constraint validation]
|
||||
expected: FAIL
|
||||
|
||||
[reset button resets controls associated with their form using the form element pointer]
|
||||
expected: FAIL
|
||||
|
||||
[reset button resets controls associated with a form using the form attribute]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -2,10 +2,3 @@
|
|||
type: testharness
|
||||
[A non-control follows by a control with same ID.]
|
||||
expected: FAIL
|
||||
|
||||
[A label in a form without a control]
|
||||
expected: FAIL
|
||||
|
||||
[A label outside a form with a control inside the form]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -0,0 +1,233 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<form id="f1"></form>
|
||||
<form id="f2">
|
||||
<input id="i1" />
|
||||
<input id="i2" form="f1" />
|
||||
<input id="i3" />
|
||||
</form>
|
||||
<script>
|
||||
test(function() {
|
||||
var i1 = document.getElementById("i1");
|
||||
var i2 = document.getElementById("i2");
|
||||
var i3 = document.getElementById("i3");
|
||||
var f1 = document.getElementById("f1");
|
||||
var f2 = document.getElementById("f2");
|
||||
|
||||
assert_equals(i1.form, f2,
|
||||
"i1 must be associated with f2 by the parser");
|
||||
assert_equals(i2.form, f1,
|
||||
"i2 is not associated with f2 by the parser " +
|
||||
"since it has the form attribute set to f1");
|
||||
|
||||
f1.appendChild(i1);
|
||||
i3.setAttribute("form", "f1");
|
||||
|
||||
assert_equals(i1.form, f1,
|
||||
"i1's form owner must be reset when parent changes");
|
||||
assert_equals(i3.form, f1,
|
||||
"i3's form owner must be reset when the form" +
|
||||
"attribute is set");
|
||||
|
||||
assert_equals(i2.form, f1);
|
||||
}, "Tests for parser inserted controls");
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<div id="placeholder">
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var reassociateableElements = [
|
||||
"button",
|
||||
"fieldset",
|
||||
"input",
|
||||
"object",
|
||||
"output",
|
||||
"select",
|
||||
"textarea",
|
||||
];
|
||||
|
||||
var form1 = null;
|
||||
var form2 = null;
|
||||
var placeholder = document.getElementById("placeholder");
|
||||
|
||||
reassociateableElements.forEach(function(localName) {
|
||||
function testControl(test_, desc) {
|
||||
test(function() {
|
||||
var control = document.createElement(localName);
|
||||
|
||||
while(placeholder.firstChild)
|
||||
placeholder.removeChild(placeholder.firstChild);
|
||||
|
||||
form1 = document.createElement("form");
|
||||
form2 = document.createElement("form");
|
||||
form1.id = "form1";
|
||||
form2.id = "form2";
|
||||
placeholder.appendChild(form1);
|
||||
placeholder.appendChild(form2);
|
||||
|
||||
test_.call(control);
|
||||
}, "[" + localName.toUpperCase() + "] " + desc);
|
||||
}
|
||||
|
||||
testControl(function() {
|
||||
form1.appendChild(this);
|
||||
assert_equals(this.form, form1);
|
||||
}, "Basic form association - control with no form attribute is associated with ancestor");
|
||||
|
||||
testControl(function() {
|
||||
this.setAttribute("form", "form1");
|
||||
form1.appendChild(this);
|
||||
assert_equals(this.form, form1);
|
||||
|
||||
form1.id = "form-one";
|
||||
assert_equals(this.form, null);
|
||||
}, "Form owner is reset to null when control's form attribute is set to an ID " +
|
||||
"that does not exist in the document");
|
||||
|
||||
testControl(function() {
|
||||
this.setAttribute("form", "");
|
||||
form1.appendChild(this);
|
||||
assert_equals(this.form, null);
|
||||
}, "Control whose form attribute is an empty string has no form owner");
|
||||
|
||||
testControl(function() {
|
||||
form1.id = "";
|
||||
this.setAttribute("form", "");
|
||||
form1.appendChild(this);
|
||||
assert_equals(this.form, null);
|
||||
}, "Control whose form attribute is an empty string has no form owner " +
|
||||
"even when form with empty attribute is present");
|
||||
|
||||
testControl(function() {
|
||||
form1.id = "FORM1";
|
||||
this.setAttribute("form", "form1");
|
||||
form1.appendChild(this);
|
||||
assert_equals(this.form, null);
|
||||
}, "Control's form attribute must be a case sensitive match for the form's id");
|
||||
|
||||
testControl(function() {
|
||||
form1.appendChild(this);
|
||||
assert_equals(this.form, form1);
|
||||
|
||||
this.setAttribute("form", "form2");
|
||||
assert_equals(this.form, form2);
|
||||
}, "Setting the form attribute of a control to the id of a non-ancestor form works");
|
||||
|
||||
testControl(function() {
|
||||
this.setAttribute("form", "form1");
|
||||
|
||||
form2.appendChild(this);
|
||||
assert_equals(this.form, form1);
|
||||
|
||||
this.removeAttribute("form");
|
||||
assert_equals(this.form, form2);
|
||||
}, "Removing form id from a control resets the form owner to ancestor");
|
||||
|
||||
testControl(function() {
|
||||
this.setAttribute("form", "form1");
|
||||
|
||||
form2.appendChild(this);
|
||||
assert_equals(this.form, form1);
|
||||
|
||||
placeholder.removeChild(form1);
|
||||
|
||||
assert_equals(this.form, null);
|
||||
}, "Removing the form owner of a control with form attribute resets " +
|
||||
"the form owner to null");
|
||||
|
||||
testControl(function() {
|
||||
var form3 = document.createElement("form");
|
||||
form3.id = "form3";
|
||||
placeholder.appendChild(form3);
|
||||
form3.appendChild(this);
|
||||
assert_equals(this.form, form3);
|
||||
|
||||
this.setAttribute("form", "form2");
|
||||
assert_equals(this.form, form2);
|
||||
|
||||
this.setAttribute("form", "form1");
|
||||
assert_equals(this.form, form1);
|
||||
}, "Changing form attibute of control resets form owner to correct form");
|
||||
|
||||
testControl(function() {
|
||||
this.setAttribute("form", "form1");
|
||||
var form3 = document.createElement("form");
|
||||
form3.id = "form3";
|
||||
|
||||
placeholder.appendChild(form3);
|
||||
placeholder.appendChild(this);
|
||||
assert_equals(this.form, form1);
|
||||
|
||||
form1.appendChild(this);
|
||||
assert_equals(this.form, form1);
|
||||
|
||||
form2.appendChild(this);
|
||||
assert_equals(this.form, form1);
|
||||
}, "Moving a control with form attribute within the document " +
|
||||
"does not change the form owner");
|
||||
|
||||
testControl(function() {
|
||||
form1.id = "form-one";
|
||||
this.setAttribute("form", "form1");
|
||||
form2.appendChild(this);
|
||||
assert_equals(this.form, null);
|
||||
|
||||
form1.id = "form1";
|
||||
assert_equals(this.form, form1);
|
||||
}, "When the id of a non-ancestor form changes from not being a match for the " +
|
||||
"form attribute to being a match, the control's form owner is reset");
|
||||
|
||||
testControl(function() {
|
||||
this.setAttribute("form", "form1");
|
||||
form1.appendChild(this);
|
||||
assert_equals(this.form, form1);
|
||||
|
||||
form2.id = "form1";
|
||||
form1.parentNode.insertBefore(form2, form1);
|
||||
assert_equals(this.form, form2);
|
||||
|
||||
form2.parentNode.removeChild(form2);
|
||||
assert_equals(this.form, form1);
|
||||
|
||||
form1.parentNode.appendChild(form2);
|
||||
assert_equals(this.form, form1);
|
||||
}, "When form element with same ID as the control's form attribute is inserted " +
|
||||
"earlier in tree order, the form owner is changed to the inserted form");
|
||||
|
||||
testControl(function() {
|
||||
this.setAttribute("form", "form1");
|
||||
form2.appendChild(this);
|
||||
assert_equals(this.form, form1);
|
||||
|
||||
var span = document.createElement("span");
|
||||
span.id = "form1";
|
||||
form1.parentNode.insertBefore(span, form1);
|
||||
assert_equals(this.form, null);
|
||||
|
||||
form1.parentNode.appendChild(span);
|
||||
assert_equals(this.form, form1);
|
||||
}, "When non-form element with same ID as the control's form attribute is " +
|
||||
"inserted earlier in tree order, the control does not have a form owner");
|
||||
|
||||
testControl(function() {
|
||||
this.setAttribute("form", "form1");
|
||||
form1.appendChild(this);
|
||||
assert_equals(this.form, form1);
|
||||
|
||||
form1.parentNode.removeChild(form1);
|
||||
assert_equals(this.form, form1);
|
||||
}, "A control that is not in the document but has the form attribute set " +
|
||||
"is associated with the nearest ancestor form if one exists");
|
||||
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,50 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">
|
||||
<form id='form1'></form>
|
||||
<table id='table1'>
|
||||
<form id='form2'>
|
||||
<tr><td><input id='input1'></td></tr>
|
||||
<tr><td><input id='input2' form='form1'></td></tr>
|
||||
</table>
|
||||
<form id="form3">
|
||||
<input id="input3" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
test(function() {
|
||||
var input1 = document.getElementById('input1');
|
||||
var input2 = document.getElementById('input2');
|
||||
var input3 = document.getElementById('input3');
|
||||
var form1 = document.getElementById('form1');
|
||||
var form2 = document.getElementById('form2');
|
||||
var form3 = document.getElementById('form3');
|
||||
|
||||
var root = document.getElementById('root');
|
||||
|
||||
assert_equals(input1.form, form2,
|
||||
"input1's form owner must be form2 as per the parsing rules");
|
||||
assert_equals(input2.form, form1,
|
||||
"input2's form owner must be the form with id 'form1'");
|
||||
assert_equals(input3.form, form2,
|
||||
"input3's form owner must be form2 as per the parsing rules");
|
||||
|
||||
root.parentNode.removeChild(root);
|
||||
|
||||
assert_equals(input1.form, form2,
|
||||
"input1's form owner must not have changed since they are both in the same subtree");
|
||||
assert_equals(input2.form, null,
|
||||
"input2 must not have a form owner since it has the form attribute set");
|
||||
assert_equals(input3.form, form2,
|
||||
"input3's form owner must not have changed since they are both in the same subtree");
|
||||
|
||||
}, "Form element and form controls nested inside a table are correctly handled");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,45 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<form id='form1'></form>
|
||||
<table id='table1'>
|
||||
<form id='form2'>
|
||||
<script>
|
||||
var t = document.getElementById('table1');
|
||||
var f = document.getElementById('form2');
|
||||
t.removeChild(f);
|
||||
</script>
|
||||
<tr><td><input id='input1'></td></tr>
|
||||
<tr><td><input id='input2' form='form1'></td></tr>
|
||||
</table>
|
||||
<form id="form3">
|
||||
<input id="input3" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
test(function() {
|
||||
var form1 = document.getElementById('form1');
|
||||
|
||||
assert_equals(document.getElementById('input1').form, null,
|
||||
"input1's form owner must be null since form2 is not in the" +
|
||||
"same home subtree");
|
||||
|
||||
assert_equals(document.getElementById('input2').form, form1,
|
||||
"input2's form owner must be the form with id 'form1'");
|
||||
|
||||
assert_equals(document.getElementById('input3').form, null,
|
||||
"input3's form owner must be null instead of form2 (as per parsing rules)" +
|
||||
"since form2 is not in the same home subtree");
|
||||
|
||||
}, "Controls nested in tables are not associated with form element inside the " +
|
||||
"table if the form had been removed by script before the controls were " +
|
||||
"inserted by the parser");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Add a link
Reference in a new issue