style-change-events.html (7010B)
1 <!doctype html> 2 <meta charset=utf-8> 3 <title>KeyframeEffect interface: style change events</title> 4 <link rel="help" 5 href="https://drafts.csswg.org/web-animations-1/#model-liveness"> 6 <script src="/resources/testharness.js"></script> 7 <script src="/resources/testharnessreport.js"></script> 8 <script src="../../testcommon.js"></script> 9 <body> 10 <div id="log"></div> 11 <script> 12 'use strict'; 13 14 // Test that each property defined in the KeyframeEffect interface does not 15 // produce style change events. 16 // 17 // There are two types of tests: 18 // 19 // MakeInEffectTest 20 // 21 // For properties that are able to cause the KeyframeEffect to start 22 // affecting the CSS 'opacity' property. 23 // 24 // This function takes either: 25 // 26 // (a) A function that makes the passed-in KeyframeEffect affect the 27 // 'opacity' property. 28 // 29 // (b) An object with the following format: 30 // 31 // { 32 // setup: elem => { /* return Animation */ } 33 // test: effect => { /* make |effect| affect 'opacity' */ } 34 // } 35 // 36 // If the latter form is used, the setup function should return an Animation 37 // whose KeyframeEffect does NOT (yet) affect the 'opacity' property (or is 38 // NOT yet in-effect). Otherwise, the transition we use to detect if a style 39 // change event has occurred will never have a chance to be triggered (since 40 // the animated style will clobber both before-change and after-change 41 // style). 42 // 43 // UsePropertyTest 44 // 45 // For properties that cannot cause the KeyframeEffect to start affecting the 46 // CSS 'opacity' property. 47 // 48 // The shape of the parameter to the UsePropertyTest is identical to the 49 // MakeInEffectTest. The only difference is that the function (or 'test' 50 // function of the object format is used) does not need to make the 51 // KeyframeEffect affect the CSS 'opacity' property, but simply needs to 52 // get/set the property under test. 53 54 const MakeInEffectTest = testFuncOrObj => { 55 let test, setup; 56 57 if (typeof testFuncOrObj === 'function') { 58 test = testFuncOrObj; 59 } else { 60 test = testFuncOrObj.test; 61 if (typeof testFuncOrObj.setup === 'function') { 62 setup = testFuncOrObj.setup; 63 } 64 } 65 66 if (!setup) { 67 setup = elem => 68 elem.animate({ color: ['blue', 'green'] }, 100 * MS_PER_SEC); 69 } 70 71 return { test, setup }; 72 }; 73 74 const UsePropertyTest = testFuncOrObj => { 75 const { test, setup } = MakeInEffectTest(testFuncOrObj); 76 77 let coveringAnimation; 78 return { 79 setup: elem => { 80 coveringAnimation = new Animation( 81 new KeyframeEffect(elem, { opacity: [0, 1] }, 100 * MS_PER_SEC) 82 ); 83 84 return setup(elem); 85 }, 86 test: effect => { 87 test(effect); 88 coveringAnimation.play(); 89 }, 90 }; 91 }; 92 93 const tests = { 94 getTiming: UsePropertyTest(effect => effect.getTiming()), 95 getComputedTiming: UsePropertyTest(effect => effect.getComputedTiming()), 96 updateTiming: MakeInEffectTest({ 97 // Initially put the effect in its before phase (with no fill mode)... 98 setup: elem => 99 elem.animate( 100 { opacity: [0.5, 1] }, 101 { 102 duration: 100 * MS_PER_SEC, 103 delay: 100 * MS_PER_SEC, 104 } 105 ), 106 // ... so that when the delay is removed, it begins to affect the opacity. 107 test: effect => { 108 effect.updateTiming({ delay: 0 }); 109 }, 110 }), 111 get target() { 112 let targetElem; 113 return MakeInEffectTest({ 114 setup: (elem, t) => { 115 targetElem = elem; 116 const targetB = createDiv(t); 117 return targetB.animate({ opacity: [0.5, 1] }, 100 * MS_PER_SEC); 118 }, 119 test: effect => { 120 effect.target = targetElem; 121 }, 122 }); 123 }, 124 pseudoElement: MakeInEffectTest({ 125 setup: elem => elem.animate( 126 {opacity: [0.5, 1]}, 127 {duration: 100 * MS_PER_SEC, pseudoElement: '::before'} 128 ), 129 test: effect => { 130 effect.pseudoElement = null; 131 }, 132 }), 133 iterationComposite: UsePropertyTest(effect => { 134 // Get iterationComposite 135 effect.iterationComposite; 136 137 // Set iterationComposite 138 effect.iterationComposite = 'accumulate'; 139 }), 140 composite: UsePropertyTest(effect => { 141 // Get composite 142 effect.composite; 143 144 // Set composite 145 effect.composite = 'add'; 146 }), 147 getKeyframes: UsePropertyTest(effect => effect.getKeyframes()), 148 setKeyframes: MakeInEffectTest(effect => 149 effect.setKeyframes({ opacity: [0.5, 1] }) 150 ), 151 get ['KeyframeEffect constructor']() { 152 let originalElem; 153 let animation; 154 return UsePropertyTest({ 155 setup: elem => { 156 originalElem = elem; 157 // Return a dummy animation so the caller has something to wait on 158 return elem.animate(null); 159 }, 160 test: () => 161 new KeyframeEffect( 162 originalElem, 163 { opacity: [0.5, 1] }, 164 100 * MS_PER_SEC 165 ), 166 }); 167 }, 168 get ['KeyframeEffect copy constructor']() { 169 let effectToClone; 170 return UsePropertyTest({ 171 setup: elem => { 172 effectToClone = new KeyframeEffect( 173 elem, 174 { opacity: [0.5, 1] }, 175 100 * MS_PER_SEC 176 ); 177 // Return a dummy animation so the caller has something to wait on 178 return elem.animate(null); 179 }, 180 test: () => new KeyframeEffect(effectToClone), 181 }); 182 }, 183 }; 184 185 // Check that each enumerable property and the constructors follow the 186 // expected behavior with regards to triggering style change events. 187 const properties = [ 188 ...Object.keys(AnimationEffect.prototype), 189 ...Object.keys(KeyframeEffect.prototype), 190 'KeyframeEffect constructor', 191 'KeyframeEffect copy constructor', 192 ]; 193 194 test(() => { 195 for (const property of Object.keys(tests)) { 196 assert_in_array( 197 property, 198 properties, 199 `Test property '${property}' should be one of the properties on ` + 200 ' KeyframeEffect' 201 ); 202 } 203 }, 'All property keys are recognized'); 204 205 for (const key of properties) { 206 promise_test(async t => { 207 assert_own_property(tests, key, `Should have a test for '${key}' property`); 208 const { setup, test } = tests[key]; 209 210 // Setup target element 211 const div = createDiv(t); 212 let gotTransition = false; 213 div.addEventListener('transitionrun', () => { 214 gotTransition = true; 215 }); 216 217 // Setup animation 218 const animation = setup(div, t); 219 220 // Setup transition start point 221 div.style.transition = 'opacity 100s'; 222 getComputedStyle(div).opacity; 223 224 // Update specified style but don't flush 225 div.style.opacity = '0.5'; 226 227 // Trigger the property 228 test(animation.effect); 229 230 // If the test function produced a style change event it will have triggered 231 // a transition. 232 233 // Wait for the animation to start and then for at least one animation 234 // frame to give the transitionrun event a chance to be dispatched. 235 await animation.ready; 236 await waitForAnimationFrames(1); 237 238 assert_false(gotTransition, 'A transition should NOT have been triggered'); 239 }, `KeyframeEffect.${key} does NOT trigger a style change event`); 240 } 241 </script> 242 </body>