Skip to content

Commit 3c8c88e

Browse files
fix($compile): Resolve leak with asynchronous compilation
Stop an asynchronous compilation when this is performed on an already destroyed scope Closes angular#9199 Closes angular#9079 Closes angular#8504 Closes angular#9197
1 parent 12f4c1f commit 3c8c88e

File tree

3 files changed

+336
-6
lines changed

3 files changed

+336
-6
lines changed

src/ng/compile.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -976,11 +976,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
976976

977977
function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) {
978978

979-
var boundTranscludeFn = function(transcludedScope, cloneFn, controllers) {
979+
var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, containingScope) {
980980
var scopeCreated = false;
981981

982982
if (!transcludedScope) {
983-
transcludedScope = scope.$new();
983+
transcludedScope = scope.$new(false, containingScope);
984984
transcludedScope.$$transcluded = true;
985985
scopeCreated = true;
986986
}
@@ -1592,7 +1592,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15921592
transcludeControllers = elementControllers;
15931593
}
15941594

1595-
return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers);
1595+
return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, scopeToChild);
15961596
}
15971597
}
15981598
}
@@ -1754,6 +1754,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
17541754
boundTranscludeFn = linkQueue.shift(),
17551755
linkNode = $compileNode[0];
17561756

1757+
if (scope.$$destroyed) continue;
1758+
17571759
if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
17581760
var oldClasses = beforeTemplateLinkNode.className;
17591761

@@ -1784,6 +1786,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
17841786

17851787
return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
17861788
var childBoundTranscludeFn = boundTranscludeFn;
1789+
if (scope.$$destroyed) return;
17871790
if (linkQueue) {
17881791
linkQueue.push(scope);
17891792
linkQueue.push(node);

src/ng/rootScope.js

+21-3
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,17 @@ function $RootScopeProvider(){
182182
* When creating widgets, it is useful for the widget to not accidentally read parent
183183
* state.
184184
*
185+
* @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will contain this
186+
* the newly created scope. Defaults to `this` scope if not provided.
187+
* This is used to ensure that $destroy events are handled correctly.
188+
*
185189
* @returns {Object} The newly created child scope.
186190
*
187191
*/
188-
$new: function(isolate) {
189-
var ChildScope,
190-
child;
192+
$new: function(isolate, parent) {
193+
var child;
194+
195+
parent = parent || this;
191196

192197
if (isolate) {
193198
child = new Scope();
@@ -220,6 +225,19 @@ function $RootScopeProvider(){
220225
} else {
221226
this.$$childHead = this.$$childTail = child;
222227
}
228+
229+
// When the new scope is not isolated or we inherit from `this`, and
230+
// the parent scope is destroyed, the property `$$destroyed` is inherited
231+
// prototypically. In all other cases, this property needs to be set
232+
// when the parent scope is destroyed.
233+
// The listener needs to be added after the parent is set
234+
if (isolate || parent != this) child.$on('$destroy', destroyChild);
235+
236+
237+
function destroyChild() {
238+
child.$$destroyed = true;
239+
}
240+
223241
return child;
224242
},
225243

0 commit comments

Comments
 (0)