construct-style-sheets
construct-style-sheets copied to clipboard
Potential Memory Leak?
Hi @calebdwilliams!
Thanks for the important polyfill! Rather than a confirmed bug report, this is more of a question about a potential memory leak in the current implementation.
I have only done a cursory review of the codebase so please correct me if I'm wrong, but it seems the following is true:
- a
Location
object holds a strong reference to aDocument
orShadowRoot
-
$locations
inConstructedStyleSheet.ts
is aWeakMap
where keys are instances ofConstructedStyleSheet
and values are arrays ofLocation
instances -
addAdopterLocation
pushes aLocation
object (including its strong reference to aDocument
orShadowRoot
) to theLocation
array that is mapped in$locations
(such mapping from the key of the specificConstructedStyleSheet
adopted by the sameDocument
orShadowRoot
strongly referred to by the Location object being pushed to the array) -
addAdopterLocation
is called when a stylesheet is initially adopted (or when any 'adopted' style node is removed) -
removeAdopterLocation
is the only place that theLocation
objects (and their strong reference to aDocument
orShadowRoot
) are removed from the array that is mapped in$locations
-
removeAdopterLocation
is called when aConstructedStyleSheet
is removed from aDocument
orShadowRoot
'sadoptedStylesheets
but is not called in any other case
Based on the above, it seems that in the following scenario we would have a memory leak:
- Custom element instance 1 is constructed and a
ConstructedStyleSheet
is constructed and adopted by Custom element instance 1'sshadowRoot
- Custom Element instances 2 - 1,000,000 are constructed and added and then later removed from the DOM, where each of custom element 2 - 1,000,000's constructors cause their shadow roots to adopt the same
ConstructedStyleSheet
from step 1
As a WeapMap
's values are strongly held until the key is referred to only by the WeakMap
itself, if Custom element instance 1 is still strongly held in the DOM after instances 2 - 1,000,000 are no longer held in the DOM or referenced by anything but this polyfill, it would seem that this polyfill would cause instances 2 - 1,000,000's not to be garbage collected. This is because each of instances 2 - 1,000,000's shadow root will still be referenced by the Location
objects in the array that is the value of the $locations
entry with the key of the ConstructedStyleSheet
still adopted by custom element instance 1's shadow root. And because shadowRoot refers to the element it is attached to with the ShadowRoot.host
property, the element itself will not be garbage collected.
If this is the case, this would seem to be a serious issue as adopting a StyleSheet when a custom element is constructed is a major use case for adopted stylesheets and constructable stylesheets so that all instances of a custom element constructor can share a single set of styles. At the very least a warning not to use this polyfill with short lived custom elements may be in order.
Please forgive me if I've missed something or if any of this is unclear. Also, unfortunately it doesn't seem we can force garbage collection so a test case where memory is not being garbage collected doesn't seem to be proof of a memory leak so we seem to be forced to report potential issues with analysis like the above: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management