From a99aa2904070a0543e84ba51115f2118319d18d2 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 (#40134) 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 PR Close #40134 --- goldens/size-tracking/aio-payloads.json | 4 +- .../css_keyframes/css_keyframes_driver.ts | 12 +++++- .../animations/browser/src/render/shared.ts | 17 ++++++-- 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, 77 insertions(+), 23 deletions(-) diff --git a/goldens/size-tracking/aio-payloads.json b/goldens/size-tracking/aio-payloads.json index b5ab7629ec..6ffc1a1e26 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": 452892, + "main-es2015": 453111, "polyfills-es2015": 55230 } } @@ -21,7 +21,7 @@ "master": { "uncompressed": { "runtime-es2015": 3153, - "main-es2015": 438598, + "main-es2015": 438824, "polyfills-es2015": 55230 } } 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 c4a6d20987..3c26a431d5 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 = findNodeToAppendKeyframeElement(element); + nodeToAppendKfElm.appendChild(kfElm); const specialStyles = packageNonAnimatableStyles(element, keyframes); const player = new CssKeyframesPlayer( @@ -118,6 +118,14 @@ export class CssKeyframesDriver implements AnimationDriver { } } +function findNodeToAppendKeyframeElement(element: any): Node { + const rootNode = element.getRootNode?.(); + if (typeof ShadowRoot !== 'undefined' && rootNode instanceof ShadowRoot) { + return rootNode; + } + return document.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 e2040f9aa1..f6356d39c1 100644 --- a/packages/animations/browser/src/render/shared.ts +++ b/packages/animations/browser/src/render/shared.ts @@ -160,10 +160,19 @@ let _query: (element: any, selector: string, multi: boolean) => any[] = // and utility methods exist. 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 79b21d2ef2..cf52e08ab4 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 c062712e8d..bf1f582db7 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