Bug 111494 - innerHTML should use lazyAttach to avoid creating a render tree until needed
Summary: innerHTML should use lazyAttach to avoid creating a render tree until needed
Status: RESOLVED CONFIGURATION CHANGED
Alias: None
Product: WebKit
Classification: Unclassified
Component: New Bugs (show other bugs)
Version: 528+ (Nightly build)
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Eric Seidel (no email)
URL:
Keywords:
Depends on: 116926
Blocks: 94733 111644
  Show dependency treegraph
 
Reported: 2013-03-05 16:30 PST by Eric Seidel (no email)
Modified: 2023-04-01 00:55 PDT (History)
14 users (show)

See Also:


Attachments
Patch (2.67 KB, patch)
2013-03-05 16:33 PST, Eric Seidel (no email)
no flags Details | Formatted Diff | Diff
updated to tot (2.74 KB, patch)
2013-03-13 17:14 PDT, Eric Seidel (no email)
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Eric Seidel (no email) 2013-03-05 16:30:19 PST
innerHTML should use lazyAttach to avoid creating a render tree until needed
Comment 1 Eric Seidel (no email) 2013-03-05 16:33:36 PST
Created attachment 191596 [details]
Patch
Comment 2 Eric Seidel (no email) 2013-03-05 16:34:17 PST
This may need a couple more tweeks.  I saw two editing-related failures on my local machine, but am not yet sure if those are from this patch or not.
Comment 3 Kentaro Hara 2013-03-05 16:35:35 PST
Comment on attachment 191596 [details]
Patch

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

> Source/WebCore/ChangeLog:15
> +        In any case, this brings our inner-html-setter number from 93 runs/sec to 507 runs/sec :)

Really!
Comment 4 Adam Barth 2013-03-05 16:39:28 PST
Comment on attachment 191596 [details]
Patch

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

> Source/WebCore/editing/markup.cpp:1100
> -        containerNode->replaceChild(fragment, containerNode->firstChild(), ec);
> +        containerNode->replaceChild(fragment, containerNode->firstChild(), ec, true);

