// Copyright 2014 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. (function(shared, scope, testing) { function groupChildDuration(node) { return node._timing.delay + node.activeDuration + node._timing.endDelay; } function constructor(children, timingInput, id) { this._id = id; this._parent = null; this.children = children || []; this._reparent(this.children); timingInput = shared.numericTimingToObject(timingInput); this._timingInput = shared.cloneTimingInput(timingInput); this._timing = shared.normalizeTimingInput(timingInput, true); this.timing = shared.makeTiming(timingInput, true, this); this.timing._effect = this; if (this._timing.duration === 'auto') { this._timing.duration = this.activeDuration; } } window.SequenceEffect = function() { constructor.apply(this, arguments); }; window.GroupEffect = function() { constructor.apply(this, arguments); }; constructor.prototype = { _isAncestor: function(effect) { var a = this; while (a !== null) { if (a == effect) return true; a = a._parent; } return false; }, _rebuild: function() { // Re-calculate durations for ancestors with specified duration 'auto'. var node = this; while (node) { if (node.timing.duration === 'auto') { node._timing.duration = node.activeDuration; } node = node._parent; } if (this._animation) { this._animation._rebuildUnderlyingAnimation(); } }, _reparent: function(newChildren) { scope.removeMulti(newChildren); for (var i = 0; i < newChildren.length; i++) { newChildren[i]._parent = this; } }, _putChild: function(args, isAppend) { var message = isAppend ? 'Cannot append an ancestor or self' : 'Cannot prepend an ancestor or self'; for (var i = 0; i < args.length; i++) { if (this._isAncestor(args[i])) { throw { type: DOMException.HIERARCHY_REQUEST_ERR, name: 'HierarchyRequestError', message: message }; } } var oldParents = []; for (var i = 0; i < args.length; i++) { isAppend ? this.children.push(args[i]) : this.children.unshift(args[i]); } this._reparent(args); this._rebuild(); }, append: function() { this._putChild(arguments, true); }, prepend: function() { this._putChild(arguments, false); }, get parent() { return this._parent; }, get firstChild() { return this.children.length ? this.children[0] : null; }, get lastChild() { return this.children.length ? this.children[this.children.length - 1] : null; }, clone: function() { var clonedTiming = shared.cloneTimingInput(this._timingInput); var clonedChildren = []; for (var i = 0; i < this.children.length; i++) { clonedChildren.push(this.children[i].clone()); } return (this instanceof GroupEffect) ? new GroupEffect(clonedChildren, clonedTiming) : new SequenceEffect(clonedChildren, clonedTiming); }, remove: function() { scope.removeMulti([this]); } }; window.SequenceEffect.prototype = Object.create(constructor.prototype); Object.defineProperty( window.SequenceEffect.prototype, 'activeDuration', { get: function() { var total = 0; this.children.forEach(function(child) { total += groupChildDuration(child); }); return Math.max(total, 0); } }); window.GroupEffect.prototype = Object.create(constructor.prototype); Object.defineProperty( window.GroupEffect.prototype, 'activeDuration', { get: function() { var max = 0; this.children.forEach(function(child) { max = Math.max(max, groupChildDuration(child)); }); return max; } }); scope.newUnderlyingAnimationForGroup = function(group) { var underlyingAnimation; var timing = null; var ticker = function(tf) { var animation = underlyingAnimation._wrapper; if (!animation) { return; } if (animation.playState == 'pending') { return; } if (!animation.effect) { return; } if (tf == null) { animation._removeChildAnimations(); return; } // If the group has a negative playback rate and is not fill backwards/both, then it should go // out of effect when it reaches the start of its active interval (tf == 0). If it is fill // backwards/both then it should stay in effect. calculateIterationProgress will return 0 in the // backwards-filling case, and null otherwise. if (tf == 0 && animation.playbackRate < 0) { if (!timing) { timing = shared.normalizeTimingInput(animation.effect.timing); } tf = shared.calculateIterationProgress(shared.calculateActiveDuration(timing), -1, timing); if (isNaN(tf) || tf == null) { animation._forEachChild(function(child) { child.currentTime = -1; }); animation._removeChildAnimations(); return; } } }; var underlyingEffect = new KeyframeEffect(null, [], group._timing, group._id); underlyingEffect.onsample = ticker; underlyingAnimation = scope.timeline._play(underlyingEffect); return underlyingAnimation; }; scope.bindAnimationForGroup = function(animation) { animation._animation._wrapper = animation; animation._isGroup = true; scope.awaitStartTime(animation); animation._constructChildAnimations(); animation._setExternalAnimation(animation); }; scope.groupChildDuration = groupChildDuration; })(webAnimationsShared, webAnimationsNext, webAnimationsTesting);