Bug 95117 - Move :before and :after into the DOM
: Move :before and :after into the DOM
Status: RESOLVED FIXED
Product: WebKit
Classification: Unclassified
Component: Layout and Rendering
: 528+ (Nightly build)
: All All
: P2 Normal
Assigned To: Elliott Sprehn
:
Depends on: 98836 103705 104462
Blocks: 100161
  Show dependency treegraph
 
Reported: 2012-08-27 13:12 PDT by Elliott Sprehn
Modified: 2013-01-02 13:19 PST (History)
24 users (show)

See Also:


Attachments
Concept (38.48 KB, image/png)
2012-08-27 13:12 PDT, Elliott Sprehn
no flags Details
WIP (32.61 KB, patch)
2012-09-28 17:04 PDT, Elliott Sprehn
no flags Details | Formatted Diff | Diff
Patch (36.65 KB, patch)
2012-10-01 15:31 PDT, Elliott Sprehn
no flags Details | Formatted Diff | Diff
Patch (36.79 KB, patch)
2012-10-01 18:11 PDT, Elliott Sprehn
no flags Details | Formatted Diff | Diff
Patch (35.23 KB, patch)
2012-10-02 18:48 PDT, Elliott Sprehn
no flags Details | Formatted Diff | Diff
Patch (64.69 KB, patch)
2012-10-04 11:32 PDT, Elliott Sprehn
no flags Details | Formatted Diff | Diff
Patch (74.86 KB, patch)
2012-10-05 18:15 PDT, Elliott Sprehn
no flags Details | Formatted Diff | Diff
Patch (135.14 KB, patch)
2012-10-08 14:05 PDT, Elliott Sprehn
no flags Details | Formatted Diff | Diff
Patch (119.96 KB, patch)
2012-10-09 18:05 PDT, Elliott Sprehn
no flags Details | Formatted Diff | Diff
Patch (120.34 KB, patch)
2012-10-10 18:28 PDT, Elliott Sprehn
no flags Details | Formatted Diff | Diff
Patch for landing (120.35 KB, patch)
2012-10-10 18:47 PDT, Elliott Sprehn
no flags Details | Formatted Diff | Diff
Patch for landing (120.34 KB, patch)
2012-10-10 18:49 PDT, Elliott Sprehn
no flags Details | Formatted Diff | Diff
Patch for landing (120.34 KB, patch)
2012-10-10 18:50 PDT, Elliott Sprehn
no flags Details | Formatted Diff | Diff
Patch for landing (120.34 KB, patch)
2012-10-10 18:51 PDT, Elliott Sprehn
no flags Details | Formatted Diff | Diff
Patch (125.27 KB, patch)
2012-10-15 19:02 PDT, Elliott Sprehn
no flags Details | Formatted Diff | Diff
Patch (129.50 KB, patch)
2012-10-24 18:33 PDT, Elliott Sprehn
no flags Details | Formatted Diff | Diff
Patch (55.04 KB, patch)
2012-11-29 17:12 PST, Elliott Sprehn
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Elliott Sprehn 2012-08-27 13:12:41 PDT
Created attachment 160788 [details]
Concept

We should move the generated content nodes into the DOM. This has the nice side effect of vastly simplifying the inside of the render tree in favor of centralized code for insert/remove from the DOM. From inside the render tree we should then be able to ignore if a node is from :before or an actual <span>.
Comment 1 Elliott Sprehn 2012-08-27 13:14:54 PDT
Note: Trivially this could be implemented by making a subclass of Node that override previousSibling/nextSibling to go through parentNode removing the need for book keeping code to maintain the prev/next ptrs. Unfortunately those aren't virtual methods :(
Comment 2 Elliott Sprehn 2012-09-28 17:04:06 PDT
Created attachment 166332 [details]
WIP

WIP patch that attempts to move :before and :after into the DOM. This reuses the existing renderers for generated content so we can do this in multiple steps. The current issues are infinite loops in RenderCounter, broken CSS inheritance in CSS tables, and a bug with counters and styleRecalc causing crashes. On the plus side this patch fixes tons of layout bugs with generated content which is awesome.
Comment 3 WebKit Review Bot 2012-09-28 17:26:11 PDT
Comment on attachment 166332 [details]
WIP

Attachment 166332 [details] did not pass chromium-ews (chromium-xvfb):
Output: http://queues.webkit.org/results/14089003
Comment 4 Early Warning System Bot 2012-09-28 17:26:43 PDT
Comment on attachment 166332 [details]
WIP

Attachment 166332 [details] did not pass qt-ews (qt):
Output: http://queues.webkit.org/results/14090003
Comment 5 Build Bot 2012-09-28 17:29:20 PDT
Comment on attachment 166332 [details]
WIP

Attachment 166332 [details] did not pass win-ews (win):
Output: http://queues.webkit.org/results/14074049
Comment 6 Early Warning System Bot 2012-09-28 17:30:02 PDT
Comment on attachment 166332 [details]
WIP

Attachment 166332 [details] did not pass qt-wk2-ews (qt):
Output: http://queues.webkit.org/results/14075051
Comment 7 Peter Beverloo (cr-android ews) 2012-09-28 17:36:47 PDT
Comment on attachment 166332 [details]
WIP

