Commit b1d6a66b authored by Xida Chen's avatar Xida Chen Committed by Commit Bot

Fix a crash by PaintWorklet + custom property animation

https://chromium-review.googlesource.com/c/chromium/src/+/2359370
The above CL made a custom property animation always composited if
it is used by paint worklet, even if the element does not have
"will-change: transform". The approach is that we give a special
ElementId which is uint64_t::max() to the paint worklet element,
and then on the CC side, once we see that element id, we know that
it is an animation associated with paint worklet and we will always
tick that animation.

The problem comes when there are two paint worklet elements.

The short version of the problem is:
CC's animation system doesn't allow two layers with the same
ElementId.

Longer version:
Then these two layers would have the same ElementId when
we try to attach a composited layer with that animation. As a
result, in the AnimationHost::RegisterAnimationForElement(), we
will have two Animation with the same ElementId. Then, the actual
crash happens at ElementAnimations::GetPropertyToElementIdMap(),
at the first DCHECK.

So the solution in this CL is to not DCHECK in certain cases.
The DCHECK actually doesn't make sense in this case where
there is no composited layer. In fact, the callers of the
ElementAnimations::GetPropertyToElementIdMap gives the result
to MutatorHostClient::ElementIsAnimatingChanged, and in there
it only cares about 4 properties. So for the other properties
that it doesn't care, we should not put it in the map.

