mirror of
https://github.com/servo/servo.git
synced 2025-08-04 13:10:20 +01:00
Implement HTMLElement.dataset (fixes #2974).
This commit is contained in:
parent
285a06ff59
commit
57c520d8cf
15 changed files with 117 additions and 138 deletions
|
@ -2,51 +2,56 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom::bindings::cell::DOMRefCell;
|
||||
use dom::bindings::codegen::Bindings::DOMStringMapBinding;
|
||||
use dom::bindings::codegen::Bindings::DOMStringMapBinding::DOMStringMapMethods;
|
||||
use dom::bindings::error::ErrorResult;
|
||||
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::node::window_from_node;
|
||||
use dom::htmlelement::{HTMLElement, HTMLElementCustomAttributeHelpers};
|
||||
use servo_util::str::DOMString;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct DOMStringMap {
|
||||
map: DOMRefCell<HashMap<DOMString, DOMString>>,
|
||||
reflector_: Reflector,
|
||||
element: JS<HTMLElement>,
|
||||
}
|
||||
|
||||
impl DOMStringMap {
|
||||
fn new_inherited() -> DOMStringMap {
|
||||
fn new_inherited(element: JSRef<HTMLElement>) -> DOMStringMap {
|
||||
DOMStringMap {
|
||||
map: DOMRefCell::new(HashMap::new()),
|
||||
reflector_: Reflector::new(),
|
||||
element: JS::from_rooted(element),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(global: GlobalRef) -> Temporary<DOMStringMap> {
|
||||
reflect_dom_object(box DOMStringMap::new_inherited(),
|
||||
global, DOMStringMapBinding::Wrap)
|
||||
pub fn new(element: JSRef<HTMLElement>) -> Temporary<DOMStringMap> {
|
||||
let window = window_from_node(element).root();
|
||||
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> {
|
||||
fn NamedCreator(self, name: DOMString, value: DOMString) {
|
||||
self.map.borrow_mut().insert(name, value);
|
||||
fn NamedCreator(self, name: DOMString, value: DOMString) -> ErrorResult {
|
||||
self.NamedSetter(name, value)
|
||||
}
|
||||
|
||||
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) {
|
||||
self.map.borrow_mut().insert(name, value);
|
||||
fn NamedSetter(self, name: DOMString, value: DOMString) -> ErrorResult {
|
||||
let element = self.element.root();
|
||||
element.set_custom_attr(name, value)
|
||||
}
|
||||
|
||||
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) => {
|
||||
*found = true;
|
||||
value.clone()
|
||||
|
|
|
@ -13,10 +13,13 @@ use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLFrameSetElementDeriv
|
|||
use dom::bindings::codegen::InheritTypes::{EventTargetCast, HTMLInputElementCast};
|
||||
use dom::bindings::codegen::InheritTypes::{HTMLElementDerived, HTMLBodyElementDerived};
|
||||
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::cssstyledeclaration::CSSStyleDeclaration;
|
||||
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::node::{Node, NodeTypeId, window_from_node};
|
||||
use dom::virtualmethods::VirtualMethods;
|
||||
|
@ -31,6 +34,7 @@ use std::default::Default;
|
|||
pub struct HTMLElement {
|
||||
element: Element,
|
||||
style_decl: MutNullableJS<CSSStyleDeclaration>,
|
||||
dataset: MutNullableJS<DOMStringMap>,
|
||||
}
|
||||
|
||||
impl HTMLElementDerived for EventTarget {
|
||||
|
@ -48,6 +52,7 @@ impl HTMLElement {
|
|||
HTMLElement {
|
||||
element: Element::new_inherited(type_id, tag_name, ns!(HTML), prefix, document),
|
||||
style_decl: Default::default(),
|
||||
dataset: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,6 +94,11 @@ impl<'a> HTMLElementMethods for JSRef<'a, HTMLElement> {
|
|||
|
||||
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> {
|
||||
if self.is_body_or_frameset() {
|
||||
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> {
|
||||
fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
|
||||
let element: &JSRef<Element> = ElementCast::from_borrowed_ref(self);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
[OverrideBuiltins]
|
||||
interface DOMStringMap {
|
||||
getter DOMString (DOMString name);
|
||||
[Throws]
|
||||
setter creator void (DOMString name, DOMString value);
|
||||
deleter void (DOMString name);
|
||||
};
|
||||
|
|
|
@ -10,7 +10,7 @@ interface HTMLElement : Element {
|
|||
attribute DOMString lang;
|
||||
// attribute boolean translate;
|
||||
// attribute DOMString dir;
|
||||
//readonly attribute DOMStringMap dataset;
|
||||
readonly attribute DOMStringMap dataset;
|
||||
|
||||
// microdata
|
||||
// attribute boolean itemScope;
|
||||
|
|
|
@ -41,8 +41,8 @@ macro_rules! sizeof_checker (
|
|||
sizeof_checker!(size_event_target, EventTarget, 56)
|
||||
sizeof_checker!(size_node, Node, 304)
|
||||
sizeof_checker!(size_element, Element, 448)
|
||||
sizeof_checker!(size_htmlelement, HTMLElement, 464)
|
||||
sizeof_checker!(size_div, HTMLDivElement, 464)
|
||||
sizeof_checker!(size_span, HTMLSpanElement, 464)
|
||||
sizeof_checker!(size_htmlelement, HTMLElement, 480)
|
||||
sizeof_checker!(size_div, HTMLDivElement, 480)
|
||||
sizeof_checker!(size_span, HTMLSpanElement, 480)
|
||||
sizeof_checker!(size_text, Text, 336)
|
||||
sizeof_checker!(size_characterdata, CharacterData, 336)
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
[custom-attrs.html]
|
||||
type: testharness
|
||||
[Setting an Element\'s dataset property should not interfere with namespaced attributes with same name]
|
||||
expected: FAIL
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
[data_unicode_attr.html]
|
||||
type: testharness
|
||||
[dataset - SBCS]
|
||||
expected: FAIL
|
||||
|
||||
[dataset - UNICODE]
|
||||
expected: FAIL
|
||||
|
|
@ -12,9 +12,6 @@
|
|||
[Deleting element.dataset[\'Foo\'\] should also remove an attribute with name \'data--foo\' should it exist.]
|
||||
expected: FAIL
|
||||
|
||||
[Deleting element.dataset[\'-foo\'\] should also remove an attribute with name \'data--foo\' should it exist.]
|
||||
expected: FAIL
|
||||
|
||||
[Deleting element.dataset[\'-Foo\'\] should also remove an attribute with name \'data---foo\' should it exist.]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -24,6 +21,3 @@
|
|||
[Deleting element.dataset[\'\xc3\xa0\'\] should also remove an attribute with name \'data-\xc3\xa0\' should it exist.]
|
||||
expected: FAIL
|
||||
|
||||
[Deleting element.dataset[\'foo\'\] should not throw if even if the element does now have an attribute with the name data-foo.]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,8 +1,3 @@
|
|||
[dataset-enumeration.html]
|
||||
type: testharness
|
||||
[A dataset should be enumeratable.]
|
||||
expected: FAIL
|
||||
|
||||
[Only attributes who qualify as dataset properties should be enumeratable in the dataset.]
|
||||
expected: FAIL
|
||||
|
||||
expected: CRASH
|
||||
|
|
|
@ -1,32 +1,3 @@
|
|||
[dataset-get.html]
|
||||
type: testharness
|
||||
[Getting element.dataset[\'foo\'\] should return the value of element.getAttribute(\'data-foo\')\']
|
||||
expected: FAIL
|
||||
|
||||
[Getting element.dataset[\'fooBar\'\] should return the value of element.getAttribute(\'data-foo-bar\')\']
|
||||
expected: FAIL
|
||||
|
||||
[Getting element.dataset[\'-\'\] should return the value of element.getAttribute(\'data--\')\']
|
||||
expected: FAIL
|
||||
|
||||
[Getting element.dataset[\'Foo\'\] should return the value of element.getAttribute(\'data--foo\')\']
|
||||
expected: FAIL
|
||||
|
||||
[Getting element.dataset[\'-Foo\'\] should return the value of element.getAttribute(\'data---foo\')\']
|
||||
expected: FAIL
|
||||
|
||||
[Getting element.dataset[\'foo\'\] should return the value of element.getAttribute(\'data-Foo\')\']
|
||||
expected: FAIL
|
||||
|
||||
[Getting element.dataset[\'\'\] should return the value of element.getAttribute(\'data-\')\']
|
||||
expected: FAIL
|
||||
|
||||
[Getting element.dataset[\'\xc3\xa0\'\] should return the value of element.getAttribute(\'data-\xc3\xa0\')\']
|
||||
expected: FAIL
|
||||
|
||||
[Getting element.dataset[\'toString\'\] should return the value of element.getAttribute(\'data-to-string\')\']
|
||||
expected: FAIL
|
||||
|
||||
[Tests that an attribute named dataFoo does not make an entry in the dataset DOMStringMap.]
|
||||
expected: FAIL
|
||||
|
||||
expected: CRASH
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
[dataset-prototype.html]
|
||||
type: testharness
|
||||
[An elements dataset property is an instance of a DOMStringMap]
|
||||
expected: FAIL
|
||||
|
||||
[Properties on Object.prototype should shine through.]
|
||||
expected: FAIL
|
||||
|
|
@ -1,32 +1,5 @@
|
|||
[dataset-set.html]
|
||||
type: testharness
|
||||
[Setting element.dataset[\'foo\'\] should also change the value of element.getAttribute(\'data-foo\')\']
|
||||
expected: FAIL
|
||||
|
||||
[Setting element.dataset[\'fooBar\'\] should also change the value of element.getAttribute(\'data-foo-bar\')\']
|
||||
expected: FAIL
|
||||
|
||||
[Setting element.dataset[\'-\'\] should also change the value of element.getAttribute(\'data--\')\']
|
||||
expected: FAIL
|
||||
|
||||
[Setting element.dataset[\'Foo\'\] should also change the value of element.getAttribute(\'data--foo\')\']
|
||||
expected: FAIL
|
||||
|
||||
[Setting element.dataset[\'-Foo\'\] should also change the value of element.getAttribute(\'data---foo\')\']
|
||||
expected: FAIL
|
||||
|
||||
[Setting element.dataset[\'\'\] should also change the value of element.getAttribute(\'data-\')\']
|
||||
expected: FAIL
|
||||
|
||||
[Setting element.dataset[\'\xc3\xa0\'\] should also change the value of element.getAttribute(\'data-\xc3\xa0\')\']
|
||||
expected: FAIL
|
||||
|
||||
[Setting element.dataset[\'-foo\'\] should throw a SYNTAX_ERR\']
|
||||
expected: FAIL
|
||||
|
||||
[Setting element.dataset[\'foo \'\] should throw an INVALID_CHARACTER_ERR\']
|
||||
expected: FAIL
|
||||
|
||||
[Setting element.dataset[\'foo\xef\xa4\x80\'\] should throw an INVALID_CHARACTER_ERR\']
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
[dataset.html]
|
||||
type: testharness
|
||||
[Should return \'undefined\' before setting an attribute]
|
||||
expected: FAIL
|
||||
|
||||
[Should return \'value\' if that\'s the value]
|
||||
expected: FAIL
|
||||
|
||||
[Should return the empty string if that\'s the value]
|
||||
expected: FAIL
|
||||
|
||||
[Should return \'undefined\' after removing an attribute]
|
||||
expected: FAIL
|
||||
|
|
@ -921,12 +921,6 @@
|
|||
[DOMStringMap interface object length]
|
||||
expected: FAIL
|
||||
|
||||
[DOMStringMap must be primary interface of document.head.dataset]
|
||||
expected: FAIL
|
||||
|
||||
[Stringification of document.head.dataset]
|
||||
expected: FAIL
|
||||
|
||||
[DOMElementMap interface: existence and properties of interface object]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -954,9 +948,6 @@
|
|||
[HTMLElement interface: attribute dir]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLElement interface: attribute dataset]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLElement interface: attribute itemScope]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -1215,9 +1206,6 @@
|
|||
[HTMLElement interface: document.createElement("noscript") must inherit property "dir" with the proper type (3)]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLElement interface: document.createElement("noscript") must inherit property "dataset" with the proper type (4)]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLElement interface: document.createElement("noscript") must inherit property "itemScope" with the proper type (5)]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,3 +1,35 @@
|
|||
[basic.html]
|
||||
type: testharness
|
||||
expected: TIMEOUT
|
||||
[<img src="/images/green-256x256.png" data-expect="256">]
|
||||
expected: FAIL
|
||||
|
||||
[<img srcset="/images/green-256x256.png 1x" data-expect="256">]
|
||||
expected: FAIL
|
||||
|
||||
[<img srcset="/images/green-256x256.png 1.6x" data-expect="160">]
|
||||
expected: FAIL
|
||||
|
||||
[<img srcset="/images/green-256x256.png 2x" data-expect="128">]
|
||||
expected: FAIL
|
||||
|
||||
[<img srcset="/images/green-256x256.png 10000x" data-expect="0">]
|
||||
expected: FAIL
|
||||
|
||||
[<img srcset="/images/green-256x256.png 9e99999999999999999999999x" data-expect="0">]
|
||||
expected: FAIL
|
||||
|
||||
[<img srcset="/images/green-256x256.png 256w" sizes="256px" data-expect="256">]
|
||||
expected: FAIL
|
||||
|
||||
[<img srcset="/images/green-256x256.png 512w" sizes="256px" data-expect="128">]
|
||||
expected: FAIL
|
||||
|
||||
[<img srcset="/images/green-256x256.png 256w" sizes="512px" data-expect="512">]
|
||||
expected: FAIL
|
||||
|
||||
[<img srcset="/images/green-256x256.png 256w" sizes="1px" data-expect="1">]
|
||||
expected: FAIL
|
||||
|
||||
[<img srcset="/images/green-256x256.png 256w" sizes="0px" data-expect="0">]
|
||||
expected: FAIL
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue