display:contents on ::before/::after pseudo elements

32 views
Skip to first unread message

Rune Lillesveen

unread,
Aug 22, 2017, 5:50:21 AM8/22/17
to style-dev, layou...@chromium.org

Hi all,

(a rather long explanation, but there's a question at the end)

One of the remaining issues in the display:contents feature is support for setting the display:contents on pseudo elements. This is only supported on ::before and ::after and means that the pseudo element will not generate a box, but the generated content for the pseudo element will.

Example:

<style>
div::before {
  display: contents;
  content: "PASS" url(image.png);
}
</style>
<div></div>

which will generate a layout tree looking like this:

      LayoutBlockFlow {DIV} at (0,0) size 784x29
        LayoutTextFragment (anonymous) at (0,9) size 38x19
          text run at (0,9) width 38: "PASS"
        LayoutImage (anonymous) at (37.53,0) size 24x24

The generated content in the current implementation are anonymous and not connected to the pseudo element in any other way than being the layout tree children of the pseudo element's layout box. As long as the pseudo element has a layout box, that works fine. We detach the boxes for generated content as a consequence of detaching the pseudo element's layout box. Also, we propagate style changes from the pseudo element to the generated content boxes in PropagateStyleToAnonymousChildren[1] by starting at the layout box for the pseudo element.

However, in the display:contents case we no longer have a layout box to start from. There is no connection between the generated content layout boxes and the pseudo element which generated them. In particular, anonymous boxes may be siblings in the layout tree but belong to separate pseudo elements. Even the element to which the pseudo element belongs may have display:contents style and the layout box parent may belong to an element far up the DOM tree. A simple example:

<style>
div::before {
  display: contents;
  content: "To which " url(debug/green-24x24.png) "pseudo element";
}
div::before {
  display: contents;
  content: " does which text " "layout object " "belong.";
}
</style>
<div></div>

which looks like this:

      LayoutBlockFlow {DIV} at (0,0) size 784x29
        LayoutTextFragment (anonymous) at (0,9) size 64x19
          text run at (0,9) width 64: "To which "
        LayoutImage (anonymous) at (63.88,0) size 24x24
        LayoutTextFragment (anonymous) at (87,9) size 99x19
          text run at (87,9) width 99: "pseudo element"
        LayoutTextFragment (anonymous) at (185,9) size 240x19
          text run at (185,9) width 240: " does which text layout object belong."

I tried to do a quick test implementation which stored the first and last generated layout object on the ::before and ::after pseudo elements, but that broke down for ::first-letter because for ::first-letter the text layout objects are first built as if there was no ::first-letter and afterwards comes the ::first-letter code to detach the original layout object and replaces it with one box for the first letter and one for the remaining text (causing dangling pointers to layout boxes in my simple implementation).


My current thought is to actually generate Node objects for the generated content and store first/last DOM nodes on ::before and ::after PseudoElement objects and let them point to the layout objects and vice versa. Any of you style/layout people who have any other ideas?

As a side-note, I think it might have been better for the ::first-letter code to actually build the proper first-letter layout structure on the first pass, but that's really a separate thing.


--
Rune Lillesveen

Emilio Cobos Álvarez

unread,
Aug 22, 2017, 6:44:40 AM8/22/17
to styl...@chromium.org
Hi Rune,

On 08/22/2017 11:50 AM, Rune Lillesveen wrote:
> I tried to do a quick test implementation which stored the first and
> last generated layout object on the ::before and ::after pseudo
> elements, but that broke down for ::first-letter because for
> ::first-letter the text layout objects are first built as if there was
> no ::first-letter and afterwards comes the ::first-letter code to detach
> the original layout object and replaces it with one box for the first
> letter and one for the remaining text (causing dangling pointers to
> layout boxes in my simple implementation).

I also got bit by exactly the same problem, fwiw, when I was trying to
implement that.

> My current thought is to actually generate Node objects for the
> generated content and store first/last DOM nodes on ::before and ::after
> PseudoElement objects and let them point to the layout objects and vice
> versa. Any of you style/layout people who have any other ideas?

I had a WIP implementation for this (I think you took a look at it
actually) that basically allowed an anonymous layout object to have a
reference to a PseudoElement instead of a Document in the `m_node`
field, so that you want to restyle the pseudo, or it gets detached,
you'd just walk the parent layout tree if the pseudo had display:
contents, looking for the "owned" layout objects (the anonymous objects
that have a pseudo-element pointer pointing back to us).

It became kind of gross with first-letter (you need to "propagate" the
`m_node` field properly), but it seemed to work, if you don't mind the
potential extra layout-object walkup in the display: contents case.

Not sure if you've considered it and discarded it already. It doesn't
seem at a glance more complex than creating anonymous nodes (though I
could be wrong).

> As a side-note, I think it might have been better for the ::first-letter
> code to actually build the proper first-letter layout structure on the
> first pass, but that's really a separate thing.

Indeed, I wonder if LayoutNG changes any of that?

-- Emilio

Rune Lillesveen

unread,
Aug 22, 2017, 7:36:38 AM8/22/17
to Emilio Cobos Álvarez, style-dev, layou...@chromium.org
On Tue, Aug 22, 2017 at 12:43 PM, Emilio Cobos Álvarez <eco...@igalia.com> wrote:
Hi Rune,

On 08/22/2017 11:50 AM, Rune Lillesveen wrote:
> I tried to do a quick test implementation which stored the first and
> last generated layout object on the ::before and ::after pseudo
> elements, but that broke down for ::first-letter because for
> ::first-letter the text layout objects are first built as if there was
> no ::first-letter and afterwards comes the ::first-letter code to detach
> the original layout object and replaces it with one box for the first
> letter and one for the remaining text (causing dangling pointers to
> layout boxes in my simple implementation).

I also got bit by exactly the same problem, fwiw, when I was trying to
implement that.

Ah. I didn't remember.

> My current thought is to actually generate Node objects for the
> generated content and store first/last DOM nodes on ::before and ::after
> PseudoElement objects and let them point to the layout objects and vice
> versa. Any of you style/layout people who have any other ideas?

I had a WIP implementation for this (I think you took a look at it
actually) that basically allowed an anonymous layout object to have a
reference to a PseudoElement instead of a Document in the `m_node`
field, so that you want to restyle the pseudo, or it gets detached,
you'd just walk the parent layout tree if the pseudo had display:
contents, looking for the "owned" layout objects (the anonymous objects
that have a pseudo-element pointer pointing back to us).

Would the WIP in question be https://codereview.chromium.org/2727853002/ ?

It became kind of gross with first-letter (you need to "propagate" the
`m_node` field properly), but it seemed to work, if you don't mind the
potential extra layout-object walkup in the display: contents case.

Not sure if you've considered it and discarded it already. It doesn't
seem at a glance more complex than creating anonymous nodes (though I
could be wrong).

I'd hoped that creating "anonymous" nodes would require less magic, but I think I'll have to try to implement it to figure it out.

> As a side-note, I think it might have been better for the ::first-letter
> code to actually build the proper first-letter layout structure on the
> first pass, but that's really a separate thing.

Indeed, I wonder if LayoutNG changes any of that?

I don't know.
 
-- 
Rune Lillesveen

Emilio Cobos Álvarez

unread,
Aug 22, 2017, 8:11:17 AM8/22/17
to Rune Lillesveen, style-dev, layou...@chromium.org


On 08/22/2017 01:36 PM, Rune Lillesveen wrote:
> Would the WIP in question be https://codereview.chromium.org/2727853002/ ?

Right :)

