Closed Bug 1150081 (css-contain-1) Opened 9 years ago Closed 5 years ago

[META] Implement CSS 'contain' property / CSS Containment Module Level 1

Categories

(Core :: Layout, enhancement)

enhancement
Not set
normal

Tracking

()

RESOLVED FIXED

People

(Reporter: wilsonpage, Unassigned)

References

(Blocks 2 open bugs, )

Details

(Keywords: meta)

The proposed CSS `containment` property allows developers to enforce a 'boundary' between a specific DOM sub-tree and the rest of the document. Much like an iframe, this boundary establishes a new layout-root, ensuring that DOM mutations in the sub-tree never trigger reflows in the parent document.

WIN FOR COMPONENT USERS: I can sandbox untrusted third-party DOM (ads, web-components, widgets, etc) and protect performance of my app.

WIN FOR COMPONENT AUTHORS: If I have to measure or mutate DOM in the scope of my component I can be sure that reflows are tightly scoped, and the parent application with remain fast.

---

Although engines may make similar reflow optimizations under the hood when possible, each engine will have its differences/quirks. Similar to `will-change`; `containment` provides a standard way for applications to indicate to the user-agent that it can optimize certain layout cases (albeit with some potential feature trade-offs [1]).

Discussion: https://twitter.com/wilsonpage/status/582947978694004736

