Skip to content

Commit

Permalink
Implement the form owner concept
Browse files Browse the repository at this point in the history
  • Loading branch information
mukilan authored and nox committed Mar 15, 2017
1 parent f90e19f commit 38a6171
Show file tree
Hide file tree
Showing 25 changed files with 1,004 additions and 165 deletions.
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion components/script/Cargo.toml
Expand Up @@ -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"
Expand Down
97 changes: 61 additions & 36 deletions components/script/dom/document.rs
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -576,20 +583,26 @@ impl Document {
self,
to_unregister,
id);
let mut id_map = self.id_map.borrow_mut();
let is_empty = match id_map.get_mut(&id) {
None => false,
Some(elements) => {
let position = elements.iter()
.position(|element| &**element == to_unregister)
.expect("This element should be in registered.");
elements.remove(position);
elements.is_empty()
// Limit the scope of the borrow because id_map might be borrowed again by
// GetElementById through the following sequence of calls
// reset_form_owner_for_listeners -> reset_form_owner -> GetElementById
{
let mut id_map = self.id_map.borrow_mut();
let is_empty = match id_map.get_mut(&id) {
None => false,
Some(elements) => {
let position = elements.iter()
.position(|element| &**element == to_unregister)
.expect("This element should be in registered.");
elements.remove(position);
elements.is_empty()
}
};
if is_empty {
id_map.remove(&id);
}
};
if is_empty {
id_map.remove(&id);
}
self.reset_form_owner_for_listeners(&id);
}

/// Associate an element present in this document with the provided id.
Expand All @@ -601,34 +614,34 @@ impl Document {
assert!(element.upcast::<Node>().is_in_doc());
assert!(!id.is_empty());

let mut id_map = self.id_map.borrow_mut();

let root = self.GetDocumentElement()
.expect("The element is in the document, so there must be a document \
element.");

match id_map.entry(id) {
Vacant(entry) => {
entry.insert(vec![JS::from_ref(element)]);
}
Occupied(entry) => {
let elements = entry.into_mut();
// Limit the scope of the borrow because id_map might be borrowed again by
// GetElementById through the following sequence of calls
// reset_form_owner_for_listeners -> reset_form_owner -> GetElementById
{
let mut id_map = self.id_map.borrow_mut();
let mut elements = id_map.entry(id.clone()).or_insert(Vec::new());
elements.insert_pre_order(element, root.r().upcast::<Node>());
}
self.reset_form_owner_for_listeners(&id);
}

let new_node = element.upcast::<Node>();
let mut head: usize = 0;
let root = root.upcast::<Node>();
for node in root.traverse_preorder() {
if let Some(elem) = node.downcast() {
if &*(*elements)[head] == elem {
head += 1;
}
if new_node == &*node || head == elements.len() {
break;
}
}
}
pub fn register_form_id_listener<T: ?Sized + FormControl>(&self, id: DOMString, listener: &T) {
let mut map = self.form_id_listener_map.borrow_mut();
let listener = listener.to_element();
let mut set = map.entry(Atom::from(id)).or_insert(HashSet::new());
set.insert(JS::from_ref(listener));
}

elements.insert(head, JS::from_ref(element));
pub fn unregister_form_id_listener<T: ?Sized + FormControl>(&self, id: DOMString, listener: &T) {
let mut map = self.form_id_listener_map.borrow_mut();
if let Occupied(mut entry) = map.entry(Atom::from(id)) {
entry.get_mut().remove(&JS::from_ref(listener.to_element()));
if entry.get().is_empty() {
entry.remove();
}
}
}
Expand Down Expand Up @@ -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(),
}
}

Expand Down Expand Up @@ -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();
}
}
}
}


Expand Down
18 changes: 18 additions & 0 deletions components/script/dom/element.rs
Expand Up @@ -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};
Expand All @@ -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};
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand Down
26 changes: 22 additions & 4 deletions components/script/dom/htmlbuttonelement.rs
Expand Up @@ -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};
Expand All @@ -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)]
Expand All @@ -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 {
Expand All @@ -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(),
}
}

Expand Down Expand Up @@ -211,6 +214,9 @@ impl VirtualMethods for HTMLButtonElement {
self.button_type.set(ButtonType::Submit);
}
}
},
&local_name!("form") => {
self.form_attribute_mutated(mutation);
}
_ => {},
}
Expand All @@ -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 {
Expand Down
26 changes: 22 additions & 4 deletions components/script/dom/htmlfieldsetelement.rs
Expand Up @@ -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};
Expand All @@ -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 {
Expand All @@ -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(),
}
}

Expand Down Expand Up @@ -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>()
}
}

0 comments on commit 38a6171

Please sign in to comment.