Introduce VirtualMethods::attribute_mutated()

This replaces before_remove_attr(), after_remove_attr() and after_set_attr().
The virtual method takes the mutated attribute and an AttributeMutation value
to disambiguate between "attribute is changed", "attribute is added" and
"attribute is removed".

In the case of "attribute is changed", the mutation value contains a reference
to the old value of the mutated attribute, which is used to unregister outdated
named elements when the "id" attribute is changed on an element.

This greatly simplifies the handling of attributes, which in many cases don't
have any specific behaviour whether they are removed or changed or added. It
also fixes a few bugs where things were put in before_remove_attr() instead of
after_remove_attr() (e.g. when removing an href attribute from a base element).

A few helper functions in Element were also renamed and made private.
This commit is contained in:
Anthony Ramine 2015-08-28 13:32:38 +02:00
parent 5672142042
commit 58e1bd0e57
27 changed files with 565 additions and 862 deletions

View file

@ -9,7 +9,7 @@ use dom::bindings::global::GlobalRef;
use dom::bindings::js::{JS, MutNullableHeap}; use dom::bindings::js::{JS, MutNullableHeap};
use dom::bindings::js::{Root, RootedReference, LayoutJS}; use dom::bindings::js::{Root, RootedReference, LayoutJS};
use dom::bindings::utils::{Reflector, reflect_dom_object}; use dom::bindings::utils::{Reflector, reflect_dom_object};
use dom::element::Element; use dom::element::{AttributeMutation, Element};
use dom::virtualmethods::vtable_for; use dom::virtualmethods::vtable_for;
use dom::window::Window; use dom::window::Window;
@ -23,12 +23,6 @@ use std::cell::Ref;
use std::mem; use std::mem;
use std::ops::Deref; use std::ops::Deref;
#[derive(HeapSizeOf)]
pub enum AttrSettingType {
FirstSetAttr,
ReplacedAttr,
}
#[derive(JSTraceable, PartialEq, Clone, HeapSizeOf)] #[derive(JSTraceable, PartialEq, Clone, HeapSizeOf)]
pub enum AttrValue { pub enum AttrValue {
String(DOMString), String(DOMString),
@ -94,6 +88,17 @@ impl AttrValue {
_ => None _ => None
} }
} }
/// Return the AttrValue as its integer representation, if any.
/// This corresponds to attribute values returned as `AttrValue::UInt(_)`
/// by `VirtualMethods::parse_plain_attribute()`.
pub fn uint(&self) -> Option<u32> {
if let AttrValue::UInt(_, value) = *self {
Some(value)
} else {
None
}
}
} }
impl Deref for AttrValue { impl Deref for AttrValue {
@ -179,7 +184,7 @@ impl AttrMethods for Attr {
None => *self.value.borrow_mut() = AttrValue::String(value), None => *self.value.borrow_mut() = AttrValue::String(value),
Some(owner) => { Some(owner) => {
let value = owner.r().parse_attribute(&self.namespace, self.local_name(), value); let value = owner.r().parse_attribute(&self.namespace, self.local_name(), value);
self.set_value(AttrSettingType::ReplacedAttr, value, owner.r()); self.set_value(value, owner.r());
} }
} }
} }
@ -236,22 +241,12 @@ impl AttrMethods for Attr {
impl Attr { impl Attr {
pub fn set_value(&self, set_type: AttrSettingType, value: AttrValue, owner: &Element) { pub fn set_value(&self, mut value: AttrValue, owner: &Element) {
assert!(Some(owner) == self.owner().r()); assert!(Some(owner) == self.owner().r());
mem::swap(&mut *self.value.borrow_mut(), &mut value);
let node = NodeCast::from_ref(owner); if self.namespace == ns!("") {
let namespace_is_null = self.namespace == ns!(""); vtable_for(NodeCast::from_ref(owner)).attribute_mutated(
self, AttributeMutation::Set(Some(&value)));
match set_type {
AttrSettingType::ReplacedAttr if namespace_is_null =>
vtable_for(&node).before_remove_attr(self),
_ => ()
}
*self.value.borrow_mut() = value;
if namespace_is_null {
vtable_for(&node).after_set_attr(self)
} }
} }

View file

@ -297,6 +297,7 @@ impl Document {
} }
/// Refresh the cached first base element in the DOM. /// Refresh the cached first base element in the DOM.
/// https://github.com/w3c/web-platform-tests/issues/2122
pub fn refresh_base_element(&self) { pub fn refresh_base_element(&self) {
let base = NodeCast::from_ref(self) let base = NodeCast::from_ref(self)
.traverse_preorder() .traverse_preorder()

View file

@ -6,7 +6,7 @@
use dom::activation::Activatable; use dom::activation::Activatable;
use dom::attr::AttrValue; use dom::attr::AttrValue;
use dom::attr::{Attr, AttrSettingType, AttrHelpersForLayout}; use dom::attr::{Attr, AttrHelpersForLayout};
use dom::bindings::cell::DOMRefCell; use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods; use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
use dom::bindings::codegen::Bindings::ElementBinding; use dom::bindings::codegen::Bindings::ElementBinding;
@ -825,6 +825,22 @@ impl Element {
impl Element { impl Element {
pub fn push_new_attribute(&self,
local_name: Atom,
value: AttrValue,
name: Atom,
namespace: Namespace,
prefix: Option<Atom>) {
let window = window_from_node(self);
let in_empty_ns = namespace == ns!("");
let attr = Attr::new(&window, local_name, value, name, namespace, prefix, Some(self));
self.attrs.borrow_mut().push(JS::from_rooted(&attr));
if in_empty_ns {
vtable_for(NodeCast::from_ref(self)).attribute_mutated(
&attr, AttributeMutation::Set(None));
}
}
pub fn get_attribute(&self, namespace: &Namespace, local_name: &Atom) -> Option<Root<Attr>> { pub fn get_attribute(&self, namespace: &Namespace, local_name: &Atom) -> Option<Root<Attr>> {
self.attrs.borrow().iter().map(JS::root).find(|attr| { self.attrs.borrow().iter().map(JS::root).find(|attr| {
attr.local_name() == local_name && attr.namespace() == namespace attr.local_name() == local_name && attr.namespace() == namespace
@ -856,15 +872,16 @@ impl Element {
}, },
}; };
let value = self.parse_attribute(&qname.ns, &qname.local, value); let value = self.parse_attribute(&qname.ns, &qname.local, value);
self.do_set_attribute(qname.local, value, name, qname.ns, prefix, |_| false) self.push_new_attribute(qname.local, value, name, qname.ns, prefix);
} }
pub fn set_attribute(&self, name: &Atom, value: AttrValue) { pub fn set_attribute(&self, name: &Atom, value: AttrValue) {
assert!(&**name == name.to_ascii_lowercase()); assert!(&**name == name.to_ascii_lowercase());
assert!(!name.contains(":")); assert!(!name.contains(":"));
self.do_set_attribute(name.clone(), value, name.clone(), self.set_first_matching_attribute(
ns!(""), None, |attr| attr.local_name() == name); name.clone(), value, name.clone(), ns!(""), None,
|attr| attr.local_name() == name);
} }
// https://html.spec.whatwg.org/multipage/#attr-data-* // https://html.spec.whatwg.org/multipage/#attr-data-*
@ -878,34 +895,27 @@ impl Element {
// Steps 2-5. // Steps 2-5.
let name = Atom::from_slice(&name); let name = Atom::from_slice(&name);
let value = self.parse_attribute(&ns!(""), &name, value); let value = self.parse_attribute(&ns!(""), &name, value);
self.do_set_attribute(name.clone(), value, name.clone(), ns!(""), None, |attr| { self.set_first_matching_attribute(
*attr.name() == name && *attr.namespace() == ns!("") name.clone(), value, name.clone(), ns!(""), None,
}); |attr| *attr.name() == name && *attr.namespace() == ns!(""));
Ok(()) Ok(())
} }
pub fn do_set_attribute<F>(&self, fn set_first_matching_attribute<F>(&self,
local_name: Atom, local_name: Atom,
value: AttrValue, value: AttrValue,
name: Atom, name: Atom,
namespace: Namespace, namespace: Namespace,
prefix: Option<Atom>, prefix: Option<Atom>,
cb: F) find: F)
where F: Fn(&Attr) -> bool where F: Fn(&Attr)
{ -> bool {
let idx = self.attrs.borrow().iter().map(JS::root).position(|attr| cb(&attr)); let attr = self.attrs.borrow().iter().map(JS::root).find(|attr| find(&attr));
let (idx, set_type) = match idx { if let Some(attr) = attr {
Some(idx) => (idx, AttrSettingType::ReplacedAttr), attr.set_value(value, self);
None => { } else {
let window = window_from_node(self); self.push_new_attribute(local_name, value, name, namespace, prefix);
let attr = Attr::new(window.r(), local_name, value.clone(),
name, namespace.clone(), prefix, Some(self));
self.attrs.borrow_mut().push(JS::from_rooted(&attr));
(self.attrs.borrow().len() - 1, AttrSettingType::FirstSetAttr)
}
}; };
(*self.attrs.borrow())[idx].root().r().set_value(set_type, value, self);
} }
pub fn parse_attribute(&self, namespace: &Namespace, local_name: &Atom, pub fn parse_attribute(&self, namespace: &Namespace, local_name: &Atom,
@ -920,41 +930,27 @@ impl Element {
pub fn remove_attribute(&self, namespace: &Namespace, local_name: &Atom) pub fn remove_attribute(&self, namespace: &Namespace, local_name: &Atom)
-> Option<Root<Attr>> { -> Option<Root<Attr>> {
self.do_remove_attribute(|attr| { self.remove_first_matching_attribute(|attr| {
attr.namespace() == namespace && attr.local_name() == local_name attr.namespace() == namespace && attr.local_name() == local_name
}) })
} }
pub fn remove_attribute_by_name(&self, name: &Atom) -> Option<Root<Attr>> { pub fn remove_attribute_by_name(&self, name: &Atom) -> Option<Root<Attr>> {
self.do_remove_attribute(|attr| attr.name() == name) self.remove_first_matching_attribute(|attr| attr.name() == name)
} }
pub fn do_remove_attribute<F>(&self, find: F) -> Option<Root<Attr>> fn remove_first_matching_attribute<F>(&self, find: F) -> Option<Root<Attr>>
where F: Fn(&Attr) -> bool where F: Fn(&Attr) -> bool
{ {
let idx = self.attrs.borrow().iter().map(JS::root).position(|attr| find(&attr)); let idx = self.attrs.borrow().iter().map(JS::root).position(|attr| find(&attr));
idx.map(|idx| { idx.map(|idx| {
let attr = (*self.attrs.borrow())[idx].root(); let attr = (*self.attrs.borrow())[idx].root();
if attr.r().namespace() == &ns!("") {
vtable_for(&NodeCast::from_ref(self)).before_remove_attr(attr.r());
}
self.attrs.borrow_mut().remove(idx); self.attrs.borrow_mut().remove(idx);
attr.r().set_owner(None); attr.set_owner(None);
if attr.r().namespace() == &ns!("") {
vtable_for(&NodeCast::from_ref(self)).after_remove_attr(attr.r().name());
}
let node = NodeCast::from_ref(self); let node = NodeCast::from_ref(self);
if node.is_in_doc() { if attr.namespace() == &ns!("") {
let document = document_from_node(self); vtable_for(node).attribute_mutated(&attr, AttributeMutation::Removed);
let damage = if attr.r().local_name() == &atom!("style") {
NodeDamage::NodeStyleDamaged
} else {
NodeDamage::OtherNodeDamage
};
document.r().content_changed(node, damage);
} }
attr attr
}) })
@ -1168,9 +1164,9 @@ impl ElementMethods for Element {
// Step 3-5. // Step 3-5.
let value = self.parse_attribute(&ns!(""), &name, value); let value = self.parse_attribute(&ns!(""), &name, value);
self.do_set_attribute(name.clone(), value, name.clone(), ns!(""), None, |attr| { self.set_first_matching_attribute(
*attr.name() == name name.clone(), value, name.clone(), ns!(""), None,
}); |attr| *attr.name() == name);
Ok(()) Ok(())
} }
@ -1183,11 +1179,9 @@ impl ElementMethods for Element {
try!(validate_and_extract(namespace, &qualified_name)); try!(validate_and_extract(namespace, &qualified_name));
let qualified_name = Atom::from_slice(&qualified_name); let qualified_name = Atom::from_slice(&qualified_name);
let value = self.parse_attribute(&namespace, &local_name, value); let value = self.parse_attribute(&namespace, &local_name, value);
self.do_set_attribute(local_name.clone(), value, qualified_name, self.set_first_matching_attribute(
namespace.clone(), prefix, |attr| { local_name.clone(), value, qualified_name, namespace.clone(), prefix,
*attr.local_name() == local_name && |attr| *attr.local_name() == local_name && *attr.namespace() == namespace);
*attr.namespace() == namespace
});
Ok(()) Ok(())
} }
@ -1454,96 +1448,52 @@ impl VirtualMethods for Element {
Some(node as &VirtualMethods) Some(node as &VirtualMethods)
} }
fn after_set_attr(&self, attr: &Attr) { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
if let Some(ref s) = self.super_type() { self.super_type().unwrap().attribute_mutated(attr, mutation);
s.after_set_attr(attr);
}
let node = NodeCast::from_ref(self); let node = NodeCast::from_ref(self);
match attr.local_name() { let doc = node.owner_doc();
&atom!("style") => { let damage = match attr.local_name() {
&atom!(style) => {
// Modifying the `style` attribute might change style. // Modifying the `style` attribute might change style.
let doc = document_from_node(self); *self.style_attribute.borrow_mut() =
let base_url = doc.r().base_url(); mutation.new_value(attr).map(|value| {
let value = attr.value(); parse_style_attribute(&value, &doc.base_url())
let style = Some(parse_style_attribute(&value, &base_url)); });
*self.style_attribute.borrow_mut() = style; NodeDamage::NodeStyleDamaged
},
if node.is_in_doc() { &atom!(class) => {
doc.r().content_changed(node, NodeDamage::NodeStyleDamaged);
}
}
&atom!("class") => {
// Modifying a class can change style. // Modifying a class can change style.
NodeDamage::NodeStyleDamaged
},
&atom!(id) => {
if node.is_in_doc() { if node.is_in_doc() {
let document = document_from_node(self); let value = attr.value().atom().unwrap().clone();
document.r().content_changed(node, NodeDamage::NodeStyleDamaged); match mutation {
AttributeMutation::Set(old_value) => {
if let Some(old_value) = old_value {
let old_value = old_value.atom().unwrap().clone();
doc.unregister_named_element(self, old_value);
}
if value != atom!("") {
doc.register_named_element(self, value);
}
},
AttributeMutation::Removed => {
if value != atom!("") {
doc.unregister_named_element(self, value);
} }
} }
&atom!("id") => {
// Modifying an ID might change style.
let value = attr.value();
if node.is_in_doc() {
let doc = document_from_node(self);
if !value.is_empty() {
let value = value.atom().unwrap().clone();
doc.r().register_named_element(self, value);
}
doc.r().content_changed(node, NodeDamage::NodeStyleDamaged);
} }
} }
NodeDamage::NodeStyleDamaged
},
_ => { _ => {
// Modifying any other attribute might change arbitrary things. // Modifying any other attribute might change arbitrary things.
NodeDamage::OtherNodeDamage
},
};
if node.is_in_doc() { if node.is_in_doc() {
let document = document_from_node(self); doc.content_changed(node, damage);
document.r().content_changed(node, NodeDamage::OtherNodeDamage);
}
}
}
}
fn before_remove_attr(&self, attr: &Attr) {
if let Some(ref s) = self.super_type() {
s.before_remove_attr(attr);
}
let node = NodeCast::from_ref(self);
match attr.local_name() {
&atom!("style") => {
// Modifying the `style` attribute might change style.
*self.style_attribute.borrow_mut() = None;
if node.is_in_doc() {
let doc = document_from_node(self);
doc.r().content_changed(node, NodeDamage::NodeStyleDamaged);
}
}
&atom!("id") => {
// Modifying an ID can change style.
let value = attr.value();
if node.is_in_doc() {
let doc = document_from_node(self);
if !value.is_empty() {
let value = value.atom().unwrap().clone();
doc.r().unregister_named_element(self, value);
}
doc.r().content_changed(node, NodeDamage::NodeStyleDamaged);
}
}
&atom!("class") => {
// Modifying a class can change style.
if node.is_in_doc() {
let document = document_from_node(self);
document.r().content_changed(node, NodeDamage::NodeStyleDamaged);
}
}
_ => {
// Modifying any other attribute might change arbitrary things.
if node.is_in_doc() {
let doc = document_from_node(self);
doc.r().content_changed(node, NodeDamage::OtherNodeDamage);
}
}
} }
} }
@ -1853,3 +1803,23 @@ impl Element {
self.set_click_in_progress(false); self.set_click_in_progress(false);
} }
} }
#[derive(Clone, Copy, PartialEq)]
pub enum AttributeMutation<'a> {
/// The attribute is set, keep track of old value.
/// https://dom.spec.whatwg.org/#attribute-is-set
Set(Option<&'a AttrValue>),
/// The attribute is removed.
/// https://dom.spec.whatwg.org/#attribute-is-removed
Removed
}
impl<'a> AttributeMutation<'a> {
pub fn new_value<'b>(&self, attr: &'b Attr) -> Option<Ref<'b, AttrValue>> {
match *self {
AttributeMutation::Set(_) => Some(attr.value()),
AttributeMutation::Removed => None,
}
}
}

View file

@ -9,7 +9,7 @@ use dom::bindings::codegen::InheritTypes::HTMLBaseElementDerived;
use dom::bindings::codegen::InheritTypes::HTMLElementCast; use dom::bindings::codegen::InheritTypes::HTMLElementCast;
use dom::bindings::js::Root; use dom::bindings::js::Root;
use dom::document::Document; use dom::document::Document;
use dom::element::ElementTypeId; use dom::element::{AttributeMutation, ElementTypeId};
use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
use dom::node::{Node, NodeTypeId, document_from_node}; use dom::node::{Node, NodeTypeId, document_from_node};
@ -56,15 +56,6 @@ impl HTMLBaseElement {
parsed.unwrap_or(base) parsed.unwrap_or(base)
} }
/// Update the cached base element in response to adding or removing an
/// attribute.
pub fn add_remove_attr(&self, attr: &Attr) {
if *attr.local_name() == atom!("href") {
let document = document_from_node(self);
document.refresh_base_element();
}
}
/// Update the cached base element in response to binding or unbinding from /// Update the cached base element in response to binding or unbinding from
/// a tree. /// a tree.
pub fn bind_unbind(&self, tree_in_doc: bool) { pub fn bind_unbind(&self, tree_in_doc: bool) {
@ -84,14 +75,11 @@ impl VirtualMethods for HTMLBaseElement {
Some(HTMLElementCast::from_ref(self) as &VirtualMethods) Some(HTMLElementCast::from_ref(self) as &VirtualMethods)
} }
fn after_set_attr(&self, attr: &Attr) { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
self.super_type().unwrap().after_set_attr(attr); self.super_type().unwrap().attribute_mutated(attr, mutation);
self.add_remove_attr(attr); if *attr.local_name() == atom!(href) {
document_from_node(self).refresh_base_element();
} }
fn before_remove_attr(&self, attr: &Attr) {
self.super_type().unwrap().before_remove_attr(attr);
self.add_remove_attr(attr);
} }
fn bind_to_tree(&self, tree_in_doc: bool) { fn bind_to_tree(&self, tree_in_doc: bool) {

View file

@ -12,7 +12,7 @@ use dom::bindings::codegen::InheritTypes::{HTMLBodyElementDerived, HTMLElementCa
use dom::bindings::js::Root; use dom::bindings::js::Root;
use dom::bindings::utils::Reflectable; use dom::bindings::utils::Reflectable;
use dom::document::Document; use dom::document::Document;
use dom::element::ElementTypeId; use dom::element::{AttributeMutation, ElementTypeId};
use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
use dom::node::{Node, NodeTypeId, window_from_node, document_from_node}; use dom::node::{Node, NodeTypeId, window_from_node, document_from_node};
@ -123,22 +123,30 @@ impl VirtualMethods for HTMLBodyElement {
chan.send(event).unwrap(); chan.send(event).unwrap();
} }
fn after_set_attr(&self, attr: &Attr) { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
if let Some(ref s) = self.super_type() { self.super_type().unwrap().attribute_mutated(attr, mutation);
s.after_set_attr(attr); match (attr.local_name(), mutation) {
} (&atom!(bgcolor), _) => {
self.background_color.set(mutation.new_value(attr).and_then(|value| {
let name = attr.local_name(); str::parse_legacy_color(&value).ok()
if name.starts_with("on") { }));
},
(&atom!(background), _) => {
*self.background.borrow_mut() = mutation.new_value(attr).and_then(|value| {
let base = document_from_node(self).url();
UrlParser::new().base_url(&base).parse(&value).ok()
});
},
(name, AttributeMutation::Set(_)) if name.starts_with("on") => {
static FORWARDED_EVENTS: &'static [&'static str] = static FORWARDED_EVENTS: &'static [&'static str] =
&["onfocus", "onload", "onscroll", "onafterprint", "onbeforeprint", &["onfocus", "onload", "onscroll", "onafterprint", "onbeforeprint",
"onbeforeunload", "onhashchange", "onlanguagechange", "onmessage", "onbeforeunload", "onhashchange", "onlanguagechange", "onmessage",
"onoffline", "ononline", "onpagehide", "onpageshow", "onpopstate", "onoffline", "ononline", "onpagehide", "onpageshow", "onpopstate",
"onstorage", "onresize", "onunload", "onerror"]; "onstorage", "onresize", "onunload", "onerror"];
let window = window_from_node(self); let window = window_from_node(self);
let (cx, url, reflector) = (window.r().get_cx(), let (cx, url, reflector) = (window.get_cx(),
window.r().get_url(), window.get_url(),
window.r().reflector().get_jsobject()); window.reflector().get_jsobject());
let evtarget = let evtarget =
if FORWARDED_EVENTS.iter().any(|&event| &**name == event) { if FORWARDED_EVENTS.iter().any(|&event| &**name == event) {
EventTargetCast::from_ref(window.r()) EventTargetCast::from_ref(window.r())
@ -148,31 +156,7 @@ impl VirtualMethods for HTMLBodyElement {
evtarget.set_event_handler_uncompiled(cx, url, reflector, evtarget.set_event_handler_uncompiled(cx, url, reflector,
&name[2..], &name[2..],
(**attr.value()).to_owned()); (**attr.value()).to_owned());
} },
match attr.local_name() {
&atom!("bgcolor") => {
self.background_color.set(str::parse_legacy_color(&attr.value()).ok())
}
&atom!("background") => {
let doc = document_from_node(self);
let base = doc.r().url();
*self.background.borrow_mut() = UrlParser::new().base_url(&base).parse(&attr.value()).ok();
}
_ => {}
}
}
fn before_remove_attr(&self, attr: &Attr) {
match self.super_type() {
Some(ref s) => s.before_remove_attr(attr),
_ => {}
}
match attr.local_name() {
&atom!("bgcolor") => self.background_color.set(None),
&atom!("background") => *self.background.borrow_mut() = None,
_ => {} _ => {}
} }
} }

View file

@ -10,7 +10,7 @@ use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, HTMLBut
use dom::bindings::codegen::InheritTypes::{HTMLButtonElementDerived, HTMLFieldSetElementDerived}; use dom::bindings::codegen::InheritTypes::{HTMLButtonElementDerived, HTMLFieldSetElementDerived};
use dom::bindings::js::Root; use dom::bindings::js::Root;
use dom::document::Document; use dom::document::Document;
use dom::element::{Element, ElementTypeId}; use dom::element::{AttributeMutation, Element, ElementTypeId};
use dom::event::Event; use dom::event::Event;
use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
@ -56,7 +56,7 @@ impl HTMLButtonElement {
HTMLButtonElement { HTMLButtonElement {
htmlelement: htmlelement:
HTMLElement::new_inherited(HTMLElementTypeId::HTMLButtonElement, localName, prefix, document), HTMLElement::new_inherited(HTMLElementTypeId::HTMLButtonElement, localName, prefix, document),
//TODO: implement button_type in after_set_attr //TODO: implement button_type in attribute_mutated
button_type: Cell::new(ButtonType::ButtonSubmit) button_type: Cell::new(ButtonType::ButtonSubmit)
} }
} }
@ -135,34 +135,25 @@ impl VirtualMethods for HTMLButtonElement {
Some(htmlelement as &VirtualMethods) Some(htmlelement as &VirtualMethods)
} }
fn after_set_attr(&self, attr: &Attr) { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
if let Some(ref s) = self.super_type() { self.super_type().unwrap().attribute_mutated(attr, mutation);
s.after_set_attr(attr);
}
match attr.local_name() { match attr.local_name() {
&atom!("disabled") => { &atom!(disabled) => {
let node = NodeCast::from_ref(self); let node = NodeCast::from_ref(self);
match mutation {
AttributeMutation::Set(Some(_)) => {}
AttributeMutation::Set(None) => {
node.set_disabled_state(true); node.set_disabled_state(true);
node.set_enabled_state(false); node.set_enabled_state(false);
}, },
_ => () AttributeMutation::Removed => {
}
}
fn before_remove_attr(&self, attr: &Attr) {
if let Some(ref s) = self.super_type() {
s.before_remove_attr(attr);
}
match attr.local_name() {
&atom!("disabled") => {
let node = NodeCast::from_ref(self);
node.set_disabled_state(false); node.set_disabled_state(false);
node.set_enabled_state(true); node.set_enabled_state(true);
node.check_ancestors_disabled_state_for_form_control(); node.check_ancestors_disabled_state_for_form_control();
}
}
}, },
_ => () _ => {},
} }
} }

View file

@ -14,7 +14,7 @@ use dom::bindings::js::{JS, LayoutJS, MutNullableHeap, HeapGCValue, Root};
use dom::bindings::utils::{Reflectable}; use dom::bindings::utils::{Reflectable};
use dom::canvasrenderingcontext2d::{CanvasRenderingContext2D, LayoutCanvasRenderingContext2DHelpers}; use dom::canvasrenderingcontext2d::{CanvasRenderingContext2D, LayoutCanvasRenderingContext2DHelpers};
use dom::document::Document; use dom::document::Document;
use dom::element::ElementTypeId; use dom::element::{AttributeMutation, ElementTypeId};
use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
use dom::node::{Node, NodeTypeId, window_from_node}; use dom::node::{Node, NodeTypeId, window_from_node};
@ -256,46 +256,25 @@ impl VirtualMethods for HTMLCanvasElement {
Some(element as &VirtualMethods) Some(element as &VirtualMethods)
} }
fn before_remove_attr(&self, attr: &Attr) { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
if let Some(ref s) = self.super_type() { self.super_type().unwrap().attribute_mutated(attr, mutation);
s.before_remove_attr(attr);
}
let recreate = match attr.local_name() { let recreate = match attr.local_name() {
&atom!("width") => { &atom!(width) => {
self.width.set(DEFAULT_WIDTH); let width = mutation.new_value(attr).and_then(|value| {
parse_unsigned_integer(value.chars())
});
self.width.set(width.unwrap_or(DEFAULT_WIDTH));
true true
} },
&atom!("height") => { &atom!(height) => {
self.height.set(DEFAULT_HEIGHT); let height = mutation.new_value(attr).and_then(|value| {
parse_unsigned_integer(value.chars())
});
self.height.set(height.unwrap_or(DEFAULT_HEIGHT));
true true
} },
_ => false, _ => false,
}; };
if recreate {
self.recreate_contexts();
}
}
fn after_set_attr(&self, attr: &Attr) {
if let Some(ref s) = self.super_type() {
s.after_set_attr(attr);
}
let value = attr.value();
let recreate = match attr.local_name() {
&atom!("width") => {
self.width.set(parse_unsigned_integer(value.chars()).unwrap_or(DEFAULT_WIDTH));
true
}
&atom!("height") => {
self.height.set(parse_unsigned_integer(value.chars()).unwrap_or(DEFAULT_HEIGHT));
true
}
_ => false,
};
if recreate { if recreate {
self.recreate_contexts(); self.recreate_contexts();
} }

View file

@ -19,7 +19,7 @@ use dom::bindings::utils::Reflectable;
use dom::cssstyledeclaration::{CSSStyleDeclaration, CSSModificationAccess}; use dom::cssstyledeclaration::{CSSStyleDeclaration, CSSModificationAccess};
use dom::document::Document; use dom::document::Document;
use dom::domstringmap::DOMStringMap; use dom::domstringmap::DOMStringMap;
use dom::element::{Element, ElementTypeId}; use dom::element::{AttributeMutation, Element, ElementTypeId};
use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlinputelement::HTMLInputElement; use dom::htmlinputelement::HTMLInputElement;
use dom::htmlmediaelement::HTMLMediaElementTypeId; use dom::htmlmediaelement::HTMLMediaElementTypeId;
@ -313,26 +313,10 @@ impl VirtualMethods for HTMLElement {
Some(element as &VirtualMethods) Some(element as &VirtualMethods)
} }
fn before_remove_attr(&self, attr: &Attr) { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
if let Some(ref s) = self.super_type() { self.super_type().unwrap().attribute_mutated(attr, mutation);
s.before_remove_attr(attr); match (attr.local_name(), mutation) {
} (name, AttributeMutation::Set(_)) if name.starts_with("on") => {
}
fn after_remove_attr(&self, name: &Atom) {
if let Some(ref s) = self.super_type() {
s.after_remove_attr(name);
}
self.update_sequentially_focusable_status();
}
fn after_set_attr(&self, attr: &Attr) {
if let Some(ref s) = self.super_type() {
s.after_set_attr(attr);
}
let name = attr.local_name();
if name.starts_with("on") {
let window = window_from_node(self); let window = window_from_node(self);
let (cx, url, reflector) = (window.r().get_cx(), let (cx, url, reflector) = (window.r().get_cx(),
window.r().get_url(), window.r().get_url(),
@ -341,9 +325,11 @@ impl VirtualMethods for HTMLElement {
evtarget.set_event_handler_uncompiled(cx, url, reflector, evtarget.set_event_handler_uncompiled(cx, url, reflector,
&name[2..], &name[2..],
(**attr.value()).to_owned()); (**attr.value()).to_owned());
},
_ => {}
} }
self.update_sequentially_focusable_status();
} }
fn bind_to_tree(&self, tree_in_doc: bool) { fn bind_to_tree(&self, tree_in_doc: bool) {
if let Some(ref s) = self.super_type() { if let Some(ref s) = self.super_type() {
s.bind_to_tree(tree_in_doc); s.bind_to_tree(tree_in_doc);

View file

@ -9,7 +9,7 @@ use dom::bindings::codegen::InheritTypes::{HTMLElementCast, HTMLLegendElementDer
use dom::bindings::codegen::InheritTypes::{HTMLFieldSetElementDerived, NodeCast}; use dom::bindings::codegen::InheritTypes::{HTMLFieldSetElementDerived, NodeCast};
use dom::bindings::js::{Root, RootedReference}; use dom::bindings::js::{Root, RootedReference};
use dom::document::Document; use dom::document::Document;
use dom::element::{Element, ElementTypeId}; use dom::element::{AttributeMutation, Element, ElementTypeId};
use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlcollection::{HTMLCollection, CollectionFilter}; use dom::htmlcollection::{HTMLCollection, CollectionFilter};
use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
@ -88,83 +88,66 @@ impl VirtualMethods for HTMLFieldSetElement {
Some(htmlelement as &VirtualMethods) Some(htmlelement as &VirtualMethods)
} }
fn after_set_attr(&self, attr: &Attr) { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
if let Some(ref s) = self.super_type() { self.super_type().unwrap().attribute_mutated(attr, mutation);
s.after_set_attr(attr);
}
match attr.local_name() { match attr.local_name() {
&atom!("disabled") => { &atom!(disabled) => {
let disabled_state = match mutation {
AttributeMutation::Set(None) => true,
AttributeMutation::Set(Some(_)) => {
// Fieldset was already disabled before.
return;
},
AttributeMutation::Removed => false,
};
let node = NodeCast::from_ref(self); let node = NodeCast::from_ref(self);
node.set_disabled_state(true); node.set_disabled_state(disabled_state);
node.set_enabled_state(false); node.set_enabled_state(!disabled_state);
let maybe_legend = node.children() let mut found_legend = false;
.find(|node| node.r().is_htmllegendelement()); let children = node.children().filter(|node| {
if found_legend {
for child in node.children() { true
if Some(child.r()) == maybe_legend.r() { } else if node.is_htmllegendelement() {
continue; found_legend = true;
false
} else {
true
} }
});
for descendant in child.r().traverse_preorder() { let fields = children.flat_map(|child| {
child.traverse_preorder().filter(|descendant| {
match descendant.r().type_id() { match descendant.r().type_id() {
NodeTypeId::Element( NodeTypeId::Element(
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLButtonElement)) | ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLButtonElement)) |
NodeTypeId::Element( NodeTypeId::Element(
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) | ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLInputElement)) |
NodeTypeId::Element( NodeTypeId::Element(
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) | ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLSelectElement)) |
NodeTypeId::Element( NodeTypeId::Element(
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) => { ElementTypeId::HTMLElement(
descendant.r().set_disabled_state(true); HTMLElementTypeId::HTMLTextAreaElement)) => {
descendant.r().set_enabled_state(false); true
}, },
_ => () _ => false,
} }
})
});
if disabled_state {
for field in fields {
field.set_disabled_state(true);
field.set_enabled_state(false);
}
} else {
for field in fields {
field.check_disabled_attribute();
field.check_ancestors_disabled_state_for_form_control();
} }
} }
}, },
_ => () _ => {},
}
}
fn before_remove_attr(&self, attr: &Attr) {
if let Some(ref s) = self.super_type() {
s.before_remove_attr(attr);
}
match attr.local_name() {
&atom!("disabled") => {
let node = NodeCast::from_ref(self);
node.set_disabled_state(false);
node.set_enabled_state(true);
let maybe_legend = node.children()
.find(|node| node.r().is_htmllegendelement());
for child in node.children() {
if Some(child.r()) == maybe_legend.r() {
continue;
}
for descendant in child.r().traverse_preorder() {
match descendant.r().type_id() {
NodeTypeId::Element(
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLButtonElement)) |
NodeTypeId::Element(
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) |
NodeTypeId::Element(
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) |
NodeTypeId::Element(
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) => {
descendant.r().check_disabled_attribute();
descendant.r().check_ancestors_disabled_state_for_form_control();
},
_ => ()
}
}
}
},
_ => ()
} }
} }
} }