> It became kind of gross with first-letter (you need to "propagate" the
> `m_node` field properly), but it seemed to work, if you don't mind the
> potential extra layout-object walkup in the display: contents case.
>
> Not sure if you've considered it and discarded it already. It doesn't
> seem at a glance more complex than creating anonymous nodes (though I
> could be wrong).
>
>
> I'd hoped that creating "anonymous" nodes would require less magic, but
> I think I'll have to try to implement it to figure it out.

Yeah, sounds fine to me if it works.

-- Emilio

Rune Lillesveen

unread,
Aug 22, 2017, 8:48:04 AM8/22/17
to Emilio Cobos Álvarez, style-dev, layou...@chromium.org
Btw, I didn't think that the LayoutObjects would be anonymous in that case.

-- 
Rune Lillesveen

Rune Lillesveen

unread,
Nov 16, 2017, 9:22:43 AM11/16/17
to style-dev, layou...@chromium.org, ru...@opera.com, dom-dev
Hi all,

I'm picking up work on display:contents again and re-post the thing below to see if I can get some more feedback. It's about using anonymous DOM nodes for generated content (text and images) in order to support display:contents on pseudo ::before/::after elements.

I'm adding dom-dev@ this time as they may have opinions about it.

Rune Lillesveen

unread,
Nov 16, 2017, 10:13:20 AM11/16/17
to Emilio Cobos Álvarez, layou...@chromium.org, style-dev, dom-dev
Right, I actually started looking at creating anonymous inlines wrapping text nodes which are direct children of display:contents in order to "inherit" computed style properly (which WebKit also does).

