    /** * @private */Ext.define('Ext.fx.runner.CssAnimation', {    extend: 'Ext.fx.runner.Css',     constructor: function() {        this.runningAnimationsMap = {};         this.elementEndStates = {};         this.animationElementMap = {};         this.keyframesRulesCache = {};         this.uniqueId = 0;         return this.callParent(arguments);    },     attachListeners: function() {        this.listenersAttached = true;         Ext.getWin().on({            animationstart: 'onAnimationStart',            animationend: 'onAnimationEnd',            scope: this        });    },     onAnimationStart: function(e) {        var name = e.browserEvent.animationName,            elementId = this.animationElementMap[name],            animation = this.runningAnimationsMap[elementId][name],            elementEndStates = this.elementEndStates,            elementEndState = elementEndStates[elementId],            data = {};         //console.log("START============= " + name);          if (elementEndState) {            delete elementEndStates[elementId];             data[elementId] = elementEndState;             this.applyStyles(data);        }         if (animation.before) {            data[elementId] = animation.before;             this.applyStyles(data);        }    },     onAnimationEnd: function(e) {        var element = e.target,            name = e.browserEvent.animationName,            animationElementMap = this.animationElementMap,            elementId = animationElementMap[name],            runningAnimationsMap = this.runningAnimationsMap,            runningAnimations = runningAnimationsMap[elementId],            animation = runningAnimations[name];         //console.log("END============= " + name);          if (animation.onBeforeEnd) {            animation.onBeforeEnd.call(animation.scope || this, element);        }         if (animation.onEnd) {            animation.onEnd.call(animation.scope || this, element);        }         delete animationElementMap[name];        delete runningAnimations[name];         this.removeKeyframesRule(name);    },     generateAnimationId: function() {        return 'animation-' + (++this.uniqueId);    },     run: function(animations) {        var data = {},            elementEndStates = this.elementEndStates,            animationElementMap = this.animationElementMap,            runningAnimationsMap = this.runningAnimationsMap,            runningAnimations, states,            elementId, animationId, i, ln, animation,            name, runningAnimation,            names, durations, easings, delays, directions, iterations;         if (!this.listenersAttached) {            this.attachListeners();        }         animations = Ext.Array.from(animations);         for (i = 0,ln = animations.length; i < ln; i++) {            animation = animations[i];             animation = Ext.factory(animation, Ext.fx.Animation);            elementId = animation.getElement().getId();            animationId = animation.getName() || this.generateAnimationId();             animationElementMap[animationId] = elementId;             animation = animation.getData();            states = animation.states;             this.addKeyframesRule(animationId, states);             runningAnimations = runningAnimationsMap[elementId];             if (!runningAnimations) {                runningAnimations = runningAnimationsMap[elementId] = {};            }             runningAnimations[animationId] = animation;             names = [];            durations = [];            easings = [];            delays = [];            directions = [];            iterations = [];             for (name in runningAnimations) {                if (runningAnimations.hasOwnProperty(name)) {                    runningAnimation = runningAnimations[name];                     names.push(name);                    durations.push(runningAnimation.duration);                    easings.push(runningAnimation.easing);                    delays.push(runningAnimation.delay);                    directions.push(runningAnimation.direction);                    iterations.push(runningAnimation.iteration);                }            }             data[elementId] = {                'animation-name'            : names,                'animation-duration'        : durations,                'animation-timing-function' : easings,                'animation-delay'           : delays,                'animation-direction'       : directions,                'animation-iteration-count' : iterations            }; //            Ext.apply(data[elementId], animation.origin);              if (animation.preserveEndState) {                elementEndStates[elementId] = states['100%'];            }        }         this.applyStyles(data);    },     addKeyframesRule: function(name, keyframes) {        var percentage, properties,            keyframesRule,            styleSheet, rules, styles, rulesLength, key, value;         styleSheet = this.getStyleSheet();        rules = styleSheet.cssRules;        rulesLength = rules.length;        styleSheet.insertRule('@' + this.vendorPrefix + 'keyframes ' + name + '{}', rulesLength);         keyframesRule = rules[rulesLength];         for (percentage in keyframes) {            properties = keyframes[percentage];             rules = keyframesRule.cssRules;            rulesLength = rules.length;             styles = [];             for (key in properties) {                value = this.formatValue(properties[key], key);                key = this.formatName(key);                 styles.push(key + ':' + value);            }             keyframesRule.insertRule(percentage + '{' + styles.join(';') + '}', rulesLength);        }         return this;    },     removeKeyframesRule: function(name) {        var styleSheet = this.getStyleSheet(),            rules = styleSheet.cssRules,            i, ln, rule;         for (i = 0,ln = rules.length; i < ln; i++) {            rule = rules[i];             if (rule.name === name) {                styleSheet.removeRule(i);                break;            }        }         return this;    }});