View file

@ -8,7 +8,7 @@ use dom::bindings::codegen::Bindings::HTMLFontElementBinding::HTMLFontElementMet
use dom::bindings::codegen::InheritTypes::{HTMLElementCast, HTMLFontElementDerived}; use dom::bindings::codegen::InheritTypes::{HTMLElementCast, HTMLFontElementDerived};
use dom::bindings::js::Root; use dom::bindings::js::Root;
use dom::document::Document; use dom::document::Document;
use dom::element::ElementTypeId; use dom::element::{AttributeMutation, ElementTypeId};
use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
use dom::node::{Node, NodeTypeId}; use dom::node::{Node, NodeTypeId};
@ -60,27 +60,15 @@ impl VirtualMethods for HTMLFontElement {
Some(htmlelement as &VirtualMethods) Some(htmlelement as &VirtualMethods)
} }
fn after_set_attr(&self, attr: &Attr) { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
if let Some(ref s) = self.super_type() { self.super_type().unwrap().attribute_mutated(attr, mutation);
s.after_set_attr(attr);
}
match attr.local_name() { match attr.local_name() {
&atom!("color") => { &atom!(color) => {
self.color.set(str::parse_legacy_color(&attr.value()).ok()) self.color.set(mutation.new_value(attr).and_then(|value| {
} str::parse_legacy_color(&value).ok()
_ => {} }));
} },
} _ => {},
fn before_remove_attr(&self, attr: &Attr) {
if let Some(ref s) = self.super_type() {
s.before_remove_attr(attr);
}
match attr.local_name() {
&atom!("color") => self.color.set(None),
_ => ()
} }
} }
} }

