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

Commit 64d0518

Browse files
committed
fix(ngAnimate): ensure that all jqLite elements are deconstructed properly
Prior to this fix if a form DOM element was fed into parts of the ngAnimate queuing code it would attempt to detect if it is a jqLite object in an unstable way which would allow a form element to return an inner input element by index. This patch ensures that jqLite instances are properly detected using a helper method. Closes #11658
1 parent b5a9053 commit 64d0518

File tree

7 files changed

+65
-36
lines changed

7 files changed

+65
-36
lines changed

src/ngAnimate/.jshintrc

+2-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"packageStyles": false,
3737
"removeFromArray": false,
3838
"stripCommentsFromElement": false,
39-
"extractElementNode": false
39+
"extractElementNode": false,
40+
"getDomNode": false
4041
}
4142
}

src/ngAnimate/animateCss.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
472472
return stagger || {};
473473
}
474474

475-
var bod = $document[0].body;
475+
var bod = getDomNode($document).body;
476476
var cancelLastRAFRequest;
477477
var rafWaitQueue = [];
478478
function waitUntilQuiet(callback) {
@@ -521,7 +521,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
521521
}
522522

523523
function init(element, options) {
524-
var node = element[0];
524+
var node = getDomNode(element);
525525
options = prepareAnimationOptions(options);
526526

527527
var temporaryStyles = [];

src/ngAnimate/animateCssDriver.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
1616
// only browsers that support these properties can render animations
1717
if (!$sniffer.animations && !$sniffer.transitions) return noop;
1818

19-
var bodyNode = $document[0].body;
20-
var rootNode = $rootElement[0];
19+
var bodyNode = getDomNode($document).body;
20+
var rootNode = getDomNode($rootElement);
2121

2222
var rootBodyElement = jqLite(bodyNode.parentNode === rootNode ? bodyNode : rootNode);
2323

@@ -44,7 +44,7 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
4444
}
4545

4646
function prepareAnchoredAnimation(classes, outAnchor, inAnchor) {
47-
var clone = jqLite(outAnchor[0].cloneNode(true));
47+
var clone = jqLite(getDomNode(outAnchor).cloneNode(true));
4848
var startingClasses = filterCssClasses(clone.attr('class') || '');
4949
var anchorClasses = pendClasses(classes, NG_ANIMATE_ANCHOR_SUFFIX);
5050

@@ -113,7 +113,7 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
113113
function calculateAnchorStyles(anchor) {
114114
var styles = {};
115115

116-
var coords = anchor[0].getBoundingClientRect();
116+
var coords = getDomNode(anchor).getBoundingClientRect();
117117

118118
// we iterate directly since safari messes up and doesn't return
119119
// all the keys for the coods object when iterated

src/ngAnimate/animateQueue.js

+26-28
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
117117
}
118118

119119
function findCallbacks(element, event) {
120-
var targetNode = element[0];
120+
var targetNode = getDomNode(element);
121121

122122
var matches = [];
123123
var entries = callbackRegistry[event];
@@ -198,7 +198,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
198198
// (bool) - Global setter
199199
bool = animationsEnabled = !!element;
200200
} else {
201-
var node = element.length ? element[0] : element;
201+
var node = getDomNode(element);
202202
var recordExists = disabledElementsLookup.get(node);
203203

204204
if (argCount === 1) {
@@ -224,7 +224,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
224224
var node, parent;
225225
element = stripCommentsFromElement(element);
226226
if (element) {
227-
node = element[0];
227+
node = getDomNode(element);
228228
parent = element.parent();
229229
}
230230

@@ -411,7 +411,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
411411
close(!status);
412412
var animationDetails = activeAnimationsLookup.get(node);
413413
if (animationDetails && animationDetails.counter === counter) {
414-
clearElementAnimationState(element);
414+
clearElementAnimationState(getDomNode(element));
415415
}
416416
notifyProgress(runner, event, 'close', {});
417417
});
@@ -438,7 +438,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
438438
}
439439

440440
function closeChildAnimations(element) {
441-
var node = element[0];
441+
var node = getDomNode(element);
442442
var children = node.querySelectorAll('[' + NG_ANIMATE_ATTR_NAME + ']');
443443
forEach(children, function(child) {
444444
var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME));
@@ -457,19 +457,17 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
457457
}
458458

459459
function clearElementAnimationState(element) {
460-
element = element.length ? element[0] : element;
461-
element.removeAttribute(NG_ANIMATE_ATTR_NAME);
462-
activeAnimationsLookup.remove(element);
460+
var node = getDomNode(element);
461+
node.removeAttribute(NG_ANIMATE_ATTR_NAME);
462+
activeAnimationsLookup.remove(node);
463463
}
464464

465-
function isMatchingElement(a,b) {
466-
a = a.length ? a[0] : a;
467-
b = b.length ? b[0] : b;
468-
return a === b;
465+
function isMatchingElement(nodeOrElmA, nodeOrElmB) {
466+
return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB);
469467
}
470468

471469
function closeParentClassBasedAnimations(startingElement) {
472-
var parentNode = startingElement[0];
470+
var parentNode = getDomNode(startingElement);
473471
do {
474472
if (!parentNode || parentNode.nodeType !== ELEMENT_NODE) break;
475473

@@ -495,25 +493,25 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
495493
}
496494
}
497495

