summaryrefslogtreecommitdiffstats
path: root/layout/style/test/test_restyles_in_smil_animation.html
blob: 17c1ebb2ab397789d1e53a2d4041252787da3008 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
<!doctype html>
<head>
<meta charset=utf-8>
<title>Tests restyles in smil animation</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/paint_listener.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
</head>
<body>

<div id="target-div">
  <svg>
    <rect id="svg-rect" width="100%" height="100%" fill="lime"/>
  </svg>
</div>

<script>
"use strict";

// Waits for |frameCount| requestAnimationFrame callbacks.
// Returns the number of frame actually waited.
function waitForAnimationFrames(frameCount) {
  return new Promise(function(resolve, reject) {
    let previousTime = document.timeline.currentTime;
    let framesWaited = 0;
    function handleFrame() {
      // SMIL uses a time resolution of 1ms but our software-based vsync timer
      // sometimes produces ticks with an interval of less than 1ms. In such
      // cases we will skip restyling for SMIL animations since the SMIL time
      // will not change.
      //
      // In the following we attempt to detect such situations and wait an
      // additional frame when we detect it. However, the detection is not
      // completely accurate since it uses the timeline time which is based on
      // the navigation start time whereas the SMIL start time is based on the
      // refresh driver time.
      //
      // To account for this inaccuracy the Promise returned by this method
      // resolves with the number of frames waited with the additional frames.
      // This can be used by the call site to add a suitable tolerance to the
      // number of restylings it expects to happen. For example, if a call site
      // is anticipating each animation frame to cause restyling, then the
      // number of restylings, x, it should expect is frameCount <= x <=
      // framesWaited.
      const difference = document.timeline.currentTime - previousTime;
      framesWaited++;
      if (difference >= 1.0 && --frameCount <= 0) {
        resolve(framesWaited);
        return;
      }

      previousTime = document.timeline.currentTime;
      window.requestAnimationFrame(handleFrame); // wait another frame
    }
    window.requestAnimationFrame(handleFrame);
  });
}

// Returns an object consisting of observed styling count and the number of
// frames actually waited because we detected a possibly overflapping SMIL
// time.
function observeStyling(frameCount) {
  let priorAnimationTriggeredRestyles = SpecialPowers.DOMWindowUtils.animationTriggeredRestyles;

  return new Promise(function(resolve) {
    return waitForAnimationFrames(frameCount).then(framesWaited => {
      const restyleCount = SpecialPowers.DOMWindowUtils.animationTriggeredRestyles - priorAnimationTriggeredRestyles;
      resolve({
        stylingCount: restyleCount,
        framesWaited: framesWaited,
      });
    });
  });
}

function ensureElementRemoval(aElement) {
  return new Promise(function(resolve) {
    aElement.remove();
    waitForAllPaintsFlushed(resolve);
  });
}

function waitForPaintFlushed() {
  return new Promise(function(resolve) {
    waitForAllPaintsFlushed(resolve);
  });
}

SimpleTest.waitForExplicitFinish();

add_task(async function smil_is_in_display_none_subtree() {
  await waitForPaintFlushed();

  var animate =
    document.createElementNS("http://www.w3.org/2000/svg", "animate");
  animate.setAttribute("attributeType", "XML");
  animate.setAttribute("attributeName", "fill");
  animate.setAttribute("values", "red;lime");
  animate.setAttribute("dur", "1s");
  animate.setAttribute("repeatCount", "indefinite");
  document.getElementById("svg-rect").appendChild(animate);

  await waitForAnimationFrames(2);

  let result = await observeStyling(5);
  // FIXME: Bug 866411: SMIL animations sometimes skip restyles when the target
  // element is newly associated with an nsIFrame.
  ok(result.stylingCount >= 4 &&
     result.stylingCount <= result.framesWaited,
     `should restyle in most frames (got ${result.stylingCount} restyles ` +
     `over ${result.framesWaited} frames, expected 4~${result.framesWaited})`);

  var div = document.getElementById("target-div");

  div.style.display = "none";
  getComputedStyle(div).display;
  await waitForPaintFlushed();

  result = await observeStyling(5);
  is(result.stylingCount, 0, "should never restyle if display:none");

  div.style.display = "";
  getComputedStyle(div).display;
  await waitForAnimationFrames(2);

  result = await observeStyling(5);
  // FIXME: Bug 866411: SMIL animations sometimes skip restyles when the target
  // element is newly associated with an nsIFrame.
  ok(result.stylingCount >= 4 &&
     result.stylingCount <= result.framesWaited,
     `should restyle again (got ${result.stylingCount} restyles over ` +
     `${result.framesWaited} frames, expected 4~${result.framesWaited})`);

  await ensureElementRemoval(animate);
});
</script>
</body>