View file

@ -17,7 +17,7 @@ use dom::bindings::js::{Root};
use dom::bindings::utils::Reflectable; use dom::bindings::utils::Reflectable;
use dom::customevent::CustomEvent; use dom::customevent::CustomEvent;
use dom::document::Document; use dom::document::Document;
use dom::element::{ElementTypeId, self}; use dom::element::{AttributeMutation, ElementTypeId, self};
use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
use dom::node::{Node, NodeTypeId, window_from_node}; use dom::node::{Node, NodeTypeId, window_from_node};
@ -354,16 +354,13 @@ impl VirtualMethods for HTMLIFrameElement {
Some(htmlelement as &VirtualMethods) Some(htmlelement as &VirtualMethods)
} }
fn after_set_attr(&self, attr: &Attr) { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
if let Some(ref s) = self.super_type() { self.super_type().unwrap().attribute_mutated(attr, mutation);
s.after_set_attr(attr);
}
match attr.local_name() { match attr.local_name() {
&atom!("sandbox") => { &atom!(sandbox) => {
self.sandbox.set(mutation.new_value(attr).map(|value| {
let mut modes = SandboxAllowance::AllowNothing as u8; let mut modes = SandboxAllowance::AllowNothing as u8;
if let Some(ref tokens) = attr.value().tokens() { for token in value.tokens().unwrap() {
for token in *tokens {
modes |= match &*token.to_ascii_lowercase() { modes |= match &*token.to_ascii_lowercase() {
"allow-same-origin" => SandboxAllowance::AllowSameOrigin, "allow-same-origin" => SandboxAllowance::AllowSameOrigin,
"allow-forms" => SandboxAllowance::AllowForms, "allow-forms" => SandboxAllowance::AllowForms,
@ -374,16 +371,17 @@ impl VirtualMethods for HTMLIFrameElement {
_ => SandboxAllowance::AllowNothing _ => SandboxAllowance::AllowNothing
} as u8; } as u8;
} }
modes
}));
},
&atom!(src) => {
if let AttributeMutation::Set(_) = mutation {
if NodeCast::from_ref(self).is_in_doc() {
self.process_the_iframe_attributes();
} }
self.sandbox.set(Some(modes));
}
&atom!("src") => {
let node = NodeCast::from_ref(self);
if node.is_in_doc() {
self.process_the_iframe_attributes()
} }
}, },
_ => () _ => {},
} }
} }
@ -394,17 +392,6 @@ impl VirtualMethods for HTMLIFrameElement {
} }
} }
fn before_remove_attr(&self, attr: &Attr) {
if let Some(ref s) = self.super_type() {
s.before_remove_attr(attr);
}
match attr.local_name() {
&atom!("sandbox") => self.sandbox.set(None),
_ => ()
}
}
fn bind_to_tree(&self, tree_in_doc: bool) { fn bind_to_tree(&self, tree_in_doc: bool) {
if let Some(ref s) = self.super_type() { if let Some(ref s) = self.super_type() {
s.bind_to_tree(tree_in_doc); s.bind_to_tree(tree_in_doc);

View file

@ -15,7 +15,7 @@ use dom::bindings::global::GlobalRef;
use dom::bindings::js::{LayoutJS, Root}; use dom::bindings::js::{LayoutJS, Root};
use dom::bindings::refcounted::Trusted; use dom::bindings::refcounted::Trusted;
use dom::document::Document; use dom::document::Document;
use dom::element::ElementTypeId; use dom::element::{AttributeMutation, ElementTypeId};
use dom::event::{Event, EventBubbles, EventCancelable}; use dom::event::{Event, EventBubbles, EventCancelable};
use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
@ -108,7 +108,7 @@ impl Runnable for ImageResponseHandlerRunnable {
impl HTMLImageElement { impl HTMLImageElement {
/// Makes the local `image` member match the status of the `src` attribute and starts /// Makes the local `image` member match the status of the `src` attribute and starts
/// prefetching the image. This method must be called after `src` is changed. /// prefetching the image. This method must be called after `src` is changed.
fn update_image(&self, value: Option<(DOMString, &Url)>) { fn update_image(&self, value: Option<(DOMString, Url)>) {
let node = NodeCast::from_ref(self); let node = NodeCast::from_ref(self);
let document = node.owner_doc(); let document = node.owner_doc();
let window = document.r().window(); let window = document.r().window();
@ -120,7 +120,7 @@ impl HTMLImageElement {
*self.image.borrow_mut() = None; *self.image.borrow_mut() = None;
} }
Some((src, base_url)) => { Some((src, base_url)) => {
let img_url = UrlParser::new().base_url(base_url).parse(&src); let img_url = UrlParser::new().base_url(&base_url).parse(&src);
// FIXME: handle URL parse errors more gracefully. // FIXME: handle URL parse errors more gracefully.
let img_url = img_url.unwrap(); let img_url = img_url.unwrap();
*self.url.borrow_mut() = Some(img_url.clone()); *self.url.borrow_mut() = Some(img_url.clone());
@ -300,29 +300,15 @@ impl VirtualMethods for HTMLImageElement {
Some(htmlelement as &VirtualMethods) Some(htmlelement as &VirtualMethods)
} }
fn after_set_attr(&self, attr: &Attr) { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
if let Some(ref s) = self.super_type() { self.super_type().unwrap().attribute_mutated(attr, mutation);
s.after_set_attr(attr);
}
match attr.local_name() { match attr.local_name() {
&atom!("src") => { &atom!(src) => {
let window = window_from_node(self); self.update_image(mutation.new_value(attr).map(|value| {
let url = window.r().get_url(); ((**value).to_owned(), window_from_node(self).get_url())
self.update_image(Some(((**attr.value()).to_owned(), &url))); }));
}, },
_ => () _ => {},
}
}
fn before_remove_attr(&self, attr: &Attr) {
if let Some(ref s) = self.super_type() {
s.before_remove_attr(attr);
}
match attr.local_name() {
&atom!("src") => self.update_image(None),
_ => ()
} }
} }

View file

@ -16,7 +16,7 @@ use dom::bindings::codegen::InheritTypes::{HTMLInputElementDerived, HTMLFieldSet
use dom::bindings::global::GlobalRef; use dom::bindings::global::GlobalRef;
use dom::bindings::js::{JS, LayoutJS, Root, RootedReference}; use dom::bindings::js::{JS, LayoutJS, Root, RootedReference};
use dom::document::Document; use dom::document::Document;
use dom::element::{Element, ElementTypeId, RawLayoutElementHelpers}; use dom::element::{AttributeMutation, Element, ElementTypeId, RawLayoutElementHelpers};
use dom::event::{Event, EventBubbles, EventCancelable}; use dom::event::{Event, EventBubbles, EventCancelable};
use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
@ -447,32 +447,44 @@ impl VirtualMethods for HTMLInputElement {
Some(htmlelement as &VirtualMethods) Some(htmlelement as &VirtualMethods)
} }
fn after_set_attr(&self, attr: &Attr) { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
if let Some(ref s) = self.super_type() { self.super_type().unwrap().attribute_mutated(attr, mutation);
s.after_set_attr(attr);
}
match attr.local_name() { match attr.local_name() {
&atom!("disabled") => { &atom!(disabled) => {
let disabled_state = match mutation {
AttributeMutation::Set(None) => true,
AttributeMutation::Set(Some(_)) => {
// Input was already disabled before.
return;
},
AttributeMutation::Removed => false,
};
let node = NodeCast::from_ref(self); let node = NodeCast::from_ref(self);
node.set_disabled_state(true); node.set_disabled_state(disabled_state);
node.set_enabled_state(false); node.set_enabled_state(!disabled_state);
node.check_ancestors_disabled_state_for_form_control();
},
&atom!(checked) if !self.checked_changed.get() => {
let checked_state = match mutation {
AttributeMutation::Set(None) => true,
AttributeMutation::Set(Some(_)) => {
// Input was already checked before.
return;
},
AttributeMutation::Removed => false,
};
self.update_checked_state(checked_state, false);
},
&atom!(size) => {
let size = mutation.new_value(attr).map(|value| {
value.uint().expect("Expected an AttrValue::UInt")
});
self.size.set(size.unwrap_or(DEFAULT_INPUT_SIZE));
} }
&atom!("checked") => { &atom!(type) => {
// https://html.spec.whatwg.org/multipage/#the-input-element:concept-input-checked-dirty match mutation {
if !self.checked_changed.get() { AttributeMutation::Set(_) => {
self.update_checked_state(true, false); let value = match &**attr.value() {
}
}
&atom!("size") => {
match *attr.value() {
AttrValue::UInt(_, value) => self.size.set(value),
_ => panic!("Expected an AttrValue::UInt"),
}
}
&atom!("type") => {
let value = attr.value();
self.input_type.set(match &**value {
"button" => InputType::InputButton, "button" => InputType::InputButton,
"submit" => InputType::InputSubmit, "submit" => InputType::InputSubmit,
"reset" => InputType::InputReset, "reset" => InputType::InputReset,
@ -481,79 +493,41 @@ impl VirtualMethods for HTMLInputElement {
"checkbox" => InputType::InputCheckbox, "checkbox" => InputType::InputCheckbox,
"password" => InputType::InputPassword, "password" => InputType::InputPassword,
_ => InputType::InputText, _ => InputType::InputText,
}); };
self.input_type.set(value);
if value == InputType::InputRadio {
self.radio_group_updated(
self.get_radio_group_name().as_ref().map(|group| &**group));
}
},
AttributeMutation::Removed => {
if self.input_type.get() == InputType::InputRadio { if self.input_type.get() == InputType::InputRadio {
self.radio_group_updated(self.get_radio_group_name() broadcast_radio_checked(
.as_ref() self,
.map(|group| &**group)); self.get_radio_group_name().as_ref().map(|group| &**group));
}
}
&atom!("value") => {
if !self.value_changed.get() {
self.textinput.borrow_mut().set_content((**attr.value()).to_owned());
}
}
&atom!("name") => {
if self.input_type.get() == InputType::InputRadio {
let value = attr.value();
self.radio_group_updated(Some(&value));
}
}
_ if attr.local_name() == &Atom::from_slice("placeholder") => {
let value = attr.value();
let stripped = value.chars()
.filter(|&c| c != '\n' && c != '\r')
.collect::<String>();
*self.placeholder.borrow_mut() = stripped;
}
_ => ()
}
}
fn before_remove_attr(&self, attr: &Attr) {
if let Some(ref s) = self.super_type() {
s.before_remove_attr(attr);
}
match attr.local_name() {
&atom!("disabled") => {
let node = NodeCast::from_ref(self);
node.set_disabled_state(false);
node.set_enabled_state(true);
node.check_ancestors_disabled_state_for_form_control();
}
&atom!("checked") => {
// https://html.spec.whatwg.org/multipage/#the-input-element:concept-input-checked-dirty
if !self.checked_changed.get() {
self.update_checked_state(false, false);
}
}
&atom!("size") => {
self.size.set(DEFAULT_INPUT_SIZE);
}
&atom!("type") => {
if self.input_type.get() == InputType::InputRadio {
broadcast_radio_checked(self,
self.get_radio_group_name()
.as_ref()
.map(|group| &**group));
} }
self.input_type.set(InputType::InputText); self.input_type.set(InputType::InputText);
} }
&atom!("value") => {
if !self.value_changed.get() {
self.textinput.borrow_mut().set_content("".to_owned());
} }
},
&atom!(value) if !self.value_changed.get() => {
let value = mutation.new_value(attr).map(|value| (**value).to_owned());
self.textinput.borrow_mut().set_content(
value.unwrap_or_else(|| "".to_owned()));
},
&atom!(name) if self.input_type.get() == InputType::InputRadio => {
self.radio_group_updated(
mutation.new_value(attr).as_ref().map(|value| &***value));
},
name if name == &Atom::from_slice("placeholder") => {
let mut placeholder = self.placeholder.borrow_mut();
placeholder.clear();
if let AttributeMutation::Set(_) = mutation {
placeholder.extend(
attr.value().chars().filter(|&c| c != '\n' && c != '\r'));
} }
&atom!("name") => { },
if self.input_type.get() == InputType::InputRadio { _ => {},
self.radio_group_updated(None);
}
}
_ if attr.local_name() == &Atom::from_slice("placeholder") => {
self.placeholder.borrow_mut().clear();
}
_ => ()
} }
} }

View file

@ -16,7 +16,7 @@ use dom::bindings::js::{RootedReference};
use dom::bindings::refcounted::Trusted; use dom::bindings::refcounted::Trusted;
use dom::document::Document; use dom::document::Document;
use dom::domtokenlist::DOMTokenList; use dom::domtokenlist::DOMTokenList;
use dom::element::{Element, ElementTypeId}; use dom::element::{AttributeMutation, Element, ElementTypeId};
use dom::event::{EventBubbles, EventCancelable, Event}; use dom::event::{EventBubbles, EventCancelable, Event};
use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
@ -103,33 +103,26 @@ impl VirtualMethods for HTMLLinkElement {
Some(htmlelement as &VirtualMethods) Some(htmlelement as &VirtualMethods)
} }
fn after_set_attr(&self, attr: &Attr) { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
if let Some(ref s) = self.super_type() { self.super_type().unwrap().attribute_mutated(attr, mutation);
s.after_set_attr(attr); if !NodeCast::from_ref(self).is_in_doc() || mutation == AttributeMutation::Removed {
}
let node = NodeCast::from_ref(self);
if !node.is_in_doc() {
return; return;
} }
let rel = get_attr(ElementCast::from_ref(self), &atom!(rel));
let element = ElementCast::from_ref(self); match attr.local_name() {
let rel = get_attr(element, &atom!("rel")); &atom!(href) => {
if is_stylesheet(&rel) {
match (rel, attr.local_name()) {
(ref rel, &atom!("href")) => {
if is_stylesheet(rel) {
self.handle_stylesheet_url(&attr.value()); self.handle_stylesheet_url(&attr.value());
} else if is_favicon(rel) { } else if is_favicon(&rel) {
self.handle_favicon_url(&attr.value()); self.handle_favicon_url(&attr.value());
} }
} },
(ref rel, &atom!("media")) => { &atom!(media) => {
if is_stylesheet(rel) { if is_stylesheet(&rel) {
self.handle_stylesheet_url(&attr.value()); self.handle_stylesheet_url(&attr.value());
} }
} },
(_, _) => () _ => {},
} }
} }

View file

@ -10,7 +10,7 @@ use dom::bindings::codegen::InheritTypes::HTMLObjectElementDerived;
use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast}; use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast};
use dom::bindings::js::Root; use dom::bindings::js::Root;
use dom::document::Document; use dom::document::Document;
use dom::element::ElementTypeId; use dom::element::{AttributeMutation, ElementTypeId};
use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
use dom::node::{Node, NodeTypeId, window_from_node}; use dom::node::{Node, NodeTypeId, window_from_node};
@ -101,16 +101,15 @@ impl VirtualMethods for HTMLObjectElement {
Some(htmlelement as &VirtualMethods) Some(htmlelement as &VirtualMethods)
} }
fn after_set_attr(&self, attr: &Attr) { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
if let Some(ref s) = self.super_type() { self.super_type().unwrap().attribute_mutated(attr, mutation);
s.after_set_attr(attr);
}
match attr.local_name() { match attr.local_name() {
&atom!("data") => { &atom!(data) => {
if let AttributeMutation::Set(_) = mutation {
self.process_data_url(); self.process_data_url();
}
}, },
_ => () _ => {},
} }
} }
} }

