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

Commit 6303c3d

Browse files
lgalfasopetebacondarwin
authored andcommitted
fix($compile): Resolve leak with asynchronous compilation
Stop an asynchronous compilation when this is performed on an already destroyed scope Closes #9199 Closes #9079 Closes #8504 Closes #9197
1 parent cd2cfaf commit 6303c3d

File tree

3 files changed

+67
-1
lines changed

3 files changed

+67
-1
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);

src/ng/rootScope.js

+4
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,10 @@ function $RootScopeProvider(){
223223
parent.$$childHead = parent.$$childTail = child;
224224
}
225225

226+
// When the new scope is not isolated or we inherit from `this`, and
227+
// the parent scope is destroyed, the property `$$destroyed` is inherited
228+
// prototypically. In all other cases, this property needs to be set
229+
// when the parent scope is destroyed.
226230
// The listener needs to be added after the parent is set
227231
if (isolate || parent != this) child.$on('$destroy', destroyChild);
228232

test/ng/compileSpec.js

+60-1
Original file line numberDiff line numberDiff line change
@@ -4537,6 +4537,65 @@ 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('isolateRed', function() {
4556+
return {
4557+
restrict: 'A',
4558+
scope: {},
4559+
template: '<div red></div>'
4560+
};
4561+
});
4562+
$compileProvider.directive('red', function() {
4563+
return {
4564+
restrict: 'A',
4565+
templateUrl: 'red.html',
4566+
scope: {},
4567+
link: linkFn
4568+
};
4569+
});
4570+
});
4571+
4572+
inject(function($compile, $rootScope, $httpBackend, $timeout, $templateCache) {
4573+
$httpBackend.whenGET('red.html').respond('<p>red.html</p>');
4574+
var template = $compile(
4575+
'<div ng-controller="Leak">' +
4576+
'<div ng-switch="code">' +
4577+
'<div ng-switch-when="red">' +
4578+
'<div isolate-red></div>' +
4579+
'</div>' +
4580+
'</div>' +
4581+
'</div>');
4582+
element = template($rootScope);
4583+
$rootScope.$digest();
4584+
$timeout.flush();
4585+
$httpBackend.flush();
4586+
expect(linkFn).not.toHaveBeenCalled();
4587+
expect(jqLiteCacheSize()).toEqual(2);
4588+
4589+
$templateCache.removeAll();
4590+
var destroyedScope = $rootScope.$new();
4591+
destroyedScope.$destroy();
4592+
var clone = template(destroyedScope);
4593+
$rootScope.$digest();
4594+
$timeout.flush();
4595+
expect(linkFn).not.toHaveBeenCalled();
4596+
});
4597+
});
4598+
45404599
if (jQuery) {
45414600
describe('cleaning up after a replaced element', function () {
45424601
var $compile, xs;
@@ -5117,7 +5176,7 @@ describe('$compile', function() {
51175176
expect(countScopes($rootScope)).toEqual(1);
51185177
}));
51195178

5120-
it('should destroy mark as destroyed all sub scopes of the scope being destroyed',
5179+
it('should mark as destroyed all sub scopes of the scope being destroyed',
51215180
inject(function($compile, $rootScope) {
51225181

51235182
element = $compile(

0 commit comments

Comments
 (0)