Chart's ResizeObserver is never disconnected, leaking memory after destroy
Expected behaviour
The ResizeObserver created in Chart#setReflow should be disconnected when the chart is destroyed, the same way the legacy window.resize fallback in the same method unbinds its listener on the destroy event.
Actual behaviour
The observer is never disconnected, and no reference to it is kept, so there is no way to disconnect it from the outside either.
The observer is never disconnected, and no reference to it is kept:
// ts/Core/Chart/Chart.ts, Chart#setReflow
if (typeof ResizeObserver === 'function') {
(new ResizeObserver(runReflow)).observe(chart.renderTo);
// Fallback for more legacy browser versions.
} else {
const unbind = addEvent(win, 'resize', runReflow);
addEvent(this, 'destroy', unbind);
}It holds chart.renderTo and the runReflow closure, which holds the chart. So every chart.destroy() leaves behind an observer, a closure and the gutted Chart, for as long as the container stays reachable, the normal case when the container is reused. Each leaked observer also keeps running runReflow on every container resize.
Reported from production as an out of memory crash in Chromium after 3 days to 3 months, depending on the chart update frequency.
Live demo with steps to reproduce
https://jsfiddle.net/BlackLabel/bezh4yua/
Expected: 0. Actual: 50.
Product version
Highcharts Core 13.0.2
Affected browser(s)
all
Possible fix: keep a reference to the observer and disconnect it on destroy, mirroring the fallback branch:
const observer = new ResizeObserver(runReflow);
observer.observe(chart.renderTo);
addEvent(this, 'destroy', () => observer.disconnect());Related: #22636 touches the same branch of setReflow, but is a separate request.
Source: highcharts/highcharts