View file

@ -9,7 +9,7 @@ use dom::bindings::codegen::InheritTypes::{HTMLElementCast, NodeCast};
use dom::bindings::codegen::InheritTypes::{HTMLOptGroupElementDerived, HTMLOptionElementDerived}; use dom::bindings::codegen::InheritTypes::{HTMLOptGroupElementDerived, HTMLOptionElementDerived};
use dom::bindings::js::Root; use dom::bindings::js::Root;
use dom::document::Document; use dom::document::Document;
use dom::element::ElementTypeId; use dom::element::{AttributeMutation, ElementTypeId};
use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
use dom::node::{Node, NodeTypeId}; use dom::node::{Node, NodeTypeId};
@ -63,44 +63,36 @@ impl VirtualMethods for HTMLOptGroupElement {
Some(htmlelement as &VirtualMethods) Some(htmlelement as &VirtualMethods)
} }
fn after_set_attr(&self, attr: &Attr) { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
if let Some(ref s) = self.super_type() { self.super_type().unwrap().attribute_mutated(attr, mutation);
s.after_set_attr(attr);
}
match attr.local_name() { match attr.local_name() {
&atom!("disabled") => { &atom!(disabled) => {
let disabled_state = match mutation {
AttributeMutation::Set(None) => true,
AttributeMutation::Set(Some(_)) => {
// Option group was already disabled.
return;
},
AttributeMutation::Removed => false,
};
let node = NodeCast::from_ref(self); let node = NodeCast::from_ref(self);
node.set_disabled_state(true); node.set_disabled_state(disabled_state);
node.set_enabled_state(false); node.set_enabled_state(!disabled_state);
for child in node.children() { let options = node.children().filter(|child| {
if child.r().is_htmloptionelement() { child.is_htmloptionelement()
child.r().set_disabled_state(true); });
child.r().set_enabled_state(false); if disabled_state {
for option in options {
option.set_disabled_state(true);
option.set_enabled_state(false);
}
} else {
for option in options {
option.check_disabled_attribute();
} }
} }
}, },
_ => (), _ => {},
}
}
fn before_remove_attr(&self, attr: &Attr) {
if let Some(ref s) = self.super_type() {
s.before_remove_attr(attr);
}
match attr.local_name() {
&atom!("disabled") => {
let node = NodeCast::from_ref(self);
node.set_disabled_state(false);
node.set_enabled_state(true);
for child in node.children() {
if child.r().is_htmloptionelement() {
child.r().check_disabled_attribute();
}
}
},
_ => ()
} }
} }
} }

