Merge branch 'master' into 91_canvas_paint_operations_optimization
diff --git a/player/js/elements/canvasElements/CVContextData.js b/player/js/elements/canvasElements/CVContextData.js
index f5d8b93..88df6fa 100644
--- a/player/js/elements/canvasElements/CVContextData.js
+++ b/player/js/elements/canvasElements/CVContextData.js
@@ -3,28 +3,57 @@
 } from '../../utils/helpers/arrays';
 import Matrix from '../../3rd_party/transformation-matrix';
 
+function CanvasContext() {
+  this.opacity = -1;
+  this.transform = createTypedArray('float32', 16);
+  this.fillStyle = '';
+  this.strokeStyle = '';
+  this.lineWidth = '';
+  this.lineCap = '';
+  this.lineJoin = '';
+  this.miterLimit = '';
+  this.id = Math.random();
+}
+
 function CVContextData() {
-  this.saved = [];
+  this.stack = [];
   this.cArrPos = 0;
   this.cTr = new Matrix();
-  this.cO = 1;
   var i;
   var len = 15;
-  this.savedOp = createTypedArray('float32', len);
   for (i = 0; i < len; i += 1) {
-    this.saved[i] = createTypedArray('float32', 16);
+    var canvasContext = new CanvasContext();
+    this.stack[i] = canvasContext;
   }
   this._length = len;
+  this.nativeContext = null;
+  this.transformMat = new Matrix();
+  this.currentOpacity = 1;
+  //
+  this.currentFillStyle = '';
+  this.appliedFillStyle = '';
+  //
+  this.currentStrokeStyle = '';
+  this.appliedStrokeStyle = '';
+  //
+  this.currentLineWidth = '';
+  this.appliedLineWidth = '';
+  //
+  this.currentLineCap = '';
+  this.appliedLineCap = '';
+  //
+  this.currentLineJoin = '';
+  this.appliedLineJoin = '';
+  //
+  this.appliedMiterLimit = '';
+  this.currentMiterLimit = '';
 }
 
 CVContextData.prototype.duplicate = function () {
   var newLength = this._length * 2;
-  var currentSavedOp = this.savedOp;
-  this.savedOp = createTypedArray('float32', newLength);
-  this.savedOp.set(currentSavedOp);
   var i = 0;
   for (i = this._length; i < newLength; i += 1) {
-    this.saved[i] = createTypedArray('float32', 16);
+    this.stack[i] = new CanvasContext();
   }
   this._length = newLength;
 };
@@ -32,59 +61,179 @@
 CVContextData.prototype.reset = function () {
   this.cArrPos = 0;
   this.cTr.reset();
-  this.cO = 1;
+  this.stack[this.cArrPos].opacity = 1;
 };
 
