Bug 140999

Summary: Removing text node does not remove its associated markers
Product: WebKit Reporter: Grzegorz Czajkowski <g.czajkowski>
Component: DOMAssignee: Grzegorz Czajkowski <g.czajkowski>
Status: RESOLVED FIXED    
Severity: Normal CC: cdumez, commit-queue, darin, enrica, esprehn+autocc, kangil.han, rniwa, webkit-bug-importer
Priority: P2 Keywords: InRadar
Version: 528+ (Nightly build)   
Hardware: Unspecified   
OS: Unspecified   
Attachments:
Description Flags
Patch
none
test_case_1, input.value = "" does not clear markers
none
Patch
none
apply Chris' comments + add async bits
none
Apply Chris' comments (round 2) and let removeMarkersAync remove specific DocumentMarkers
none
test_case_2, removing text node does not clear markers
none
Apply Ryosuke's comments rniwa: review+

Description Grzegorz Czajkowski 2015-01-28 08:12:20 PST
Clearing text on HTMLTextAreaElement and HTMLInputElement does not remove its previous markers. 

For example, running the following script two times will cause that Document stores two markers (one for current node and one for previous which was remved!).

<script>
    textArea.value = "";
    textArea.focus();
    document.execCommand("InsertText", false, "welllcome ");
</script>

document().markers().showMarkers()

2 nodes have markers:
0x7f31b5122dc0 1:[0:9](0)
0x7f31b5122d70 1:[0:9](0)

This happens when text to insert is empty and setInnerTextValue("") calls removeChildren() on the previous node.

It does not affect user as the absolete node has been removed and its markers not drawn.
However does it make sense to store it until document's detach?
Comment 1 Grzegorz Czajkowski 2015-01-28 08:17:49 PST
Created attachment 245544 [details]
Patch
Comment 2 Darin Adler 2015-01-28 09:04:53 PST
Comment on attachment 245544 [details]
Patch

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

> Source/WebCore/dom/ContainerNodeAlgorithms.h:242
> +    if (is<Text>(node))
> +        node.document().markers().removeMarkers(&node);

This is in a really hot code path; I don’t think we want to do this in this way. Lets find a way that is more efficient.
Comment 3 Grzegorz Czajkowski 2015-01-28 10:45:04 PST
Comment on attachment 245544 [details]
Patch

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

>> Source/WebCore/dom/ContainerNodeAlgorithms.h:242
>> +        node.document().markers().removeMarkers(&node);
> 
> This is in a really hot code path; I don’t think we want to do this in this way. Lets find a way that is more efficient.

Right. I'll try to propose a new approach. Thanks!
Comment 4 Grzegorz Czajkowski 2015-01-29 08:10:59 PST
I was playing with this bug a bit and it turned out that it's reproducible for contenteditable as well, for example:

<script>
    div.focus();
    document.execCommand("InsertText", false, "welllcome ");

    div.innerHTML = "";   // after that we still store markers for "welllcome" in DocumentMakerController
</script>