View file

@ -12,7 +12,7 @@ use dom::bindings::codegen::InheritTypes::{HTMLOptionElementDerived};
use dom::bindings::codegen::InheritTypes::{HTMLScriptElementDerived}; use dom::bindings::codegen::InheritTypes::{HTMLScriptElementDerived};
use dom::bindings::js::Root; use dom::bindings::js::Root;
use dom::document::Document; use dom::document::Document;
use dom::element::ElementTypeId; use dom::element::{AttributeMutation, ElementTypeId};
use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
use dom::node::{Node, NodeTypeId}; use dom::node::{Node, NodeTypeId};
@ -131,34 +131,24 @@ impl VirtualMethods for HTMLOptionElement {
Some(htmlelement as &VirtualMethods) Some(htmlelement as &VirtualMethods)
} }
fn after_set_attr(&self, attr: &Attr) { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
if let Some(ref s) = self.super_type() { self.super_type().unwrap().attribute_mutated(attr, mutation);
s.after_set_attr(attr);
}
match attr.local_name() { match attr.local_name() {
&atom!("disabled") => { &atom!(disabled) => {
let node = NodeCast::from_ref(self); let node = NodeCast::from_ref(self);
match mutation {
AttributeMutation::Set(_) => {
node.set_disabled_state(true); node.set_disabled_state(true);
node.set_enabled_state(false); node.set_enabled_state(false);
}, },
_ => () AttributeMutation::Removed => {
}
}
fn before_remove_attr(&self, attr: &Attr) {
if let Some(ref s) = self.super_type() {
s.before_remove_attr(attr);
}
match attr.local_name() {
&atom!("disabled") => {
let node = NodeCast::from_ref(self);
node.set_disabled_state(false); node.set_disabled_state(false);
node.set_enabled_state(true); node.set_enabled_state(true);
node.check_parent_disabled_state_for_option(); node.check_parent_disabled_state_for_option();
}
}
}, },
_ => () _ => {},
} }
} }

