From 842f52e841b61ab1389a316b3d0d0f02f34ec5e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Wed, 21 Dec 2016 14:14:45 -0800 Subject: [PATCH] fix(animations): always recover from a failed animation step (#13604) --- .../animation/animation_integration_spec.ts | 48 +++++++++++++++++-- .../platform-browser/src/dom/dom_renderer.ts | 10 ++-- .../src/private_import_core.ts | 2 + .../platform-server/src/server_renderer.ts | 10 ++-- 4 files changed, 61 insertions(+), 9 deletions(-) diff --git a/modules/@angular/core/test/animation/animation_integration_spec.ts b/modules/@angular/core/test/animation/animation_integration_spec.ts index 77de31dd5b..a83eed562e 100644 --- a/modules/@angular/core/test/animation/animation_integration_spec.ts +++ b/modules/@angular/core/test/animation/animation_integration_spec.ts @@ -17,13 +17,11 @@ import {WebAnimationsDriver} from '@angular/platform-browser/src/dom/web_animati import {WebAnimationsPlayer} from '@angular/platform-browser/src/dom/web_animations_player'; import {expect} from '@angular/platform-browser/testing/matchers'; import {MockAnimationDriver} from '@angular/platform-browser/testing/mock_animation_driver'; - -import {DomAnimatePlayer} from '../../../platform-browser/src/dom/dom_animate_player'; import {ApplicationRef, Component, HostBinding, HostListener, NgModule, NgZone, destroyPlatform} from '../../index'; import {DEFAULT_STATE} from '../../src/animation/animation_constants'; import {AnimationGroupPlayer} from '../../src/animation/animation_group_player'; import {AnimationKeyframe} from '../../src/animation/animation_keyframe'; -import {AnimationPlayer} from '../../src/animation/animation_player'; +import {AnimationPlayer, NoOpAnimationPlayer} from '../../src/animation/animation_player'; import {AnimationStyles} from '../../src/animation/animation_styles'; import {AnimationTransitionEvent} from '../../src/animation/animation_transition_event'; import {AUTO_STYLE, animate, group, keyframes, sequence, state, style, transition, trigger} from '../../src/animation/metadata'; @@ -2244,6 +2242,42 @@ function declareTests({useJit}: {useJit: boolean}) { })); }); + describe('error handling', () => { + it('should recover if an animation driver or player throws an error during an animation', + fakeAsync(() => { + TestBed.configureTestingModule({ + declarations: [DummyIfCmp], + providers: [{provide: AnimationDriver, useClass: ErroneousAnimationDriver}], + imports: [CommonModule] + }); + TestBed.overrideComponent(DummyIfCmp, { + set: { + template: ` +
+ `, + animations: [trigger('myAnimation', [transition( + '* => *', + [ + animate(1000, style({transform: 'noooooo'})), + ])])] + } + }); + + const fixture = TestBed.createComponent(DummyIfCmp); + const cmp = fixture.componentInstance; + let started = false; + let done = false; + cmp.callback1 = (event: AnimationTransitionEvent) => started = true; + cmp.callback2 = (event: AnimationTransitionEvent) => done = true; + cmp.exp = true; + fixture.detectChanges(); + flushMicrotasks(); + + expect(started).toBe(true); + expect(done).toBe(true); + })); + }); + describe('full animation integration tests', () => { if (!getDOM().supportsWebAnimation()) return; @@ -2537,3 +2571,11 @@ class ExtendedWebAnimationsDriver extends WebAnimationsDriver { return player; } } + +class ErroneousAnimationDriver extends MockAnimationDriver { + animate( + element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], + duration: number, delay: number, easing: string): WebAnimationsPlayer { + throw new Error(); + } +} diff --git a/modules/@angular/platform-browser/src/dom/dom_renderer.ts b/modules/@angular/platform-browser/src/dom/dom_renderer.ts index bf64552c87..608d6a0e30 100644 --- a/modules/@angular/platform-browser/src/dom/dom_renderer.ts +++ b/modules/@angular/platform-browser/src/dom/dom_renderer.ts @@ -9,7 +9,7 @@ import {APP_ID, Inject, Injectable, RenderComponentType, Renderer, RootRenderer, ViewEncapsulation} from '@angular/core'; import {isBlank, isPresent, stringify} from '../facade/lang'; -import {AnimationKeyframe, AnimationPlayer, AnimationStyles, DirectRenderer, RenderDebugInfo} from '../private_import_core'; +import {AnimationKeyframe, AnimationPlayer, AnimationStyles, DirectRenderer, NoOpAnimationPlayer, RenderDebugInfo} from '../private_import_core'; import {AnimationDriver} from './animation_driver'; import {DOCUMENT} from './dom_tokens'; @@ -262,8 +262,12 @@ export class DomRenderer implements Renderer { element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number, easing: string, previousPlayers: AnimationPlayer[] = []): AnimationPlayer { - return this._animationDriver.animate( - element, startingStyles, keyframes, duration, delay, easing, previousPlayers); + try { + return this._animationDriver.animate( + element, startingStyles, keyframes, duration, delay, easing, previousPlayers); + } catch (e) { + return new NoOpAnimationPlayer(); + } } } diff --git a/modules/@angular/platform-server/src/private_import_core.ts b/modules/@angular/platform-server/src/private_import_core.ts index 317204cfdb..375e90aea3 100644 --- a/modules/@angular/platform-server/src/private_import_core.ts +++ b/modules/@angular/platform-server/src/private_import_core.ts @@ -21,3 +21,5 @@ export type RenderDebugInfo = typeof r._RenderDebugInfo; export var RenderDebugInfo: typeof r.RenderDebugInfo = r.RenderDebugInfo; export type DebugDomRootRenderer = typeof r._DebugDomRootRenderer; export var DebugDomRootRenderer: typeof r.DebugDomRootRenderer = r.DebugDomRootRenderer; +export type NoOpAnimationPlayer = typeof r._NoOpAnimationPlayer; +export var NoOpAnimationPlayer: typeof r.NoOpAnimationPlayer = r.NoOpAnimationPlayer; diff --git a/modules/@angular/platform-server/src/server_renderer.ts b/modules/@angular/platform-server/src/server_renderer.ts index 5a09ff65c4..e59b976508 100644 --- a/modules/@angular/platform-server/src/server_renderer.ts +++ b/modules/@angular/platform-server/src/server_renderer.ts @@ -10,7 +10,7 @@ import {APP_ID, Inject, Injectable, NgZone, RenderComponentType, Renderer, RootR import {AnimationDriver, DOCUMENT} from '@angular/platform-browser'; import {isBlank, isPresent, stringify} from './facade/lang'; -import {AnimationKeyframe, AnimationPlayer, AnimationStyles, RenderDebugInfo} from './private_import_core'; +import {AnimationKeyframe, AnimationPlayer, AnimationStyles, NoOpAnimationPlayer, RenderDebugInfo} from './private_import_core'; import {NAMESPACE_URIS, SharedStylesHost, flattenStyles, getDOM, isNamespaced, shimContentAttribute, shimHostAttribute, splitNamespace} from './private_import_platform-browser'; const TEMPLATE_COMMENT_TEXT = 'template bindings={}'; @@ -208,8 +208,12 @@ export class ServerRenderer implements Renderer { element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number, easing: string, previousPlayers: AnimationPlayer[] = []): AnimationPlayer { - return this._animationDriver.animate( - element, startingStyles, keyframes, duration, delay, easing, previousPlayers); + try { + return this._animationDriver.animate( + element, startingStyles, keyframes, duration, delay, easing, previousPlayers); + } catch (e) { + return new NoOpAnimationPlayer(); + } } }