Home > Back-end >  Does requestAnimationFrame() not stop in Firefox when I move away from the tab?
Does requestAnimationFrame() not stop in Firefox when I move away from the tab?

Time:01-23

The doc at https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame says:

requestAnimationFrame() calls are paused in most browsers when running in background tabs

What does "background tabs" mean? When I switch from one tab to another, does the older tab become a become a background tab? If so, then the following example does not behave the way the doc says.

In the example below I am using requestAnimationFrame() callback to log the timestamp at which the callback is called. The callback also plays an audio tone to hear whenever it is called.

function playAudio() {
  const ctx = new AudioContext();
  const osc = ctx.createOscillator();
  const startTime = ctx.currentTime;
  const stopTime = startTime   0.2;
  osc.frequency.value = 440;
  osc.connect(ctx.destination);
  osc.start(startTime);
  osc.stop(stopTime);
}

let lastActionTime = 0;

function action(timestamp) {
  console.log(timestamp);
  window.requestAnimationFrame(action);
  if (timestamp - lastActionTime < 1000) {  // Throttle
    return
  }
  playAudio()
  lastActionTime = timestamp
}

function startAction() {
  window.requestAnimationFrame(action);
}

window.onload = function() {
  const startButton = document.getElementById("start");
  startButton.addEventListener("click", startAction)
}
<button id="start">Start</button>

To run this example, click on the "start" button. An audio tone starts playing every 1 second. Now switch away to another tab. In Firefox, I see that when I move away to another tab the audio tone still keeps playing although with much longer delays. Does this behavior conform to the spec? Shouldn't requestAnimationFrame stop calling my callback completely when I move away from the tab?

CodePudding user response:

Generally the "background tabs" means the tabs whose contents have been hidden. This not necessarily means that switching from one tab to another the older tab will become a background tab, that behavior is true only for tabs in the same browser window. Moreover, even without switching, a tab can become a "background tab" when "minimizing" the browser window.
Indeed requestAnimationFrame() behaves as per doc, the confusion may arise because the doc does not clearly specify that it will be paused multiple times (with subsequent resumes).
Regarding the specs, there is none, as with all the timers built into the web platform, so apart from the behavior specified by whatwg every browser implements it's own features.
Should requestAnimationFrame() stop calling the callback completely when moving away from the tab? Maybe, but since this behavior is not documented by specs nor standards there is no clear answer for this. Anyway, if that is the main requirement, it could be achieved easily with the Page Visibility API, specifically the document.visibilityState, as below:

function playAudio() {
  const ctx = new AudioContext();
  const osc = ctx.createOscillator();
  const startTime = ctx.currentTime;
  const stopTime = startTime   0.2;
  osc.frequency.value = 440;
  osc.connect(ctx.destination);
  osc.start(startTime);
  osc.stop(stopTime);
}

let lastActionTime = 0;

function action(timestamp) {
  console.log(timestamp);
  window.requestAnimationFrame(action);
  if (document.visibilityState === 'hidden')
    return;
  if (timestamp - lastActionTime < 1000) {  // Throttle
    return
  }
  playAudio()
  lastActionTime = timestamp
}

function startAction() {
  window.requestAnimationFrame(action);
}

window.onload = function() {
  const startButton = document.getElementById("start");
  startButton.addEventListener("click", startAction)
}
<button id="start">Start</button>

CodePudding user response:

What happens here is that Firefox doesn't throttle any timers when it has an active AudioContext, as I was told in https://bugzilla.mozilla.org/show_bug.cgi?id=1344524#c26

A running AudioContext will disable timer throttling

So your very test is what causes it to fail. Register the times when the callbacks fired in an Array and you'll see that it's indeed throttled.

Regarding the specs, they do offer some leeway here as this is mostly a performance / battery saving optimization, and that all devices may not need the same. So all they say about when to fire animation frames is

A navigable has no rendering opportunities if its active document is render-blocked; otherwise, rendering opportunities are determined based on hardware constraints such as display refresh rates and other factors such as page performance or whether the document's visibility state is "visible". Rendering opportunities typically occur at regular intervals.

Note: This specification does not mandate any particular model for selecting rendering opportunities. But for example, if the browser is attempting to achieve a 60Hz refresh rate, then rendering opportunities occur at a maximum of every 60th of a second (about 16.7ms). If the browser finds that a navigable is not able to sustain this rate, it might drop to a more sustainable 30 rendering opportunities per second for that navigable, rather than occasionally dropping frames. Similarly, if a navigable is not visible, the user agent might decide to drop that page to a much slower 4 rendering opportunities per second, or even less.

(Where the "Note" section is not authoritative)

  • Related