[1] http://dev.w3.org/csswg/css-containment/#strictly-contained
roc: dbaron mentioned you didn't want to pursue this feature. It would be good to get your input. We have specific cases in Gaia that would benefit from `containment`; I also feel it's missing feature of the whole web-components story.
Flags: needinfo?(roc)
One interesting point mentioned in the spec is that is gives the layout-engine the opportunity to skip layout of nodes within a contained sub-tree when they are outside of the viewport. This is because they don't need to be painted and don't actually impact the layout of the outside document.
(In reply to Wilson Page [:wilsonpage] from comment #2)
> One interesting point mentioned in the spec is that is gives the
> layout-engine the opportunity to skip layout of nodes within a contained
> sub-tree when they are outside of the viewport. This is because they don't
> need to be painted and don't actually impact the layout of the outside
> document.

Layout is needed to determine whether or not something is within the viewport.  So I'm not quite sure what you're suggesting.

The main optimizations I would expect from containment would be to have the content outside the boundary not influencing the cost of layout changes inside the boundary, and possibly vice-versa.  We could also implement similar optimizations in some cases without the containment property, although in a somewhat more restrictive way (i.e., try making the optimization, and then determine whether it was valid while doing it, and if it wasn't valid, do more work, which would then total slightly more than what we would have done without trying the optimization first).

I think there might also be some painting-related optimizations we could make, though I can't think of them right now.
(In reply to David Baron [:dbaron] ⏰UTC-7 from comment #3)
> Layout is needed to determine whether or not something is within the
> viewport.  So I'm not quite sure what you're suggesting.

I understand layout would be required to determine the size/position of the node acting as the container, but if this container was outside of the viewport, the engine wouldn't have to perform layout on any nodes *within* its sub-tree.
Ah, ok, when the contained subtree is outside of the viewport.

If we implement this, we'd need a separate bug for each optimization that we'd be expected to make; the list isn't obvious (nor is it in the spec, though it probably should be).
(In reply to David Baron [:dbaron] ⏰UTC-7 from comment #5)
> Ah, ok, when the contained subtree is outside of the viewport.
> 
> If we implement this, we'd need a separate bug for each optimization that
> we'd be expected to make; the list isn't obvious (nor is it in the spec,
> though it probably should be).

I'm not qualified to do this. Who can take this on?
Wilson, presumably Gaia has a list of optimizations they expect? If you could collect that list and post it here, that would be a good first step.
(In reply to Anne (:annevk) from comment #7)
> Wilson, presumably Gaia has a list of optimizations they expect? If you
> could collect that list and post it here, that would be a good first step.

Off the top of my head:

- <gaia-header> [1] needs to measure and mutate elements within its scope on `attachedCallback`. With containment this wouldn't trigger forced-sync-layout on the entire app, only the small scope of the component.

- v3 is looking at playing with clearly separated view-panels. Different developers will be working on different panels. If the layout of each panel is 'contained' then we know that costly layout within one panel won't regress the render performance of another. In other words bad code is contained.

- Infinite scrolling lists (or 'virtual lists') involve the recycling of a small number of DOM nodes, replacing their content and using transforms to create the illusion of millions of list items. If each list item was a container, we could be sure that we could reach 60fps as mutations wouldn't trigger costly reflow anywhere else in the document.

- I'll add more if/when I think of them.

[1] http://github.com/gaia-components/gaia-header
(In reply to Wilson Page [:wilsonpage] from comment #8)
> - v3 is looking at playing with clearly separated view-panels. Different
> developers will be working on different panels. If the layout of each panel
> is 'contained' then we know that costly layout within one panel won't
> regress the render performance of another. In other words bad code is
> contained.

If I understand this correctly, the "new Gaia architecture proposal" (not v3) try to address the very same problem stated here with iframe-level separation. We don't really need iframe for layout isolation if this is implemented (we do need it to constrain memory etc but that's unrelated.) 

To rephrase, iframes allow us manually contain layout of an abstract UI widget, but I highly doubt we could do so with all UI widget unless we employ some crazy build-time transformation.

Needinfo Vivien to make sure.
Flags: needinfo?(21)
OS: Mac OS X → All
Hardware: x86 → All
Summary: Implement CSS `containment` property → Implement CSS `contain` property
(In reply to Tim Guan-tin Chien [:timdream] (slow response; please ni? to queue) from comment #9)
> (In reply to Wilson Page [:wilsonpage] from comment #8)
> > - v3 is looking at playing with clearly separated view-panels. Different
> > developers will be working on different panels. If the layout of each panel
> > is 'contained' then we know that costly layout within one panel won't
> > regress the render performance of another. In other words bad code is
> > contained.
> 
> If I understand this correctly, the "new Gaia architecture proposal" (not
> v3) try to address the very same problem stated here with iframe-level
> separation. We don't really need iframe for layout isolation if this is
> implemented (we do need it to constrain memory etc but that's unrelated.) 
> 

Iframes address that and a few other things. So we still need them atm for Views. 

> To rephrase, iframes allow us manually contain layout of an abstract UI
> widget, but I highly doubt we could do so with all UI widget unless we
> employ some crazy build-time transformation.
> 

You're correct. It will be overkill to create one iframe per widget. While there is rarely more than 3 panels opened at the same time in an app (at least in a Gaia app), each view can contains several widgets.
Flags: needinfo?(21)
Summary: Implement CSS `contain` property → Implement CSS 'contain' property
(In reply to Wilson Page [:wilsonpage] from comment #1)
> roc: dbaron mentioned you didn't want to pursue this feature. It would be
> good to get your input. We have specific cases in Gaia that would benefit
> from `containment`; I also feel it's missing feature of the whole
> web-components story.

IIRC most of the discussion I had with Tab about 'contain' was about using it to implement scalable 'virtual lists'. 'contain' turns out not to be useful for that --- creating DOM nodes for each element of the list is not scalable, and if you avoid creating DOM nodes for hidden list items, you don't need 'contain'.

For your use-cases here, 'contain' seems useful.

The situation is a little different from "will-change"; "will-change" lets authors convey information that the engine clearly cannot know by itself. With "contain", that's not yet so clear.

(In reply to David Baron [:dbaron] ⏰UTC-7 from comment #5)
> If we implement this, we'd need a separate bug for each optimization that
> we'd be expected to make; the list isn't obvious (nor is it in the spec,
> though it probably should be).

I think the most general way to look at it is that frame, restyle and reflow flushes may be able to stop at 'contain' boundaries. I.e. 'contain' helps us partition the DOM, so that each frame construction, restyle or reflow flush can be restricted to a subset of those partitions.

In particular
a) When rendering, partitions outside the displayport (or otherwise totally hidden, e.g. opacity:0) do not need frame, restyle or reflow flushes.
b) CSSOM queries can be directed to a particular partition (or set of partitions) and other partitions do not need to be flushed.

To really take advantage of this, I think we need to give FlushPendingNotifications some kind of scope parameter(s).
Flags: needinfo?(roc)
I think the major optimizations would be something like:

 (1) treat elements with 'contain' as reflow roots, so that dynamic changes inside them don't require reflow to propagate from outside of them.  (This is a trivial extension of bug 1159042.)

 (2) when we need up-to-date layout information for APIs (e.g., element.offsetWidth), only flush layout in the containment partition with the information we need

 (3) when we need to paint, only flush layout in the containment partitions that are visible on screen


I'm a little less sure about what we can apply to style flushes.
(FWIW, while the spec currently just has "contain: none | strict", Tab posted a proposal on www-style to make the value a bit more complex than this, to provide more fine-grained control over what is being contained: https://lists.w3.org/Archives/Public/www-style/2015Apr/0364.html )
And the first draft is now in the spec: http://dev.w3.org/csswg/css-containment/
Assignee: nobody → kzentner
Depends on: 1170173
Depends on: css-contain-paint
Depends on: 1172087
Depends on: 1178895
I've added a note to https://developer.mozilla.org/en-US/Firefox/Experimental_features#CSS and https://developer.mozilla.org/en-US/docs/Web/CSS/contain about the implementation status.

As Kyle was inactive for more than a year, I've unassigned him from this bug.

Sebastian
Assignee: zentner.kyle → nobody
Alias: css-contain-1
Please implement CSS containment, it's critical for layout performance! It's the main thing stopping us porting our PWA to Firefox: https://www.scirra.com/blog/ashley/35/layout-is-the-next-frontier-of-web-app-performance
Blocks: 1374321
Keywords: meta
Summary: Implement CSS 'contain' property → [META] Implement CSS 'contain' property / CSS Containment Module Level 1
Depends on: 1463589
Depends on: 1463599
Depends on: 1465250
Depends on: 1465936
Depends on: 1471758
Depends on: 1476462
Depends on: 1487493
Depends on: 1495470
Depends on: 1530896
Type: defect → enhancement
Depends on: 1555757
Depends on: 1563061

This feature shipped Firefox 69 (via bug 1487493 comment 11), so let's call this [meta] and close it out.

(yay!)

Status: NEW → RESOLVED
Closed: 5 years ago
Resolution: --- → FIXED
You need to log in before you can comment on or make changes to this bug.