diff --git a/src/ng/compile.js b/src/ng/compile.js index f5b0d450edea..74c14752a319 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -1842,12 +1842,22 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, previousCompileContext) { var linkFns = [], + // `nodeList` can be either an element's `.childNodes` (live NodeList) + // or a jqLite/jQuery collection or an array + notLiveList = isArray(nodeList) || (nodeList instanceof jqLite), attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound; + for (var i = 0; i < nodeList.length; i++) { attrs = new Attributes(); - // we must always refer to nodeList[i] since the nodes can be replaced underneath us. + // Workaround for #11781 and #14924 + if (msie === 11) { + mergeConsecutiveTextNodes(nodeList, i, notLiveList); + } + + // We must always refer to `nodeList[i]` hereafter, + // since the nodes can be replaced underneath us. directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, ignoreDirective); @@ -1938,6 +1948,32 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } } + function mergeConsecutiveTextNodes(nodeList, idx, notLiveList) { + var node = nodeList[idx]; + var parent = node.parentNode; + var sibling; + + if (node.nodeType !== NODE_TYPE_TEXT) { + return; + } + + while (true) { + sibling = parent ? node.nextSibling : nodeList[idx + 1]; + if (!sibling || sibling.nodeType !== NODE_TYPE_TEXT) { + break; + } + + node.nodeValue = node.nodeValue + sibling.nodeValue; + + if (sibling.parentNode) { + sibling.parentNode.removeChild(sibling); + } + if (notLiveList && sibling === nodeList[idx + 1]) { + nodeList.splice(idx + 1, 1); + } + } + } + function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { function boundTranscludeFn(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) { @@ -2046,13 +2082,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } break; case NODE_TYPE_TEXT: /* Text Node */ - if (msie === 11) { - // Workaround for #11781 - while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === NODE_TYPE_TEXT) { - node.nodeValue = node.nodeValue + node.nextSibling.nodeValue; - node.parentNode.removeChild(node.nextSibling); - } - } addTextInterpolateDirective(directives, node.nodeValue); break; case NODE_TYPE_COMMENT: /* Comment */ @@ -2325,9 +2354,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { var slots = createMap(); - $template = jqLite(jqLiteClone(compileNode)).contents(); - - if (isObject(directiveValue)) { + if (!isObject(directiveValue)) { + $template = jqLite(jqLiteClone(compileNode)).contents(); + } else { // We have transclusion slots, // collect them up, compile them and store their transclusion functions diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index bfed32aed046..77cfca8f8ff1 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -3260,6 +3260,26 @@ describe('$compile', function() { })); + it('should not process text nodes merged into their sibling', inject(function($compile, $rootScope) { + var div = document.createElement('div'); + div.appendChild(document.createTextNode('1{{ value }}')); + div.appendChild(document.createTextNode('2{{ value }}')); + div.appendChild(document.createTextNode('3{{ value }}')); + + element = jqLite(div.childNodes); + + var initialWatcherCount = $rootScope.$countWatchers(); + $compile(element)($rootScope); + $rootScope.$apply('value = 0'); + var newWatcherCount = $rootScope.$countWatchers() - initialWatcherCount; + + expect(element.text()).toBe('102030'); + expect(newWatcherCount).toBe(3); + + dealoc(div); + })); + + it('should support custom start/end interpolation symbols in template and directive template', function() { module(function($interpolateProvider, $compileProvider) { @@ -8530,10 +8550,38 @@ describe('$compile', function() { element = $compile('
')($rootScope); expect(capturedChildCtrl).toBeTruthy(); }); - }); + // See issue https://github.com/angular/angular.js/issues/14924 + it('should not process top-level transcluded text nodes merged into their sibling', + function() { + module(function() { + directive('transclude', valueFn({ + template: '', + transclude: true, + scope: {} + })); + }); + + inject(function($compile) { + element = jqLite('
'); + element[0].appendChild(document.createTextNode('1{{ value }}')); + element[0].appendChild(document.createTextNode('2{{ value }}')); + element[0].appendChild(document.createTextNode('3{{ value }}')); + + var initialWatcherCount = $rootScope.$countWatchers(); + $compile(element)($rootScope); + $rootScope.$apply('value = 0'); + var newWatcherCount = $rootScope.$countWatchers() - initialWatcherCount; + + expect(element.text()).toBe('102030'); + expect(newWatcherCount).toBe(3); + }); + } + ); + + // see issue https://github.com/angular/angular.js/issues/9413 describe('passing a parent bound transclude function to the link ' + 'function returned from `$compile`', function() { @@ -9996,6 +10044,39 @@ describe('$compile', function() { expect(element.children().eq(2).text()).toEqual('dorothy'); }); }); + + + // See issue https://github.com/angular/angular.js/issues/14924 + it('should not process top-level transcluded text nodes merged into their sibling', + function() { + module(function() { + directive('transclude', valueFn({ + template: '', + transclude: {}, + scope: {} + })); + }); + + inject(function($compile) { + element = jqLite('
'); + element[0].appendChild(document.createTextNode('1{{ value }}')); + element[0].appendChild(document.createTextNode('2{{ value }}')); + element[0].appendChild(document.createTextNode('3{{ value }}')); + + var initialWatcherCount = $rootScope.$countWatchers(); + $compile(element)($rootScope); + $rootScope.$apply('value = 0'); + var newWatcherCount = $rootScope.$countWatchers() - initialWatcherCount; + + expect(element.text()).toBe('102030'); + expect(newWatcherCount).toBe(3); + + if (msie === 11) { + expect(element.find('ng-transclude').contents().length).toBe(1); + } + }); + } + ); });