Bug description
Every time I click Download as image on a chart, the entire Superset UI locks up for roughly 6 seconds (longer on complex charts like large tables or Deck.gl maps). The page becomes completely unresponsive during this time, I cannot interact with anything. Once the freeze passes, the image downloads normally.
The freeze is also synchronous. the browser does not show a spinner or loading state. The tab simply stops responding entirely mid-click, which is a distinct and more jarring experience than a normal async load.
What makes this feel like a bug rather than expected behaviour is that the freeze is exactly the same duration every single time I download the same chart. Whether it is my first download or my tenth, the UI hangs for the same amount of time. It never gets faster. On most web applications, repeated operations on the same data get faster because the browser or application caches the work it has already done. That is not happening here.
Steps to Reproduce:
1-Open any dashboard containing a chart with moderate/high visual complexity. a table chart with 50+ rows, a pivot table, or a bar/line chart with a legend and axis labels all reproduce this reliably. Simpler charts (e.g. a big number tile with a single value) may only produce a short freeze.
2-download the chart as an image
3-the entire browser tab freezes for 6 seconds. No spinner or loading indicator , the page is just completely unresponsive. Then the .jpg file downloads.
4-Without refreshing the page, click Download as image on the same chart again immediately.
5- the tab freezes for exactly the same duration as the first time. The second download is no faster than the first.
6-repeat n amount of times steps 2-4,The freeze duration is consistent on every single attempt, it never decreases.
expected behavior:
Subsequent downloads of the same chart in the same session should be significantly faster (ideally under 200ms) because the style information has already been calculated and should be reused. Most of the expensive/complex work is already done.
The UI should not completely freeze during the download. or at minimum, the freeze should be short enough to be imperceptible on repeated downloads.
This is not a slow network request. The freeze happens before any file is sent to the browser. it is the browser itself constructing the image from the live DOM. The download dialog appears only after the freeze ends, confirming the freeze is the image generation, not the file transfer.
The code that processes styles is supposed to remember the style measurements it has already taken for each DOM element, so it does not have to re-measure them on the next download. This caching mechanism is present in the code. it is just being cleared at the end of every download, meaning the next download starts from scratch with no memory of the previous one. The cache fills up, the image is generated, and then the cache is immediately thrown away before it can ever benefit a second download.
Proposed Fix:
I traced through superset-frontend/src/utils/downloadAsImage.tsx to find where the cache is being cleared. The file is around 323 lines and the issue is a single line (256) inside the createEnhancedClone function's cleanup callback:
const cleanup = () => {
styleCache.delete?.(originalElement); // ← this line is the problem
if (tempContainer.parentElement) {
tempContainer.parentElement.removeChild(tempContainer);
}
};
styleCache is a WeakMap declared at module scope on line 85. It is meant to persist across downloads for the lifetime of the page. that is the whole point of it being module-scoped rather than created fresh inside each download call. Calling ".delete()" on it here wipes out the entry for originalElement immediately after every download completes, so the next download finds an empty cache and has to redo all the getComputedStyle work from scratch.
The fix is to remove that one line. as Weakmap is designed to release its entries automatically once the DOM element they are keyed on is garbage-collected by the browser. Removing entries by hand while the element is still live and mounted in the DOM defeats the cache with no benefit.
The cleanup function still needs to remove the temporary off-screen container it created (the tempContainer lines below), so those stay. Only the styleCache.delete line is removed:
const cleanup = () => {
if (tempContainer.parentElement) {
tempContainer.parentElement.removeChild(tempContainer);
}
};
Screenshots/recordings
No response
Superset version
master / latest-dev
Python version
3.9
Node version
16
Browser
Chrome
Additional context
No response
Checklist
Bug description
Every time I click Download as image on a chart, the entire Superset UI locks up for roughly 6 seconds (longer on complex charts like large tables or Deck.gl maps). The page becomes completely unresponsive during this time, I cannot interact with anything. Once the freeze passes, the image downloads normally.
The freeze is also synchronous. the browser does not show a spinner or loading state. The tab simply stops responding entirely mid-click, which is a distinct and more jarring experience than a normal async load.
What makes this feel like a bug rather than expected behaviour is that the freeze is exactly the same duration every single time I download the same chart. Whether it is my first download or my tenth, the UI hangs for the same amount of time. It never gets faster. On most web applications, repeated operations on the same data get faster because the browser or application caches the work it has already done. That is not happening here.
Steps to Reproduce:
1-Open any dashboard containing a chart with moderate/high visual complexity. a table chart with 50+ rows, a pivot table, or a bar/line chart with a legend and axis labels all reproduce this reliably. Simpler charts (e.g. a big number tile with a single value) may only produce a short freeze.
2-download the chart as an image
3-the entire browser tab freezes for 6 seconds. No spinner or loading indicator , the page is just completely unresponsive. Then the .jpg file downloads.
4-Without refreshing the page, click Download as image on the same chart again immediately.
5- the tab freezes for exactly the same duration as the first time. The second download is no faster than the first.
6-repeat n amount of times steps 2-4,The freeze duration is consistent on every single attempt, it never decreases.
expected behavior:
Subsequent downloads of the same chart in the same session should be significantly faster (ideally under 200ms) because the style information has already been calculated and should be reused. Most of the expensive/complex work is already done.
The UI should not completely freeze during the download. or at minimum, the freeze should be short enough to be imperceptible on repeated downloads.
This is not a slow network request. The freeze happens before any file is sent to the browser. it is the browser itself constructing the image from the live DOM. The download dialog appears only after the freeze ends, confirming the freeze is the image generation, not the file transfer.
The code that processes styles is supposed to remember the style measurements it has already taken for each DOM element, so it does not have to re-measure them on the next download. This caching mechanism is present in the code. it is just being cleared at the end of every download, meaning the next download starts from scratch with no memory of the previous one. The cache fills up, the image is generated, and then the cache is immediately thrown away before it can ever benefit a second download.
Proposed Fix:
I traced through superset-frontend/src/utils/downloadAsImage.tsx to find where the cache is being cleared. The file is around 323 lines and the issue is a single line (256) inside the createEnhancedClone function's cleanup callback:
const cleanup = () => {
styleCache.delete?.(originalElement); // ← this line is the problem
if (tempContainer.parentElement) {
tempContainer.parentElement.removeChild(tempContainer);
}
};
styleCache is a WeakMap declared at module scope on line 85. It is meant to persist across downloads for the lifetime of the page. that is the whole point of it being module-scoped rather than created fresh inside each download call. Calling ".delete()" on it here wipes out the entry for originalElement immediately after every download completes, so the next download finds an empty cache and has to redo all the getComputedStyle work from scratch.
The fix is to remove that one line. as Weakmap is designed to release its entries automatically once the DOM element they are keyed on is garbage-collected by the browser. Removing entries by hand while the element is still live and mounted in the DOM defeats the cache with no benefit.
The cleanup function still needs to remove the temporary off-screen container it created (the tempContainer lines below), so those stay. Only the styleCache.delete line is removed:
const cleanup = () => {
if (tempContainer.parentElement) {
tempContainer.parentElement.removeChild(tempContainer);
}
};
Screenshots/recordings
No response
Superset version
master / latest-dev
Python version
3.9
Node version
16
Browser
Chrome
Additional context
No response
Checklist