Skip to content

Commit 64b03c4

Browse files
authored
Animations: hardening to prevent animations on first page (#14404)
1 parent 8efa363 commit 64b03c4

File tree

4 files changed

+84
-2
lines changed

4 files changed

+84
-2
lines changed

includes/AMP/Story_Sanitizer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public function sanitize(): void {
5555
$this->add_poster_images( $this->dom, $this->args['poster_images'] );
5656
// This needs to be called before use_semantic_heading_tags() because it relies on the style attribute.
5757
$this->deduplicate_inline_styles( $this->dom );
58+
$this->disable_first_page_animations( $this->dom );
5859
$this->add_video_cache( $this->dom, $this->args['video_cache'] );
5960
$this->remove_blob_urls( $this->dom );
6061
$this->sanitize_srcset( $this->dom );

includes/AMP/Traits/Sanitization_Utils.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,39 @@ private function deduplicate_inline_styles( $document ): void {
470470
}
471471
}
472472

473+
/**
474+
* Adds CSS to reset animations for elements on the first page.
475+
*
476+
* Animations on the first page of a story are not allowed,
477+
* and removed at both the creation & serialization level (in the output package),
478+
* as well as the renderer (in AMP itself).
479+
*
480+
* This inline CSS adds another layer of defense, especially for older stories
481+
* that might still have some animation data on the first page.
482+
*
483+
* @since 1.43.0
484+
*
485+
* @param Document|AMP_Document $document Document instance.
486+
*/
487+
private function disable_first_page_animations( $document ): void {
488+
$animated_elements = $document->xpath->query( './/div[ contains( @class, "animation-wrapper" ) ]' );
489+
490+
if ( ! $animated_elements || 0 === $animated_elements->length ) {
491+
return;
492+
}
493+
494+
$style_element = $document->createElement( 'style' );
495+
496+
if ( ! $style_element ) {
497+
return;
498+
}
499+
500+
$style_rule = $document->createTextNode( 'amp-story-page:first-of-type .animation-wrapper { --initial-opacity: 1; --initial-transform: none; }' );
501+
$style_element->appendChild( $style_rule );
502+
503+
$document->head->appendChild( $style_element );
504+
}
505+
473506
/**
474507
* Enables using video cache by adding the necessary attribute to `<amp-video>`
475508
*

packages/output/src/story.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,10 @@ function OutputStory({
102102
poster-portrait-src={featuredMediaUrl}
103103
background-audio={backgroundAudio?.resource?.src ?? undefined}
104104
>
105-
{pages.map((page) => (
105+
{pages.map((page, index) => (
106106
<OutputPage
107107
key={page.id}
108-
page={page}
108+
page={index === 0 ? { ...page, animations: undefined } : page}
109109
defaultAutoAdvance={autoAdvance}
110110
defaultPageDuration={defaultPageDuration}
111111
flags={flags}

packages/output/src/utils/test/getStoryMarkup.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,54 @@ describe('getStoryMarkup', () => {
119119
} as TextElement,
120120
],
121121
},
122+
{
123+
id: '2',
124+
backgroundColor: { color: { r: 255, g: 255, b: 255 } },
125+
animations: [
126+
{
127+
id: '1',
128+
targets: ['2'],
129+
type: AnimationType.Bounce,
130+
duration: 1000,
131+
},
132+
{ id: '2', targets: ['2'], type: AnimationType.Spin, duration: 1000 },
133+
],
134+
elements: [
135+
{
136+
id: '2',
137+
type: ElementType.Text,
138+
x: 0,
139+
y: 0,
140+
width: 211,
141+
height: 221,
142+
rotationAngle: 1,
143+
content: 'Hello World',
144+
font: {
145+
family: 'Roboto',
146+
service: FontService.GoogleFonts,
147+
fallbacks: ['Arial', 'sans-serif'],
148+
},
149+
color: {
150+
color: {
151+
r: 255,
152+
g: 255,
153+
b: 255,
154+
a: 0.5,
155+
},
156+
},
157+
padding: {
158+
locked: false,
159+
vertical: 0,
160+
horizontal: 0,
161+
},
162+
marginOffset: 0,
163+
fontSize: 10,
164+
lineHeight: 1,
165+
textAlign: 'center',
166+
backgroundColor: { color: { r: 255, g: 255, b: 255 } },
167+
} as TextElement,
168+
],
169+
},
122170
];
123171
const markup = getStoryMarkup(story, pages, metadata);
124172
expect(markup).toContain('Hello World');

0 commit comments

Comments
 (0)