-CVContextData.prototype.popTransform = function () {
-  var popped = this.saved[this.cArrPos];
+CVContextData.prototype.restore = function (forceRestore) {
+  this.cArrPos -= 1;
+  var currentContext = this.stack[this.cArrPos];
+  var transform = currentContext.transform;
   var i;
   var arr = this.cTr.props;
   for (i = 0; i < 16; i += 1) {
-    arr[i] = popped[i];
+    arr[i] = transform[i];
   }
-  return popped;
+  if (forceRestore) {
+    this.nativeContext.restore();
+    var prevStack = this.stack[this.cArrPos + 1];
+    this.appliedFillStyle = prevStack.fillStyle;
+    this.appliedStrokeStyle = prevStack.strokeStyle;
+    this.appliedLineWidth = prevStack.lineWidth;
+    this.appliedLineCap = prevStack.lineCap;
+    this.appliedLineJoin = prevStack.lineJoin;
+    this.appliedMiterLimit = prevStack.miterLimit;
+  }
+  this.nativeContext.setTransform(transform[0], transform[1], transform[4], transform[5], transform[12], transform[13]);
+  if (forceRestore || (currentContext.opacity !== -1 && this.currentOpacity !== currentContext.opacity)) {
+    this.nativeContext.globalAlpha = currentContext.opacity;
+    this.currentOpacity = currentContext.opacity;
+  }
+  this.currentFillStyle = currentContext.fillStyle;
+  this.currentStrokeStyle = currentContext.strokeStyle;
+  this.currentLineWidth = currentContext.lineWidth;
+  this.currentLineCap = currentContext.lineCap;
+  this.currentLineJoin = currentContext.lineJoin;
+  this.currentMiterLimit = currentContext.miterLimit;
 };
 
-CVContextData.prototype.popOpacity = function () {
-  var popped = this.savedOp[this.cArrPos];
-  this.cO = popped;
-  return popped;
-};
-
-CVContextData.prototype.pop = function () {
-  this.cArrPos -= 1;
-  var transform = this.popTransform();
-  var opacity = this.popOpacity();
-  return {
-    transform: transform,
-    opacity: opacity,
-  };
-};
-
-CVContextData.prototype.push = function () {
+CVContextData.prototype.save = function (saveOnNativeFlag) {
+  if (saveOnNativeFlag) {
+    this.nativeContext.save();
+  }
   var props = this.cTr.props;
   if (this._length <= this.cArrPos) {
     this.duplicate();
   }
+
+  var currentStack = this.stack[this.cArrPos];
   var i;
-  var arr = this.saved[this.cArrPos];
   for (i = 0; i < 16; i += 1) {
-    arr[i] = props[i];
+    currentStack.transform[i] = props[i];
   }
-  this.savedOp[this.cArrPos] = this.cO;
   this.cArrPos += 1;
-};
-
-CVContextData.prototype.getTransform = function () {
-  return this.cTr;
-};
-
-CVContextData.prototype.getOpacity = function () {
-  return this.cO;
+  var newStack = this.stack[this.cArrPos];
+  newStack.opacity = currentStack.opacity;
+  newStack.fillStyle = currentStack.fillStyle;
+  newStack.strokeStyle = currentStack.strokeStyle;
+  newStack.lineWidth = currentStack.lineWidth;
+  newStack.lineCap = currentStack.lineCap;
+  newStack.lineJoin = currentStack.lineJoin;
+  newStack.miterLimit = currentStack.miterLimit;
 };
 
 CVContextData.prototype.setOpacity = function (value) {
-  this.cO = value;
+  this.stack[this.cArrPos].opacity = value;
+};
+
+CVContextData.prototype.setContext = function (value) {
+  this.nativeContext = value;
+};
+
+CVContextData.prototype.fillStyle = function (value) {
+  if (this.stack[this.cArrPos].fillStyle !== value) {
+    this.currentFillStyle = value;
+    this.stack[this.cArrPos].fillStyle = value;
+  }
+};
+
+CVContextData.prototype.strokeStyle = function (value) {
+  if (this.stack[this.cArrPos].strokeStyle !== value) {
+    this.currentStrokeStyle = value;
+    this.stack[this.cArrPos].strokeStyle = value;
+  }
+};
+
+CVContextData.prototype.lineWidth = function (value) {
+  if (this.stack[this.cArrPos].lineWidth !== value) {
+    this.currentLineWidth = value;
+    this.stack[this.cArrPos].lineWidth = value;
+  }
+};
+
+CVContextData.prototype.lineCap = function (value) {
+  if (this.stack[this.cArrPos].lineCap !== value) {
+    this.currentLineCap = value;
+    this.stack[this.cArrPos].lineCap = value;
+  }
+};
+
+CVContextData.prototype.lineJoin = function (value) {
+  if (this.stack[this.cArrPos].lineJoin !== value) {
+    this.currentLineJoin = value;
+    this.stack[this.cArrPos].lineJoin = value;
+  }
+};
+
+CVContextData.prototype.miterLimit = function (value) {
+  if (this.stack[this.cArrPos].miterLimit !== value) {
+    this.currentMiterLimit = value;
+    this.stack[this.cArrPos].miterLimit = value;
+  }
+};
+
+CVContextData.prototype.transform = function (props) {
+  this.transformMat.cloneFromProps(props);
+  // Taking the last transform value from the stored stack of transforms
+  var currentTransform = this.cTr;
+  // Applying the last transform value after the new transform to respect the order of transformations
+  this.transformMat.multiply(currentTransform);
+  // Storing the new transformed value in the stored transform
+  currentTransform.cloneFromProps(this.transformMat.props);
+  var trProps = currentTransform.props;
+  // Applying the new transform to the canvas
+  this.nativeContext.setTransform(trProps[0], trProps[1], trProps[4], trProps[5], trProps[12], trProps[13]);
+};
+
+CVContextData.prototype.opacity = function (op) {
+  var currentOpacity = this.stack[this.cArrPos].opacity;
+  currentOpacity *= op < 0 ? 0 : op;
+  if (this.stack[this.cArrPos].opacity !== currentOpacity) {
+    if (this.currentOpacity !== op) {
+      this.nativeContext.globalAlpha = op;
+      this.currentOpacity = op;
+    }
+    this.stack[this.cArrPos].opacity = currentOpacity;
+  }
+};
+
+CVContextData.prototype.fill = function (rule) {
+  if (this.appliedFillStyle !== this.currentFillStyle) {
+    this.appliedFillStyle = this.currentFillStyle;
+    this.nativeContext.fillStyle = this.appliedFillStyle;
+  }
+  this.nativeContext.fill(rule);
+};
+
+CVContextData.prototype.fillRect = function (x, y, w, h) {
+  if (this.appliedFillStyle !== this.currentFillStyle) {
+    this.appliedFillStyle = this.currentFillStyle;
+    this.nativeContext.fillStyle = this.appliedFillStyle;
+  }
+  this.nativeContext.fillRect(x, y, w, h);
+};
+
+CVContextData.prototype.stroke = function () {
+  if (this.appliedStrokeStyle !== this.currentStrokeStyle) {
+    this.appliedStrokeStyle = this.currentStrokeStyle;
+    this.nativeContext.strokeStyle = this.appliedStrokeStyle;
+  }
+  if (this.appliedLineWidth !== this.currentLineWidth) {
+    this.appliedLineWidth = this.currentLineWidth;
+    this.nativeContext.lineWidth = this.appliedLineWidth;
+  }
+  if (this.appliedLineCap !== this.currentLineCap) {
+    this.appliedLineCap = this.currentLineCap;
+    this.nativeContext.lineCap = this.appliedLineCap;
+  }
+  if (this.appliedLineJoin !== this.currentLineJoin) {
+    this.appliedLineJoin = this.currentLineJoin;
+    this.nativeContext.lineJoin = this.appliedLineJoin;
+  }
+  if (this.appliedMiterLimit !== this.currentMiterLimit) {
+    this.appliedMiterLimit = this.currentMiterLimit;
+    this.nativeContext.miterLimit = this.appliedMiterLimit;
+  }
+  this.nativeContext.stroke();
 };
 
 export default CVContextData;
diff --git a/player/js/elements/canvasElements/CVShapeElement.js b/player/js/elements/canvasElements/CVShapeElement.js
index f37cf51..3217bf5 100644
--- a/player/js/elements/canvasElements/CVShapeElement.js
+++ b/player/js/elements/canvasElements/CVShapeElement.js
@@ -295,13 +295,19 @@
       renderer.save();
       elems = currentStyle.elements;
       if (type === 'st' || type === 'gs') {
-        ctx.strokeStyle = type === 'st' ? currentStyle.co : currentStyle.grd;
-        ctx.lineWidth = currentStyle.wi;
-        ctx.lineCap = currentStyle.lc;
-        ctx.lineJoin = currentStyle.lj;
-        ctx.miterLimit = currentStyle.ml || 0;
+        renderer.ctxStrokeStyle(type === 'st' ? currentStyle.co : currentStyle.grd);
+        // ctx.strokeStyle = type === 'st' ? currentStyle.co : currentStyle.grd;
+        renderer.ctxLineWidth(currentStyle.wi);
+        // ctx.lineWidth = currentStyle.wi;
+        renderer.ctxLineCap(currentStyle.lc);
+        // ctx.lineCap = currentStyle.lc;
+        renderer.ctxLineJoin(currentStyle.lj);
+        // ctx.lineJoin = currentStyle.lj;
+        renderer.ctxMiterLimit(currentStyle.ml || 0);
+        // ctx.miterLimit = currentStyle.ml || 0;
       } else {
-        ctx.fillStyle = type === 'fl' ? currentStyle.co : currentStyle.grd;
+        renderer.ctxFillStyle(type === 'fl' ? currentStyle.co : currentStyle.grd);
+        // ctx.fillStyle = type === 'fl' ? currentStyle.co : currentStyle.grd;
       }
       renderer.ctxOpacity(currentStyle.coOp);
       if (type !== 'st' && type !== 'gs') {
@@ -330,14 +336,16 @@
           }
         }
         if (type === 'st' || type === 'gs') {
-          ctx.stroke();
+          // ctx.stroke();
+          renderer.ctxStroke();
           if (currentStyle.da) {
             ctx.setLineDash(this.dashResetter);
           }
         }
       }
       if (type !== 'st' && type !== 'gs') {
-        ctx.fill(currentStyle.r);
+        // ctx.fill(currentStyle.r);
+        this.globalData.renderer.ctxFill(currentStyle.r);
       }
       renderer.restore();
     }
