fix(animations): change throw -> trace to avoid unnecessary app crash (#5475)

* fix(animations): change throw -> trace to avoid unnecessary app crash

Fixes major cause of crashes/bugs in production apps using animation.

* Fix fix animation throw (#1)

* chore(tests): Cleanup code snippets comments

* refactor(animations): Plat-specific cancel and play methods refactored
This commit is contained in:
Nathan Walker
2018-03-08 10:42:33 -08:00
committed by Svetoslav
parent 8141737f74
commit fa80355464
5 changed files with 82 additions and 31 deletions

View File

@ -54,6 +54,31 @@ export function test_AnimatingProperties(done) {
// << animation-properties
}
export function test_PlayRejectsWhenAlreadyPlayingAnimation(done) {
let label = prepareTest();
var animation = label.createAnimation({ translate: { x: 100, y: 100 }, duration: 5 });
animation.play();
animation.play().then(() => {
// should never get here
throw new Error("Already playing.");
}, (e) => {
TKUnit.assert(animation.isPlaying === true, "animation.isPlaying should be true since it's currently playing.");
if (e === "Animation is already playing.") {
done();
}
});
}
export function test_CancelIgnoredWhenNotPlayingAnimation() {
let label = prepareTest();
var animation = label.createAnimation({ translate: { x: 100, y: 100 }, duration: 5 });
animation.cancel(); // should not throw
TKUnit.assert(!animation.isPlaying, "animation.isPlaying should be falsey since it was never played.");
}
export function test_CancellingAnimation(done) {
let label = prepareTest();

View File

@ -10,9 +10,9 @@ import { View } from "../core/view";
// Types.
import { Color } from "../../color";
import { isEnabled as traceEnabled, write as traceWrite, categories as traceCategories } from "../../trace";
import { isEnabled as traceEnabled, write as traceWrite, categories as traceCategories, messageType as traceType } from "../../trace";
export { Color, traceEnabled, traceWrite, traceCategories };
export { Color, traceEnabled, traceWrite, traceCategories, traceType };
export { AnimationPromise } from ".";
export module Properties {
@ -84,11 +84,16 @@ export abstract class AnimationBase implements AnimationBaseDefinition {
abstract _resolveAnimationCurve(curve: any): any;
public play(): AnimationPromiseDefinition {
if (this.isPlaying) {
throw new Error("Animation is already playing.");
protected _rejectAlreadyPlaying(): AnimationPromiseDefinition{
const reason = "Animation is already playing.";
traceWrite(reason, traceCategories.Animation, traceType.warn);
return <AnimationPromiseDefinition>new Promise<void>((resolve, reject) => {
reject(reason);
});
}
public play(): AnimationPromiseDefinition {
// We have to actually create a "Promise" due to a bug in the v8 engine and decedent promises
// We just cast it to a animationPromise so that all the rest of the code works fine
var animationFinishedPromise = <AnimationPromiseDefinition>new Promise<void>((resolve, reject) => {
@ -123,9 +128,7 @@ export abstract class AnimationBase implements AnimationBaseDefinition {
}
public cancel(): void {
if (!this.isPlaying) {
throw new Error("Animation is not currently playing.");
}
// Implemented in platform specific files
}
public get isPlaying(): boolean {

View File

@ -2,7 +2,7 @@
import { AnimationDefinition } from ".";
import { View } from "../core/view";
import { AnimationBase, Properties, PropertyAnimation, CubicBezierAnimationCurve, AnimationPromise, Color, traceWrite, traceEnabled, traceCategories } from "./animation-common";
import { AnimationBase, Properties, PropertyAnimation, CubicBezierAnimationCurve, AnimationPromise, Color, traceWrite, traceEnabled, traceCategories, traceType } from "./animation-common";
import {
opacityProperty, backgroundColorProperty, rotateProperty,
translateXProperty, translateYProperty, scaleXProperty, scaleYProperty
@ -135,6 +135,10 @@ export class Animation extends AnimationBase {
}
public play(): AnimationPromise {
if (this.isPlaying) {
return this._rejectAlreadyPlaying();
}
let animationFinishedPromise = super.play();
this._animators = new Array<android.animation.Animator>();
@ -170,10 +174,13 @@ export class Animation extends AnimationBase {
}
public cancel(): void {
super.cancel();
if (traceEnabled()) {
traceWrite("Cancelling AnimatorSet.", traceCategories.Animation);
if (!this.isPlaying) {
traceWrite("Animation is not currently playing.", traceCategories.Animation, traceType.warn);
return;
}
traceWrite("Cancelling AnimatorSet.", traceCategories.Animation);
this._animatorSet.cancel();
}

View File

@ -1,7 +1,7 @@
import { AnimationDefinition } from ".";
import { View } from "../core/view";
import { AnimationBase, Properties, PropertyAnimation, CubicBezierAnimationCurve, AnimationPromise, traceWrite, traceEnabled, traceCategories } from "./animation-common";
import { AnimationBase, Properties, PropertyAnimation, CubicBezierAnimationCurve, AnimationPromise, traceWrite, traceEnabled, traceCategories, traceType } from "./animation-common";
import {
opacityProperty, backgroundColorProperty, rotateProperty,
translateXProperty, translateYProperty, scaleXProperty, scaleYProperty
@ -210,6 +210,10 @@ export class Animation extends AnimationBase {
}
public play(): AnimationPromise {
if (this.isPlaying) {
return this._rejectAlreadyPlaying();
}
let animationFinishedPromise = super.play();
this._finishedAnimations = 0;
this._cancelledAnimations = 0;
@ -218,7 +222,10 @@ export class Animation extends AnimationBase {
}
public cancel(): void {
super.cancel();
if (!this.isPlaying) {
traceWrite("Animation is not currently playing.", traceCategories.Animation, traceType.warn);
return;
}
let i = 0;
let length = this._mergedPropertyAnimations.length;

View File

@ -12,6 +12,8 @@ import { View, Color } from "../core/view";
import { AnimationCurve } from "../enums";
import { isEnabled as traceEnabled, write as traceWrite, categories as traceCategories, messageType as traceType } from "../../trace";
// Types.
import { unsetValue } from "../core/properties";
import { Animation } from "./animation";
@ -143,7 +145,11 @@ export class KeyframeAnimation implements KeyframeAnimationDefinition {
}
public cancel() {
if (this._isPlaying) {
if (!this.isPlaying) {
traceWrite("Keyframe animation is already playing.", traceCategories.Animation, traceType.warn);
return;
}
this._isPlaying = false;
for (let i = this._nativeAnimations.length - 1; i >= 0; i--) {
let animation = this._nativeAnimations[i];
@ -157,11 +163,14 @@ export class KeyframeAnimation implements KeyframeAnimationDefinition {
}
this._rejectAnimationFinishedPromise();
}
}
public play(view: View): Promise<void> {
if (this._isPlaying) {
throw new Error("Animation is already playing.");
const reason = "Keyframe animation is already playing.";
traceWrite(reason, traceCategories.Animation, traceType.warn);
return new Promise<void>((resolve, reject) => {
reject(reason);
});
}
let animationFinishedPromise = new Promise<void>((resolve, reject) => {