Skip to content

Commit 5843ad0

Browse files
authored
Provide solution to long standing memleak (#4853)
* Provide solution to long standing memleak * Experiment w/ perf
1 parent f3430e0 commit 5843ad0

File tree

4 files changed

+39
-7
lines changed

4 files changed

+39
-7
lines changed

compat/src/suspense.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { Component, createElement, options, Fragment } from 'preact';
2-
import { MODE_HYDRATE, COMPONENT_FORCE } from '../../src/constants';
2+
import {
3+
MODE_HYDRATE,
4+
FORCE_PROPS_REVALIDATE,
5+
COMPONENT_FORCE
6+
} from '../../src/constants';
37
import { assign } from './util';
48

59
const oldCatchError = options._catchError;
@@ -67,6 +71,10 @@ function detachedClone(vnode, detachedParent, parentDom) {
6771

6872
function removeOriginal(vnode, detachedParent, originalParent) {
6973
if (vnode && originalParent) {
74+
if (typeof vnode.type == 'string') {
75+
vnode._flags |= FORCE_PROPS_REVALIDATE;
76+
}
77+
7078
vnode._original = null;
7179
vnode._children =
7280
vnode._children &&

compat/test/browser/suspense-hydration.test.js

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,14 @@ describe('suspense hydration', () => {
656656
rerender();
657657

658658
expect(scratch.innerHTML, 'second suspend').to.equal(div('fallback'));
659+
expect(getLog()).to.deep.equal([
660+
'<div>b1.remove()',
661+
'<div>a.remove()',
662+
'<div>c.remove()',
663+
'<div>.appendChild(#text)',
664+
'<div>.appendChild(<div>fallback)'
665+
]);
666+
clearLog();
659667

660668
return resolve2(() => <div onClick={bOnClickSpy}>b2</div>);
661669
})
@@ -664,12 +672,20 @@ describe('suspense hydration', () => {
664672
expect(scratch.innerHTML, 'second suspend resumes').to.equal(
665673
[div('a'), div('b2'), div('c')].join('')
666674
);
667-
668-
scratch.lastChild.dispatchEvent(createEvent('click'));
669-
expect(cOnClickSpy).toHaveBeenCalledTimes(2);
675+
expect(getLog()).to.deep.equal([
676+
'<div>fallback.appendChild(<div>a)',
677+
'<div>fallback.remove()',
678+
'<div>a.appendChild(<div>a)',
679+
'<div>.appendChild(#text)',
680+
'<div>a.appendChild(<div>b2)',
681+
'<div>ab2.appendChild(<div>c)'
682+
]);
670683

671684
scratch.firstChild.nextSibling.dispatchEvent(createEvent('click'));
672685
expect(bOnClickSpy).toHaveBeenCalledTimes(2);
686+
687+
scratch.lastChild.dispatchEvent(createEvent('click'));
688+
expect(cOnClickSpy).toHaveBeenCalledTimes(2);
673689
});
674690
});
675691

src/constants.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ export const MODE_SUSPENDED = 1 << 7;
77
export const INSERT_VNODE = 1 << 2;
88
/** Indicates a VNode has been matched with another VNode in the diff */
99
export const MATCHED = 1 << 1;
10+
/** Indicates that this vnode has been unmounted before indicating the loss of event listeners */
11+
export const FORCE_PROPS_REVALIDATE = 1 << 0;
1012

1113
// component._bits
1214
/** Component is processing an exception */

src/diff/index.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
COMPONENT_PENDING_ERROR,
55
COMPONENT_PROCESSING_EXCEPTION,
66
EMPTY_OBJ,
7+
FORCE_PROPS_REVALIDATE,
78
MATH_NAMESPACE,
89
MODE_HYDRATE,
910
MODE_SUSPENDED,
@@ -388,7 +389,7 @@ export function diff(
388389

389390
if ((tmp = options.diffed)) tmp(newVNode);
390391

391-
return newVNode._flags & MODE_SUSPENDED ? undefined : oldDom;
392+
return newVNode._flags & MODE_SUSPENDED ? UNDEFINED : oldDom;
392393
}
393394

394395
function markAsForce(vnode) {
@@ -566,6 +567,7 @@ function diffElementNodes(
566567

567568
// During hydration, props are not diffed at all (including dangerouslySetInnerHTML)
568569
// @TODO we should warn in debug mode when props don't match here.
570+
const shouldRevalidateProps = oldVNode._flags & FORCE_PROPS_REVALIDATE;
569571
for (i in newProps) {
570572
value = newProps[i];
571573
if (i == 'children') {
@@ -578,7 +580,7 @@ function diffElementNodes(
578580
checked = value;
579581
} else if (
580582
(!isHydrating || typeof value == 'function') &&
581-
oldProps[i] !== value
583+
(oldProps[i] !== value || shouldRevalidateProps)
582584
) {
583585
setProperty(dom, i, value, oldProps[i], namespace);
584586
}
@@ -723,7 +725,11 @@ export function unmount(vnode, parentVNode, skipRemove) {
723725
removeNode(vnode._dom);
724726
}
725727

726-
vnode._component = vnode._parent = vnode._dom = UNDEFINED;
728+
if (vnode._dom && vnode._dom._listeners) {
729+
vnode._dom._listeners = NULL;
730+
}
731+
732+
vnode._dom = vnode._component = vnode._parent = NULL;
727733
}
728734

729735
/** The `.render()` method for a PFC backing instance. */

0 commit comments

Comments
 (0)