diff --git a/player/js/elements/canvasElements/CVSolidElement.js b/player/js/elements/canvasElements/CVSolidElement.js
index 61eadd8..23e0d98 100644
--- a/player/js/elements/canvasElements/CVSolidElement.js
+++ b/player/js/elements/canvasElements/CVSolidElement.js
@@ -19,9 +19,11 @@
 CVSolidElement.prototype.prepareFrame = IImageElement.prototype.prepareFrame;
 
 CVSolidElement.prototype.renderInnerContent = function () {
-  var ctx = this.canvasContext;
-  ctx.fillStyle = this.data.sc;
-  ctx.fillRect(0, 0, this.data.sw, this.data.sh);
+  // var ctx = this.canvasContext;
+  this.globalData.renderer.ctxFillStyle(this.data.sc);
+  // ctx.fillStyle = this.data.sc;
+  this.globalData.renderer.ctxFillRect(0, 0, this.data.sw, this.data.sh);
+  // ctx.fillRect(0, 0, this.data.sw, this.data.sh);
   //
 };
 
diff --git a/player/js/elements/canvasElements/CVTextElement.js b/player/js/elements/canvasElements/CVTextElement.js
index b815e84..52ef05e 100644
--- a/player/js/elements/canvasElements/CVTextElement.js
+++ b/player/js/elements/canvasElements/CVTextElement.js
@@ -130,9 +130,12 @@
   this.validateText();
   var ctx = this.canvasContext;
   ctx.font = this.values.fValue;
