Re: [D3E] Possible Changes to Mutation Events
Stewart Brodie wrote: Maciej Stachowiak [EMAIL PROTECTED] wrote: On Jul 16, 2008, at 5:00 PM, Stewart Brodie wrote: Maciej Stachowiak [EMAIL PROTECTED] wrote: On Jul 16, 2008, at 2:03 PM, Stewart Brodie wrote: I agree with all that, but it's not the whole story, because making this change has potentially severe consequences for memory usage if you start moving large subtrees around within a document. Just how long is the event queue allowed to get? It will only grow without bound if every mutation event handler in turn modifies the DOM itself, or if a compound DOM operation is unbounded. Unbounded queueing is a real concern. Having said that, the current situation is that we have unbounded recursion, which is equally unacceptable, although the recursion depth is at least defined by the number of recursive calls made by the event listeners, whereas the queue length is dependent on the number of nodes in the affected subtree, which is likely to be substantially greater. Can you describe a plausible scenario where growth of the mutation event queue would be a significant issue? You've said repeatedly you are worried about it but have not given an example of when this might happen. Are you talking about the kind of programmer error that results in an infinite loop? [Many apologies for the delayed reply] The worst case I can think of is Node.replaceChild() where the removed child is a large subtree and the new child is a large subtree that's attached somewhere else in the document. This sort of content is easily feasible in the sort of UI applications that we have to support (for example, a TV electronic programme guide), and yet the memory we have available is severely constrained. If each subtree contains just 100 nodes, that's 303 events to be queued. Even if the new subtree is not attached, that's still 203 events. What happens if we have no memory left in which to queue these events? So how much memory are these 303 events compared to the 200 nodes that are involved in the mutation? I would expect it to be pretty small. My point is that it's easy to create a scenario where most algorithms consume a lot of data in absolute terms. But if it requires that large amounts of data is already being consumed then this doesn't seem like a big practical problem. I would actually argue that the problem here is that the DOMNodeRemovedFromDocument event is pretty insanely defined. The rest is just side effects from that. This is why we haven't implemented it in mozilla as of yet. Concentrating on the removal side, currently, I have to queue 0 events, and I can optimise away DOMNodeRemovedFromDocument completely, usually. However, I have to ensure that the detached node and its parent are still valid after the dispatch of DOMNodeRemoved. With these changes, I could only optimise away the events if *neither* DOMNodeRemoved or DOMNodeRemovedFromDocument have any listeners. Sure, the DNRFD events would get optimised out at the point of dispatch, but I've had to store a whole load of information in the meantime. Hmm.. this is indeed a problem. I definitely want to be able to optimize away most of the mutation code if there isn't someone actually listening to these events. One way to fix it would be to state that if no listeners are registered for an event by the time it is scheduled, an implementation is allowed to not fire the event at all, even if a listener got registered before the event would have actually fired. It seems like it's a very odd edge case where anyone would notice a difference at all. It requires that one mutation event listener registers another listener for another event. It'd be ugly, but I can't think of anything better really. The alternative would be to queue a promise to generate the event for every node in the subtree, but that promise would end up being fulfilled based on the state of the tree after DOMNodeRemoved listeners had been executed. However, it would reduce the likelihood of memory problems dramatically. Yes, noone is saying that you need to actually create the actual event objects until it's needed. / Jonas
Re: [D3E] Possible Changes to Mutation Events
On Thu, 17 Jul 2008 17:51:42 -0700, Jonas Sicking [EMAIL PROTECTED] wrote: As for when the events fire (note that this is just clarifications of the spec, not changes to it): For events that fire after the mutation takes place I propose that we add a concept of a compound operation and state that while compound operations are in progress no mutation events are fired. I was thinking about this some more, and it seems to me that a compound operation is more or less a lightweight transaction (transaction in the sense that is referred to in 1.6.4 of L2 events). It might be useful to expose this functionality to authors as well. If a couple of methods were added to allow authors to indicate the start and end of a compound operation/transaction, and it were guaranteed that no mutation events would be dispatched until the end of the compound operation, they would have to worry less about dealing with interference from other mutation listeners while they're doing a high-level compound operation. It would be pretty trivial to implement on top of internal compound events, and would solve the problem of revalidating assumptions for authors as well as implementors. The only pitfall I can see is if authors started a compound operation and never ended it, causing an unbounded queue of events. We'd have to allow implementations to drop the queue (or some events) if it got too large. Thoughts? kats
Re: [D3E] Possible Changes to Mutation Events
Doug Schepers wrote: Hi, Jonas- Thanks for this modified proposal. I want to hear back from those who've already commented as to their disposition, and to solicit comments from other known implementors (e.g., gtk, BitFlash, Opera, JSR), but I think your proposal is reasonable, and well detailed. A few comments inline... Jonas Sicking wrote (on 7/17/08 8:51 PM): * Add a |readonly attribute long relatedIndex;| property to the MutationEvent interface. * Add a DOMChildRemoved event which is fired on a node when one of its children is removed. The relatedNode property contains the removed child, and relatedIndex contains the index the child had immediately before the removal. The event is fired after the removal takes place. * Add a DOMDescendantRemovedFromDocument event which is fired on a node when the node is in a document, but any of nodes the descendants is removed from the document. The event is fired after the removal takes place. The relatedNode property contains the removed descendant. The relatedIndex property contains the index the child had immediately before the removal. (Should relatedIndex be -1 when the node wasn't removed from its parent, but rather an ancestor was?) What is the rationale for having both 'DOMChildRemoved' and 'DOMDescendantRemovedFromDocument'? Wouldn't a single one, 'DOMDescendantRemovedFromDocument' (or, preferably, 'DOMDescendantRemoved'), work about as well? You already give a way to detect if it was a child or a descendant. Well, I'd start by asking what the rationale is for mutation events at all :) They seem to only solve the very simple cases where all parties that mutate the page cooperate nicely with each other and with the parties that listen to mutation events. But I would have expected that in those cases the parties that mutate the DOM could instead have notified the listening parties directly. But I digress :) Specifically the two new events correspond to the current DOMNodeRemoved and DOMNodeRemovedFromDocument, so the rationale is the same as for those old events. However I can only guess at that rationale is. DOMNodeRemoved is useful when you want to know when a node is removed from its parent. DOMNodeRemovedFromDocument is useful when you want to know when a node is no longer part of a document. The latter seems in general more useful, for example for keeping a TOC of headings in a page, or a list of clickable links. However the latter seems very complicated to implement without severely regressing performance any time there is a listener for the event. Whenever a node is removed you have to fire an event for each and every node in the removed subtree, presumably including attribute nodes. Also DOMNodeRemoved can be mostly used to emulate DOMNodeRemovedFromDocument by checking if the node you are interested in (i.e. the link or the heading) has the removed node in its parent chain. In mozilla we have never implemented DOMNodeRemovedFromDocument or DOMNodeInsertedIntoDocument due to its high cost. Likewise I doubt that we'll implement DOMDescendantRemovedFromDocument. I'm not sure what other vendors have done about the old event or feel about the new. I understand that having the distinction means that you could filter on the level of depth to fire events on, but I'm asking if this is useful and necessary. I take it you are asking under the general assumption that mutation events are useful at all? :) I generally think that DOMDescendantRemovedFromDocument is likely easier to use, but seems prohibitively expensive to implement correctly, whereas DOMChildRemoved seems to cover most use cases, though with a bit more effort on the side of the listener. Also note that all these events have a capture phase, so you can always attack your listener on an ancestor of the nodes you are interested in, such as the document node. * Specify *when* the events fire (see details below). We should do this regardless, since it is tightening up the spec, not changing it (though admittedly, it may force some implementations to change anyway... but that means more interop). * Deprecate the DOMNodeRemoved and DOMNodeRemovedFromDocument events. If this means making them optional or just discouraged I don't really care. I'd even be ok with simply leaving them in as is. Mozilla will simply remove our implementation of the DOMNodeRemoved event. We've never supported the DOMNodeRemovedFromDocument event. If Mozilla is determined to remove them regardless of what the spec says, then I would rather leave them in as specced, but deprecate them. Yes, given that someone pointed out that we can't really fire DOMNodeRemoved after the event takes place as that means that the parent chain is broken and so the event wouldn't reach any ancestors, I see no other alternative than dropping the event entirely. Do note that there is still nothing that defines when these events should fire. I.e. if you do
Re: [D3E] Possible Changes to Mutation Events
Kartikaya Gupta wrote: On Thu, 17 Jul 2008 17:51:42 -0700, Jonas Sicking [EMAIL PROTECTED] wrote: * Add a DOMDescendantRemovedFromDocument event which is fired on a node when the node is in a document, but any of nodes the descendants is removed from the document. The event is fired after the removal takes place. The relatedNode property contains the removed descendant. The relatedIndex property contains the index the child had immediately before the removal. (Should relatedIndex be -1 when the node wasn't removed from its parent, but rather an ancestor was?) From this description it seems bit ambiguous as to exactly which node is the target of the event, since there could be multiple nodes that satisfy the conditions (a) being attached to the document and (b) ancestor of the removed node. I assume you mean the node that satisfies the conditions that is deepest in the tree? And yes, I think relatedIndex should be -1 if an ancestor was the one that was removed, so that it's easier to distinguish between the roots of the removed subtrees and the non-roots. Good point. Yes, I meant the one deepest in the tree. Another way to put it is that the target is always the parent old of the root of the subtree that was removed. / Jonas
Re: [D3E] Possible Changes to Mutation Events
Hi, Jonas- Jonas Sicking wrote (on 7/18/08 2:51 PM): Well, I'd start by asking what the rationale is for mutation events at all :) They seem to only solve the very simple cases where all parties that mutate the page cooperate nicely with each other and with the parties that listen to mutation events. But I would have expected that in those cases the parties that mutate the DOM could instead have notified the listening parties directly. I admit I've used mutation events only sparingly in my own projects, so I'm not the most qualified to answer. However, many authors I've talked to or read from speak of them glowingly, so I will defer to the notion that they are useful. I know you deal a lot with the more complex mashup cases, but are those really the most common case? I suspect that even today, the very simple case where only one site is involved are the more common. Or does all parties also mean cases where people are using script frameworks like dojo? In mozilla we have never implemented DOMNodeRemovedFromDocument or DOMNodeInsertedIntoDocument due to its high cost. Likewise I doubt that we'll implement DOMDescendantRemovedFromDocument. I'm not sure what other vendors have done about the old event or feel about the new. I am very reluctant to specify something that browser vendors won't implement. Unless other vendors feel strongly about including it, or Mozilla changes their mind and decides to include it, I'm not going to waste everyone's time by adding this. The only reason to add a feature is to make sure that authors can use it interoperably, in my mind. As an author, I was (and am!) always bitter and disappointed when I read about *exactly* the feature I need in a spec, but it's not implemented at all, or not implemented interoperably enough to use. I have no intention of building up authors expectations like that if it will be futile. If possible, I prefer to keep looking for a solution that fits the use cases and will be widely implemented. But I want to resolve this soon. This specification is at the awkward point where it's too established to change it much, and too unstable to rely on it. That sucks for everyone. I understand that having the distinction means that you could filter on the level of depth to fire events on, but I'm asking if this is useful and necessary. I take it you are asking under the general assumption that mutation events are useful at all? :) That is the general tenor of the authors, who, along with users, are my chief constituents. Do note that there is still nothing that defines when these events should fire. I.e. if you do foo.textContent = '', does that fire a DOMNodeRemoved on all elements before any of them are removed, or does it fire the event on each of them as they are removed. Though it might not be needed to be defined if the events get deprecated anyway... No, I intend to define that regardless of whether or not we deprecate it. Regards- -Doug Schepers W3C Team Contact, WebApps, SVG, and CDF
Re: [D3E] Possible Changes to Mutation Events
Kartikaya Gupta wrote: On Thu, 17 Jul 2008 11:48:52 -0400, Boris Zbarsky [EMAIL PROTECTED] wrote: There are countless other implementations of MutationEvents out in the world (http://google.com/codesearch?hl=enlr=q=DOMNodeRemoved+-mozilla+-webcoresbtn=Search). They exist in more languages and are used in more contexts than I care to enumerate That's fine. How many of those contexts have to assume that all DOM access is malicious? More than zero, I think. There's at least one gtk implementation that (at a quick glance) would have to deal with potentially malicious users. And how well is gtk dealing with this? Has anyone done any extensive testing, such as fuzzing, to try to do evil things inside these mutation listeners? / Jonas
Re: [D3E] Possible Changes to Mutation Events
On Jul 16, 2008, at 10:33 PM, Kartikaya Gupta wrote: You could argue that this example is contrived (and it is), but I think it still illustrates the point. The current interleaving of mutations and events is bad for (some) implementations and good for web authors. Your proposed interleaving is good for (some) implementations and bad for web authors. In both cases it's for the same reason - being able to make assumptions simplifies code, so the side that gets to make those assumptions is better off, and the other side has to revalidate their assumptions. That would be a valid argument if mutation events were good for Web developers in the first place. But they are pretty hard to use either way, and generally are unused by most content. Like the Mozilla developers who have posted on this thread, I consider them a marginal- value feature. I also consider this entire problem to be more of an implementation detail than anything else. The current spec can pose a security risk if not properly implemented, but that's true of any spec. The security risk identified is only a problem on C/C++ implementations. Speaking as a Java implementor, I prefer the spec as it stands now. It is far easier to simpler for me to assume listeners don't mutate the DOM, and then catch any exceptions that get thrown when that assumption is violated. With the proposed changes, I would have to implement some complicated queuing solution that increases memory requirements dramatically. There's almost certainly some cases where such an approach will lead to wrong behavior instead of an exception. Since your implementation takes the ostrich approach, I don't consider this very strong evidence of the spec being easy to implement in any language. Regards, Maciej
Re: [D3E] Possible Changes to Mutation Events
Kartikaya Gupta wrote: On Wed, 16 Jul 2008 16:18:39 -0500, Jonas Sicking [EMAIL PROTECTED] wrote: Laurens Holst wrote: I see, so the motivation for the change request to DOMNodeRemoved is that the second change request (throwing events at the end, after all operations) is be impossible to do if events are not always thrown at the end. And the motivation for throwing events at the end seems to be for a specific kind of optimisation called âqueuing of eventsâ. I would appreciate if someone could describe this optimisation. Here is the problem we are struggling with is that the current design is very complex to implement. Any code we have that somewhere inside the code requires the DOM to be mutated mean that after the mutation we recheck all invariants after each mutation. This is because during the mutation a mutation event could have fired which has completely changed the world under us. These changes can be as severe as totally changing the structure of the whole DOM, navigating away to another webpage altogether and/or closing the current window. I understand your concerns, and while your proposed solution would solve your problem, it pushes this exact same burden onto web authors. Say we go ahead change the spec so that all the events are queued up and fired at the end of a compound operation. Now listeners that receive these events cannot be sure the DOM hasn't changed out from under *them* as part of a compound operation. In the case where there are multiple people listening to mutation events for a DOM, and occationally mutating the DOM during those listeners, mutation events are already useless. There is no way you can know that by the time you get the event is represents reality at all. The node you just got a remove-event for might already be inserted in exactly the same place again. If this isn't the case, i.e. where one person writes all listeners, or listeners don't mutate the DOM, then I don't see that we are pushing the problem onto authors. Yes, the DOM will look different by the time handler fires, but I don't see that it should significantly harder to deal with. Consider the following example: htmlbody style.lastLink { color: red }/style a href=http://example.org;i want to be the last link/a div id=emptyMe a href=http://example.org;example two/a a class=lastLink href=http://example.org;example three/a /div script type=text/javascript var numLinks = document.links.length; document.addEventListener( DOMNodeRemovedFromDocument, function(e) { if (e.target.nodeName == 'A') { // or e.relatedNode.nodeName as the case may be if (--numLinks 0) { document.links[ numLinks - 1 ].className = 'lastLink'; } } }, true ); /script /body/html The above would be trivial to rewrite to use document.links[document.links.length - 1].className = 'lastLink'; If you did something like document.getElementById('emptyMe').innerHTML = '' and considered it a compound operation, the code above, which works with current implementations, will die because numLinks will be out of sync with document.links.length, and the array indexing will fail. To avoid this scenario, the code has to be rewritten to re-query document.links.length instead of assuming numLinks will always be valid. This is exactly the same problem you're currently having - the DOM is changing under the code unexpectedly, forcing it to recheck assumptions. Note that there is nothing in the spec that says that this isn't already the case. For example, an implementation would be totally allowed to in the case of document.getElementById('emptyMe').innerHTML = ''; fire all DOMNodeRemovedFromDocument events before doing any mutations to the DOM. It could then do all removals while firing no events. This seems like it would break your code. The fact is, it would be extremely hard to define how implementations should behave in all the various specs that cause mutations to occur, if you also have to define exactly how to behave if mutation listeners mutate the DOM. If instead we instead allowed those specs to state what is a compound operation, it can allow the speced behavior to happen inside a compound operation, and then mutation listeners are dealt with afterwards. / Jonas
Re: [D3E] Possible Changes to Mutation Events
Doug Schepers wrote: Jonas proposes two substantive changes to this: * DOMNodeRemoved and DOMNodeRemovedFromDocument would be fired after the mutation rather than before * DOM operations that perform multiple sub-operations (such as moving an element) would be dispatched (in order of operation) after all the sub-operations are complete. So based on the feedback so far in this thread, here is a revised proposal from me, with the added feature that it's backwards compatible with DOM Events Level 2: * Add a |readonly attribute long relatedIndex;| property to the MutationEvent interface. * Add a DOMChildRemoved event which is fired on a node when one of its children is removed. The relatedNode property contains the removed child, and relatedIndex contains the index the child had immediately before the removal. The event is fired after the removal takes place. * Add a DOMDescendantRemovedFromDocument event which is fired on a node when the node is in a document, but any of nodes the descendants is removed from the document. The event is fired after the removal takes place. The relatedNode property contains the removed descendant. The relatedIndex property contains the index the child had immediately before the removal. (Should relatedIndex be -1 when the node wasn't removed from its parent, but rather an ancestor was?) * Specify *when* the events fire (see details below). * Deprecate the DOMNodeRemoved and DOMNodeRemovedFromDocument events. If this means making them optional or just discouraged I don't really care. I'd even be ok with simply leaving them in as is. Mozilla will simply remove our implementation of the DOMNodeRemoved event. We've never supported the DOMNodeRemovedFromDocument event. As for when the events fire (note that this is just clarifications of the spec, not changes to it): For events that fire after the mutation takes place I propose that we add a concept of a compound operation and state that while compound operations are in progress no mutation events are fired. Instead the events are queued up. After the outermost compound operation finishes, but before it returns control to the caller, the queue of mutation events is processed. A compound operation may itself contain several compound operations. For example parent.replaceChild(newChild, oldChild) can consist of a removal (removing newChild from its old parent) a second removal (removing oldChild from parent) and an insertion (inserting newChild into its new parent). Processing of the queue wouldn't start until after the outermost compound operation finishes. One important detail here is that processing of the queue starts *after* the compound operation finishes. This means that if a mutation listener causes another mutation to happen, this is considered a new compound operation. So if the mutation listener causes another mutation to happen, then the mutation events queued up during that compound operation fires before that compound operation returns. There are two ways to implement this: Either you move the currently queued events off of the queue before starting to process them. Or, when starting an outermost compound operation, remember what the current length of the queue is, and when finishing the operation, only process queued events added after that index. The whole point of this inner queuing is to allow mutations inside mutation listeners to behave like mutations outside them. So if code inside a mutation listeners calls .replaceChild, it can count on that the mutation listeners for that mutation has fired by the time replaceChild returns. What exactly constitutes a compound operation is left up to the specs that describe the operations. For example setting .innerHTML should likely constitute a compound operation. For DOM Core, every function that mutates the DOM should be considered a compound operation. This all does sound a bit complicated. However the same would be true for any sufficiently detailed specification of how mutation events behave. As stated, the current wording of the spec allows for wildly different and useless firing logic. The only thing that we could somewhat simplify would be to not use separate queues for mutations inside mutation listeners. However the simplification would be pretty marginal, and I think it would be confusing that mutations inside mutation listeners wouldn't cause events to fire until after all other pending events had fired. / Jonas
Re: [D3E] Possible Changes to Mutation Events
Hi, Jonas- Thanks for this modified proposal. I want to hear back from those who've already commented as to their disposition, and to solicit comments from other known implementors (e.g., gtk, BitFlash, Opera, JSR), but I think your proposal is reasonable, and well detailed. A few comments inline... Jonas Sicking wrote (on 7/17/08 8:51 PM): * Add a |readonly attribute long relatedIndex;| property to the MutationEvent interface. * Add a DOMChildRemoved event which is fired on a node when one of its children is removed. The relatedNode property contains the removed child, and relatedIndex contains the index the child had immediately before the removal. The event is fired after the removal takes place. * Add a DOMDescendantRemovedFromDocument event which is fired on a node when the node is in a document, but any of nodes the descendants is removed from the document. The event is fired after the removal takes place. The relatedNode property contains the removed descendant. The relatedIndex property contains the index the child had immediately before the removal. (Should relatedIndex be -1 when the node wasn't removed from its parent, but rather an ancestor was?) What is the rationale for having both 'DOMChildRemoved' and 'DOMDescendantRemovedFromDocument'? Wouldn't a single one, 'DOMDescendantRemovedFromDocument' (or, preferably, 'DOMDescendantRemoved'), work about as well? You already give a way to detect if it was a child or a descendant. I understand that having the distinction means that you could filter on the level of depth to fire events on, but I'm asking if this is useful and necessary. * Specify *when* the events fire (see details below). We should do this regardless, since it is tightening up the spec, not changing it (though admittedly, it may force some implementations to change anyway... but that means more interop). * Deprecate the DOMNodeRemoved and DOMNodeRemovedFromDocument events. If this means making them optional or just discouraged I don't really care. I'd even be ok with simply leaving them in as is. Mozilla will simply remove our implementation of the DOMNodeRemoved event. We've never supported the DOMNodeRemovedFromDocument event. If Mozilla is determined to remove them regardless of what the spec says, then I would rather leave them in as specced, but deprecate them. It sounds like we will have alignment from Mozilla and Safari/WebKit, and I'm sure that IE would rather implement this simpler version. I imagine that Opera would join in the fun. Hopefully others would align as well. Then we would have interoperable mutation events in all 4 major desktop browsers, decreasing the need for script library compensation. A script lib could still provide DOMNodeRemoved and DOMNodeRemovedFromDocument as patches for older code, it seems. As for when the events fire (note that this is just clarifications of the spec, not changes to it): For events that fire after the mutation takes place I propose that we add a concept of a compound operation and state that while compound operations are in progress no mutation events are fired. Instead the events are queued up. After the outermost compound operation finishes, but before it returns control to the caller, the queue of mutation events is processed. I really like the concept of the compound operation, and I will add that in regardless of other conclusions. I will specify that a host language should indicate when an operation is a compound operation, and describe the precise order of operations. (See next comment for details.) A compound operation may itself contain several compound operations. For example parent.replaceChild(newChild, oldChild) can consist of a removal (removing newChild from its old parent) a second removal (removing oldChild from parent) and an insertion (inserting newChild into its new parent). Processing of the queue wouldn't start until after the outermost compound operation finishes. This actually has at least 3 variant orders of operation (assuming the removal of newChild, which may not be in the document at all yet): * remove newChild, remove oldChild, insert newChild * remove newChild, insert newChild, remove oldChild * remove oldChild, remove newChild, insert newChild All of these are possible, and DOM3 Core doesn't indicate an order. So this adds to the unpredictability of dealing with any compound operation, including mutation events. Requiring a spec to state the order would help authors. What exactly constitutes a compound operation is left up to the specs that describe the operations. For example setting .innerHTML should likely constitute a compound operation. For DOM Core, every function that mutates the DOM should be considered a compound operation. Maybe a DOM3 Core 2nd edition, or DOM4 Core, could provide these stricter event orders for compound operations. Regards- -Doug Schepers
Re: [D3E] Possible Changes to Mutation Events
Hi Doug, Doug Schepers schreef: Sergey Ilinsky wrote (on 7/15/08 6:39 AM): Doug Schepers wrote: 1. DOMNodeRemoved and DOMNodeRemovedFromDocument would be fired after the mutation rather than before 2. DOM operations that perform multiple sub-operations (such as moving an element) would be dispatched (in order of operation) after all the sub-operations are complete. General concerns: 1) Clearly defined use cases seem to be missing from the proposal, would it be possible to bring them all to the table? That's a reasonable request I think that Jonas and Maciej described some of the use cases (from an implementor's point of view) in their discussion: 1. optimizing based on queuing of events 2. reduction of code 3. consistency and predictability of behavior 4. interoperability on the issue of when the events fire (currently the spec says, Many single modifications of the tree can cause multiple mutation events to be dispatched. Rather than attempt to specify the ordering of mutation events due to every possible modification of the tree, the ordering of these events is left to the implementation. [1]) I see, so the motivation for the change request to DOMNodeRemoved is that the second change request (throwing events at the end, after all operations) is be impossible to do if events are not always thrown at the end. And the motivation for throwing events at the end seems to be for a specific kind of optimisation called ‘queuing of events’. I would appreciate if someone could describe this optimisation. Even ignoring the serious backwards compatibility issues that Sergey described, I do not think this is a good idea. By defining that all events have to be fired at the end of the operation, e.g. Document.renameNode can never be implemented by just calling existing DOM operations; the implementation would need to call some internal event-less version of the methods (e.g. removeNodeNoEvent()). It seems to me that such a definition would possibly make implementations more complex if not impossible (in case the implementation provides no access to events-less methods), and put more constraints on the underlying implementation, as the implementation would now be required to throw the events separately from the actual operations (which I do not think would be good design). To provide a more concrete example, a DOM (e.g. the Mozilla DOM) could never be extended with a custom document.renameNode method that performs the operations as described in the DOM level 3 specification, because the events would fire too soon. Not that Backbase implements events like this, by the way. With regard to the DOMNodeRemoved change request, I do not think these arguments apply. It is doubtful that moving the dispatching of the event to another place will reduce code (2), there is a sensible reason for why some events fire before and some fire after the action (3), and the spec clearly defines how the event should work (4). I don’t really understand exactly what kind of optimisation (1) entails, but I think it would not matter for ‘event queueing’ if the event was fired before or after the action. To expand a little more on item 3; I do not think the ‘consistency’ argument really applies here, because there are plenty of events that fire before the actual action takes place (e.g. all cancelable events). Now of all 7 mutation events, only two are fired before the action, so it is true the majority of events fires after. However they do this because it makes sense for their specific operations, not because of some unwritten rule that events always have to be fired after the action. By the way, note that Backbase is also looking at this from an implementor’s point of view :). It would be nice to have more use case outlined. This knife cuts both ways, of course. Can you cite some cases (preferably in use today) that rely on keeping things the way they are? In our product we have several controls that either access the parentNode property (which would no longer be accessible) or have an event listener for DOMNodeRemoved on their parent. Also, it is fair to assume that there will be several customer projects expecting the currently defined functionality. It is not so much that with the requested changes the desired functionality could not be achieved (although DOMNodeRemovedFromDocument would need to provide the relatedNode property, and we would have to attach DOMNodeRemoved event handlers to all child nodes instead of their parent). The objection is rather that backwards compatibility with a REC from 2000 is broken, for reasons that remains largely unclear. The defined behaviour of DOMNodeRemoved makes sense, and is useful. 2) The changes contradict with DOM-Level-2 Events where Mutation was initially defined (back in the year 2000) thus creating backwards incompatible behavior Not necessarily. It has the potential to create backwards-incompatibility,
Re: [D3E] Possible Changes to Mutation Events
Hi, Grauw- First, I want to note that I'm just collecting data here... I can see both sides of the argument on this particular issue, so as editor, I'm happy to specify this however it seems most beneficial to authors and implementors (in that order). I appreciate all the good feedback everyone is supplying. I realize that by relaying and summarizing Jonas' and Maciej's request, I may seem to have already aligned myself with a side here, but any defense I'm making is as an advocate for the authors and for the devil (not necessarily in that order). So, I will leave it up to Jonas and Maciej to present counter-arguments. Just a couple comments inline... Laurens Holst wrote (on 7/16/08 9:36 AM): To expand a little more on item 3; I do not think the ‘consistency’ argument really applies here, because there are plenty of events that fire before the actual action takes place (e.g. all cancelable events). Now of all 7 mutation events, only two are fired before the action, so it is true the majority of events fires after. However they do this because it makes sense for their specific operations, not because of some unwritten rule that events always have to be fired after the action. The consistency and predictability I was referring to was not alignment with the other mutation events firing after the operation, but rather with: a) The bit in DOM3 Events that says, Rather than attempt to specify the ordering of mutation events due to every possible modification of the tree, the ordering of these events is left to the implementation. b) predictability that operations will not end up with surprising results regarding the tree structure as a result of complex operations. By the way, note that Backbase is also looking at this from an implementor’s point of view :). Yes, it seems Backbase would be considered both an implementor and an author (that is, you implement the functionality of the spec, and provide content on top of that functionality). It would be interesting so see an examination of the different needs and markets for desktop browser vendors, mobile browser vendors, and script library vendors, with regards to reliance on underlying platforms and ability to push changes out into deployed code. I took a look through the Backbase demo... very impressive stuff. Much of it was even accessible and navigable by keyboard control, which was even nicer. Not necessarily. It has the potential to create backwards-incompatibility, but only if there are scripts and implementations that rely on the specific details that are proposed for change, i.e. that depend on the DOMNodeRemoved and DOMNodeRemovedFromDocument events firing before removal. If the script only relies on knowing that a node was removed, and doesn't care much when, then it's not actually a problem. The differences are pretty big, you shouldn’t downplay them as being ‘details’. I’m sure things will break. I think this is a very bad idea. I didn't mean details in a pejorative sense, but rather as a contrast to a more drastic change, such as removing mutation events altogether. The point was that certain aspects of the events could possibly be changed without affecting existing applications or script code; you have evidence that this is not the case here, but without knowing the details of that, it was hard to judge. The devil is in the details, after all... and specifications are, let's face is, all about details. It is certain. Some examples of what would break in our product: Okay, thanks for the details, especially regarding what remedial steps you'd have to take. I disagree with your suggestion that the event as it is currently specified would be “difficult to implement” or “unpredictable to use”. I was really trying to characterize Jonas' stance, and may not have been successful at it. I invite him to provide more details. I do not care so much about backwards compatibility with earlier revisions of the DOM level 3 spec (although I hope there won’t be really big changes :)), however this concerns compatibility with DOM level 2 which has been a REC since 2000. As far as I know (and if Appendix B: Changes is not omitting anything), DOM level 3 has so far not introduced any backwards incompatibilities with DOM level 2. Doing this would set a very bad precedent. None of the other changes currently being considered would affect compatibility with DOM2... most of them have to do with Key Identifiers and IMEs, new events (e.g. wheel, mousewheel, dblclick), event order, and default actions (off the top of my head). I'm glad that you are paying attention, and look forward to your feedback on these other issues, too. I think you should be very, very reluctant to break backwards compatibility with an 8-year old REC. The DOM specifications are a core part of XML technologies, and if those standardised core technologies can not be trusted to be compatible
Re: [D3E] Possible Changes to Mutation Events
Laurens Holst wrote: I see, so the motivation for the change request to DOMNodeRemoved is that the second change request (throwing events at the end, after all operations) is be impossible to do if events are not always thrown at the end. And the motivation for throwing events at the end seems to be for a specific kind of optimisation called ‘queuing of events’. It's not quite an optimization. At heart, it's a security requirement: untrusted script must not be run while data structures are in an inconsistent state. It can be worked around by adding a lot more code to make sure things are in a consistent state at all times during, say, a DocumentFragment insertion, which is what some implementations have been doing. But this greatly increases complexity and reduces performance of cases that are commonly used on the Web (innerHTML). By defining that all events have to be fired at the end of the operation, e.g. Document.renameNode can never be implemented by just calling existing DOM operations This is a serious concern, yes. On the other hand, that's already the case in a lot of situations with the DOM because of the way ranges work. You end up having to call notification-less versions of basic methods... With regard to the DOMNodeRemoved change request, I do not think these arguments apply. It is doubtful that moving the dispatching of the event to another place will reduce code I can guarantee you that it would reduce code and improve security in Gecko. there is a sensible reason for why some events fire before and some fire after the action Agreed, but in practice this causes some problems. If it's possible to address the problems while still addressing the mutation event use cases, that would be great. In our product we have several controls that either access the parentNode property (which would no longer be accessible) I should note that being able to use parentNode like this assumes that you are the only one listening for the mutation event. That might be a good assumption in your project, of course... or have an event listener for DOMNodeRemoved on their parent. Whatever we do, this case certainly needs to work, in my opinion. Firing DOMNodeRemoved on the node that actually got removed is not as important as this. Again, in my opinion. -Boris
Re: [D3E] Possible Changes to Mutation Events
On Jul 16, 2008, at 6:36 AM, Laurens Holst wrote: Hi Doug, Doug Schepers schreef: Sergey Ilinsky wrote (on 7/15/08 6:39 AM): Doug Schepers wrote: 1. DOMNodeRemoved and DOMNodeRemovedFromDocument would be fired after the mutation rather than before 2. DOM operations that perform multiple sub-operations (such as moving an element) would be dispatched (in order of operation) after all the sub-operations are complete. General concerns: 1) Clearly defined use cases seem to be missing from the proposal, would it be possible to bring them all to the table? That's a reasonable request I think that Jonas and Maciej described some of the use cases (from an implementor's point of view) in their discussion: 1. optimizing based on queuing of events 2. reduction of code 3. consistency and predictability of behavior 4. interoperability on the issue of when the events fire (currently the spec says, Many single modifications of the tree can cause multiple mutation events to be dispatched. Rather than attempt to specify the ordering of mutation events due to every possible modification of the tree, the ordering of these events is left to the implementation. [1]) I see, so the motivation for the change request to DOMNodeRemoved is that the second change request (throwing events at the end, after all operations) is be impossible to do if events are not always thrown at the end. And the motivation for throwing events at the end seems to be for a specific kind of optimisation called ‘queuing of events’. I would appreciate if someone could describe this optimisation. The purpose is not optimization, but rather reducing code complexity and risk. DOM mutation events can make arbitrary changes to the DOM, including ones that may invalidate the rest of the operation. Let's say you call parent.replaceChild(old, new). If the DOMNodeRemoved notification is fired before the removal of old, or even between the removal and the insertion, it might remove old from parent and moved elsewhere in the document. The remove notification for new (if it already had a parent) could also move old, or new, or parent. There's no particularly valid reason to do this, but Web-facing implementations must be robust in the face of broken or malicious code. This means that at every stage of a multistep operation, the implementation has to recheck its assumptions. In WebKit and Gecko, the code for many of the basic DOM operations often is more than 50% code to dispatch mutation events, re-check assumptions and abort if needed. Dispatching mutation events at the end of a compound operation doesn't have this problem - there is no need to re-check assumptions because the operation is complete. Even ignoring the serious backwards compatibility issues that Sergey described, I do not think this is a good idea. By defining that all events have to be fired at the end of the operation, e.g. Document.renameNode can never be implemented by just calling existing DOM operations; the implementation would need to call some internal event-less version of the methods (e.g. removeNodeNoEvent()). First of all, this is not a big deal for implementations. Second, it seems to me this is true whether removeNode fires the event first or last. It seems to me that such a definition would possibly make implementations more complex if not impossible (in case the implementation provides no access to events-less methods), and put more constraints on the underlying implementation, as the implementation would now be required to throw the events separately from the actual operations (which I do not think would be good design). No, it would make implementations much simpler by removing all the code that handles the very unlikely case of the mutation event listener modifying the DOM in a way that invalidates the operation. I know for sure this is the case for WebKit's DOM implementation, and Mozilla folks have told me the same is true for Gecko. [...snip...] I do not care so much about backwards compatibility with earlier revisions of the DOM level 3 spec (although I hope there won’t be really big changes :)), however this concerns compatibility with DOM level 2 which has been a REC since 2000. As far as I know (and if Appendix B: Changes is not omitting anything), DOM level 3 has so far not introduced any backwards incompatibilities with DOM level 2. Doing this would set a very bad precedent. If you introduce incompatible behaviour with regard to DOM level 2, there is no way to prevent existing applications from breaking either, because DOM does not provide a version mechanism that knows an older version is expected and could provide backwards compatible behaviour. And either way, I think having to branch code (or worse, providing different implementations) based on version is undesirable. I think you should be very, very reluctant to break
Re: [D3E] Possible Changes to Mutation Events
Doug Schepers wrote: * DOMNodeRemoved and DOMNodeRemovedFromDocument would be fired after the mutation rather than before * DOM operations that perform multiple sub-operations (such as moving an element) would be dispatched (in order of operation) after all the sub-operations are complete. General concerns: 1) Clearly defined use cases seem to be missing from the proposal, would it be possible to bring them all to the table? 2) The changes contradict with DOM-Level-2 Events where Mutation was initially defined (back in the year 2000) thus creating backwards incompatible behavior Specific concerns: 1) If DOMNodeRemovedFromDocument is fired after the mutation, then in the listener for this event there is no way to know where Node was removed from. (This does not apply to DOMNodeRemoved, since it has a relatedNode property pointing to node removed) 2) If DOMNodeRemoved is fired after the mutation, event won't be capable of bubbling (I did not yet dig into any specific functionality that depends on the present behavior (and potential change) of the events in subject (for sure lots of stuff would break)) Tech Lead at Backbase, Sergey Ilinsky/ P.S. Backbase Ajax Framework contains an implementation of DOM-Events (Level-3) module (as well as other DOM modules) and it is dependent on the present behavior.
Re: [D3E] Possible Changes to Mutation Events
On Tue, 15 Jul 2008 12:39:16 +0200, Sergey Ilinsky [EMAIL PROTECTED] wrote: Specific concerns: 1) If DOMNodeRemovedFromDocument is fired after the mutation, then in the listener for this event there is no way to know where Node was removed from. (This does not apply to DOMNodeRemoved, since it has a relatedNode property pointing to node removed) 2) If DOMNodeRemoved is fired after the mutation, event won't be capable of bubbling +1. If you make these events fire after the mutation, then they won't go anywhere since the target of the event (the removed node) is free-standing. In order to listen for these events, you'd have to register a listener on every single DOM node, which seems pretty silly. Even if you changed one or both of the events to fire on the parent node, it would still be impossible to determine exactly where the removed node was removed from (i.e. what was the removed node's .nextSibling before it was removed?) The same problem applies to batching up mutations and firing them at the end; there might be operations whose mutation events get discarded because some other mutation happens to the tree before the event is fired. kats
Re: [D3E] Possible Changes to Mutation Events
Sergey Ilinsky wrote: 1) Clearly defined use cases seem to be missing from the proposal, would it be possible to bring them all to the table? It's more a matter of sanity of implementation than anything else, for what it's worth. 2) The changes contradict with DOM-Level-2 Events where Mutation was initially defined (back in the year 2000) thus creating backwards incompatible behavior Yes. The whole proposal is to change the Events specification. 1) If DOMNodeRemovedFromDocument is fired after the mutation, then in the listener for this event there is no way to know where Node was removed from. (This does not apply to DOMNodeRemoved, since it has a relatedNode property pointing to node removed) Should DOMNodeRemovedFromDocument have a relatedNode too? 2) If DOMNodeRemoved is fired after the mutation, event won't be capable of bubbling This is a much more serious issue. To be honest, I think the original mutation event design was pretty weird. I would have fired the event on the parent, with the relatedNode being the node being removed or added... That would avoid this issue. Of course that would be inconsistent with the way DOMNodeInserted works. -Boris
Re: [D3E] Possible Changes to Mutation Events
On Tue, 15 Jul 2008 11:20:17 -0400, Boris Zbarsky [EMAIL PROTECTED] wrote: Kartikaya Gupta wrote: The same problem applies to batching up mutations and firing them at the end; there might be operations whose mutation events get discarded because some other mutation happens to the tree before the event is fired. I'm not sure I follow this. What exactly is the problem here? Sorry, I misread the original thread and misunderstood the scope of the change. Never mind.
Re: [D3E] Possible Changes to Mutation Events
Boris Zbarsky wrote: 1) If DOMNodeRemovedFromDocument is fired after the mutation, then in the listener for this event there is no way to know where Node was removed from. (This does not apply to DOMNodeRemoved, since it has a relatedNode property pointing to node removed) Should DOMNodeRemovedFromDocument have a relatedNode too? A single relatedNode property would not be enough to satisfy developer's curiosity on where exactly node was removed from. 2) If DOMNodeRemoved is fired after the mutation, event won't be capable of bubbling This is a much more serious issue. To be honest, I think the original mutation event design was pretty weird. I would have fired the event on the parent, with the relatedNode being the node being removed or added... That would avoid this issue. Of course that would be inconsistent with the way DOMNodeInserted works. Although I do not share negative attitude to the design of Mutation module, I know there are indeed use cases that are not well satisfied with it. The bad part about the potential to changing something in Mutation Module is that it is almost not possible to say a there without having to say b - the design is very much tight and consistent. -Boris Sergey Ilinsky/
Re: [D3E] Possible Changes to Mutation Events
Hi, Sergey- Thanks for your prompt and informative feedback! Sergey Ilinsky wrote (on 7/15/08 6:39 AM): Doug Schepers wrote: * DOMNodeRemoved and DOMNodeRemovedFromDocument would be fired after the mutation rather than before * DOM operations that perform multiple sub-operations (such as moving an element) would be dispatched (in order of operation) after all the sub-operations are complete. General concerns: 1) Clearly defined use cases seem to be missing from the proposal, would it be possible to bring them all to the table? That's a reasonable request I think that Jonas and Maciej described some of the use cases (from an implementor's point of view) in their discussion: * optimizing based on queuing of events * reduction of code * consistency and predictability of behavior * interoperability on the issue of when the events fire (currently the spec says, Many single modifications of the tree can cause multiple mutation events to be dispatched. Rather than attempt to specify the ordering of mutation events due to every possible modification of the tree, the ordering of these events is left to the implementation. [1]) It would be nice to have more use case outlined. This knife cuts both ways, of course. Can you cite some cases (preferably in use today) that rely on keeping things the way they are? 2) The changes contradict with DOM-Level-2 Events where Mutation was initially defined (back in the year 2000) thus creating backwards incompatible behavior Not necessarily. It has the potential to create backwards-incompatibility, but only if there are scripts and implementations that rely on the specific details that are proposed for change, i.e. that depend on the DOMNodeRemoved and DOMNodeRemovedFromDocument events firing before removal. If the script only relies on knowing that a node was removed, and doesn't care much when, then it's not actually a problem. Specific concerns: 1) If DOMNodeRemovedFromDocument is fired after the mutation, then in the listener for this event there is no way to know where Node was removed from. (This does not apply to DOMNodeRemoved, since it has a relatedNode property pointing to node removed) 2) If DOMNodeRemoved is fired after the mutation, event won't be capable of bubbling I don't believe that is correct. DOM3 Event states: At the beginning of the dispatch, implementations must first determine the event object's propagation path. This is an ordered list of event targets the object may propagate to. The last item in the list is the event's target; the preceding items in the list are referred to as the target's ancestors and the immediately preceding item as the target's parent. Once determined, the propagation path cannot be changed. As an example, in the DOM event flow event listeners might change the position of the target node in the document while the event object is being dispatched; such changes do not affect the propagation path. [2] You could simply add another event listener on the target element's parent (or other relevant ancestor) to find the tree context, if necessary. A use case demonstrating that this wouldn't suffice would weigh in favor of not changing it. (I did not yet dig into any specific functionality that depends on the present behavior (and potential change) of the events in subject (for sure lots of stuff would break)) Are you certain about that? I don't want stuff to break (especially in an irreparable way), but neither do I want to forestall making a change to the spec that would be easier to implement and more predictable to use, based on supposition of problems. It would be very useful if you could point out what would break, and how. P.S. Backbase Ajax Framework contains an implementation of DOM-Events (Level-3) module (as well as other DOM modules) and it is dependent on the present behavior. Can you please confirm that the behavior your implementation relies on would actually be negatively affected by the particular changes detailed? Please consider that this may actually have a positive impact on your implementation's performance and maintainability. Detailed comments would be helpful. I will note that other changes are also being made to the specification, and while we are trying to be as backwards compatible with earlier revisions of the spec as possible, we also place a high priority on the spec meeting its use cases and requirements. I'm sensitive that this spec is too long in being finished, and that implementations reasonably have relied on it despite its status, but it's a balancing act. [1] http://www.w3.org/TR/DOM-Level-3-Events/events.html#Events-eventgroupings-mutationevents [2] http://www.w3.org/TR/DOM-Level-3-Events/events.html#Events-flow Regards- -Doug Schepers W3C Team Contact, WebApps, SVG, and CDF
Re: [D3E] Possible Changes to Mutation Events
Doug Schepers wrote: 2) If DOMNodeRemoved is fired after the mutation, event won't be capable of bubbling I don't believe that is correct. DOM3 Event states: At the beginning of the dispatch, implementations must first determine the event object's propagation path. I think Sergey's point was that if dispatch happens after the removal, and the target is the node being removed, this path will have nothing else in it. -Boris