Attachment 166332 [details] did not pass cr-android-ews (chromium-android):
Output: http://queues.webkit.org/results/14085020
Comment 8 Elliott Sprehn 2012-09-28 17:38:03 PDT
http://trac.webkit.org/changeset/129423 moved usesBeforeAfterRules() off of Document which is where all the build failures came from.
Comment 9 Build Bot 2012-09-28 17:46:52 PDT
Comment on attachment 166332 [details]
WIP

Attachment 166332 [details] did not pass mac-ews (mac):
Output: http://queues.webkit.org/results/14085021
Comment 10 Gyuyoung Kim 2012-09-28 17:53:43 PDT
Comment on attachment 166332 [details]
WIP

Attachment 166332 [details] did not pass efl-ews (efl):
Output: http://queues.webkit.org/results/14078031
Comment 11 Elliott Sprehn 2012-10-01 15:31:00 PDT
Created attachment 166559 [details]
Patch
Comment 12 Elliott Sprehn 2012-10-01 18:11:01 PDT
Created attachment 166585 [details]
Patch
Comment 13 Eric Seidel 2012-10-02 10:18:29 PDT
Comment on attachment 166585 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=166585&action=review

> Source/WebCore/dom/Element.cpp:1214
> +        beforeContent->setNeedsStyleRecalc();

Is this safe during recalcStyle?  Will this crawl up and tell our parents, and possibyl our previous siblings they need style-recalc again?

> Source/WebCore/dom/Element.cpp:1860
> +PassRefPtr<PseudoElement> Element::beforePseudoElement() const
> +{
> +    return hasRareData() ? elementRareData()->m_generatedBefore : 0;
> +}

Why PassRefPtr?  These functxions are not creating a new object, or expecting the caller to take ownership, are they?

> Source/WebCore/dom/PseudoElement.cpp:79
> +        child->setStyle(renderStyle());

So this assumes that we don't ever modify this style... since otherwise we'd modify our parent's style, or?  Don't we normally just unconditionally inherit styles?  I guess these are really siblings of the element in question instead of children...

> Source/WebCore/dom/PseudoElement.cpp:105
> +        renderer = new (arena) RenderTextFragment(document(), static_cast<const TextContentData*>(content)->text().impl());

You might benifit from toTextContentData helpers which would ASSERT as well as do the cast.

> Source/WebCore/dom/PseudoElement.cpp:112
> +        if (const StyleImage* styleImage = static_cast<const ImageContentData*>(content)->image())
> +            image->setImageResource(RenderImageResourceStyleImage::create(const_cast<StyleImage*>(styleImage)));

Why are these casts safe?

> Source/WebCore/dom/PseudoElement.cpp:121
> +        renderer = new (arena) RenderCounter(document(), *static_cast<const CounterContentData*>(content)->counter());
> +        break;
> +    case CONTENT_QUOTE:
> +        renderer = new (arena) RenderQuote(document(), static_cast<const QuoteContentData*>(content)->quote());

Again to*() helpers which also ASSERT the type might be useful.

> Source/WebCore/rendering/RenderObject.cpp:135
> -        // RenderImageResourceStyleImage requires a style being present on the image but we don't want to
> -        // trigger a style change now as the node is not fully attached. Moving this code to style change
> -        // doesn't make sense as it should be run once at renderer creation.
> -        image->m_style = style;
> +        image->setStyle(style);

Are we now triggering a style change and causing badness?
Comment 14 Dimitri Glazkov (Google) 2012-10-02 10:26:25 PDT
Comment on attachment 166585 [details]
Patch

Why aren't we just using the Shadow DOM plumbing? A lot of this code seems to duplicate it.
Comment 15 Elliott Sprehn 2012-10-02 10:43:30 PDT
Comment on attachment 166585 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=166585&action=review

>> Source/WebCore/dom/Element.cpp:1214
>> +        beforeContent->setNeedsStyleRecalc();
> 
> Is this safe during recalcStyle?  Will this crawl up and tell our parents, and possibyl our previous siblings they need style-recalc again?

I assumed it was safe because just a few lines below we do this on our children:

if ((forceCheckOfNextElementSibling || forceCheckOfAnyElementSibling))
    element->setNeedsStyleRecalc();

I didn't see a difference either way in the tests I ran, but it seems like a recalc of our style should mean a recalc of the pseudo since they're derived from them.

>> Source/WebCore/dom/Element.cpp:1860
>> +}
> 
> Why PassRefPtr?  These functxions are not creating a new object, or expecting the caller to take ownership, are they?

I'll switch to bare ptrs on the return types.

>> Source/WebCore/dom/PseudoElement.cpp:79
>> +        child->setStyle(renderStyle());
> 
> So this assumes that we don't ever modify this style... since otherwise we'd modify our parent's style, or?  Don't we normally just unconditionally inherit styles?  I guess these are really siblings of the element in question instead of children...

This code is just copy and paste from RenderObjectChildList's updateBeforeAfterStyle so it might be wrong. If the PseudoElement had font-size: 50%; and then I inherit for the text here, do I get 25% or do I keep the 50%?

>> Source/WebCore/dom/PseudoElement.cpp:112
>> +            image->setImageResource(RenderImageResourceStyleImage::create(const_cast<StyleImage*>(styleImage)));
> 
> Why are these casts safe?

They're safe because the switch() checks the type, this switch is actually copy and pasted from RenderObjectChildList. They're pretty gross though... I'll put together a separate patch that fixes this while I continue here.