Since the root cause is the same (we don't remove markers while children are being removed) I'll suggest removing them somewhere inside ContatinerNode::removeChildren().
Comment 5 Grzegorz Czajkowski 2015-01-30 02:57:51 PST
Created attachment 245704 [details]
test_case_1, input.value = "" does not clear markers
Comment 6 Grzegorz Czajkowski 2015-01-30 03:03:41 PST
Created attachment 245705 [details]
Patch
Comment 7 Chris Dumez 2015-01-30 11:19:46 PST
Comment on attachment 245705 [details]
Patch

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

> Source/WebCore/dom/ContainerNode.cpp:664
> +            if (is<Text>(removedChild))

Special-casing Text here seems weird. Also, I am a bit worried about the performance impact of this change. Wouldn't it be possible to override "virtual void removedFrom(ContainerNode& insertionPoint);" in Text class and call removeMarkers() there instead? It would be much nicer if Text handling was in Text class instead of ContainerNode.
Comment 8 Grzegorz Czajkowski 2015-02-02 03:06:52 PST
Comment on attachment 245705 [details]
Patch

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

>> Source/WebCore/dom/ContainerNode.cpp:664
>> +            if (is<Text>(removedChild))
> 
> Special-casing Text here seems weird. Also, I am a bit worried about the performance impact of this change. Wouldn't it be possible to override "virtual void removedFrom(ContainerNode& insertionPoint);" in Text class and call removeMarkers() there instead? It would be much nicer if Text handling was in Text class instead of ContainerNode.

Moving this to Text::removedFrom(ContainerNode) seems like a good idea. Thanks!
Additionally, I'll try to add some async bits to make it non-blocking.
Comment 9 Grzegorz Czajkowski 2015-02-02 05:50:36 PST
Created attachment 245871 [details]
apply Chris' comments + add async bits
Comment 10 Chris Dumez 2015-02-02 10:07:30 PST
Comment on attachment 245871 [details]
apply Chris' comments + add async bits

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

> Source/WebCore/dom/DocumentMarkerController.h:81
> +    void willRemoveAllMarkersFrom(Node*);

Why doesn't this take a reference?

> Source/WebCore/dom/DocumentMarkerController.h:113
> +    void removeMarkersFromRemovedNodeTimerFired();

We usually put private methods before the private data members, not after.

> Source/WebCore/dom/Text.cpp:200
> +    document().markers().willRemoveAllMarkersFrom(this);

I am not familiar with these markers and I cannot cannot if doing things asynchronously makes sense here so I'll let someone else review. However, I'll say that willRemoveAllMarkersFrom() naming is confusing as it doesn't tell me that it actually removes the markers asynchronously. We probably want something like removeMarkersAsync().

> Source/WebCore/dom/Text.cpp:201
> +    Node::removedFrom(removalPoint);

CharacterData:: as this is the parent class. Even if CharacterData doesn't override this function now, it could later.

> Source/WebCore/dom/Text.h:53
> +    virtual void removedFrom(ContainerNode& removalPoint) override final;

Should probably be protected?
Comment 11 Grzegorz Czajkowski 2015-02-02 11:51:11 PST
(In reply to comment #10)
> Comment on attachment 245871 [details]
> apply Chris' comments + add async bits
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=245871&action=review
> 
> > Source/WebCore/dom/DocumentMarkerController.h:81
> > +    void willRemoveAllMarkersFrom(Node*);
> 
> Why doesn't this take a reference?
> 
> > Source/WebCore/dom/DocumentMarkerController.h:113
> > +    void removeMarkersFromRemovedNodeTimerFired();
> 
> We usually put private methods before the private data members, not after.
> 
> > Source/WebCore/dom/Text.cpp:200
> > +    document().markers().willRemoveAllMarkersFrom(this);
> 
> I am not familiar with these markers and I cannot cannot if doing things
> asynchronously makes sense here so I'll let someone else review. However,
> I'll say that willRemoveAllMarkersFrom() naming is confusing as it doesn't
> tell me that it actually removes the markers asynchronously. We probably
> want something like removeMarkersAsync().
> 
> > Source/WebCore/dom/Text.cpp:201
> > +    Node::removedFrom(removalPoint);
> 
> CharacterData:: as this is the parent class. Even if CharacterData doesn't
> override this function now, it could later.
> 
> > Source/WebCore/dom/Text.h:53
> > +    virtual void removedFrom(ContainerNode& removalPoint) override final;
> 
> Should probably be protected?

Thanks for comprehensive review! I'll take all the comments into consideration.
Comment 12 Grzegorz Czajkowski 2015-02-03 05:44:56 PST
Created attachment 245933 [details]
Apply Chris' comments (round 2) and let removeMarkersAync remove specific DocumentMarkers
Comment 13 Chris Dumez 2015-02-03 19:13:59 PST
I'll let Darin or Ryosuke review as I am not familiar with these document markers.
Comment 14 Grzegorz Czajkowski 2015-02-04 11:56:09 PST
ping reviewers
this is a friendly request for review. Thanks
Comment 15 Ryosuke Niwa 2015-02-12 02:39:27 PST
Comment on attachment 245933 [details]
Apply Chris' comments (round 2) and let removeMarkersAync remove specific DocumentMarkers

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

> Source/WebCore/dom/Text.cpp:203
> +void Text::removedFrom(ContainerNode& removalPoint)
> +{
> +    document().markers().removeMarkersAsync(*this);
> +    CharacterData::removedFrom(removalPoint);
> +}
> +

I don't think it makes sense to add Text::removedFrom just to remove markers.
We should be doing this in either one of Document::nodeWillBeRemoved, Document::nodeChildrenWillBeRemoved, or Document::updateRangesAfterChildrenChanged instead.

And I don't think making only this one code path async make sense. If anything, we should check hasMarkers() before calling removeMarkers
or make all of Document::textInserted, Document::textRemoved, etc… async as well.
Comment 16 Ryosuke Niwa 2015-02-12 02:41:57 PST
Comment on attachment 245933 [details]
Apply Chris' comments (round 2) and let removeMarkersAync remove specific DocumentMarkers

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

> Source/WebCore/dom/DocumentMarkerController.cpp:474
> +    m_removeMarkersTimerData.first = &node;
> +    m_removeMarkersTimerData.second = markerTypes;

I don't think this would work if two different nodes were removed in a single task.
Comment 17 Grzegorz Czajkowski 2015-02-13 03:19:44 PST
Created attachment 246515 [details]
test_case_2, removing text node does not clear markers

I was playing with this bug a bit and found out that we don't remove markers on div.removeChild(div.firstChild);
Comment 18 Grzegorz Czajkowski 2015-02-13 03:21:00 PST
Comment on attachment 245933 [details]
Apply Chris' comments (round 2) and let removeMarkersAync remove specific DocumentMarkers

Clear review flag due to Ryosuke's review.
Comment 19 Grzegorz Czajkowski 2015-02-13 07:53:53 PST
Created attachment 246526 [details]
Apply Ryosuke's comments
Comment 20 Ryosuke Niwa 2015-02-13 17:04:12 PST
Comment on attachment 246526 [details]
Apply Ryosuke's comments

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

> Source/WebCore/dom/Document.cpp:3713
> +    if (is<Text>(n))

We should check m_markers->hasMarkers() here as well.
Comment 21 Grzegorz Czajkowski 2015-02-15 03:32:46 PST
(In reply to comment #20)
> Comment on attachment 246526 [details]
> Apply Ryosuke's comments
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=246526&action=review
> 
> > Source/WebCore/dom/Document.cpp:3713
> > +    if (is<Text>(n))
> 
> We should check m_markers->hasMarkers() here as well.

Happy to see r+, thanks. I think we can skip it here as inside removeMarkers() we already check them, https://trac.webkit.org/browser/trunk/Source/WebCore/dom/DocumentMarkerController.cpp#L454
Comment 22 Grzegorz Czajkowski 2015-02-16 00:26:11 PST
Committed r180139: <http://trac.webkit.org/changeset/180139>
Comment 23 Radar WebKit Bug Importer 2015-02-16 04:54:59 PST
<rdar://problem/19844628>