[Proposal] navigator.scheduling.isFramePending

isFramePending is similar to the proposed isInputPending API. Whereas isInputPending signals that an input event has been received but not yet dispatched, isFramePending signals that the browser intends to update the visual display – including updating animations and running requestAnimationFrame callbacks – at the next opportunity.

The goal is to provide web developers with more complete information about the state of the browser. With this information, javascript code can choose to defer work and yield control back to the browser in time to meet video refresh deadlines. The net effect should be fewer missed video frames (less jank).

Explainer

3 Likes

It feels like the intent here is to allow some potentially long-running JS to yield back to the event loop, allowing the render steps to run.

Given that, would isFramePending be true for pending frames that aren’t dependant on the main thread? Eg, compositor-driven animations & scrolling.

I don’t get it. Wouldn’t the isFramePending API return the estimated duration till next display refresh? Not just a bool? What would that mean exactly?

@szager, did you mean to link the explainer to isFramePending instead of isInputPending?

@briankardell

According to this Twitter reply, seems it was intentional, although I don’t see how they are similar enough to warrant simply linking to the other?

Seems the developer would want to know the duration remaining until the next display refresh? If it was just a bool, what would that even mean? Display refresh is guaranteed to happen, input isn’t. I must be misunderstanding though.

No, isFramePending would not be true for compositor-only animations. Long-running javascript won’t cause those animations to jank, so there’s no need to yield the main thread.

Whoops, fixed; thanks for the catch.

We initially investigated returning the estimated time until the next rendering deadline, as you suggest. Unfortunately, it’s not always possible to determine that number accurately; and an inaccurate time budget is in many ways worse than no time budget at all.

The return value of isFramePending indicates that the browser intends to run the update the rendering steps without delay at the next opportunity (after the currently-running javascript task finishes). Developers would presumably use it like this:

button.onclick = evt => {
  doSomeWork();
  if (isFramePending()) {
    // Schedule the rest of the work to run after the rendering update
    setTimeout(doMoreWork);
  } else {
    doMoreWork();
  }
};

Unfortunately, it’s not always possible to determine that number accurately

Anywhere I can read as to why that’s the case?

Not that I know of. The way rendering opportunities are identified varies significantly between browsers and also between operating systems. Some use software timers to match the cadence of the display hardware’s refresh rate, some rely on a signal from the OS.

In general though, it’s possible to extrapolate the time of the next rendering opportunity fairly accurately if the browser has been generating frames steadily. But if the page has been idle and hasn’t produced an animation frame recently, and then the page’s DOM is modified in a way that requires a display update, it’s impossible to predict accurately when the next rendering opportunity will happen.

To me this feels like a scheduling API and even mentions isInputPending as a counterpart. Is there a reason this isn’t in the navigator.scheduling namespace, alongside isInputPending, rather than directly on window?

To know “time to next frame” at any arbitrary point when JS is running requires that the browser be continually running a requestAnimationFrame-like refresh loop, since it has to know the last frame time to estimate the next frame time. That would mean that all idle pages have to be constantly running an update loop, which is a huge power drain.

A concern I have here is that this API exposes things that trigger rendering updates, which I don’t think is currently detectable. Consider:

let hasFrame = window.isFramePending(); // hasFrame is false
foo.style.color = 'red';
let hasFrameNow = window.isFramePending(); // hasFrameNow is true

Maybe that’s OK?

You are quite right, this was my mistake; it is meant to be navigator.scheduling.isFramePending.

That’s a good point; for example, we wouldn’t want this to be a vector for visited-link exploits:

<a id=probe href="http://not-a-real-site.com">foo</a>
<script>
probe.href = "http://www.amazon.com";
if (navigator.scheduling.isFramePending()) {
  // Frame was scheduled to paint the link with the "visited" color
  browser_history_contains_amazon = true;
}
</script>

Another chromium-specific concern comes to mind, which is that isFramePending may enable a process-isolated iframe to detect the fact that it is process-isolated. When an iframe is same-process, and the parent document invalidates style, then an animation frame will be scheduled for the entire in-process frame tree. But for a process-isolated iframe, an invalidation in the parent document will not cause the iframe process to schedule an animation frame.

Yeah, agreed… Depending on different optimizations or what not it may be different between engines. For example if the above happened on a disconnected subtree or on a display: none subtree Gecko has a fast way to skip the whole rendering update. Which means it’s going to be pretty hard to implement interoperably… But I guess the whole navigator.scheduling proposal is sorta like that.