Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

Commit b7f175b

Browse files
committed
fix(element binder): fix memory leak with expando value holding onto node
Expando keys are weak references but the values are not. The ElementProbe value holds strong references to the node and the injector keep it from being garbage collected.
1 parent 317c23c commit b7f175b

File tree

3 files changed

+36
-3
lines changed

3 files changed

+36
-3
lines changed

lib/core_dom/element_binder.dart

+1
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ class ElementBinder {
319319
nodeInjector = parentInjector.createChild([nodeModule]);
320320
probe = _expando[node] = new ElementProbe(
321321
parentInjector.getByKey(_ELEMENT_PROBE_KEY), node, nodeInjector, scope);
322+
scope.on(ScopeEvent.DESTROY).listen((_) {_expando[node] = null;});
322323

323324
_link(nodeInjector, probe, scope, nodeAttrs, formatters);
324325

lib/core_dom/shadow_dom_component_factory.dart

+3-2
Original file line numberDiff line numberDiff line change
@@ -179,17 +179,18 @@ class _ComponentFactory implements Function {
179179

180180
Injector createShadowInjector(injector, TemplateLoader templateLoader) {
181181
var probe;
182-
var shadowModule = new Module()
182+
var shadowModule = new Module()
183183
..bindByKey(typeKey)
184184
..bindByKey(_NG_ELEMENT_KEY)
185185
..bindByKey(_EVENT_HANDLER_KEY, toImplementation: ShadowRootEventHandler)
186186
..bindByKey(_SCOPE_KEY, toValue: shadowScope)
187187
..bindByKey(_TEMPLATE_LOADER_KEY, toValue: templateLoader)
188188
..bindByKey(_SHADOW_ROOT_KEY, toValue: shadowDom)
189189
..bindByKey(_ELEMENT_PROBE, toFactory: (_) => probe);
190-
shadowInjector = injector.createChild([shadowModule], name: SHADOW_DOM_INJECTOR_NAME);
190+
shadowInjector = injector.createChild([shadowModule], name: SHADOW_DOM_INJECTOR_NAME);
191191
probe = _expando[shadowDom] = new ElementProbe(
192192
injector.getByKey(_ELEMENT_PROBE), shadowDom, shadowInjector, shadowScope);
193+
shadowScope.on(ScopeEvent.DESTROY).listen((ScopeEvent) {_expando[shadowDom] = null;});
193194
return shadowInjector;
194195
}
195196
}

test/core_dom/compiler_spec.dart

+32-1
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,28 @@ void main() {
727727
expect(_.rootElement.shadowRoot).toBeNotNull();
728728
}
729729
}));
730+
731+
describe('expando memory', () {
732+
Expando expando;
733+
734+
beforeEach(inject((Expando _expando) => expando = _expando));
735+
736+
['shadowy', 'shadowless'].forEach((selector) {
737+
it('should release expando when a node is freed ($selector)', async(() {
738+
_.rootScope.context['flag'] = true;
739+
_.compile('<div><div ng-if=flag><$selector>x</$selector></div></div>');
740+
microLeap(); _.rootScope.apply();
741+
var element = _.rootElement.querySelector('$selector');
742+
if (element.shadowRoot != null) {
743+
element = element.shadowRoot;
744+
}
745+
expect(expando[element]).not.toEqual(null);
746+
_.rootScope.context['flag'] = false;
747+
microLeap(); _.rootScope.apply();
748+
expect(expando[element]).toEqual(null);
749+
}));
750+
});
751+
});
730752
});
731753

732754
describe('bindings', () {
@@ -849,7 +871,6 @@ void main() {
849871
expect(_.rootElement.text).toEqual('MyController');
850872
});
851873
});
852-
853874
}));
854875
}
855876

@@ -975,6 +996,16 @@ class SimpleComponent {
975996
}
976997
}
977998

999+
@Component(
1000+
selector: 'simple2',
1001+
template: r'{{name}}(<content>SHADOW-CONTENT</content>)')
1002+
class Simple2Component {
1003+
Scope scope;
1004+
Simple2Component(Scope this.scope) {
1005+
scope.context['name'] = 'INNER';
1006+
}
1007+
}
1008+
9781009
@Component(
9791010
selector: 'shadowy',
9801011
template: r'With shadow DOM',

0 commit comments

Comments
 (0)