-  ctx.lineCap = 'butt';
-  ctx.lineJoin = 'miter';
-  ctx.miterLimit = 4;
+  this.globalData.renderer.ctxLineCap('butt');
+  // ctx.lineCap = 'butt';
+  this.globalData.renderer.ctxLineJoin('miter');
+  // ctx.lineJoin = 'miter';
+  this.globalData.renderer.ctxMiterLimit(4);
+  // ctx.miterLimit = 4;
 
   if (!this.data.singleShape) {
     this.textAnimator.getMeasures(this.textProperty.currentData, this.lettersChangedFlag);
@@ -155,23 +158,26 @@
   var lastStrokeW = null;
   var commands;
   var pathArr;
+  var renderer = this.globalData.renderer;
   for (i = 0; i < len; i += 1) {
     if (!letters[i].n) {
       renderedLetter = renderedLetters[i];
       if (renderedLetter) {
-        this.globalData.renderer.save();
-        this.globalData.renderer.ctxTransform(renderedLetter.p);
-        this.globalData.renderer.ctxOpacity(renderedLetter.o);
+        renderer.save();
+        renderer.ctxTransform(renderedLetter.p);
+        renderer.ctxOpacity(renderedLetter.o);
       }
       if (this.fill) {
         if (renderedLetter && renderedLetter.fc) {
           if (lastFill !== renderedLetter.fc) {
+            renderer.ctxFillStyle(renderedLetter.fc);
             lastFill = renderedLetter.fc;
-            ctx.fillStyle = renderedLetter.fc;
+            // ctx.fillStyle = renderedLetter.fc;
           }
         } else if (lastFill !== this.values.fill) {
           lastFill = this.values.fill;
-          ctx.fillStyle = this.values.fill;
+          renderer.ctxFillStyle(this.values.fill);
+          // ctx.fillStyle = this.values.fill;
         }
         commands = this.textSpans[i].elem;
         jLen = commands.length;
@@ -185,27 +191,32 @@
           }
         }
         this.globalData.canvasContext.closePath();
-        this.globalData.canvasContext.fill();
+        renderer.ctxFill();
+        // this.globalData.canvasContext.fill();
         /// ctx.fillText(this.textSpans[i].val,0,0);
       }
       if (this.stroke) {
         if (renderedLetter && renderedLetter.sw) {
           if (lastStrokeW !== renderedLetter.sw) {
             lastStrokeW = renderedLetter.sw;
-            ctx.lineWidth = renderedLetter.sw;
+            renderer.ctxLineWidth(renderedLetter.sw);
+            // ctx.lineWidth = renderedLetter.sw;
           }
         } else if (lastStrokeW !== this.values.sWidth) {
           lastStrokeW = this.values.sWidth;
-          ctx.lineWidth = this.values.sWidth;
+          renderer.ctxLineWidth(this.values.sWidth);
+          // ctx.lineWidth = this.values.sWidth;
         }
         if (renderedLetter && renderedLetter.sc) {
           if (lastStroke !== renderedLetter.sc) {
             lastStroke = renderedLetter.sc;
-            ctx.strokeStyle = renderedLetter.sc;
+            renderer.ctxStrokeStyle(renderedLetter.sc);
+            // ctx.strokeStyle = renderedLetter.sc;
           }
         } else if (lastStroke !== this.values.stroke) {
           lastStroke = this.values.stroke;
-          ctx.strokeStyle = this.values.stroke;
+          renderer.ctxStrokeStyle(this.values.stroke);
+          // ctx.strokeStyle = this.values.stroke;
         }
         commands = this.textSpans[i].elem;
         jLen = commands.length;
@@ -219,7 +230,8 @@
           }
         }
         this.globalData.canvasContext.closePath();