One thing that could be good about having anonymous nodes for generated content is that you wouldn't have to re-attach the pseudo element itself when the generated content changes.

On Thu, Nov 16, 2017 at 4:05 PM Emilio Cobos Álvarez <emi...@crisal.io> wrote:
Hi Rune,

In case it's useful, WebKit implemented it creating an anonymous inline
box for the pseudo-element:

  https://bugs.webkit.org/show_bug.cgi?id=178584

I think this is a nice approach because you recreate the content layout
objects anyway when the content property changes, and there are only
inlines inside the generated content if I'm not wrong, so shouldn't be
observable.

 -- Emilio
> --
> You received this message because you are subscribed to the Google
> Groups "layout-dev" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to layout-dev+...@chromium.org
> <mailto:layout-dev+...@chromium.org>.
> To post to this group, send email to layou...@chromium.org
> <mailto:layou...@chromium.org>.
> To view this discussion on the web visit
> https://groups.google.com/a/chromium.org/d/msgid/layout-dev/135e4a2c-7cdd-4142-9704-3f3c542e5849%40chromium.org
> <https://groups.google.com/a/chromium.org/d/msgid/layout-dev/135e4a2c-7cdd-4142-9704-3f3c542e5849%40chromium.org?utm_medium=email&utm_source=footer>.
--
Rune Lillesveen

Hayato Ito

unread,
Nov 17, 2017, 1:56:34 AM11/17/17
to Rune Lillesveen, Emilio Cobos Álvarez, layou...@chromium.org, style-dev, dom-dev
Hmm. I think we don't want to add a node for a pseudo element in DOM trees.
Usually, we object to that. That is the first exceptional case, isn't it?

>     <style>
>     div::before {
>       display: contents;
>       content: "PASS" url(image.png);
>     }

I am wondering what is the use case of "display: contents" for ::before / ::after ?
If there is no strong use case for that, can we disable "display: contents" for pseudo elements all together, spec-wise?

Hayato Ito

unread,
Nov 17, 2017, 2:09:06 AM11/17/17
to Rune Lillesveen, Emilio Cobos Álvarez, layou...@chromium.org, style-dev, dom-dev
Ah, your proposal is adding a node object as a child of pseudo elements.
Then, please ignore my previous comment.

I don't think dom-team has the best person to answer about pseudo elements.

My current assumption is that PseudoElements objects already join a DOM tree, and we have a lot of special treatments for PseudoElements, so that it is not exposed to JavaScript.

Is my understanding correct?

Rune Lillesveen

unread,
Nov 17, 2017, 3:30:44 AM11/17/17
to Hayato Ito, Emilio Cobos Álvarez, layou...@chromium.org, style-dev, dom-dev
On Fri, Nov 17, 2017 at 8:09 AM Hayato Ito <hay...@chromium.org> wrote:
On Fri, Nov 17, 2017 at 3:56 PM Hayato Ito <hay...@chromium.org> wrote:
On Fri, Nov 17, 2017 at 12:13 AM Rune Lillesveen <fut...@chromium.org> wrote:
Right, I actually started looking at creating anonymous inlines wrapping text nodes which are direct children of display:contents in order to "inherit" computed style properly (which WebKit also does).

One thing that could be good about having anonymous nodes for generated content is that you wouldn't have to re-attach the pseudo element itself when the generated content changes.

