Implement HTMLElement.dataset

Make DOMStringMap use related Element's custom attributes values.
This commit is contained in:
Bruno de Oliveira Abinader 2014-11-19 11:57:39 -04:00
parent e1a7d99df7
commit 628ee92d73
4 changed files with 79 additions and 18 deletions

View file

@ -2,51 +2,56 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::DOMStringMapBinding; use dom::bindings::codegen::Bindings::DOMStringMapBinding;
use dom::bindings::codegen::Bindings::DOMStringMapBinding::DOMStringMapMethods; use dom::bindings::codegen::Bindings::DOMStringMapBinding::DOMStringMapMethods;
use dom::bindings::error::ErrorResult;
use dom::bindings::global::GlobalRef; use dom::bindings::global::GlobalRef;
use dom::bindings::js::{JSRef, Temporary}; use dom::bindings::js::{JS, JSRef, Temporary};
use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object}; use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
use dom::node::window_from_node;
use dom::htmlelement::{HTMLElement, HTMLElementCustomAttributeHelpers};
use servo_util::str::DOMString; use servo_util::str::DOMString;
use std::collections::HashMap;
#[dom_struct] #[dom_struct]
pub struct DOMStringMap { pub struct DOMStringMap {
map: DOMRefCell<HashMap<DOMString, DOMString>>,
reflector_: Reflector, reflector_: Reflector,
element: JS<HTMLElement>,
} }
impl DOMStringMap { impl DOMStringMap {
fn new_inherited() -> DOMStringMap { fn new_inherited(element: JSRef<HTMLElement>) -> DOMStringMap {
DOMStringMap { DOMStringMap {
map: DOMRefCell::new(HashMap::new()),
reflector_: Reflector::new(), reflector_: Reflector::new(),
element: JS::from_rooted(element),
} }
} }
pub fn new(global: GlobalRef) -> Temporary<DOMStringMap> { pub fn new(element: JSRef<HTMLElement>) -> Temporary<DOMStringMap> {
reflect_dom_object(box DOMStringMap::new_inherited(), let window = window_from_node(element).root();
global, DOMStringMapBinding::Wrap) reflect_dom_object(box DOMStringMap::new_inherited(element),
GlobalRef::Window(window.root_ref()), DOMStringMapBinding::Wrap)
} }
} }
// https://html.spec.whatwg.org/#domstringmap
impl<'a> DOMStringMapMethods for JSRef<'a, DOMStringMap> { impl<'a> DOMStringMapMethods for JSRef<'a, DOMStringMap> {
fn NamedCreator(self, name: DOMString, value: DOMString) { fn NamedCreator(self, name: DOMString, value: DOMString) -> ErrorResult {
self.map.borrow_mut().insert(name, value); self.NamedSetter(name, value)
} }
fn NamedDeleter(self, name: DOMString) { fn NamedDeleter(self, name: DOMString) {
self.map.borrow_mut().remove(&name); let element = self.element.root();
element.delete_custom_attr(name)
} }
fn NamedSetter(self, name: DOMString, value: DOMString) { fn NamedSetter(self, name: DOMString, value: DOMString) -> ErrorResult {
self.map.borrow_mut().insert(name, value); let element = self.element.root();
element.set_custom_attr(name, value)
} }
fn NamedGetter(self, name: DOMString, found: &mut bool) -> DOMString { fn NamedGetter(self, name: DOMString, found: &mut bool) -> DOMString {
match self.map.borrow().get(&name) { let element = self.element.root();
match element.get_custom_attr(name) {
Some(value) => { Some(value) => {
*found = true; *found = true;
value.clone() value.clone()

View file

@ -13,10 +13,13 @@ use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLFrameSetElementDeriv
use dom::bindings::codegen::InheritTypes::{EventTargetCast, HTMLInputElementCast}; use dom::bindings::codegen::InheritTypes::{EventTargetCast, HTMLInputElementCast};
use dom::bindings::codegen::InheritTypes::{HTMLElementDerived, HTMLBodyElementDerived}; use dom::bindings::codegen::InheritTypes::{HTMLElementDerived, HTMLBodyElementDerived};
use dom::bindings::js::{JSRef, Temporary, MutNullableJS}; use dom::bindings::js::{JSRef, Temporary, MutNullableJS};
use dom::bindings::error::ErrorResult;
use dom::bindings::error::Error::Syntax;
use dom::bindings::utils::{Reflectable, Reflector}; use dom::bindings::utils::{Reflectable, Reflector};
use dom::cssstyledeclaration::CSSStyleDeclaration; use dom::cssstyledeclaration::CSSStyleDeclaration;
use dom::document::Document; use dom::document::Document;
use dom::element::{Element, ElementTypeId, ActivationElementHelpers}; use dom::domstringmap::DOMStringMap;
use dom::element::{Element, ElementTypeId, ActivationElementHelpers, AttributeHandlers};
use dom::eventtarget::{EventTarget, EventTargetHelpers, EventTargetTypeId}; use dom::eventtarget::{EventTarget, EventTargetHelpers, EventTargetTypeId};
use dom::node::{Node, NodeTypeId, window_from_node}; use dom::node::{Node, NodeTypeId, window_from_node};
use dom::virtualmethods::VirtualMethods; use dom::virtualmethods::VirtualMethods;
@ -31,6 +34,7 @@ use std::default::Default;
pub struct HTMLElement { pub struct HTMLElement {
element: Element, element: Element,
style_decl: MutNullableJS<CSSStyleDeclaration>, style_decl: MutNullableJS<CSSStyleDeclaration>,
dataset: MutNullableJS<DOMStringMap>,
} }
impl HTMLElementDerived for EventTarget { impl HTMLElementDerived for EventTarget {
@ -48,6 +52,7 @@ impl HTMLElement {
HTMLElement { HTMLElement {
element: Element::new_inherited(type_id, tag_name, ns!(HTML), prefix, document), element: Element::new_inherited(type_id, tag_name, ns!(HTML), prefix, document),
style_decl: Default::default(), style_decl: Default::default(),
dataset: Default::default(),
} }
} }
@ -89,6 +94,11 @@ impl<'a> HTMLElementMethods for JSRef<'a, HTMLElement> {
global_event_handlers!(NoOnload) global_event_handlers!(NoOnload)
// https://html.spec.whatwg.org/multipage/dom.html#dom-dataset
fn Dataset(self) -> Temporary<DOMStringMap> {
self.dataset.or_init(|| DOMStringMap::new(self))
}
fn GetOnload(self) -> Option<EventHandlerNonNull> { fn GetOnload(self) -> Option<EventHandlerNonNull> {
if self.is_body_or_frameset() { if self.is_body_or_frameset() {
let win = window_from_node(self).root(); let win = window_from_node(self).root();
@ -122,6 +132,51 @@ impl<'a> HTMLElementMethods for JSRef<'a, HTMLElement> {
} }
} }
// https://html.spec.whatwg.org/#attr-data-*
pub trait HTMLElementCustomAttributeHelpers {
fn set_custom_attr(self, name: DOMString, value: DOMString) -> ErrorResult;
fn get_custom_attr(self, name: DOMString) -> Option<DOMString>;
fn delete_custom_attr(self, name: DOMString);
}
fn to_snake_case(name: DOMString) -> DOMString {
let mut attr_name = "data-".into_string();
for ch in name.as_slice().chars() {
if ch.is_uppercase() {
attr_name.push('\x2d');
attr_name.push(ch.to_lowercase());
} else {
attr_name.push(ch);
}
}
attr_name
}
impl<'a> HTMLElementCustomAttributeHelpers for JSRef<'a, HTMLElement> {
fn set_custom_attr(self, name: DOMString, value: DOMString) -> ErrorResult {
if name.as_slice().chars()
.skip_while(|&ch| ch != '\u002d')
.nth(1).map_or(false, |ch| ch as u8 - b'a' < 26) {
return Err(Syntax);
}
let element: JSRef<Element> = ElementCast::from_ref(self);
element.set_custom_attribute(to_snake_case(name), value)
}
fn get_custom_attr(self, name: DOMString) -> Option<DOMString> {
let element: JSRef<Element> = ElementCast::from_ref(self);
element.get_attribute(ns!(""), &Atom::from_slice(to_snake_case(name).as_slice())).map(|attr| {
let attr = attr.root();
attr.value().as_slice().to_string()
})
}
fn delete_custom_attr(self, name: DOMString) {
let element: JSRef<Element> = ElementCast::from_ref(self);
element.remove_attribute(ns!(""), to_snake_case(name).as_slice())
}
}
impl<'a> VirtualMethods for JSRef<'a, HTMLElement> { impl<'a> VirtualMethods for JSRef<'a, HTMLElement> {
fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> { fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
let element: &JSRef<Element> = ElementCast::from_borrowed_ref(self); let element: &JSRef<Element> = ElementCast::from_borrowed_ref(self);

View file

@ -7,6 +7,7 @@
[OverrideBuiltins] [OverrideBuiltins]
interface DOMStringMap { interface DOMStringMap {
getter DOMString (DOMString name); getter DOMString (DOMString name);
[Throws]
setter creator void (DOMString name, DOMString value); setter creator void (DOMString name, DOMString value);
deleter void (DOMString name); deleter void (DOMString name);
}; };

View file

@ -10,7 +10,7 @@ interface HTMLElement : Element {
attribute DOMString lang; attribute DOMString lang;
// attribute boolean translate; // attribute boolean translate;
// attribute DOMString dir; // attribute DOMString dir;
//readonly attribute DOMStringMap dataset; readonly attribute DOMStringMap dataset;
// microdata // microdata
// attribute boolean itemScope; // attribute boolean itemScope;