View file

@ -21,7 +21,7 @@ use dom::bindings::js::{JS, Root};
use dom::bindings::refcounted::Trusted; use dom::bindings::refcounted::Trusted;
use dom::bindings::trace::JSTraceable; use dom::bindings::trace::JSTraceable;
use dom::document::Document; use dom::document::Document;
use dom::element::{ElementCreator, ElementTypeId}; use dom::element::{AttributeMutation, ElementCreator, ElementTypeId};
use dom::event::{Event, EventBubbles, EventCancelable}; use dom::event::{Event, EventBubbles, EventCancelable};
use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
@ -532,15 +532,19 @@ impl VirtualMethods for HTMLScriptElement {
Some(htmlelement as &VirtualMethods) Some(htmlelement as &VirtualMethods)
} }
fn after_set_attr(&self, attr: &Attr) { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
if let Some(ref s) = self.super_type() { self.super_type().unwrap().attribute_mutated(attr, mutation);
s.after_set_attr(attr); match attr.local_name() {
} &atom!("src") => {
let node = NodeCast::from_ref(self); if let AttributeMutation::Set(_) = mutation {
if attr.local_name() == &atom!("src") && !self.parser_inserted.get() && node.is_in_doc() { if !self.parser_inserted.get() && NodeCast::from_ref(self).is_in_doc() {
self.prepare(); self.prepare();
} }
} }
},
_ => {},
}
}
fn children_changed(&self, mutation: &ChildrenMutation) { fn children_changed(&self, mutation: &ChildrenMutation) {
if let Some(ref s) = self.super_type() { if let Some(ref s) = self.super_type() {

View file

@ -11,7 +11,7 @@ use dom::bindings::codegen::UnionTypes::HTMLElementOrLong;
use dom::bindings::codegen::UnionTypes::HTMLOptionElementOrHTMLOptGroupElement; use dom::bindings::codegen::UnionTypes::HTMLOptionElementOrHTMLOptGroupElement;
use dom::bindings::js::Root; use dom::bindings::js::Root;
use dom::document::Document; use dom::document::Document;
use dom::element::ElementTypeId; use dom::element::{AttributeMutation, ElementTypeId};
use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
use dom::node::{Node, NodeTypeId, window_from_node}; use dom::node::{Node, NodeTypeId, window_from_node};
@ -109,34 +109,21 @@ impl VirtualMethods for HTMLSelectElement {
Some(htmlelement as &VirtualMethods) Some(htmlelement as &VirtualMethods)
} }
fn after_set_attr(&self, attr: &Attr) { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
if let Some(ref s) = self.super_type() { self.super_type().unwrap().attribute_mutated(attr, mutation);
s.after_set_attr(attr); if attr.local_name() == &atom!(disabled) {
}
match attr.local_name() {
&atom!("disabled") => {
let node = NodeCast::from_ref(self); let node = NodeCast::from_ref(self);
match mutation {
AttributeMutation::Set(_) => {
node.set_disabled_state(true); node.set_disabled_state(true);
node.set_enabled_state(false); node.set_enabled_state(false);
}, },
_ => () AttributeMutation::Removed => {
}
}
fn before_remove_attr(&self, attr: &Attr) {
if let Some(ref s) = self.super_type() {
s.before_remove_attr(attr);
}
match attr.local_name() {
&atom!("disabled") => {
let node = NodeCast::from_ref(self);
node.set_disabled_state(false); node.set_disabled_state(false);
node.set_enabled_state(true); node.set_enabled_state(true);
node.check_ancestors_disabled_state_for_form_control(); node.check_ancestors_disabled_state_for_form_control();
}, }
_ => () }
} }
} }

View file

@ -6,7 +6,7 @@ use dom::attr::{Attr, AttrValue};
use dom::bindings::codegen::Bindings::HTMLTableCellElementBinding::HTMLTableCellElementMethods; use dom::bindings::codegen::Bindings::HTMLTableCellElementBinding::HTMLTableCellElementMethods;
use dom::bindings::codegen::InheritTypes::{HTMLElementCast, HTMLTableCellElementDerived}; use dom::bindings::codegen::InheritTypes::{HTMLElementCast, HTMLTableCellElementDerived};
use dom::document::Document; use dom::document::Document;
use dom::element::ElementTypeId; use dom::element::{AttributeMutation, ElementTypeId};
use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
use dom::node::NodeTypeId; use dom::node::NodeTypeId;
@ -101,38 +101,26 @@ impl VirtualMethods for HTMLTableCellElement {
Some(htmlelement as &VirtualMethods) Some(htmlelement as &VirtualMethods)
} }
fn after_set_attr(&self, attr: &Attr) { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
if let Some(ref s) = self.super_type() { self.super_type().unwrap().attribute_mutated(attr, mutation);
s.after_set_attr(attr);
}
match attr.local_name() { match attr.local_name() {
&atom!("bgcolor") => { &atom!(bgcolor) => {
self.background_color.set(str::parse_legacy_color(&attr.value()).ok()) self.background_color.set(mutation.new_value(attr).and_then(|value| {
} str::parse_legacy_color(&value).ok()
&atom!("colspan") => { }));
match *attr.value() {
AttrValue::UInt(_, colspan) => {
self.colspan.set(Some(max(DEFAULT_COLSPAN, colspan)))
}, },
_ => unreachable!(), &atom!(colspan) => {
} self.colspan.set(mutation.new_value(attr).map(|value| {
max(DEFAULT_COLSPAN, value.uint().unwrap())
}));
}, },
&atom!("width") => self.width.set(str::parse_length(&attr.value())), &atom!(width) => {
_ => () let width = mutation.new_value(attr).map(|value| {
} str::parse_length(&value)
} });
self.width.set(width.unwrap_or(LengthOrPercentageOrAuto::Auto));
fn before_remove_attr(&self, attr: &Attr) { },
if let Some(ref s) = self.super_type() { _ => {},
s.before_remove_attr(attr);
}
match attr.local_name() {
&atom!("bgcolor") => self.background_color.set(None),
&atom!("colspan") => self.colspan.set(None),
&atom!("width") => self.width.set(LengthOrPercentageOrAuto::Auto),
_ => ()
} }
} }

