whatwg / html

HTML Standard
https://html.spec.whatwg.org/multipage/
Other
8.11k stars 2.67k forks source link

Proposal: Add Element.isRendered() function #7379

Open vmpstr opened 2 years ago

vmpstr commented 2 years ago

Hello,

We have a couple of ways of making content not rendered. Specifically, display: none and content-visibility: hidden are two things that will prevent rendering during the typical lifecycle updates.

When the developer wants to get something like getBoundingClientRect or getComputedStyle from an element in a subtree affected by one of those properties, the behavior is different. display: none subtrees won't do much work and return result quickly (although I'm not entirely sure for getComputedStyle -- does it recompute the style even if it's in display: none subtree?)

For content-visibility: hidden, however, the intended behavior and what does happen is that we update enough of the subtree to answer the developer query. In practice, for something like getBoundingClientRect, it means that we ignore the optimization that content-visiblity: hidden property provides and update the style and layout of the subtree in order to return an up-to-date result. This is intended to support cases of speculative layout -- the developer may want to know how much space something takes up without showing that something and without keeping it always up to date (as would be the case with visibility: hidden).

The problem is that if the developer is using content-visibility: hidden to hide content (like a different tab of the site), any inadvertent access in that subtree for any number of layout properties (such as offsetWidth, etc) will cause the update to happen.

This poses a problem particularly for sites that want to move from display: none to content-visibility: hidden to reap some performance gains when unhiding the content. Specifically, there is a large performance cliff here -- display: none would do almost no work, content-visibility: hidden does a lot of work -- for accesses to some properties.

Moreover, there isn't an obvious way to check whether access to the element would cause this update -- we can't even check getComputedStyle for content-visibility property on the node or its ancestor without risking the update from happening. The only reliable way is to determine the ancestor chain and then check getComputedStyle in the top down way (starting with the highest ancestor first). This isn't obvious at all.

Anyway, all this is to say that I propose adding Element.isRendered which would return false on display: none elements and its subtree as well as it would return false on elements in a content-visibility: hidden elements and possibly content-visibility: auto elements when the element isn't relevant to the user.

That way, the developer can have an option of basically having a library that does

if (e.isRendered())
  return e.getBoundingClientRect();
return someAcceptableDefaultRect;

which ensures no work happens.

Let me know what you think!

Yay295 commented 2 years ago

I suppose isRendered would also be false for an image without a set width or height that hasn't been loaded yet?

vmpstr commented 2 years ago

That's a good question. I don't know. I think it makes sense for it to return false in that case since we haven't "allocated" visual space for the image.

I'm also weary about exposing too much internal optimizations for no good reason. For example, for <iframe>s, I propose that this returns true even if the UA decided to throttle the frame for whatever reason.

I'm also actually a bit on the fence about content-visibility: auto elements -- maybe it should just return true

domenic commented 2 years ago

Seems reasonable to me. Note this would probably end up as part of the CSSOM View spec: https://drafts.csswg.org/cssom-view/#extension-to-the-element-interface

vmpstr commented 2 years ago

I think having the return value of isRendered match https://html.spec.whatwg.org/#being-rendered (as @emilio suggested) is a good idea