tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

stateful-animator.https.html (8488B)


      1 <!DOCTYPE html>
      2 <title>Basic use of stateful animator</title>
      3 <link rel="help" href="https://drafts.css-houdini.org/css-animationworklet/">
      4 
      5 <script src="/resources/testharness.js"></script>
      6 <script src="/resources/testharnessreport.js"></script>
      7 <script src="/web-animations/testcommon.js"></script>
      8 <script src="common.js"></script>
      9 
     10 <div id="target"></div>
     11 
     12 <script id="stateful_animator_basic" type="text/worklet">
     13  registerAnimator("stateful_animator_basic", class {
     14    constructor(options, state = { test_local_time: 0 }) {
     15      this.test_local_time = state.test_local_time;
     16    }
     17    animate(currentTime, effect) {
     18      effect.localTime = this.test_local_time++;
     19    }
     20    state() {
     21      return {
     22        test_local_time: this.test_local_time
     23      };
     24    }
     25  });
     26 </script>
     27 
     28 <script id="stateless_animator_basic" type="text/worklet">
     29  registerAnimator("stateless_animator_basic", class {
     30    constructor(options, state = { test_local_time: 0 }) {
     31      this.test_local_time = state.test_local_time;
     32    }
     33    animate(currentTime, effect) {
     34      effect.localTime = this.test_local_time++;
     35    }
     36    // Unless a valid state function is provided, the animator is considered
     37    // stateless. e.g. animator with incorrect state function name.
     38    State() {
     39      return {
     40        test_local_time: this.test_local_time
     41      };
     42    }
     43  });
     44 </script>
     45 
     46 <script id="stateless_animator_preserves_effect_local_time" type="text/worklet">
     47  registerAnimator("stateless_animator_preserves_effect_local_time", class {
     48    animate(currentTime, effect) {
     49      // The local time will be carried over to the new global scope.
     50      effect.localTime = effect.localTime ? effect.localTime + 1 : 1;
     51    }
     52  });
     53 </script>
     54 
     55 <script id="stateless_animator_does_not_copy_effect_object" type="text/worklet">
     56  registerAnimator("stateless_animator_does_not_copy_effect_object", class {
     57    animate(currentTime, effect) {
     58      effect.localTime = effect.localTime ? effect.localTime + 1 : 1;
     59      effect.foo = effect.foo ? effect.foo + 1 : 1;
     60      // This condition becomes true once we switch global scope and only preserve local time
     61      // otherwise these values keep increasing in lock step.
     62      if (effect.localTime > effect.foo) {
     63        // This works as long as we switch global scope before 10000 frames.
     64        // which is a safe assumption.
     65        effect.localTime = 10000;
     66      }
     67    }
     68  });
     69 </script>
     70 
     71 <script id="state_function_returns_empty" type="text/worklet">
     72  registerAnimator("state_function_returns_empty", class {
     73    constructor(options, state = { test_local_time: 0 }) {
     74      this.test_local_time = state.test_local_time;
     75    }
     76    animate(currentTime, effect) {
     77      effect.localTime = this.test_local_time++;
     78    }
     79    state() {}
     80  });
     81 </script>
     82 
     83 <script id="state_function_returns_not_serializable" type="text/worklet">
     84  registerAnimator("state_function_returns_not_serializable", class {
     85    constructor(options) {
     86      this.test_local_time = 0;
     87    }
     88    animate(currentTime, effect) {
     89      effect.localTime = this.test_local_time++;
     90    }
     91    state() {
     92      return new Symbol('foo');
     93    }
     94  });
     95 </script>
     96 
     97 <script>
     98  const EXPECTED_FRAMES_TO_A_SCOPE_SWITCH = 15;
     99  async function localTimeDoesNotUpdate(animation) {
    100    // The local time stops increasing after the animator instance being dropped.
    101    // e.g. 0, 1, 2, .., n, n, n, n, .. where n is the frame that the global
    102    // scope switches at.
    103    let last_local_time = animation.effect.getComputedTiming().localTime;
    104    let frame_count = 0;
    105    const FRAMES_WITHOUT_CHANGE = 10;
    106    do {
    107      await new Promise(window.requestAnimationFrame);
    108      let current_local_time = animation.effect.getComputedTiming().localTime;
    109      if (approxEquals(last_local_time, current_local_time))
    110        ++frame_count;
    111      else
    112        frame_count = 0;
    113      last_local_time = current_local_time;
    114    } while (frame_count < FRAMES_WITHOUT_CHANGE);
    115  }
    116 
    117  async function localTimeResetsToZero(animation) {
    118    // The local time is reset upon global scope switching. e.g.
    119    // 0, 1, 2, .., 0, 1, 2, .., 0, 1, 2, .., 0, 1, 2, ...
    120    let reset_count = 0;
    121    const LOCAL_TIME_RESET_CHECK = 3;
    122    do {
    123      await new Promise(window.requestAnimationFrame);
    124      if (approxEquals(0, animation.effect.getComputedTiming().localTime))
    125        ++reset_count;
    126    } while (reset_count < LOCAL_TIME_RESET_CHECK);
    127  }
    128 
    129  promise_test(async t => {
    130    await runInAnimationWorklet(document.getElementById('stateful_animator_basic').textContent);
    131    const target = document.getElementById('target');
    132    const effect = new KeyframeEffect(target, [{ opacity: 0 }], { duration: 1000 });
    133    const animation = new WorkletAnimation('stateful_animator_basic', effect);
    134    animation.play();
    135 
    136    // effect.localTime should be correctly increased upon global scope
    137    // switches for stateful animators.
    138    await waitForAnimationFrameWithCondition(_ => {
    139      return approxEquals(animation.effect.getComputedTiming().localTime,
    140          EXPECTED_FRAMES_TO_A_SCOPE_SWITCH);
    141    });
    142 
    143    animation.cancel();
    144  }, "Stateful animator can use its state to update the animation. Pass if test does not timeout");
    145 
    146  promise_test(async t => {
    147    await runInAnimationWorklet(document.getElementById('stateless_animator_basic').textContent);
    148    const target = document.getElementById('target');
    149    const effect = new KeyframeEffect(target, [{ opacity: 0 }], { duration: 1000 });
    150    const animation = new WorkletAnimation('stateless_animator_basic', effect);
    151    animation.play();
    152 
    153    // The local time should be reset to 0 upon global scope switching for
    154    // stateless animators.
    155    await localTimeResetsToZero(animation);
    156 
    157    animation.cancel();
    158  }, "Stateless animator gets reecreated with 'undefined' state.");
    159 
    160  promise_test(async t => {
    161    await runInAnimationWorklet(document.getElementById('stateless_animator_preserves_effect_local_time').textContent);
    162    const target = document.getElementById('target');
    163    const effect = new KeyframeEffect(target, [{ opacity: 0 }], { duration: 1000 });
    164    const animation = new WorkletAnimation('stateless_animator_preserves_effect_local_time', effect);
    165    animation.play();
    166 
    167    await waitForAnimationFrameWithCondition(_ => {
    168        return approxEquals(animation.effect.getComputedTiming().localTime,
    169            EXPECTED_FRAMES_TO_A_SCOPE_SWITCH);
    170    });
    171 
    172    animation.cancel();
    173  }, "Stateless animator should preserve the local time of its effect.");
    174 
    175  promise_test(async t => {
    176    await runInAnimationWorklet(document.getElementById('stateless_animator_does_not_copy_effect_object').textContent);
    177    const target = document.getElementById('target');
    178    const effect = new KeyframeEffect(target, [{ opacity: 0 }], { duration: 1000 });
    179    const animation = new WorkletAnimation('stateless_animator_does_not_copy_effect_object', effect);
    180    animation.play();
    181 
    182    await waitForAnimationFrameWithCondition(_ => {
    183        return approxEquals(animation.effect.getComputedTiming().localTime, 10000);
    184    });
    185 
    186    animation.cancel();
    187  }, "Stateless animator should not copy the effect object.");
    188 
    189  promise_test(async t => {
    190    await runInAnimationWorklet(document.getElementById('state_function_returns_empty').textContent);
    191    const target = document.getElementById('target');
    192    const effect = new KeyframeEffect(target, [{ opacity: 0 }], { duration: 1000 });
    193    const animation = new WorkletAnimation('state_function_returns_empty', effect);
    194    animation.play();
    195 
    196    // The local time should be reset to 0 upon global scope switching for
    197    // stateless animators.
    198    await localTimeResetsToZero(animation);
    199 
    200    animation.cancel();
    201  }, "Stateful animator gets recreated with 'undefined' state if state function returns undefined.");
    202 
    203  promise_test(async t => {
    204    await runInAnimationWorklet(document.getElementById('state_function_returns_not_serializable').textContent);
    205    const target = document.getElementById('target');
    206    const effect = new KeyframeEffect(target, [{ opacity: 0 }], { duration: 1000, iteration: Infinity });
    207    const animation = new WorkletAnimation('state_function_returns_not_serializable', effect);
    208    animation.play();
    209 
    210    // The local time of an animation increases until the registered animator
    211    // gets removed.
    212    await localTimeDoesNotUpdate(animation);
    213 
    214    animation.cancel();
    215  }, "Stateful Animator instance gets dropped (does not get migrated) if state function is not serializable.");
    216 </script>