View file

@ -11,7 +11,7 @@ use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, HTMLTab
use dom::bindings::codegen::InheritTypes::{HTMLTableElementDerived, NodeCast}; use dom::bindings::codegen::InheritTypes::{HTMLTableElementDerived, NodeCast};
use dom::bindings::js::{Root, RootedReference}; use dom::bindings::js::{Root, RootedReference};
use dom::document::Document; use dom::document::Document;
use dom::element::ElementTypeId; use dom::element::{AttributeMutation, ElementTypeId};
use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
use dom::htmltablecaptionelement::HTMLTableCaptionElement; use dom::htmltablecaptionelement::HTMLTableCaptionElement;
@ -157,39 +157,32 @@ impl VirtualMethods for HTMLTableElement {
Some(htmlelement as &VirtualMethods) Some(htmlelement as &VirtualMethods)
} }
fn after_set_attr(&self, attr: &Attr) { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
if let Some(ref s) = self.super_type() { self.super_type().unwrap().attribute_mutated(attr, mutation);
s.after_set_attr(attr);
}
match attr.local_name() { match attr.local_name() {
&atom!("bgcolor") => { &atom!(bgcolor) => {
self.background_color.set(str::parse_legacy_color(&attr.value()).ok()) self.background_color.set(mutation.new_value(attr).and_then(|value| {
} str::parse_legacy_color(&value).ok()
&atom!("border") => { }));
},
&atom!(border) => {
// According to HTML5 § 14.3.9, invalid values map to 1px. // According to HTML5 § 14.3.9, invalid values map to 1px.
self.border.set(Some(str::parse_unsigned_integer(attr.value() self.border.set(mutation.new_value(attr).map(|value| {
.chars()).unwrap_or(1))) str::parse_unsigned_integer(value.chars()).unwrap_or(1)
}));
} }
&atom!("cellspacing") => { &atom!(cellspacing) => {
self.cellspacing.set(str::parse_unsigned_integer(attr.value().chars())) self.cellspacing.set(mutation.new_value(attr).and_then(|value| {
} str::parse_unsigned_integer(value.chars())
&atom!("width") => self.width.set(str::parse_length(&attr.value())), }));
_ => () },
} &atom!(width) => {
} let width = mutation.new_value(attr).map(|value| {
str::parse_length(&value)
fn before_remove_attr(&self, attr: &Attr) { });
if let Some(ref s) = self.super_type() { self.width.set(width.unwrap_or(LengthOrPercentageOrAuto::Auto));
s.before_remove_attr(attr); },
} _ => {},
match attr.local_name() {
&atom!("bgcolor") => self.background_color.set(None),
&atom!("border") => self.border.set(None),
&atom!("cellspacing") => self.cellspacing.set(None),
&atom!("width") => self.width.set(LengthOrPercentageOrAuto::Auto),
_ => ()
} }
} }

View file

