Bug 55666

Summary: DOMNodeRemoved events are not dispatched
Product: WebKit Reporter: Andrey Adaikin <aandrey>
Component: DOMAssignee: Nobody <webkit-unassigned>
Status: RESOLVED WONTFIX    
Severity: Normal CC: aandrey, ap, darin, jamesr, mihaip, mjs, ojan, rniwa
Priority: P2    
Version: 528+ (Nightly build)   
Hardware: All   
OS: All   
Attachments:
Description Flags
Test on missing DOMNodeRemoved events none

Description Andrey Adaikin 2011-03-03 06:22:44 PST
DOMNodeRemoved events are not dispatched in some cases (I'd even say, it is rarely dispatched).

Repro steps:
- open the attached test case
- click DEL or BACKSPACE to delete the selected text
- expected log messages from the DOMNodeRemoved event handler, but nothing happens

NOTE: This test works on Safari 5.0.3 (6533.19.4) on Mac, but not on the latest versions of Chrome.

AFAIU, this may be a good point to look into: https://bugs.webkit.org/show_bug.cgi?id=46936
Comment 1 Andrey Adaikin 2011-03-03 06:24:07 PST
Created attachment 84552 [details]
Test on missing DOMNodeRemoved events
Comment 2 Andrey Adaikin 2011-03-03 06:31:30 PST
Also, if you delete the line 8 in the test case, here is the output in Safari 5.0.3:

Event DOMNodeRemoved: // line #8
Event DOMNodeInserted: 
Event DOMNodeRemoved: // line #9
Event DOMNodeInserted: 
Event DOMNodeRemoved: 
Event DOMNodeRemoved: 
Event DOMNodeInserted: // line #9
Event DOMNodeRemoved: 

And in the latest Chrome 11.0.686.1:

Event DOMNodeInserted: // line #9
Event DOMNodeRemoved: // line #9
Event DOMNodeInserted: // line #9
Event DOMNodeRemoved: // line #9
Event DOMNodeInserted: // line #9
Event DOMNodeRemoved: // line #9
Event DOMNodeInserted: // line #9
Comment 3 Ryosuke Niwa 2011-03-03 21:34:15 PST
This isn't a bug but rather a conscious behavior change per http://trac.webkit.org/changeset/73690.  After this changeset, WebKit fires DOMNodeRemoved AFTER the node has been removed, in which case, the event doesn't bubble to the node's ancestors because by the time we fire the event, the node had already removed from the document and therefore has no parent.

You can listen to DOMSubtreeModified to detect node removal if you want to detect descendent nodes being removed.  Please let me know if this alternative isn't adequate for your purpose and poses significant challenges.
Comment 4 Ojan Vafai 2011-03-03 22:48:05 PST
Is there a page that breaks because of this? If not, I'm tempted to WONTFIX this given that subtreemodified still works and DOMNodeRemoved still works if you add the listener to the node itself.
Comment 5 Andrey Adaikin 2011-03-03 23:55:18 PST
Strange, according to the http://www.w3.org/TR/DOM-Level-3-Events/#event-type-DOMNodeRemoved - "This event must be dispatched before the removal takes place." Is this a wrong place to look at, or we just don't care about the standards? :)

I am more than happy to use a workaround using DOMSubtreeModified, but I failed to find one... Can you give me a good recipe how to use it, so that I should not miss DOMNodeRemoved events? If I remember correctly, I was not able to determine the particular DOM node that was removed from a subtree. Seems like there is no way now to catch the removed DOM node directly from the event?!
Comment 6 Ojan Vafai 2011-03-04 00:05:09 PST
(In reply to comment #5)
> Strange, according to the http://www.w3.org/TR/DOM-Level-3-Events/#event-type-DOMNodeRemoved - "This event must be dispatched before the removal takes place." Is this a wrong place to look at, or we just don't care about the standards? :)

This in an intentional experiment to see if we can get away with diverging from the current specification without hurting web compatibility. If we succeed, then we will encourage other browser vendors to follow suite and will try to get the standard changed.

> I am more than happy to use a workaround using DOMSubtreeModified, but I failed to find one... Can you give me a good recipe how to use it, so that I should not miss DOMNodeRemoved events? If I remember correctly, I was not able to determine the particular DOM node that was removed from a subtree. Seems like there is no way now to catch the removed DOM node directly from the event?!

Correct. There's no way to get the list of nodes removed without keeping track of it yourself. Is there a web page that's not working? What are you trying to do that you need this information?

At a high-level, mutation events cause a lot of problems for browser developers without really helping web developers much. In addition, they have performance costs that web developers are rightfully unaware of. So we're seeing how much we can get away with modifying them to avoid these problems. There are a number of proposals for full replacements of mutation events, but none of them are currently being worked on.
Comment 7 Ryosuke Niwa 2011-03-04 00:17:29 PST
(In reply to comment #5)
> Strange, according to the http://www.w3.org/TR/DOM-Level-3-Events/#event-type-DOMNodeRemoved - "This event must be dispatched before the removal takes place." Is this a wrong place to look at, or we just don't care about the standards? :)

This is a willful violation of the spec.  Also note that DOM Level 3 deprecates the use of DOM mutation events entirely.

> I am more than happy to use a workaround using DOMSubtreeModified, but I failed to find one... Can you give me a good recipe how to use it, so that I should not miss DOMNodeRemoved events? If I remember correctly, I was not able to determine the particular DOM node that was removed from a subtree. Seems like there is no way now to catch the removed DOM node directly from the event?!

Ah, DOMSubtreeModified might not be a good alternative.  Try attaching an event listener for DOMNodeRemoved to every child node.  Then the event will fire for each node after the node has been removed.
Comment 8 Andrey Adaikin 2011-03-04 00:23:37 PST
I see. I am working on the editor inside the Web Inspector: https://bugs.webkit.org/show_bug.cgi?id=53588

I already use the DOMSubtreeModified event to hack around the "missing" DOMNodeRemoved events, but in some corner cases it still does not work. I do not want to traverse the whole subtree every time a node is removed for obvious performance reasons. Also it may not be a good idea to listen for DOMNodeRemoved on every DOM node inside the subtree - it does not seem to be a robust solution, and, if I understand correctly, the parent node of the remove node will still be unavailable.

In other words, a DOMNodeRemoved event dispatched BEFORE the removal takes place - is exactly what I need. And now I have to invent a workaround to "emulate" it :)
Comment 9 Ryosuke Niwa 2011-03-04 00:29:31 PST
(In reply to comment #8)
> I already use the DOMSubtreeModified event to hack around the "missing" DOMNodeRemoved events, but in some corner cases it still does not work. I do not want to traverse the whole subtree every time a node is removed for obvious performance reasons.

Why do you need to detect a node removal?

>Also it may not be a good idea to listen for DOMNodeRemoved on every DOM node inside the subtree - it does not seem to be a robust solution, and, if I understand correctly, the parent node of the remove node will still be unavailable.

Yes, the parent node won't available.

> In other words, a DOMNodeRemoved event dispatched BEFORE the removal takes place - is exactly what I need. And now I have to invent a workaround to "emulate" it :)

For what do you use DOMNodeRemoved?
Comment 10 Andrey Adaikin 2011-03-04 00:38:56 PST
In the contentEditable editor I listen for the DOM mutation events to find out the DOM nodes that were modified, inserted or removed by the user, so that I could update the underlying text model being edited. For the "modified" part I use DOMCharacterDataModified event, and for the "inserted" - DOMNodeInserted, and both work perfectly. The problem now is how to track the removed DOM nodes...
Comment 11 Ryosuke Niwa 2011-03-04 01:20:18 PST
(In reply to comment #10)
> In the contentEditable editor I listen for the DOM mutation events to find out the DOM nodes that were modified, inserted or removed by the user, so that I could update the underlying text model being edited. For the "modified" part I use DOMCharacterDataModified event, and for the "inserted" - DOMNodeInserted, and both work perfectly. The problem now is how to track the removed DOM nodes...

You can listen to DOMNodeInserted, and then add an event listener for DOMNodeRemoved on that node inside the event listener and you can also remember the parent node.  When the node is removed, DOMNodeRemoved is fired.
Comment 12 Ryosuke Niwa 2011-03-04 01:31:52 PST
I did some research on how IE9 handles this.  It fires DOMNodeRemoved (possibly others) on all the nodes it intends to remove before making any mutations.  It then mutates the DOM and fires the rest of mutation events asynchronously.

Mimicking this behavior in WebKit poses a challenge because editing code assumes that mutations happen in place (i.e. when we invoke a function).  One possible work-around is to simulate the entire editing command in some isolated world where we detect which node is removed, and then re-run the editing command on the actual DOM.  It's like run the command, undo it, and then redo it again where only the last redo part is visible to scripts.
Comment 13 Andrey Adaikin 2011-03-04 01:46:47 PST
Ryosuke, thanks for the research and your ideas!

I will try to workaround this by adding DOMNodeRemoved listeners only on direct children of the subtree I need to monitor, and traverse those children on the DOMSubtreeModified events to find the removed nodes inside those subtrees. Such combined solution seems to be acceptable for me from the performance point of view.
Comment 14 Ryosuke Niwa 2011-03-04 03:25:29 PST
(In reply to comment #13)
> I will try to workaround this by adding DOMNodeRemoved listeners only on direct children of the subtree I need to monitor, and traverse those children on the DOMSubtreeModified events to find the removed nodes inside those subtrees. Such combined solution seems to be acceptable for me from the performance point of view.

Great! Let me know how it goes. I'm very much interested in knowing if new behavior makes something impossible or there is a reasonable work-around to it.
Comment 15 Andrey Adaikin 2011-03-04 07:00:08 PST
I implemented the suggested workaround (details here: https://bugs.webkit.org/show_bug.cgi?id=55769), and it seems working fine.

Marking this issue as WONTFIX.