498-
function areAnimationsAllowed(element, parent, event) {
496+
function areAnimationsAllowed(element, parentElement, event) {
499497
var bodyElementDetected = false;
500498
var rootElementDetected = false;
501499
var parentAnimationDetected = false;
502500
var animateChildren;
503501

504502
var parentHost = element.data(NG_ANIMATE_PIN_DATA);
505503
if (parentHost) {
506-
parent = parentHost;
504+
parentElement = parentHost;
507505
}
508506

509-
while (parent && parent.length) {
507+
while (parentElement && parentElement.length) {
510508
if (!rootElementDetected) {
511509
// angular doesn't want to attempt to animate elements outside of the application
512510
// therefore we need to ensure that the rootElement is an ancestor of the current element
513-
rootElementDetected = isMatchingElement(parent, $rootElement);
511+
rootElementDetected = isMatchingElement(parentElement, $rootElement);
514512
}
515513

516-
var parentNode = parent[0];
514+
var parentNode = parentElement[0];
517515
if (parentNode.nodeType !== ELEMENT_NODE) {
518516
// no point in inspecting the #document element
519517
break;
@@ -528,7 +526,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
528526
}
529527

530528
if (isUndefined(animateChildren) || animateChildren === true) {
531-
var value = parent.data(NG_ANIMATE_CHILDREN_DATA);
529+
var value = parentElement.data(NG_ANIMATE_CHILDREN_DATA);
532530
if (isDefined(value)) {
533531
animateChildren = value;
534532
}
@@ -540,22 +538,22 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
540538
if (!rootElementDetected) {
541539
// angular doesn't want to attempt to animate elements outside of the application
542540
// therefore we need to ensure that the rootElement is an ancestor of the current element
543-
rootElementDetected = isMatchingElement(parent, $rootElement);
541+
rootElementDetected = isMatchingElement(parentElement, $rootElement);
544542
if (!rootElementDetected) {
545-
parentHost = parent.data(NG_ANIMATE_PIN_DATA);
543+
parentHost = parentElement.data(NG_ANIMATE_PIN_DATA);
546544
if (parentHost) {
547-
parent = parentHost;
545+
parentElement = parentHost;
548546
}
549547
}
550548
}
551549

552550
if (!bodyElementDetected) {
553551
// we also need to ensure that the element is or will be apart of the body element
554552
// otherwise it is pointless to even issue an animation to be rendered
555-
bodyElementDetected = isMatchingElement(parent, bodyElement);
553+
bodyElementDetected = isMatchingElement(parentElement, bodyElement);
556554
}
557555

558-
parent = parent.parent();
556+
parentElement = parentElement.parent();
559557
}
560558

561559
var allowAnimation = !parentAnimationDetected || animateChildren;
@@ -566,14 +564,14 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
566564
details = details || {};
567565
details.state = state;
568566

569-
element = element.length ? element[0] : element;
570-
element.setAttribute(NG_ANIMATE_ATTR_NAME, state);
567+
var node = getDomNode(element);
568+
node.setAttribute(NG_ANIMATE_ATTR_NAME, state);
571569

572-
var oldValue = activeAnimationsLookup.get(element);
570+
var oldValue = activeAnimationsLookup.get(node);
573571
var newValue = oldValue
574572
? extend(oldValue, details)
575573
: details;
576-
activeAnimationsLookup.put(element, newValue);
574+
activeAnimationsLookup.put(node, newValue);
577575
}
578576
}];
579577
}];

src/ngAnimate/animation.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
128128
var refLookup = {};
129129
forEach(animations, function(animation, index) {
130130
var element = animation.element;
131-
var node = element[0];
131+
var node = getDomNode(element);
132132
var event = animation.event;
133133
var enterOrMove = ['enter', 'move'].indexOf(event) >= 0;
134134
var anchorNodes = animation.structural ? getAnchorNodes(node) : [];

src/ngAnimate/shared.js

+4
Original file line numberDiff line numberDiff line change
@@ -245,3 +245,7 @@ function resolveElementClasses(existing, toAdd, toRemove) {
245245

246246
return classes;
247247
}
248+
249+
function getDomNode(element) {
250+
return (element instanceof angular.element) ? element[0] : element;
251+
}

test/ngAnimate/animateSpec.js

+26
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,32 @@ describe("animations", function() {
571571
expect(itsOver).toBe(true);
572572
}));
573573

574+
it('should immediately end a parent class-based form animation if a structural child is active',
575+
inject(function($rootScope, $animate, $rootElement, $$rAF, $$AnimateRunner) {
576+
577+
parent.remove();
578+
element.remove();
579+
580+
parent = jqLite('<form></form>');
581+
$rootElement.append(parent);
582+
583+
element = jqLite('<input type="text" name="myInput" />');
584+
585+
$animate.addClass(parent, 'abc');
586+
$rootScope.$digest();
587+
588+
// we do this since the old runner was already closed
589+
overriddenAnimationRunner = new $$AnimateRunner();
590+
591+
$animate.enter(element, parent);
592+
$rootScope.$digest();
593+
594+
$$rAF.flush();
595+
596+
expect(parent.attr('data-ng-animate')).toBeFalsy();
597+
expect(element.attr('data-ng-animate')).toBeTruthy();
598+
}));
599+
574600
it('should not end a pre-digest parent animation if it does not have any classes to add/remove',
575601
inject(function($rootScope, $animate, $$rAF) {
576602

0 commit comments

Comments
 (0)