@@ -52,20 +52,39 @@ class SafeRenderManager extends Disposable {
5252 /// If not specified, a new div will be used.
5353 final Element mountNode;
5454
55+ final _contentRefObject = createRef <dynamic >();
56+
5557 /// The ref to the component rendered by [render] .
5658 ///
5759 /// Due to react_dom.render calls not being guaranteed to be synchronous.
5860 /// this may not be populated until later than expected.
59- dynamic contentRef;
61+ dynamic get contentRef => _contentRefObject.current;
62+
63+ // This setter only exists for backwards compatibility purposes.
64+ @Deprecated ('This ref should only be read, since it gets set by the rendered content' )
65+ set contentRef (dynamic value) => _contentRefObject.current = value;
6066
6167 _RenderState _state = _RenderState .unmounted;
6268
6369 /// A list of [render] calls queued up while the component is in the process
6470 /// of rendering.
6571 List <ReactElement > _renderQueue = [];
6672
67- SafeRenderManager ({Element mountNode, this .autoAttachMountNode = false })
68- : mountNode = mountNode ?? DivElement ();
73+ /// A ref to `this` , allowing us to create closures with references to this object that can be "revoked",
74+ /// so that any trees retained before React clears out old children https://github.com/facebook/react/issues/16087
75+ /// don't retain a reference to this object.
76+ ///
77+ /// In those cases, usually it's only SafeRenderManagerHelper/SafeRenderManager that get retained,
78+ /// potentially since SafeRenderManagerHelper's tryUnmountContent plus the actual unmount are enough for React to
79+ /// clear out old children.
80+ ///
81+ /// This is mostly in place to ease automated memory testing by preventing SafeRenderManager and its LeakFlags from
82+ /// being retained; any content retained by React will still be able to flag leaks.
83+ final _selfRef = createRef <SafeRenderManager >();
84+
85+ SafeRenderManager ({Element mountNode, this .autoAttachMountNode = false }) : mountNode = mountNode ?? DivElement () {
86+ _selfRef.current = this ;
87+ }
6988
7089 /// Renders [content] into [mountNode] , chaining existing callback refs to
7190 /// provide access to the rendered component via [contentRef] .
@@ -97,15 +116,19 @@ class SafeRenderManager extends Disposable {
97116 if (autoAttachMountNode && ! document.contains (mountNode)) {
98117 document.body.append (mountNode);
99118 }
119+ // Create a closure variable so we're not retaining a reference to `this` inside `ref`.
120+ final _selfRef = this ._selfRef;
100121 react_dom.render ((SafeRenderManagerHelper ()
101- ..ref = _helperRef
122+ ..ref = (ref) {
123+ _selfRef.current? ._helperRef (ref);
124+ }
102125 ..getInitialContent = () {
103126 final value = content;
104127 // Clear this closure variable out so it isn't retained.
105128 content = null ;
106129 return value;
107130 }
108- ..contentRef = _contentCallbackRef
131+ ..contentRef = _contentRefObject
109132 )(), mountNode);
110133 } catch (_) {
111134 _state = _RenderState .unmounted;
@@ -203,10 +226,6 @@ class SafeRenderManager extends Disposable {
203226 }
204227 }
205228
206- void _contentCallbackRef (ref) {
207- contentRef = ref;
208- }
209-
210229 @override
211230 Future <Null > onDispose () async {
212231 var completer = Completer <Null >();
@@ -232,6 +251,9 @@ class SafeRenderManager extends Disposable {
232251
233252 await completerFuture;
234253
254+ // Prevent any closures retaining `_selfRef` from retaining `this`.
255+ _selfRef.current = null ;
256+
235257 await super .onDispose ();
236258 }
237259}
0 commit comments