-
Notifications
You must be signed in to change notification settings - Fork 27.4k
refactor($compile): remove skipDestroyOnNextJQueryCleanData, fix data leak on some replaced nodes #12094
refactor($compile): remove skipDestroyOnNextJQueryCleanData, fix data leak on some replaced nodes #12094
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5844,7 +5844,9 @@ describe('$compile', function() { | |
|
||
testCleanup(); | ||
|
||
expect(cleanedCount).toBe(xs.length); | ||
// The ng-repeat template is removed/cleaned (the +1) | ||
// and each clone of the ng-repeat template is also removed (xs.length) | ||
expect(cleanedCount).toBe(xs.length + 1); | ||
|
||
// Restore the previous jQuery.cleanData. | ||
jQuery.cleanData = currentCleanData; | ||
|
@@ -8587,4 +8589,126 @@ describe('$compile', function() { | |
expect(element.hasClass('fire')).toBe(true); | ||
})); | ||
}); | ||
|
||
describe('element replacement', function() { | ||
it('should broadcast $destroy only on removed elements, not replaced', function() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does anyone know if this is actually intentional? Why is this done? It seems like the replaced node was never Should There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you try to remove the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removing I thought of another case though which I think might show that not invoking the DOM $destory is (sometimes) wrong... <div ng-form="myForm" lower-priority-replace-directive> In this case the However if that low priority directive was doing transclude instead of replace then maybe not invoking the DOM $destory is valid? Meaning There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting; just removing the This might be related, I totally forgot about this WIP PR: #8695 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was that PR ready or was there more to look into still? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm tempted to just drop this test for now. The cases when |
||
var linkCalls = []; | ||
var destroyCalls = []; | ||
|
||
module(function($compileProvider) { | ||
$compileProvider.directive('replace', function() { | ||
return { | ||
multiElement: true, | ||
replace: true, | ||
templateUrl: 'template123' | ||
}; | ||
}); | ||
|
||
$compileProvider.directive('foo', function() { | ||
return { | ||
priority: 1, // before the replace directive | ||
link: function($scope, $element, $attrs) { | ||
linkCalls.push($attrs.foo); | ||
$element.on('$destroy', function() { | ||
destroyCalls.push($attrs.foo); | ||
}); | ||
} | ||
}; | ||
}); | ||
}); | ||
|
||
inject(function($compile, $templateCache, $rootScope) { | ||
$templateCache.put('template123', '<p></p>'); | ||
|
||
$compile( | ||
'<div replace-start foo="1"><span foo="1.1"></span></div>' + | ||
'<div foo="2"><span foo="2.1"></span></div>' + | ||
'<div replace-end foo="3"><span foo="3.1"></span></div>' | ||
)($rootScope); | ||
|
||
expect(linkCalls).toEqual(['2', '3']); | ||
expect(destroyCalls).toEqual([]); | ||
$rootScope.$apply(); | ||
expect(linkCalls).toEqual(['2', '3', '1']); | ||
expect(destroyCalls).toEqual(['2', '3']); | ||
}); | ||
}); | ||
|
||
function getAll($root) { | ||
// check for .querySelectorAll to support comment nodes | ||
return [$root[0]].concat($root[0].querySelectorAll ? sliceArgs($root[0].querySelectorAll('*')) : []); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where can There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For comment nodes in the transclude-element case. I've added a comment. |
||
} | ||
|
||
function testCompileLinkDataCleanup(template) { | ||
inject(function($compile, $rootScope) { | ||
var toCompile = jqLite(template); | ||
|
||
var preCompiledChildren = getAll(toCompile); | ||
forEach(preCompiledChildren, function(element, i) { | ||
jqLite.data(element, 'foo', 'template#' + i); | ||
}); | ||
|
||
var linkedElements = $compile(toCompile)($rootScope); | ||
$rootScope.$apply(); | ||
linkedElements.remove(); | ||
|
||
forEach(preCompiledChildren, function(element, i) { | ||
expect(jqLite.hasData(element)).toBe(false, "template#" + i); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The second param becomes part of the error message if the |
||
}); | ||
forEach(getAll(linkedElements), function(element, i) { | ||
expect(jqLite.hasData(element)).toBe(false, "linked#" + i); | ||
}); | ||
}); | ||
} | ||
it('should clean data of element-transcluded link-cloned elements', function() { | ||
testCompileLinkDataCleanup('<div><div ng-repeat-start="i in [1,2]"><span></span></div><div ng-repeat-end></div></div>'); | ||
}); | ||
it('should clean data of element-transcluded elements', function() { | ||
testCompileLinkDataCleanup('<div ng-if-start="false"><span><span/></div><span></span><div ng-if-end><span></span></div>'); | ||
}); | ||
|
||
function testReplaceElementCleanup(dirOptions) { | ||
var template = '<div></div>'; | ||
module(function($compileProvider) { | ||
$compileProvider.directive('theDir', function() { | ||
return { | ||
multiElement: true, | ||
replace: dirOptions.replace, | ||
transclude: dirOptions.transclude, | ||
template: dirOptions.asyncTemplate ? undefined : template, | ||
templateUrl: dirOptions.asyncTemplate ? 'the-dir-template-url' : undefined | ||
}; | ||
}); | ||
}); | ||
inject(function($templateCache, $compile, $rootScope) { | ||
$templateCache.put('the-dir-template-url', template); | ||
|
||
testCompileLinkDataCleanup( | ||
'<div>' + | ||
'<div the-dir-start><span></span></div>' + | ||
'<div><span></span><span></span></div>' + | ||
'<div the-dir-end><span></span></div>' + | ||
'</div>' | ||
); | ||
}); | ||
} | ||
it('should clean data of elements removed for directive template', function() { | ||
testReplaceElementCleanup({}); | ||
}); | ||
it('should clean data of elements removed for directive templateUrl', function() { | ||
testReplaceElementCleanup({asyncTmeplate: true}); | ||
}); | ||
it('should clean data of elements transcluded into directive template', function() { | ||
testReplaceElementCleanup({transclude: true}); | ||
}); | ||
it('should clean data of elements transcluded into directive templateUrl', function() { | ||
testReplaceElementCleanup({transclude: true, asyncTmeplate: true}); | ||
}); | ||
it('should clean data of elements replaced with directive template', function() { | ||
testReplaceElementCleanup({replace: true}); | ||
}); | ||
it('should clean data of elements replaced with directive templateUrl', function() { | ||
testReplaceElementCleanup({replace: true, asyncTemplate: true}); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't delete the whole above comment. The following:
seems still relevant to the
.off('$destroy')
code.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if that comment is 100% correct.
.remove()
doesn't "deallocate [the] scope" it just fires a DOM $destroy event (not the scope $destroy event). It doesn't actually touch the scope...