PSA: New JS helpers to query layout state without forcing reflow

Kris Maglione kmaglione at mozilla.com
Wed Aug 2 19:11:57 UTC 2017


In bug 1383367, I landed a set of new helpers to make it possible to 
query the layout state (e.g., element size and position, or computed 
style such as color) without forcing an uninterruptible reflow.

Note: Please read on for caveats before jumping into using this.

The one that you probably really care about is promiseLayoutFlushed[1], 
which is in a lot of ways the converse of (and complement to) 
requestAnimationFrame. Its use looks something like this:

    let width = await BrowserUtils.promiseLayoutFlushed(document, "layout", () => {
      return elem.clientWidth;
    });

    requestAnimationFrame(() => {
      otherElem.style.width = `${width - 2*borderWidth}px`;
    });

This will cause the layout width of an element to be queried as soon as 
that's possible without a reflow (which may be immediately, or just 
after some other code queries the layout state, or just after the next 
paint), and then uses that width to update the DOM in the next animation 
frame.

Essentially, as long as your `promiseLayoutFlushed` callback queries the 
layout *but never alters the DOM*, and your requestAnimationFrame 
callbacks alter the DOM *but never query the layout*, all queries and 
updates should happen in groups, and we shouldn't have any 
uninterruptible reflows. In theory, either of these things on their own 
should be enough to guarantee that, but in practice, we have a lot of 
legacy code that doesn't follow those rules, so this is another tool in 
our toolbox to get us close to that ideal.


Important caveats: *Please* be careful how you use this. And especially 
keep in mind that there may need to be a paint before your promise 
resolves and any dependent DOM updates happen. If you're not absolutely 
sure that that won't result in glitchy UI behavior, *please test 
carefully* to make sure that it doesn't.

At the moment, the implementation is fairly crude, and any callbacks 
which need to be deferred happen after the next full reflow, even if a 
full reflow isn't required. And there's no way to force deferred 
callbacks to be handled before the next paint aside from forcing a 
layout flush. It should be possible in the future, though, to schedule 
an interruptible reflow just before the next animation frame in cases 
where we know that's required, and trigger our deferred callbacks before 
paint. If you have a particular need for that, please let me know and I 
may be able to make it a higher priority.



[1]: https://dxr.mozilla.org/mozilla-central/rev/ef9a0f01e4f68214f0ff8f4631783b8a0e075a82/toolkit/modules/BrowserUtils.jsm#702-729


More information about the firefox-dev mailing list