-        this.globalData.canvasContext.stroke();
+        renderer.ctxStroke();
+        // this.globalData.canvasContext.stroke();
         /// ctx.strokeText(letters[i].val,0,0);
       }
       if (renderedLetter) {
diff --git a/player/js/renderers/CanvasRenderer.js b/player/js/renderers/CanvasRenderer.js
index 231d06f..0a7c988 100644
--- a/player/js/renderers/CanvasRenderer.js
+++ b/player/js/renderers/CanvasRenderer.js
@@ -36,6 +36,20 @@
   this.transformMat = new Matrix();
   this.completeLayers = false;
   this.rendererType = 'canvas';
+  if (this.renderConfig.clearCanvas) {
+    this.ctxTransform = this.contextData.transform.bind(this.contextData);
+    this.ctxOpacity = this.contextData.opacity.bind(this.contextData);
+    this.ctxFillStyle = this.contextData.fillStyle.bind(this.contextData);
+    this.ctxStrokeStyle = this.contextData.strokeStyle.bind(this.contextData);
+    this.ctxLineWidth = this.contextData.lineWidth.bind(this.contextData);
+    this.ctxLineCap = this.contextData.lineCap.bind(this.contextData);
+    this.ctxLineJoin = this.contextData.lineJoin.bind(this.contextData);
+    this.ctxMiterLimit = this.contextData.miterLimit.bind(this.contextData);
+    this.ctxFill = this.contextData.fill.bind(this.contextData);
+    this.ctxFillRect = this.contextData.fillRect.bind(this.contextData);
+    this.ctxStroke = this.contextData.stroke.bind(this.contextData);
+    this.save = this.contextData.save.bind(this.contextData);
+  }
 }
 extendPrototype([CanvasRendererBase], CanvasRenderer);
 