I wish the last parameter had more readable value than "true" (but that's obviously not something you need to solve in this patch).
Comment 5 Kentaro Hara 2013-03-05 16:40:45 PST
The 2400% regression bug is here: https://bugs.webkit.org/show_bug.cgi?id=94733
Comment 6 Eric Seidel (no email) 2013-03-05 16:41:07 PST
(In reply to comment #4)
> (From update of attachment 191596 [details])
> View in context: https://bugs.webkit.org/attachment.cgi?id=191596&action=review
> 
> > Source/WebCore/editing/markup.cpp:1100
> > -        containerNode->replaceChild(fragment, containerNode->firstChild(), ec);
> > +        containerNode->replaceChild(fragment, containerNode->firstChild(), ec, true);
> 
> I wish the last parameter had more readable value than "true" (but that's obviously not something you need to solve in this patch).

I tried shaving that Yak first, actually. :)   But that will wait for a second patch.  I'm going to also change the default from false to true, since I suspect any place which depends on implicit "false" behavior is probably wrong.  There are some cases where it might make performance-sense to explicitly require a synchronous attach (like for editing I could imagine).
Comment 7 Eric Seidel (no email) 2013-03-05 17:43:41 PST
(In reply to comment #4)
> (From update of attachment 191596 [details])
> View in context: https://bugs.webkit.org/attachment.cgi?id=191596&action=review
> 
> > Source/WebCore/editing/markup.cpp:1100
> > -        containerNode->replaceChild(fragment, containerNode->firstChild(), ec);
> > +        containerNode->replaceChild(fragment, containerNode->firstChild(), ec, true);
> 
> I wish the last parameter had more readable value than "true" (but that's obviously not something you need to solve in this patch).

Bug 111503.
Comment 8 Eric Seidel (no email) 2013-03-05 17:54:43 PST
If bug 111503 lands before this one, this will need a trivial rebase.
Comment 9 WebKit Review Bot 2013-03-05 19:46:52 PST
Comment on attachment 191596 [details]
Patch

Attachment 191596 [details] did not pass chromium-ews (chromium-xvfb):
Output: http://webkit-commit-queue.appspot.com/results/17082077

New failing tests:
fast/forms/basic-buttons.html
editing/execCommand/change-font.html
Comment 10 Build Bot 2013-03-06 00:42:49 PST
Comment on attachment 191596 [details]
Patch

Attachment 191596 [details] did not pass mac-wk2-ews (mac-wk2):
Output: http://webkit-commit-queue.appspot.com/results/17081088
Comment 11 Build Bot 2013-03-06 03:09:20 PST
Comment on attachment 191596 [details]
Patch

Attachment 191596 [details] did not pass win-ews (win):
Output: http://webkit-commit-queue.appspot.com/results/17021095
Comment 12 Build Bot 2013-03-06 07:04:32 PST
Comment on attachment 191596 [details]
Patch

Attachment 191596 [details] did not pass mac-ews (mac):
Output: http://webkit-commit-queue.appspot.com/results/17065088
Comment 13 Eric Seidel (no email) 2013-03-06 12:07:59 PST
--- /src/WebKit/Source/WebKit/chromium/webkit/Release/layout-test-results/fast/forms/basic-buttons-expected.txt
+++ /src/WebKit/Source/WebKit/chromium/webkit/Release/layout-test-results/fast/forms/basic-buttons-actual.txt
@@ -14,6 +14,7 @@
           text run at (0,40) width 617: "with text (\"foo\") and then uses six different paddings to make sure each of the buttons render properly. "
         RenderBR {BR} at (617,40) size 0x19
         RenderBR {BR} at (0,60) size 0x19
+        RenderText {#text} at (0,0) size 0x0
       RenderTable {TABLE} at (0,80) size 657x285
         RenderTableSection {TBODY} at (0,0) size 657x285
           RenderTableRow {TR} at (0,0) size 657x22

And:

--- /src/WebKit/Source/WebKit/chromium/webkit/Release/layout-test-results/editing/execCommand/change-font-expected.txt
+++ /src/WebKit/Source/WebKit/chromium/webkit/Release/layout-test-results/editing/execCommand/change-font-actual.txt
@@ -1,2 +1,5 @@
 There should only be one font tag.
 SUCCESS
+
+

Are the failures.

Both come from uses of innerHTML on the body element.

I'm not sure about either.  Maybe there is some white-space collapsing that we would normally do during synchronous attach() which we somehow miss in the async case?  The rendering of both pages is correct, this is a DRT-only issue.  Not yet sure if we care. :)
Comment 14 Eric Seidel (no email) 2013-03-06 12:41:22 PST
These failures sound very related to the code Haraken touched in bug 110786.  (I'm not suggesting that his change broke anything here.  Just that he's touched that code and it's probably not lazy-attach aware.)
Comment 15 Eric Seidel (no email) 2013-03-06 12:45:52 PST
(In reply to comment #14)
> These failures sound very related to the code Haraken touched in bug 110786.  (I'm not suggesting that his change broke anything here.  Just that he's touched that code and it's probably not lazy-attach aware.)

I suspect that we should move the "don't ignore text renderers in all cases" code in Node::attach() to style recalc and that would fix this issue.

Is suspect we're currently broken wrt ignoring text nodes and display/visibility changes of renderers.
Comment 16 Eric Seidel (no email) 2013-03-06 12:52:39 PST
This FIXME in Element::recalcStyle *terrifies* me:
        if (ch == Detach || !currentStyle) {
            // FIXME: The style gets computed twice by calling attach. We could do better if we passed the style along.
            reattach();

If we're really recalc-ing style twice for intial attach of a lazy-attached node, that's absolutely horrible.
Comment 17 Eric Seidel (no email) 2013-03-06 14:41:48 PST
(In reply to comment #16)
> This FIXME in Element::recalcStyle *terrifies* me:
>         if (ch == Detach || !currentStyle) {
>             // FIXME: The style gets computed twice by calling attach. We could do better if we passed the style along.
>             reattach();
> 
> If we're really recalc-ing style twice for intial attach of a lazy-attached node, that's absolutely horrible.

Filed bug 111627.
Comment 18 Antti Koivisto 2013-03-07 05:41:57 PST
Comment on attachment 191596 [details]
Patch

We should really do all attaching lazily (including that triggered by the parser).
Comment 19 Eric Seidel (no email) 2013-03-07 13:03:50 PST
(In reply to comment #18)
> (From update of attachment 191596 [details])
> We should really do all attaching lazily (including that triggered by the parser).

Agreed.  Do you have any theories for the failures mentioned in comment 13?  Debugging those is blocking this change.
Comment 20 Antti Koivisto 2013-03-08 00:42:38 PST
> Agreed.  Do you have any theories for the failures mentioned in comment 13?

Not without debugging no.
Comment 22 Eric Seidel (no email) 2013-03-13 17:14:15 PDT
Created attachment 193025 [details]
updated to tot
Comment 23 Eric Seidel (no email) 2013-05-10 14:41:56 PDT
With the patch applied, this test:

<!DOCTYPE html>
<html>
<body>
text
<br>
<script>
document.body.innerHTML += '<div></div>';
</script>
</body>
</html>

Fails with this diff:

@@ -7,4 +7,7 @@
         RenderText {#text} at (0,0) size 26x19
           text run at (0,0) width 26: "text "
         RenderBR {BR} at (26,0) size 0x19
+        RenderText {#text} at (0,0) size 0x0
       RenderBlock {DIV} at (0,20) size 784x0
+      RenderBlock (anonymous) at (0,20) size 784x0
+        RenderText {#text} at (0,0) size 0x0

Still debuggin.
Comment 24 Eric Seidel (no email) 2013-05-10 15:27:36 PDT
Interestingly, removing this block in Node::attach() makes the behavior change go away. :)  (At least in the test in question.)

    // If this node got a renderer it may be the previousRenderer() of sibling text nodes and thus affect the
    // result of Text::textRendererIsNeeded() for those nodes.
    if (renderer()) {
        for (Node* next = nextSibling(); next; next = next->nextSibling()) {
            if (next->renderer())
                break;
            if (!next->attached())
                break; // Assume this means none of the following siblings are attached.
            if (!next->isTextNode())
                continue;
            ASSERT(!next->renderer());
            toText(next)->createTextRendererIfNeeded();
            // If we again decided not to create a renderer for next, we can bail out the loop,
            // because it won't affect the result of Text::textRendererIsNeeded() for the rest
            // of sibling nodes.
            if (!next->renderer())
                break;
        }
    }


I'm not yet entirely sure what the "correct" behavior for these tests are, or if that block is doing what it really means to.
Comment 25 Eric Seidel (no email) 2013-05-10 17:24:44 PDT
The following printf-logging on the above test:

@@ -221,8 +221,11 @@ bool Text::textRendererIsNeeded(const NodeRenderingContext& context)
         return true;
 
     RenderObject* prev = context.previousRenderer();
-    if (prev && prev->isBR()) // <span><br/> <br/></span>
+    printf("prev: %p, br: %i\n", prev, prev && prev->isBR());
+    if (prev && prev->isBR()) {
+        // <span><br/> <br/></span>
         return false;
+    }
 
     if (parent->isRenderInline()) {
         // <span><div/> <div/></span>
@@ -236,12 +239,15 @@ bool Text::textRendererIsNeeded(const NodeRenderingContext& context)
         while (first && first->isFloatingOrOutOfFlowPositioned())
             first = first->nextSibling();
         RenderObject* next = context.nextRenderer();
-        if (!first || next == first)
+        if (!first || next == first) {
+            printf("magic!\n");
             // Whitespace at the start of a block just goes away.  Don't even
             // make a render object for this text.
             return false;
         }
+    }
 
+    printf("Default true: '%s'\n", wholeText().utf8().data());
     return true;
 }


Produces the following change:

@@ -1,8 +1,19 @@
-prev: 0x3c7cbc586038, br: 1
-prev: 0x3c7cbc3594b8, br: 1
-prev: 0x3c7cbc6dc138, br: 0
-prev: 0x3c7cbc6dc138, br: 0
-prev: 0x3c7cbc6dc138, br: 0
+prev: 0x1af39d059578, br: 1
+prev: (nil), br: 0
+magic!
+prev: (nil), br: 0
+magic!
+prev: (nil), br: 0
+magic!
+prev: 0x1af39d4ee0f8, br: 0
+Default true: '
+'
+prev: 0x1af39d512cf8, br: 0
+Default true: '
+
+
+
+'
 layer at (0,0) size 800x600
   RenderView at (0,0) size 800x600
 layer at (0,0) size 800x36
@@ -12,4 +23,7 @@
         RenderText {#text} at (0,0) size 26x19
           text run at (0,0) width 26: "text "
         RenderBR {BR} at (26,0) size 0x19
+        RenderText {#text} at (0,0) size 0x0
       RenderBlock {DIV} at (0,20) size 784x0
+      RenderBlock (anonymous) at (0,20) size 784x0
+        RenderText {#text} at (0,0) size 0x0


Clearly we're taking a very different path for text node creation during lazyAttach.  Still investigating.
Comment 26 Ryosuke Niwa 2013-05-10 17:40:56 PDT
(In reply to comment #24)
> Interestingly, removing this block in Node::attach() makes the behavior change go away. :)  (At least in the test in question.)

How about reverting http://trac.webkit.org/changeset/144526 ?
Comment 27 Eric Seidel (no email) 2013-05-10 18:31:53 PDT
I believe the problem here is simply the code in Node::attach().

When lazy-attaching, nothing has a renderer yet.  But the first thing we attach, then walks through its siblings, creating a text renderers.  (See comment 24 for the context.)

lazyAttach marks you as "attached", which confuses this code.
Comment 28 Anne van Kesteren 2023-04-01 00:55:11 PDT
This has all been substantially changed.