Skip to content

Commit ab2e603

Browse files
authored
Release - v2.2.14 (#3006)
1 parent e57011c commit ab2e603

10 files changed

Lines changed: 1194 additions & 164 deletions

render/domFor.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
var delayedRemoval = new WeakMap
44

5-
function *domFor(vnode, object = {}) {
5+
function *domFor(vnode) {
66
// To avoid unintended mangling of the internal bundler,
77
// parameter destructuring is not used here.
88
var dom = vnode.dom
99
var domSize = vnode.domSize
10-
var generation = object.generation
10+
var generation = delayedRemoval.get(dom)
1111
if (dom != null) do {
1212
var nextSibling = dom.nextSibling
1313

render/hyperscript.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ function execSelector(state, vnode) {
6262
attrs = Object.assign({type: attrs.type}, attrs)
6363
}
6464

65+
// This reduces the complexity of the evaluation of "is" within the render function.
66+
vnode.is = attrs.is
67+
6568
vnode.attrs = attrs
6669

6770
return vnode

render/render.js

Lines changed: 51 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ module.exports = function() {
114114
function createElement(parent, vnode, hooks, ns, nextSibling) {
115115
var tag = vnode.tag
116116
var attrs = vnode.attrs
117-
var is = attrs && attrs.is
117+
var is = vnode.is
118118

119119
ns = getNameSpace(vnode) || ns
120120

@@ -396,7 +396,7 @@ module.exports = function() {
396396
}
397397
function updateNode(parent, old, vnode, hooks, nextSibling, ns) {
398398
var oldTag = old.tag, tag = vnode.tag
399-
if (oldTag === tag) {
399+
if (oldTag === tag && old.is === vnode.is) {
400400
vnode.state = old.state
401401
vnode.events = old.events
402402
if (shouldNotUpdate(vnode, old)) return
@@ -426,7 +426,7 @@ module.exports = function() {
426426
}
427427
function updateHTML(parent, old, vnode, ns, nextSibling) {
428428
if (old.children !== vnode.children) {
429-
removeDOM(parent, old, undefined)
429+
removeDOM(parent, old)
430430
createHTML(parent, vnode, ns, nextSibling)
431431
}
432432
else {
@@ -585,71 +585,38 @@ module.exports = function() {
585585
if (vnode != null) removeNode(parent, vnode)
586586
}
587587
}
588-
function removeNode(parent, vnode) {
589-
var mask = 0
588+
function tryBlockRemove(parent, vnode, source, counter) {
590589
var original = vnode.state
591-
var stateResult, attrsResult
592-
if (typeof vnode.tag !== "string" && typeof vnode.state.onbeforeremove === "function") {
593-
var result = callHook.call(vnode.state.onbeforeremove, vnode)
594-
if (result != null && typeof result.then === "function") {
595-
mask = 1
596-
stateResult = result
597-
}
598-
}
599-
if (vnode.attrs && typeof vnode.attrs.onbeforeremove === "function") {
600-
var result = callHook.call(vnode.attrs.onbeforeremove, vnode)
601-
if (result != null && typeof result.then === "function") {
602-
// eslint-disable-next-line no-bitwise
603-
mask |= 2
604-
attrsResult = result
605-
}
606-
}
607-
checkState(vnode, original)
608-
var generation
609-
// If we can, try to fast-path it and avoid all the overhead of awaiting
610-
if (!mask) {
590+
var result = callHook.call(source.onbeforeremove, vnode)
591+
if (result == null) return
592+
593+
var generation = currentRender
594+
for (var dom of domFor(vnode)) delayedRemoval.set(dom, generation)
595+
counter.v++
596+
597+
Promise.resolve(result).finally(function () {
598+
checkState(vnode, original)
599+
tryResumeRemove(parent, vnode, counter)
600+
})
601+
}
602+
function tryResumeRemove(parent, vnode, counter) {
603+
if (--counter.v === 0) {
611604
onremove(vnode)
612-
removeDOM(parent, vnode, generation)
613-
} else {
614-
generation = currentRender
615-
for (var dom of domFor(vnode)) delayedRemoval.set(dom, generation)
616-
if (stateResult != null) {
617-
stateResult.finally(function () {
618-
// eslint-disable-next-line no-bitwise
619-
if (mask & 1) {
620-
// eslint-disable-next-line no-bitwise
621-
mask &= 2
622-
if (!mask) {
623-
checkState(vnode, original)
624-
onremove(vnode)
625-
removeDOM(parent, vnode, generation)
626-
}
627-
}
628-
})
629-
}
630-
if (attrsResult != null) {
631-
attrsResult.finally(function () {
632-
// eslint-disable-next-line no-bitwise
633-
if (mask & 2) {
634-
// eslint-disable-next-line no-bitwise
635-
mask &= 1
636-
if (!mask) {
637-
checkState(vnode, original)
638-
onremove(vnode)
639-
removeDOM(parent, vnode, generation)
640-
}
641-
}
642-
})
643-
}
605+
removeDOM(parent, vnode)
644606
}
645607
}
646-
function removeDOM(parent, vnode, generation) {
608+
function removeNode(parent, vnode) {
609+
var counter = {v: 1}
610+
if (typeof vnode.tag !== "string" && typeof vnode.state.onbeforeremove === "function") tryBlockRemove(parent, vnode, vnode.state, counter)
611+
if (vnode.attrs && typeof vnode.attrs.onbeforeremove === "function") tryBlockRemove(parent, vnode, vnode.attrs, counter)
612+
tryResumeRemove(parent, vnode, counter)
613+
}
614+
function removeDOM(parent, vnode) {
647615
if (vnode.dom == null) return
648616
if (vnode.domSize == null) {
649-
// don't allocate for the common case
650-
if (delayedRemoval.get(vnode.dom) === generation) parent.removeChild(vnode.dom)
617+
parent.removeChild(vnode.dom)
651618
} else {
652-
for (var dom of domFor(vnode, {generation})) parent.removeChild(dom)
619+
for (var dom of domFor(vnode)) parent.removeChild(dom)
653620
}
654621
}
655622

@@ -676,7 +643,7 @@ module.exports = function() {
676643
}
677644
}
678645
function setAttr(vnode, key, old, value, ns) {
679-
if (key === "key" || key === "is" || value == null || isLifecycleMethod(key) || (old === value && !isFormAttribute(vnode, key)) && typeof value !== "object") return
646+
if (key === "key" || value == null || isLifecycleMethod(key) || (old === value && !isFormAttribute(vnode, key)) && typeof value !== "object") return
680647
if (key[0] === "o" && key[1] === "n") return updateEvent(vnode, key, value)
681648
if (key.slice(0, 6) === "xlink:") vnode.dom.setAttributeNS("http://www.w3.org/1999/xlink", key.slice(6), value)
682649
else if (key === "style") updateStyle(vnode.dom, old, value)
@@ -709,7 +676,7 @@ module.exports = function() {
709676
}
710677
}
711678
function removeAttr(vnode, key, old, ns) {
712-
if (key === "key" || key === "is" || old == null || isLifecycleMethod(key)) return
679+
if (key === "key" || old == null || isLifecycleMethod(key)) return
713680
if (key[0] === "o" && key[1] === "n") updateEvent(vnode, key, undefined)
714681
else if (key === "style") updateStyle(vnode.dom, old, null)
715682
else if (
@@ -743,22 +710,24 @@ module.exports = function() {
743710
if ("selectedIndex" in attrs) setAttr(vnode, "selectedIndex", null, attrs.selectedIndex, undefined)
744711
}
745712
function updateAttrs(vnode, old, attrs, ns) {
746-
if (old && old === attrs) {
747-
console.warn("Don't reuse attrs object, use new object for every redraw, this will throw in next major")
748-
}
749-
if (attrs != null) {
750-
for (var key in attrs) {
751-
setAttr(vnode, key, old && old[key], attrs[key], ns)
752-
}
753-
}
713+
// Some attributes may NOT be case-sensitive (e.g. data-***),
714+
// so removal should be done first to prevent accidental removal for newly setting values.
754715
var val
755716
if (old != null) {
717+
if (old === attrs) {
718+
console.warn("Don't reuse attrs object, use new object for every redraw, this will throw in next major")
719+
}
756720
for (var key in old) {
757721
if (((val = old[key]) != null) && (attrs == null || attrs[key] == null)) {
758722
removeAttr(vnode, key, val, ns)
759723
}
760724
}
761725
}
726+
if (attrs != null) {
727+
for (var key in attrs) {
728+
setAttr(vnode, key, old && old[key], attrs[key], ns)
729+
}
730+
}
762731
}
763732
function isFormAttribute(vnode, attr) {
764733
return attr === "value" || attr === "checked" || attr === "selectedIndex" || attr === "selected" && vnode.dom === activeElement(vnode.dom) || vnode.tag === "option" && vnode.dom.parentNode === activeElement(vnode.dom)
@@ -770,7 +739,7 @@ module.exports = function() {
770739
// Filter out namespaced keys
771740
return ns === undefined && (
772741
// If it's a custom element, just keep it.
773-
vnode.tag.indexOf("-") > -1 || vnode.attrs != null && vnode.attrs.is ||
742+
vnode.tag.indexOf("-") > -1 || vnode.is ||
774743
// If it's a normal element, let's try to avoid a few browser bugs.
775744
key !== "href" && key !== "list" && key !== "form" && key !== "width" && key !== "height"// && key !== "type"
776745
// Defer the property check until *after* we check everything.
@@ -789,7 +758,7 @@ module.exports = function() {
789758
element.style = style
790759
} else if (old == null || typeof old !== "object") {
791760
// `old` is missing or a string, `style` is an object.
792-
element.style.cssText = ""
761+
element.style = ""
793762
// Add new style properties
794763
for (var key in style) {
795764
var value = style[key]
@@ -800,6 +769,15 @@ module.exports = function() {
800769
}
801770
} else {
802771
// Both old & new are (different) objects.
772+
// Remove style properties that no longer exist
773+
// Style properties may have two cases(dash-case and camelCase),
774+
// so removal should be done first to prevent accidental removal for newly setting values.
775+
for (var key in old) {
776+
if (old[key] != null && style[key] == null) {
777+
if (key.includes("-")) element.style.removeProperty(key)
778+
else element.style[key] = ""
779+
}
780+
}
803781
// Update style properties that have changed
804782
for (var key in style) {
805783
var value = style[key]
@@ -808,13 +786,6 @@ module.exports = function() {
808786
else element.style[key] = value
809787
}
810788
}
811-
// Remove style properties that no longer exist
812-
for (var key in old) {
813-
if (old[key] != null && style[key] == null) {
814-
if (key.includes("-")) element.style.removeProperty(key)
815-
else element.style[key] = ""
816-
}
817-
}
818789
}
819790
}
820791

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
</head>
6+
<body>
7+
<p>This is a test for special case-handling of attribute and style properties. (#2988).</p>
8+
<p>Open your browser's Developer Console and follow these steps:</p>
9+
<ol>
10+
<li>Check the background color of the "foo" below.</li>
11+
<ul>
12+
<li>If it is light green, it is correct. The style has been updated properly.</li>
13+
<li>If it is red or yellow, the style has not been updated properly.</li>
14+
</ul>
15+
<li>Check the logs displayed in the console.</li>
16+
<ul>
17+
<li>If the attribute has been updated correctly, you should see the following message: "If you see this message, the update process is correct."</li>
18+
<li>If "null" is displayed, the attribute has not been updated properly.</li>
19+
</ul>
20+
</ol>
21+
22+
<div id="root" style="background-color: red;"></div>
23+
<script src="../../../mithril.js"></script>
24+
<script>
25+
// data-*** is NOT case-sensitive
26+
// style properties have two cases (camelCase and dash-case)
27+
var a = m("div#a", {"data-sampleId": "If you see this message, something is wrong.", style: {backgroundColor: "yellow"}}, "foo")
28+
var b = m("div#a", {"data-sampleid": "If you see this message, the update process is correct.", style: {"background-color": "lightgreen"}}, "foo")
29+
30+
// background color is yellow
31+
m.render(document.getElementById("root"), a)
32+
33+
// background color is lightgreen?
34+
m.render(document.getElementById("root"), b)
35+
36+
// data-sampleid is "If you see this message, the update process is correct."?
37+
console.log(document.querySelector("#a").getAttribute("data-sampleid"))
38+
</script>
39+
</body>
40+
</html>

render/tests/test-attributes.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ o.spec("attributes", function() {
8080
o(spies[0].callCount).equals(0)
8181
o(spies[2].callCount).equals(0)
8282
o(spies[3].calls).deepEquals([{this: spies[3].elem, args: ["custom", "x"]}])
83-
o(spies[4].calls).deepEquals([{this: spies[4].elem, args: ["custom", "x"]}])
84-
o(spies[5].calls).deepEquals([{this: spies[5].elem, args: ["custom", "x"]}])
83+
o(spies[4].calls).deepEquals([{this: spies[4].elem, args: ["is", "something-special"]}, {this: spies[4].elem, args: ["custom", "x"]}])
84+
o(spies[5].calls).deepEquals([{this: spies[5].elem, args: ["is", "something-special"]}, {this: spies[5].elem, args: ["custom", "x"]}])
8585
})
8686

8787
o("when vnode is customElement with property, custom setAttribute not called", function(){
@@ -124,8 +124,8 @@ o.spec("attributes", function() {
124124
o(spies[1].callCount).equals(0)
125125
o(spies[2].callCount).equals(0)
126126
o(spies[3].callCount).equals(0)
127-
o(spies[4].callCount).equals(0)
128-
o(spies[5].callCount).equals(0)
127+
o(spies[4].callCount).equals(1) // setAttribute("is", "something-special") is called
128+
o(spies[5].callCount).equals(1) // setAttribute("is", "something-special") is called
129129
o(getters[0].callCount).equals(0)
130130
o(getters[1].callCount).equals(0)
131131
o(getters[2].callCount).equals(0)

0 commit comments

Comments
 (0)