diff --git a/player/js/renderers/CanvasRendererBase.js b/player/js/renderers/CanvasRendererBase.js
index 6c801ec..dd199f5 100644
--- a/player/js/renderers/CanvasRendererBase.js
+++ b/player/js/renderers/CanvasRendererBase.js
@@ -6,43 +6,13 @@
 } from '../utils/helpers/arrays';
 import createTag from '../utils/helpers/html_elements';
 import SVGRenderer from './SVGRenderer';
-import Matrix from '../3rd_party/transformation-matrix';
 import BaseRenderer from './BaseRenderer';
-import CVContextData from '../elements/canvasElements/CVContextData';
 import CVShapeElement from '../elements/canvasElements/CVShapeElement';
 import CVTextElement from '../elements/canvasElements/CVTextElement';
 import CVImageElement from '../elements/canvasElements/CVImageElement';
 import CVSolidElement from '../elements/canvasElements/CVSolidElement';
 
-function CanvasRendererBase(animationItem, config) {
-  this.animationItem = animationItem;
-  this.renderConfig = {
-    clearCanvas: (config && config.clearCanvas !== undefined) ? config.clearCanvas : true,
-    context: (config && config.context) || null,
-    progressiveLoad: (config && config.progressiveLoad) || false,
-    preserveAspectRatio: (config && config.preserveAspectRatio) || 'xMidYMid meet',
-    imagePreserveAspectRatio: (config && config.imagePreserveAspectRatio) || 'xMidYMid slice',
-    contentVisibility: (config && config.contentVisibility) || 'visible',
-    className: (config && config.className) || '',
-    id: (config && config.id) || '',
-  };
-  this.renderConfig.dpr = (config && config.dpr) || 1;
-  if (this.animationItem.wrapper) {
-    this.renderConfig.dpr = (config && config.dpr) || window.devicePixelRatio || 1;
-  }
-  this.renderedFrame = -1;
-  this.globalData = {
-    frameNum: -1,
-    _mdf: false,
-    renderConfig: this.renderConfig,
-    currentGlobalAlpha: -1,
-  };
-  this.contextData = new CVContextData();
-  this.elements = [];
-  this.pendingElements = [];
-  this.transformMat = new Matrix();
-  this.completeLayers = false;
-  this.rendererType = 'canvas';
+function CanvasRendererBase() {
 }
 extendPrototype([BaseRenderer], CanvasRendererBase);
 
@@ -68,39 +38,47 @@
   if (props[0] === 1 && props[1] === 0 && props[4] === 0 && props[5] === 1 && props[12] === 0 && props[13] === 0) {
     return;
   }
-  if (!this.renderConfig.clearCanvas) {
-    this.canvasContext.transform(props[0], props[1], props[4], props[5], props[12], props[13]);
-    return;
-  }
-  // Resetting the canvas transform matrix to the new transform
-  this.transformMat.cloneFromProps(props);
-  // Taking the last transform value from the stored stack of transforms
-  var currentTransform = this.contextData.getTransform();
-  // Applying the last transform value after the new transform to respect the order of transformations
-  this.transformMat.multiply(currentTransform);
-  // Storing the new transformed value in the stored transform
-  currentTransform.cloneFromProps(this.transformMat.props);
-  var trProps = currentTransform.props;
-  // Applying the new transform to the canvas
-  this.canvasContext.setTransform(trProps[0], trProps[1], trProps[4], trProps[5], trProps[12], trProps[13]);
+  this.canvasContext.transform(props[0], props[1], props[4], props[5], props[12], props[13]);
 };
 
 CanvasRendererBase.prototype.ctxOpacity = function (op) {
-  /* if(op === 1){
-        return;
-    } */
-  var currentOpacity = this.contextData.getOpacity();
-  if (!this.renderConfig.clearCanvas) {
-    this.canvasContext.globalAlpha *= op < 0 ? 0 : op;
-    this.globalData.currentGlobalAlpha = currentOpacity;
-    return;
-  }
-  currentOpacity *= op < 0 ? 0 : op;
-  this.contextData.setOpacity(currentOpacity);
-  if (this.globalData.currentGlobalAlpha !== currentOpacity) {
-    this.canvasContext.globalAlpha = currentOpacity;
-    this.globalData.currentGlobalAlpha = currentOpacity;
-  }
+  this.canvasContext.globalAlpha *= op < 0 ? 0 : op;
+};
+
+CanvasRendererBase.prototype.ctxFillStyle = function (value) {
+  this.canvasContext.fillStyle = value;
+};
+
+CanvasRendererBase.prototype.ctxStrokeStyle = function (value) {
+  this.canvasContext.strokeStyle = value;
+};
+
+CanvasRendererBase.prototype.ctxLineWidth = function (value) {
+  this.canvasContext.lineWidth = value;
+};
+
+CanvasRendererBase.prototype.ctxLineCap = function (value) {
+  this.canvasContext.lineCap = value;
+};
+
+CanvasRendererBase.prototype.ctxLineJoin = function (value) {
+  this.canvasContext.lineJoin = value;
+};
+
+CanvasRendererBase.prototype.ctxMiterLimit = function (value) {
+  this.canvasContext.miterLimit = value;
+};
+
+CanvasRendererBase.prototype.ctxFill = function (rule) {
+  this.canvasContext.fill(rule);
+};
+
+CanvasRendererBase.prototype.ctxFillRect = function (x, y, w, h) {
+  this.canvasContext.fillRect(x, y, w, h);
+};
+
+CanvasRendererBase.prototype.ctxStroke = function () {
+  this.canvasContext.stroke();
 };
 
 CanvasRendererBase.prototype.reset = function () {
@@ -111,15 +89,8 @@
   this.contextData.reset();
 };
 