@ -7,7 +7,7 @@ use dom::bindings::codegen::Bindings::HTMLTableRowElementBinding;
use dom::bindings::codegen::InheritTypes::{HTMLElementCast, HTMLTableRowElementDerived}; use dom::bindings::codegen::InheritTypes::{HTMLElementCast, HTMLTableRowElementDerived};
use dom::bindings::js::Root; use dom::bindings::js::Root;
use dom::document::Document; use dom::document::Document;
use dom::element::ElementTypeId; use dom::element::{AttributeMutation, ElementTypeId};
use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
use dom::node::{Node, NodeTypeId}; use dom::node::{Node, NodeTypeId};
@ -62,29 +62,15 @@ impl VirtualMethods for HTMLTableRowElement {
Some(htmlelement as &VirtualMethods) Some(htmlelement as &VirtualMethods)
} }
fn after_set_attr(&self, attr: &Attr) { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
if let Some(ref s) = self.super_type() { self.super_type().unwrap().attribute_mutated(attr, mutation);
s.after_set_attr(attr);
}
match attr.local_name() { match attr.local_name() {
&atom!("bgcolor") => { &atom!(bgcolor) => {
self.background_color.set(str::parse_legacy_color(&attr.value()).ok()); self.background_color.set(mutation.new_value(attr).and_then(|value| {
str::parse_legacy_color(&value).ok()
}));
}, },
_ => () _ => {},
}
}
fn before_remove_attr(&self, attr: &Attr) {
if let Some(ref s) = self.super_type() {
s.before_remove_attr(attr);
}
match attr.local_name() {
&atom!("bgcolor") => {
self.background_color.set(None);
},
_ => ()
} }
} }
} }

View file

@ -7,7 +7,7 @@ use dom::bindings::codegen::Bindings::HTMLTableSectionElementBinding;
use dom::bindings::codegen::InheritTypes::{HTMLElementCast, HTMLTableSectionElementDerived}; use dom::bindings::codegen::InheritTypes::{HTMLElementCast, HTMLTableSectionElementDerived};
use dom::bindings::js::Root; use dom::bindings::js::Root;
use dom::document::Document; use dom::document::Document;
use dom::element::ElementTypeId; use dom::element::{AttributeMutation, ElementTypeId};
use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
use dom::node::{Node, NodeTypeId}; use dom::node::{Node, NodeTypeId};
@ -61,29 +61,15 @@ impl VirtualMethods for HTMLTableSectionElement {
Some(htmlelement as &VirtualMethods) Some(htmlelement as &VirtualMethods)
} }
fn after_set_attr(&self, attr: &Attr) { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
if let Some(ref s) = self.super_type() { self.super_type().unwrap().attribute_mutated(attr, mutation);
s.after_set_attr(attr);
}
match attr.local_name() { match attr.local_name() {
&atom!("bgcolor") => { &atom!(bgcolor) => {
self.background_color.set(str::parse_legacy_color(&attr.value()).ok()); self.background_color.set(mutation.new_value(attr).and_then(|value| {
str::parse_legacy_color(&value).ok()
}));
}, },
_ => () _ => {},
}
}
fn before_remove_attr(&self, attr: &Attr) {
if let Some(ref s) = self.super_type() {
s.before_remove_attr(attr);
}
match attr.local_name() {
&atom!("bgcolor") => {
self.background_color.set(None);
},
_ => ()
} }
} }
} }

View file

@ -15,7 +15,7 @@ use dom::bindings::global::GlobalRef;
use dom::bindings::js::{LayoutJS, Root}; use dom::bindings::js::{LayoutJS, Root};
use dom::bindings::refcounted::Trusted; use dom::bindings::refcounted::Trusted;
use dom::document::Document; use dom::document::Document;
use dom::element::{Element, ElementTypeId}; use dom::element::{AttributeMutation, Element, ElementTypeId};
use dom::event::{Event, EventBubbles, EventCancelable}; use dom::event::{Event, EventBubbles, EventCancelable};
use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
@ -242,52 +242,36 @@ impl VirtualMethods for HTMLTextAreaElement {
Some(htmlelement as &VirtualMethods) Some(htmlelement as &VirtualMethods)
} }
fn after_set_attr(&self, attr: &Attr) { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
if let Some(ref s) = self.super_type() { self.super_type().unwrap().attribute_mutated(attr, mutation);
s.after_set_attr(attr);
}
match attr.local_name() { match attr.local_name() {
&atom!("disabled") => { &atom!(disabled) => {
let node = NodeCast::from_ref(self); let node = NodeCast::from_ref(self);
match mutation {
AttributeMutation::Set(_) => {
node.set_disabled_state(true); node.set_disabled_state(true);
node.set_enabled_state(false); node.set_enabled_state(false);
}, },
&atom!("cols") => { AttributeMutation::Removed => {
match *attr.value() {
AttrValue::UInt(_, value) => self.cols.set(value),
_ => panic!("Expected an AttrValue::UInt"),
}
},
&atom!("rows") => {
match *attr.value() {
AttrValue::UInt(_, value) => self.rows.set(value),
_ => panic!("Expected an AttrValue::UInt"),
}
},
_ => ()
}
}
fn before_remove_attr(&self, attr: &Attr) {
if let Some(ref s) = self.super_type() {
s.before_remove_attr(attr);
}
match attr.local_name() {
&atom!("disabled") => {
let node = NodeCast::from_ref(self);
node.set_disabled_state(false); node.set_disabled_state(false);
node.set_enabled_state(true); node.set_enabled_state(true);
node.check_ancestors_disabled_state_for_form_control(); node.check_ancestors_disabled_state_for_form_control();
}
}
}, },
&atom!("cols") => { &atom!(cols) => {
self.cols.set(DEFAULT_COLS); let cols = mutation.new_value(attr).map(|value| {
value.uint().expect("Expected an AttrValue::UInt")
});
self.cols.set(cols.unwrap_or(DEFAULT_COLS));
}, },
&atom!("rows") => { &atom!(rows) => {
self.rows.set(DEFAULT_ROWS); let rows = mutation.new_value(attr).map(|value| {
value.uint().expect("Expected an AttrValue::UInt")
});
self.rows.set(rows.unwrap_or(DEFAULT_ROWS));
}, },
_ => () _ => {},
} }
} }

View file

@ -33,7 +33,7 @@ use dom::bindings::codegen::InheritTypes::HTMLTableSectionElementCast;
use dom::bindings::codegen::InheritTypes::HTMLTextAreaElementCast; use dom::bindings::codegen::InheritTypes::HTMLTextAreaElementCast;
use dom::bindings::codegen::InheritTypes::HTMLTitleElementCast; use dom::bindings::codegen::InheritTypes::HTMLTitleElementCast;
use dom::document::Document; use dom::document::Document;
use dom::element::ElementTypeId; use dom::element::{AttributeMutation, ElementTypeId};
use dom::event::Event; use dom::event::Event;
use dom::htmlelement::HTMLElementTypeId; use dom::htmlelement::HTMLElementTypeId;
use dom::node::NodeTypeId; use dom::node::NodeTypeId;
@ -50,27 +50,12 @@ pub trait VirtualMethods {
/// if any. /// if any.
fn super_type(&self) -> Option<&VirtualMethods>; fn super_type(&self) -> Option<&VirtualMethods>;
/// Called when changing or adding attributes, after the attribute's value /// Called when attributes of a node are mutated.
/// has been updated. /// https://dom.spec.whatwg.org/#attribute-is-set
fn after_set_attr(&self, attr: &Attr) { /// https://dom.spec.whatwg.org/#attribute-is-removed
if let Some(ref s) = self.super_type() { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
s.after_set_attr(attr); if let Some(s) = self.super_type() {
} s.attribute_mutated(attr, mutation);
}
/// Called when changing or removing attributes, before any modification
/// has taken place.
fn before_remove_attr(&self, attr: &Attr) {
if let Some(ref s) = self.super_type() {
s.before_remove_attr(attr);
}
}
/// Called when changing or removing attributes, after all modification
/// has taken place.
fn after_remove_attr(&self, name: &Atom) {
if let Some(ref s) = self.super_type() {
s.after_remove_attr(name);
} }
} }

View file

@ -170,6 +170,7 @@
test(function() { test(function() {
var optgroup = document.createElement("optgroup"); var optgroup = document.createElement("optgroup");
optgroup.disabled = true; optgroup.disabled = true;
check_disabled_selector(optgroup, true);
var option = document.createElement("option"); var option = document.createElement("option");
check_disabled_selector(option, false); check_disabled_selector(option, false);

View file

@ -48,6 +48,9 @@
document.getElementById("button1").setAttribute("disabled", "disabled"); document.getElementById("button1").setAttribute("disabled", "disabled");
testSelector(":disabled", ["button1", "input2", "select2", "optgroup2", "option2", "textarea2", "fieldset2", "clubname", "clubnum"], "':disabled' should also match elements whose disabled attribute has been set"); testSelector(":disabled", ["button1", "input2", "select2", "optgroup2", "option2", "textarea2", "fieldset2", "clubname", "clubnum"], "':disabled' should also match elements whose disabled attribute has been set");
document.getElementById("button1").setAttribute("disabled", "disabled");
testSelector(":disabled", ["button1", "input2", "select2", "optgroup2", "option2", "textarea2", "fieldset2", "clubname", "clubnum"], "':disabled' should also match elements whose disabled attribute has been set twice");
document.getElementById("input2").setAttribute("type", "submit"); // change input type to submit document.getElementById("input2").setAttribute("type", "submit"); // change input type to submit
testSelector(":disabled", ["button1", "input2", "select2", "optgroup2", "option2", "textarea2", "fieldset2", "clubname", "clubnum"], "':disabled' should also match disabled elements whose type has changed"); testSelector(":disabled", ["button1", "input2", "select2", "optgroup2", "option2", "textarea2", "fieldset2", "clubname", "clubnum"], "':disabled' should also match disabled elements whose type has changed");