Skip to content
This repository was archived by the owner on Mar 13, 2018. It is now read-only.

Commit ac1d697

Browse files
committed
Invalidate shadow renderer when classList is mutated
Fixes #443
1 parent 41b5434 commit ac1d697

File tree

6 files changed

+192
-26
lines changed

6 files changed

+192
-26
lines changed

shadowdom.js

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
'src/wrappers/node-interfaces.js',
3131
'src/wrappers/CharacterData.js',
3232
'src/wrappers/Text.js',
33+
'src/wrappers/DOMTokenList.js',
3334
'src/wrappers/Element.js',
3435
'src/wrappers/HTMLElement.js',
3536
'src/wrappers/HTMLCanvasElement.js',

src/wrappers/DOMTokenList.js

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2014 The Polymer Authors. All rights reserved.
2+
// Use of this source code is goverened by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
(function(scope) {
6+
'use strict';
7+
8+
function invalidateClass(el) {
9+
scope.invalidateRendererBasedOnAttribute(el, 'class');
10+
}
11+
12+
function DOMTokenList(impl, ownerElement) {
13+
this.impl = impl;
14+
this.ownerElement_ = ownerElement;
15+
}
16+
17+
DOMTokenList.prototype = {
18+
get length() {
19+
return this.impl.length;
20+
},
21+
item: function(index) {
22+
return this.impl.item(index);
23+
},
24+
contains: function(token) {
25+
return this.impl.contains(token);
26+
},
27+
add: function() {
28+
this.impl.add.apply(this.impl, arguments);
29+
invalidateClass(this.ownerElement_);
30+
},
31+
remove: function() {
32+
this.impl.remove.apply(this.impl, arguments);
33+
invalidateClass(this.ownerElement_);
34+
},
35+
toggle: function(token) {
36+
var rv = this.impl.toggle.apply(this.impl, arguments);
37+
invalidateClass(this.ownerElement_);
38+
return rv;
39+
},
40+
toString: function() {
41+
return this.impl.toString();
42+
}
43+
};
44+
45+
scope.wrappers.DOMTokenList = DOMTokenList;
46+
})(window.ShadowDOMPolyfill);

src/wrappers/Element.js

+30-24
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88
var ChildNodeInterface = scope.ChildNodeInterface;
99
var GetElementsByInterface = scope.GetElementsByInterface;
1010
var Node = scope.wrappers.Node;
11+
var DOMTokenList = scope.wrappers.DOMTokenList;
1112
var ParentNodeInterface = scope.ParentNodeInterface;
1213
var SelectorsInterface = scope.SelectorsInterface;
1314
var addWrapNodeListMethod = scope.addWrapNodeListMethod;
1415
var enqueueMutation = scope.enqueueMutation;
1516
var mixin = scope.mixin;
1617
var oneOf = scope.oneOf;
1718
var registerWrapper = scope.registerWrapper;
19+
var unwrap = scope.unwrap;
1820
var wrappers = scope.wrappers;
1921

2022
var OriginalElement = window.Element;
@@ -54,6 +56,8 @@
5456
});
5557
}
5658

59+
var classListTable = new WeakMap();
60+
5761
function Element(node) {
5862
Node.call(this, node);
5963
}
@@ -91,6 +95,31 @@
9195

9296
matches: function(selector) {
9397
return originalMatches.call(this.impl, selector);
98+
},
99+
100+
get classList() {
101+
var list = classListTable.get(this);
102+
if (!list) {
103+
classListTable.set(this,
104+
list = new DOMTokenList(unwrap(this).classList, this));
105+
}
106+
return list;
107+
},
108+
109+
get className() {
110+
return unwrap(this).className;
111+
},
112+
113+
set className(v) {
114+
this.setAttribute('class', v);
115+
},
116+
117+
get id() {
118+
return unwrap(this).id;
119+
},
120+
121+
set id(v) {
122+
this.setAttribute('id', v);
94123
}
95124
});
96125

@@ -107,28 +136,6 @@
107136
Element.prototype.createShadowRoot;
108137
}
109138

110-
/**
111-
* Useful for generating the accessor pair for a property that reflects an
112-
* attribute.
113-
*/
114-
function setterDirtiesAttribute(prototype, propertyName, opt_attrName) {
115-
var attrName = opt_attrName || propertyName;
116-
Object.defineProperty(prototype, propertyName, {
117-
get: function() {
118-
return this.impl[propertyName];
119-
},
120-
set: function(v) {
121-
this.impl[propertyName] = v;
122-
invalidateRendererBasedOnAttribute(this, attrName);
123-
},
124-
configurable: true,
125-
enumerable: true
126-
});
127-
}
128-
129-
setterDirtiesAttribute(Element.prototype, 'id');
130-
setterDirtiesAttribute(Element.prototype, 'className', 'class');
131-
132139
mixin(Element.prototype, ChildNodeInterface);
133140
mixin(Element.prototype, GetElementsByInterface);
134141
mixin(Element.prototype, ParentNodeInterface);
@@ -137,8 +144,7 @@
137144
registerWrapper(OriginalElement, Element,
138145
document.createElementNS(null, 'x'));
139146