-CanvasRendererBase.prototype.save = function (actionFlag) {
-  if (!this.renderConfig.clearCanvas) {
-    this.canvasContext.save();
-    return;
-  }
-  if (actionFlag) {
-    this.canvasContext.save();
-  }
-  this.contextData.push();
+CanvasRendererBase.prototype.save = function () {
+  this.canvasContext.save();
 };
 
 CanvasRendererBase.prototype.restore = function (actionFlag) {
@@ -128,17 +99,9 @@
     return;
   }
   if (actionFlag) {
-    this.canvasContext.restore();
     this.globalData.blendMode = 'source-over';
   }
-  var popped = this.contextData.pop();
-  var transform = popped.transform;
-  var opacity = popped.opacity;
-  this.canvasContext.setTransform(transform[0], transform[1], transform[4], transform[5], transform[12], transform[13]);
-  if (this.globalData.currentGlobalAlpha !== opacity) {
-    this.canvasContext.globalAlpha = opacity;
-    this.globalData.currentGlobalAlpha = opacity;
-  }
+  this.contextData.restore(actionFlag);
 };
 
 CanvasRendererBase.prototype.configAnimation = function (animData) {
@@ -164,6 +127,7 @@
   } else {
     this.canvasContext = this.renderConfig.context;
   }
+  this.contextData.setContext(this.canvasContext);
   this.data = animData;
   this.layers = animData.layers;
   this.transformCanvas = {
@@ -300,7 +264,7 @@
     this.checkLayers(num);
   }
 
-  for (i = 0; i < len; i += 1) {
+  for (i = len - 1; i >= 0; i -= 1) {
     if (this.completeLayers || this.elements[i]) {
       this.elements[i].prepareFrame(num - this.layers[i].st);
     }