mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-26 10:27:32 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
19
dom/animation/test/chrome/file_animate_xrays.html
Normal file
19
dom/animation/test/chrome/file_animate_xrays.html
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset=utf-8>
|
||||
<script>
|
||||
Element.prototype.animate = function() {
|
||||
throw 'Called animate() as defined in content document';
|
||||
}
|
||||
// Bug 1211783: Use KeyframeEffect (not KeyframeEffectReadOnly) here
|
||||
for (var obj of [KeyframeEffectReadOnly, Animation]) {
|
||||
obj = function() {
|
||||
throw 'Called overridden ' + String(obj) + ' constructor';
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<body>
|
||||
<div id="target"></div>
|
||||
</body>
|
||||
</html>
|
||||
31
dom/animation/test/chrome/test_animate_xrays.html
Normal file
31
dom/animation/test/chrome/test_animate_xrays.html
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<!doctype html>
|
||||
<head>
|
||||
<meta charset=utf-8>
|
||||
<script type="application/javascript" src="../testharness.js"></script>
|
||||
<script type="application/javascript" src="../testharnessreport.js"></script>
|
||||
<script type="application/javascript" src="../testcommon.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1045994"
|
||||
target="_blank">Mozilla Bug 1045994</a>
|
||||
<div id="log"></div>
|
||||
<iframe id="iframe"
|
||||
src="http://example.org/tests/dom/animation/test/chrome/file_animate_xrays.html"></iframe>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
var win = document.getElementById('iframe').contentWindow;
|
||||
|
||||
async_test(function(t) {
|
||||
window.addEventListener('load', t.step_func(function() {
|
||||
var target = win.document.getElementById('target');
|
||||
var anim = target.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
|
||||
// In the x-ray case, the frames object will be given an opaque wrapper
|
||||
// so it won't be possible to fetch any frames from it.
|
||||
assert_equals(anim.effect.getKeyframes().length, 0);
|
||||
t.done();
|
||||
}));
|
||||
}, 'Calling animate() across x-rays');
|
||||
|
||||
</script>
|
||||
</body>
|
||||
1177
dom/animation/test/chrome/test_animation_observers.html
Normal file
1177
dom/animation/test/chrome/test_animation_observers.html
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,957 @@
|
|||
<!doctype html>
|
||||
<head>
|
||||
<meta charset=utf-8>
|
||||
<title>Bug 1196114 - Test metadata related to which animation properties
|
||||
are running on the compositor</title>
|
||||
<script type="application/javascript" src="../testharness.js"></script>
|
||||
<script type="application/javascript" src="../testharnessreport.js"></script>
|
||||
<script type="application/javascript" src="../testcommon.js"></script>
|
||||
<style>
|
||||
.compositable {
|
||||
/* Element needs geometry to be eligible for layerization */
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: white;
|
||||
}
|
||||
@keyframes fade {
|
||||
from { opacity: 1 }
|
||||
to { opacity: 0 }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1196114"
|
||||
target="_blank">Mozilla Bug 1196114</a>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// This is used for obtaining localized strings.
|
||||
var gStringBundle;
|
||||
|
||||
W3CTest.runner.requestLongerTimeout(2);
|
||||
|
||||
SpecialPowers.pushPrefEnv({ "set": [
|
||||
["general.useragent.locale", "en-US"],
|
||||
// Need to set devPixelsPerPx explicitly to gain
|
||||
// consistent pixel values in warning messages
|
||||
// regardless of platform DPIs.
|
||||
["layout.css.devPixelsPerPx", 1],
|
||||
] },
|
||||
start);
|
||||
|
||||
function compare_property_state(a, b) {
|
||||
if (a.property > b.property) {
|
||||
return -1;
|
||||
} else if (a.property < b.property) {
|
||||
return 1;
|
||||
}
|
||||
if (a.runningOnCompositor != b.runningOnCompositor) {
|
||||
return a.runningOnCompositor ? 1 : -1;
|
||||
}
|
||||
return a.warning > b.warning ? -1 : 1;
|
||||
}
|
||||
|
||||
function assert_animation_property_state_equals(actual, expected) {
|
||||
assert_equals(actual.length, expected.length, 'Number of properties');
|
||||
|
||||
var sortedActual = actual.sort(compare_property_state);
|
||||
var sortedExpected = expected.sort(compare_property_state);
|
||||
|
||||
for (var i = 0; i < sortedActual.length; i++) {
|
||||
assert_equals(sortedActual[i].property,
|
||||
sortedExpected[i].property,
|
||||
'CSS property name should match');
|
||||
assert_equals(sortedActual[i].runningOnCompositor,
|
||||
sortedExpected[i].runningOnCompositor,
|
||||
'runningOnCompositor property should match');
|
||||
if (sortedExpected[i].warning instanceof RegExp) {
|
||||
assert_regexp_match(sortedActual[i].warning,
|
||||
sortedExpected[i].warning,
|
||||
'warning message should match');
|
||||
} else if (sortedExpected[i].warning) {
|
||||
assert_equals(sortedActual[i].warning,
|
||||
gStringBundle.GetStringFromName(sortedExpected[i].warning),
|
||||
'warning message should match');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the animation is running on compositor and
|
||||
// warning property is not set for the CSS property regardless
|
||||
// expected values.
|
||||
function assert_property_state_on_compositor(actual, expected) {
|
||||
assert_equals(actual.length, expected.length);
|
||||
|
||||
var sortedActual = actual.sort(compare_property_state);
|
||||
var sortedExpected = expected.sort(compare_property_state);
|
||||
|
||||
for (var i = 0; i < sortedActual.length; i++) {
|
||||
assert_equals(sortedActual[i].property,
|
||||
sortedExpected[i].property,
|
||||
'CSS property name should match');
|
||||
assert_true(sortedActual[i].runningOnCompositor,
|
||||
'runningOnCompositor property should be true on ' +
|
||||
sortedActual[i].property);
|
||||
assert_not_exists(sortedActual[i], 'warning',
|
||||
'warning property should not be set');
|
||||
}
|
||||
}
|
||||
|
||||
var gAnimationsTests = [
|
||||
{
|
||||
desc: 'animations on compositor',
|
||||
frames: {
|
||||
opacity: [0, 1]
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
property: 'opacity',
|
||||
runningOnCompositor: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'animations on main thread',
|
||||
frames: {
|
||||
backgroundColor: ['white', 'red']
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
property: 'background-color',
|
||||
runningOnCompositor: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'animations on both threads',
|
||||
frames: {
|
||||
backgroundColor: ['white', 'red'],
|
||||
transform: ['translate(0px)', 'translate(100px)']
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
property: 'background-color',
|
||||
runningOnCompositor: false
|
||||
},
|
||||
{
|
||||
property: 'transform',
|
||||
runningOnCompositor: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'two animation properties on compositor thread',
|
||||
frames: {
|
||||
opacity: [0, 1],
|
||||
transform: ['translate(0px)', 'translate(100px)']
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
property: 'opacity',
|
||||
runningOnCompositor: true
|
||||
},
|
||||
{
|
||||
property: 'transform',
|
||||
runningOnCompositor: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'opacity on compositor with animation of geometric properties',
|
||||
frames: {
|
||||
width: ['100px', '200px'],
|
||||
opacity: [0, 1]
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
property: 'width',
|
||||
runningOnCompositor: false
|
||||
},
|
||||
{
|
||||
property: 'opacity',
|
||||
runningOnCompositor: true
|
||||
}
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
// Test cases that check results of adding/removing a 'width' property on the
|
||||
// same animation object.
|
||||
var gAnimationWithGeometricKeyframeTests = [
|
||||
{
|
||||
desc: 'transform',
|
||||
frames: {
|
||||
transform: ['translate(0px)', 'translate(100px)']
|
||||
},
|
||||
expected: {
|
||||
withoutGeometric: [
|
||||
{
|
||||
property: 'transform',
|
||||
runningOnCompositor: true
|
||||
}
|
||||
],
|
||||
withGeometric: [
|
||||
{
|
||||
property: 'width',
|
||||
runningOnCompositor: false
|
||||
},
|
||||
{
|
||||
property: 'transform',
|
||||
runningOnCompositor: false,
|
||||
warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: 'opacity and transform',
|
||||
frames: {
|
||||
opacity: [0, 1],
|
||||
transform: ['translate(0px)', 'translate(100px)']
|
||||
},
|
||||
expected: {
|
||||
withoutGeometric: [
|
||||
{
|
||||
property: 'opacity',
|
||||
runningOnCompositor: true
|
||||
},
|
||||
{
|
||||
property: 'transform',
|
||||
runningOnCompositor: true
|
||||
}
|
||||
],
|
||||
withGeometric: [
|
||||
{
|
||||
property: 'width',
|
||||
runningOnCompositor: false
|
||||
},
|
||||
{
|
||||
property: 'opacity',
|
||||
runningOnCompositor: true
|
||||
},
|
||||
{
|
||||
property: 'transform',
|
||||
runningOnCompositor: false,
|
||||
warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
// Performance warning tests that set and clear a style property.
|
||||
var gPerformanceWarningTestsStyle = [
|
||||
{
|
||||
desc: 'preserve-3d transform',
|
||||
frames: {
|
||||
transform: ['translate(0px)', 'translate(100px)']
|
||||
},
|
||||
style: 'transform-style: preserve-3d',
|
||||
expected: [
|
||||
{
|
||||
property: 'transform',
|
||||
runningOnCompositor: false,
|
||||
warning: 'CompositorAnimationWarningTransformPreserve3D'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'transform with backface-visibility:hidden',
|
||||
frames: {
|
||||
transform: ['translate(0px)', 'translate(100px)']
|
||||
},
|
||||
style: 'backface-visibility: hidden;',
|
||||
expected: [
|
||||
{
|
||||
property: 'transform',
|
||||
runningOnCompositor: false,
|
||||
warning: 'CompositorAnimationWarningTransformBackfaceVisibilityHidden'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'opacity and transform with preserve-3d',
|
||||
frames: {
|
||||
opacity: [0, 1],
|
||||
transform: ['translate(0px)', 'translate(100px)']
|
||||
},
|
||||
style: 'transform-style: preserve-3d',
|
||||
expected: [
|
||||
{
|
||||
property: 'opacity',
|
||||
runningOnCompositor: true
|
||||
},
|
||||
{
|
||||
property: 'transform',
|
||||
runningOnCompositor: false,
|
||||
warning: 'CompositorAnimationWarningTransformPreserve3D'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'opacity and transform with backface-visibility:hidden',
|
||||
frames: {
|
||||
opacity: [0, 1],
|
||||
transform: ['translate(0px)', 'translate(100px)']
|
||||
},
|
||||
style: 'backface-visibility: hidden;',
|
||||
expected: [
|
||||
{
|
||||
property: 'opacity',
|
||||
runningOnCompositor: true
|
||||
},
|
||||
{
|
||||
property: 'transform',
|
||||
runningOnCompositor: false,
|
||||
warning: 'CompositorAnimationWarningTransformBackfaceVisibilityHidden'
|
||||
}
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
// Performance warning tests that set and clear the id property
|
||||
var gPerformanceWarningTestsId= [
|
||||
{
|
||||
desc: 'moz-element referencing a transform',
|
||||
frames: {
|
||||
transform: ['translate(0px)', 'translate(100px)']
|
||||
},
|
||||
id: 'transformed',
|
||||
createelement: 'width:100px; height:100px; background: -moz-element(#transformed)',
|
||||
expected: [
|
||||
{
|
||||
property: 'transform',
|
||||
runningOnCompositor: false,
|
||||
warning: 'CompositorAnimationWarningHasRenderingObserver'
|
||||
}
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
var gMultipleAsyncAnimationsTests = [
|
||||
{
|
||||
desc: 'opacity and transform with preserve-3d',
|
||||
style: 'transform-style: preserve-3d',
|
||||
animations: [
|
||||
{
|
||||
frames: {
|
||||
transform: ['translate(0px)', 'translate(100px)']
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
property: 'transform',
|
||||
runningOnCompositor: false,
|
||||
warning: 'CompositorAnimationWarningTransformPreserve3D'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
frames: {
|
||||
opacity: [0, 1]
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
property: 'opacity',
|
||||
runningOnCompositor: true,
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
desc: 'opacity and transform with backface-visibility:hidden',
|
||||
style: 'backface-visibility: hidden;',
|
||||
animations: [
|
||||
{
|
||||
frames: {
|
||||
transform: ['translate(0px)', 'translate(100px)']
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
property: 'transform',
|
||||
runningOnCompositor: false,
|
||||
warning: 'CompositorAnimationWarningTransformBackfaceVisibilityHidden'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
frames: {
|
||||
opacity: [0, 1]
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
property: 'opacity',
|
||||
runningOnCompositor: true,
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Test cases that check results of adding/removing a 'width' keyframe on the
|
||||
// same animation object, where multiple animation objects belong to the same
|
||||
// element.
|
||||
// The 'width' property is added to animations[1].
|
||||
var gMultipleAsyncAnimationsWithGeometricKeyframeTests = [
|
||||
{
|
||||
desc: 'transform and opacity with geometric keyframes',
|
||||
animations: [
|
||||
{
|
||||
frames: {
|
||||
transform: ['translate(0px)', 'translate(100px)']
|
||||
},
|
||||
expected: {
|
||||
withoutGeometric: [
|
||||
{
|
||||
property: 'transform',
|
||||
runningOnCompositor: true
|
||||
}
|
||||
],
|
||||
withGeometric: [
|
||||
{
|
||||
property: 'transform',
|
||||
runningOnCompositor: false,
|
||||
warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
frames: {
|
||||
opacity: [0, 1]
|
||||
},
|
||||
expected: {
|
||||
withoutGeometric: [
|
||||
{
|
||||
property: 'opacity',
|
||||
runningOnCompositor: true,
|
||||
}
|
||||
],
|
||||
withGeometric: [
|
||||
{
|
||||
property: 'width',
|
||||
runningOnCompositor: false,
|
||||
},
|
||||
{
|
||||
property: 'opacity',
|
||||
runningOnCompositor: true,
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
desc: 'opacity and transform with geometric keyframes',
|
||||
animations: [
|
||||
{
|
||||
frames: {
|
||||
opacity: [0, 1]
|
||||
},
|
||||
expected: {
|
||||
withoutGeometric: [
|
||||
{
|
||||
property: 'opacity',
|
||||
runningOnCompositor: true,
|
||||
}
|
||||
],
|
||||
withGeometric: [
|
||||
{
|
||||
property: 'opacity',
|
||||
runningOnCompositor: true,
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
frames: {
|
||||
transform: ['translate(0px)', 'translate(100px)']
|
||||
},
|
||||
expected: {
|
||||
withoutGeometric: [
|
||||
{
|
||||
property: 'transform',
|
||||
runningOnCompositor: true
|
||||
}
|
||||
],
|
||||
withGeometric: [
|
||||
{
|
||||
property: 'width',
|
||||
runningOnCompositor: false,
|
||||
},
|
||||
{
|
||||
property: 'transform',
|
||||
runningOnCompositor: false,
|
||||
warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
// Test cases that check results of adding/removing 'width' animation on the
|
||||
// same element which has async animations.
|
||||
var gMultipleAsyncAnimationsWithGeometricAnimationTests = [
|
||||
{
|
||||
desc: 'transform',
|
||||
animations: [
|
||||
{
|
||||
frames: {
|
||||
transform: ['translate(0px)', 'translate(100px)']
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
property: 'transform',
|
||||
runningOnCompositor: false,
|
||||
warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'opacity',
|
||||
animations: [
|
||||
{
|
||||
frames: {
|
||||
opacity: [0, 1]
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
property: 'opacity',
|
||||
runningOnCompositor: true
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'opacity and transform',
|
||||
animations: [
|
||||
{
|
||||
frames: {
|
||||
transform: ['translate(0px)', 'translate(100px)']
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
property: 'transform',
|
||||
runningOnCompositor: false,
|
||||
warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
frames: {
|
||||
opacity: [0, 1]
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
property: 'opacity',
|
||||
runningOnCompositor: true,
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
var gAnimationsOnTooSmallElementTests = [
|
||||
{
|
||||
desc: 'opacity on too small element',
|
||||
frames: {
|
||||
opacity: [0, 1]
|
||||
},
|
||||
style: { style: 'width: 8px; height: 8px; background-color: red;' +
|
||||
// We need to set transform here to try creating an
|
||||
// individual frame for this opacity element.
|
||||
// Without this, this small element is created on the same
|
||||
// nsIFrame of mochitest iframe, i.e. the document which are
|
||||
// running this test, as a result the layer corresponding
|
||||
// to the frame is sent to compositor.
|
||||
'transform: translateX(100px);' },
|
||||
expected: [
|
||||
{
|
||||
property: 'opacity',
|
||||
runningOnCompositor: false,
|
||||
warning: /Animation cannot be run on the compositor because frame size \(8, 8\) is smaller than \(16, 16\)/
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'transform on too small element',
|
||||
frames: {
|
||||
transform: ['translate(0px)', 'translate(100px)']
|
||||
},
|
||||
style: { style: 'width: 8px; height: 8px; background-color: red;' },
|
||||
expected: [
|
||||
{
|
||||
property: 'transform',
|
||||
runningOnCompositor: false,
|
||||
warning: /Animation cannot be run on the compositor because frame size \(8, 8\) is smaller than \(16, 16\)/
|
||||
}
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
function start() {
|
||||
var bundleService = SpecialPowers.Cc['@mozilla.org/intl/stringbundle;1']
|
||||
.getService(SpecialPowers.Ci.nsIStringBundleService);
|
||||
gStringBundle = bundleService
|
||||
.createBundle("chrome://global/locale/layout_errors.properties");
|
||||
|
||||
gAnimationsTests.forEach(function(subtest) {
|
||||
promise_test(function(t) {
|
||||
var animation = addDivAndAnimate(t,
|
||||
{ class: 'compositable' },
|
||||
subtest.frames, 100 * MS_PER_SEC);
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_property_state_equals(
|
||||
animation.effect.getProperties(),
|
||||
subtest.expected);
|
||||
});
|
||||
}, subtest.desc);
|
||||
});
|
||||
|
||||
gAnimationWithGeometricKeyframeTests.forEach(function(subtest) {
|
||||
promise_test(function(t) {
|
||||
var animation = addDivAndAnimate(t,
|
||||
{ class: 'compositable' },
|
||||
subtest.frames, 100 * MS_PER_SEC);
|
||||
return animation.ready.then(function() {
|
||||
// First, a transform animation is running on compositor.
|
||||
assert_animation_property_state_equals(
|
||||
animation.effect.getProperties(),
|
||||
subtest.expected.withoutGeometric);
|
||||
}).then(function() {
|
||||
// Add a 'width' property.
|
||||
var keyframes = animation.effect.getKeyframes();
|
||||
|
||||
keyframes[0].width = '100px';
|
||||
keyframes[1].width = '200px';
|
||||
|
||||
animation.effect.setKeyframes(keyframes);
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
// Now the transform animation is not running on compositor because of
|
||||
// the 'width' property.
|
||||
assert_animation_property_state_equals(
|
||||
animation.effect.getProperties(),
|
||||
subtest.expected.withGeometric);
|
||||
}).then(function() {
|
||||
// Remove the 'width' property.
|
||||
var keyframes = animation.effect.getKeyframes();
|
||||
|
||||
delete keyframes[0].width;
|
||||
delete keyframes[1].width;
|
||||
|
||||
animation.effect.setKeyframes(keyframes);
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
// Finally, the transform animation is running on compositor.
|
||||
assert_animation_property_state_equals(
|
||||
animation.effect.getProperties(),
|
||||
subtest.expected.withoutGeometric);
|
||||
});
|
||||
}, 'An animation has: ' + subtest.desc);
|
||||
});
|
||||
|
||||
gPerformanceWarningTestsStyle.forEach(function(subtest) {
|
||||
promise_test(function(t) {
|
||||
var animation = addDivAndAnimate(t,
|
||||
{ class: 'compositable' },
|
||||
subtest.frames, 100 * MS_PER_SEC);
|
||||
return animation.ready.then(function() {
|
||||
assert_property_state_on_compositor(
|
||||
animation.effect.getProperties(),
|
||||
subtest.expected);
|
||||
animation.effect.target.style = subtest.style;
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_property_state_equals(
|
||||
animation.effect.getProperties(),
|
||||
subtest.expected);
|
||||
animation.effect.target.style = '';
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_property_state_on_compositor(
|
||||
animation.effect.getProperties(),
|
||||
subtest.expected);
|
||||
});
|
||||
}, subtest.desc);
|
||||
});
|
||||
|
||||
gPerformanceWarningTestsId.forEach(function(subtest) {
|
||||
promise_test(function(t) {
|
||||
if (subtest.createelement) {
|
||||
addDiv(t, { style: subtest.createelement });
|
||||
}
|
||||
|
||||
var animation = addDivAndAnimate(t,
|
||||
{ class: 'compositable' },
|
||||
subtest.frames, 100 * MS_PER_SEC);
|
||||
return animation.ready.then(function() {
|
||||
assert_property_state_on_compositor(
|
||||
animation.effect.getProperties(),
|
||||
subtest.expected);
|
||||
animation.effect.target.id = subtest.id;
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_property_state_equals(
|
||||
animation.effect.getProperties(),
|
||||
subtest.expected);
|
||||
animation.effect.target.id = '';
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_property_state_on_compositor(
|
||||
animation.effect.getProperties(),
|
||||
subtest.expected);
|
||||
});
|
||||
}, subtest.desc);
|
||||
});
|
||||
|
||||
gMultipleAsyncAnimationsTests.forEach(function(subtest) {
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t, { class: 'compositable' });
|
||||
var animations = subtest.animations.map(function(anim) {
|
||||
var animation = div.animate(anim.frames, 100 * MS_PER_SEC);
|
||||
|
||||
// Bind expected values to animation object.
|
||||
animation.expected = anim.expected;
|
||||
return animation;
|
||||
});
|
||||
return waitForAllAnimations(animations).then(function() {
|
||||
animations.forEach(function(anim) {
|
||||
assert_property_state_on_compositor(
|
||||
anim.effect.getProperties(),
|
||||
anim.expected);
|
||||
});
|
||||
div.style = subtest.style;
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
animations.forEach(function(anim) {
|
||||
assert_animation_property_state_equals(
|
||||
anim.effect.getProperties(),
|
||||
anim.expected);
|
||||
});
|
||||
div.style = '';
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
animations.forEach(function(anim) {
|
||||
assert_property_state_on_compositor(
|
||||
anim.effect.getProperties(),
|
||||
anim.expected);
|
||||
});
|
||||
});
|
||||
}, 'Multiple animations: ' + subtest.desc);
|
||||
});
|
||||
|
||||
gMultipleAsyncAnimationsWithGeometricKeyframeTests.forEach(function(subtest) {
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t, { class: 'compositable' });
|
||||
var animations = subtest.animations.map(function(anim) {
|
||||
var animation = div.animate(anim.frames, 100 * MS_PER_SEC);
|
||||
|
||||
// Bind expected values to animation object.
|
||||
animation.expected = anim.expected;
|
||||
return animation;
|
||||
});
|
||||
return waitForAllAnimations(animations).then(function() {
|
||||
// First, all animations are running on compositor.
|
||||
animations.forEach(function(anim) {
|
||||
assert_animation_property_state_equals(
|
||||
anim.effect.getProperties(),
|
||||
anim.expected.withoutGeometric);
|
||||
});
|
||||
}).then(function() {
|
||||
// Add a 'width' property to animations[1].
|
||||
var keyframes = animations[1].effect.getKeyframes();
|
||||
|
||||
keyframes[0].width = '100px';
|
||||
keyframes[1].width = '200px';
|
||||
|
||||
animations[1].effect.setKeyframes(keyframes);
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
// Now the transform animation is not running on compositor because of
|
||||
// the 'width' property.
|
||||
animations.forEach(function(anim) {
|
||||
assert_animation_property_state_equals(
|
||||
anim.effect.getProperties(),
|
||||
anim.expected.withGeometric);
|
||||
});
|
||||
}).then(function() {
|
||||
// Remove the 'width' property from animations[1].
|
||||
var keyframes = animations[1].effect.getKeyframes();
|
||||
|
||||
delete keyframes[0].width;
|
||||
delete keyframes[1].width;
|
||||
|
||||
animations[1].effect.setKeyframes(keyframes);
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
// Finally, all animations are running on compositor.
|
||||
animations.forEach(function(anim) {
|
||||
assert_animation_property_state_equals(
|
||||
anim.effect.getProperties(),
|
||||
anim.expected.withoutGeometric);
|
||||
});
|
||||
});
|
||||
}, 'Multiple animations with geometric property: ' + subtest.desc);
|
||||
});
|
||||
|
||||
gMultipleAsyncAnimationsWithGeometricAnimationTests.forEach(function(subtest) {
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t, { class: 'compositable' });
|
||||
var animations = subtest.animations.map(function(anim) {
|
||||
var animation = div.animate(anim.frames, 100 * MS_PER_SEC);
|
||||
|
||||
// Bind expected values to animation object.
|
||||
animation.expected = anim.expected;
|
||||
return animation;
|
||||
});
|
||||
|
||||
var widthAnimation;
|
||||
|
||||
return waitForAllAnimations(animations).then(function() {
|
||||
animations.forEach(function(anim) {
|
||||
assert_property_state_on_compositor(
|
||||
anim.effect.getProperties(),
|
||||
anim.expected);
|
||||
});
|
||||
}).then(function() {
|
||||
// Append 'width' animation on the same element.
|
||||
widthAnimation = div.animate({ width: ['100px', '200px'] },
|
||||
100 * MS_PER_SEC);
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
// Now transform animations are not running on compositor because of
|
||||
// the 'width' animation.
|
||||
animations.forEach(function(anim) {
|
||||
assert_animation_property_state_equals(
|
||||
anim.effect.getProperties(),
|
||||
anim.expected);
|
||||
});
|
||||
// Remove the 'width' animation.
|
||||
widthAnimation.cancel();
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
// Now all animations are running on compositor.
|
||||
animations.forEach(function(anim) {
|
||||
assert_property_state_on_compositor(
|
||||
anim.effect.getProperties(),
|
||||
anim.expected);
|
||||
});
|
||||
});
|
||||
}, 'Multiple async animations and geometric animation: ' + subtest.desc);
|
||||
});
|
||||
|
||||
gAnimationsOnTooSmallElementTests.forEach(function(subtest) {
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t, subtest.style);
|
||||
var animation = div.animate(subtest.frames, 100 * MS_PER_SEC);
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_property_state_equals(
|
||||
animation.effect.getProperties(),
|
||||
subtest.expected);
|
||||
});
|
||||
}, subtest.desc);
|
||||
});
|
||||
|
||||
promise_test(function(t) {
|
||||
var animation = addDivAndAnimate(t,
|
||||
{ class: 'compositable' },
|
||||
{ transform: [ 'translate(0px)',
|
||||
'translate(100px)'] },
|
||||
100 * MS_PER_SEC);
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_property_state_equals(
|
||||
animation.effect.getProperties(),
|
||||
[ { property: 'transform', runningOnCompositor: true } ]);
|
||||
animation.effect.target.style = 'width: 10000px; height: 10000px';
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
// viewport depends on test environment.
|
||||
var expectedWarning = new RegExp(
|
||||
"Animation cannot be run on the compositor because the frame size " +
|
||||
"\\(10000, 10000\\) is bigger than the viewport \\(\\d+, \\d+\\) " +
|
||||
"or the visual rectangle \\(10000, 10000\\) is larger than the " +
|
||||
"maximum allowed value \\(\\d+\\)");
|
||||
assert_animation_property_state_equals(
|
||||
animation.effect.getProperties(),
|
||||
[ {
|
||||
property: 'transform',
|
||||
runningOnCompositor: false,
|
||||
warning: expectedWarning
|
||||
} ]);
|
||||
animation.effect.target.style = 'width: 100px; height: 100px';
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
// FIXME: Bug 1253164: the animation should get back on compositor.
|
||||
assert_animation_property_state_equals(
|
||||
animation.effect.getProperties(),
|
||||
[ { property: 'transform', runningOnCompositor: false } ]);
|
||||
});
|
||||
}, 'transform on too big element');
|
||||
|
||||
promise_test(function(t) {
|
||||
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('width', '100');
|
||||
svg.setAttribute('height', '100');
|
||||
var rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
||||
rect.setAttribute('width', '100');
|
||||
rect.setAttribute('height', '100');
|
||||
rect.setAttribute('fill', 'red');
|
||||
svg.appendChild(rect);
|
||||
document.body.appendChild(svg);
|
||||
t.add_cleanup(function() {
|
||||
svg.remove();
|
||||
});
|
||||
|
||||
var animation = svg.animate(
|
||||
{ transform: ['translate(0px)', 'translate(100px)'] }, 100 * MS_PER_SEC);
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_property_state_equals(
|
||||
animation.effect.getProperties(),
|
||||
[ { property: 'transform', runningOnCompositor: true } ]);
|
||||
svg.setAttribute('transform', 'translate(10, 20)');
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_property_state_equals(
|
||||
animation.effect.getProperties(),
|
||||
[ {
|
||||
property: 'transform',
|
||||
runningOnCompositor: false,
|
||||
warning: 'CompositorAnimationWarningTransformSVG'
|
||||
} ]);
|
||||
svg.removeAttribute('transform');
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_property_state_equals(
|
||||
animation.effect.getProperties(),
|
||||
[ { property: 'transform', runningOnCompositor: true } ]);
|
||||
});
|
||||
}, 'transform of nsIFrame with SVG transform');
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t, { class: 'compositable',
|
||||
style: 'animation: fade 100s' });
|
||||
var cssAnimation = div.getAnimations()[0];
|
||||
var scriptAnimation = div.animate({ opacity: [ 1, 0 ] }, 100 * MS_PER_SEC);
|
||||
return scriptAnimation.ready.then(function() {
|
||||
assert_animation_property_state_equals(
|
||||
cssAnimation.effect.getProperties(),
|
||||
[ { property: 'opacity', runningOnCompositor: true } ]);
|
||||
assert_animation_property_state_equals(
|
||||
scriptAnimation.effect.getProperties(),
|
||||
[ { property: 'opacity', runningOnCompositor: true } ]);
|
||||
});
|
||||
}, 'overridden animation');
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
993
dom/animation/test/chrome/test_animation_properties.html
Normal file
993
dom/animation/test/chrome/test_animation_properties.html
Normal file
|
|
@ -0,0 +1,993 @@
|
|||
<!doctype html>
|
||||
<head>
|
||||
<meta charset=utf-8>
|
||||
<title>Bug 1254419 - Test the values returned by
|
||||
KeyframeEffectReadOnly.getProperties()</title>
|
||||
<script type="application/javascript" src="../testharness.js"></script>
|
||||
<script type="application/javascript" src="../testharnessreport.js"></script>
|
||||
<script type="application/javascript" src="../testcommon.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1254419"
|
||||
target="_blank">Mozilla Bug 1254419</a>
|
||||
<div id="log"></div>
|
||||
<style>
|
||||
|
||||
:root {
|
||||
--var-100px: 100px;
|
||||
--var-100px-200px: 100px 200px;
|
||||
}
|
||||
div {
|
||||
font-size: 10px; /* For calculating em-based units */
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
function assert_properties_equal(actual, expected) {
|
||||
assert_equals(actual.length, expected.length);
|
||||
|
||||
var compareProperties = (a, b) =>
|
||||
a.property == b.property ? 0 : (a.property < b.property ? -1 : 1);
|
||||
|
||||
var sortedActual = actual.sort(compareProperties);
|
||||
var sortedExpected = expected.sort(compareProperties);
|
||||
|
||||
// We want to serialize the values in the following form:
|
||||
//
|
||||
// { offset: 0, easing: linear, composite: replace, value: 5px }, ...
|
||||
//
|
||||
// So that we can just compare strings and, in the failure case,
|
||||
// easily see where the differences lie.
|
||||
var serializeMember = value => {
|
||||
return typeof value === 'undefined' ? '<not set>' : value;
|
||||
}
|
||||
var serializeValues = values =>
|
||||
values.map(value =>
|
||||
'{ ' +
|
||||
[ 'offset', 'value', 'easing', 'composite' ].map(
|
||||
member => `${member}: ${serializeMember(value[member])}`
|
||||
).join(', ') +
|
||||
' }')
|
||||
.join(', ');
|
||||
|
||||
for (var i = 0; i < sortedActual.length; i++) {
|
||||
assert_equals(sortedActual[i].property,
|
||||
sortedExpected[i].property,
|
||||
'CSS property name should match');
|
||||
assert_equals(serializeValues(sortedActual[i].values),
|
||||
serializeValues(sortedExpected[i].values),
|
||||
`Values arrays do not match for `
|
||||
+ `${sortedActual[i].property} property`);
|
||||
}
|
||||
}
|
||||
|
||||
// Shorthand for constructing a value object
|
||||
function value(offset, value, composite, easing) {
|
||||
return { offset: offset, value: value, easing: easing, composite: composite };
|
||||
}
|
||||
|
||||
var gTests = [
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
//
|
||||
// Tests for property-indexed specifications
|
||||
//
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
{ desc: 'a one-property two-value property-indexed specification',
|
||||
frames: { left: ['10px', '20px'] },
|
||||
expected: [ { property: 'left',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '20px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a one-shorthand-property two-value property-indexed'
|
||||
+ ' specification',
|
||||
frames: { margin: ['10px', '10px 20px 30px 40px'] },
|
||||
expected: [ { property: 'margin-top',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '10px', 'replace') ] },
|
||||
{ property: 'margin-right',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '20px', 'replace') ] },
|
||||
{ property: 'margin-bottom',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '30px', 'replace') ] },
|
||||
{ property: 'margin-left',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '40px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a two-property (one shorthand and one of its longhand'
|
||||
+ ' components) two-value property-indexed specification',
|
||||
frames: { marginTop: ['50px', '60px'],
|
||||
margin: ['10px', '10px 20px 30px 40px'] },
|
||||
expected: [ { property: 'margin-top',
|
||||
values: [ value(0, '50px', 'replace', 'linear'),
|
||||
value(1, '60px', 'replace') ] },
|
||||
{ property: 'margin-right',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '20px', 'replace') ] },
|
||||
{ property: 'margin-bottom',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '30px', 'replace') ] },
|
||||
{ property: 'margin-left',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '40px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a two-property property-indexed specification with different'
|
||||
+ ' numbers of values',
|
||||
frames: { left: ['10px', '20px', '30px'],
|
||||
top: ['40px', '50px'] },
|
||||
expected: [ { property: 'left',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(0.5, '20px', 'replace', 'linear'),
|
||||
value(1, '30px', 'replace') ] },
|
||||
{ property: 'top',
|
||||
values: [ value(0, '40px', 'replace', 'linear'),
|
||||
value(1, '50px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a property-indexed specification with an invalid value',
|
||||
frames: { left: ['10px', '20px', '30px', '40px', '50px'],
|
||||
top: ['15px', '25px', 'invalid', '45px', '55px'] },
|
||||
expected: [ { property: 'left',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(0.25, '20px', 'replace', 'linear'),
|
||||
value(0.5, '30px', 'replace', 'linear'),
|
||||
value(0.75, '40px', 'replace', 'linear'),
|
||||
value(1, '50px', 'replace') ] },
|
||||
{ property: 'top',
|
||||
values: [ value(0, '15px', 'replace', 'linear'),
|
||||
value(0.25, '25px', 'replace', 'linear'),
|
||||
value(0.75, '45px', 'replace', 'linear'),
|
||||
value(1, '55px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a one-property two-value property-indexed specification that'
|
||||
+ ' needs to stringify its values',
|
||||
frames: { opacity: [0, 1] },
|
||||
expected: [ { property: 'opacity',
|
||||
values: [ value(0, '0', 'replace', 'linear'),
|
||||
value(1, '1', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a property-indexed keyframe where a lesser shorthand precedes'
|
||||
+ ' a greater shorthand',
|
||||
frames: { borderLeft: [ '1px solid rgb(1, 2, 3)',
|
||||
'2px solid rgb(4, 5, 6)' ],
|
||||
border: [ '3px dotted rgb(7, 8, 9)',
|
||||
'4px dashed rgb(10, 11, 12)' ] },
|
||||
expected: [ { property: 'border-bottom-color',
|
||||
values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
|
||||
value(1, 'rgb(10, 11, 12)', 'replace') ] },
|
||||
{ property: 'border-left-color',
|
||||
values: [ value(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
|
||||
value(1, 'rgb(4, 5, 6)', 'replace') ] },
|
||||
{ property: 'border-right-color',
|
||||
values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
|
||||
value(1, 'rgb(10, 11, 12)', 'replace') ] },
|
||||
{ property: 'border-top-color',
|
||||
values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
|
||||
value(1, 'rgb(10, 11, 12)', 'replace') ] },
|
||||
{ property: 'border-bottom-width',
|
||||
values: [ value(0, '3px', 'replace', 'linear'),
|
||||
value(1, '4px', 'replace') ] },
|
||||
{ property: 'border-left-width',
|
||||
values: [ value(0, '1px', 'replace', 'linear'),
|
||||
value(1, '2px', 'replace') ] },
|
||||
{ property: 'border-right-width',
|
||||
values: [ value(0, '3px', 'replace', 'linear'),
|
||||
value(1, '4px', 'replace') ] },
|
||||
{ property: 'border-top-width',
|
||||
values: [ value(0, '3px', 'replace', 'linear'),
|
||||
value(1, '4px', 'replace') ] },
|
||||
{ property: 'border-bottom-style',
|
||||
values: [ value(0, 'dotted', 'replace', 'linear'),
|
||||
value(1, 'dashed', 'replace') ] },
|
||||
{ property: 'border-left-style',
|
||||
values: [ value(0, 'solid', 'replace', 'linear'),
|
||||
value(1, 'solid', 'replace') ] },
|
||||
{ property: 'border-right-style',
|
||||
values: [ value(0, 'dotted', 'replace', 'linear'),
|
||||
value(1, 'dashed', 'replace') ] },
|
||||
{ property: 'border-top-style',
|
||||
values: [ value(0, 'dotted', 'replace', 'linear'),
|
||||
value(1, 'dashed', 'replace') ] },
|
||||
{ property: 'border-image-outset',
|
||||
values: [ value(0, '0 0 0 0', 'replace', 'linear'),
|
||||
value(1, '0 0 0 0', 'replace') ] },
|
||||
{ property: 'border-image-repeat',
|
||||
values: [ value(0, 'stretch stretch', 'replace', 'linear'),
|
||||
value(1, 'stretch stretch', 'replace') ] },
|
||||
{ property: 'border-image-slice',
|
||||
values: [ value(0, '100% 100% 100% 100%',
|
||||
'replace', 'linear'),
|
||||
value(1, '100% 100% 100% 100%', 'replace') ] },
|
||||
{ property: 'border-image-source',
|
||||
values: [ value(0, 'none', 'replace', 'linear'),
|
||||
value(1, 'none', 'replace') ] },
|
||||
{ property: 'border-image-width',
|
||||
values: [ value(0, '1 1 1 1', 'replace', 'linear'),
|
||||
value(1, '1 1 1 1', 'replace') ] },
|
||||
{ property: '-moz-border-bottom-colors',
|
||||
values: [ value(0, 'none', 'replace', 'linear'),
|
||||
value(1, 'none', 'replace') ] },
|
||||
{ property: '-moz-border-left-colors',
|
||||
values: [ value(0, 'none', 'replace', 'linear'),
|
||||
value(1, 'none', 'replace') ] },
|
||||
{ property: '-moz-border-right-colors',
|
||||
values: [ value(0, 'none', 'replace', 'linear'),
|
||||
value(1, 'none', 'replace') ] },
|
||||
{ property: '-moz-border-top-colors',
|
||||
values: [ value(0, 'none', 'replace', 'linear'),
|
||||
value(1, 'none', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a property-indexed keyframe where a greater shorthand precedes'
|
||||
+ ' a lesser shorthand',
|
||||
frames: { border: [ '3px dotted rgb(7, 8, 9)',
|
||||
'4px dashed rgb(10, 11, 12)' ],
|
||||
borderLeft: [ '1px solid rgb(1, 2, 3)',
|
||||
'2px solid rgb(4, 5, 6)' ] },
|
||||
expected: [ { property: 'border-bottom-color',
|
||||
values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
|
||||
value(1, 'rgb(10, 11, 12)', 'replace') ] },
|
||||
{ property: 'border-left-color',
|
||||
values: [ value(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
|
||||
value(1, 'rgb(4, 5, 6)', 'replace') ] },
|
||||
{ property: 'border-right-color',
|
||||
values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
|
||||
value(1, 'rgb(10, 11, 12)', 'replace') ] },
|
||||
{ property: 'border-top-color',
|
||||
values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
|
||||
value(1, 'rgb(10, 11, 12)', 'replace') ] },
|
||||
{ property: 'border-bottom-width',
|
||||
values: [ value(0, '3px', 'replace', 'linear'),
|
||||
value(1, '4px', 'replace') ] },
|
||||
{ property: 'border-left-width',
|
||||
values: [ value(0, '1px', 'replace', 'linear'),
|
||||
value(1, '2px', 'replace') ] },
|
||||
{ property: 'border-right-width',
|
||||
values: [ value(0, '3px', 'replace', 'linear'),
|
||||
value(1, '4px', 'replace') ] },
|
||||
{ property: 'border-top-width',
|
||||
values: [ value(0, '3px', 'replace', 'linear'),
|
||||
value(1, '4px', 'replace') ] },
|
||||
{ property: 'border-bottom-style',
|
||||
values: [ value(0, 'dotted', 'replace', 'linear'),
|
||||
value(1, 'dashed', 'replace') ] },
|
||||
{ property: 'border-left-style',
|
||||
values: [ value(0, 'solid', 'replace', 'linear'),
|
||||
value(1, 'solid', 'replace') ] },
|
||||
{ property: 'border-right-style',
|
||||
values: [ value(0, 'dotted', 'replace', 'linear'),
|
||||
value(1, 'dashed', 'replace') ] },
|
||||
{ property: 'border-top-style',
|
||||
values: [ value(0, 'dotted', 'replace', 'linear'),
|
||||
value(1, 'dashed', 'replace') ] },
|
||||
{ property: 'border-image-outset',
|
||||
values: [ value(0, '0 0 0 0', 'replace', 'linear'),
|
||||
value(1, '0 0 0 0', 'replace') ] },
|
||||
{ property: 'border-image-repeat',
|
||||
values: [ value(0, 'stretch stretch', 'replace', 'linear'),
|
||||
value(1, 'stretch stretch', 'replace') ] },
|
||||
{ property: 'border-image-slice',
|
||||
values: [ value(0, '100% 100% 100% 100%',
|
||||
'replace', 'linear'),
|
||||
value(1, '100% 100% 100% 100%', 'replace') ] },
|
||||
{ property: 'border-image-source',
|
||||
values: [ value(0, 'none', 'replace', 'linear'),
|
||||
value(1, 'none', 'replace') ] },
|
||||
{ property: 'border-image-width',
|
||||
values: [ value(0, '1 1 1 1', 'replace', 'linear'),
|
||||
value(1, '1 1 1 1', 'replace') ] },
|
||||
{ property: '-moz-border-bottom-colors',
|
||||
values: [ value(0, 'none', 'replace', 'linear'),
|
||||
value(1, 'none', 'replace') ] },
|
||||
{ property: '-moz-border-left-colors',
|
||||
values: [ value(0, 'none', 'replace', 'linear'),
|
||||
value(1, 'none', 'replace') ] },
|
||||
{ property: '-moz-border-right-colors',
|
||||
values: [ value(0, 'none', 'replace', 'linear'),
|
||||
value(1, 'none', 'replace') ] },
|
||||
{ property: '-moz-border-top-colors',
|
||||
values: [ value(0, 'none', 'replace', 'linear'),
|
||||
value(1, 'none', 'replace') ] } ]
|
||||
},
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
//
|
||||
// Tests for keyframe sequences
|
||||
//
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
{ desc: 'a keyframe sequence specification with repeated values at'
|
||||
+ ' offset 0/1 with different easings',
|
||||
frames: [ { offset: 0.0, left: '100px', easing: 'ease' },
|
||||
{ offset: 0.0, left: '200px', easing: 'ease' },
|
||||
{ offset: 0.5, left: '300px', easing: 'linear' },
|
||||
{ offset: 1.0, left: '400px', easing: 'ease-out' },
|
||||
{ offset: 1.0, left: '500px', easing: 'step-end' } ],
|
||||
expected: [ { property: 'left',
|
||||
values: [ value(0, '100px', 'replace'),
|
||||
value(0, '200px', 'replace', 'ease'),
|
||||
value(0.5, '300px', 'replace', 'linear'),
|
||||
value(1, '400px', 'replace'),
|
||||
value(1, '500px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a one-property two-keyframe sequence',
|
||||
frames: [ { offset: 0, left: '10px' },
|
||||
{ offset: 1, left: '20px' } ],
|
||||
expected: [ { property: 'left',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '20px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a two-property two-keyframe sequence',
|
||||
frames: [ { offset: 0, left: '10px', top: '30px' },
|
||||
{ offset: 1, left: '20px', top: '40px' } ],
|
||||
expected: [ { property: 'left',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '20px', 'replace') ] },
|
||||
{ property: 'top',
|
||||
values: [ value(0, '30px', 'replace', 'linear'),
|
||||
value(1, '40px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a one shorthand property two-keyframe sequence',
|
||||
frames: [ { offset: 0, margin: '10px' },
|
||||
{ offset: 1, margin: '20px 30px 40px 50px' } ],
|
||||
expected: [ { property: 'margin-top',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '20px', 'replace') ] },
|
||||
{ property: 'margin-right',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '30px', 'replace') ] },
|
||||
{ property: 'margin-bottom',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '40px', 'replace') ] },
|
||||
{ property: 'margin-left',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '50px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a two-property (a shorthand and one of its component longhands)'
|
||||
+ ' two-keyframe sequence',
|
||||
frames: [ { offset: 0, margin: '10px', marginTop: '20px' },
|
||||
{ offset: 1, marginTop: '70px',
|
||||
margin: '30px 40px 50px 60px' } ],
|
||||
expected: [ { property: 'margin-top',
|
||||
values: [ value(0, '20px', 'replace', 'linear'),
|
||||
value(1, '70px', 'replace') ] },
|
||||
{ property: 'margin-right',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '40px', 'replace') ] },
|
||||
{ property: 'margin-bottom',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '50px', 'replace') ] },
|
||||
{ property: 'margin-left',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '60px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a keyframe sequence with duplicate values for a given interior'
|
||||
+ ' offset',
|
||||
frames: [ { offset: 0.0, left: '10px' },
|
||||
{ offset: 0.5, left: '20px' },
|
||||
{ offset: 0.5, left: '30px' },
|
||||
{ offset: 0.5, left: '40px' },
|
||||
{ offset: 1.0, left: '50px' } ],
|
||||
expected: [ { property: 'left',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(0.5, '20px', 'replace'),
|
||||
value(0.5, '40px', 'replace', 'linear'),
|
||||
value(1, '50px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a keyframe sequence with duplicate values for offsets 0 and 1',
|
||||
frames: [ { offset: 0, left: '10px' },
|
||||
{ offset: 0, left: '20px' },
|
||||
{ offset: 0, left: '30px' },
|
||||
{ offset: 1, left: '40px' },
|
||||
{ offset: 1, left: '50px' },
|
||||
{ offset: 1, left: '60px' } ],
|
||||
expected: [ { property: 'left',
|
||||
values: [ value(0, '10px', 'replace'),
|
||||
value(0, '30px', 'replace', 'linear'),
|
||||
value(1, '40px', 'replace'),
|
||||
value(1, '60px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a two-property four-keyframe sequence',
|
||||
frames: [ { offset: 0, left: '10px' },
|
||||
{ offset: 0, top: '20px' },
|
||||
{ offset: 1, top: '30px' },
|
||||
{ offset: 1, left: '40px' } ],
|
||||
expected: [ { property: 'left',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '40px', 'replace') ] },
|
||||
{ property: 'top',
|
||||
values: [ value(0, '20px', 'replace', 'linear'),
|
||||
value(1, '30px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a one-property keyframe sequence with some omitted offsets',
|
||||
frames: [ { offset: 0.00, left: '10px' },
|
||||
{ offset: 0.25, left: '20px' },
|
||||
{ left: '30px' },
|
||||
{ left: '40px' },
|
||||
{ offset: 1.00, left: '50px' } ],
|
||||
expected: [ { property: 'left',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(0.25, '20px', 'replace', 'linear'),
|
||||
value(0.5, '30px', 'replace', 'linear'),
|
||||
value(0.75, '40px', 'replace', 'linear'),
|
||||
value(1, '50px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a two-property keyframe sequence with some omitted offsets',
|
||||
frames: [ { offset: 0.00, left: '10px', top: '20px' },
|
||||
{ offset: 0.25, left: '30px' },
|
||||
{ left: '40px' },
|
||||
{ left: '50px', top: '60px' },
|
||||
{ offset: 1.00, left: '70px', top: '80px' } ],
|
||||
expected: [ { property: 'left',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(0.25, '30px', 'replace', 'linear'),
|
||||
value(0.5, '40px', 'replace', 'linear'),
|
||||
value(0.75, '50px', 'replace', 'linear'),
|
||||
value(1, '70px', 'replace') ] },
|
||||
{ property: 'top',
|
||||
values: [ value(0, '20px', 'replace', 'linear'),
|
||||
value(0.75, '60px', 'replace', 'linear'),
|
||||
value(1, '80px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a one-property keyframe sequence with all omitted offsets',
|
||||
frames: [ { left: '10px' },
|
||||
{ left: '20px' },
|
||||
{ left: '30px' },
|
||||
{ left: '40px' },
|
||||
{ left: '50px' } ],
|
||||
expected: [ { property: 'left',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(0.25, '20px', 'replace', 'linear'),
|
||||
value(0.5, '30px', 'replace', 'linear'),
|
||||
value(0.75, '40px', 'replace', 'linear'),
|
||||
value(1, '50px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a keyframe sequence with different easing values, but the'
|
||||
+ ' same easing value for a given offset',
|
||||
frames: [ { offset: 0.0, easing: 'ease', left: '10px'},
|
||||
{ offset: 0.0, easing: 'ease', top: '20px'},
|
||||
{ offset: 0.5, easing: 'linear', left: '30px' },
|
||||
{ offset: 0.5, easing: 'linear', top: '40px' },
|
||||
{ offset: 1.0, easing: 'step-end', left: '50px' },
|
||||
{ offset: 1.0, easing: 'step-end', top: '60px' } ],
|
||||
expected: [ { property: 'left',
|
||||
values: [ value(0, '10px', 'replace', 'ease'),
|
||||
value(0.5, '30px', 'replace', 'linear'),
|
||||
value(1, '50px', 'replace') ] },
|
||||
{ property: 'top',
|
||||
values: [ value(0, '20px', 'replace', 'ease'),
|
||||
value(0.5, '40px', 'replace', 'linear'),
|
||||
value(1, '60px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a one-property two-keyframe sequence that needs to'
|
||||
+ ' stringify its values',
|
||||
frames: [ { offset: 0, opacity: 0 },
|
||||
{ offset: 1, opacity: 1 } ],
|
||||
expected: [ { property: 'opacity',
|
||||
values: [ value(0, '0', 'replace', 'linear'),
|
||||
value(1, '1', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a keyframe sequence where shorthand precedes longhand',
|
||||
frames: [ { offset: 0, margin: '10px', marginRight: '20px' },
|
||||
{ offset: 1, margin: '30px' } ],
|
||||
expected: [ { property: 'margin-top',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '30px', 'replace') ] },
|
||||
{ property: 'margin-right',
|
||||
values: [ value(0, '20px', 'replace', 'linear'),
|
||||
value(1, '30px', 'replace') ] },
|
||||
{ property: 'margin-bottom',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '30px', 'replace') ] },
|
||||
{ property: 'margin-left',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '30px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a keyframe sequence where longhand precedes shorthand',
|
||||
frames: [ { offset: 0, marginRight: '20px', margin: '10px' },
|
||||
{ offset: 1, margin: '30px' } ],
|
||||
expected: [ { property: 'margin-top',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '30px', 'replace') ] },
|
||||
{ property: 'margin-right',
|
||||
values: [ value(0, '20px', 'replace', 'linear'),
|
||||
value(1, '30px', 'replace') ] },
|
||||
{ property: 'margin-bottom',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '30px', 'replace') ] },
|
||||
{ property: 'margin-left',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '30px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a keyframe sequence where lesser shorthand precedes greater'
|
||||
+ ' shorthand',
|
||||
frames: [ { offset: 0, borderLeft: '1px solid rgb(1, 2, 3)',
|
||||
border: '2px dotted rgb(4, 5, 6)' },
|
||||
{ offset: 1, border: '3px dashed rgb(7, 8, 9)' } ],
|
||||
expected: [ { property: 'border-bottom-color',
|
||||
values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
|
||||
value(1, 'rgb(7, 8, 9)', 'replace') ] },
|
||||
{ property: 'border-left-color',
|
||||
values: [ value(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
|
||||
value(1, 'rgb(7, 8, 9)', 'replace') ] },
|
||||
{ property: 'border-right-color',
|
||||
values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
|
||||
value(1, 'rgb(7, 8, 9)', 'replace') ] },
|
||||
{ property: 'border-top-color',
|
||||
values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
|
||||
value(1, 'rgb(7, 8, 9)', 'replace') ] },
|
||||
{ property: 'border-bottom-width',
|
||||
values: [ value(0, '2px', 'replace', 'linear'),
|
||||
value(1, '3px', 'replace') ] },
|
||||
{ property: 'border-left-width',
|
||||
values: [ value(0, '1px', 'replace', 'linear'),
|
||||
value(1, '3px', 'replace') ] },
|
||||
{ property: 'border-right-width',
|
||||
values: [ value(0, '2px', 'replace', 'linear'),
|
||||
value(1, '3px', 'replace') ] },
|
||||
{ property: 'border-top-width',
|
||||
values: [ value(0, '2px', 'replace', 'linear'),
|
||||
value(1, '3px', 'replace') ] },
|
||||
{ property: 'border-bottom-style',
|
||||
values: [ value(0, 'dotted', 'replace', 'linear'),
|
||||
value(1, 'dashed', 'replace') ] },
|
||||
{ property: 'border-left-style',
|
||||
values: [ value(0, 'solid', 'replace', 'linear'),
|
||||
value(1, 'dashed', 'replace') ] },
|
||||
{ property: 'border-right-style',
|
||||
values: [ value(0, 'dotted', 'replace', 'linear'),
|
||||
value(1, 'dashed', 'replace') ] },
|
||||
{ property: 'border-top-style',
|
||||
values: [ value(0, 'dotted', 'replace', 'linear'),
|
||||
value(1, 'dashed', 'replace') ] },
|
||||
{ property: 'border-image-outset',
|
||||
values: [ value(0, '0 0 0 0', 'replace', 'linear'),
|
||||
value(1, '0 0 0 0', 'replace') ] },
|
||||
{ property: 'border-image-repeat',
|
||||
values: [ value(0, 'stretch stretch', 'replace', 'linear'),
|
||||
value(1, 'stretch stretch', 'replace') ] },
|
||||
{ property: 'border-image-slice',
|
||||
values: [ value(0, '100% 100% 100% 100%',
|
||||
'replace', 'linear'),
|
||||
value(1, '100% 100% 100% 100%', 'replace') ] },
|
||||
{ property: 'border-image-source',
|
||||
values: [ value(0, 'none', 'replace', 'linear'),
|
||||
value(1, 'none', 'replace') ] },
|
||||
{ property: 'border-image-width',
|
||||
values: [ value(0, '1 1 1 1', 'replace', 'linear'),
|
||||
value(1, '1 1 1 1', 'replace') ] },
|
||||
{ property: '-moz-border-bottom-colors',
|
||||
values: [ value(0, 'none', 'replace', 'linear'),
|
||||
value(1, 'none', 'replace') ] },
|
||||
{ property: '-moz-border-left-colors',
|
||||
values: [ value(0, 'none', 'replace', 'linear'),
|
||||
value(1, 'none', 'replace') ] },
|
||||
{ property: '-moz-border-right-colors',
|
||||
values: [ value(0, 'none', 'replace', 'linear'),
|
||||
value(1, 'none', 'replace') ] },
|
||||
{ property: '-moz-border-top-colors',
|
||||
values: [ value(0, 'none', 'replace', 'linear'),
|
||||
value(1, 'none', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a keyframe sequence where greater shorthand precedes'
|
||||
+ ' lesser shorthand',
|
||||
frames: [ { offset: 0, border: '2px dotted rgb(4, 5, 6)',
|
||||
borderLeft: '1px solid rgb(1, 2, 3)' },
|
||||
{ offset: 1, border: '3px dashed rgb(7, 8, 9)' } ],
|
||||
expected: [ { property: 'border-bottom-color',
|
||||
values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
|
||||
value(1, 'rgb(7, 8, 9)', 'replace') ] },
|
||||
{ property: 'border-left-color',
|
||||
values: [ value(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
|
||||
value(1, 'rgb(7, 8, 9)', 'replace') ] },
|
||||
{ property: 'border-right-color',
|
||||
values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
|
||||
value(1, 'rgb(7, 8, 9)', 'replace') ] },
|
||||
{ property: 'border-top-color',
|
||||
values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
|
||||
value(1, 'rgb(7, 8, 9)', 'replace') ] },
|
||||
{ property: 'border-bottom-width',
|
||||
values: [ value(0, '2px', 'replace', 'linear'),
|
||||
value(1, '3px', 'replace') ] },
|
||||
{ property: 'border-left-width',
|
||||
values: [ value(0, '1px', 'replace', 'linear'),
|
||||
value(1, '3px', 'replace') ] },
|
||||
{ property: 'border-right-width',
|
||||
values: [ value(0, '2px', 'replace', 'linear'),
|
||||
value(1, '3px', 'replace') ] },
|
||||
{ property: 'border-top-width',
|
||||
values: [ value(0, '2px', 'replace', 'linear'),
|
||||
value(1, '3px', 'replace') ] },
|
||||
{ property: 'border-bottom-style',
|
||||
values: [ value(0, 'dotted', 'replace', 'linear'),
|
||||
value(1, 'dashed', 'replace') ] },
|
||||
{ property: 'border-left-style',
|
||||
values: [ value(0, 'solid', 'replace', 'linear'),
|
||||
value(1, 'dashed', 'replace') ] },
|
||||
{ property: 'border-right-style',
|
||||
values: [ value(0, 'dotted', 'replace', 'linear'),
|
||||
value(1, 'dashed', 'replace') ] },
|
||||
{ property: 'border-top-style',
|
||||
values: [ value(0, 'dotted', 'replace', 'linear'),
|
||||
value(1, 'dashed', 'replace') ] },
|
||||
{ property: 'border-image-outset',
|
||||
values: [ value(0, '0 0 0 0', 'replace', 'linear'),
|
||||
value(1, '0 0 0 0', 'replace') ] },
|
||||
{ property: 'border-image-repeat',
|
||||
values: [ value(0, 'stretch stretch', 'replace', 'linear'),
|
||||
value(1, 'stretch stretch', 'replace') ] },
|
||||
{ property: 'border-image-slice',
|
||||
values: [ value(0, '100% 100% 100% 100%',
|
||||
'replace', 'linear'),
|
||||
value(1, '100% 100% 100% 100%', 'replace') ] },
|
||||
{ property: 'border-image-source',
|
||||
values: [ value(0, 'none', 'replace', 'linear'),
|
||||
value(1, 'none', 'replace') ] },
|
||||
{ property: 'border-image-width',
|
||||
values: [ value(0, '1 1 1 1', 'replace', 'linear'),
|
||||
value(1, '1 1 1 1', 'replace') ] },
|
||||
{ property: '-moz-border-bottom-colors',
|
||||
values: [ value(0, 'none', 'replace', 'linear'),
|
||||
value(1, 'none', 'replace') ] },
|
||||
{ property: '-moz-border-left-colors',
|
||||
values: [ value(0, 'none', 'replace', 'linear'),
|
||||
value(1, 'none', 'replace') ] },
|
||||
{ property: '-moz-border-right-colors',
|
||||
values: [ value(0, 'none', 'replace', 'linear'),
|
||||
value(1, 'none', 'replace') ] },
|
||||
{ property: '-moz-border-top-colors',
|
||||
values: [ value(0, 'none', 'replace', 'linear'),
|
||||
value(1, 'none', 'replace') ] } ]
|
||||
},
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
//
|
||||
// Tests for unit conversion
|
||||
//
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
{ desc: 'em units are resolved to px values',
|
||||
frames: { left: ['10em', '20em'] },
|
||||
expected: [ { property: 'left',
|
||||
values: [ value(0, '100px', 'replace', 'linear'),
|
||||
value(1, '200px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'calc() expressions are resolved to the equivalent units',
|
||||
frames: { left: ['calc(10em + 10px)', 'calc(10em + 10%)'] },
|
||||
expected: [ { property: 'left',
|
||||
values: [ value(0, 'calc(110px)', 'replace', 'linear'),
|
||||
value(1, 'calc(100px + 10%)', 'replace') ] } ]
|
||||
},
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
//
|
||||
// Tests for CSS variable handling conversion
|
||||
//
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
{ desc: 'CSS variables are resolved to their corresponding values',
|
||||
frames: { left: ['10px', 'var(--var-100px)'] },
|
||||
expected: [ { property: 'left',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '100px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'CSS variables in calc() expressions are resolved',
|
||||
frames: { left: ['10px', 'calc(var(--var-100px) / 2 - 10%)'] },
|
||||
expected: [ { property: 'left',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, 'calc(50px + -10%)', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'CSS variables in shorthands are resolved to their corresponding'
|
||||
+ ' values',
|
||||
frames: { margin: ['10px', 'var(--var-100px-200px)'] },
|
||||
expected: [ { property: 'margin-top',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '100px', 'replace') ] },
|
||||
{ property: 'margin-right',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '200px', 'replace') ] },
|
||||
{ property: 'margin-bottom',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '100px', 'replace') ] },
|
||||
{ property: 'margin-left',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '200px', 'replace') ] } ]
|
||||
},
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
//
|
||||
// Tests for properties that parse correctly but which we fail to
|
||||
// convert to computed values.
|
||||
//
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
{ desc: 'a property that can\'t be resolved to computed values in'
|
||||
+ ' initial keyframe',
|
||||
frames: [ { margin: '5px', simulateComputeValuesFailure: true },
|
||||
{ margin: '5px' } ],
|
||||
expected: [ ]
|
||||
},
|
||||
{ desc: 'a property that can\'t be resolved to computed values in'
|
||||
+ ' initial keyframe where we have enough values to create'
|
||||
+ ' a final segment',
|
||||
frames: [ { margin: '5px', simulateComputeValuesFailure: true },
|
||||
{ margin: '5px' },
|
||||
{ margin: '5px' } ],
|
||||
expected: [ ]
|
||||
},
|
||||
{ desc: 'a property that can\'t be resolved to computed values in'
|
||||
+ ' initial overlapping keyframes (first in series of two)',
|
||||
frames: [ { margin: '5px', offset: 0,
|
||||
simulateComputeValuesFailure: true },
|
||||
{ margin: '5px', offset: 0 },
|
||||
{ margin: '5px' } ],
|
||||
expected: [ { property: 'margin-top',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-right',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-bottom',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-left',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a property that can\'t be resolved to computed values in'
|
||||
+ ' initial overlapping keyframes (second in series of two)',
|
||||
frames: [ { margin: '5px', offset: 0 },
|
||||
{ margin: '5px', offset: 0,
|
||||
simulateComputeValuesFailure: true },
|
||||
{ margin: '5px' } ],
|
||||
expected: [ { property: 'margin-top',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-right',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-bottom',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-left',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a property that can\'t be resolved to computed values in'
|
||||
+ ' initial overlapping keyframes (second in series of three)',
|
||||
frames: [ { margin: '5px', offset: 0 },
|
||||
{ margin: '5px', offset: 0,
|
||||
simulateComputeValuesFailure: true },
|
||||
{ margin: '5px', offset: 0 },
|
||||
{ margin: '5px' } ],
|
||||
expected: [ { property: 'margin-top',
|
||||
values: [ value(0, '5px', 'replace'),
|
||||
value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-right',
|
||||
values: [ value(0, '5px', 'replace'),
|
||||
value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-bottom',
|
||||
values: [ value(0, '5px', 'replace'),
|
||||
value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-left',
|
||||
values: [ value(0, '5px', 'replace'),
|
||||
value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a property that can\'t be resolved to computed values in'
|
||||
+ ' final keyframe',
|
||||
frames: [ { margin: '5px' },
|
||||
{ margin: '5px', simulateComputeValuesFailure: true } ],
|
||||
expected: [ ]
|
||||
},
|
||||
{ desc: 'a property that can\'t be resolved to computed values in'
|
||||
+ ' final keyframe where it forms the last segment in the series',
|
||||
frames: [ { margin: '5px' },
|
||||
{ margin: '5px',
|
||||
marginLeft: '5px',
|
||||
marginRight: '5px',
|
||||
marginBottom: '5px',
|
||||
// margin-top sorts last and only it will be missing since
|
||||
// the other longhand components are specified
|
||||
simulateComputeValuesFailure: true } ],
|
||||
expected: [ { property: 'margin-bottom',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-left',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-right',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a property that can\'t be resolved to computed values in'
|
||||
+ ' final keyframe where we have enough values to create'
|
||||
+ ' an initial segment',
|
||||
frames: [ { margin: '5px' },
|
||||
{ margin: '5px' },
|
||||
{ margin: '5px', simulateComputeValuesFailure: true } ],
|
||||
expected: [ ]
|
||||
},
|
||||
{ desc: 'a property that can\'t be resolved to computed values in'
|
||||
+ ' final overlapping keyframes (first in series of two)',
|
||||
frames: [ { margin: '5px' },
|
||||
{ margin: '5px', offset: 1,
|
||||
simulateComputeValuesFailure: true },
|
||||
{ margin: '5px', offset: 1 } ],
|
||||
expected: [ { property: 'margin-top',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-right',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-bottom',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-left',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a property that can\'t be resolved to computed values in'
|
||||
+ ' final overlapping keyframes (second in series of two)',
|
||||
frames: [ { margin: '5px' },
|
||||
{ margin: '5px', offset: 1 },
|
||||
{ margin: '5px', offset: 1,
|
||||
simulateComputeValuesFailure: true } ],
|
||||
expected: [ { property: 'margin-top',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-right',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-bottom',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-left',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a property that can\'t be resolved to computed values in'
|
||||
+ ' final overlapping keyframes (second in series of three)',
|
||||
frames: [ { margin: '5px' },
|
||||
{ margin: '5px', offset: 1 },
|
||||
{ margin: '5px', offset: 1,
|
||||
simulateComputeValuesFailure: true },
|
||||
{ margin: '5px', offset: 1 } ],
|
||||
expected: [ { property: 'margin-top',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-right',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-bottom',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-left',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace'),
|
||||
value(1, '5px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a property that can\'t be resolved to computed values in'
|
||||
+ ' intermediate keyframe',
|
||||
frames: [ { margin: '5px' },
|
||||
{ margin: '5px', simulateComputeValuesFailure: true },
|
||||
{ margin: '5px' } ],
|
||||
expected: [ { property: 'margin-top',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-right',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-bottom',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-left',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a property that can\'t be resolved to computed values in'
|
||||
+ ' initial keyframe along with other values',
|
||||
// simulateComputeValuesFailure only applies to shorthands so we can set
|
||||
// it on the same keyframe and it will only apply to |margin| and not
|
||||
// |left|.
|
||||
frames: [ { margin: '77%', left: '10px',
|
||||
simulateComputeValuesFailure: true },
|
||||
{ margin: '5px', left: '20px' } ],
|
||||
expected: [ { property: 'left',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '20px', 'replace') ] } ],
|
||||
},
|
||||
{ desc: 'a property that can\'t be resolved to computed values in'
|
||||
+ ' initial keyframe along with other values where those'
|
||||
+ ' values sort after the property with missing values',
|
||||
frames: [ { margin: '77%', right: '10px',
|
||||
simulateComputeValuesFailure: true },
|
||||
{ margin: '5px', right: '20px' } ],
|
||||
expected: [ { property: 'right',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '20px', 'replace') ] } ],
|
||||
},
|
||||
{ desc: 'a property that can\'t be resolved to computed values in'
|
||||
+ ' final keyframe along with other values',
|
||||
frames: [ { margin: '5px', left: '10px' },
|
||||
{ margin: '5px', left: '20px',
|
||||
simulateComputeValuesFailure: true } ],
|
||||
expected: [ { property: 'left',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '20px', 'replace') ] } ],
|
||||
},
|
||||
{ desc: 'a property that can\'t be resolved to computed values in'
|
||||
+ ' final keyframe along with other values where those'
|
||||
+ ' values sort after the property with missing values',
|
||||
frames: [ { margin: '5px', right: '10px' },
|
||||
{ margin: '5px', right: '20px',
|
||||
simulateComputeValuesFailure: true } ],
|
||||
expected: [ { property: 'right',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '20px', 'replace') ] } ],
|
||||
},
|
||||
{ desc: 'a property that can\'t be resolved to computed values in'
|
||||
+ ' an intermediate keyframe along with other values',
|
||||
frames: [ { margin: '5px', left: '10px' },
|
||||
{ margin: '5px', left: '20px',
|
||||
simulateComputeValuesFailure: true },
|
||||
{ margin: '5px', left: '30px' } ],
|
||||
expected: [ { property: 'margin-top',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-right',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-bottom',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-left',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'left',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(0.5, '20px', 'replace', 'linear'),
|
||||
value(1, '30px', 'replace') ] } ]
|
||||
},
|
||||
{ desc: 'a property that can\'t be resolved to computed values in'
|
||||
+ ' an intermediate keyframe by itself',
|
||||
frames: [ { margin: '5px', left: '10px' },
|
||||
{ margin: '5px',
|
||||
simulateComputeValuesFailure: true },
|
||||
{ margin: '5px', left: '30px' } ],
|
||||
expected: [ { property: 'margin-top',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-right',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-bottom',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'margin-left',
|
||||
values: [ value(0, '5px', 'replace', 'linear'),
|
||||
value(1, '5px', 'replace') ] },
|
||||
{ property: 'left',
|
||||
values: [ value(0, '10px', 'replace', 'linear'),
|
||||
value(1, '30px', 'replace') ] } ]
|
||||
},
|
||||
];
|
||||
|
||||
gTests.forEach(function(subtest) {
|
||||
test(function(t) {
|
||||
var div = addDiv(t);
|
||||
var animation = div.animate(subtest.frames, 100 * MS_PER_SEC);
|
||||
assert_properties_equal(animation.effect.getProperties(),
|
||||
subtest.expected);
|
||||
}, subtest.desc);
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
<!DOCTYPE html>
|
||||
<head>
|
||||
<meta charset=utf-8>
|
||||
<title>Test getAnimations() for generated-content elements</title>
|
||||
<script type="application/javascript" src="../testharness.js"></script>
|
||||
<script type="application/javascript" src="../testharnessreport.js"></script>
|
||||
<script type="application/javascript" src="../testcommon.js"></script>
|
||||
<style>
|
||||
@keyframes anim { }
|
||||
@keyframes anim2 { }
|
||||
.before::before {
|
||||
content: '';
|
||||
animation: anim 100s;
|
||||
}
|
||||
.after::after {
|
||||
content: '';
|
||||
animation: anim 100s, anim2 100s;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id='root' class='before after'>
|
||||
<div class='before'></div>
|
||||
<div></div>
|
||||
</div>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
const {Cc, Ci, Cu} = SpecialPowers;
|
||||
|
||||
function getWalker(node) {
|
||||
var walker = Cc["@mozilla.org/inspector/deep-tree-walker;1"].
|
||||
createInstance(Ci.inIDeepTreeWalker);
|
||||
walker.showAnonymousContent = true;
|
||||
walker.init(node.ownerDocument, Ci.nsIDOMNodeFilter.SHOW_ALL);
|
||||
walker.currentNode = node;
|
||||
return walker;
|
||||
}
|
||||
|
||||
test(function(t) {
|
||||
var root = document.getElementById('root');
|
||||
// Flush first to make sure the generated-content elements are ready
|
||||
// in the tree.
|
||||
flushComputedStyle(root);
|
||||
var before = getWalker(root).firstChild();
|
||||
var after = getWalker(root).lastChild();
|
||||
|
||||
// Sanity Checks
|
||||
assert_equals(document.getAnimations().length, 4,
|
||||
'All animations in this document');
|
||||
assert_equals(before.tagName, '_moz_generated_content_before',
|
||||
'First child is ::before element');
|
||||
assert_equals(after.tagName, '_moz_generated_content_after',
|
||||
'Last child is ::after element');
|
||||
|
||||
// Test Element.getAnimations() for generated-content elements
|
||||
assert_equals(before.getAnimations().length, 1,
|
||||
'Animations of ::before generated-content element');
|
||||
assert_equals(after.getAnimations().length, 2,
|
||||
'Animations of ::after generated-content element');
|
||||
}, 'Element.getAnimations() used on generated-content elements');
|
||||
|
||||
test(function(t) {
|
||||
var root = document.getElementById('root');
|
||||
flushComputedStyle(root);
|
||||
var walker = getWalker(root);
|
||||
|
||||
var animations = [];
|
||||
var element = walker.currentNode;
|
||||
while (element) {
|
||||
if (element.getAnimations) {
|
||||
animations = [...animations, ...element.getAnimations()];
|
||||
}
|
||||
element = walker.nextNode();
|
||||
}
|
||||
|
||||
assert_equals(animations.length, document.getAnimations().length,
|
||||
'The number of animations got by DeepTreeWalker and ' +
|
||||
'document.getAnimations() should be the same');
|
||||
}, 'Element.getAnimations() used by traversing DeepTreeWalker');
|
||||
|
||||
</script>
|
||||
</body>
|
||||
854
dom/animation/test/chrome/test_observers_for_sync_api.html
Normal file
854
dom/animation/test/chrome/test_observers_for_sync_api.html
Normal file
|
|
@ -0,0 +1,854 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<title>
|
||||
Test chrome-only MutationObserver animation notifications for sync APIs
|
||||
</title>
|
||||
<script type="application/javascript" src="../testharness.js"></script>
|
||||
<script type="application/javascript" src="../testharnessreport.js"></script>
|
||||
<script type="application/javascript" src="../testcommon.js"></script>
|
||||
<div id="log"></div>
|
||||
<style>
|
||||
@keyframes anim {
|
||||
to { transform: translate(100px); }
|
||||
}
|
||||
@keyframes anotherAnim {
|
||||
to { transform: translate(0px); }
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
|
||||
function assert_record_list(actual, expected, desc, index, listName) {
|
||||
assert_equals(actual.length, expected.length,
|
||||
`${desc} - record[${index}].${listName} length`);
|
||||
if (actual.length != expected.length) {
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < actual.length; i++) {
|
||||
assert_not_equals(actual.indexOf(expected[i]), -1,
|
||||
`${desc} - record[${index}].${listName} contains expected Animation`);
|
||||
}
|
||||
}
|
||||
|
||||
function assert_equals_records(actual, expected, desc) {
|
||||
assert_equals(actual.length, expected.length, `${desc} - number of records`);
|
||||
if (actual.length != expected.length) {
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < actual.length; i++) {
|
||||
assert_record_list(actual[i].addedAnimations,
|
||||
expected[i].added, desc, i, "addedAnimations");
|
||||
assert_record_list(actual[i].changedAnimations,
|
||||
expected[i].changed, desc, i, "changedAnimations");
|
||||
assert_record_list(actual[i].removedAnimations,
|
||||
expected[i].removed, desc, i, "removedAnimations");
|
||||
}
|
||||
}
|
||||
|
||||
// Create a pseudo element
|
||||
function createPseudo(test, element, type) {
|
||||
addStyle(test, { '@keyframes anim': '',
|
||||
['.pseudo::' + type]: 'animation: anim 10s;' });
|
||||
element.classList.add('pseudo');
|
||||
var anims = document.getAnimations();
|
||||
assert_true(anims.length >= 1);
|
||||
var anim = anims[anims.length - 1];
|
||||
assert_equals(anim.effect.target.parentElement, element);
|
||||
assert_equals(anim.effect.target.type, '::' + type);
|
||||
anim.cancel();
|
||||
return anim.effect.target;
|
||||
}
|
||||
|
||||
[ { subtree: false },
|
||||
{ subtree: true }
|
||||
].forEach(aOptions => {
|
||||
test(t => {
|
||||
var div = addDiv(t);
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var anim = div.animate({ opacity: [ 0, 1 ] }, 200 * MS_PER_SEC);
|
||||
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [anim], changed: [], removed: [] }],
|
||||
"records after animation is added");
|
||||
|
||||
anim.effect.timing.duration = 100 * MS_PER_SEC;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [anim], removed: [] }],
|
||||
"records after duration is changed");
|
||||
|
||||
anim.effect.timing.duration = 100 * MS_PER_SEC;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[], "records after assigning same value");
|
||||
|
||||
anim.currentTime = anim.effect.timing.duration * 2;
|
||||
anim.finish();
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [], removed: [anim] }],
|
||||
"records after animation end");
|
||||
|
||||
anim.effect.timing.duration = anim.effect.timing.duration * 3;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [anim], changed: [], removed: [] }],
|
||||
"records after animation restarted");
|
||||
|
||||
anim.effect.timing.duration = "auto";
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [], removed: [anim] }],
|
||||
"records after duration set \"auto\"");
|
||||
|
||||
anim.effect.timing.duration = "auto";
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[], "records after assigning same value \"auto\"");
|
||||
}, "change_duration_and_currenttime");
|
||||
|
||||
test(t => {
|
||||
var div = addDiv(t);
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var anim = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
|
||||
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [anim], changed: [], removed: [] }],
|
||||
"records after animation is added");
|
||||
|
||||
anim.effect.timing.endDelay = 10 * MS_PER_SEC;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [anim], removed: [] }],
|
||||
"records after endDelay is changed");
|
||||
|
||||
anim.effect.timing.endDelay = 10 * MS_PER_SEC;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[], "records after assigning same value");
|
||||
|
||||
anim.currentTime = 109 * MS_PER_SEC;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [], removed: [anim] }],
|
||||
"records after currentTime during endDelay");
|
||||
|
||||
anim.effect.timing.endDelay = -110 * MS_PER_SEC;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[], "records after assigning negative value");
|
||||
}, "change_enddelay_and_currenttime");
|
||||
|
||||
test(t => {
|
||||
var div = addDiv(t);
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var anim = div.animate({ opacity: [ 0, 1 ] },
|
||||
{ duration: 100 * MS_PER_SEC,
|
||||
endDelay: -100 * MS_PER_SEC });
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[], "records after animation is added");
|
||||
}, "zero_end_time");
|
||||
|
||||
test(t => {
|
||||
var div = addDiv(t);
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var anim = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
|
||||
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [anim], changed: [], removed: [] }],
|
||||
"records after animation is added");
|
||||
|
||||
anim.effect.timing.iterations = 2;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [anim], removed: [] }],
|
||||
"records after iterations is changed");
|
||||
|
||||
anim.effect.timing.iterations = 2;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[], "records after assigning same value");
|
||||
|
||||
anim.effect.timing.iterations = 0;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [], removed: [anim] }],
|
||||
"records after animation end");
|
||||
|
||||
anim.effect.timing.iterations = Infinity;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [anim], changed: [], removed: [] }],
|
||||
"records after animation restarted");
|
||||
}, "change_iterations");
|
||||
|
||||
test(t => {
|
||||
var div = addDiv(t);
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var anim = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
|
||||
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [anim], changed: [], removed: [] }],
|
||||
"records after animation is added");
|
||||
|
||||
anim.effect.timing.delay = 100;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [anim], removed: [] }],
|
||||
"records after delay is changed");
|
||||
|
||||
anim.effect.timing.delay = 100;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[], "records after assigning same value");
|
||||
|
||||
anim.effect.timing.delay = -100 * MS_PER_SEC;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [], removed: [anim] }],
|
||||
"records after animation end");
|
||||
|
||||
anim.effect.timing.delay = 0;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [anim], changed: [], removed: [] }],
|
||||
"records after animation restarted");
|
||||
}, "change_delay");
|
||||
|
||||
test(t => {
|
||||
var div = addDiv(t);
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var anim = div.animate({ opacity: [ 0, 1 ] },
|
||||
{ duration: 100 * MS_PER_SEC,
|
||||
easing: "steps(2, start)" });
|
||||
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [anim], changed: [], removed: [] }],
|
||||
"records after animation is added");
|
||||
|
||||
anim.effect.timing.easing = "steps(2, end)";
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [anim], removed: [] }],
|
||||
"records after easing is changed");
|
||||
|
||||
anim.effect.timing.easing = "steps(2, end)";
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[], "records after assigning same value");
|
||||
}, "change_easing");
|
||||
|
||||
test(t => {
|
||||
var div = addDiv(t);
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var anim = div.animate({ opacity: [ 0, 1 ] },
|
||||
{ duration: 100, delay: -100 });
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[], "records after assigning negative value");
|
||||
}, "negative_delay_in_constructor");
|
||||
|
||||
test(t => {
|
||||
var div = addDiv(t);
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var effect = new KeyframeEffectReadOnly(null,
|
||||
{ opacity: [ 0, 1 ] },
|
||||
{ duration: 100 * MS_PER_SEC });
|
||||
var anim = new Animation(effect, document.timeline);
|
||||
anim.play();
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[], "no records after animation is added");
|
||||
}, "create_animation_without_target");
|
||||
|
||||
test(t => {
|
||||
var div = addDiv(t);
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var anim = div.animate({ opacity: [ 0, 1 ] },
|
||||
{ duration: 100 * MS_PER_SEC });
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [anim], changed: [], removed: [] }],
|
||||
"records after animation is added");
|
||||
|
||||
anim.effect.target = div;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[], "no records after setting the same target");
|
||||
|
||||
anim.effect.target = null;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [], removed: [anim] }],
|
||||
"records after setting null");
|
||||
|
||||
anim.effect.target = null;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[], "records after setting redundant null");
|
||||
}, "set_redundant_animation_target");
|
||||
|
||||
test(t => {
|
||||
var div = addDiv(t);
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var anim = div.animate({ opacity: [ 0, 1 ] },
|
||||
{ duration: 100 * MS_PER_SEC });
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [anim], changed: [], removed: [] }],
|
||||
"records after animation is added");
|
||||
|
||||
anim.effect = null;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [], removed: [anim] }],
|
||||
"records after animation is removed");
|
||||
}, "set_null_animation_effect");
|
||||
|
||||
test(t => {
|
||||
var div = addDiv(t);
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var anim = new Animation();
|
||||
anim.play();
|
||||
anim.effect = new KeyframeEffect(div, { opacity: [ 0, 1 ] },
|
||||
100 * MS_PER_SEC);
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [anim], changed: [], removed: [] }],
|
||||
"records after animation is added");
|
||||
}, "set_effect_on_null_effect_animation");
|
||||
|
||||
test(t => {
|
||||
var div = addDiv(t);
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var anim = div.animate({ marginLeft: [ "0px", "100px" ] },
|
||||
100 * MS_PER_SEC);
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [anim], changed: [], removed: [] }],
|
||||
"records after animation is added");
|
||||
|
||||
anim.effect = new KeyframeEffect(div, { opacity: [ 0, 1 ] },
|
||||
100 * MS_PER_SEC);
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [anim], removed: [] }],
|
||||
"records after replace effects");
|
||||
}, "replace_effect_targeting_on_the_same_element");
|
||||
|
||||
test(t => {
|
||||
var div = addDiv(t);
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var anim = div.animate({ marginLeft: [ "0px", "100px" ] },
|
||||
100 * MS_PER_SEC);
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [anim], changed: [], removed: [] }],
|
||||
"records after animation is added");
|
||||
|
||||
anim.currentTime = 60 * MS_PER_SEC;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [anim], removed: [] }],
|
||||
"records after animation is changed");
|
||||
|
||||
anim.effect = new KeyframeEffect(div, { opacity: [ 0, 1 ] },
|
||||
50 * MS_PER_SEC);
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [], removed: [anim] }],
|
||||
"records after replacing effects");
|
||||
}, "replace_effect_targeting_on_the_same_element_not_in_effect");
|
||||
|
||||
test(t => {
|
||||
var div = addDiv(t);
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var anim = div.animate([ { marginLeft: "0px" },
|
||||
{ marginLeft: "-20px" },
|
||||
{ marginLeft: "100px" },
|
||||
{ marginLeft: "50px" } ],
|
||||
{ duration: 100 * MS_PER_SEC });
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [anim], changed: [], removed: [] }],
|
||||
"records after animation is added");
|
||||
|
||||
anim.effect.spacing = "paced(margin-left)";
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [anim], removed: [] }],
|
||||
"records after animation is changed");
|
||||
}, "set_spacing");
|
||||
|
||||
test(t => {
|
||||
var div = addDiv(t);
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var anim = div.animate([ { marginLeft: "0px" },
|
||||
{ marginLeft: "-20px" },
|
||||
{ marginLeft: "100px" },
|
||||
{ marginLeft: "50px" } ],
|
||||
{ duration: 100 * MS_PER_SEC,
|
||||
spacing: "paced(margin-left)" });
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [anim], changed: [], removed: [] }],
|
||||
"records after animation is added");
|
||||
|
||||
anim.effect.spacing = "paced(animation-duration)";
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [anim], removed: [] }],
|
||||
"records after setting a non-animatable paced property");
|
||||
}, "set_spacing_on_a_non-animatable_property");
|
||||
|
||||
test(t => {
|
||||
var div = addDiv(t);
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var anim = div.animate([ { marginLeft: "0px" },
|
||||
{ marginLeft: "-20px" },
|
||||
{ marginLeft: "100px" },
|
||||
{ marginLeft: "50px" } ],
|
||||
{ duration: 100 * MS_PER_SEC,
|
||||
spacing: "paced(margin-left)" });
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [anim], changed: [], removed: [] }],
|
||||
"records after animation is added");
|
||||
|
||||
anim.effect.spacing = "paced(margin-left)";
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[], "no record after setting the same spacing");
|
||||
}, "set_the_same_spacing");
|
||||
|
||||
// Test that starting a single animation that is cancelled by calling
|
||||
// cancel() dispatches an added notification and then a removed
|
||||
// notification.
|
||||
test(t => {
|
||||
var div = addDiv(t, { style: "animation: anim 100s forwards" });
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var animations = div.getAnimations();
|
||||
assert_equals(animations.length, 1,
|
||||
"getAnimations().length after animation start");
|
||||
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: animations, changed: [], removed: [] }],
|
||||
"records after animation start");
|
||||
|
||||
animations[0].cancel();
|
||||
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [], removed: animations }],
|
||||
"records after animation end");
|
||||
|
||||
// Re-trigger the animation.
|
||||
animations[0].play();
|
||||
|
||||
// Single MutationRecord for the Animation (re-)addition.
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: animations, changed: [], removed: [] }],
|
||||
"records after animation start");
|
||||
}, "single_animation_cancelled_api");
|
||||
|
||||
// Test that updating a property on the Animation object dispatches a changed
|
||||
// notification.
|
||||
[
|
||||
{ prop: "playbackRate", val: 0.5 },
|
||||
{ prop: "startTime", val: 50 * MS_PER_SEC },
|
||||
{ prop: "currentTime", val: 50 * MS_PER_SEC },
|
||||
].forEach(function(aChangeTest) {
|
||||
test(t => {
|
||||
// We use a forwards fill mode so that even if the change we make causes
|
||||
// the animation to become finished, it will still be "relevant" so we
|
||||
// won't mark it as removed.
|
||||
var div = addDiv(t, { style: "animation: anim 100s forwards" });
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var animations = div.getAnimations();
|
||||
assert_equals(animations.length, 1,
|
||||
"getAnimations().length after animation start");
|
||||
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: animations, changed: [], removed: [] }],
|
||||
"records after animation start");
|
||||
|
||||
// Update the property.
|
||||
animations[0][aChangeTest.prop] = aChangeTest.val;
|
||||
|
||||
// Make a redundant change.
|
||||
animations[0][aChangeTest.prop] = animations[0][aChangeTest.prop];
|
||||
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: animations, removed: [] }],
|
||||
"records after animation property change");
|
||||
}, `single_animation_api_change_${aChangeTest.prop}`);
|
||||
});
|
||||
|
||||
// Test that making a redundant change to currentTime while an Animation
|
||||
// is pause-pending still generates a change MutationRecord since setting
|
||||
// the currentTime to any value in this state aborts the pending pause.
|
||||
test(t => {
|
||||
var div = addDiv(t, { style: "animation: anim 100s" });
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var animations = div.getAnimations();
|
||||
assert_equals(animations.length, 1,
|
||||
"getAnimations().length after animation start");
|
||||
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: animations, changed: [], removed: [] }],
|
||||
"records after animation start");
|
||||
|
||||
animations[0].pause();
|
||||
|
||||
// We are now pause-pending. Even if we make a redundant change to the
|
||||
// currentTime, we should still get a change record because setting the
|
||||
// currentTime while pause-pending has the effect of cancelling a pause.
|
||||
animations[0].currentTime = animations[0].currentTime;
|
||||
|
||||
// Two MutationRecords for the Animation changes: one for pausing, one
|
||||
// for aborting the pause.
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: animations, removed: [] },
|
||||
{ added: [], changed: animations, removed: [] }],
|
||||
"records after pausing then seeking");
|
||||
}, "change_currentTime_while_pause_pending");
|
||||
|
||||
// Test that calling finish() on a forwards-filling Animation dispatches
|
||||
// a changed notification.
|
||||
test(t => {
|
||||
var div = addDiv(t, { style: "animation: anim 100s forwards" });
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var animations = div.getAnimations();
|
||||
assert_equals(animations.length, 1,
|
||||
"getAnimations().length after animation start");
|
||||
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: animations, changed: [], removed: [] }],
|
||||
"records after animation start");
|
||||
|
||||
animations[0].finish();
|
||||
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: animations, removed: [] }],
|
||||
"records after finish()");
|
||||
|
||||
// Redundant finish.
|
||||
animations[0].finish();
|
||||
|
||||
// Ensure no change records.
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[], "records after redundant finish()");
|
||||
}, "finish_with_forwards_fill");
|
||||
|
||||
// Test that calling finish() on an Animation that does not fill forwards,
|
||||
// dispatches a removal notification.
|
||||
test(t => {
|
||||
var div = addDiv(t, { style: "animation: anim 100s" });
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var animations = div.getAnimations();
|
||||
assert_equals(animations.length, 1,
|
||||
"getAnimations().length after animation start");
|
||||
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: animations, changed: [], removed: [] }],
|
||||
"records after animation start");
|
||||
|
||||
animations[0].finish();
|
||||
|
||||
// Single MutationRecord for the Animation removal.
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [], removed: animations }],
|
||||
"records after finishing");
|
||||
}, "finish_without_fill");
|
||||
|
||||
// Test that calling finish() on a forwards-filling Animation dispatches
|
||||
test(t => {
|
||||
var div = addDiv(t, { style: "animation: anim 100s" });
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var animation = div.getAnimations()[0];
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [animation], changed: [], removed: []}],
|
||||
"records after creation");
|
||||
animation.id = "new id";
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [animation], removed: []}],
|
||||
"records after id is changed");
|
||||
|
||||
animation.id = "new id";
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[], "records after assigning same value with id");
|
||||
}, "change_id");
|
||||
|
||||
// Test that calling reverse() dispatches a changed notification.
|
||||
test(t => {
|
||||
var div = addDiv(t, { style: "animation: anim 100s both" });
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var animations = div.getAnimations();
|
||||
assert_equals(animations.length, 1,
|
||||
"getAnimations().length after animation start");
|
||||
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: animations, changed: [], removed: [] }],
|
||||
"records after animation start");
|
||||
|
||||
animations[0].reverse();
|
||||
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: animations, removed: [] }],
|
||||
"records after calling reverse()");
|
||||
}, "reverse");
|
||||
|
||||
// Test that calling reverse() does *not* dispatch a changed notification
|
||||
// when playbackRate == 0.
|
||||
test(t => {
|
||||
var div = addDiv(t, { style: "animation: anim 100s both" });
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var animations = div.getAnimations();
|
||||
assert_equals(animations.length, 1,
|
||||
"getAnimations().length after animation start");
|
||||
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: animations, changed: [], removed: [] }],
|
||||
"records after animation start");
|
||||
|
||||
// Seek to the middle and set playbackRate to zero.
|
||||
animations[0].currentTime = 50 * MS_PER_SEC;
|
||||
animations[0].playbackRate = 0;
|
||||
|
||||
// Two MutationRecords, one for each change.
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: animations, removed: [] },
|
||||
{ added: [], changed: animations, removed: [] }],
|
||||
"records after seeking and setting playbackRate");
|
||||
|
||||
animations[0].reverse();
|
||||
|
||||
// We should get no notifications.
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[], "records after calling reverse()");
|
||||
}, "reverse_with_zero_playbackRate");
|
||||
|
||||
// Test that attempting to start an animation that should already be finished
|
||||
// does not send any notifications.
|
||||
test(t => {
|
||||
// Start an animation that should already be finished.
|
||||
var div = addDiv(t, { style: "animation: anim 1s -2s;" });
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
// The animation should cause no Animations to be created.
|
||||
var animations = div.getAnimations();
|
||||
assert_equals(animations.length, 0,
|
||||
"getAnimations().length after animation start");
|
||||
|
||||
// And we should get no notifications.
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[], "records after attempted animation start");
|
||||
}, "already_finished");
|
||||
|
||||
test(t => {
|
||||
var div = addDiv(t, { style: "animation: anim 100s, anotherAnim 100s" });
|
||||
var observer =
|
||||
setupSynchronousObserver(t,
|
||||
aOptions.subtree ? div.parentNode : div,
|
||||
aOptions.subtree);
|
||||
|
||||
var animations = div.getAnimations();
|
||||
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: animations, changed: [], removed: []}],
|
||||
"records after creation");
|
||||
|
||||
div.style.animation = "anotherAnim 100s, anim 100s";
|
||||
animations = div.getAnimations();
|
||||
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: animations, removed: []}],
|
||||
"records after the order is changed");
|
||||
|
||||
div.style.animation = "anotherAnim 100s, anim 100s";
|
||||
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[], "no records after applying the same order");
|
||||
}, "animtion_order_change");
|
||||
|
||||
});
|
||||
|
||||
test(t => {
|
||||
var div = addDiv(t);
|
||||
var observer = setupSynchronousObserver(t, div, true);
|
||||
|
||||
var child = document.createElement("div");
|
||||
div.appendChild(child);
|
||||
|
||||
var anim1 = div.animate({ marginLeft: [ "0px", "50px" ] },
|
||||
100 * MS_PER_SEC);
|
||||
var anim2 = child.animate({ marginLeft: [ "0px", "100px" ] },
|
||||
50 * MS_PER_SEC);
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [anim1], changed: [], removed: [] },
|
||||
{ added: [anim2], changed: [], removed: [] }],
|
||||
"records after animation is added");
|
||||
|
||||
// After setting a new effect, we remove the current animation, anim1,
|
||||
// because it is no longer attached to |div|, and then remove the previous
|
||||
// animation, anim2. Finally, add back the anim1 which is in effect on
|
||||
// |child| now. In addition, we sort them by tree order and they are
|
||||
// batched.
|
||||
anim1.effect = anim2.effect;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [], removed: [anim1] }, // div
|
||||
{ added: [anim1], changed: [], removed: [anim2] }], // child
|
||||
"records after animation effects are changed");
|
||||
}, "set_effect_with_previous_animation");
|
||||
|
||||
test(t => {
|
||||
var div = addDiv(t);
|
||||
var observer = setupSynchronousObserver(t, document, true);
|
||||
|
||||
var anim = div.animate({ opacity: [ 0, 1 ] },
|
||||
{ duration: 100 * MS_PER_SEC });
|
||||
|
||||
var newTarget = document.createElement("div");
|
||||
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [anim], changed: [], removed: [] }],
|
||||
"records after animation is added");
|
||||
|
||||
anim.effect.target = null;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [], removed: [anim] }],
|
||||
"records after setting null");
|
||||
|
||||
anim.effect.target = div;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [anim], changed: [], removed: [] }],
|
||||
"records after setting a target");
|
||||
|
||||
anim.effect.target = addDiv(t);
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [], removed: [anim] },
|
||||
{ added: [anim], changed: [], removed: [] }],
|
||||
"records after setting a different target");
|
||||
}, "set_animation_target");
|
||||
|
||||
test(t => {
|
||||
var div = addDiv(t);
|
||||
var pseudoTarget = createPseudo(t, div, 'before');
|
||||
var observer = setupSynchronousObserver(t, div, true);
|
||||
|
||||
var anim = pseudoTarget.animate({ opacity: [ 0, 1 ] }, 200 * MS_PER_SEC);
|
||||
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [anim], changed: [], removed: [] }],
|
||||
"records after animation is added");
|
||||
|
||||
anim.effect.timing.duration = 100 * MS_PER_SEC;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [anim], removed: [] }],
|
||||
"records after duration is changed");
|
||||
|
||||
anim.effect.timing.duration = 100 * MS_PER_SEC;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[], "records after assigning same value");
|
||||
|
||||
anim.currentTime = anim.effect.timing.duration * 2;
|
||||
anim.finish();
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [], removed: [anim] }],
|
||||
"records after animation end");
|
||||
|
||||
anim.effect.timing.duration = anim.effect.timing.duration * 3;
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [anim], changed: [], removed: [] }],
|
||||
"records after animation restarted");
|
||||
|
||||
anim.effect.timing.duration = "auto";
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [], removed: [anim] }],
|
||||
"records after duration set \"auto\"");
|
||||
|
||||
anim.effect.timing.duration = "auto";
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[], "records after assigning same value \"auto\"");
|
||||
}, "change_duration_and_currenttime_on_pseudo_elements");
|
||||
|
||||
test(t => {
|
||||
var div = addDiv(t);
|
||||
var pseudoTarget = createPseudo(t, div, 'before');
|
||||
var observer = setupSynchronousObserver(t, div, false);
|
||||
|
||||
var anim = div.animate({ opacity: [ 0, 1 ] },
|
||||
{ duration: 100 * MS_PER_SEC });
|
||||
var pAnim = pseudoTarget.animate({ opacity: [ 0, 1 ] },
|
||||
{ duration: 100 * MS_PER_SEC });
|
||||
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [anim], changed: [], removed: [] }],
|
||||
"records after animation is added");
|
||||
|
||||
anim.finish();
|
||||
pAnim.finish();
|
||||
|
||||
assert_equals_records(observer.takeRecords(),
|
||||
[{ added: [], changed: [], removed: [anim] }],
|
||||
"records after animation is finished");
|
||||
}, "exclude_animations_targeting_pseudo_elements");
|
||||
|
||||
</script>
|
||||
815
dom/animation/test/chrome/test_restyles.html
Normal file
815
dom/animation/test/chrome/test_restyles.html
Normal file
|
|
@ -0,0 +1,815 @@
|
|||
<!doctype html>
|
||||
<head>
|
||||
<meta charset=utf-8>
|
||||
<title>Tests restyles caused by animations</title>
|
||||
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
|
||||
<script src="chrome://mochikit/content/tests/SimpleTest/paint_listener.js"></script>
|
||||
<script src="../testcommon.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
<style>
|
||||
@keyframes opacity {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
@keyframes background-color {
|
||||
from { background-color: red; }
|
||||
to { background-color: blue; }
|
||||
}
|
||||
@keyframes rotate {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
div {
|
||||
/* Element needs geometry to be eligible for layerization */
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: white;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
function observeStyling(frameCount, onFrame) {
|
||||
var docShell = window.QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
|
||||
.getInterface(SpecialPowers.Ci.nsIWebNavigation)
|
||||
.QueryInterface(SpecialPowers.Ci.nsIDocShell);
|
||||
|
||||
docShell.recordProfileTimelineMarkers = true;
|
||||
docShell.popProfileTimelineMarkers();
|
||||
|
||||
return new Promise(function(resolve) {
|
||||
return waitForAnimationFrames(frameCount, onFrame).then(function() {
|
||||
var markers = docShell.popProfileTimelineMarkers();
|
||||
docShell.recordProfileTimelineMarkers = false;
|
||||
var stylingMarkers = markers.filter(function(marker, index) {
|
||||
return marker.name == 'Styles' &&
|
||||
(marker.restyleHint == 'eRestyle_CSSAnimations' ||
|
||||
marker.restyleHint == 'eRestyle_CSSTransitions');
|
||||
});
|
||||
resolve(stylingMarkers);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function ensureElementRemoval(aElement) {
|
||||
return new Promise(function(resolve) {
|
||||
aElement.remove();
|
||||
waitForAllPaintsFlushed(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
var omtaEnabled = isOMTAEnabled();
|
||||
|
||||
var isAndroid = !!navigator.userAgent.includes("Android");
|
||||
|
||||
function add_task_if_omta_enabled(test) {
|
||||
if (!omtaEnabled) {
|
||||
info(test.name + " is skipped because OMTA is disabled");
|
||||
return;
|
||||
}
|
||||
add_task(test);
|
||||
}
|
||||
|
||||
// We need to wait for all paints before running tests to avoid contaminations
|
||||
// from styling of this document itself.
|
||||
waitForAllPaints(function() {
|
||||
add_task(function* restyling_for_main_thread_animations() {
|
||||
var div = addDiv(null, { style: 'animation: background-color 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
yield animation.ready;
|
||||
ok(!animation.isRunningOnCompositor);
|
||||
|
||||
var markers = yield observeStyling(5);
|
||||
is(markers.length, 5,
|
||||
'CSS animations running on the main-thread should update style ' +
|
||||
'on the main thread');
|
||||
yield ensureElementRemoval(div);
|
||||
});
|
||||
|
||||
add_task_if_omta_enabled(function* no_restyling_for_compositor_animations() {
|
||||
var div = addDiv(null, { style: 'animation: opacity 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
yield animation.ready;
|
||||
ok(animation.isRunningOnCompositor);
|
||||
|
||||
var markers = yield observeStyling(5);
|
||||
is(markers.length, 0,
|
||||
'CSS animations running on the compositor should not update style ' +
|
||||
'on the main thread');
|
||||
yield ensureElementRemoval(div);
|
||||
});
|
||||
|
||||
add_task_if_omta_enabled(function* no_restyling_for_compositor_transitions() {
|
||||
var div = addDiv(null, { style: 'transition: opacity 100s; opacity: 0' });
|
||||
getComputedStyle(div).opacity;
|
||||
div.style.opacity = 1;
|
||||
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
yield animation.ready;
|
||||
ok(animation.isRunningOnCompositor);
|
||||
|
||||
var markers = yield observeStyling(5);
|
||||
is(markers.length, 0,
|
||||
'CSS transitions running on the compositor should not update style ' +
|
||||
'on the main thread');
|
||||
yield ensureElementRemoval(div);
|
||||
});
|
||||
|
||||
add_task_if_omta_enabled(function* no_restyling_when_animation_duration_is_changed() {
|
||||
var div = addDiv(null, { style: 'animation: opacity 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
yield animation.ready;
|
||||
ok(animation.isRunningOnCompositor);
|
||||
|
||||
div.animationDuration = '200s';
|
||||
|
||||
var markers = yield observeStyling(5);
|
||||
is(markers.length, 0,
|
||||
'Animations running on the compositor should not update style ' +
|
||||
'on the main thread');
|
||||
yield ensureElementRemoval(div);
|
||||
});
|
||||
|
||||
add_task_if_omta_enabled(function* only_one_restyling_after_finish_is_called() {
|
||||
var div = addDiv(null, { style: 'animation: opacity 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
yield animation.ready;
|
||||
ok(animation.isRunningOnCompositor);
|
||||
|
||||
animation.finish();
|
||||
|
||||
var markers = yield observeStyling(5);
|
||||
is(markers.length, 1,
|
||||
'Animations running on the compositor should only update style ' +
|
||||
'once after finish() is called');
|
||||
yield ensureElementRemoval(div);
|
||||
});
|
||||
|
||||
add_task(function* no_restyling_mouse_movement_on_finished_transition() {
|
||||
var div = addDiv(null, { style: 'transition: opacity 1ms; opacity: 0' });
|
||||
getComputedStyle(div).opacity;
|
||||
div.style.opacity = 1;
|
||||
|
||||
var animation = div.getAnimations()[0];
|
||||
var initialRect = div.getBoundingClientRect();
|
||||
|
||||
yield animation.finished;
|
||||
|
||||
var mouseX = initialRect.left + initialRect.width / 2;
|
||||
var mouseY = initialRect.top + initialRect.height / 2;
|
||||
var markers = yield observeStyling(5, function() {
|
||||
// We can't use synthesizeMouse here since synthesizeMouse causes
|
||||
// layout flush.
|
||||
synthesizeMouseAtPoint(mouseX++, mouseY++,
|
||||
{ type: 'mousemove' }, window);
|
||||
});
|
||||
|
||||
is(markers.length, 0,
|
||||
'Bug 1219236: Finished transitions should never cause restyles ' +
|
||||
'when mouse is moved on the animations');
|
||||
yield ensureElementRemoval(div);
|
||||
});
|
||||
|
||||
add_task(function* no_restyling_mouse_movement_on_finished_animation() {
|
||||
var div = addDiv(null, { style: 'animation: opacity 1ms' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
var initialRect = div.getBoundingClientRect();
|
||||
|
||||
yield animation.finished;
|
||||
|
||||
var mouseX = initialRect.left + initialRect.width / 2;
|
||||
var mouseY = initialRect.top + initialRect.height / 2;
|
||||
var markers = yield observeStyling(5, function() {
|
||||
// We can't use synthesizeMouse here since synthesizeMouse causes
|
||||
// layout flush.
|
||||
synthesizeMouseAtPoint(mouseX++, mouseY++,
|
||||
{ type: 'mousemove' }, window);
|
||||
});
|
||||
|
||||
is(markers.length, 0,
|
||||
'Bug 1219236: Finished animations should never cause restyles ' +
|
||||
'when mouse is moved on the animations');
|
||||
yield ensureElementRemoval(div);
|
||||
});
|
||||
|
||||
add_task_if_omta_enabled(function* no_restyling_compositor_animations_out_of_view_element() {
|
||||
if (!SpecialPowers.getBoolPref('dom.animations.offscreen-throttling')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var div = addDiv(null,
|
||||
{ style: 'animation: opacity 100s; transform: translateY(-400px);' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
yield animation.ready;
|
||||
ok(!animation.isRunningOnCompositor);
|
||||
|
||||
var markers = yield observeStyling(5);
|
||||
|
||||
is(markers.length, 0,
|
||||
'Animations running on the compositor in an out-of-view element ' +
|
||||
'should never cause restyles');
|
||||
yield ensureElementRemoval(div);
|
||||
});
|
||||
|
||||
add_task(function* no_restyling_main_thread_animations_out_of_view_element() {
|
||||
if (!SpecialPowers.getBoolPref('dom.animations.offscreen-throttling')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var div = addDiv(null,
|
||||
{ style: 'animation: background-color 100s; transform: translateY(-400px);' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
yield animation.ready;
|
||||
var markers = yield observeStyling(5);
|
||||
|
||||
is(markers.length, 0,
|
||||
'Animations running on the main-thread in an out-of-view element ' +
|
||||
'should never cause restyles');
|
||||
yield ensureElementRemoval(div);
|
||||
});
|
||||
|
||||
add_task_if_omta_enabled(function* no_restyling_compositor_animations_in_scrolled_out_element() {
|
||||
if (!SpecialPowers.getBoolPref('dom.animations.offscreen-throttling')) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
On Android the opacity animation runs on the compositor even if it is
|
||||
scrolled out of view. We will fix this in bug 1247800.
|
||||
*/
|
||||
if (isAndroid) {
|
||||
return;
|
||||
}
|
||||
var parentElement = addDiv(null,
|
||||
{ style: 'overflow-y: scroll; height: 20px;' });
|
||||
var div = addDiv(null,
|
||||
{ style: 'animation: opacity 100s; position: relative; top: 100px;' });
|
||||
parentElement.appendChild(div);
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
yield animation.ready;
|
||||
|
||||
var markers = yield observeStyling(5);
|
||||
|
||||
is(markers.length, 0,
|
||||
'Animations running on the compositor for elements ' +
|
||||
'which are scrolled out should never cause restyles');
|
||||
|
||||
yield ensureElementRemoval(parentElement);
|
||||
});
|
||||
|
||||
add_task(function* no_restyling_main_thread_animations_in_scrolled_out_element() {
|
||||
if (!SpecialPowers.getBoolPref('dom.animations.offscreen-throttling')) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
On Android throttled animations are left behind on the main thread in some
|
||||
frames, We will fix this in bug 1247800.
|
||||
*/
|
||||
if (isAndroid) {
|
||||
return;
|
||||
}
|
||||
|
||||
var parentElement = addDiv(null,
|
||||
{ style: 'overflow-y: scroll; height: 20px;' });
|
||||
var div = addDiv(null,
|
||||
{ style: 'animation: background-color 100s; position: relative; top: 100px;' });
|
||||
parentElement.appendChild(div);
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
yield animation.ready;
|
||||
var markers = yield observeStyling(5);
|
||||
|
||||
is(markers.length, 0,
|
||||
'Animations running on the main-thread for elements ' +
|
||||
'which are scrolled out should never cause restyles');
|
||||
|
||||
yield ensureElementRemoval(parentElement);
|
||||
});
|
||||
|
||||
add_task(function* no_restyling_main_thread_animations_in_nested_scrolled_out_element() {
|
||||
if (!SpecialPowers.getBoolPref('dom.animations.offscreen-throttling')) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
On Android throttled animations are left behind on the main thread in some
|
||||
frames, We will fix this in bug 1247800.
|
||||
*/
|
||||
if (isAndroid) {
|
||||
return;
|
||||
}
|
||||
|
||||
var grandParent = addDiv(null,
|
||||
{ style: 'overflow-y: scroll; height: 20px;' });
|
||||
var parentElement = addDiv(null,
|
||||
{ style: 'overflow-y: scroll; height: 100px;' });
|
||||
var div = addDiv(null,
|
||||
{ style: 'animation: background-color 100s; position: relative; top: 100px;' });
|
||||
grandParent.appendChild(parentElement);
|
||||
parentElement.appendChild(div);
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
yield animation.ready;
|
||||
var markers = yield observeStyling(5);
|
||||
|
||||
is(markers.length, 0,
|
||||
'Animations running on the main-thread which are in nested elements ' +
|
||||
'which are scrolled out should never cause restyles');
|
||||
|
||||
yield ensureElementRemoval(grandParent);
|
||||
});
|
||||
|
||||
add_task_if_omta_enabled(function* no_restyling_compositor_animations_in_visiblily_hidden_element() {
|
||||
var div = addDiv(null,
|
||||
{ style: 'animation: opacity 100s; visibility: hidden' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
yield animation.ready;
|
||||
ok(!animation.isRunningOnCompositor);
|
||||
|
||||
var markers = yield observeStyling(5);
|
||||
|
||||
todo_is(markers.length, 0,
|
||||
'Bug 1237454: Animations running on the compositor in ' +
|
||||
'visibility hidden element should never cause restyles');
|
||||
yield ensureElementRemoval(div);
|
||||
});
|
||||
|
||||
add_task(function* restyling_main_thread_animations_moved_in_view_by_scrolling() {
|
||||
if (!SpecialPowers.getBoolPref('dom.animations.offscreen-throttling')) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
On Android throttled animations are left behind on the main thread in some
|
||||
frames, We will fix this in bug 1247800.
|
||||
*/
|
||||
if (isAndroid) {
|
||||
return;
|
||||
}
|
||||
|
||||
var parentElement = addDiv(null,
|
||||
{ style: 'overflow-y: scroll; height: 20px;' });
|
||||
var div = addDiv(null,
|
||||
{ style: 'animation: background-color 100s; position: relative; top: 100px;' });
|
||||
parentElement.appendChild(div);
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
var parentRect = parentElement.getBoundingClientRect();
|
||||
var centerX = parentRect.left + parentRect.width / 2;
|
||||
var centerY = parentRect.top + parentRect.height / 2;
|
||||
|
||||
yield animation.ready;
|
||||
|
||||
var markers = yield observeStyling(1, function() {
|
||||
// We can't use synthesizeWheel here since synthesizeWheel causes
|
||||
// layout flush.
|
||||
synthesizeWheelAtPoint(centerX, centerY,
|
||||
{ deltaMode: WheelEvent.DOM_DELTA_PIXEL,
|
||||
deltaY: 100 });
|
||||
});
|
||||
|
||||
is(markers.length, 1,
|
||||
'Animations running on the main-thread which were in scrolled out ' +
|
||||
'elements should update restyling soon after the element moved in ' +
|
||||
'view by scrolling');
|
||||
|
||||
yield ensureElementRemoval(parentElement);
|
||||
});
|
||||
|
||||
add_task(function* restyling_main_thread_animations_moved_in_view_by_scrolling() {
|
||||
if (!SpecialPowers.getBoolPref('dom.animations.offscreen-throttling')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var grandParent = addDiv(null,
|
||||
{ style: 'overflow-y: scroll; height: 20px;' });
|
||||
var parentElement = addDiv(null,
|
||||
{ style: 'overflow-y: scroll; height: 200px;' });
|
||||
var div = addDiv(null,
|
||||
{ style: 'animation: background-color 100s; position: relative; top: 100px;' });
|
||||
grandParent.appendChild(parentElement);
|
||||
parentElement.appendChild(div);
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
var parentRect = grandParent.getBoundingClientRect();
|
||||
var centerX = parentRect.left + parentRect.width / 2;
|
||||
var centerY = parentRect.top + parentRect.height / 2;
|
||||
|
||||
yield animation.ready;
|
||||
|
||||
var markers = yield observeStyling(1, function() {
|
||||
// We can't use synthesizeWheel here since synthesizeWheel causes
|
||||
// layout flush.
|
||||
synthesizeWheelAtPoint(centerX, centerY,
|
||||
{ deltaMode: WheelEvent.DOM_DELTA_PIXEL,
|
||||
deltaY: 100 });
|
||||
});
|
||||
|
||||
// FIXME: We should reduce a redundant restyle here.
|
||||
ok(markers.length >= 1,
|
||||
'Animations running on the main-thread which were in nested scrolled ' +
|
||||
'out elements should update restyle soon after the element moved ' +
|
||||
'in view by scrolling');
|
||||
|
||||
yield ensureElementRemoval(grandParent);
|
||||
});
|
||||
|
||||
add_task(function* restyling_main_thread_animations_move_out_of_view_by_scrolling() {
|
||||
if (!SpecialPowers.getBoolPref('dom.animations.offscreen-throttling')) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
On Android throttled animations are left behind on the main thread in some
|
||||
frames, We will fix this in bug 1247800.
|
||||
*/
|
||||
if (isAndroid) {
|
||||
return;
|
||||
}
|
||||
|
||||
var parentElement = addDiv(null,
|
||||
{ style: 'overflow-y: scroll; height: 200px;' });
|
||||
var div = addDiv(null,
|
||||
{ style: 'animation: background-color 100s;' });
|
||||
var pad = addDiv(null,
|
||||
{ style: 'height: 400px;' });
|
||||
parentElement.appendChild(div);
|
||||
parentElement.appendChild(pad);
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
var parentRect = parentElement.getBoundingClientRect();
|
||||
var centerX = parentRect.left + parentRect.width / 2;
|
||||
var centerY = parentRect.top + parentRect.height / 2;
|
||||
|
||||
yield animation.ready;
|
||||
|
||||
// We can't use synthesizeWheel here since synthesizeWheel causes
|
||||
// layout flush.
|
||||
synthesizeWheelAtPoint(centerX, centerY,
|
||||
{ deltaMode: WheelEvent.DOM_DELTA_PIXEL,
|
||||
deltaY: 200 });
|
||||
|
||||
var markers = yield observeStyling(5);
|
||||
|
||||
// FIXME: We should reduce a redundant restyle here.
|
||||
ok(markers.length >= 0,
|
||||
'Animations running on the main-thread which are in scrolled out ' +
|
||||
'elements should throttle restyling');
|
||||
|
||||
yield ensureElementRemoval(parentElement);
|
||||
});
|
||||
|
||||
add_task(function* restyling_main_thread_animations_moved_in_view_by_resizing() {
|
||||
if (!SpecialPowers.getBoolPref('dom.animations.offscreen-throttling')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var parentElement = addDiv(null,
|
||||
{ style: 'overflow-y: scroll; height: 20px;' });
|
||||
var div = addDiv(null,
|
||||
{ style: 'animation: background-color 100s; position: relative; top: 100px;' });
|
||||
parentElement.appendChild(div);
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
yield animation.ready;
|
||||
|
||||
var markers = yield observeStyling(1, function() {
|
||||
parentElement.style.height = '100px';
|
||||
});
|
||||
|
||||
is(markers.length, 1,
|
||||
'Animations running on the main-thread which was in scrolled out ' +
|
||||
'elements should update restyling soon after the element moved in ' +
|
||||
'view by resizing');
|
||||
|
||||
yield ensureElementRemoval(parentElement);
|
||||
});
|
||||
|
||||
add_task(function* no_restyling_main_thread_animations_in_visiblily_hidden_element() {
|
||||
var div = addDiv(null,
|
||||
{ style: 'animation: background-color 100s; visibility: hidden' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
yield animation.ready;
|
||||
var markers = yield observeStyling(5);
|
||||
|
||||
todo_is(markers.length, 0,
|
||||
'Bug 1237454: Animations running on the main-thread in ' +
|
||||
'visibility hidden element should never cause restyles');
|
||||
yield ensureElementRemoval(div);
|
||||
});
|
||||
|
||||
add_task_if_omta_enabled(function* no_restyling_compositor_animations_after_pause_is_called() {
|
||||
var div = addDiv(null, { style: 'animation: opacity 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
yield animation.ready;
|
||||
ok(animation.isRunningOnCompositor);
|
||||
|
||||
animation.pause();
|
||||
|
||||
yield animation.ready;
|
||||
|
||||
var markers = yield observeStyling(5);
|
||||
is(markers.length, 0,
|
||||
'Bug 1232563: Paused animations running on the compositor should ' +
|
||||
'never cause restyles once after pause() is called');
|
||||
yield ensureElementRemoval(div);
|
||||
});
|
||||
|
||||
add_task(function* no_restyling_main_thread_animations_after_pause_is_called() {
|
||||
var div = addDiv(null, { style: 'animation: background-color 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
yield animation.ready;
|
||||
|
||||
animation.pause();
|
||||
|
||||
yield animation.ready;
|
||||
|
||||
var markers = yield observeStyling(5);
|
||||
is(markers.length, 0,
|
||||
'Bug 1232563: Paused animations running on the main-thread should ' +
|
||||
'never cause restyles after pause() is called');
|
||||
yield ensureElementRemoval(div);
|
||||
});
|
||||
|
||||
add_task_if_omta_enabled(function* only_one_restyling_when_current_time_is_set_to_middle_of_duration() {
|
||||
var div = addDiv(null, { style: 'animation: opacity 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
yield animation.ready;
|
||||
|
||||
animation.currentTime = 50 * MS_PER_SEC;
|
||||
|
||||
var markers = yield observeStyling(5);
|
||||
is(markers.length, 1,
|
||||
'Bug 1235478: Animations running on the compositor should only once ' +
|
||||
'update style when currentTime is set to middle of duration time');
|
||||
yield ensureElementRemoval(div);
|
||||
});
|
||||
|
||||
add_task_if_omta_enabled(function* change_duration_and_currenttime() {
|
||||
var div = addDiv(null);
|
||||
var animation = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
|
||||
|
||||
yield animation.ready;
|
||||
ok(animation.isRunningOnCompositor);
|
||||
|
||||
// Set currentTime to a time longer than duration.
|
||||
animation.currentTime = 500 * MS_PER_SEC;
|
||||
|
||||
// Now the animation immediately get back from compositor.
|
||||
ok(!animation.isRunningOnCompositor);
|
||||
|
||||
// Extend the duration.
|
||||
animation.effect.timing.duration = 800 * MS_PER_SEC;
|
||||
var markers = yield observeStyling(5);
|
||||
is(markers.length, 1,
|
||||
'Animations running on the compositor should update style ' +
|
||||
'when timing.duration is made longer than the current time');
|
||||
|
||||
yield ensureElementRemoval(div);
|
||||
});
|
||||
|
||||
add_task(function* script_animation_on_display_none_element() {
|
||||
var div = addDiv(null);
|
||||
var animation = div.animate({ backgroundColor: [ 'red', 'blue' ] },
|
||||
100 * MS_PER_SEC);
|
||||
|
||||
yield animation.ready;
|
||||
|
||||
div.style.display = 'none';
|
||||
|
||||
// We need to wait a frame to apply display:none style.
|
||||
yield waitForFrame();
|
||||
|
||||
is(animation.playState, 'running',
|
||||
'Script animations keep running even when the target element has ' +
|
||||
'"display: none" style');
|
||||
|
||||
ok(!animation.isRunningOnCompositor,
|
||||
'Script animations on "display:none" element should not run on the ' +
|
||||
'compositor');
|
||||
|
||||
var markers = yield observeStyling(5);
|
||||
is(markers.length, 0,
|
||||
'Script animations on "display: none" element should not update styles');
|
||||
|
||||
div.style.display = '';
|
||||
|
||||
// We need to wait a frame to unapply display:none style.
|
||||
yield waitForFrame();
|
||||
|
||||
var markers = yield observeStyling(5);
|
||||
is(markers.length, 5,
|
||||
'Script animations restored from "display: none" state should update ' +
|
||||
'styles');
|
||||
|
||||
yield ensureElementRemoval(div);
|
||||
});
|
||||
|
||||
add_task_if_omta_enabled(function* compositable_script_animation_on_display_none_element() {
|
||||
var div = addDiv(null);
|
||||
var animation = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
|
||||
|
||||
yield animation.ready;
|
||||
|
||||
div.style.display = 'none';
|
||||
|
||||
// We need to wait a frame to apply display:none style.
|
||||
yield waitForFrame();
|
||||
|
||||
is(animation.playState, 'running',
|
||||
'Opacity script animations keep running even when the target element ' +
|
||||
'has "display: none" style');
|
||||
|
||||
ok(!animation.isRunningOnCompositor,
|
||||
'Opacity script animations on "display:none" element should not ' +
|
||||
'run on the compositor');
|
||||
|
||||
var markers = yield observeStyling(5);
|
||||
is(markers.length, 0,
|
||||
'Opacity script animations on "display: none" element should not ' +
|
||||
'update styles');
|
||||
|
||||
div.style.display = '';
|
||||
|
||||
// We need to wait a frame to unapply display:none style.
|
||||
yield waitForFrame();
|
||||
|
||||
ok(animation.isRunningOnCompositor,
|
||||
'Opacity script animations restored from "display: none" should be ' +
|
||||
'run on the compositor');
|
||||
|
||||
yield ensureElementRemoval(div);
|
||||
});
|
||||
|
||||
add_task(function* restyling_for_empty_keyframes() {
|
||||
var div = addDiv(null);
|
||||
var animation = div.animate({ }, 100 * MS_PER_SEC);
|
||||
|
||||
yield animation.ready;
|
||||
var markers = yield observeStyling(5);
|
||||
|
||||
is(markers.length, 0,
|
||||
'Animations with no keyframes should not cause restyles');
|
||||
|
||||
animation.effect.setKeyframes({ backgroundColor: ['red', 'blue'] });
|
||||
markers = yield observeStyling(5);
|
||||
|
||||
is(markers.length, 5,
|
||||
'Setting valid keyframes should cause regular animation restyles to ' +
|
||||
'occur');
|
||||
|
||||
animation.effect.setKeyframes({ });
|
||||
markers = yield observeStyling(5);
|
||||
|
||||
is(markers.length, 1,
|
||||
'Setting an empty set of keyframes should trigger a single restyle ' +
|
||||
'to remove the previous animated style');
|
||||
|
||||
yield ensureElementRemoval(div);
|
||||
});
|
||||
|
||||
add_task_if_omta_enabled(function* no_restyling_when_animation_style_when_re_setting_same_animation_property() {
|
||||
var div = addDiv(null, { style: 'animation: opacity 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
yield animation.ready;
|
||||
ok(animation.isRunningOnCompositor);
|
||||
// Apply the same animation style
|
||||
div.style.animation = 'opacity 100s';
|
||||
var markers = yield observeStyling(5);
|
||||
is(markers.length, 0,
|
||||
'Applying same animation style ' +
|
||||
'should never cause restyles');
|
||||
yield ensureElementRemoval(div);
|
||||
});
|
||||
|
||||
add_task(function* necessary_update_should_be_invoked() {
|
||||
var div = addDiv(null, { style: 'animation: background-color 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
yield animation.ready;
|
||||
yield waitForAnimationFrames(5);
|
||||
// Apply another animation style
|
||||
div.style.animation = 'background-color 110s';
|
||||
var animation = div.getAnimations()[0];
|
||||
var markers = yield observeStyling(5);
|
||||
is(markers.length, 5,
|
||||
'Applying animation style with different duration ' +
|
||||
'should cause restyles on every frame.');
|
||||
yield ensureElementRemoval(div);
|
||||
});
|
||||
|
||||
add_task_if_omta_enabled(
|
||||
function* changing_cascading_result_for_main_thread_animation() {
|
||||
var div = addDiv(null, { style: 'background-color: blue' });
|
||||
var animation = div.animate({ opacity: [0, 1],
|
||||
backgroundColor: ['green', 'red'] },
|
||||
100 * MS_PER_SEC);
|
||||
yield animation.ready;
|
||||
ok(animation.isRunningOnCompositor,
|
||||
'The opacity animation is running on the compositor');
|
||||
// Make the background-color style as !important to cause an update
|
||||
// to the cascade.
|
||||
// Bug 1300982: The background-color animation should be no longer
|
||||
// running on the main thread.
|
||||
div.style.setProperty('background-color', '1', 'important');
|
||||
var markers = yield observeStyling(5);
|
||||
todo_is(markers.length, 0,
|
||||
'Changing cascading result for the property running on the main ' +
|
||||
'thread does not cause synchronization layer of opacity animation ' +
|
||||
'running on the compositor');
|
||||
yield ensureElementRemoval(div);
|
||||
}
|
||||
);
|
||||
|
||||
add_task(function* restyling_for_animation_on_orphaned_element() {
|
||||
var div = addDiv(null);
|
||||
var animation = div.animate({ marginLeft: [ '0px', '100px' ] },
|
||||
100 * MS_PER_SEC);
|
||||
|
||||
yield animation.ready;
|
||||
|
||||
div.remove();
|
||||
|
||||
var markers = yield observeStyling(5);
|
||||
is(markers.length, 0,
|
||||
'Animation on orphaned element should not cause restyles');
|
||||
|
||||
document.body.appendChild(div);
|
||||
|
||||
markers = yield observeStyling(1);
|
||||
// We are observing restyles in rAF callback which is processed before
|
||||
// restyling process in each frame, so in the first frame there should be
|
||||
// no observed restyle since we don't process restyle while the element
|
||||
// is not attached to the document.
|
||||
is(markers.length, 0,
|
||||
'We observe no restyle in the first frame right after re-atatching ' +
|
||||
'to the document');
|
||||
markers = yield observeStyling(5);
|
||||
is(markers.length, 5,
|
||||
'Animation on re-attached to the document begins to update style');
|
||||
|
||||
yield ensureElementRemoval(div);
|
||||
});
|
||||
|
||||
add_task_if_omta_enabled(
|
||||
// Tests that if we remove an element from the document whose animation
|
||||
// cascade needs recalculating, that it is correctly updated when it is
|
||||
// re-attached to the document.
|
||||
function* restyling_for_opacity_animation_on_re_attached_element() {
|
||||
var div = addDiv(null, { style: 'opacity: 1 ! important' });
|
||||
var animation = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
|
||||
|
||||
yield animation.ready;
|
||||
ok(!animation.isRunningOnCompositor,
|
||||
'The opacity animation overridden by an !important rule is NOT ' +
|
||||
'running on the compositor');
|
||||
|
||||
// Drop the !important rule to update the cascade.
|
||||
div.style.setProperty('opacity', '1', '');
|
||||
|
||||
div.remove();
|
||||
|
||||
var markers = yield observeStyling(5);
|
||||
is(markers.length, 0,
|
||||
'Opacity animation on orphaned element should not cause restyles');
|
||||
|
||||
document.body.appendChild(div);
|
||||
|
||||
// Need a frame to give the animation a chance to be sent to the
|
||||
// compositor.
|
||||
yield waitForFrame();
|
||||
|
||||
ok(animation.isRunningOnCompositor,
|
||||
'The opacity animation which is no longer overridden by the ' +
|
||||
'!important rule begins running on the compositor even if the ' +
|
||||
'!important rule had been dropped before the target element was ' +
|
||||
'removed');
|
||||
|
||||
yield ensureElementRemoval(div);
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
966
dom/animation/test/chrome/test_running_on_compositor.html
Normal file
966
dom/animation/test/chrome/test_running_on_compositor.html
Normal file
|
|
@ -0,0 +1,966 @@
|
|||
<!doctype html>
|
||||
<head>
|
||||
<meta charset=utf-8>
|
||||
<title>Bug 1045994 - Add a chrome-only property to inspect if an animation is
|
||||
running on the compositor or not</title>
|
||||
<script type="application/javascript" src="../testharness.js"></script>
|
||||
<script type="application/javascript" src="../testharnessreport.js"></script>
|
||||
<script type="application/javascript" src="../testcommon.js"></script>
|
||||
<style>
|
||||
@keyframes anim {
|
||||
to { transform: translate(100px) }
|
||||
}
|
||||
@keyframes transform-starts-with-none {
|
||||
0% { transform: none }
|
||||
99% { transform: none }
|
||||
100% { transform: translate(100px) }
|
||||
}
|
||||
@keyframes opacity {
|
||||
to { opacity: 0 }
|
||||
}
|
||||
@keyframes background_and_translate {
|
||||
to { background-color: red; transform: translate(100px); }
|
||||
}
|
||||
@keyframes background {
|
||||
to { background-color: red; }
|
||||
}
|
||||
@keyframes rotate {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
@keyframes rotate-and-opacity {
|
||||
from { transform: rotate(0deg); opacity: 1;}
|
||||
to { transform: rotate(360deg); opacity: 0;}
|
||||
}
|
||||
div {
|
||||
/* Element needs geometry to be eligible for layerization */
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: white;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1045994"
|
||||
target="_blank">Mozilla Bug 1045994</a>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
/** Test for bug 1045994 - Add a chrome-only property to inspect if an
|
||||
animation is running on the compositor or not **/
|
||||
|
||||
var omtaEnabled = isOMTAEnabled();
|
||||
|
||||
function assert_animation_is_running_on_compositor(animation, desc) {
|
||||
assert_equals(animation.isRunningOnCompositor, omtaEnabled,
|
||||
desc + ' at ' + animation.currentTime + 'ms');
|
||||
}
|
||||
|
||||
function assert_animation_is_not_running_on_compositor(animation, desc) {
|
||||
assert_equals(animation.isRunningOnCompositor, false,
|
||||
desc + ' at ' + animation.currentTime + 'ms');
|
||||
}
|
||||
|
||||
promise_test(function(t) {
|
||||
// FIXME: When we implement Element.animate, use that here instead of CSS
|
||||
// so that we remove any dependency on the CSS mapping.
|
||||
var div = addDiv(t, { style: 'animation: anim 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Animation reports that it is running on the compositor'
|
||||
+ ' during playback');
|
||||
|
||||
div.style.animationPlayState = 'paused';
|
||||
|
||||
return animation.ready;
|
||||
}).then(function() {
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Animation reports that it is NOT running on the compositor'
|
||||
+ ' when paused');
|
||||
});
|
||||
}, '');
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t, { style: 'animation: background 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Animation reports that it is NOT running on the compositor'
|
||||
+ ' for animation of "background"');
|
||||
});
|
||||
}, 'isRunningOnCompositor is false for animation of "background"');
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t, { style: 'animation: background_and_translate 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Animation reports that it is running on the compositor'
|
||||
+ ' when the animation has two properties, where one can run'
|
||||
+ ' on the compositor, the other cannot');
|
||||
});
|
||||
}, 'isRunningOnCompositor is true if the animation has at least one ' +
|
||||
'property can run on compositor');
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t, { style: 'animation: anim 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
return animation.ready.then(function() {
|
||||
animation.pause();
|
||||
return animation.ready;
|
||||
}).then(function() {
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Animation reports that it is NOT running on the compositor'
|
||||
+ ' when animation.pause() is called');
|
||||
});
|
||||
}, 'isRunningOnCompositor is false when the animation.pause() is called');
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t, { style: 'animation: anim 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
return animation.ready.then(function() {
|
||||
animation.finish();
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Animation reports that it is NOT running on the compositor'
|
||||
+ ' immediately after animation.finish() is called');
|
||||
// Check that we don't set the flag back again on the next tick.
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Animation reports that it is NOT running on the compositor'
|
||||
+ ' on the next tick after animation.finish() is called');
|
||||
});
|
||||
}, 'isRunningOnCompositor is false when the animation.finish() is called');
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t, { style: 'animation: anim 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
return animation.ready.then(function() {
|
||||
animation.currentTime = 100 * MS_PER_SEC;
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Animation reports that it is NOT running on the compositor'
|
||||
+ ' immediately after manually seeking the animation to the end');
|
||||
// Check that we don't set the flag back again on the next tick.
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Animation reports that it is NOT running on the compositor'
|
||||
+ ' on the next tick after manually seeking the animation to the end');
|
||||
});
|
||||
}, 'isRunningOnCompositor is false when manually seeking the animation to ' +
|
||||
'the end');
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t, { style: 'animation: anim 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
return animation.ready.then(function() {
|
||||
animation.cancel();
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Animation reports that it is NOT running on the compositor'
|
||||
+ ' immediately after animation.cancel() is called');
|
||||
// Check that we don't set the flag back again on the next tick.
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Animation reports that it is NOT running on the compositor'
|
||||
+ ' on the next tick after animation.cancel() is called');
|
||||
});
|
||||
}, 'isRunningOnCompositor is false when animation.cancel() is called');
|
||||
|
||||
// This is to test that we don't simply clobber the flag when ticking
|
||||
// animations and then set it again during painting.
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t, { style: 'animation: anim 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
return animation.ready.then(function() {
|
||||
return new Promise(function(resolve) {
|
||||
window.requestAnimationFrame(function() {
|
||||
t.step(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Animation reports that it is running on the compositor'
|
||||
+ ' in requestAnimationFrame callback');
|
||||
});
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
}, 'isRunningOnCompositor is true in requestAnimationFrame callback');
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t, { style: 'animation: anim 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
return animation.ready.then(function() {
|
||||
return new Promise(function(resolve) {
|
||||
var observer = new MutationObserver(function(records) {
|
||||
var changedAnimation;
|
||||
|
||||
records.forEach(function(record) {
|
||||
changedAnimation =
|
||||
record.changedAnimations.find(function(changedAnim) {
|
||||
return changedAnim == animation;
|
||||
});
|
||||
});
|
||||
|
||||
t.step(function() {
|
||||
assert_true(!!changedAnimation, 'The animation should be recorded '
|
||||
+ 'as one of the changedAnimations');
|
||||
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Animation reports that it is running on the compositor'
|
||||
+ ' in MutationObserver callback');
|
||||
});
|
||||
|
||||
resolve();
|
||||
});
|
||||
observer.observe(div, { animations: true, subtree: false });
|
||||
t.add_cleanup(function() {
|
||||
observer.disconnect();
|
||||
});
|
||||
div.style.animationDuration = "200s";
|
||||
});
|
||||
});
|
||||
}, 'isRunningOnCompositor is true in MutationObserver callback');
|
||||
|
||||
// This is to test that we don't temporarily clear the flag when forcing
|
||||
// an unthrottled sample.
|
||||
promise_test(function(t) {
|
||||
return new Promise(function(resolve) {
|
||||
// Needs scrollbars to cause overflow.
|
||||
SpecialPowers.pushPrefEnv({ set: [["ui.showHideScrollbars", 1]] },
|
||||
resolve);
|
||||
}).then(function() {
|
||||
var div = addDiv(t, { style: 'animation: rotate 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
return animation.ready.then(function() {
|
||||
return new Promise(function(resolve) {
|
||||
var timeAtStart = window.performance.now();
|
||||
function handleFrame() {
|
||||
t.step(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Animation reports that it is running on the compositor'
|
||||
+ ' in requestAnimationFrame callback');
|
||||
});
|
||||
|
||||
// we have to wait at least 200ms because this animation is
|
||||
// unthrottled on every 200ms.
|
||||
// See http://hg.mozilla.org/mozilla-central/file/cafb1c90f794/layout/style/AnimationCommon.cpp#l863
|
||||
if (window.performance.now() - timeAtStart > 200) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
window.requestAnimationFrame(handleFrame);
|
||||
}
|
||||
window.requestAnimationFrame(handleFrame);
|
||||
});
|
||||
});
|
||||
});
|
||||
}, 'isRunningOnCompositor remains true in requestAnimationFrameCallback for ' +
|
||||
'overflow animation');
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t, { style: 'transition: opacity 100s; opacity: 1' });
|
||||
|
||||
getComputedStyle(div).opacity;
|
||||
|
||||
div.style.opacity = 0;
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Transition reports that it is running on the compositor'
|
||||
+ ' during playback for opacity transition');
|
||||
});
|
||||
}, 'isRunningOnCompositor for transitions');
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t, { style: 'animation: rotate-and-opacity 100s; ' +
|
||||
'backface-visibility: hidden; ' +
|
||||
'transform: none !important;' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'If an animation has a property that can run on the compositor and a '
|
||||
+ 'property that cannot (due to Gecko limitations) but where the latter'
|
||||
+ 'property is overridden in the CSS cascade, the animation should '
|
||||
+ 'still report that it is running on the compositor');
|
||||
});
|
||||
}, 'isRunningOnCompositor is true when a property that would otherwise block ' +
|
||||
'running on the compositor is overridden in the CSS cascade');
|
||||
|
||||
promise_test(function(t) {
|
||||
var animation = addDivAndAnimate(t,
|
||||
{},
|
||||
{ opacity: [ 0, 1 ] }, 200 * MS_PER_SEC);
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Animation reports that it is running on the compositor');
|
||||
|
||||
animation.currentTime = 150 * MS_PER_SEC;
|
||||
animation.effect.timing.duration = 100 * MS_PER_SEC;
|
||||
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Animation reports that it is NOT running on the compositor'
|
||||
+ ' when the animation is set a shorter duration than current time');
|
||||
});
|
||||
}, 'animation is immediately removed from compositor' +
|
||||
'when timing.duration is made shorter than the current time');
|
||||
|
||||
promise_test(function(t) {
|
||||
var animation = addDivAndAnimate(t,
|
||||
{},
|
||||
{ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Animation reports that it is running on the compositor');
|
||||
|
||||
animation.currentTime = 500 * MS_PER_SEC;
|
||||
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Animation reports that it is NOT running on the compositor'
|
||||
+ ' when finished');
|
||||
|
||||
animation.effect.timing.duration = 1000 * MS_PER_SEC;
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Animation reports that it is running on the compositor'
|
||||
+ ' when restarted');
|
||||
});
|
||||
}, 'animation is added to compositor' +
|
||||
' when timing.duration is made longer than the current time');
|
||||
|
||||
promise_test(function(t) {
|
||||
var animation = addDivAndAnimate(t,
|
||||
{},
|
||||
{ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Animation reports that it is running on the compositor');
|
||||
|
||||
animation.effect.timing.endDelay = 100 * MS_PER_SEC;
|
||||
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Animation reports that it is running on the compositor'
|
||||
+ ' when endDelay is changed');
|
||||
|
||||
animation.currentTime = 110 * MS_PER_SEC;
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Animation reports that it is NOT running on the compositor'
|
||||
+ ' when currentTime is during endDelay');
|
||||
});
|
||||
}, 'animation is removed from compositor' +
|
||||
' when current time is made longer than the duration even during endDelay');
|
||||
|
||||
promise_test(function(t) {
|
||||
var animation = addDivAndAnimate(t,
|
||||
{},
|
||||
{ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Animation reports that it is running on the compositor');
|
||||
|
||||
animation.effect.timing.endDelay = -200 * MS_PER_SEC;
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Animation reports that it is NOT running on the compositor'
|
||||
+ ' when endTime is negative value');
|
||||
});
|
||||
}, 'animation is removed from compositor' +
|
||||
' when endTime is negative value');
|
||||
|
||||
promise_test(function(t) {
|
||||
var animation = addDivAndAnimate(t,
|
||||
{},
|
||||
{ opacity: [ 0, 1 ] }, 200 * MS_PER_SEC);
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Animation reports that it is running on the compositor');
|
||||
|
||||
animation.effect.timing.endDelay = -100 * MS_PER_SEC;
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Animation reports that it is running on the compositor'
|
||||
+ ' when endTime is positive and endDelay is negative');
|
||||
animation.currentTime = 110 * MS_PER_SEC;
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Animation reports that it is NOT running on the compositor'
|
||||
+ ' when currentTime is after endTime');
|
||||
});
|
||||
}, 'animation is NOT running on compositor' +
|
||||
' when endTime is positive and endDelay is negative');
|
||||
|
||||
promise_test(function(t) {
|
||||
var effect = new KeyframeEffect(null,
|
||||
{ opacity: [ 0, 1 ] },
|
||||
100 * MS_PER_SEC);
|
||||
var animation = new Animation(effect, document.timeline);
|
||||
animation.play();
|
||||
|
||||
var div = addDiv(t);
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Animation with null target reports that it is not running ' +
|
||||
'on the compositor');
|
||||
|
||||
animation.effect.target = div;
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Animation reports that it is running on the compositor ' +
|
||||
'after setting a valid target');
|
||||
});
|
||||
}, 'animation is added to the compositor when setting a valid target');
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t);
|
||||
var animation = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Animation reports that it is running on the compositor');
|
||||
|
||||
animation.effect.target = null;
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Animation reports that it is NOT running on the ' +
|
||||
'compositor after setting null target');
|
||||
});
|
||||
}, 'animation is removed from the compositor when setting null target');
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t);
|
||||
var animation = div.animate({ opacity: [ 0, 1 ] },
|
||||
{ duration: 100 * MS_PER_SEC,
|
||||
delay: 100 * MS_PER_SEC,
|
||||
fill: 'backwards' });
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Animation with fill:backwards in delay phase reports ' +
|
||||
'that it is running on the compositor');
|
||||
|
||||
animation.currentTime = 100 * MS_PER_SEC;
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Animation with fill:backwards in delay phase reports ' +
|
||||
'that it is running on the compositor after delay phase');
|
||||
});
|
||||
}, 'animation with fill:backwards in delay phase is running on the ' +
|
||||
' main-thread while it is in delay phase');
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t);
|
||||
var animation = div.animate([{ opacity: 1, offset: 0 },
|
||||
{ opacity: 1, offset: 0.99 },
|
||||
{ opacity: 0, offset: 1 }], 100 * MS_PER_SEC);
|
||||
|
||||
var another = addDiv(t);
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Opacity animation on a 100% opacity keyframe reports ' +
|
||||
'that it is running on the compositor from the begining');
|
||||
|
||||
animation.effect.target = another;
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Opacity animation on a 100% opacity keyframe keeps ' +
|
||||
'running on the compositor after changing the target ' +
|
||||
'element');
|
||||
});
|
||||
}, '100% opacity animations with keeps running on the ' +
|
||||
'compositor after changing the target element');
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t);
|
||||
var animation = div.animate({ color: ['red', 'black'] }, 100 * MS_PER_SEC);
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Color animation reports that it is not running on the ' +
|
||||
'compositor');
|
||||
|
||||
animation.effect.setKeyframes([{ opacity: 1, offset: 0 },
|
||||
{ opacity: 1, offset: 0.99 },
|
||||
{ opacity: 0, offset: 1 }]);
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'100% opacity animation set by using setKeyframes reports ' +
|
||||
'that it is running on the compositor');
|
||||
});
|
||||
}, '100% opacity animation set up by converting an existing animation with ' +
|
||||
'cannot be run on the compositor, is running on the compositor');
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t);
|
||||
var animation = div.animate({ color: ['red', 'black'] }, 100 * MS_PER_SEC);
|
||||
var effect = new KeyframeEffect(div,
|
||||
[{ opacity: 1, offset: 0 },
|
||||
{ opacity: 1, offset: 0.99 },
|
||||
{ opacity: 0, offset: 1 }],
|
||||
100 * MS_PER_SEC);
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Color animation reports that it is not running on the ' +
|
||||
'compositor');
|
||||
|
||||
animation.effect = effect;
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'100% opacity animation set up by changing effects reports ' +
|
||||
'that it is running on the compositor');
|
||||
});
|
||||
}, '100% opacity animation set up by changing the effects on an existing ' +
|
||||
'animation which cannot be run on the compositor, is running on the ' +
|
||||
'compositor');
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t, { style: "opacity: 1 ! important" });
|
||||
|
||||
var animation = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Opacity animation on an element which has 100% opacity style with ' +
|
||||
'!important flag reports that it is not running on the compositor');
|
||||
// Clear important flag from the opacity style on the target element.
|
||||
div.style.setProperty("opacity", "1", "");
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Opacity animation reports that it is running on the compositor after '
|
||||
+ 'clearing the !important flag');
|
||||
});
|
||||
}, 'Clearing *important* opacity style on the target element sends the ' +
|
||||
'animation to the compositor');
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t);
|
||||
var lowerAnimation = div.animate({ opacity: [1, 0] }, 100 * MS_PER_SEC);
|
||||
var higherAnimation = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
|
||||
|
||||
return Promise.all([lowerAnimation.ready, higherAnimation.ready]).then(function() {
|
||||
assert_animation_is_running_on_compositor(higherAnimation,
|
||||
'A higher-priority opacity animation on an element ' +
|
||||
'reports that it is running on the compositor');
|
||||
assert_animation_is_running_on_compositor(lowerAnimation,
|
||||
'A lower-priority opacity animation on the same ' +
|
||||
'element also reports that it is running on the compositor');
|
||||
});
|
||||
}, 'Opacity animations on the same element run on the compositor');
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t, { style: 'transition: opacity 100s; opacity: 1' });
|
||||
|
||||
getComputedStyle(div).opacity;
|
||||
|
||||
div.style.opacity = 0;
|
||||
getComputedStyle(div).opacity;
|
||||
|
||||
var transition = div.getAnimations()[0];
|
||||
var animation = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
|
||||
|
||||
return Promise.all([transition.ready, animation.ready]).then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'An opacity animation on an element reports that' +
|
||||
'that it is running on the compositor');
|
||||
assert_animation_is_running_on_compositor(transition,
|
||||
'An opacity transition on the same element reports that ' +
|
||||
'it is running on the compositor');
|
||||
});
|
||||
}, 'Both of transition and script animation on the same element run on the ' +
|
||||
'compositor');
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t);
|
||||
var importantOpacityElement = addDiv(t, { style: "opacity: 1 ! important" });
|
||||
|
||||
var animation = div.animate({ opacity: [1, 0] }, 100 * MS_PER_SEC);
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Opacity animation on an element reports ' +
|
||||
'that it is running on the compositor');
|
||||
|
||||
animation.effect.target = null;
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Animation is no longer running on the compositor after ' +
|
||||
'removing from the element');
|
||||
animation.effect.target = importantOpacityElement;
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Animation is NOT running on the compositor even after ' +
|
||||
'being applied to a different element which has an ' +
|
||||
'!important opacity declaration');
|
||||
});
|
||||
}, 'Animation continues not running on the compositor after being ' +
|
||||
'applied to an element which has an important declaration and ' +
|
||||
'having previously been temporarily associated with no target element');
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t);
|
||||
var another = addDiv(t);
|
||||
|
||||
var lowerAnimation = div.animate({ opacity: [1, 0] }, 100 * MS_PER_SEC);
|
||||
var higherAnimation = another.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
|
||||
|
||||
return Promise.all([lowerAnimation.ready, higherAnimation.ready]).then(function() {
|
||||
assert_animation_is_running_on_compositor(lowerAnimation,
|
||||
'An opacity animation on an element reports that ' +
|
||||
'it is running on the compositor');
|
||||
assert_animation_is_running_on_compositor(higherAnimation,
|
||||
'Opacity animation on a different element reports ' +
|
||||
'that it is running on the compositor');
|
||||
|
||||
lowerAnimation.effect.target = null;
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_is_not_running_on_compositor(lowerAnimation,
|
||||
'Animation is no longer running on the compositor after ' +
|
||||
'being removed from the element');
|
||||
lowerAnimation.effect.target = another;
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_is_running_on_compositor(lowerAnimation,
|
||||
'A lower-priority animation begins running ' +
|
||||
'on the compositor after being applied to an element ' +
|
||||
'which has a higher-priority animation');
|
||||
assert_animation_is_running_on_compositor(higherAnimation,
|
||||
'A higher-priority animation continues to run on the ' +
|
||||
'compositor even after a lower-priority animation is ' +
|
||||
'applied to the same element');
|
||||
});
|
||||
}, 'Animation begins running on the compositor after being applied ' +
|
||||
'to an element which has a higher-priority animation and after ' +
|
||||
'being temporarily associated with no target element');
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t);
|
||||
var another = addDiv(t);
|
||||
|
||||
var lowerAnimation = div.animate({ opacity: [1, 0] }, 100 * MS_PER_SEC);
|
||||
var higherAnimation = another.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
|
||||
|
||||
return Promise.all([lowerAnimation.ready, higherAnimation.ready]).then(function() {
|
||||
assert_animation_is_running_on_compositor(lowerAnimation,
|
||||
'An opacity animation on an element reports that ' +
|
||||
'it is running on the compositor');
|
||||
assert_animation_is_running_on_compositor(higherAnimation,
|
||||
'Opacity animation on a different element reports ' +
|
||||
'that it is running on the compositor');
|
||||
|
||||
higherAnimation.effect.target = null;
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_is_not_running_on_compositor(higherAnimation,
|
||||
'Animation is no longer running on the compositor after ' +
|
||||
'being removed from the element');
|
||||
higherAnimation.effect.target = div;
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_is_running_on_compositor(lowerAnimation,
|
||||
'Animation continues running on the compositor after ' +
|
||||
'a higher-priority animation applied to the same element');
|
||||
assert_animation_is_running_on_compositor(higherAnimation,
|
||||
'A higher-priority animation begins to running on the ' +
|
||||
'compositor after being applied to an element which has ' +
|
||||
'a lower-priority-animation');
|
||||
});
|
||||
}, 'Animation begins running on the compositor after being applied ' +
|
||||
'to an element which has a lower-priority animation once after ' +
|
||||
'disassociating with an element');
|
||||
|
||||
var delayPhaseTests = [
|
||||
{
|
||||
desc: 'script animation of opacity',
|
||||
setupAnimation: function(t) {
|
||||
return addDiv(t).animate(
|
||||
{ opacity: [0, 1] },
|
||||
{ delay: 100 * MS_PER_SEC, duration: 100 * MS_PER_SEC });
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: 'script animation of transform',
|
||||
setupAnimation: function(t) {
|
||||
return addDiv(t).animate(
|
||||
{ transform: ['translateX(0px)', 'translateX(100px)'] },
|
||||
{ delay: 100 * MS_PER_SEC, duration: 100 * MS_PER_SEC });
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: 'CSS animation of opacity',
|
||||
setupAnimation: function(t) {
|
||||
return addDiv(t, { style: 'animation: opacity 100s 100s' })
|
||||
.getAnimations()[0];
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: 'CSS animation of transform',
|
||||
setupAnimation: function(t) {
|
||||
return addDiv(t, { style: 'animation: anim 100s 100s' })
|
||||
.getAnimations()[0];
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: 'CSS transition of opacity',
|
||||
setupAnimation: function(t) {
|
||||
var div = addDiv(t, { style: 'transition: opacity 100s 100s' });
|
||||
getComputedStyle(div).opacity;
|
||||
|
||||
div.style.opacity = 0;
|
||||
return div.getAnimations()[0];
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: 'CSS transition of transform',
|
||||
setupAnimation: function(t) {
|
||||
var div = addDiv(t, { style: 'transition: transform 100s 100s' });
|
||||
getComputedStyle(div).transform;
|
||||
|
||||
div.style.transform = 'translateX(100px)';
|
||||
return div.getAnimations()[0];
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
delayPhaseTests.forEach(function(test) {
|
||||
promise_test(function(t) {
|
||||
var animation = test.setupAnimation(t);
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
test.desc + ' reports that it is running on the '
|
||||
+ 'compositor even though it is in the delay phase');
|
||||
});
|
||||
}, 'isRunningOnCompositor for ' + test.desc + ' is true even though ' +
|
||||
'it is in the delay phase');
|
||||
});
|
||||
|
||||
// The purpose of thie test cases is to check that
|
||||
// NS_FRAME_MAY_BE_TRANSFORMED flag on the associated nsIFrame persists
|
||||
// after transform style on the frame is removed.
|
||||
var delayPhaseWithTransformStyleTests = [
|
||||
{
|
||||
desc: 'script animation of transform with transform style',
|
||||
setupAnimation: function(t) {
|
||||
return addDiv(t, { style: 'transform: translateX(10px)' }).animate(
|
||||
{ transform: ['translateX(0px)', 'translateX(100px)'] },
|
||||
{ delay: 100 * MS_PER_SEC, duration: 100 * MS_PER_SEC });
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: 'CSS animation of transform with transform style',
|
||||
setupAnimation: function(t) {
|
||||
return addDiv(t, { style: 'animation: anim 100s 100s;' +
|
||||
'transform: translateX(10px)' })
|
||||
.getAnimations()[0];
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: 'CSS transition of transform with transform style',
|
||||
setupAnimation: function(t) {
|
||||
var div = addDiv(t, { style: 'transition: transform 100s 100s;' +
|
||||
'transform: translateX(10px)'});
|
||||
getComputedStyle(div).transform;
|
||||
|
||||
div.style.transform = 'translateX(100px)';
|
||||
return div.getAnimations()[0];
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
delayPhaseWithTransformStyleTests.forEach(function(test) {
|
||||
promise_test(function(t) {
|
||||
var animation = test.setupAnimation(t);
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
test.desc + ' reports that it is running on the '
|
||||
+ 'compositor even though it is in the delay phase');
|
||||
}).then(function() {
|
||||
// Remove the initial transform style during delay phase.
|
||||
animation.effect.target.style.transform = 'none';
|
||||
return animation.ready;
|
||||
}).then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
test.desc + ' reports that it keeps running on the '
|
||||
+ 'compositor after removing the initial transform style');
|
||||
});
|
||||
}, 'isRunningOnCompositor for ' + test.desc + ' is true after removing ' +
|
||||
'the initial transform style during the delay phase');
|
||||
});
|
||||
|
||||
var startsWithNoneTests = [
|
||||
{
|
||||
desc: 'script animation of transform starts with transform:none segment',
|
||||
setupAnimation: function(t) {
|
||||
return addDiv(t).animate(
|
||||
{ transform: ['none', 'none', 'translateX(100px)'] }, 100 * MS_PER_SEC);
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: 'CSS animation of transform starts with transform:none segment',
|
||||
setupAnimation: function(t) {
|
||||
return addDiv(t,
|
||||
{ style: 'animation: transform-starts-with-none 100s 100s' })
|
||||
.getAnimations()[0];
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
startsWithNoneTests.forEach(function(test) {
|
||||
promise_test(function(t) {
|
||||
var animation = test.setupAnimation(t);
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
test.desc + ' reports that it is running on the '
|
||||
+ 'compositor even though it is in transform:none segment');
|
||||
});
|
||||
}, 'isRunningOnCompositor for ' + test.desc + ' is true even though ' +
|
||||
'it is in transform:none segment');
|
||||
});
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t, { style: 'opacity: 1 ! important' });
|
||||
|
||||
var animation = div.animate(
|
||||
{ opacity: [0, 1] },
|
||||
{ delay: 100 * MS_PER_SEC, duration: 100 * MS_PER_SEC });
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Opacity animation on an element which has opacity:1 important style'
|
||||
+ 'reports that it is not running on the compositor');
|
||||
// Clear the opacity style on the target element.
|
||||
div.style.setProperty("opacity", "1", "");
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Opacity animations reports that it is running on the compositor after '
|
||||
+ 'clearing the opacity style on the element');
|
||||
});
|
||||
}, 'Clearing *important* opacity style on the target element sends the ' +
|
||||
'animation to the compositor even if the animation is in the delay phase');
|
||||
|
||||
promise_test(function(t) {
|
||||
var opaqueDiv = addDiv(t, { style: 'opacity: 1 ! important' });
|
||||
var anotherDiv = addDiv(t);
|
||||
|
||||
var animation = opaqueDiv.animate(
|
||||
{ opacity: [0, 1] },
|
||||
{ delay: 100 * MS_PER_SEC, duration: 100 * MS_PER_SEC });
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Opacity animation on an element which has opacity:1 important style'
|
||||
+ 'reports that it is not running on the compositor');
|
||||
// Changing target element to another element which has no opacity style.
|
||||
animation.effect.target = anotherDiv;
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Opacity animations reports that it is running on the compositor after '
|
||||
+ 'changing the target element to another elemenent having no '
|
||||
+ 'opacity style');
|
||||
});
|
||||
}, 'Changing target element of opacity animation sends the animation to the ' +
|
||||
'the compositor even if the animation is in the delay phase');
|
||||
|
||||
promise_test(function(t) {
|
||||
var animation =
|
||||
addDivAndAnimate(t,
|
||||
{},
|
||||
{ width: ['100px', '200px'] },
|
||||
{ duration: 100 * MS_PER_SEC, delay: 100 * MS_PER_SEC });
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Width animation reports that it is not running on the compositor '
|
||||
+ 'in the delay phase');
|
||||
// Changing to property runnable on the compositor.
|
||||
animation.effect.setKeyframes({ opacity: [0, 1] });
|
||||
return waitForFrame();
|
||||
}).then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Opacity animation reports that it is running on the compositor '
|
||||
+ 'after changing the property from width property in the delay phase');
|
||||
});
|
||||
}, 'Dynamic change to a property runnable on the compositor ' +
|
||||
'in the delay phase');
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t, { style: 'transition: opacity 100s; ' +
|
||||
'opacity: 0 !important' });
|
||||
getComputedStyle(div).opacity;
|
||||
|
||||
div.style.setProperty('opacity', '1', 'important');
|
||||
getComputedStyle(div).opacity;
|
||||
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
return animation.ready.then(function() {
|
||||
assert_animation_is_running_on_compositor(animation,
|
||||
'Transition reports that it is running on the compositor even if the ' +
|
||||
'property is overridden by an !important rule');
|
||||
});
|
||||
}, 'Transitions override important rules');
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t, { style: 'transition: opacity 100s; ' +
|
||||
'opacity: 0 !important' });
|
||||
getComputedStyle(div).opacity;
|
||||
|
||||
div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
|
||||
|
||||
div.style.setProperty('opacity', '1', 'important');
|
||||
getComputedStyle(div).opacity;
|
||||
|
||||
var [transition, animation] = div.getAnimations();
|
||||
|
||||
return Promise.all([transition.ready, animation.ready]).then(function() {
|
||||
assert_animation_is_not_running_on_compositor(transition,
|
||||
'Transition suppressed by an animation which is overridden by an ' +
|
||||
'!important rule reports that it is NOT running on the compositor');
|
||||
assert_animation_is_not_running_on_compositor(animation,
|
||||
'Animation overridden by an !important rule reports that it is ' +
|
||||
'NOT running on the compositor');
|
||||
});
|
||||
}, 'Neither transition nor animation does run on the compositor if the ' +
|
||||
'property is overridden by an !important rule');
|
||||
|
||||
</script>
|
||||
</body>
|
||||
Loading…
Add table
Add a link
Reference in a new issue