140-
// TODO(arv): Export setterDirtiesAttribute and apply it to more bindings
141-
// that reflect attributes.
147+
scope.invalidateRendererBasedOnAttribute = invalidateRendererBasedOnAttribute;
142148
scope.matchesNames = matchesNames;
143149
scope.wrappers.Element = Element;
144150
})(window.ShadowDOMPolyfill);

test/js/DOMTokenList.js

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2014 The Polymer Authors. All rights reserved.
3+
* Use of this source code is goverened by a BSD-style
4+
* license that can be found in the LICENSE file.
5+
*/
6+
7+
suite('DOMTokenList', function() {
8+
9+
test('instanceof', function() {
10+
var div = document.createElement('div');
11+
assert.instanceOf(div.classList, DOMTokenList);
12+
});
13+
14+
test('identity', function() {
15+
var div = document.createElement('div');
16+
assert.equal(div.classList, div.classList);
17+
});
18+
19+
test('length', function() {
20+
var div = document.createElement('div');
21+
var classList = div.classList;
22+
assert.equal(classList.length, 0);
23+
div.className = 'a';
24+
assert.equal(classList.length, 1);
25+
div.className = 'a b';
26+
assert.equal(classList.length, 2);
27+
div.className = 'a b a';
28+
assert.equal(classList.length, 3);
29+
});
30+
31+
test('item', function() {
32+
var div = document.createElement('div');
33+
var classList = div.classList;
34+
assert.isNull(classList.item(0));
35+
div.className = 'a';
36+
assert.equal(classList.item(0), 'a');
37+
assert.isNull(classList.item(1));
38+
div.className = 'a b';
39+
assert.equal(classList.item(0), 'a');
40+
assert.equal(classList.item(1), 'b');
41+
assert.isNull(classList.item(2));
42+
div.className = 'a b a';
43+
assert.equal(classList.item(0), 'a');
44+
assert.equal(classList.item(1), 'b');
45+
assert.equal(classList.item(2), 'a');
46+
assert.isNull(classList.item(3));
47+
});
48+
49+
test('contains', function() {
50+
var div = document.createElement('div');
51+
var classList = div.classList;
52+
assert.isFalse(classList.contains());
53+
assert.isFalse(classList.contains('a'));
54+
div.className = 'a';
55+
assert.isTrue(classList.contains('a'));
56+
div.className = 'a b';
57+
assert.isTrue(classList.contains('a'));
58+
assert.isTrue(classList.contains('b'));
59+
});
60+
61+
test('add', function() {
62+
var div = document.createElement('div');
63+
var classList = div.classList;
64+
classList.add('a');
65+
assert.equal(div.className, 'a');
66+
classList.add('b');
67+
assert.equal(div.className, 'a b');
68+
classList.add('a');
69+
assert.equal(div.className, 'a b');
70+
});
71+
72+
test('remove', function() {
73+
var div = document.createElement('div');
74+
var classList = div.classList;
75+
div.className = 'a b';
76+
classList.remove('a');
77+
assert.equal(div.className, 'b');
78+
classList.remove('a');
79+
assert.equal(div.className, 'b');
80+
classList.remove('b');
81+
assert.equal(div.className, '');
82+
});
83+
84+
test('toggle', function() {
85+
var div = document.createElement('div');
86+
var classList = div.classList;
87+
div.className = 'a b';
88+
classList.toggle('a');
89+
assert.equal(div.className, 'b');
90+
classList.toggle('a');
91+
assert.equal(div.className, 'b a');
92+
classList.toggle('b');
93+
assert.equal(div.className, 'a');
94+
});
95+
96+
test('toString', function() {
97+
var div = document.createElement('div');
98+
var classList = div.classList;
99+
div.className = 'a';
100+
assert.equal(classList.toString(), 'a');
101+
div.className = 'b a';
102+
assert.equal(classList.toString(), 'b a');
103+
});
104+
});

test/js/test.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -356,9 +356,17 @@ suite('Shadow DOM', function() {
356356
assert.equal(getVisualInnerHtml(host), '<a class="a"></a>');
357357
assert.equal(calls, 4);
358358

359-
a.className = null;
359+
a.classList.remove('a');
360360
assert.equal(getVisualInnerHtml(host), '');
361361
assert.equal(calls, 5);
362+
363+
a.classList.add('a');
364+
assert.equal(getVisualInnerHtml(host), '<a class="a"></a>');
365+
assert.equal(calls, 6);
366+
367+
a.className = null;
368+
assert.equal(getVisualInnerHtml(host), '');
369+
assert.equal(calls, 7);
362370
});
363371

364372
});
@@ -485,4 +493,4 @@ suite('Shadow DOM', function() {
485493

486494
});
487495

488-
});
496+
});

test/test.main.js

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ var modules = [
7676
'ChildNodeInterface.js',
7777
'Comment.js',
7878
'Document.js',
79+
'DOMTokenList.js',
7980
'Element.js',
8081
'HTMLAudioElement.js',
8182
'HTMLBodyElement.js',

0 commit comments

Comments
 (0)