>> Source/WebCore/rendering/RenderObject.cpp:135
>> +        image->setStyle(style);
> 
> Are we now triggering a style change and causing badness?

Ack. This is a bad merge. I'll revert, I have a bunch of merge badness in this file it seems.
Comment 16 Elliott Sprehn 2012-10-02 10:44:11 PDT
(In reply to comment #14)
> (From update of attachment 166585 [details])
> Why aren't we just using the Shadow DOM plumbing? A lot of this code seems to duplicate it.

Which "a lot" are you talking about?
Comment 17 Dimitri Glazkov (Google) 2012-10-02 10:48:12 PDT
(In reply to comment #16)
> (In reply to comment #14)
> > (From update of attachment 166585 [details] [details])
> > Why aren't we just using the Shadow DOM plumbing? A lot of this code seems to duplicate it.
> 
> Which "a lot" are you talking about?

Code in Element, Node, NodeRenderingContext, adding stuff to RareNodeData. Can we see if we can maybe reframe :before/:after in terms of shadow DOM? I would be happy to help.
Comment 18 Elliott Sprehn 2012-10-02 18:48:42 PDT
Created attachment 166797 [details]
Patch

The crash was some kind of heap corruption that comes from generated content in display: run-in; so Ive disabled support for that for now. This wont work quite right with Shadow DOM yet either, but as Dimitri mentioned we might take a different approach then, but Id like to get this in before that since Shadow DOM is all fluxy right now anyway. Either way this patch now passes all the tests except for RunIn ones (and a few where the patch fixed bugs)
Comment 19 Elliott Sprehn 2012-10-04 11:32:04 PDT
Created attachment 167144 [details]
Patch

This now passes all the tests except for ones that this fixes (yay). I don't have the build files updated for non-Chromium ports yet, but if someone wants to review this while I get that and the test output sorted...
Comment 20 Ojan Vafai 2012-10-04 15:37:58 PDT
Comment on attachment 167144 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=167144&action=review

> Source/WebCore/dom/ElementRareData.h:46
> +    void clearGeneratedContent();

This doesn't seem to get called anywhere. Where do you delete generated content nodes when a selector no longer applies? Presumably we have some tests that cover this.
Comment 21 Elliott Sprehn 2012-10-04 20:29:08 PDT
Comment on attachment 167144 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=167144&action=review

>> Source/WebCore/dom/ElementRareData.h:46
>> +    void clearGeneratedContent();
> 
> This doesn't seem to get called anywhere. Where do you delete generated content nodes when a selector no longer applies? Presumably we have some tests that cover this.

Woops, that method name is leftover from a previous iteration of the code. In Element::recalcStyle if we already have a :before or :after we call recalcStyle on it which would detach() the PseudoElement if there's no more content resulting in a PseudoElement without a renderer(). You'll see there's a check there:

afterContent->recalcStyle(change);
if (!afterContent->renderer())
  setAfterPseudoElement(0); // free the after content.

We also remove it in Element::detach.
Comment 22 Elliott Sprehn 2012-10-05 18:15:55 PDT
Created attachment 167431 [details]
Patch
Comment 23 Elliott Sprehn 2012-10-05 18:21:22 PDT
(In reply to comment #22)
> Created an attachment (id=167431) [details]
> Patch

I've identified some places where our tests are lacking or bugs this fixes that we need more tests (ex. animations work on :before and :after now) so I'll work on that, but code is good for review!
Comment 24 Build Bot 2012-10-05 18:50:46 PDT
Comment on attachment 167431 [details]
Patch

Attachment 167431 [details] did not pass win-ews (win):
Output: http://queues.webkit.org/results/14168949
Comment 25 Gyuyoung Kim 2012-10-05 19:02:19 PDT
Comment on attachment 167431 [details]
Patch

Attachment 167431 [details] did not pass efl-ews (efl):
Output: http://queues.webkit.org/results/14171984
Comment 26 Build Bot 2012-10-05 19:19:45 PDT
Comment on attachment 167431 [details]
Patch

Attachment 167431 [details] did not pass mac-ews (mac):
Output: http://queues.webkit.org/results/14179692

New failing tests:
fast/css-generated-content/before-content-continuation-chain.html
fast/css-generated-content/table-row-group-to-inline.html
http/tests/workers/terminate-during-sync-operation.html
Comment 27 WebKit Review Bot 2012-10-05 19:33:58 PDT
Comment on attachment 167431 [details]
Patch

Attachment 167431 [details] did not pass chromium-ews (chromium-xvfb):
Output: http://queues.webkit.org/results/14168957

New failing tests:
fast/css-generated-content/before-content-continuation-chain.html
fast/forms/time-multiple-fields/time-multiple-fields-appearance-pseudo-elements.html
fast/css-generated-content/table-row-group-to-inline.html
Comment 28 WebKit Review Bot 2012-10-05 20:38:18 PDT
Comment on attachment 167431 [details]
Patch

Attachment 167431 [details] did not pass chromium-ews (chromium-xvfb):
Output: http://queues.webkit.org/results/14171997

New failing tests:
fast/css-generated-content/before-content-continuation-chain.html
fast/forms/time-multiple-fields/time-multiple-fields-appearance-pseudo-elements.html
fast/css-generated-content/table-row-group-to-inline.html
Comment 29 Elliott Sprehn 2012-10-08 14:05:37 PDT
Created attachment 167604 [details]
Patch
Comment 30 Elliott Sprehn 2012-10-08 16:21:36 PDT
(In reply to comment #29)
> Created an attachment (id=167604) [details]
> Patch

This passes all tests except a few more than have to do with generated content on inputs:

[4/13] fast/forms/date-multiple-fields/date-multiple-fields-appearance-pseudo-elements.html failed unexpectedly (image diff)
[5/13] fast/forms/month-multiple-fields/month-multiple-fields-appearance-pseudo-elements.html failed unexpectedly (image diff)
[9/13] fast/forms/week-multiple-fields/week-multiple-fields-appearance-pseudo-elements.html failed unexpectedly (image diff)

These tests are bad because they're checking that you can add :before and :after content to the special <input> elements which is wrong. We shouldn't even support generated content on those things yet (and we don't support it for most input types).
Comment 31 WebKit Review Bot 2012-10-08 17:07:48 PDT
Comment on attachment 167604 [details]
Patch

Attachment 167604 [details] did not pass chromium-ews (chromium-xvfb):
Output: http://queues.webkit.org/results/14205824

New failing tests:
fast/forms/month-multiple-fields/month-multiple-fields-appearance-pseudo-elements.html
fast/forms/week-multiple-fields/week-multiple-fields-appearance-pseudo-elements.html
fast/forms/date-multiple-fields/date-multiple-fields-appearance-pseudo-elements.html
Comment 32 Eric Seidel 2012-10-09 11:04:20 PDT
Comment on attachment 167604 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=167604&action=review

Overall this looks good. I need to give it a few more passes before I fully understand it and can comment intelligently.

> Source/WebCore/dom/PseudoElement.cpp:76
> +    if (RenderObject* renderer = this->renderer()) {

Early return probably is cleaner than indenting this whole block.

> Source/WebCore/dom/PseudoElement.cpp:106
> +    if (child->isText()) {
> +        child->setStyle(renderStyle());
> +    } else if (child->isImage()) {

I think single-line ifs don't have { } in webkit.

> LayoutTests/ChangeLog:10
> +        Note: The output of table-row-group-to-inline.html is wrong (but always was) until Bug 86261 is fixed.

Could you explain the changes we see in the image results?
Comment 33 Elliott Sprehn 2012-10-09 12:52:32 PDT
(In reply to comment #32)
> (From update of attachment 167604 [details])
> View in context: https://bugs.webkit.org/attachment.cgi?id=167604&action=review
> ...
> > LayoutTests/ChangeLog:10
> > +        Note: The output of table-row-group-to-inline.html is wrong (but always was) until Bug 86261 is fixed.
> 
> Could you explain the changes we see in the image results?

I'll update the ChangeLog. There's always been a bug where we leave anonymous RenderTable wrappers when removing nodes, that's why you see the output of the test on two lines in the old -expected.png, but now the way generated content gets inserted is the same as DOM nodes so instead of:

RenderText
RenderTable
RenderText

which used to put this on two lines we end up with:

RenderTable
RenderText
RenderText

Which puts it all on one line. It *should* be all on one line, but it shouldn't be pushed down a line. To fix this we need to clean up anonymous table wrappers.
Comment 34 Eric Seidel 2012-10-09 13:53:07 PDT
Comment on attachment 167604 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=167604&action=review

> Source/WebCore/dom/Element.cpp:1253
> +    if (PseudoElement* afterContent = afterPseudoElement()) {
> +        afterContent->setNeedsStyleRecalc();
> +        afterContent->recalcStyle(change);
> +        if (!afterContent->renderer())
> +            elementRareData()->setAfterPseudoElement(0); // free afterContent
> +    } else if (RefPtr<PseudoElement> afterContent = createPseudoElementIfNeeded(AFTER)) {

This block is mostly a copy/paste of the above.  it seems with some smarts we could rip the one non-sharable setAfterPseudoElement line out of these and share the rest in a helper function.
Maybe something liek this?
if (PseudoElement* newAfterContent = updateBeforeAfterContent(afterPseudoElement(), AFTER))
    setAfterPseudoElement(newAfterContent);

> Source/WebCore/dom/Element.cpp:1850
> +    RenderStyle* style = renderer()->getCachedPseudoStyle(pseudoId);
> +    if (style && PseudoElement::pseudoRendererIsNeeded(style))
> +        return PseudoElement::create(this, pseudoId);

nit: I might have continued the pattern of early return, and made this be the negative, return 0, and the final clause of the funtion return the created element.

> Source/WebCore/dom/Element.cpp:1858
> +PseudoId Element::pseudoId() const
> +{
> +    return hasRareData() ? elementRareData()->m_pseudoId : NOPSEUDO;
> +}

I wonder how hot any of these are and if they'll need to be inlined.

> Source/WebCore/dom/Node.cpp:476
> +    if (isElementNode() && !previousSibling()) {

I think previousSibling is cheaper than isElementNode() and should probably be first.

> Source/WebCore/dom/Node.cpp:490
> +    if (isElementNode() && !nextSibling()) {

Same.

> Source/WebCore/dom/Node.cpp:494
> +        if (isBeforePseudoElement() && parent->firstChild())

if firstChild is faster, we might move that first.  I dont' remember it's comparitive speed on Element.  It's slow on RenderObject. :)

> Source/WebCore/dom/NodeRenderingContext.cpp:97
> +    // FIXME: This is wrong when the next sibling was actually moved around by shadow insertion points.

Can you link to a bug, or give hints to proper behavior?

> Source/WebCore/dom/PseudoElement.cpp:62
> +    bool hasContent = style->contentData() && style->contentData()->type() != CONTENT_NONE;
> +    bool hasRegionThread = !style->regionThread().isEmpty();
> +    return hasContent || hasRegionThread;

You might explain why :)

> Source/WebCore/dom/PseudoElement.cpp:89
> +            RenderObject* child = createRendererForContent(content);
> +            if (!child)
> +                continue;
> +            if (renderer->isChildAllowed(child, renderStyle()))
> +                renderer->addChild(child);
> +            else
> +                child->destroy();

unconditional-create/conditional-destroy patterns like this are inefficent.  I believe this is how existing code works, so this is fine... but hopefully the destroy() branch is very uncommon. :)

> Source/WebCore/dom/PseudoElement.cpp:110
> +        // Images must inherit the pseudo so the width/height styles on the element don't affect it.
> +        RefPtr<RenderStyle> style = RenderStyle::create();
> +        style->inheritFrom(renderStyle());
> +        child->setStyle(style.release());

It's unclear to me why images are special.  Is the non-inherit path an optimization?  Does text need to not-inherit?  If so, why?  Do those reasons not apply to images?

> Source/WebCore/dom/PseudoElement.cpp:121
> +    RenderObject* renderer = this->renderer();
> +    for (RenderObject* child = renderer->nextInPreOrder(renderer); child; child = child->nextInPreOrder(renderer))
> +        updateChildStyle(child);

Should probably explain why.

// Because PseudoElements are not part of the DOM tree, they don't participate in style recalc normally, so we have to fake our own for our child elements.

or some such...

> Source/WebCore/dom/PseudoElement.cpp:132
> +    switch (content->type()) {
> +    case CONTENT_NONE:
> +        ASSERT_NOT_REACHED();
> +        return 0;

Is this switch copied from somewhere?  Can we share code?

> Source/WebCore/dom/PseudoElement.cpp:134
> +        renderer = new (arena) RenderTextFragment(document(), static_cast<const TextContentData*>(content)->text().impl());

Too bad we don't have toTextContentData(), those tend to be cleaner/safer than raw static_casts, as they ASSERT and are fewer chars...

> Source/WebCore/dom/PseudoElement.h:58
> +inline PassRefPtr<PseudoElement> PseudoElement::create(Element* parent, PseudoId pseudoId)

I don't think you need to mark things in headers as inline?  I thought that was implicit?

Also, why declare this after the class instead of inline in teh declaration?

> Source/WebCore/rendering/HitTestResult.cpp:265
> +    if (n && n->isPseudoElement())
> +        n = n->parentOrHostNode();

How does shadow avoid this?
Comment 35 Elliott Sprehn 2012-10-09 18:05:39 PDT
Created attachment 167888 [details]
Patch

Changes per review by eseidel
Comment 36 Eric Seidel 2012-10-09 18:11:52 PDT
Comment on attachment 167888 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=167888&action=review

> Source/WebCore/dom/Element.cpp:1020
> +        updatePseudoElement(BEFORE);

Wow, this is so much nicer.
Comment 37 Elliott Sprehn 2012-10-09 18:15:04 PDT
(In reply to comment #34)
> (From update of attachment 167604 [details])
> View in context: https://bugs.webkit.org/attachment.cgi?id=167604&action=review
> 
> 
> > Source/WebCore/dom/Element.cpp:1858
> > +PseudoId Element::pseudoId() const
> > +{
> > +    return hasRareData() ? elementRareData()->m_pseudoId : NOPSEUDO;
> > +}
> 
> I wonder how hot any of these are and if they'll need to be inlined.

Not sure. I originally had a lot of work in this patch for that but most cases don't need that perf difference. hasRareData() is a bitfield check and this method isn't virtual so I'd hope it's reasonably fast.

> 
> > Source/WebCore/dom/Node.cpp:476
> > +    if (isElementNode() && !previousSibling()) {
> 
> I think previousSibling is cheaper than isElementNode() and should probably be first.

isElementNode() is a bitfield check in the DOM so it's fast.

> > Source/WebCore/dom/PseudoElement.cpp:89
> > ...
> > +                child->destroy();
> 
> unconditional-create/conditional-destroy patterns like this are inefficent.  I believe this is how existing code works, so this is fine... but hopefully the destroy() branch is very uncommon. :)

This should be very uncommon.

> 
> > Source/WebCore/dom/PseudoElement.cpp:110
> > +        // Images must inherit the pseudo so the width/height styles on the element don't affect it.
> > +        RefPtr<RenderStyle> style = RenderStyle::create();
> > +        style->inheritFrom(renderStyle());
> > +        child->setStyle(style.release());
> 
> It's unclear to me why images are special.  Is the non-inherit path an optimization?  Does text need to not-inherit?  If so, why?  Do those reasons not apply to images?

I added a comment. Images in generated content should always be intrinsically sized. If we didn't inherit 
 then the width/height on the generated content node itself would change the sizing of the image inside it.


> > Source/WebCore/dom/PseudoElement.cpp:132
> > +    switch (content->type()) {
> > +    case CONTENT_NONE:
> > +        ASSERT_NOT_REACHED();
> > +        return 0;
> 
> Is this switch copied from somewhere?  Can we share code?

Well, it's copied from the old RenderObjectChildList but modified a bit. In a follow up patch I want to move renderer creation to virtual methods on the ContentData classes and get rid of this. There's no reason for this kind of perf optimization here.

> 
> > Source/WebCore/dom/PseudoElement.cpp:134
> > +        renderer = new (arena) RenderTextFragment(document(), static_cast<const TextContentData*>(content)->text().impl());
> 
> Too bad we don't have toTextContentData(), those tend to be cleaner/safer than raw static_casts, as they ASSERT and are fewer chars...

Yeah, after this lands I'll create a patch that does that.

> 
> > Source/WebCore/dom/PseudoElement.h:58
> > +inline PassRefPtr<PseudoElement> PseudoElement::create(Element* parent, PseudoId pseudoId)
> 
> I don't think you need to mark things in headers as inline?  I thought that was implicit?
> 
> Also, why declare this after the class instead of inline in teh declaration?

You must have inline or you'll get duplicate symbol declarations I think. We always put it in there. It should just be in the class though (I fixed it).

> 
> > Source/WebCore/rendering/HitTestResult.cpp:265
> > +    if (n && n->isPseudoElement())
> > +        n = n->parentOrHostNode();
> 
> How does shadow avoid this?

Shadow does some magic for event retargeting, but it's designed to only happen during event dispatch and we need it for all kinds of things like selection or clicking links (ie you should never hit the content). We might be able to fix shadow DOM, but I'd like to wait for that.
Comment 38 Elliott Sprehn 2012-10-09 18:23:58 PDT
Comment on attachment 167888 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=167888&action=review

> Source/WebCore/dom/Element.h:488
> +    void setAfterPseudoElement(PassRefPtr<PseudoElement>);

Woops, these methods don't exist anymore. I'll remove them.
Comment 39 Elliott Sprehn 2012-10-09 18:24:05 PDT
Comment on attachment 167888 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=167888&action=review

> Source/WebCore/dom/Element.h:488
> +    void setAfterPseudoElement(PassRefPtr<PseudoElement>);

Woops, these methods don't exist anymore. I'll remove them.
Comment 40 Eric Seidel 2012-10-10 13:56:00 PDT
Comment on attachment 167888 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=167888&action=review

The only question is the perf. :)

> Source/WebCore/dom/Element.cpp:1820
> +        existing->setNeedsStyleRecalc();
> +        existing->recalcStyle(change);

Why do we need to setNeedsStyleRecalc()?  And do you need to upgrade the change to Detach or Force?  It seems any style change could be a pseudo change, so we do need this setNeedsStyleRecalc or we need to pass Force.  You should document this now that we understand what's going on.

> Source/WebCore/dom/PseudoElement.cpp:107
> +    if (child->isText())
> +        child->setStyle(renderStyle());
> +    else if (child->isImage()) {

Instead add an early return, which is if (!child->isText() || !child->isImage()) with a note, saying that it must not be our rendererer so we don't manage the style.

Then ahve a "optimized" path for text.

And then fallback for images to the normal inherit path.

> Source/WebCore/dom/PseudoElement.cpp:122
> +    // PseudoElements are not part of the DOM tree and don't participte in the recalcStyle flow so we must
> +    // propagate the style downward manually.

note to you: YOu said you wanted to update this.
Comment 41 Elliott Sprehn 2012-10-10 18:28:03 PDT
Created attachment 168113 [details]
Patch

Final review comments
Comment 42 Elliott Sprehn 2012-10-10 18:33:23 PDT
(In reply to comment #40)
> (From update of attachment 167888 [details])
> View in context: https://bugs.webkit.org/attachment.cgi?id=167888&action=review
> 
> The only question is the perf. :)

I changed it so there's a fast path that's inline and non-virtual and a slow path for things with custom callbacks which is very rare (search for setHasCustomCallbacks). With this patch there's no difference for Parser/html5-full-render.
Comment 43 Elliott Sprehn 2012-10-10 18:47:58 PDT
Created attachment 168116 [details]
Patch for landing
Comment 44 Elliott Sprehn 2012-10-10 18:49:11 PDT
Created attachment 168118 [details]
Patch for landing
Comment 45 Elliott Sprehn 2012-10-10 18:50:35 PDT
Created attachment 168119 [details]
Patch for landing
Comment 46 Elliott Sprehn 2012-10-10 18:51:55 PDT
Created attachment 168121 [details]
Patch for landing
Comment 47 Elliott Sprehn 2012-10-10 18:53:25 PDT
(In reply to comment #46)
> Created an attachment (id=168121) [details]
> Patch for landing

Seems webkit patch got confused when I renamed the bug or I lost the r+ bit somehow so I had to manually put in the reviewer in and fix the bug name.

Assuming this goes green on all the bots I just need a cq+. :)
Comment 48 WebKit Review Bot 2012-10-10 20:17:41 PDT
Comment on attachment 168121 [details]
Patch for landing

Clearing flags on attachment: 168121

Committed r131004: <http://trac.webkit.org/changeset/131004>
Comment 49 WebKit Review Bot 2012-10-10 20:17:49 PDT
All reviewed patches have been landed.  Closing bug.
Comment 50 Pavel Feldman 2012-10-11 06:19:29 PDT
I needed to roll this back because it broke inspector (and hence is likely to break the web). Try opening console and typing there - it is not editable.

http://trac.webkit.org/changeset/131050
Comment 51 Elliott Sprehn 2012-10-11 09:44:23 PDT
(In reply to comment #50)
> I needed to roll this back because it broke inspector (and hence is likely to break the web). Try opening console and typing there - it is not editable.
> 
> http://trac.webkit.org/changeset/131050

Interesting. How do I get the web inspector to open for the web inspector? This seems like it's related to editing and generated content, but I can't find any reference to contentEditable in the inspector code.
Comment 52 Andreas Kling 2012-10-11 20:33:21 PDT
Looks like this patch caused a 7.91MB increase in memory usage on the Parser/html5-full-render:Malloc test while it was in the tree. ( http://webkit-perf.appspot.com/graph.html#tests=[[6268923,2001,7288486]]&sel=none&displayrange=7&datatype=running )
Comment 53 Elliott Sprehn 2012-10-11 22:23:17 PDT
(In reply to comment #52)
> Looks like this patch caused a 7.91MB increase in memory usage on the Parser/html5-full-render:Malloc test while it was in the tree. ( http://webkit-perf.appspot.com/graph.html#tests=[[6268923,2001,7288486]]&sel=none&displayrange=7&datatype=running )

Woah that can't be right. Two RefPtrs in rare data shouldn't be causing 8mb of memory usage... how do I profile this?
Comment 54 Elliott Sprehn 2012-10-12 11:00:19 PDT
(In reply to comment #53)
> (In reply to comment #52)
> > Looks like this patch caused a 7.91MB increase in memory usage on the Parser/html5-full-render:Malloc test while it was in the tree. ( http://webkit-perf.appspot.com/graph.html#tests=[[6268923,2001,7288486]]&sel=none&displayrange=7&datatype=running )
> 
> Woah that can't be right. Two RefPtrs in rare data shouldn't be causing 8mb of memory usage... how do I profile this?

I looked at the HTML5 spec http://www.whatwg.org/specs/web-apps/current-work/ and it has 137447 elements and 186374 text noes.

This patch means creating another 4459 Element's for the PseudoElements, or a 1.37% growth in the number of Nodes and a 3.2% growth in the number of Elements. Presumably our memory should be scaling with those factors.

There must be some bug in my code, I can't imagine that Element is 1.8k per instance.
Comment 55 Andreas Kling 2012-10-12 11:02:04 PDT
(In reply to comment #54)
> (In reply to comment #53)
> > (In reply to comment #52)
> > > Looks like this patch caused a 7.91MB increase in memory usage on the Parser/html5-full-render:Malloc test while it was in the tree. ( http://webkit-perf.appspot.com/graph.html#tests=[[6268923,2001,7288486]]&sel=none&displayrange=7&datatype=running )
> > 
> > Woah that can't be right. Two RefPtrs in rare data shouldn't be causing 8mb of memory usage... how do I profile this?
> 
> I looked at the HTML5 spec http://www.whatwg.org/specs/web-apps/current-work/ and it has 137447 elements and 186374 text noes.
> 
> This patch means creating another 4459 Element's for the PseudoElements, or a 1.37% growth in the number of Nodes and a 3.2% growth in the number of Elements. Presumably our memory should be scaling with those factors.
> 
> There must be some bug in my code, I can't imagine that Element is 1.8k per instance.

If you are developing on Mac, I suggest using Instruments.app with the "Allocations" tool and getting a full profile of loading the test file. (And make sure you're using a release build, as debug builds have quite different memory usage characteristics.)
Comment 56 Ojan Vafai 2012-10-12 12:17:34 PDT
(In reply to comment #54)
> This patch means creating another 4459 Element's for the PseudoElements, or a 1.37% growth in the number of Nodes and a 3.2% growth in the number of Elements. Presumably our memory should be scaling with those factors.
> 
> There must be some bug in my code, I can't imagine that Element is 1.8k per instance.

I would guess that this is the cost of creating a ton of ElementRareDatas on nodes that otherwise wouldn't need them. ElementRareData is actually quite large. Any pages that uses a lot of generated content nodes, but otherwise doesn't hit ElementRareData, will definitely use a lot more memory.
Comment 57 Elliott Sprehn 2012-10-15 19:02:54 PDT
Created attachment 168835 [details]
Patch

Fix use-after-free bugs from not explictly calling detch and fix Range code that was confused by the new nodes which broke the DevTools inspector. This isn't meant for landing, I'm going to instead break this apart into parts where we can land most of it without turning it on and then land the final part to avoid having to roll it in and out repeatedly.
Comment 58 Elliott Sprehn 2012-10-16 01:37:35 PDT
(In reply to comment #57)
> Created an attachment (id=168835) [details]
> Patch
> 
> Fix use-after-free bugs from not explictly calling detch and fix Range code that was confused by the new nodes which broke the DevTools inspector. This isn't meant for landing, I'm going to instead break this apart into parts where we can land most of it without turning it on and then land the final part to avoid having to roll it in and out repeatedly.

I've found the leak: I keep the PseudoElement in a RefPtr in the ElementRareData and just assign 0 to it to remove the last ref, but because of TreeShared::deref() semantics even though the m_refCount is 0 the PseudoElement doesn't actually get freed because it still has a parent. Instead to follow the TreeShared design we should just forcibly delete the PseudoElements ignoring their ref count (or I guess we could unset it's parent and then deref?).

I now see a 2MB increase in memory usage on that test in Instruments and it's stable between runs so it's not leaking anymore, but it also seems to allocate 13k PseudoElements per run even though there should only be 2.5k :/
Comment 59 Elliott Sprehn 2012-10-16 02:38:05 PDT
(In reply to comment #58)
> ...
> I now see a 2MB increase in memory usage on that test in Instruments and it's stable between runs so it's not leaking anymore, but it also seems to allocate 13k PseudoElements per run even though there should only be 2.5k :/

Figured this out, there really are 13k pseudos in that test's output but there's only 2.5k in the source html5.html file! There's a bug in html5-full-render.html when chunking up the source HTML using substring where it should be using substr.

html5-full-render.html:16 has (w/ chunkSize = 500000):

spec.substring(chunkIndex * chunkSize, chunkSize);

but this is wrong because substring is (from, to) so it should be:

spec.substr(chunkIndex * chunkSize, chunkSize);

to get chunks of the specified size. Right now the html5-full-render.html actually loads massive chunks of the HTML5 spec that wrap around to the start repeatedly over and over yielding the following chunk sizes:

Chunk 1: 500000
Chunk 2: 0 
Chunk 3: 500000 
Chunk 4: 1000000
Chunk 5: 1500000
Chunk 6: 2000000
Chunk 7: 2500000
Chunk 8: 3000000
Chunk 9: 3500000
Chunk 10: 4000000
Chunk 11: 4500000
Chunk 12: 5000000
Chunk 13: 5500000

So while each chunk was supposed to be 500000, we end up loading chunks anywhere from 2-11x that in size.

@eseidel: It looks like you were the one who landed this test with the chunking in https://trac.webkit.org/changeset/96647 I can fix the bug, but the test will become less stressful since we'll be loading 5x less chars: 33.5m vs 6.5m chars.

The good news is that there's only a ~150 byte overhead per pseudo with this patch.

What do you want to do here Eric? :)
Comment 60 Eric Seidel 2012-10-16 10:19:17 PDT
Lets fix the perf test first, in a separate bug.  :)  Then, lets re-land this with the known memory effect, documented in the ChangeLog.  Thanks!
Comment 61 Ojan Vafai 2012-10-16 11:18:38 PDT
(In reply to comment #59)
> The good news is that there's only a ~150 byte overhead per pseudo with this patch.

That's 150 bytes more than we used to use or 150 bytes total per pseudo? Would you mind instrumenting your local build to spit out the number of psuedoelements and then testing out a few major sites (e.g. test the html spec, gmail, facebook, etc)? That way we can get a sense of what sort of memory impact to expect.
Comment 62 Elliott Sprehn 2012-10-17 00:04:28 PDT
(In reply to comment #61)
> (In reply to comment #59)
> > The good news is that there's only a ~150 byte overhead per pseudo with this patch.
> 
> That's 150 bytes more than we used to use or 150 bytes total per pseudo? Would you mind instrumenting your local build to spit out the number of psuedoelements and then testing out a few major sites (e.g. test the html spec, gmail, facebook, etc)? That way we can get a sense of what sort of memory impact to expect.

That's 150 bytes more than we used to use for each one. Gmail is ~40, Docs is ~30 pseudos. Facebook uses 175 on first load of my timeline and uses more and more as I scroll down and content loads. Twitter uses 134 on first load and uses more as I scroll down and content loads.

So that's +25k memory for Facebook, +19k for Twitter, and about +6k for Docs and Gmail. 6500 pseudos is equal to approximately 1MB of increased RAM usage. 

This is not any different than if you inserted the equivalent number of DOM nodes and touched anything that allocates rare data including: classList, childNodes, getElementsBy(TagName|ClassName) or a bunch of other things on them. 

We could move them out of the RareData and into static maps to reduce memory usage, but I think it'd make more sense to do that in a separate patch after this lands.
Comment 63 Elliott Sprehn 2012-10-24 18:33:09 PDT
Created attachment 170532 [details]
Patch
Comment 64 Elliott Sprehn 2012-10-24 19:51:29 PDT
(In reply to comment #63)
> Created an attachment (id=170532) [details]
> Patch

I posted this just to get it up there and get eyes on the changes/simplifications. I want to land this in parts so we don't need to roll the whole thing out again if something goes wrong.
Comment 65 Build Bot 2012-10-25 03:06:52 PDT
Comment on attachment 170532 [details]
Patch

Attachment 170532 [details] did not pass mac-ews (mac):
Output: http://queues.webkit.org/results/14562541

New failing tests:
editing/selection/hit-test-anonymous.html
Comment 66 WebKit Review Bot 2012-10-25 04:11:41 PDT
Comment on attachment 170532 [details]
Patch

Attachment 170532 [details] did not pass chromium-ews (chromium-xvfb):
Output: http://queues.webkit.org/results/14564534

New failing tests:
editing/selection/hit-test-anonymous.html
Comment 67 Elliott Sprehn 2012-11-29 17:12:22 PST
Created attachment 176852 [details]
Patch
Comment 68 Elliott Sprehn 2013-01-02 13:19:15 PST
This is all done!