Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Fixes #2247. #2250

Closed
wants to merge 3 commits into from

2 participants

@ibolmo
Owner

This fixes a nasty regression that custom attributes set by HTML text
(e.g. innerHTML) would previously be considered expando and therefore
thought to have been considered a fake attribute. Therefore, we didn't
return the value.

This fix relies now on outerHTML to check for the existence of the
attribute. Keep in mind this also fixes the previous bug of returning
custom functions since any new el.attribute = are not shown in
outerHTML.

PASSED: IE6-9.

@ibolmo ibolmo Fixes #2247.
This fixes a nasty regression that custom attributes set by HTML text
(e.g. innerHTML) would previously be considered `expando` and therefore
thought to have been considered a fake attribute. Therefore, we didn't
return the value.

This fix relies now on outerHTML to check for the existence of the
attribute. Keep in mind this also fixes the previous bug of returning
custom functions since any new `el.attribute =` are not shown in
outerHTML.

PASSED: IE6-9.
d0208f9
@ibolmo
Owner

Fixes #2247.

@arian this seems to be performant. At least a single hit, then cached for any further lookups.

Source/Element/Element.js
@@ -607,6 +607,9 @@ Element.implement({
} else {
if (value == null){
this.removeAttribute(name);
+ /* <ltIE9> */
+ if (pollutesGetAttribute) delete attributeWhiteList[name];
@ibolmo Owner
ibolmo added a note

I sneaked this in. Not likely case, but I thought it's necessary to be symmetrical. If we add to the whitelist, we then remove from the white list if we remove the property.

@arian Owner
arian added a note

euhm, now it removes it for the first element with this attribute, however there can be more elements with this attribute. So if you do .removeProperty('data-foo') other elements that try to get/set this property won't work correctly, right?

@ibolmo Owner
ibolmo added a note

hrm.. good point .. perhaps attributeWhiteList should be per element. Perhaps in storage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Source/Element/Element.js
@@ -627,8 +630,9 @@ Element.implement({
if (getter) return getter(this);
/* <ltIE9> */
if (pollutesGetAttribute && !attributeWhiteList[name]){
- var attr = this.getAttributeNode(name);
- if (!attr || attr.expando) return null;
+ var outer = this.outerHTML, i = outer.indexOf(name);
+ if ((i < 0) || (i > outer.indexOf('><'))) return null;
@arian Owner
arian added a note

outer.indexOf('><'): what if the outerHTML is <a href="#">text</a>, then there isn't a >< in that string...

@ibolmo Owner
ibolmo added a note

I'm going to add that the coverage, and you made me also realize that i could save a couple of ops if I still check for expando. I'll modify this afternoon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@ibolmo
Owner

@arian have another go pls :D

@arian
Owner

What if we'd have a proper hasAttribute method?

As you probably know these are the results in oldIE:

var div = makeElement('<div data-custom="a">hello</a>');

div.somerandomproperty = 1;

// the comments are the actual outcome.

var attr = div.getAttributeNode('data-custom');
expect(attr.specified).toEqual(true); // true
expect(attr.expando).toEqual(undefined); // true

div.randomproperty = 1;

attr = div.getAttributeNode('somerandomproperty');
expect(attr).toEqual(undefined); // DOMNode
expect(attr && attr.specified).toEqual(undefined); // true
expect(attr && attr.expando).toEqual(undefined); // true

After some research the only solution I found is using outerHTML indeed.

@ibolmo
Owner

That's a great idea @arian and I'll add a github issue for enhancing the MooTools API for 1.5.

I tried to augment the Slick.hasAttribute but there's a couple of problems.

  1. Slick doesn't need this fix. It's very -- very -- unlikely that anyone would check attribute with value of a function (the original problem).
  2. We don't use setProperty or removeProperty from Slick. Therefore it's very hard to whitelist dynamic properties.

So my initial idea of adding this code to Slick is out of the question. I think we'll have to wait for 1.5 to use a cleaner hasAttribute lookup and then return the property if we should

@ibolmo
Owner

@arian when you're ready give me the :+1: or :-1:

@arian
Owner
div = new Element('div', {html: '<div class="><" data-custom="other"></div>'}).getFirst();
expect(div.get('data-custom')).toEqual('other');

fails.

@ibolmo ibolmo Adding coverage for even more edge cases.
I enjoy a good stumping. Keep 'em coming @arian. :D
79f2635
@arian arian closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 24, 2012
  1. @ibolmo

    Fixes #2247.

    ibolmo authored
    This fixes a nasty regression that custom attributes set by HTML text
    (e.g. innerHTML) would previously be considered `expando` and therefore
    thought to have been considered a fake attribute. Therefore, we didn't
    return the value.
    
    This fix relies now on outerHTML to check for the existence of the
    attribute. Keep in mind this also fixes the previous bug of returning
    custom functions since any new `el.attribute =` are not shown in
    outerHTML.
    
    PASSED: IE6-9.
Commits on Jan 26, 2012
  1. @ibolmo

    attributeWhiteList now per Element, fixed for unnested html, a little

    ibolmo authored
    optimization for valid attributes.
    
    PASSED: IE6-9
Commits on Jan 27, 2012
  1. @ibolmo

    Adding coverage for even more edge cases.

    ibolmo authored
    I enjoy a good stumping. Keep 'em coming @arian. :D
This page is out of date. Refresh to see the latest.
Showing with 43 additions and 4 deletions.
  1. +16 −4 Source/Element/Element.js
  2. +27 −0 Specs/1.4client/Element/Element.js
View
20 Source/Element/Element.js
@@ -595,7 +595,6 @@ var pollutesGetAttribute = (function(div){
return (div.getAttribute('random') == 'attribute');
})(document.createElement('div'));
-if (pollutesGetAttribute) var attributeWhiteList = {};
/* <ltIE9> */
Element.implement({
@@ -605,8 +604,15 @@ Element.implement({
if (setter){
setter(this, value);
} else {
+ /* <ltIE9> */
+ if (pollutesGetAttribute) var attributeWhiteList = this.retrieve('$attributeWhiteList', {});
+ /* </ltIE9> */
+
if (value == null){
this.removeAttribute(name);
+ /* <ltIE9> */
+ if (pollutesGetAttribute) delete attributeWhiteList[name];
+ /* </ltIE9> */
} else {
this.setAttribute(name, value);
/* <ltIE9> */
@@ -626,9 +632,15 @@ Element.implement({
var getter = propertyGetters[name.toLowerCase()];
if (getter) return getter(this);
/* <ltIE9> */
- if (pollutesGetAttribute && !attributeWhiteList[name]){
- var attr = this.getAttributeNode(name);
- if (!attr || attr.expando) return null;
+ if (pollutesGetAttribute){
+ var attr = this.getAttributeNode(name), attributeWhiteList = this.retrieve('$attributeWhiteList', {});
+ if (!attr) return null;
+ if (attr.expando && !attributeWhiteList[name]){
+ var outer = this.outerHTML;
+ // segment by the opening tag and find mention of attribute name
+ if (outer.substr(0, outer.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(name) < 0) return null;
+ attributeWhiteList[name] = true;
+ }
}
/* </ltIE9> */
var result = Slick.getAttribute(this, name);
View
27 Specs/1.4client/Element/Element.js
@@ -26,6 +26,33 @@ describe('Element', function(){
expect(div.getProperty('random')).toEqual('attribute');
});
+ it('should get custom attributes in html', function(){
+ var div = new Element('div', {html: '<div data-load="typical"></div>'}).getFirst();
+ expect(div.get('data-load')).toEqual('typical');
+
+ div = new Element('div', {html: '<div data-custom></div>'}).getFirst();
+ expect(div.get('data-custom')).toEqual('');
+
+ div = new Element('div', {html: '<div data-custom="nested"><a data-custom="other"></a></div>'}).getFirst();
+ expect(div.get('data-custom')).toEqual('nested');
+
+ div = new Element('div', {html: '<div><a data-custom="other"></a></div>'}).getFirst();
+ expect(div.get('data-custom')).toEqual(null);
+
+ div = new Element('div', {html: '<a data-custom="singular" href="#">href</a>'}).getFirst();
+ expect(div.get('data-custom')).toEqual('singular');
+
+ div = new Element('div', {html: '<div class="><" data-custom="evil attribute values"></div>'}).getFirst();
+ expect(div.get('data-custom')).toEqual('evil attribute values');
+
+ div = new Element('div', {html: '<div class="> . <" data-custom="aggrevated evil attribute values"></div>'}).getFirst();
+ expect(div.get('data-custom')).toEqual('aggrevated evil attribute values');
+
+ div = new Element('div', {html: '<a href="#"> data-custom="singular"</a>'}).getFirst();
+ expect(div.get('data-custom')).toEqual(null);
+ });
+
+
});
describe('Element.set', function(){
Something went wrong with that request. Please try again.