Bug: 1151755
Change-Id: I5479ccae80f3c89db98d27518ef013dded527ece
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2553691
Commit-Queue: Xida Chen <xidachen@chromium.org>
Reviewed-by: default avatarRobert Flack <flackr@chromium.org>
Reviewed-by: default avatarKevin Ellis <kevers@chromium.org>
Cr-Commit-Position: refs/heads/master@{#831090}
parent 9ab29d69
...@@ -6,6 +6,8 @@ ...@@ -6,6 +6,8 @@
#include <inttypes.h> #include <inttypes.h>
#include <algorithm> #include <algorithm>
#include <string>
#include <utility>
#include "base/stl_util.h" #include "base/stl_util.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
......
...@@ -146,7 +146,7 @@ void AnimationHost::SetHasInlineStyleMutation(bool has_inline_style_mutation) { ...@@ -146,7 +146,7 @@ void AnimationHost::SetHasInlineStyleMutation(bool has_inline_style_mutation) {
void AnimationHost::UpdateRegisteredElementIds(ElementListType changed_list) { void AnimationHost::UpdateRegisteredElementIds(ElementListType changed_list) {
for (auto map_entry : element_to_animations_map_) { for (auto map_entry : element_to_animations_map_) {
// kReservedElementId is reserved for an paint worklet element that animates // kReservedElementId is reserved for a paint worklet element that animates
// a custom property. This element is assumed to always be present as no // a custom property. This element is assumed to always be present as no
// element is needed to tick this animation. // element is needed to tick this animation.
if (mutator_host_client()->IsElementInPropertyTrees(map_entry.first, if (mutator_host_client()->IsElementInPropertyTrees(map_entry.first,
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <stddef.h> #include <stddef.h>
#include <algorithm> #include <algorithm>
#include <utility>
#include "base/numerics/ranges.h" #include "base/numerics/ranges.h"
#include "cc/animation/animation_delegate.h" #include "cc/animation/animation_delegate.h"
...@@ -35,6 +36,12 @@ ElementId CalculateTargetElementId(const ElementAnimations* element_animations, ...@@ -35,6 +36,12 @@ ElementId CalculateTargetElementId(const ElementAnimations* element_animations,
return element_animations->element_id(); return element_animations->element_id();
} }
bool UsingPaintWorklet(int property_index) {
// The set of properties where its animation uses paint worklet infra.
return property_index == TargetProperty::BACKGROUND_COLOR ||
property_index == TargetProperty::CSS_CUSTOM_PROPERTY;
}
} // namespace } // namespace
scoped_refptr<ElementAnimations> ElementAnimations::Create( scoped_refptr<ElementAnimations> ElementAnimations::Create(
...@@ -534,6 +541,18 @@ PropertyToElementIdMap ElementAnimations::GetPropertyToElementIdMap() const { ...@@ -534,6 +541,18 @@ PropertyToElementIdMap ElementAnimations::GetPropertyToElementIdMap() const {
for (int property_index = TargetProperty::FIRST_TARGET_PROPERTY; for (int property_index = TargetProperty::FIRST_TARGET_PROPERTY;
property_index <= TargetProperty::LAST_TARGET_PROPERTY; property_index <= TargetProperty::LAST_TARGET_PROPERTY;
++property_index) { ++property_index) {
// We skip the set of properties that uses paint worklet, because the
// animation is not directly associated with the element its compositing
// layer targets and we use reserved element id when we attach a layer for
// the animation. In that case, the DCHECK here is no longer applicable.
// For example, when we have two paint worklet elements with two different
// custom property animations, then these two KeyframeModels would have
// different element_id and thus fail the first DCHECK here.
// It is not valid to include these properties in the PropertyToElementIdMap
// as they do not map to a single element id. Therefore, these properties
// should not be included in the map.
if (UsingPaintWorklet(property_index))
continue;
TargetProperty::Type property = TargetProperty::Type property =
static_cast<TargetProperty::Type>(property_index); static_cast<TargetProperty::Type>(property_index);
ElementId element_id_for_property; ElementId element_id_for_property;
......
...@@ -4,7 +4,10 @@ ...@@ -4,7 +4,10 @@
#include "cc/animation/keyframe_effect.h" #include "cc/animation/keyframe_effect.h"
#include <algorithm>
#include <memory> #include <memory>
#include <string>
#include <utility>
#include "base/stl_util.h" #include "base/stl_util.h"
#include "base/time/time.h" #include "base/time/time.h"
......
<!DOCTYPE html>
<html>
<body>
<canvas id ="canvas" width="200" height="400"></canvas>
<script>
var canvas = document.getElementById('canvas');
var context = canvas.getContext("2d");
context.fillStyle = 'green';
context.fillRect(0, 0, 100, 100);
context.fillStyle = 'rgb(128, 128, 0)';
context.fillRect(0, 200, 200, 200);
</script>
</body>
</html>
<!DOCTYPE html>
<html class="reftest-wait">
<link rel="help" href="https://drafts.css-houdini.org/css-paint-api/">
<link rel="match" href="two-element-custom-property-animation-ref.html">
<style>
#footainer {
width: 200px;
height: 200px;
}
.fooanimate {
background-image: paint(foo);
animation: expand 5s;
}
#bartainer {
width: 200px;
height: 200px;
}
.baranimate {
background-image: paint(bar);
animation: colorChange 5s;
}
@keyframes expand {
0% { --foo: 0; }
0.01% { --foo: 100; }
99% { --foo: 100; }
100% { --foo: 200; }
}
@keyframes colorChange {
0% { --bar: rgb(255, 0, 0); }
0.01% { --bar: rgb(128, 128, 0); }
99% { --bar: rgb(128, 128, 0); }
100% { --bar: rgb(0, 255, 0); }
}
</style>
<script src="/common/reftest-wait.js"></script>
<script src="/common/worklet-reftest.js"></script>
<body>
<div id="footainer"></div>
<div id="bartainer"></div>
<script id="code" type="text/worklet">
registerPaint('foo', class {
static get inputProperties() { return ['--foo']; }
paint(ctx, geom, properties) {
let fooValue = parseFloat(properties.get('--foo').toString());
ctx.fillStyle = 'green';
ctx.fillRect(0, 0, fooValue, fooValue);
}
});
registerPaint('bar', class {
static get inputProperties() { return ['--bar']; }
paint(ctx, geom, properties) {
ctx.fillStyle = properties.get('--bar').toString();
ctx.fillRect(0, 0, 200, 200);
}
});
</script>
<script type="text/javascript">
CSS.registerProperty({
name: '--foo',
syntax: '<number>',
initialValue: '0',
inherits: false
});
CSS.registerProperty({
name: '--bar',
syntax: '<color>',
initialValue: 'rgb(0, 0, 0)',
inherits: false
});
</script>
<script>
var code = document.getElementById('code').textContent;
var blob = new Blob([code], {type: 'text/javascript'});
CSS.paintWorklet.addModule(URL.createObjectURL(blob)).then(function() {
document.getElementById('footainer').classList.add('fooanimate');
document.getElementById('bartainer').classList.add('baranimate');
requestAnimationFrame(function() {
takeScreenshot();
});
});
</script>
</body>
</html>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment