From 80354b5d220888f729b22fbaeecf17a96516868a Mon Sep 17 00:00:00 2001 From: jeripeierSBB Date: Tue, 15 Dec 2020 17:40:47 +0100 Subject: [PATCH] fix(animations): allow animations on elements in the shadow DOM When determining whether to run an animation, the `TransitionAnimationPlayer` checks to see if a DOM element is attached to the document. This is done by checking to see if the element is "contained" by the document body node. Previously, if the element was inside a shadow DOM, the engine would determine that the element was not attached, even if the shadow DOM's host was attached to the document. This commit updates the `containsElement()` method on `AnimationDriver` implementations to also include shadow DOM elements as being contained if their shadow host element is contained. Further, when using CSS keyframes to trigger animations, the styling was always added to the `head` element of the document, even for animations on elements within a shadow DOM. This meant that those elements never receive those styles and the animation would not run. This commit updates the insertion of these styles so that they are added, to the element's "root node", which is the nearest shadow DOM host, or the `head` of the document if the element is not in a shadow DOM. Closes #25672 --- goldens/size-tracking/aio-payloads.json | 4 +- .../css_keyframes/css_keyframes_driver.ts | 16 +++++++- .../animations/browser/src/render/shared.ts | 16 ++++++-- packages/animations/browser/test/BUILD.bazel | 1 + .../css_keyframes_driver_spec.ts | 41 +++++++++++++++---- .../web_animations_driver_spec.ts | 18 +++++++- .../linker/projection_integration_spec.ts | 7 +--- 7 files changed, 81 insertions(+), 22 deletions(-) diff --git a/goldens/size-tracking/aio-payloads.json b/goldens/size-tracking/aio-payloads.json index b8d341e40e2bab..1fe556f329e492 100755 --- a/goldens/size-tracking/aio-payloads.json +++ b/goldens/size-tracking/aio-payloads.json @@ -12,7 +12,7 @@ "master": { "uncompressed": { "runtime-es2015": 3033, - "main-es2015": 451600, + "main-es2015": 452289, "polyfills-es2015": 52215 } } @@ -21,7 +21,7 @@ "master": { "uncompressed": { "runtime-es2015": 3153, - "main-es2015": 437306, + "main-es2015": 437924, "polyfills-es2015": 52493 } } diff --git a/packages/animations/browser/src/render/css_keyframes/css_keyframes_driver.ts b/packages/animations/browser/src/render/css_keyframes/css_keyframes_driver.ts index c4a6d20987614d..e60022b77acf87 100644 --- a/packages/animations/browser/src/render/css_keyframes/css_keyframes_driver.ts +++ b/packages/animations/browser/src/render/css_keyframes/css_keyframes_driver.ts @@ -20,7 +20,6 @@ const TAB_SPACE = ' '; export class CssKeyframesDriver implements AnimationDriver { private _count = 0; - private readonly _head: any = document.querySelector('head'); validateStyleProperty(prop: string): boolean { return validateStyleProperty(prop); @@ -107,7 +106,8 @@ export class CssKeyframesDriver implements AnimationDriver { const animationName = `${KEYFRAMES_NAME_PREFIX}${this._count++}`; const kfElm = this.buildKeyframeElement(element, animationName, keyframes); - document.querySelector('head')!.appendChild(kfElm); + const nodeToAppendKfElm = findNodeToAppendKfElm(element); + nodeToAppendKfElm.appendChild(kfElm); const specialStyles = packageNonAnimatableStyles(element, keyframes); const player = new CssKeyframesPlayer( @@ -118,6 +118,18 @@ export class CssKeyframesDriver implements AnimationDriver { } } +// TODO: Once we drop IE 11 support, method can be simplified +// to the native browser function `getRootNode` +function findNodeToAppendKfElm(element: any): Node { + while (element && element !== document.documentElement) { + if (element.shadowRoot) { + return element.shadowRoot; + } + element = element.parentNode || element.host; + } + return document.querySelector('head')!; +} + function flattenKeyframesIntoStyles(keyframes: null|{[key: string]: any}| {[key: string]: any}[]): {[key: string]: any} { let flatKeyframes: {[key: string]: any} = {}; diff --git a/packages/animations/browser/src/render/shared.ts b/packages/animations/browser/src/render/shared.ts index e2040f9aa15d2d..58285ce47d0098 100644 --- a/packages/animations/browser/src/render/shared.ts +++ b/packages/animations/browser/src/render/shared.ts @@ -161,9 +161,19 @@ let _query: (element: any, selector: string, multi: boolean) => any[] = const _isNode = isNode(); if (_isNode || typeof Element !== 'undefined') { // this is well supported in all browsers - _contains = (elm1: any, elm2: any) => { - return elm1.contains(elm2) as boolean; - }; + if (!isBrowser()) { + _contains = (elm1, elm2) => elm1.contains(elm2); + } else { + _contains = (elm1, elm2) => { + while (elm2 && elm2 !== document.documentElement) { + if (elm2 === elm1) { + return true; + } + elm2 = elm2.parentNode || elm2.host; // consider host to support shadow DOM + } + return false; + }; + } _matches = (() => { if (_isNode || Element.prototype.matches) { diff --git a/packages/animations/browser/test/BUILD.bazel b/packages/animations/browser/test/BUILD.bazel index 79b21d2ef2d229..cf52e08ab4934c 100644 --- a/packages/animations/browser/test/BUILD.bazel +++ b/packages/animations/browser/test/BUILD.bazel @@ -24,6 +24,7 @@ ts_library( "//packages/animations/browser/testing", "//packages/core", "//packages/core/testing", + "//packages/platform-browser/testing", ], ) diff --git a/packages/animations/browser/test/render/css_keyframes/css_keyframes_driver_spec.ts b/packages/animations/browser/test/render/css_keyframes/css_keyframes_driver_spec.ts index c062712e8da238..bf1f582db77c52 100644 --- a/packages/animations/browser/test/render/css_keyframes/css_keyframes_driver_spec.ts +++ b/packages/animations/browser/test/render/css_keyframes/css_keyframes_driver_spec.ts @@ -5,7 +5,9 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {fakeAsync, flushMicrotasks, TestBed} from '@angular/core/testing'; +import {fakeAsync, flushMicrotasks} from '@angular/core/testing'; + +import {browserDetection} from '@angular/platform-browser/testing/src/browser_util'; import {CssKeyframesDriver} from '../../../src/render/css_keyframes/css_keyframes_driver'; import {CssKeyframesPlayer} from '../../../src/render/css_keyframes/css_keyframes_player'; @@ -104,7 +106,7 @@ describe('CssKeyframesDriver tests', () => { expect(easing).toEqual('ease-out'); }); - it('should animate until the `animationend` method is emitted, but stil retain the