Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 7a29630

Browse files
committed
fix($compile): Resolve leak with asynchronous compilation
Stop an asynchronous compilation when this is performed on an already destroyed scope Closes #9199
1 parent 67e9d02 commit 7a29630

File tree

2 files changed

+55
-0
lines changed

2 files changed

+55
-0
lines changed

src/ng/compile.js

+3
Original file line numberDiff line numberDiff line change
@@ -2011,6 +2011,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
20112011
boundTranscludeFn = linkQueue.shift(),
20122012
linkNode = $compileNode[0];
20132013

2014+
if (scope.$$destroyed) continue;
2015+
20142016
if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
20152017
var oldClasses = beforeTemplateLinkNode.className;
20162018

@@ -2037,6 +2039,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
20372039

20382040
return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
20392041
var childBoundTranscludeFn = boundTranscludeFn;
2042+
if (scope.$$destroyed) return;
20402043
if (linkQueue) {
20412044
linkQueue.push(scope);
20422045
linkQueue.push(node);

test/ng/compileSpec.js

+52
Original file line numberDiff line numberDiff line change
@@ -4537,6 +4537,58 @@ describe('$compile', function() {
45374537
});
45384538
});
45394539

4540+
it('should not leak when continuing the compilation of elements on a scope that was destroyed', function() {
4541+
if (jQuery) {
4542+
// jQuery 2.x doesn't expose the cache storage.
4543+
return;
4544+
}
4545+
4546+
var linkFn = jasmine.createSpy('linkFn');
4547+
4548+
module(function($controllerProvider, $compileProvider) {
4549+
$controllerProvider.register('Leak', function ($scope, $timeout) {
4550+
$scope.code = 'red';
4551+
$timeout(function () {
4552+
$scope.code = 'blue';
4553+
});
4554+
});
4555+
$compileProvider.directive('red', function() {
4556+
return {
4557+
restrict: 'A',
4558+
templateUrl: 'red.html',
4559+
scope: {},
4560+
link: linkFn
4561+
};
4562+
});
4563+
});
4564+
4565+
inject(function($compile, $rootScope, $httpBackend, $timeout, $templateCache) {
4566+
$httpBackend.whenGET('red.html').respond('<p>red.html</p>');
4567+
var template = $compile(
4568+
'<div ng-controller="Leak">' +
4569+
'<div ng-switch="code">' +
4570+
'<div ng-switch-when="red">' +
4571+
'<div red></div>' +
4572+
'</div>' +
4573+
'</div>' +
4574+
'</div>');
4575+
element = template($rootScope);
4576+
$rootScope.$digest();
4577+
$timeout.flush();
4578+
$httpBackend.flush();
4579+
expect(linkFn).not.toHaveBeenCalled();
4580+
expect(jqLiteCacheSize()).toEqual(2);
4581+
4582+
$templateCache.removeAll();
4583+
var destroyedScope = $rootScope.$new();
4584+
destroyedScope.$destroy();
4585+
var clone = template(destroyedScope);
4586+
$rootScope.$digest();
4587+
$timeout.flush();
4588+
expect(linkFn).not.toHaveBeenCalled();
4589+
});
4590+
});
4591+
45404592
if (jQuery) {
45414593
describe('cleaning up after a replaced element', function () {
45424594
var $compile, xs;

0 commit comments

Comments
 (0)