On Thu, Nov 16, 2017 at 4:05 PM Emilio Cobos Álvarez <emi...@crisal.io> wrote:
Hi Rune,

In case it's useful, WebKit implemented it creating an anonymous inline
box for the pseudo-element: 

  https://bugs.webkit.org/show_bug.cgi?id=178584

I think this is a nice approach because you recreate the content layout
objects anyway when the content property changes, and there are only
inlines inside the generated content if I'm not wrong, so shouldn't be
observable.
 

>     My current thought is to actually generate Node objects for the
>     generated content and store first/last DOM nodes on ::before and
>     ::after PseudoElement objects and let them point to the layout
>     objects and vice versa. Any of you style/layout people who have any
>     other ideas?
>

Hmm. I think we don't want to add a node for a pseudo element in DOM trees.
Usually, we object to that. That is the first exceptional case, isn't it?


Ah, your proposal is adding a node object as a child of pseudo elements.
Then, please ignore my previous comment.

Yes, that's the idea.
 
I don't think dom-team has the best person to answer about pseudo elements.

My current assumption is that PseudoElements objects already join a DOM tree, and we have a lot of special treatments for PseudoElements, so that it is not exposed to JavaScript. 

Is my understanding correct?

Yes, the bad thing is we need special treatment for insert/remove to avoid things like mutation events.

>     <style>
>     div::before {
>       display: contents;
>       content: "PASS" url(image.png);
>     }

I am wondering what is the use case of "display: contents" for ::before / ::after ?
If there is no strong use case for that, can we disable "display: contents" for pseudo elements all together, spec-wise?

It was discussed and resolved here: https://github.com/w3c/csswg-drafts/issues/1345

If what Emilio says WebKit's done by wrapping an anonymous inline around the generated content is non-observable, that's probably a simpler path to support this.

--
Rune Lillesveen

Emilio Cobos Álvarez

unread,
Nov 20, 2017, 9:41:43 PM11/20/17
to Rune Lillesveen, Hayato Ito, layou...@chromium.org, style-dev, dom-dev
Beware that little after I said that, I thought a bit more about it and
realized that it was indeed observable, filed:

https://bugs.webkit.org/show_bug.cgi?id=179812

I think getComputedStyle is the only place that needs special treatment,
because pseudos are not exposed anywhere else in the DOM I know about,
so it may still not be too bad...

-- Emilio

Emilio Cobos Álvarez

unread,
Nov 20, 2017, 9:41:43 PM11/20/17
to Rune Lillesveen, Hayato Ito, layou...@chromium.org, style-dev, dom-dev
Beware that little after I said that, I thought a bit more about it and
realized that it was indeed observable, filed:

https://bugs.webkit.org/show_bug.cgi?id=179812

I think getComputedStyle is the only place that needs special treatment,
because pseudos are not exposed anywhere else in the DOM I know about,
so it may still not be too bad...

-- Emilio

> --
> Rune Lillesveen
>
> --
> You received this message because you are subscribed to the Google
> Groups "style-dev" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to style-dev+...@chromium.org
> <mailto:style-dev+...@chromium.org>.
> To post to this group, send email to styl...@chromium.org
> <mailto:styl...@chromium.org>.
> To view this discussion on the web, visit
> https://groups.google.com/a/chromium.org/d/msgid/style-dev/CACuPfeQ5a5_R6O3OPdPpD6n6ka1ZCGYPSPHBqDKEtVfCJidzhQ%40mail.gmail.com
> <https://groups.google.com/a/chromium.org/d/msgid/style-dev/CACuPfeQ5a5_R6O3OPdPpD6n6ka1ZCGYPSPHBqDKEtVfCJidzhQ%40mail.gmail.com?utm_medium=email&utm_source=footer>.

Rune Lillesveen

unread,
Nov 21, 2017, 8:48:23 AM11/21/17
to Emilio Cobos Álvarez, Hayato Ito, layou...@chromium.org, style-dev, dom-dev
Thanks for the pointers! Did something similar to WebKit in https://chromium-review.googlesource.com/c/chromium/src/+/781863

--
Rune Lillesveen
Reply all
Reply to author
Forward
0 new messages