Re: DOM Mutation Events Replacement: When to deliver mutations
On 08/11/2011 03:44 AM, Rafael Weinstein wrote: Although everyone seems to agree that mutations should be delivered after the DOM operations which generated them complete, the question remains: When, exactly, should mutations be delivered? The four options I'm aware of are: 1) Immediately - i.e. while the operation is underway. [Note: This is how current DOM Mutation events work]. 2) Upon completion of the outer-most DOM operation. i.e. Immediately before a the lowest-on-the-stack DOM operation returns, but after it has done all of its work. 3) At the end of the current Task. i.e. immediately before the UA is about to fetch a new Task to run. 4) Scheduled as a future Task. i.e. fully async. --- Discussion: Options 1 4 are don't seem to have any proponents that I know of, so briefly: Option 1, Immediately: Pro: -It's conceptually the easiest thing to understand. The following *always* hold: -For calling code: When any DOM operation I make completes, all observers will have run. -For notified code: If I'm being called, the operation which caused this is below me on the stack. Con: -Because mutations must be delivered for some DOM operations before the operation is complete, UAs must tolerate all ways in which script may invalidate their assumptions before they do further work. Option 4, Scheduled as a future Task: Pro: -Conceptually easy to understand -Easy to implement. Con: -It's too late. Most use cases for mutation observation require that observers run before a paint occurs. E.g. a widget library which watches for special attributes. Script may create adiv class=FooButton and an observer will react to this by decorating the div as a FooButton. It is unacceptable (creates visual artifacts/flickering) to have the div be painted before the widget library has decorated it as a FooButton. Both of these options appear to be non-starters. Option 1 has been shown by experience to be an unreasonable implementation burden for UAs. Option 4 clearly doesn't handle properly important use cases. When I proposed watchSelector [2], the idea was clearly to propose an option that provided semantics similar to Option 4 as described here. The primary benefits I sought were: Pros: * batching - allow a script to operate on the DOM's cumulative changes, vs. incremental changes. * filtering - provide a well-known mechanism for quickly and precisely identifying the nodes in the document that should be observed. * performance - fully async has the potential to very fast to implement I think the filtering benefit could be extended to either Option 2 or 3. I prefer Option 3 because if offer a larger opportunity for batching. Cons: * See previously stated use case as argument against this approach * The approach didn't account for node movement within the document (reparenting of elements). * The approach (using Selectors) was deemed too risky because web developers can provide complex selectors that make running the mutation detection algorithm expensive for the UA. I still like the idea of using selectors, and after a conversation with Jonas, I realized that the perf concern can be eliminated yet preserve the filtering benefit by only allowing what CSS calls simple selectors [3], which are basically attribute/class/id selectors w/out combinators. Text mutations are generally in an entirely different category (primarily editing scenarios), and obviously won't work with Selectors. Additionally, the context information for text nodes is different than for elements, which leads me to believe that for text mutations, some different API may be necessary. --- Options 2 3 have proponents. Since I'm one of them (a proponent), I'll just summarize the main *pro* arguments for each and invite those who wish (including myself), to weigh in with further support or criticism in follow-on emails. Option 2: Upon completion of the outer-most DOM operation. Pro: -It's conceptually close to fully synchronous. For simple uses (specifically, setting aside the case of making DOM operations within a mutation callback), it has the advantages of Option 1, without its disadvantages. Because of this, it's similar to the behavior of current Mutation Events. Pros: * May be very easy to migrate existing sites/apps from current mutation events code to this option. If the message of the new mutation events model we create is we finally fixed mutation events, please use this new code instead then the easier we make the transition to the new model the better. However, in my experience websites and apps don't just migrate because the new model is available. Web sites generally follow the pattern: if it isn't broke, don't fix it. To me, this means that classic Mutation events aren't going away, and unless the new model provides significant advantages over the old model, legacy code just won't update. It's really the new code that we're
Re: DOM Mutation Events Replacement: Findings from implementing net-effect projections
On Wed, Aug 17, 2011 at 3:17 AM, Olli Pettay olli.pet...@helsinki.fiwrote: On 08/17/2011 04:54 AM, Rafael Weinstein wrote: TL;DR; 1) ObserveSubtree semantics doesn't provide a robust mechanism for observing a tree/fragment, and if we don't provide something more complete, libraries will likely register observers at every node in the document. ModificationBatch approach should provide as much information as current mutation events. No, the problem is that the list of mutations is given asynchronously in some cases. Say you have the following DOM tree: body div span class=special #text('hello') and div is removed. At this point, the list of mutations we have is: (ChildlistChanged, body, div) where div has the subtree: span class=special #text('hello') If observers (e.g. of widget library) wanted to detect whether any span with the special classname has been removed from the document, it can do so when it receives this list of mutations in this case. But now suppose that another observer of this mutation list decides to remove span from div's child list before my observer sees the list of mutations. Oops! I don't have any idea whether span was removed or not because all I get to see is (ChildlistChanged, body, div) and div doesn't have any children. Sure, this issue is prevalent in the existing mutation events but it's a good use case to address. In fact, providing a way to work-around this issue (by providing means to watch all nodes with the same owner document) might provide a strong incentive for authors to start using our new API. But is that a common enough case which the API needs to handle. I would think that a script library which wants to handle such case, can just use the API to observe everything. Recall the distributed editing use cases pointed by Dave Raggett for example: http://lists.w3.org/Archives/Public/public-webapps/2011JulSep/0416.html http://lists.w3.org/Archives/Public/public-webapps/2011JulSep/0381.html - Ryosuke
Re: DOM Mutation Events Replacement: Findings from implementing net-effect projections
On 08/17/2011 04:54 AM, Rafael Weinstein wrote: TL;DR; 1) ObserveSubtree semantics doesn't provide a robust mechanism for observing a tree/fragment, and if we don't provide something more complete, libraries will likely register observers at every node in the document. ModificationBatch approach should provide as much information as current mutation events. 2) Not providing position information (e.g childlistIndex) in ChildlistChanged mutations means that the algorithmic complexity of computing whether/where nodes have moved to doesn't improve vs. having to bruce-force compute. Yeah, I think we really to provide some position information. Adding index is easy (although it can be slow in some implementations, but that is implementation specific thing) I'll add this to ModificationBatch. 3) Not providing lastValue for text changes (attribute, textNode, etc...) may adversely impact some use cases. I agree. --- Following up on points 6 7 from http://lists.w3.org/Archives/Public/public-webapps/2011JulSep/0779.html ... I've prototyped the main net-effect projections that I believe various use cases will need. I've published to code to this public github repository: https://github.com/rafaelw/MutationProjections I encourage anyone interested to check my work and make corrections and/or suggestions, or create a branch that experiments in any direction they see fit. --- Per point (3) in the email above, we've concluded that mutations should be delivered in batches, since any number of mutations can have taken place since an observer was notified. Also, though it varies, there's generally a hierarchy of expense for types of operations done from JS: reads/writes on pure JS data DOM Reads DOM Writes External I/O Often, an application is willing to do a some amount of work of a less expensive operation to avoid doing any work of a more expensive operation. Thus, a mutation observer is highly motivated, for both correctness and performance, to answer the question: What is the net-effect of some set of DOM changes that have taken place since I last got to run? --- Depending on the use case, what happened likely means some combination of the following: 1) Reachability: What nodes were added to or removed from the document? 2) Matching: What nodes stopped or started matching a given pattern? This could be arbitrarily complex, but in the use cases discussed, this usually means a simple selector. 3) Parent changes: What nodes are now children of a new parent node? 4) Movement: What nodes moved to a new position in the document? 5) Value change: What character/text or attribute nodes changed value (for attributes, also, whether the attribute was added or removed)? Note that none of the above requires any mechanism of mutation observation. All can be computed given the opportunity to inspect the DOM at two points in time. However, without any information about what happened, the expense of answering each of the above questions is roughly proportional to the number of nodes in the document. All are roughly linear, except (4) Movement, which is roughly quadratic. It seems to me that a good goal for any mutation event replacement should be that the expense of answering the above projections be proportional the number of mutations which actually took place. I've implemented the null case (lacking any mutation information) for 1, 3 4 here: https://github.com/rafaelw/MutationProjections/blob/master/projections.js#L39 And the same projections, but taking mutation records to improve performance here: https://github.com/rafaelw/MutationProjections/blob/master/projections.js#L240 --- Observing sets of nodes In order to implement the above projections, I prototyped: Document.observeOwnedNodes I'm not proposing this (yet), but I think it is conceptually the most general case (btw, to give credit to Jonas, he pointed out that this might be solution to the problem I'm about to describe, though I'm pretty sure he meant sometime down the road). I'm not quite sure what all the Document.observeOwnedNodes approach tries to observe. I assume it should be observing everything, all the changes to child nodes and also changes to attributes. (currently it handles only elements) Is that really needed? The problem is that observeSubtree doesn't report anything about what happened to a node that was removed from the document, modified, then returned to the document. Consider the following case: -Register a subtreeObserver at the root of the document -Some other code removes an element, appends a new element to it, modifies an attribute, then returns the node to the document. Now, in order to properly compute the above projections, we are nearly back to the null case (matching becomes proportional the number of descendants of added/removed nodes that we heard about, but everything else returns to being proportional to the number of nodes in the
Re: DOM Mutation Events Replacement: Findings from implementing net-effect projections
On Wed, Aug 17, 2011 at 3:17 AM, Olli Pettay olli.pet...@helsinki.fi wrote: On 08/17/2011 04:54 AM, Rafael Weinstein wrote: TL;DR; 1) ObserveSubtree semantics doesn't provide a robust mechanism for observing a tree/fragment, and if we don't provide something more complete, libraries will likely register observers at every node in the document. ModificationBatch approach should provide as much information as current mutation events. 2) Not providing position information (e.g childlistIndex) in ChildlistChanged mutations means that the algorithmic complexity of computing whether/where nodes have moved to doesn't improve vs. having to bruce-force compute. Yeah, I think we really to provide some position information. Adding index is easy (although it can be slow in some implementations, but that is implementation specific thing) I'll add this to ModificationBatch. 3) Not providing lastValue for text changes (attribute, textNode, etc...) may adversely impact some use cases. I agree. --- Following up on points 6 7 from http://lists.w3.org/Archives/Public/public-webapps/2011JulSep/0779.html ... I've prototyped the main net-effect projections that I believe various use cases will need. I've published to code to this public github repository: https://github.com/rafaelw/MutationProjections I encourage anyone interested to check my work and make corrections and/or suggestions, or create a branch that experiments in any direction they see fit. --- Per point (3) in the email above, we've concluded that mutations should be delivered in batches, since any number of mutations can have taken place since an observer was notified. Also, though it varies, there's generally a hierarchy of expense for types of operations done from JS: reads/writes on pure JS data DOM Reads DOM Writes External I/O Often, an application is willing to do a some amount of work of a less expensive operation to avoid doing any work of a more expensive operation. Thus, a mutation observer is highly motivated, for both correctness and performance, to answer the question: What is the net-effect of some set of DOM changes that have taken place since I last got to run? --- Depending on the use case, what happened likely means some combination of the following: 1) Reachability: What nodes were added to or removed from the document? 2) Matching: What nodes stopped or started matching a given pattern? This could be arbitrarily complex, but in the use cases discussed, this usually means a simple selector. 3) Parent changes: What nodes are now children of a new parent node? 4) Movement: What nodes moved to a new position in the document? 5) Value change: What character/text or attribute nodes changed value (for attributes, also, whether the attribute was added or removed)? Note that none of the above requires any mechanism of mutation observation. All can be computed given the opportunity to inspect the DOM at two points in time. However, without any information about what happened, the expense of answering each of the above questions is roughly proportional to the number of nodes in the document. All are roughly linear, except (4) Movement, which is roughly quadratic. It seems to me that a good goal for any mutation event replacement should be that the expense of answering the above projections be proportional the number of mutations which actually took place. I've implemented the null case (lacking any mutation information) for 1, 3 4 here: https://github.com/rafaelw/MutationProjections/blob/master/projections.js#L39 And the same projections, but taking mutation records to improve performance here: https://github.com/rafaelw/MutationProjections/blob/master/projections.js#L240 --- Observing sets of nodes In order to implement the above projections, I prototyped: Document.observeOwnedNodes I'm not proposing this (yet), but I think it is conceptually the most general case (btw, to give credit to Jonas, he pointed out that this might be solution to the problem I'm about to describe, though I'm pretty sure he meant sometime down the road). I'm not quite sure what all the Document.observeOwnedNodes approach tries to observe. I assume it should be observing everything, all the changes to child nodes and also changes to attributes. (currently it handles only elements) Is that really needed? It's not fully implemented in the shim I created, in that it only reports ChildlistChanged mutations. It's purpose is to provide a single registration that allows you to be notified of all changes that happen to all nodes owned by this document, regardless of whether they are current in the document. The problem is that observeSubtree doesn't report anything about what happened to a node that was removed from the document, modified, then returned to the document. Consider the following case:
Re: Mutation events replacement
Here are some tryserver builds. http://ftp.mozilla.org/pub/mozilla.org/firefox/try-builds/opet...@mozilla.com-e57a1a317f25/ The default is mostly-sync approach, but if one sets dom.AlmostAsyncModificationBatch to true (load about:config, right click, add new boolean), and restarts the browser, callbacks are called at the end of the task. API is http://hg.mozilla.org/try/file/23ac4760d571/dom/interfaces/core/nsIDOMModificationBatch.idl and a very simple test page http://mozilla.pettay.fi/modificationbatchtest.html The API doesn't have the index of added/removed child node, but that could be added easily. -Olli On 08/10/2011 10:49 PM, Olli Pettay wrote: FYI, I'm planning to implement the proposal (using vendor prefixed API) so that I can create tryserver builds. I'll post the links to builds here later, hopefully in a few days, when I find the time to do the actual implementation. -Olli On 08/04/2011 04:38 PM, Olli Pettay wrote: Hi all, here is a bit different proposal for mutation events replacement. This is using the mostly-sync approach, and batching can make it easier to use with several simultaneous script libraries; each one would use their own batch. For performance reasons it might be useful to have an attribute name filter for AttrChange, but such thing could be added later. One advantage this approach has is that it doesn't pollute Node API. -Olli interface Modification { /** * type is either TextChange, ChildListChange or AttrChange. * (More types can be added later if needed.) */ readonly attribute DOMString type; /** * Target of the change. * If an attribute is changed, target is the element, * if an element is added or removed, target is the node * which was added or removed. * If text is changed, target is the CharacterData node which was * changed. */ readonly attribute Node target; /** * parent node of the target right before the change happened, * or null. */ readonly attribute Node targetParent; /** * The node which is batching the change. */ readonly attribute Node currentTarget; /** * The name of the attribute which was changed, or null. */ readonly attribute DOMString attrName; /* * The previous value of the attribute or CharacterData node, or null. * If a new attribute is added, prevValue is null. */ readonly attribute DOMString prevValue; /* * The new value of the attribute or CharacterData node, or null. * If an attribute is removed, newValue is null. */ readonly attribute DOMString newValue; }; [Callback, NoInterfaceObject] interface ModificationCallback { void handleBatch(in ModificationBatch aBatch); }; [Constructor(in ModificationCallback aDoneCallback)] interface ModificationBatch { /** * Modifications is non-empty array only while aDoneCallback * is called. And while that happens, modifications list doesn't * change. */ readonly attribute Modification[] modifications; void batchTextChanges(in Node aNode); void unbatchTextChanges(in Node aNode); void batchChildListChanges(in Node aNode); void unbatchChildListChanges(in Node aNode); void batchAttrChanges(in Node aNode); void unbatchAttrChanges(in Node aNode); void batchAll(); void unbatchAll(); }; aDoneCallback is called right before the call which is modifying DOM returns. If aDoneCallback modifies DOM, new modifications list will be collected and callbacks will be called right after the initial aDoneCallback returns. ModificationBatches are always handled in the order they are *created*. Callbacks are never called if modifications list is empty. Example 1: // log all the attribute changes var o = new ModificationBatch(function(b) { for (var i = 0; i b.modifications.length; ++i) { var m = b.modifications[i]; if (m.prevValue == null) { console.log(m.attrName + added); } else if (m.newValue == null) { console.log(m.attrName + removed); } else { console.log(m.attrName + modified); } } } ); o.batchAttrChanges(document);
Re: DOM Mutation Events Replacement: When to deliver mutations
On Mon, 15 Aug 2011 14:23:10 +0200, Anne van Kesteren ann...@opera.com wrote: Since there seems to be consensus to not do either Immediately or New task should I remove those from http://wiki.whatwg.org/wiki/Modifications now? It is fine with me if someone else does it too. I instead added a new section upfront that attempts to summarize the debate thus far: http://wiki.whatwg.org/wiki/Modifications#Invoking_Modification_Listeners What we are missing is input from library authors (though I suppose we are getting some here from Google, that is not entirely clear to me). Is there a pain with mutation events for them or is that restricted to UA implementors? The end of task approach has a problem with showModalDialog(). Is this going to be a problem with dialog modal as well? Any other constructs? showModalDialog() may be bad practice, but dialog modal is not. In addition to that I think it would be great if Rafael could expand on point 6 and 7 made in http://lists.w3.org/Archives/Public/public-webapps/2011JulSep/0779.html so that we can hash that out as well against what Olli has proposed thus far. -- Anne van Kesteren http://annevankesteren.nl/
Re: DOM Mutation Events Replacement: When to deliver mutations
On Mon, Aug 15, 2011 at 5:23 AM, Anne van Kesteren ann...@opera.com wrote: Since there seems to be consensus to not do either Immediately or New task should I remove those from http://wiki.whatwg.org/wiki/** Modifications http://wiki.whatwg.org/wiki/Modifications now? It is fine with me if someone else does it too. That sounds great! Thanks for doing that. On Tue, Aug 16, 2011 at 8:21 AM, Anne van Kesteren ann...@opera.com wrote: What we are missing is input from library authors (though I suppose we are getting some here from Google, that is not entirely clear to me). Is there a pain with mutation events for them or is that restricted to UA implementors? Yeah, that's my feeling as well. We need more input from application and library authors. - Ryosuke
DOM Mutation Events Replacement: Findings from implementing net-effect projections
TL;DR; 1) ObserveSubtree semantics doesn't provide a robust mechanism for observing a tree/fragment, and if we don't provide something more complete, libraries will likely register observers at every node in the document. 2) Not providing position information (e.g childlistIndex) in ChildlistChanged mutations means that the algorithmic complexity of computing whether/where nodes have moved to doesn't improve vs. having to bruce-force compute. 3) Not providing lastValue for text changes (attribute, textNode, etc...) may adversely impact some use cases. --- Following up on points 6 7 from http://lists.w3.org/Archives/Public/public-webapps/2011JulSep/0779.html ... I've prototyped the main net-effect projections that I believe various use cases will need. I've published to code to this public github repository: https://github.com/rafaelw/MutationProjections I encourage anyone interested to check my work and make corrections and/or suggestions, or create a branch that experiments in any direction they see fit. --- Per point (3) in the email above, we've concluded that mutations should be delivered in batches, since any number of mutations can have taken place since an observer was notified. Also, though it varies, there's generally a hierarchy of expense for types of operations done from JS: reads/writes on pure JS data DOM Reads DOM Writes External I/O Often, an application is willing to do a some amount of work of a less expensive operation to avoid doing any work of a more expensive operation. Thus, a mutation observer is highly motivated, for both correctness and performance, to answer the question: What is the net-effect of some set of DOM changes that have taken place since I last got to run? --- Depending on the use case, what happened likely means some combination of the following: 1) Reachability: What nodes were added to or removed from the document? 2) Matching: What nodes stopped or started matching a given pattern? This could be arbitrarily complex, but in the use cases discussed, this usually means a simple selector. 3) Parent changes: What nodes are now children of a new parent node? 4) Movement: What nodes moved to a new position in the document? 5) Value change: What character/text or attribute nodes changed value (for attributes, also, whether the attribute was added or removed)? Note that none of the above requires any mechanism of mutation observation. All can be computed given the opportunity to inspect the DOM at two points in time. However, without any information about what happened, the expense of answering each of the above questions is roughly proportional to the number of nodes in the document. All are roughly linear, except (4) Movement, which is roughly quadratic. It seems to me that a good goal for any mutation event replacement should be that the expense of answering the above projections be proportional the number of mutations which actually took place. I've implemented the null case (lacking any mutation information) for 1, 3 4 here: https://github.com/rafaelw/MutationProjections/blob/master/projections.js#L39 And the same projections, but taking mutation records to improve performance here: https://github.com/rafaelw/MutationProjections/blob/master/projections.js#L240 --- Observing sets of nodes In order to implement the above projections, I prototyped: Document.observeOwnedNodes I'm not proposing this (yet), but I think it is conceptually the most general case (btw, to give credit to Jonas, he pointed out that this might be solution to the problem I'm about to describe, though I'm pretty sure he meant sometime down the road). The problem is that observeSubtree doesn't report anything about what happened to a node that was removed from the document, modified, then returned to the document. Consider the following case: -Register a subtreeObserver at the root of the document -Some other code removes an element, appends a new element to it, modifies an attribute, then returns the node to the document. Now, in order to properly compute the above projections, we are nearly back to the null case (matching becomes proportional the number of descendants of added/removed nodes that we heard about, but everything else returns to being proportional to the number of nodes in the document). You may consider this to be an esoteric case, but imagine the situation that library authors will be in. They will have two basic options: a) Use observeSubtree and explain to their users that they have to be careful never to modify something outside the tree. b) Simply register an observer at all nodes in the document. Picking the second option means that my library works properly and I don't need my users to be careful. It burns plenty of memory, but library authors aren't well known for being conservative in that regard -- especially since its pretty hard for them to quantify the memory impact of anything. The danger is
Re: DOM Mutation Events Replacement: When to deliver mutations
On Sat, 13 Aug 2011 17:41:32 +0200, Anne van Kesteren ann...@opera.com wrote: I created http://wiki.whatwg.org/wiki/Modifications based on your email and the reply from Olli. It probably needs a bit more context. I will try to contact the relevant people at Opera to see if we have any input in the matter. From the perspective of the DOM specification option 2 would be easiest, but that is not really a good argument either way. It does not matter much to Opera. I would personally prefer it if we could stay away from tasks so the definition of modification listeners can be fully contained by node and range modification methods. Since there seems to be consensus to not do either Immediately or New task should I remove those from http://wiki.whatwg.org/wiki/Modifications now? It is fine with me if someone else does it too. -- Anne van Kesteren http://annevankesteren.nl/
Re: DOM Mutation Events Replacement: When to deliver mutations
On Thu, 11 Aug 2011 02:44:32 +0200, Rafael Weinstein rafa...@google.com wrote: Although everyone seems to agree that mutations should be delivered after the DOM operations which generated them complete, the question remains: When, exactly, should mutations be delivered? I created http://wiki.whatwg.org/wiki/Modifications based on your email and the reply from Olli. It probably needs a bit more context. I will try to contact the relevant people at Opera to see if we have any input in the matter. From the perspective of the DOM specification option 2 would be easiest, but that is not really a good argument either way. -- Anne van Kesteren http://annevankesteren.nl/
Re: DOM Mutation Events Replacement: When to deliver mutations
On 08/11/2011 03:44 AM, Rafael Weinstein wrote: Although everyone seems to agree that mutations should be delivered after the DOM operations which generated them complete, the question remains: When, exactly, should mutations be delivered? The four options I'm aware of are: 1) Immediately - i.e. while the operation is underway. [Note: This is how current DOM Mutation events work]. 2) Upon completion of the outer-most DOM operation. i.e. Immediately before a the lowest-on-the-stack DOM operation returns, but after it has done all of its work. 3) At the end of the current Task. i.e. immediately before the UA is about to fetch a new Task to run. 4) Scheduled as a future Task. i.e. fully async. --- Discussion: Options 1 4 are don't seem to have any proponents that I know of, so briefly: Option 1, Immediately: Pro: -It's conceptually the easiest thing to understand. The following *always* hold: -For calling code: When any DOM operation I make completes, all observers will have run. -For notified code: If I'm being called, the operation which caused this is below me on the stack. Con: -Because mutations must be delivered for some DOM operations before the operation is complete, UAs must tolerate all ways in which script may invalidate their assumptions before they do further work. Option 4, Scheduled as a future Task: Pro: -Conceptually easy to understand -Easy to implement. Con: -It's too late. Most use cases for mutation observation require that observers run before a paint occurs. E.g. a widget library which watches for special attributes. Script may create adiv class=FooButton and an observer will react to this by decorating the div as a FooButton. It is unacceptable (creates visual artifacts/flickering) to have the div be painted before the widget library has decorated it as a FooButton. Both of these options appear to be non-starters. Option 1 has been shown by experience to be an unreasonable implementation burden for UAs. Option 4 clearly doesn't handle properly important use cases. --- Options 2 3 have proponents. Since I'm one of them (a proponent), I'll just summarize the main *pro* arguments for each and invite those who wish (including myself), to weigh in with further support or criticism in follow-on emails. Option 2: Upon completion of the outer-most DOM operation. Pro: -It's conceptually close to fully synchronous. For simple uses (specifically, setting aside the case of making DOM operations within a mutation callback), it has the advantages of Option 1, without its disadvantages. Because of this, it's similar to the behavior of current Mutation Events. Pro: Semantics are consistent: delivery happens right before the outermost DOM operation returns. Easier transition from mutation events to the new API. Not bound to tasks. Side effects, like problems related to spinning event loop are per mutation callback, not per whole task. Option 3: At the end of the current Task. Pro: -No code is at risk for having its assumptions invalidated while it is trying to do work. All participants (main application script, libraries which are implemented using DOM mutation observation) are allowed to complete whatever work (DOM operations) they wish before another participant starts doing work. Con: Since the approach is bound to tasks, it is not clear what should happen if event loop spins while handling the task. What if some other task modifies the DOM[1], when should the mutation callbacks fire? Because of this issue, tasks, which may spin event loop, should not also modify DOM since that may cause some unexpected result. Callback handling is moved far away from the actual mutation. Pro: Can batch more, since the callbacks are called later than in option 2. -Olli [1] showModalDialog(javascript:opener.document.body.textContent = '';, , );
Re: DOM Mutation Events Replacement: When to deliver mutations
Thanks Olli. I think this is now a fairly complete summary of the issues identified thus far. It'd be great to get some additional views -- in particular from folks representing UAs that haven't yet registered any observations or opinons. Note: I think what Olli has listed is fair, but I'm concerned that because of terminology (consistent vs inconsistent semantics), others may be confused. I'm going to clarify a bit. I believe my comments should be uncontroversial. Olli (or anyone else), please correct me if this isn't so. On Thu, Aug 11, 2011 at 2:02 AM, Olli Pettay olli.pet...@helsinki.fi wrote: On 08/11/2011 03:44 AM, Rafael Weinstein wrote: Although everyone seems to agree that mutations should be delivered after the DOM operations which generated them complete, the question remains: When, exactly, should mutations be delivered? The four options I'm aware of are: 1) Immediately - i.e. while the operation is underway. [Note: This is how current DOM Mutation events work]. 2) Upon completion of the outer-most DOM operation. i.e. Immediately before a the lowest-on-the-stack DOM operation returns, but after it has done all of its work. 3) At the end of the current Task. i.e. immediately before the UA is about to fetch a new Task to run. 4) Scheduled as a future Task. i.e. fully async. --- Discussion: Options 1 4 are don't seem to have any proponents that I know of, so briefly: Option 1, Immediately: Pro: -It's conceptually the easiest thing to understand. The following *always* hold: -For calling code: When any DOM operation I make completes, all observers will have run. -For notified code: If I'm being called, the operation which caused this is below me on the stack. Con: -Because mutations must be delivered for some DOM operations before the operation is complete, UAs must tolerate all ways in which script may invalidate their assumptions before they do further work. Option 4, Scheduled as a future Task: Pro: -Conceptually easy to understand -Easy to implement. Con: -It's too late. Most use cases for mutation observation require that observers run before a paint occurs. E.g. a widget library which watches for special attributes. Script may create adiv class=FooButton and an observer will react to this by decorating the div as a FooButton. It is unacceptable (creates visual artifacts/flickering) to have the div be painted before the widget library has decorated it as a FooButton. Both of these options appear to be non-starters. Option 1 has been shown by experience to be an unreasonable implementation burden for UAs. Option 4 clearly doesn't handle properly important use cases. --- Options 2 3 have proponents. Since I'm one of them (a proponent), I'll just summarize the main *pro* arguments for each and invite those who wish (including myself), to weigh in with further support or criticism in follow-on emails. Option 2: Upon completion of the outer-most DOM operation. Pro: -It's conceptually close to fully synchronous. For simple uses (specifically, setting aside the case of making DOM operations within a mutation callback), it has the advantages of Option 1, without its disadvantages. Because of this, it's similar to the behavior of current Mutation Events. Pro: Semantics are consistent: delivery happens right before the outermost DOM operation returns. This statement is true. When I described Option 2 (perhaps too harshly) as having inconsistent semantics, I was referrer only to the expectations of Callers and Observers. To be totally clear: Parties: Caller = any code which performers a DOM operation which triggers a mutation. Observer = any code to whom the mutation is delivered. Expectations for synchrony: Caller: When any DOM operation I make completes, all observers will have been notified. Observer: If I'm being notified, the Caller which triggered the mutation is below me on the stack. Parties: Caller Observer Options 1:Always Always 2:Sometimes(a) Sometimes(a) 3:Never Never 4:Never Never (a) True when Caller is run outside of a mutation observer callback. False when Caller is run inside a mutation observer callback. Easier transition from mutation events to the new API. Not bound to tasks. Side effects, like problems related to spinning event loop are per mutation callback, not per whole task. Option 3: At the end of the current Task. Pro: -No code is at risk for having its assumptions invalidated while it is trying to do work. All participants (main application script, libraries which are implemented using DOM mutation observation) are allowed to complete whatever work (DOM operations) they wish before another participant starts doing work. Con: Since the approach is bound to tasks, it is not clear what should happen if event loop spins while handling the task. What
Re: DOM Mutation Events Replacement: When to deliver mutations
On 08/11/2011 06:13 PM, Rafael Weinstein wrote: Con: Since the approach is bound to tasks, it is not clear what should happen if event loop spins while handling the task. What if some other task modifies the DOM[1], when should the mutation callbacks fire? Because of this issue, tasks, which may spin event loop, should not also modify DOM since that may cause some unexpected result. I think the *pro* side of this you listed is more fair. Both Options 2 3 must answer this question. It's true that because Option 3 is later, it sort of has this issue more. And it has a lot more. Since for example when handling an event, all the listeners for it are called in the same task and if one event listener modifies DOM and some other spins event loop, it is hard to see what is causing the somewhat unexpected behavior. However, what should happen has been defined. In both cases, if there are any mutations which are queued for delivery when an inner event loop spins up, they are *not* delivered inside the inner event loop. In both Options, they are always delivered in the loop which queued them. But what happens when event loop spins within a task, and some inner task causes new mutations? We want to notify about mutations in the order they have happened, right? So if there are pending mutations to notify, the inner task must just queue notifications to the queue of the outermost task. This could effectively disable all the mutation callbacks for example when a modal dialog (showModalDialog) is open. Option 2 has similar problem, but *only* while handling mutation callbacks, not during the whole task. -Olli Callback handling is moved far away from the actual mutation. Pro: Can batch more, since the callbacks are called later than in option 2. -Olli [1] showModalDialog(javascript:opener.document.body.textContent = '';, , );
Re: DOM Mutation Events Replacement: When to deliver mutations
On Thu, Aug 11, 2011 at 9:12 AM, Olli Pettay olli.pet...@helsinki.fi wrote: On 08/11/2011 06:13 PM, Rafael Weinstein wrote: Con: Since the approach is bound to tasks, it is not clear what should happen if event loop spins while handling the task. What if some other task modifies the DOM[1], when should the mutation callbacks fire? Because of this issue, tasks, which may spin event loop, should not also modify DOM since that may cause some unexpected result. I think the *pro* side of this you listed is more fair. Both Options 2 3 must answer this question. It's true that because Option 3 is later, it sort of has this issue more. And it has a lot more. Since for example when handling an event, all the listeners for it are called in the same task and if one event listener modifies DOM and some other spins event loop, it is hard to see what is causing the somewhat unexpected behavior. However, what should happen has been defined. In both cases, if there are any mutations which are queued for delivery when an inner event loop spins up, they are *not* delivered inside the inner event loop. In both Options, they are always delivered in the loop which queued them. But what happens when event loop spins within a task, and some inner task causes new mutations? We want to notify about mutations in the order they have happened, right? In general, yes. But I believe the idea is that spinning an inner event loop is an exception. In that case delivering mutations in the order they were generated will be broken. To be perfectly precise: Mutations will be delivered in the order they were generated *for and within any given event loop*. There's no question this is unfortunate. The case in which the bad thing happens is you: -Made some modifications to the main document -Used showModalDialog -Modified the opener document from the event loop of showModalDialog -Got confused because mutations from within the showModalDialog were delivered before the mutations made before calling it I suppose this comes down to judgement. Mine is that it's acceptable for us to not attempt to improve the outcome in this case. So if there are pending mutations to notify, the inner task must just queue notifications to the queue of the outermost task. This could effectively disable all the mutation callbacks for example when a modal dialog (showModalDialog) is open. Option 2 has similar problem, but *only* while handling mutation callbacks, not during the whole task. -Olli Callback handling is moved far away from the actual mutation. Pro: Can batch more, since the callbacks are called later than in option 2. -Olli [1] showModalDialog(javascript:opener.document.body.textContent = '';, , );
Re: Mutation events replacement
FYI, I'm planning to implement the proposal (using vendor prefixed API) so that I can create tryserver builds. I'll post the links to builds here later, hopefully in a few days, when I find the time to do the actual implementation. -Olli On 08/04/2011 04:38 PM, Olli Pettay wrote: Hi all, here is a bit different proposal for mutation events replacement. This is using the mostly-sync approach, and batching can make it easier to use with several simultaneous script libraries; each one would use their own batch. For performance reasons it might be useful to have an attribute name filter for AttrChange, but such thing could be added later. One advantage this approach has is that it doesn't pollute Node API. -Olli interface Modification { /** * type is either TextChange, ChildListChange or AttrChange. * (More types can be added later if needed.) */ readonly attribute DOMString type; /** * Target of the change. * If an attribute is changed, target is the element, * if an element is added or removed, target is the node * which was added or removed. * If text is changed, target is the CharacterData node which was * changed. */ readonly attribute Node target; /** * parent node of the target right before the change happened, * or null. */ readonly attribute Node targetParent; /** * The node which is batching the change. */ readonly attribute Node currentTarget; /** * The name of the attribute which was changed, or null. */ readonly attribute DOMString attrName; /* * The previous value of the attribute or CharacterData node, or null. * If a new attribute is added, prevValue is null. */ readonly attribute DOMString prevValue; /* * The new value of the attribute or CharacterData node, or null. * If an attribute is removed, newValue is null. */ readonly attribute DOMString newValue; }; [Callback, NoInterfaceObject] interface ModificationCallback { void handleBatch(in ModificationBatch aBatch); }; [Constructor(in ModificationCallback aDoneCallback)] interface ModificationBatch { /** * Modifications is non-empty array only while aDoneCallback * is called. And while that happens, modifications list doesn't * change. */ readonly attribute Modification[] modifications; void batchTextChanges(in Node aNode); void unbatchTextChanges(in Node aNode); void batchChildListChanges(in Node aNode); void unbatchChildListChanges(in Node aNode); void batchAttrChanges(in Node aNode); void unbatchAttrChanges(in Node aNode); void batchAll(); void unbatchAll(); }; aDoneCallback is called right before the call which is modifying DOM returns. If aDoneCallback modifies DOM, new modifications list will be collected and callbacks will be called right after the initial aDoneCallback returns. ModificationBatches are always handled in the order they are *created*. Callbacks are never called if modifications list is empty. Example 1: // log all the attribute changes var o = new ModificationBatch(function(b) { for (var i = 0; i b.modifications.length; ++i) { var m = b.modifications[i]; if (m.prevValue == null) { console.log(m.attrName + added); } else if (m.newValue == null) { console.log(m.attrName + removed); } else { console.log(m.attrName + modified); } } } ); o.batchAttrChanges(document);
DOM Mutation Events Replacement: The Story So Far / Existing Points of Consensus
What follows is an attempt to summarize the (relatively recent) discussions regarding replacing DOM Mutation Events. My goals here are to: -Provide a quick primer for those who haven't read all hundred or so emails. -Reiterate the aspects of a solution which seem to have broad support. -Identify the main points of divergence which remain. Problem: DOM Mutation events as currently specified and implemented are widely viewed as fatally flawed because they are: (1) Verbose (fire too often) (2) Slow (because of event propagation -- and because they prevent certain UA run-time optimizations) (3) Crashy (have been the source of many crashers for UAs because script can tear up the world in any way it wishes while a DOM operation is underway). Solution: *Agreed upon design points: Primarily because of a proposal made by Jonas Sicking in 2009, the group has been largely in agreement about the following: (1) The vocabulary of mutations should be more expressive and require fewer words to adequately describe what happened . For instance, a single innerHTML assignment which results in removing N nodes and inserting M nodes should be expressed as a single mutation (e.g. { mutation: ChildlistChanged, added: [...], removed: [...] } ) -- not a sequence of mutations, one for each node inserted or removed. (2) Mutations should avoid the expense of event propagation (mainly capture bubble). (3) Mutations should be delivered to observers after the DOM operations which generated them complete -- removing the possibility of having script interfere with their operation. For example, an execCommand() operation is permitted to make any all DOM operations it needs *before* it has to notify any observers of what happened. Through discussing Jonas's proposal, we observed that in a system which allows multiple observers that can, themselves, make mutations, observers will generally need to be tolerant of an arbitrary number of mutations having occurred before being notified. Further, there is strong performance motivation for observers to respond to the net-effect of a set of mutations, rather than acting immediately in response to each mutation. Thus: (4) Observers should be called with the *set* of mutations which has occurred since they were last called (or since registration), rather than being called once per mutation. I.e. Deliver mutations in batches of everything that has happened since the last time I called you - up till now. To my understanding, the most recent proposals made by Jonas, Olli Pettay, Adam Klein and myself all agree on the above four design points. *Design points lacking consensus: (5) When are mutations delivered? There are four options here, only two of which have proponents. =I'm going to try to summarize the discussion on this point in a separate email. (6) The semantics for expressing interest in sets of nodes (7) What information is contained in mutation records =I've done a survey of the main use cases sited and prototyped the main net-effect projections. I'll summarize my findings in another email which attempts to layout the main trade-offs I see so far.
DOM Mutation Events Replacement: When to deliver mutations
Although everyone seems to agree that mutations should be delivered after the DOM operations which generated them complete, the question remains: When, exactly, should mutations be delivered? The four options I'm aware of are: 1) Immediately - i.e. while the operation is underway. [Note: This is how current DOM Mutation events work]. 2) Upon completion of the outer-most DOM operation. i.e. Immediately before a the lowest-on-the-stack DOM operation returns, but after it has done all of its work. 3) At the end of the current Task. i.e. immediately before the UA is about to fetch a new Task to run. 4) Scheduled as a future Task. i.e. fully async. --- Discussion: Options 1 4 are don't seem to have any proponents that I know of, so briefly: Option 1, Immediately: Pro: -It's conceptually the easiest thing to understand. The following *always* hold: -For calling code: When any DOM operation I make completes, all observers will have run. -For notified code: If I'm being called, the operation which caused this is below me on the stack. Con: -Because mutations must be delivered for some DOM operations before the operation is complete, UAs must tolerate all ways in which script may invalidate their assumptions before they do further work. Option 4, Scheduled as a future Task: Pro: -Conceptually easy to understand -Easy to implement. Con: -It's too late. Most use cases for mutation observation require that observers run before a paint occurs. E.g. a widget library which watches for special attributes. Script may create a div class=FooButton and an observer will react to this by decorating the div as a FooButton. It is unacceptable (creates visual artifacts/flickering) to have the div be painted before the widget library has decorated it as a FooButton. Both of these options appear to be non-starters. Option 1 has been shown by experience to be an unreasonable implementation burden for UAs. Option 4 clearly doesn't handle properly important use cases. --- Options 2 3 have proponents. Since I'm one of them (a proponent), I'll just summarize the main *pro* arguments for each and invite those who wish (including myself), to weigh in with further support or criticism in follow-on emails. Option 2: Upon completion of the outer-most DOM operation. Pro: -It's conceptually close to fully synchronous. For simple uses (specifically, setting aside the case of making DOM operations within a mutation callback), it has the advantages of Option 1, without its disadvantages. Because of this, it's similar to the behavior of current Mutation Events. Option 3: At the end of the current Task. Pro: -No code is at risk for having its assumptions invalidated while it is trying to do work. All participants (main application script, libraries which are implemented using DOM mutation observation) are allowed to complete whatever work (DOM operations) they wish before another participant starts doing work.
Re: DOM Mutation Events Replacement: When to deliver mutations
Ok. Being a proponent, here's my further (opinionated) support for Option 3 and criticism for Option 2. On Wed, Aug 10, 2011 at 5:44 PM, Rafael Weinstein rafa...@google.com wrote: Although everyone seems to agree that mutations should be delivered after the DOM operations which generated them complete, the question remains: When, exactly, should mutations be delivered? The four options I'm aware of are: 1) Immediately - i.e. while the operation is underway. [Note: This is how current DOM Mutation events work]. 2) Upon completion of the outer-most DOM operation. i.e. Immediately before a the lowest-on-the-stack DOM operation returns, but after it has done all of its work. 3) At the end of the current Task. i.e. immediately before the UA is about to fetch a new Task to run. 4) Scheduled as a future Task. i.e. fully async. --- Discussion: Options 1 4 are don't seem to have any proponents that I know of, so briefly: Option 1, Immediately: Pro: -It's conceptually the easiest thing to understand. The following *always* hold: -For calling code: When any DOM operation I make completes, all observers will have run. -For notified code: If I'm being called, the operation which caused this is below me on the stack. Con: -Because mutations must be delivered for some DOM operations before the operation is complete, UAs must tolerate all ways in which script may invalidate their assumptions before they do further work. Option 4, Scheduled as a future Task: Pro: -Conceptually easy to understand -Easy to implement. Con: -It's too late. Most use cases for mutation observation require that observers run before a paint occurs. E.g. a widget library which watches for special attributes. Script may create a div class=FooButton and an observer will react to this by decorating the div as a FooButton. It is unacceptable (creates visual artifacts/flickering) to have the div be painted before the widget library has decorated it as a FooButton. Both of these options appear to be non-starters. Option 1 has been shown by experience to be an unreasonable implementation burden for UAs. Option 4 clearly doesn't handle properly important use cases. --- Options 2 3 have proponents. Since I'm one of them (a proponent), I'll just summarize the main *pro* arguments for each and invite those who wish (including myself), to weigh in with further support or criticism in follow-on emails. Option 2: Upon completion of the outer-most DOM operation. Pro: -It's conceptually close to fully synchronous. For simple uses (specifically, setting aside the case of making DOM operations within a mutation callback), it has the advantages of Option 1, without its disadvantages. Because of this, it's similar to the behavior of current Mutation Events. Con: -The timing delays delivery just long enough to guarantee that DOM operations don't have to worry about having their work interfered with, but encourages application script to leave itself exposed to exactly the same risk. -The semantics of delivery are inconsistent. Delivery of mutations is synchronous if calling operation is performed outside of a mutation callback and async if performed inside a mutation callback. Option 3: At the end of the current Task. Pro: -No code is at risk for having its assumptions invalidated while it is trying to do work. All participants (main application script, libraries which are implemented using DOM mutation observation) are allowed to complete whatever work (DOM operations) they wish before another participant starts doing work. Pro: -Creates (and requires use of -- creates a pit of success) a time to run which is ideal in two ways: 1) Performance: The main script is finished doing its work. The observer can minimize work by reacting to only the net-effect of what happened. I.e. not do work in intermediate states which ultimately become irrelevant. E.g. a widget library which needs to destruct widgets which are removed from the document. If a widget is removed but later added elsewhere in the same script event, the library would prefer to avoid destructing the widget and just allow it to be moved. 2) Correctness: The observer isn't at risk for attempting to act when the main script has put the DOM (temporarily) in an inconsistent state. E.g. a templating library which depends upon the value of two attributes of a single element. If script wishes to change both values but cannot do so without creating a temporarily nonsensical state, the library would prefer not to have to react to the nonsensical state and simply wait for the consistent (final) state. To be fair to Option 2, it doesn't preclude these (but it isn't sufficient -- a separate ability to schedule work at the end of the Task would be required). Con: -Behaves quite differently from the current mutation events.
Re: Mutation events replacement
On Wed, Aug 10, 2011 at 12:49 PM, Olli Pettay olli.pet...@helsinki.fiwrote: FYI, I'm planning to implement the proposal (using vendor prefixed API) so that I can create tryserver builds. I'll post the links to builds here later, hopefully in a few days, when I find the time to do the actual implementation. I don't mean to offend you or despise your effort (I certainly love to see DOM mutation replacement, and am very grateful for your effort) but I don't think we have reached a consensus yet as to what API we're implementing. Of course, you can always implement any vendor prefixed API you'd like. However, as I understand it, our goal here is to deprecate the existing mutation events and replace it with new API. Given that, I feel like we should spend a little more time sorting out details so that we can be reasonably confident that all major browsers will eventually implement the new API since there are at least 3 conflicting proposals at the moment. - Ryosuke
Re: Mutation events replacement
On 8/10/11 9:06 PM, Ryosuke Niwa wrote: On Wed, Aug 10, 2011 at 12:49 PM, Olli Pettay olli.pet...@helsinki.fi mailto:olli.pet...@helsinki.fi wrote: FYI, I'm planning to implement the proposal (using vendor prefixed API) so that I can create tryserver builds. I'll post the links to builds here later, hopefully in a few days, when I find the time to do the actual implementation. I don't mean to offend you or despise your effort (I certainly love to see DOM mutation replacement, and am very grateful for your effort) but I don't think we have reached a consensus yet as to what API we're implementing. In case Olli's e-mail wasn't clear, he's not talking about implementing and shipping in Mozilla releases or even nightlies. He's talking about creating some test builds so people who are interested can try actually using a browser with the proposal implemented and see what issues they run into, etc. -Boris
Re: Mutation events replacement
On Wed, Aug 10, 2011 at 8:32 PM, Boris Zbarsky bzbar...@mit.edu wrote: On 8/10/11 9:06 PM, Ryosuke Niwa Software Engineer Google Inc. wrote: On Wed, Aug 10, 2011 at 12:49 PM, Olli Pettay olli.pet...@helsinki.fi mailto:Olli.Pettay@helsinki.**fi olli.pet...@helsinki.fi wrote: FYI, I'm planning to implement the proposal (using vendor prefixed API) so that I can create tryserver builds. I'll post the links to builds here later, hopefully in a few days, when I find the time to do the actual implementation. I don't mean to offend you or despise your effort (I certainly love to see DOM mutation replacement, and am very grateful for your effort) but I don't think we have reached a consensus yet as to what API we're implementing. In case Olli's e-mail wasn't clear, he's not talking about implementing and shipping in Mozilla releases or even nightlies. He's talking about creating some test builds so people who are interested can try actually using a browser with the proposal implemented and see what issues they run into, etc. Ah, ok. That makes sense. Thanks for the clarification. - Ryosuke
Re: Mutation events replacement
Hi all, here is a bit different proposal for mutation events replacement. This is using the mostly-sync approach, and batching can make it easier to use with several simultaneous script libraries; each one would use their own batch. For performance reasons it might be useful to have an attribute name filter for AttrChange, but such thing could be added later. One advantage this approach has is that it doesn't pollute Node API. -Olli interface Modification { /** * type is either TextChange, ChildListChange or AttrChange. * (More types can be added later if needed.) */ readonly attribute DOMString type; /** * Target of the change. * If an attribute is changed, target is the element, * if an element is added or removed, target is the node * which was added or removed. * If text is changed, target is the CharacterData node which was * changed. */ readonly attribute Nodetarget; /** * parent node of the target right before the change happened, * or null. */ readonly attribute NodetargetParent; /** * The node which is batching the change. */ readonly attribute NodecurrentTarget; /** * The name of the attribute which was changed, or null. */ readonly attribute DOMString attrName; /* * The previous value of the attribute or CharacterData node, or null. * If a new attribute is added, prevValue is null. */ readonly attribute DOMString prevValue; /* * The new value of the attribute or CharacterData node, or null. * If an attribute is removed, newValue is null. */ readonly attribute DOMString newValue; }; [Callback, NoInterfaceObject] interface ModificationCallback { void handleBatch(in ModificationBatch aBatch); }; [Constructor(in ModificationCallback aDoneCallback)] interface ModificationBatch { /** * Modifications is non-empty array only while aDoneCallback * is called. And while that happens, modifications list doesn't * change. */ readonly attribute Modification[] modifications; void batchTextChanges(in Node aNode); void unbatchTextChanges(in Node aNode); void batchChildListChanges(in Node aNode); void unbatchChildListChanges(in Node aNode); void batchAttrChanges(in Node aNode); void unbatchAttrChanges(in Node aNode); void batchAll(); void unbatchAll(); }; aDoneCallback is called right before the call which is modifying DOM returns. If aDoneCallback modifies DOM, new modifications list will be collected and callbacks will be called right after the initial aDoneCallback returns. ModificationBatches are always handled in the order they are *created*. Callbacks are never called if modifications list is empty. Example 1: // log all the attribute changes var o = new ModificationBatch(function(b) { for (var i = 0; i b.modifications.length; ++i) { var m = b.modifications[i]; if (m.prevValue == null) { console.log(m.attrName + added); } else if (m.newValue == null) { console.log(m.attrName + removed); } else { console.log(m.attrName + modified); } } } ); o.batchAttrChanges(document);
Re: Mutation events replacement
/** * The name of the attribute which was changed, or null. */ readonly attribute DOMString attrName; There should be probably also attribute namespace void batchAttrChanges(in Node aNode); A filter could be added here -void batchAttrChanges(in Node aNode); +void batchAttrChanges(in Node aNode, [optional] in DOMString aReportValues); Where aReportValues could a comma separated list of attr localNames, or *. Default would be to report only that the attribute has changed, but to not include any values.
Re: Mutation events replacement
On 08/04/2011 10:14 PM, David Flanagan wrote: On 8/4/11 6:38 AM, Olli Pettay wrote: Hi all, here is a bit different proposal for mutation events replacement. This is using the mostly-sync approach, and batching can make it easier to use with several simultaneous script libraries; each one would use their own batch. For performance reasons it might be useful to have an attribute name filter for AttrChange, but such thing could be added later. One advantage this approach has is that it doesn't pollute Node API. -Olli I'm intrigued by the idea, but confused by the API (or at least by naming issues in the API). - ModificationBatch is both a batch of modifications and also a set of methods for requesting modification batches. Should there be separate ModificationBatch and ModificationBatcher interfaces? Why? You have an object which can listen for some modifications and then at some point call the callback that some modifications have happened. But perhaps the interface could be called something else. I'd like it to have batch or some similar meaning in it. - The pattern of passing a callback function to a constructor is novel in the DOM. Will this confuse people too much? Well, I don't think new Foo(callback); is more difficult to understand than say var foo = new Foo(); foo.onSomething = callback; Also, if the callback is passed to the constructor, there isn't a deregistration method. I assume that you achieve this by calling unbatchAll(), Yes that, or calling unbatchFoo if you called batchFoo before. but that seems non-parallel. Registering the callback doesn't need to have anything parallel. It is the batch*/unbatch* which are parallel. And batch/unbatchAll are kind of catch-all. What if the ModificationCallback was passed to the individual batch/unbatch methods instead? Well, the whole idea is that the same callback would handle a batch of modifications. - The name batchTextChanges() implies to me that it is expressing a preference to receive text changes in batched form, and that if you don't call this method you'll still get text changes, just one at a time. I don't think that is the intent, but that is what the name implies to me. How about something (verbose) like addTextChangeBatchListener() (and pass the ModificationCallback to this method instead of to the constructor) As I said, the idea is not to have separate ModificationCallbacks for different types, but to have a callback per ModificationBatch. If you need different callbacks, you can create a new ModificationBatch. But yes, the naming can be changed, if it is misleading. Suggestions welcome :) -Olli David interface Modification { /** * type is either TextChange, ChildListChange or AttrChange. * (More types can be added later if needed.) */ readonly attribute DOMString type; /** * Target of the change. * If an attribute is changed, target is the element, * if an element is added or removed, target is the node * which was added or removed. * If text is changed, target is the CharacterData node which was * changed. */ readonly attribute Node target; /** * parent node of the target right before the change happened, * or null. */ readonly attribute Node targetParent; /** * The node which is batching the change. */ readonly attribute Node currentTarget; /** * The name of the attribute which was changed, or null. */ readonly attribute DOMString attrName; /* * The previous value of the attribute or CharacterData node, or null. * If a new attribute is added, prevValue is null. */ readonly attribute DOMString prevValue; /* * The new value of the attribute or CharacterData node, or null. * If an attribute is removed, newValue is null. */ readonly attribute DOMString newValue; }; [Callback, NoInterfaceObject] interface ModificationCallback { void handleBatch(in ModificationBatch aBatch); }; [Constructor(in ModificationCallback aDoneCallback)] interface ModificationBatch { /** * Modifications is non-empty array only while aDoneCallback * is called. And while that happens, modifications list doesn't * change. */ readonly attribute Modification[] modifications; void batchTextChanges(in Node aNode); void unbatchTextChanges(in Node aNode); void batchChildListChanges(in Node aNode); void unbatchChildListChanges(in Node aNode); void batchAttrChanges(in Node aNode); void unbatchAttrChanges(in Node aNode); void batchAll(); void unbatchAll(); }; aDoneCallback is called right before the call which is modifying DOM returns. If aDoneCallback modifies DOM, new modifications list will be collected and callbacks will be called right after the initial aDoneCallback returns. ModificationBatches are always handled in the order they are *created*. Callbacks are never called if modifications list is empty. Example 1: // log all the attribute changes var o = new ModificationBatch(function(b) { for (var i = 0; i b.modifications.length; ++i) { var m = b.modifications[i]; if (m.prevValue == null) { console.log
Re: More use-cases for mutation events replacement
On Mon, Jul 25, 2011 at 11:12 PM, Sean Hogan shogu...@westnet.com.au wrote: I assume you are referring to the NodeWatch proposal from Microsoft. 1st draft: http://www.w3.org/2008/webapps/wiki/Selector-based_Mutation_Events 2nd draft: http://www.w3.org/2008/webapps/wiki/MutationReplacement#NodeWatch_.28A_Microsoft_Proposal.29 I wasn't aware of that proposal. It seems like we came up with the same basic idea independently. I think the utility of this proposal is unnecessarily limited by the restriction of one watcher per node. Also, it is not clear that handlers would be called before page reflow / repaint. Yeah, those are two immediate problems I see. Also (based on looking at the second draft, not the first): * I'm not sure what the use-case is for a minimum frequency. If it's not going to be really really common, it shouldn't be part of the API, because authors can always fake it with setTimeout() and some globals. * I don't think we want to return a handle -- don't other APIs let you unwatch by just passing the same callback you originally passed? That makes more sense, IMO. * It says it throws an INDEX_SIZE_ERR if the minimum frequency is negative, but it's an unsigned long, so WebIDL already specifies different behavior if it's negative (it wraps).
Re: Mutation events replacement
On 24/07/11 16:18, Aryeh Gregor wrote: On Fri, Jul 22, 2011 at 6:58 PM, Jonas Sicking jo...@sicking.cc wrote: We should have much richer events to aid with rich text editing. Using mutation notifications for this is will not create a good experience for the page author. Agreed. I'd be really interested in specific use-cases if people are using mutation events for editing here. I am interested in web-based collaboration, e.g. for distributed meetings, something we do a lot at W3C. We currently rely on IRC plus a bunch of scripts to support meeting management, such as the agenda, actions, resolutions, and generating an HTML version of the minutes from the IRC record. My aim is to replace the current IRC system with something much better that runs in the browser. An associated use case is to allow people to edit slide presentations as they are being shown, where the slides are expressed in HTML via a microformat (HTML Slidy). I am using mutation events with content-editable plus clean up operations to work around variations in how different browsers treat Enter, Backspace and Delete. Higher level editing actions that are application specific (e.g. insert slide or next agendum) can be serialized as such instead of serializing the constituent mutations. Cut, copy, and paste, and drag and drop operations are further challenges to ensuring interoperability. I keep a local undo/redo queue for mutations, and frequently serialize the changes for exchange with other clients via a lightweight websockets server module. One client acts as the senior editor, automatically reviewing edits proposed by other clients (junior editors). This role is swapped around automatically or manually. The senior editor serializes the official changes as a sequence of updates for the trunk version of the document. Junior editors need to be able to revert their DOM to the latest trunk version, and reapply their local (proposed changes) after adjusting them for the changes since the last common version (a 3 way merge). Ditto for their undo/redo queue. Likewise, the senior editor needs to adjust the proposed changes to an earlier trunk version to align with subsequent trunk versions. This probably sounds complicated, but it works, and the performance is pretty good! -- Dave Raggettd...@w3.org http://www.w3.org/People/Raggett
Re: More use-cases for mutation events replacement
On 25/07/11 2:18 AM, Aryeh Gregor wrote: When discussing mutation events use-cases, mostly so far people have been talking about editors. However, I think mutation event replacements would have a much more general appeal if they were easily usable in certain cases with little performance impact. Specifically, one use-case I've run into a lot is I want to modify some class of node soon after it gets added to the DOM, but before it's actually painted. Examples of where this has come up for me in practice: snip What would solve all of these use-cases is a way to register a handler that would get notified every time an element is added to the DOM that matches a particular CSS selector, which is guaranteed to run at some point before the element is actually painted. Thus it could be a special type of event that runs when the event loop spins *before* there's any opportunity to paint, or it could be semi-synchronous, or whatever, as long as it runs before paint. Then I could easily solve all the use-cases: snip It seems to me this dovetails pretty nicely with some of the proposed mutation events replacement APIs. Specifically, people have been talking about allowing filtering of events, so this use-case should be solved easily enough if you can use CSS selectors as filters. In that case, the perf hit from using such events should be negligible, right? I assume you are referring to the NodeWatch proposal from Microsoft. 1st draft: http://www.w3.org/2008/webapps/wiki/Selector-based_Mutation_Events 2nd draft: http://www.w3.org/2008/webapps/wiki/MutationReplacement#NodeWatch_.28A_Microsoft_Proposal.29 I think the utility of this proposal is unnecessarily limited by the restriction of one watcher per node. Also, it is not clear that handlers would be called before page reflow / repaint. If these issues were resolved, then this feature plus some shadow DOM capabilities would facilitate a performant JS implementation of (something approaching) XBL2.
Re: Mutation events replacement
On Fri, Jul 22, 2011 at 11:54 AM, Boris Zbarsky bzbar...@mit.edu wrote: Actually, you can pretty easily do it in the other order (move the text into the b, and then put the b in the DOM), and may want to so as to minimize the number of changes the the live DOM; that's something that's often recommended as a performance enhancement. Hmm. Interesting. So far I've been writing my draft on the theory that my move preserving ranges operation would actually be implemented as I've specced it, so that all Ranges (not just the current selection) would remain in logically the same place after the DOM operations. The way I've designed it, you have to move stuff around within the tree rather than removing and re-adding it. But of course, that design could always be changed. Either I could just give up on preserving anything other than the current Selection, or I could define different primitives. So point taken. Editing doesn't *have* to involve moving nodes at all. I don't need software that uses mutation events. I need software that triggers editing operations, so I can them actually measure what DOM mutations are performed in the course of these editing operations. What use do you have here for software that doesn't want to use DOM mutations to start with? The question is what users of mutation handlers will need, right? If you do need such software, though, some of the most important WYSIWYG editors out there are TinyMCE and CKEditor, which have easy-to-use online demos: http://tinymce.moxiecode.com/ http://ckeditor.com/ A typical workload is paste in the contents of some blog post or other that you grab from someplace (often this would come preloaded if you're editing or quoting an existing post), then change around some text, type a couple of paragraphs, add an image or some smilies or something, make some links, that sort of thing. On Fri, Jul 22, 2011 at 6:57 PM, Jonas Sicking jo...@sicking.cc wrote: On Fri, Jul 22, 2011 at 2:08 AM, Dave Raggett d...@w3.org wrote: But if you are going to, *don't* coalesce mutations when the resulting DOM tree is dependent on the order in which those mutations took place. This is critical to distributed editing applications. The DOM should have no such behavior. The only exception to this rule that I know of is script elements. They execute their contained script the first time they are inserted into a Document, but don't undo that action when removed (for obvious reasons), nor do they redo it when inserted again. The order of mutations makes a big difference if you're recording them as things like insert node X into node Y at offset Z. If you append two children to a given node, the order you append them in will affect the resulting DOM. Likewise if you insert two nodes at the same index, or before the same existing child, or if you insert two nodes at particular indices but remove some child in between. How could you record arbitrary DOM mutations such that the order wouldn't matter in general? On Fri, Jul 22, 2011 at 6:58 PM, Jonas Sicking jo...@sicking.cc wrote: We should have much richer events to aid with rich text editing. Using mutation notifications for this is will not create a good experience for the page author. Agreed. I'd be really interested in specific use-cases if people are using mutation events for editing here.
More use-cases for mutation events replacement
When discussing mutation events use-cases, mostly so far people have been talking about editors. However, I think mutation event replacements would have a much more general appeal if they were easily usable in certain cases with little performance impact. Specifically, one use-case I've run into a lot is I want to modify some class of node soon after it gets added to the DOM, but before it's actually painted. Examples of where this has come up for me in practice: 1) Some images in Wikipedia articles are offensive to some users, who may want to block them by default. However, we want to serve the same page content to different users for caching reasons, only varying the HTML used for the interface. One way to solve this would be to add classes to potentially offensive images, then have a script run that replaces the image with a placeholder before it's visible to the user. Currently, as far as I can tell, the only way to do this is to put a script immediately after the img. This could theoretically fail if the script winds up getting parsed too long after the img, like if it winds up in a different TCP segment that then gets dropped and takes a few seconds to resend while the image loads from cache. More practically, it's incompatible with CSP, which prohibits inline script. It also can't be used in situations like Wikipedia, where administrators can add scripts to the head but cannot add inline script. It's also excessively verbose if there are lots of places per page you need to do it. (Actual writeup of requirements, albeit abandoned: http://www.mediawiki.org/wiki/User:Simetrical/Censorship) 2) Some pages have content that should be collapsed by default with a way for the user to un-collapse it, but they should be uncollapsed if the user has script disabled, since otherwise the user won't be able to access the contents. This is true for some Wikipedia infoboxes, for instance. details might solve this use-case without the need for script, but it might not (e.g., styling might not be flexible enough). Supposing it doesn't, the way you'd currently have to do this is add a script right after the opening tag that collapses it and adds the uncollapse button. But again, inline script is incompatible with CSP, and incompatible with setups like Wikipedia where you might not be allowed to add inline script, and excessively verbose if there are many occurrences per page. 3) In current HTML drafts, details auto-closes p. I just filed a bug asking that it be made an inline element that doesn't auto-close p: http://www.w3.org/Bugs/Public/show_bug.cgi?id=13345. I want this because smaug complained that my specs didn't contain rationale, and when I pointed out that I had detailed rationale in comments, he said I should make it visible to the reader. So I want to have inline details at the end of some li's or p's. If the change I request is made, details will still auto-close p in current browsers, so I'd want to work around that with a shim for browsers using the current HTML parser. The obvious thing to do would be to run some script after every details is added that's the next sibling of a p, and move it inside the p. Again, this would require a lot of repetitive use of script. 4) Prior to the invention of the autofocus attribute, just like in all the cases above, the only way to reliably autofocus inputs was to add a script immediately after the input. This case is moot now that autofocus exists, but it illustrates that there are more use-cases in the same vein. What would solve all of these use-cases is a way to register a handler that would get notified every time an element is added to the DOM that matches a particular CSS selector, which is guaranteed to run at some point before the element is actually painted. Thus it could be a special type of event that runs when the event loop spins *before* there's any opportunity to paint, or it could be semi-synchronous, or whatever, as long as it runs before paint. Then I could easily solve all the use-cases: 1) Register a handler for img that changes the .src of the newly-added Element. 2) Register a handler for .collapsed or whatever that sets the appropriate part to display: none and adds the uncollapse button. 3) Register a handler for p + details that moves the details inside the p. (This would be trickier if I sometimes put details in the middle of p, but still doable, and anyway I don't plan to.) 4) Register a handler for .autofocus or whatever that calls focus(). It seems to me this dovetails pretty nicely with some of the proposed mutation events replacement APIs. Specifically, people have been talking about allowing filtering of events, so this use-case should be solved easily enough if you can use CSS selectors as filters. In that case, the perf hit from using such events should be negligible, right? I think there are lots of cases like the four I gave above where this sort of API would be handy for very general-purpose
Re: Mutation events replacement
On 22/07/11 02:26, Adam Klein wrote: This is only complex because you're coalescing the mutations, right? In Rafael's original proposal, each mutation would result in a single immutable mutation record, so the semantics would be to deliver (by appending to a queue associated with each observer) a mutation record to any currently-registered observers. Or is there some other concern with beginning notifications partway through a task? I would suggest avoiding coalescing mutations altogether! But if you are going to, *don't* coalesce mutations when the resulting DOM tree is dependent on the order in which those mutations took place. This is critical to distributed editing applications. -- Dave Raggettd...@w3.org http://www.w3.org/People/Raggett
Re: Mutation events replacement
On Thu, Jul 21, 2011 at 4:21 PM, Boris Zbarsky bzbar...@mit.edu wrote: I'd really like numbers. Having looked at the Gecko editor code in the past, I don't share your assurance that this is how it works That said, if you point to a workload, I (or anyone else; it's open source!) can probably generate some numbers by instrumenting the Gecko DOM. But I need a workload. Pretty much any formatting command is going to involve adding and removing wrapper elements. To add a wrapper element, say adding a b around some text to make it bold, you first have to insert the wrapper before or after the thing you want to wrap, then move all the nodes to wrap into the wrapper. Likewise, to remove a wrapper, you have to first move all its contents adjacent to it, then actually remove it from its parent. Likewise, for instance, suppose you delete some text that spans blocks, like: pfoo[bar/pdivbaz]quz/div. The result will be something like pfoo[]quz/p. How do you do that? First delete bar and baz, then move quz to the p, then remove the div. Or let's say you have pfoo[]bar/p and the user hits Enter -- you first create an empty p after the existing one, then you move bar into it. Of the 37 execCommand()s I've defined, every single one will commonly move at least one node within the DOM, except for insertHorizontalRule and the ones that don't actually change the DOM (copy, selectAll, styleWithCSS, useCSS). I defined an algorithm move preserving ranges to handle this because of the range mutation problem: http://aryeh.name/spec/editcommands/editcommands.html#preserving-ranges It's invoked in 17 places in my draft currently, and nearly all of those are in general algorithms that are themselves invoked in multiple places. So I don't have any numbers, but anecdotally, editing things definitely does a lot of moving. If you want numbers, though, you probably don't want to look at my implementation -- you want some real-world software that actually uses mutation events.
Re: Mutation events replacement
On 7/22/11 11:44 AM, Aryeh Gregor wrote: Pretty much any formatting command is going to involve adding and removing wrapper elements. To add a wrapper element, say adding ab around some text to make it bold, you first have to insert the wrapper before or after the thing you want to wrap, then move all the nodes to wrap into the wrapper. Actually, you can pretty easily do it in the other order (move the text into the b, and then put the b in the DOM), and may want to so as to minimize the number of changes the the live DOM; that's something that's often recommended as a performance enhancement. Likewise, to remove a wrapper, you have to first move all its contents adjacent to it, then actually remove it from its parent. Again, these can easily happen in the opposite order So I don't have any numbers, but anecdotally, editing things definitely does a lot of moving. If you want numbers, though, you probably don't want to look at my implementation -- you want some real-world software that actually uses mutation events. I don't need software that uses mutation events. I need software that triggers editing operations, so I can them actually measure what DOM mutations are performed in the course of these editing operations. -Boris
Re: Mutation events replacement
On 22/07/11 16:54, Boris Zbarsky wrote: I don't need software that uses mutation events. I need software that triggers editing operations, so I can them actually measure what DOM mutations are performed in the course of these editing operations. How about: * The many blogging tools with rich text edit modes * Google docs is great for live communal editing as everyone can see or make changes at the same time. -- Dave Raggettd...@w3.org http://www.w3.org/People/Raggett
Re: Mutation events replacement
On 7/22/11 12:32 PM, Dave Raggett wrote: On 22/07/11 16:54, Boris Zbarsky wrote: I don't need software that uses mutation events. I need software that triggers editing operations, so I can them actually measure what DOM mutations are performed in the course of these editing operations. How about: * The many blogging tools with rich text edit modes * Google docs is great for live communal editing as everyone can see or make changes at the same time. Here's my issue. I use neither of those. So I'm not sure what a reasonable workload is for either one. If someone wants to write up a reasonable workload test description, that would be very much appreciated... -Boris
Re: Mutation events replacement
On Thu, Jul 21, 2011 at 6:26 PM, Adam Klein ad...@chromium.org wrote: On Thu, Jul 21, 2011 at 4:30 PM, Jonas Sicking jo...@sicking.cc wrote: On Thu, Jul 21, 2011 at 8:19 AM, Olli Pettay olli.pet...@helsinki.fi wrote: On 07/21/2011 06:01 PM, Boris Zbarsky wrote: On 7/21/11 5:08 AM, Dave Raggett wrote: Thanks for the explanation. Apps would need a way to disable notifications during such animation sequences, and would be able to find another means to serialize the animation (at a higher level). I'm not sure I trust apps to do that, which is why I think the default behavior should be that they just don't get the information. This raises the question is unregistering and re-regtistering a mutation notification handler cheap or do we need an alternative mechanism for temporarily suspending notifications? Olli is better able than I to answer this for Gecko. In the current WIP patch for mutation event replacement registering and unregistering listeners is cheap, and if there are no listeners, performance isn't affected at all. This is with the sync approach. If async approach is taken, listener handling becomes significantly more complicated. What if the listener is added after the mutation has happened, should it be called? If not, then we need to keep a list of changes for all the listeners separately. Hmm.. the most trivial implementation is to keep different lists for different listeners no matter what. However maybe it's worth allowing listeners to be able to share mutation objects? Is that what you were thinking? That might be good for performance since it'll create fewer objects, but it'll also add more complexity since it requires more bookkeeping. Though note that the list of mutation objects will have to be per-listener no matter what (i.e. both in the mostly-sync and the almost-async suggestions) since different listeners will be observing different parts of the tree and thus will see different set of mutations. The simplest solution that I can think of is to say that the addMutationObserver function doesn't take effect until at the end of the task. So any listener registered during a task won't get notifications until at the end of the following task. There are other solutions that we could use, but they seem much more complex and so I'd rather avoid them unless there's a good reason to. This is only complex because you're coalescing the mutations, right? In Rafael's original proposal, each mutation would result in a single immutable mutation record, so the semantics would be to deliver (by appending to a queue associated with each observer) a mutation record to any currently-registered observers. I think that's the only reason yes. / Jonas
Re: Mutation events replacement
On Tue, Jul 19, 2011 at 4:56 PM, Jonas Sicking jo...@sicking.cc wrote: On Tue, Jul 19, 2011 at 4:18 PM, Ryosuke Niwa rn...@webkit.org wrote: Thanks for the new proposal, Jonas. I'm very excited about the progress we're making towards a saner world! On Tue, Jul 19, 2011 at 4:01 PM, Jonas Sicking jo...@sicking.cc wrote: [ { target: node1, type: childlist, added: [a, b, c, d], removed: [x, y] }, { target: node1, type: attributes, changed: [class, bgcolor, href] }, { target: node2, type: characterdata }, { target: node3, type: childlist, added: [r, s, t, x], removed: [z] } ] A few things to note here: * There is only ever one entry in the array for a given target+type pair. If, for example, multiple changes are made to the classlist of a given node, these changes are added to the added/removed lists. * For childlist changes, you get the full list of which nodes were added and removed. For editing purposes, it's also crucial to know from/to where nodes are removed/inserted. It seems like adding an offset trivially solves this problem without much overhead. I'm not really sure how you're expecting to use indexes. Not that once one node is removed, the index changes for all other nodes. Can you provide a short example code demonstrating how you'd use the index? It's not really short, but it's more or less the analog of doing a projection of a set of splice events if emitted by a hypothetical observable array (startIndex, numDeleted, numAdded). The code is implemented inside MDV here: https://code.google.com/p/mdv/source/browse/trunk/model.js#853 The goal of the projection is to produce a new set of splice mutation records which represent the net effect on the sequence (joining, collapsing, canceling), such that the new set minimally describes the changes and could be applied, in order, to a copy of the sequence's previous state to arrive at the new state. * For attributes changes you get a full list of which attributes were changed. However you do not get the new and old value of the attributes as this could result in significant overhead for attributes like style for example. Again, it'll be very useful to have old and new values for editing purposes. Although I have a reservation as to whether we should do for style or not because calling mutation listeners every time script modifies some style property will be quite expensive as it requires serializing CSSStyleDeclaration. * For characterdata you don't get the old or new value of the node. We could also simply add the before/after values here as there shouldn't be as much serialization overhead involved. For editing purposes, seeing old and new value is essential. As has been previously mentioned, providing the old value comes with significant overhead. For your usecase it seems like this is overhead that you'll need to live with, but for many others it might not be. We could solve this by making it configurable if you want the old values or not. For example by having separate notification types that contain before and after values. Though note that providing the after-values rarely seems useful as you can simply get them as needed from the DOM. As for childlists, the only sane solution I can think of would be to provide what the whole childlist looked like before modifications started. / Jonas
Re: Mutation events replacement
On Fri, Jul 22, 2011 at 2:08 AM, Dave Raggett d...@w3.org wrote: On 22/07/11 02:26, Adam Klein wrote: This is only complex because you're coalescing the mutations, right? In Rafael's original proposal, each mutation would result in a single immutable mutation record, so the semantics would be to deliver (by appending to a queue associated with each observer) a mutation record to any currently-registered observers. Or is there some other concern with beginning notifications partway through a task? I would suggest avoiding coalescing mutations altogether! But if you are going to, *don't* coalesce mutations when the resulting DOM tree is dependent on the order in which those mutations took place. This is critical to distributed editing applications. The DOM should have no such behavior. The only exception to this rule that I know of is script elements. They execute their contained script the first time they are inserted into a Document, but don't undo that action when removed (for obvious reasons), nor do they redo it when inserted again. / Jonas
Re: Mutation events replacement
On Fri, Jul 22, 2011 at 9:32 AM, Dave Raggett d...@w3.org wrote: On 22/07/11 16:54, Boris Zbarsky wrote: I don't need software that uses mutation events. I need software that triggers editing operations, so I can them actually measure what DOM mutations are performed in the course of these editing operations. How about: * The many blogging tools with rich text edit modes * Google docs is great for live communal editing as everyone can see or make changes at the same time. We should have much richer events to aid with rich text editing. Using mutation notifications for this is will not create a good experience for the page author. / Jonas
Re: Mutation events replacement
On Fri, Jul 22, 2011 at 3:58 PM, Jonas Sicking jo...@sicking.cc wrote: We should have much richer events to aid with rich text editing. Using mutation notifications for this is will not create a good experience for the page author. But this is a big use case of mutation events today. If we were to replace mutation events, then we certainly need to address this use case. - Ryosuke
Re: Mutation events replacement
On Fri, Jul 22, 2011 at 4:44 PM, Ryosuke Niwa rn...@webkit.org wrote: On Fri, Jul 22, 2011 at 3:58 PM, Jonas Sicking jo...@sicking.cc wrote: We should have much richer events to aid with rich text editing. Using mutation notifications for this is will not create a good experience for the page author. But this is a big use case of mutation events today. If we were to replace mutation events, then we certainly need to address this use case. I wouldn't want to remove mutation events from browsers before adding better editing events I think. / Jonas
Re: Mutation events replacement
On 20/07/11 18:23, Boris Zbarsky wrote: It's pretty common to have situations where lots (10-20) of properties are set in inline style, especially in cases where the inline style is being changed via CSS2Properties from script (think animations and the like, where the objects being animated tend to have width, height, various margin/border/padding/background properties, top, left, etc all set). Those are precisely the cases that are most performance-sensitive and where the overhead of serializing the style attribute on every mutation is highest due to the large number of properties set. Thanks for the explanation. Apps would need a way to disable notifications during such animation sequences, and would be able to find another means to serialize the animation (at a higher level). This raises the question is unregistering and re-regtistering a mutation notification handler cheap or do we need an alternative mechanism for temporarily suspending notifications? -- Dave Raggettd...@w3.org http://www.w3.org/People/Raggett
Re: Mutation events replacement
On 07/21/2011 01:56 AM, Jonas Sicking wrote: On Wed, Jul 20, 2011 at 10:30 AM, Olli Pettayolli.pet...@helsinki.fi wrote: On 07/20/2011 06:46 PM, Jonas Sicking wrote: Hence I'm leaning towards using the almost-asynchronous proposal for now. If we end up getting the feedback from people that use mutation events today that they won't be able to solve the same use cases, then we can consider using the synchronous notifications. However I think that it would be beneficial to try to go almost-async for now. I disagree. I had hoped for a bit more of an explanation than that ;-) Such as why do you not think that synchronous events will be a problem for web developers just like they have been for us? In practice synchronous events have been a problem to us because we are in C++, which is unsafe language. Web devs use JS. The only C++ specific problem that we've had is dangling pointers. However only a small set of our problems would have been solved by making local points strong. only a small set of our problems is a major understatement. Huge number of problems have been fixed using either strong references or things like nsWeakFrame (the original reason for nsWeakFrame was to fix a class of bugs related to dangling pointers which were caused by mutation event listeners doing something unexpected). We'd still have problems with indexes changing under us, and nodes that we removed from one location now being inserted elsewhere, etc. Another way to look at it is that due to C++ specific problems, when unexpected things happen during callbacks, we end up possibly crashing. In Javascript, if unexpected things happen during callbacks, you'll get buggy behavior. That's better, but still not good. Web devs usually want something synchronous, like sync XHR (sync XHR has other problems not related to mutation handling). Synchronous is easier to understand. That is a wholly different type of sync API. Those are APIs where the return value is delivered through a callback rather than as a return value, forcing you to create awkward code like: doSomething(function(res1) { res1.callFunc(function(res2) { doSomethingElse(res2); } } There's a very good problem description here: http://tamejs.org/ (ignore the proposed solution, it's the problem description that's interesting for this discussion). Reading... will comment later. Those problems aren't happening here as far as I can tell. There are no return values delivered asynchronously, nor any of the problems described in the tamejs page. / Jonas
Re: Mutation events replacement
On 7/21/11 5:08 AM, Dave Raggett wrote: Thanks for the explanation. Apps would need a way to disable notifications during such animation sequences, and would be able to find another means to serialize the animation (at a higher level). I'm not sure I trust apps to do that, which is why I think the default behavior should be that they just don't get the information. This raises the question is unregistering and re-regtistering a mutation notification handler cheap or do we need an alternative mechanism for temporarily suspending notifications? Olli is better able than I to answer this for Gecko. -Boris
Re: Mutation events replacement
On 07/21/2011 06:01 PM, Boris Zbarsky wrote: On 7/21/11 5:08 AM, Dave Raggett wrote: Thanks for the explanation. Apps would need a way to disable notifications during such animation sequences, and would be able to find another means to serialize the animation (at a higher level). I'm not sure I trust apps to do that, which is why I think the default behavior should be that they just don't get the information. This raises the question is unregistering and re-regtistering a mutation notification handler cheap or do we need an alternative mechanism for temporarily suspending notifications? Olli is better able than I to answer this for Gecko. In the current WIP patch for mutation event replacement registering and unregistering listeners is cheap, and if there are no listeners, performance isn't affected at all. This is with the sync approach. If async approach is taken, listener handling becomes significantly more complicated. What if the listener is added after the mutation has happened, should it be called? If not, then we need to keep a list of changes for all the listeners separately. If yes, then we need to track all the changes all the time, even if there are no listeners. -Olli
Re: Mutation events replacement
On Wed, Jul 20, 2011 at 3:11 PM, Ryosuke Niwa rn...@webkit.org wrote: But internally, a node movement is a removal then an insertion. There's always possibility that a node gets removed then inserted again after mutation observers are invoked. Also, what happens if a function removed a bunch of nodes and then inserted back one of them? I'm suggesting that we change insertNode()/appendChild()/etc. so that they're *not* internally a removal then an insertion: they're internally atomic. If you call foo.removeChild(bar); foo.appendChild(bar) then that would be a remove/insert no matter what. But if you call foo.appendChild(bar) and bar has a parent and bar is not the last child of foo, that would be a move. Yes, this causes problems as long as mutation events exist. But when mutation event handlers modify the DOM, behavior is undefined and is totally inconsistent between browsers in practice, so I don't think it's a big deal. Just do whatever's convenient and leave the behavior inconsistent in this case like in others. We don't need to standardize behavior here unless we're going to standardize behavior in all other cases where DOM mutation listeners mutate the DOM, which we aren't. On Wed, Jul 20, 2011 at 10:17 PM, Boris Zbarsky bzbar...@mit.edu wrote: What I do have a strong opinion on is that it would be good to have some data on how common move operations are compared to remove and insert on the web. Then we'll at least know how common or edge-case the situation is and hence how much effort we should spend on optimizing for it... I can say that it's very common and critical for editors. Tons of what you're doing is shuffling nodes around: splitting up text nodes and wrapping bits of them in new elements that you just inserted before them, moving all the contents of an element next to it before you remove it, etc. Editors of various types seem like they're one of the big use-cases for a mutation events replacement anyway, so my guess is it's important. But nobody's even made a list of use-cases for mutation listeners, have they? I don't think moving nodes is as common a use-case for typical sites. But typical sites don't want mutation listeners either, so they aren't what we should be concerned about here.
Re: Mutation events replacement
On 7/21/11 4:15 PM, Aryeh Gregor wrote: I can say that it's very common and critical for editors. I'd really like numbers. Having looked at the Gecko editor code in the past, I don't share your assurance that this is how it works That said, if you point to a workload, I (or anyone else; it's open source!) can probably generate some numbers by instrumenting the Gecko DOM. But I need a workload. -Boris
Re: Mutation events replacement
On Thu, Jul 21, 2011 at 8:19 AM, Olli Pettay olli.pet...@helsinki.fi wrote: On 07/21/2011 06:01 PM, Boris Zbarsky wrote: On 7/21/11 5:08 AM, Dave Raggett wrote: Thanks for the explanation. Apps would need a way to disable notifications during such animation sequences, and would be able to find another means to serialize the animation (at a higher level). I'm not sure I trust apps to do that, which is why I think the default behavior should be that they just don't get the information. This raises the question is unregistering and re-regtistering a mutation notification handler cheap or do we need an alternative mechanism for temporarily suspending notifications? Olli is better able than I to answer this for Gecko. In the current WIP patch for mutation event replacement registering and unregistering listeners is cheap, and if there are no listeners, performance isn't affected at all. This is with the sync approach. If async approach is taken, listener handling becomes significantly more complicated. What if the listener is added after the mutation has happened, should it be called? If not, then we need to keep a list of changes for all the listeners separately. Hmm.. the most trivial implementation is to keep different lists for different listeners no matter what. However maybe it's worth allowing listeners to be able to share mutation objects? Is that what you were thinking? That might be good for performance since it'll create fewer objects, but it'll also add more complexity since it requires more bookkeeping. Though note that the list of mutation objects will have to be per-listener no matter what (i.e. both in the mostly-sync and the almost-async suggestions) since different listeners will be observing different parts of the tree and thus will see different set of mutations. The simplest solution that I can think of is to say that the addMutationObserver function doesn't take effect until at the end of the task. So any listener registered during a task won't get notifications until at the end of the following task. There are other solutions that we could use, but they seem much more complex and so I'd rather avoid them unless there's a good reason to. / Jonas
Re: Mutation events replacement
On Thu, Jul 21, 2011 at 4:30 PM, Jonas Sicking jo...@sicking.cc wrote: On Thu, Jul 21, 2011 at 8:19 AM, Olli Pettay olli.pet...@helsinki.fi wrote: On 07/21/2011 06:01 PM, Boris Zbarsky wrote: On 7/21/11 5:08 AM, Dave Raggett wrote: Thanks for the explanation. Apps would need a way to disable notifications during such animation sequences, and would be able to find another means to serialize the animation (at a higher level). I'm not sure I trust apps to do that, which is why I think the default behavior should be that they just don't get the information. This raises the question is unregistering and re-regtistering a mutation notification handler cheap or do we need an alternative mechanism for temporarily suspending notifications? Olli is better able than I to answer this for Gecko. In the current WIP patch for mutation event replacement registering and unregistering listeners is cheap, and if there are no listeners, performance isn't affected at all. This is with the sync approach. If async approach is taken, listener handling becomes significantly more complicated. What if the listener is added after the mutation has happened, should it be called? If not, then we need to keep a list of changes for all the listeners separately. Hmm.. the most trivial implementation is to keep different lists for different listeners no matter what. However maybe it's worth allowing listeners to be able to share mutation objects? Is that what you were thinking? That might be good for performance since it'll create fewer objects, but it'll also add more complexity since it requires more bookkeeping. Though note that the list of mutation objects will have to be per-listener no matter what (i.e. both in the mostly-sync and the almost-async suggestions) since different listeners will be observing different parts of the tree and thus will see different set of mutations. The simplest solution that I can think of is to say that the addMutationObserver function doesn't take effect until at the end of the task. So any listener registered during a task won't get notifications until at the end of the following task. There are other solutions that we could use, but they seem much more complex and so I'd rather avoid them unless there's a good reason to. This is only complex because you're coalescing the mutations, right? In Rafael's original proposal, each mutation would result in a single immutable mutation record, so the semantics would be to deliver (by appending to a queue associated with each observer) a mutation record to any currently-registered observers. Or is there some other concern with beginning notifications partway through a task? - Adam
Re: Mutation events replacement
Thanks Jonas for the proposal. For changes to attributes and changes to the value of text nodes, it should be possible to applications to request to see the before/after values. You note that style attributes may be long as an argument against permitting applications to see the before value. But what if an editing application really wants to see such changes. It could create a copy of the DOM, but that is even more expensive when editing a sizable document. The size of text nodes may also be long, but a remote editing application could determine whether running a diff algorithm against the before/after strings is justified for serialization purposes. In summary, let's allow applications to choose what data they want to see! The almost asynchronous (option 2) works for me for the timing issue. -- Dave Raggettd...@w3.org http://www.w3.org/People/Raggett
Re: Mutation events replacement
On 20/07/11 00:56, Jonas Sicking wrote: On Tue, Jul 19, 2011 at 4:18 PM, Ryosuke Niwarn...@webkit.org wrote: For editing purposes, it's also crucial to know from/to where nodes are removed/inserted. It seems like adding an offset trivially solves this problem without much overhead. I'm not really sure how you're expecting to use indexes. Not that once one node is removed, the index changes for all other nodes. Can you provide a short example code demonstrating how you'd use the index? Actually, this is what I use in my editing app. The serialization of a change involves a tumbler naming scheme for DOM nodes based upon the child number, e.g.4.2.1 identifies 1st child of the 2nd child of the 4th child of the editing root element. With the current mutation events, the application script has to compute these reference strings and can do so because of the synchronous nature of existing mutation events, and the knowledge that this particular application doesn't mutate the DOM within such mutation event handlers. For the notification scheme, this could be computed by the browser, and it would be critical for the notification array of changes to be in the sequence in which they occurred. This allows other editing clients to apply the changes correctly, despite the reference string changing as nodes before or above it are inserted or removed. If the notification scheme only supplies the index number for the node that was inserted/removed/changed the app will need a way to find the index numbers for that node's ancestors at the time the mutation took place. This presents challenges since the notified sequence of mutations may have altered the index number for the node's parent (perhaps even removing it from the tree) subsequent to the mutation in question. A two pass algorithm may be possible that uses the state of the DOM tree *after* the sequence of mutations has been applied to compute the tumbler for each node at the time a given mutation occurred given the child number reported for the mutation. This index number needs to be the position at which a new child was added, or the position of the child that was removed. I would much prefer the notification to supply the tumbler for the node based upon a designated root element, supplied when the notification observer was registered. If you are concerned with the cost of computing the tumbler, this could be done only when the application requests it. p.s. there should be a lightweight means to temporarily disable notifications when the application performs house keeping operations on the DOM tree. -- Dave Raggettd...@w3.org http://www.w3.org/People/Raggett
Re: Mutation events replacement
On 07/20/2011 02:01 AM, Jonas Sicking wrote: On Thu, Jul 7, 2011 at 6:38 PM, Jonas Sickingjo...@sicking.cc wrote: On Thu, Jul 7, 2011 at 5:23 PM, Rafael Weinsteinrafa...@google.com wrote: So yes, my proposal only solves the usecase outside mutation handlers. However this is arguably better than never solving the use case as in your proposal. I'm sure people will end up writing buggy code, but ideally this will be found and fixed fairly easily as the behavior is consistent. We are at least giving people the tools needed to implement the synchronous behavior. Ok. Thanks for clarifying. It's helpful to understand this. I'm glad there's mostly common ground on the larger issue. The point of contention is clearly whether accommodating some form of sync mutation actions is a goal or non-goal. Yup, that seems to be the case. I think the main reason I'm arguing for allowing synchronous callbacks is that I'm concerned that without them people are going to stick to mutation events. If I was designing this feature from scratch, I'd be much happier to use some sort of async callback. However given that we need something that people can migrate to, and we don't really know what they're using mutation events for, I'm more conservative. Ok, here is my updated proposal. There are two issues at stake here: When to send notifications, and what they contain. I'll get to when to send them second as that is a more controversial. As for what the notification contain, lets first start at how to register for notifications. Since we want a single callback to contain information about all mutations that has happened, we need the ability to choose, for a single callback, which mutations we should tell it about. Something like this would work: node.addMutationListener(listener, { childlist: true, attributes: true, characterdata: true }); node.removeMutationListener(listener); 'listener' above would be a function which receives a single argument when notifications fire. The value of this argument would be an Array which could look something like this: [ { target: node1, type: childlist, added: [a, b, c, d], removed: [x, y] }, { target: node1, type: attributes, changed: [class, bgcolor, href] }, { target: node2, type: characterdata }, { target: node3, type: childlist, added: [r, s, t, x], removed: [z] } ] A few things to note here: * There is only ever one entry in the array for a given target+type pair. If, for example, multiple changes are made to the classlist of a given node, these changes are added to the added/removed lists. * For childlist changes, you get the full list of which nodes were added and removed. * For attributes changes you get a full list of which attributes were changed. However you do not get the new and old value of the attributes as this could result in significant overhead for attributes like style for example. * For characterdata you don't get the old or new value of the node. We could also simply add the before/after values here as there shouldn't be as much serialization overhead involved. A nice thing with the above approach is that it is very expandable if we want to introduce more types of notifications in the future. Some examples that have been mentioned are the ability to be notified about class changes, text-content changes and changes to individual attributes. We could do that using: node.addMutationListener(listener, { class: [myclass1, warning], textcontent: true, attributes: [type, title, data-foo] }); We could also add the ability to get notified about microdata changes or data-prefix-* changes. But for now I think we should start with a minimal set and see if people use it. But it's good to know that we have a path forward. There are of course a few more things that needs to be defined. Here some of them: * The notification-objects are added to the list in the order they happen. With the exception that if there is a notification-object for the specific target+type then a new object isn't created, but rather added to the existing one. * If you call addMutationListener with the same listener multiple times any new flags are added to the existing registration. So node.addMutationListener(listener, { attributes: true }); node.addMutationListener(listener, { childlist: true }); is equivalent to node.addMutationListener(listener, { childlist: true, attributes: true }); * For the childlist notifications, nodes are added to the added/removed lists in document order when a whole list of them are added or removed. For example for .appendChild(docfragment) or .textContent = . * If a node is first added and then removed from a childlist, it doesn't appear in neither the added nor the removed lists for the childlist notification. * If a node is removed and then readded to a childlist, it appears in both the added and the removed lists. This is needed to indicate that it might have a different location now. So, this leaves the issue of when to fire these notifications.
Re: Mutation events replacement
On 7/20/11 4:26 AM, Dave Raggett wrote: You note that style attributes may be long as an argument against permitting applications to see the before value. The problem is not the length per se. The problem is that the value is not stored anywhere and has to be generated based on other data structures, which can be very expensive. For example, as sane algorithm for generating this value will examine all the individual CSS property values to determine which of them can be collapsed into shorthands. In summary, let's allow applications to choose what data they want to see! As long as the slow as molasses behavior is opt-in, not opt-out... ;) -Boris
Re: Mutation events replacement
On 20/07/11 16:32, Boris Zbarsky wrote: On 7/20/11 4:26 AM, Dave Raggett wrote: You note that style attributes may be long as an argument against permitting applications to see the before value. The problem is not the length per se. The problem is that the value is not stored anywhere and has to be generated based on other data structures, which can be very expensive. For example, as sane algorithm for generating this value will examine all the individual CSS property values to determine which of them can be collapsed into shorthands. In summary, let's allow applications to choose what data they want to see! As long as the slow as molasses behavior is opt-in, not opt-out... ;) -Boris Perhaps we need to distinguish auto generated attributes from those that are set by markup or scripts. Could you please clarify for me the difference between the html style attribute and the one you are referring to? My understanding is that the html style attribute is set via markup or scripts and *doesn't* reflect all of the computed style properties for this DOM node. Many thanks, -- Dave Raggettd...@w3.org http://www.w3.org/People/Raggett
Re: Mutation events replacement
On Wed, Jul 20, 2011 at 5:20 AM, Olli Pettay olli.pet...@helsinki.fi wrote: On 07/20/2011 02:01 AM, Jonas Sicking wrote: On Thu, Jul 7, 2011 at 6:38 PM, Jonas Sickingjo...@sicking.cc wrote: On Thu, Jul 7, 2011 at 5:23 PM, Rafael Weinsteinrafa...@google.com wrote: So yes, my proposal only solves the usecase outside mutation handlers. However this is arguably better than never solving the use case as in your proposal. I'm sure people will end up writing buggy code, but ideally this will be found and fixed fairly easily as the behavior is consistent. We are at least giving people the tools needed to implement the synchronous behavior. Ok. Thanks for clarifying. It's helpful to understand this. I'm glad there's mostly common ground on the larger issue. The point of contention is clearly whether accommodating some form of sync mutation actions is a goal or non-goal. Yup, that seems to be the case. I think the main reason I'm arguing for allowing synchronous callbacks is that I'm concerned that without them people are going to stick to mutation events. If I was designing this feature from scratch, I'd be much happier to use some sort of async callback. However given that we need something that people can migrate to, and we don't really know what they're using mutation events for, I'm more conservative. Ok, here is my updated proposal. There are two issues at stake here: When to send notifications, and what they contain. I'll get to when to send them second as that is a more controversial. As for what the notification contain, lets first start at how to register for notifications. Since we want a single callback to contain information about all mutations that has happened, we need the ability to choose, for a single callback, which mutations we should tell it about. Something like this would work: node.addMutationListener(listener, { childlist: true, attributes: true, characterdata: true }); node.removeMutationListener(listener); 'listener' above would be a function which receives a single argument when notifications fire. The value of this argument would be an Array which could look something like this: [ { target: node1, type: childlist, added: [a, b, c, d], removed: [x, y] }, { target: node1, type: attributes, changed: [class, bgcolor, href] }, { target: node2, type: characterdata }, { target: node3, type: childlist, added: [r, s, t, x], removed: [z] } ] A few things to note here: * There is only ever one entry in the array for a given target+type pair. If, for example, multiple changes are made to the classlist of a given node, these changes are added to the added/removed lists. * For childlist changes, you get the full list of which nodes were added and removed. * For attributes changes you get a full list of which attributes were changed. However you do not get the new and old value of the attributes as this could result in significant overhead for attributes like style for example. * For characterdata you don't get the old or new value of the node. We could also simply add the before/after values here as there shouldn't be as much serialization overhead involved. A nice thing with the above approach is that it is very expandable if we want to introduce more types of notifications in the future. Some examples that have been mentioned are the ability to be notified about class changes, text-content changes and changes to individual attributes. We could do that using: node.addMutationListener(listener, { class: [myclass1, warning], textcontent: true, attributes: [type, title, data-foo] }); We could also add the ability to get notified about microdata changes or data-prefix-* changes. But for now I think we should start with a minimal set and see if people use it. But it's good to know that we have a path forward. There are of course a few more things that needs to be defined. Here some of them: * The notification-objects are added to the list in the order they happen. With the exception that if there is a notification-object for the specific target+type then a new object isn't created, but rather added to the existing one. * If you call addMutationListener with the same listener multiple times any new flags are added to the existing registration. So node.addMutationListener(listener, { attributes: true }); node.addMutationListener(listener, { childlist: true }); is equivalent to node.addMutationListener(listener, { childlist: true, attributes: true }); * For the childlist notifications, nodes are added to the added/removed lists in document order when a whole list of them are added or removed. For example for .appendChild(docfragment) or .textContent = . * If a node is first added and then removed from a childlist, it doesn't appear in neither the added nor the removed lists for the childlist notification. * If a node is removed and then readded to a childlist, it appears in both the added and
Re: Mutation events replacement
On Wed, Jul 20, 2011 at 8:43 AM, Dave Raggett d...@w3.org wrote: Perhaps we need to distinguish auto generated attributes from those that are set by markup or scripts. Could you please clarify for me the difference between the html style attribute and the one you are referring to? My understanding is that the html style attribute is set via markup or scripts and *doesn't* reflect all of the computed style properties for this DOM node. In WebKit, style attribute is stored as CSSMutableStyleDeclaration instead of as a string to allow fast style re-calculation. - Ryosuke
Re: Mutation events replacement
On 20/07/11 17:05, Ryosuke Niwa wrote: On Wed, Jul 20, 2011 at 8:43 AM, Dave Raggett d...@w3.org mailto:d...@w3.org wrote: Perhaps we need to distinguish auto generated attributes from those that are set by markup or scripts. Could you please clarify for me the difference between the html style attribute and the one you are referring to? My understanding is that the html style attribute is set via markup or scripts and *doesn't* reflect all of the computed style properties for this DOM node. In WebKit, style attribute is stored as CSSMutableStyleDeclaration instead of as a string to allow fast style re-calculation. Isn't there a cheap way to distinguish changes to the DOM (setAttribute) from indirect changes to how CSSMutableStyleDeclaration is formatted to text? It sounds as if you already have a setter function that knows how to update the CSSMutableStyleDeclaration from a string, so I would have thought that this is easy to deal with, right? -- Dave Raggettd...@w3.org http://www.w3.org/People/Raggett
Re: Mutation events replacement
On Wed, Jul 20, 2011 at 9:32 AM, Dave Raggett d...@w3.org wrote: Isn't there a cheap way to distinguish changes to the DOM (setAttribute) from indirect changes to how CSSMutableStyleDeclaration is formatted to text? It sounds as if you already have a setter function that knows how to update the CSSMutableStyleDeclaration from a string, so I would have thought that this is easy to deal with, right? Yes. The real issue isn't that modifying style attribute as string is expensive (of course it is expensive) but the problem is that the fact our internal representation of style attribute isn't string so that whenever style attribute is changed, we'd have to serialize CSSMutableStyleDeclaration. I'm not sure how much of cost that is in practice because style attribute tends to be short in many cases but this feature cannot be turned on by default as it becomes a significant performance burden on UA for all other use cases. Maybe we can treat style attribute differently and tell which property was added/removed/modified? - Ryosuke
Re: Mutation events replacement
* Dave Raggett wrote: Perhaps we need to distinguish auto generated attributes from those that are set by markup or scripts. Could you please clarify for me the difference between the html style attribute and the one you are referring to? My understanding is that the html style attribute is set via markup or scripts and *doesn't* reflect all of the computed style properties for this DOM node. You can manipulate the style attribute using DOM Level 2 Style features like the ElementCSSInlineStyle interface instead of setting the value as a string as you would when using .setAttribute and similar features. p.../p script onload = function() { var p = document.getElementsByTagName('p').item(0); p.style.margin = '0'; alert(p.getAttribute('style')) } /script This would alert something like `margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px` or `margin: 0px;`. -- Björn Höhrmann · mailto:bjo...@hoehrmann.de · http://bjoern.hoehrmann.de Am Badedeich 7 · Telefon: +49(0)160/4415681 · http://www.bjoernsworld.de 25899 Dagebüll · PGP Pub. KeyID: 0xA4357E78 · http://www.websitedev.de/
Re: Mutation events replacement
On 7/20/11 11:43 AM, Dave Raggett wrote: Perhaps we need to distinguish auto generated attributes from those that are set by markup or scripts. I'm not sure what you mean. Could you please clarify for me the difference between the html style attribute and the one you are referring to? There isn't one. My understanding is that the html style attribute is set via markup or scripts and *doesn't* reflect all of the computed style properties for this DOM node. That's correct. Let me give you a concrete testcase: !DOCTYPE html body style=margin: 0; script document.body.style.marginLeft = 5px; document.body.style.marginRight = 5px; document.body.style.top = 17px; alert(document.body.getAttribute(style)); /script This alerts margin: 0pt 5px; top: 17px in Gecko, margin-top: 0px; margin-bottom: 0px; margin-left: 5px; margin-right: 5px; top: 17px in WebKit and Presto. So the value of the style attribute changes when you modify inline style via the CSS2Properties and CSSDeclaration interfaces. Not only that, but what's stored are the longhand property names and values, with shorthand generation happening at serialization time in at least some UAs. It's pretty common to have situations where lots (10-20) of properties are set in inline style, especially in cases where the inline style is being changed via CSS2Properties from script (think animations and the like, where the objects being animated tend to have width, height, various margin/border/padding/background properties, top, left, etc all set). Those are precisely the cases that are most performance-sensitive and where the overhead of serializing the style attribute on every mutation is highest due to the large number of properties set. -Boris
Re: Mutation events replacement
On 07/20/2011 06:46 PM, Jonas Sicking wrote: Hence I'm leaning towards using the almost-asynchronous proposal for now. If we end up getting the feedback from people that use mutation events today that they won't be able to solve the same use cases, then we can consider using the synchronous notifications. However I think that it would be beneficial to try to go almost-async for now. I disagree. I had hoped for a bit more of an explanation than that ;-) Such as why do you not think that synchronous events will be a problem for web developers just like they have been for us? In practice synchronous events have been a problem to us because we are in C++, which is unsafe language. Web devs use JS. Web devs usually want something synchronous, like sync XHR (sync XHR has other problems not related to mutation handling). Synchronous is easier to understand. -Olli / Jonas
Re: Mutation events replacement
* Boris Zbarsky wrote: It's pretty common to have situations where lots (10-20) of properties are set in inline style, especially in cases where the inline style is being changed via CSS2Properties from script (think animations and the like, where the objects being animated tend to have width, height, various margin/border/padding/background properties, top, left, etc all set). Those are precisely the cases that are most performance-sensitive and where the overhead of serializing the style attribute on every mutation is highest due to the large number of properties set. Depending on the design of the mutation notification system and what level of complexity people find palatable, it would naturally also be possible to serialize lazily and additionally limit when the values are available (further style changes made by the listener could in- validate the information and you'd get an exception on access, for instance). So the information being available as part of the API does not necessarily imply performance problems. -- Björn Höhrmann · mailto:bjo...@hoehrmann.de · http://bjoern.hoehrmann.de Am Badedeich 7 · Telefon: +49(0)160/4415681 · http://www.bjoernsworld.de 25899 Dagebüll · PGP Pub. KeyID: 0xA4357E78 · http://www.websitedev.de/
Re: Mutation events replacement
On Wed, Jul 20, 2011 at 1:43 AM, David Flanagan dflana...@mozilla.com wrote: Finally, I still think it is worth thinking about trying to model the distinction between removing nodes from the document tree and moving them (atomically) within the tree. I'll chip in that I think this is useful. It makes things somewhat more complicated, but remove/insert and move are conceptually very different. I'd really want to handle them differently for range mutations, as I previously explained: http://lists.whatwg.org/htdig.cgi/whatwg-whatwg.org/2011-March/031053.html A move operation is unnecessary if the goal is to synchronize changes between DOMs, but it's useful if the goal is to store and update data about the nodes themselves. In that case, moving a node could imply a very different sort of change to the data than removing/inserting. Specifically, you might want to throw away data when a node is removed, but keep it when the node is moved. Like: * If some nodes get moved to a nearby position and are in a Range to start with, they might conceptually belong in the Range afterward. See the examples in the e-mail I link to before. If they're removed and re-inserted, you have to keep extra state somewhere to track that. In my edit commands spec, I had to work around this in many different places by defining special primitives like move preserving ranges, or in some cases by manually saying For every Range with boundary point X, do Y. * If you're associating spellcheck data with text nodes in an editable region, then if a node gets moved elsewhere within the region, you want to keep the data. If it gets removed, you want to throw away the data. * Other things? Of course, we'd have to update every method anywhere that moves nodes to do so atomically instead of removing then inserting. Do we have a list of use-cases for mutation events anywhere?
Re: Mutation events replacement
* Boris Zbarsky wrote: On 7/20/11 2:19 PM, Bjoern Hoehrmann wrote: Depending on the design of the mutation notification system and what level of complexity people find palatable, it would naturally also be possible to serialize lazily The only way to do that is to make sure the pre-mutation data is kept somewhere. Doing that is _expensive_. We (Gecko) have been there, done that, and moved away from it. Simple example: you get a notification whenever a script could observe the .getAttribute value changes, and you get it before the change is applied. Then you have all the data you need without expending effort on that: you have the old state directly, and you know what change you're about to make; the serialization code would just have to be able to pro- duce a string assuming certain changes were made (which may be easy or hard depending on implementation details). Not a suggestion, but with the idea being to re-design the system from scratch, it does seem important to understand that the cost here is not coming from offering old and new values while notifying about changes, but from the combination of doing that and other design decisions like notifying after applying changes, allowing notifications to trigger new changes, and so on. We got here from confusion about why it's expensive. -- Björn Höhrmann · mailto:bjo...@hoehrmann.de · http://bjoern.hoehrmann.de Am Badedeich 7 · Telefon: +49(0)160/4415681 · http://www.bjoernsworld.de 25899 Dagebüll · PGP Pub. KeyID: 0xA4357E78 · http://www.websitedev.de/
Re: Mutation events replacement
On Wed, Jul 20, 2011 at 12:53 PM, David Flanagan dflana...@mozilla.comwrote: On 7/20/11 12:11 PM, Ryosuke Niwa wrote: But internally, a node movement is a removal then an insertion. There's always possibility that a node gets removed then inserted again after mutation observers are invoked. Also, what happens if a function removed a bunch of nodes and then inserted back one of them? My definition of moving a node atomically is taking a node that is already in the tree and passing it to appendChild() or insertBefore(). Everything else is regular node removal followed by node insertion. But appendChild or insertBefore can happen as a part of a larger operation. Then we'd end up splitting the list in half whenever we have this move entry. Also, since the existing mutation events and new API must co-exist for some period of time, there's a chance that mutation event listeners can modify DOM while the node is detached from the document synchronously. If, on the other hand, there was some way for the listener to know that the node was moved atomically, then it would know that it hadn't missed any mutation events and it could retain whatever cached state it had for node A, changing only the parent. I'm not sure if we can have a concept of atomicity in DOM. Boris might have a strong opinion on this. Here's one possible way that the distinction between move and remove could be made. Keep the added and removed lists of nodes exactly as they are now. But when a node is moved atomically, also add it to an array of moved nodes. Listeners that don't care about the move/remove distinction can just use the added and removed properties as in the current proposal. But listeners that do care can check any added or removed node against the moved array to see if it was an atomic move. That sounds like a reasonable compromise since it can be an opt-in feature, and UAs probably don't have to do anything special while making the mutation list. - Ryosuke
Re: Mutation events replacement
On 7/20/11 12:11 PM, Ryosuke Niwa wrote: On Wed, Jul 20, 2011 at 11:56 AM, Aryeh Gregor simetrical+...@gmail.com mailto:simetrical%2b...@gmail.com wrote: On Wed, Jul 20, 2011 at 1:43 AM, David Flanagan dflana...@mozilla.com mailto:dflana...@mozilla.com wrote: Finally, I still think it is worth thinking about trying to model the distinction between removing nodes from the document tree and moving them (atomically) within the tree. I'll chip in that I think this is useful. It makes things somewhat more complicated, but remove/insert and move are conceptually very different. But internally, a node movement is a removal then an insertion. There's always possibility that a node gets removed then inserted again after mutation observers are invoked. Also, what happens if a function removed a bunch of nodes and then inserted back one of them? My definition of moving a node atomically is taking a node that is already in the tree and passing it to appendChild() or insertBefore(). Everything else is regular node removal followed by node insertion. If you get a mutation event that says that node A was removed from node B and inserted into node C, you know nothing about the state of node A, since it could have been mutated while it was outside of the tree and no mutation events would have been recorded. Its attributes, text and children all could have changed, so the mutation listener has to basically discard everything it knows about node A and treat it as a brand-new node. If, on the other hand, there was some way for the listener to know that the node was moved atomically, then it would know that it hadn't missed any mutation events and it could retain whatever cached state it had for node A, changing only the parent. Here's one possible way that the distinction between move and remove could be made: keep the added and removed lists of nodes exactly as they are now. But when a node is moved atomically, also add it to an array of moved nodes. Listeners that don't care about the move/remove distinction can just use the added and removed properties as in the current proposal. But listeners that do care can check any added or removed node against the moved array to see if it was an atomic move. Another approach that might work: define a reparent or move mutation event type. So when node A is moved from parent B to parent C, the mutations would be: [{target:A, type:reparent}, {target:B, type:childList, removed:[A]}, {target:C, type:childList, added:[B]}] To make this work, if an atomic move was followed by a removal, the reparent mutation would have to be removed from the list of mutations. David e.g. say we have divhellobrworldbrw3cbr/div And we have a hypothetical function that does: 1. Remove all children of div 2. Inserts w3c back after div. Then what should the list of mutations contain? Should it contain 2 items one that says it removed hello, world, and 3 br's, and then another one saying w3c moved? But then child nodes are not all consecutive and scripts won't be able to infer where these nodes were even if we provided offsets or before/after node. Should it contain 3 items, one that says hello, world and the first 2 br's are removed, then one for moving w3c, and then another one for removing the last br? But then UAs have to keep reorganizing the list as the function modifies more DOM because there is no way to differentiate w3c until it's inserted back into DOM. - Ryosuke
Re: Mutation events replacement
On Tue, Jul 19, 2011 at 8:23 PM, Boris Zbarsky bzbar...@mit.edu wrote: On 7/19/11 7:18 PM, Ryosuke Niwa Software Engineer Google Inc. wrote: For editing purposes, it's also crucial to know from/to where nodes are removed/inserted. It seems like adding an offset trivially solves this problem without much overhead. I'm not convinced about without much overhead. In general, adding an offset is O(N) in number of childnodes in many existing implementations that can be improved, but only at the cost of more memory or performance elsewhere. That's a good point. It should probably before/after node instead. Again, it'll be very useful to have old and new values for editing purposes. Although I have a reservation as to whether we should do for style or not because calling mutation listeners every time script modifies some style property will be quite expensive as it requires serializing CSSStyleDeclaration. Yes, that is _exactly_ the problem. Right so it should be an opt-in feature as you suggested. - Ryosuke
Re: Mutation events replacement
On 7/19/11 4:01 PM, Jonas Sicking wrote: 'listener' above would be a function which receives a single argument when notifications fire. The value of this argument would be an Array which could look something like this: [ { target: node1, type: childlist, added: [a, b, c, d], removed: [x, y] }, { target: node1, type: attributes, changed: [class, bgcolor, href] }, { target: node2, type: characterdata }, { target: node3, type: childlist, added: [r, s, t, x], removed: [z] } ] Given that childlist and attribute changes are merged together into arrays, there is, in general, no way to reconstruct the ordering of mutations. In the example above, I'd assume that the first change to node1 occurred before the change to node 2. But there are 8 other changes to node1 and we know nothing about their ordering relative to the others. So, if mutation order is not preserved, is an array the right data structure for this unordered set of mutations? There is a Map type proposed for ES.next that allows objects as keys: http://wiki.ecmascript.org/doku.php?id=harmony:simple_maps_and_sets Would it be helpful to use that as the basis of this data structure? Or could DOMCore define a NodeMap type (to go along with NodeList?) I don't have a specific use-case in mind, but I wanted to bring this up since I imagine it would be nice to be able to quickly find all mutations for a given target node without having to do a linear search with Array.filter() or similar. David
Re: Mutation events replacement
On Wed, Jul 20, 2011 at 10:30 AM, Olli Pettay olli.pet...@helsinki.fiwrote: On 07/20/2011 06:46 PM, Jonas Sicking wrote: Hence I'm leaning towards using the almost-asynchronous proposal for now. If we end up getting the feedback from people that use mutation events today that they won't be able to solve the same use cases, then we can consider using the synchronous notifications. However I think that it would be beneficial to try to go almost-async for now. I disagree. I had hoped for a bit more of an explanation than that ;-) Such as why do you not think that synchronous events will be a problem for web developers just like they have been for us? In practice synchronous events have been a problem to us because we are in C++, which is unsafe language. Web devs use JS. In many cases, where you would have had a crash in C++, you would have a bug and/or exception in JS. It's for exactly the same reason. Your code cannot make assumptions about the state of the DOM because other code may have run that changes it out from under you. A contrived example: var firstChild = node.firstChild; node.appendChild(randomNode); // Some mutation handler runs here that removes firstChild from the DOM. alert(firstChild.parentNode.innerHTML); // An exception gets thrown because firstChild.parentNode is now null. You can easily imagine more complicated examples that you would easily hit in the real world if there are multiple libraries acting on the same DOM. Web devs usually want something synchronous, like sync XHR (sync XHR has other problems not related to mutation handling). Synchronous is easier to understand. -Olli / Jonas
Re: Mutation events replacement
On Wed, Jul 20, 2011 at 10:30 AM, Olli Pettay olli.pet...@helsinki.fi wrote: On 07/20/2011 06:46 PM, Jonas Sicking wrote: Hence I'm leaning towards using the almost-asynchronous proposal for now. If we end up getting the feedback from people that use mutation events today that they won't be able to solve the same use cases, then we can consider using the synchronous notifications. However I think that it would be beneficial to try to go almost-async for now. I disagree. I had hoped for a bit more of an explanation than that ;-) Such as why do you not think that synchronous events will be a problem for web developers just like they have been for us? In practice synchronous events have been a problem to us because we are in C++, which is unsafe language. Web devs use JS. The only C++ specific problem that we've had is dangling pointers. However only a small set of our problems would have been solved by making local points strong. We'd still have problems with indexes changing under us, and nodes that we removed from one location now being inserted elsewhere, etc. Another way to look at it is that due to C++ specific problems, when unexpected things happen during callbacks, we end up possibly crashing. In Javascript, if unexpected things happen during callbacks, you'll get buggy behavior. That's better, but still not good. Web devs usually want something synchronous, like sync XHR (sync XHR has other problems not related to mutation handling). Synchronous is easier to understand. That is a wholly different type of sync API. Those are APIs where the return value is delivered through a callback rather than as a return value, forcing you to create awkward code like: doSomething(function(res1) { res1.callFunc(function(res2) { doSomethingElse(res2); } } There's a very good problem description here: http://tamejs.org/ (ignore the proposed solution, it's the problem description that's interesting for this discussion). Those problems aren't happening here as far as I can tell. There are no return values delivered asynchronously, nor any of the problems described in the tamejs page. / Jonas
Re: Mutation events replacement
On 21/07/11 6:18 AM, David Flanagan wrote: On 7/20/11 12:11 PM, Ryosuke Niwa wrote: On Wed, Jul 20, 2011 at 11:56 AM, Aryeh Gregor simetrical+...@gmail.com mailto:simetrical%2b...@gmail.com wrote: On Wed, Jul 20, 2011 at 1:43 AM, David Flanagan dflana...@mozilla.com mailto:dflana...@mozilla.com wrote: Finally, I still think it is worth thinking about trying to model the distinction between removing nodes from the document tree and moving them (atomically) within the tree. I'll chip in that I think this is useful. It makes things somewhat more complicated, but remove/insert and move are conceptually very different. But internally, a node movement is a removal then an insertion. There's always possibility that a node gets removed then inserted again after mutation observers are invoked. Also, what happens if a function removed a bunch of nodes and then inserted back one of them? My definition of moving a node atomically is taking a node that is already in the tree and passing it to appendChild() or insertBefore(). Everything else is regular node removal followed by node insertion. If you get a mutation event that says that node A was removed from node B and inserted into node C, you know nothing about the state of node A, since it could have been mutated while it was outside of the tree and no mutation events would have been recorded. Its attributes, text and children all could have changed, so the mutation listener has to basically discard everything it knows about node A and treat it as a brand-new node. Under Jonas' original proposal, mutation listeners would be called for nodes that are outside the document (the API would be available on Document, Element and DocumentFragment interfaces). As long as you add listeners to node A before it is removed from node B you can be informed of mutations on (or below) node A before it is inserted into node C. Of course, the optimal place to add a listener to node A is in a synchronous mutation listener that is fired when node A is removed. In the case of asynchronous mutation listeners a better solution might be to have the API on the Document interface and use an approach similar to event delegation.
Re: Mutation events replacement
On 7/20/11 4:10 PM, Bjoern Hoehrmann wrote: Simple example: you get a notification whenever a script could observe the .getAttribute value changes, and you get it before the change is applied. Right, the synchronous will mutate notification. Having that does simplify things. As discussed on this list earlier, this can only be done if you trust your callees or can enforce that your callees don't mess things up during the notification. In this case, the former is clearly false and the latter would basically involve changing the spec for every single API in the platform to behave slightly differently during the will mutate notification. I agree that if this constraint somehow didn't exist the design space would be bigger for mutation events. But it does exist, and it doesn't seem practical so far to remove it... -Boris
Re: Mutation events replacement
On 7/20/11 7:17 PM, Boris Zbarsky wrote: On 7/20/11 4:14 PM, Ryosuke Niwa wrote: I'm not sure if we can have a concept of atomicity in DOM. Boris might have a strong opinion on this. I don't yet. What I do have a strong opinion on is that it would be good to have some data on how common move operations are compared to remove and insert on the web. Then we'll at least know how common or edge-case the situation is and hence how much effort we should spend on optimizing for it... -Boris I agree that it would be good to have data. All I have is the intuition that moves in the form of reparenting elements is fairly common. I assume that there is a lot of code out there that dynamically decorates static content (to add hyperlinks, animation, etc.) by reparenting that content into a container element using some variation on this basic code: var container = document.createElement('div'); parent.insertBefore(container, target); container.appendChild(target); But you're right that this might be an edge case that is not worth optimizing. If reparent events are treated as a new category of mutation events, then they can be added later, if needed, since Jonas's proposal allows for that sort of extension. David
Re: Mutation events replacement
On Thu, Jul 7, 2011 at 6:38 PM, Jonas Sicking jo...@sicking.cc wrote: On Thu, Jul 7, 2011 at 5:23 PM, Rafael Weinstein rafa...@google.com wrote: So yes, my proposal only solves the usecase outside mutation handlers. However this is arguably better than never solving the use case as in your proposal. I'm sure people will end up writing buggy code, but ideally this will be found and fixed fairly easily as the behavior is consistent. We are at least giving people the tools needed to implement the synchronous behavior. Ok. Thanks for clarifying. It's helpful to understand this. I'm glad there's mostly common ground on the larger issue. The point of contention is clearly whether accommodating some form of sync mutation actions is a goal or non-goal. Yup, that seems to be the case. I think the main reason I'm arguing for allowing synchronous callbacks is that I'm concerned that without them people are going to stick to mutation events. If I was designing this feature from scratch, I'd be much happier to use some sort of async callback. However given that we need something that people can migrate to, and we don't really know what they're using mutation events for, I'm more conservative. Ok, here is my updated proposal. There are two issues at stake here: When to send notifications, and what they contain. I'll get to when to send them second as that is a more controversial. As for what the notification contain, lets first start at how to register for notifications. Since we want a single callback to contain information about all mutations that has happened, we need the ability to choose, for a single callback, which mutations we should tell it about. Something like this would work: node.addMutationListener(listener, { childlist: true, attributes: true, characterdata: true }); node.removeMutationListener(listener); 'listener' above would be a function which receives a single argument when notifications fire. The value of this argument would be an Array which could look something like this: [ { target: node1, type: childlist, added: [a, b, c, d], removed: [x, y] }, { target: node1, type: attributes, changed: [class, bgcolor, href] }, { target: node2, type: characterdata }, { target: node3, type: childlist, added: [r, s, t, x], removed: [z] } ] A few things to note here: * There is only ever one entry in the array for a given target+type pair. If, for example, multiple changes are made to the classlist of a given node, these changes are added to the added/removed lists. * For childlist changes, you get the full list of which nodes were added and removed. * For attributes changes you get a full list of which attributes were changed. However you do not get the new and old value of the attributes as this could result in significant overhead for attributes like style for example. * For characterdata you don't get the old or new value of the node. We could also simply add the before/after values here as there shouldn't be as much serialization overhead involved. A nice thing with the above approach is that it is very expandable if we want to introduce more types of notifications in the future. Some examples that have been mentioned are the ability to be notified about class changes, text-content changes and changes to individual attributes. We could do that using: node.addMutationListener(listener, { class: [myclass1, warning], textcontent: true, attributes: [type, title, data-foo] }); We could also add the ability to get notified about microdata changes or data-prefix-* changes. But for now I think we should start with a minimal set and see if people use it. But it's good to know that we have a path forward. There are of course a few more things that needs to be defined. Here some of them: * The notification-objects are added to the list in the order they happen. With the exception that if there is a notification-object for the specific target+type then a new object isn't created, but rather added to the existing one. * If you call addMutationListener with the same listener multiple times any new flags are added to the existing registration. So node.addMutationListener(listener, { attributes: true }); node.addMutationListener(listener, { childlist: true }); is equivalent to node.addMutationListener(listener, { childlist: true, attributes: true }); * For the childlist notifications, nodes are added to the added/removed lists in document order when a whole list of them are added or removed. For example for .appendChild(docfragment) or .textContent = . * If a node is first added and then removed from a childlist, it doesn't appear in neither the added nor the removed lists for the childlist notification. * If a node is removed and then readded to a childlist, it appears in both the added and the removed lists. This is needed to indicate that it might have a different location now. So, this leaves the issue of when to fire these notifications. I had a very interesting talk with
Re: Mutation events replacement
Thanks for the new proposal, Jonas. I'm very excited about the progress we're making towards a saner world! On Tue, Jul 19, 2011 at 4:01 PM, Jonas Sicking jo...@sicking.cc wrote: [ { target: node1, type: childlist, added: [a, b, c, d], removed: [x, y] }, { target: node1, type: attributes, changed: [class, bgcolor, href] }, { target: node2, type: characterdata }, { target: node3, type: childlist, added: [r, s, t, x], removed: [z] } ] A few things to note here: * There is only ever one entry in the array for a given target+type pair. If, for example, multiple changes are made to the classlist of a given node, these changes are added to the added/removed lists. * For childlist changes, you get the full list of which nodes were added and removed. For editing purposes, it's also crucial to know from/to where nodes are removed/inserted. It seems like adding an offset trivially solves this problem without much overhead. * For attributes changes you get a full list of which attributes were changed. However you do not get the new and old value of the attributes as this could result in significant overhead for attributes like style for example. Again, it'll be very useful to have old and new values for editing purposes. Although I have a reservation as to whether we should do for style or not because calling mutation listeners every time script modifies some style property will be quite expensive as it requires serializing CSSStyleDeclaration. * For characterdata you don't get the old or new value of the node. We could also simply add the before/after values here as there shouldn't be as much serialization overhead involved. For editing purposes, seeing old and new value is essential. - Ryosuke
Re: Mutation events replacement
On Tue, Jul 19, 2011 at 4:18 PM, Ryosuke Niwa rn...@webkit.org wrote: Thanks for the new proposal, Jonas. I'm very excited about the progress we're making towards a saner world! On Tue, Jul 19, 2011 at 4:01 PM, Jonas Sicking jo...@sicking.cc wrote: [ { target: node1, type: childlist, added: [a, b, c, d], removed: [x, y] }, { target: node1, type: attributes, changed: [class, bgcolor, href] }, { target: node2, type: characterdata }, { target: node3, type: childlist, added: [r, s, t, x], removed: [z] } ] A few things to note here: * There is only ever one entry in the array for a given target+type pair. If, for example, multiple changes are made to the classlist of a given node, these changes are added to the added/removed lists. * For childlist changes, you get the full list of which nodes were added and removed. For editing purposes, it's also crucial to know from/to where nodes are removed/inserted. It seems like adding an offset trivially solves this problem without much overhead. I'm not really sure how you're expecting to use indexes. Not that once one node is removed, the index changes for all other nodes. Can you provide a short example code demonstrating how you'd use the index? * For attributes changes you get a full list of which attributes were changed. However you do not get the new and old value of the attributes as this could result in significant overhead for attributes like style for example. Again, it'll be very useful to have old and new values for editing purposes. Although I have a reservation as to whether we should do for style or not because calling mutation listeners every time script modifies some style property will be quite expensive as it requires serializing CSSStyleDeclaration. * For characterdata you don't get the old or new value of the node. We could also simply add the before/after values here as there shouldn't be as much serialization overhead involved. For editing purposes, seeing old and new value is essential. As has been previously mentioned, providing the old value comes with significant overhead. For your usecase it seems like this is overhead that you'll need to live with, but for many others it might not be. We could solve this by making it configurable if you want the old values or not. For example by having separate notification types that contain before and after values. Though note that providing the after-values rarely seems useful as you can simply get them as needed from the DOM. As for childlists, the only sane solution I can think of would be to provide what the whole childlist looked like before modifications started. / Jonas
Re: Mutation events replacement
On Tue, Jul 19, 2011 at 4:56 PM, Jonas Sicking jo...@sicking.cc wrote: On Tue, Jul 19, 2011 at 4:18 PM, Ryosuke Niwa rn...@webkit.org wrote: For editing purposes, it's also crucial to know from/to where nodes are removed/inserted. It seems like adding an offset trivially solves this problem without much overhead. I'm not really sure how you're expecting to use indexes. Not that once one node is removed, the index changes for all other nodes. Can you provide a short example code demonstrating how you'd use the index? But if you have the full list of mutations that had happened, you should be able to infer exactly where nodes and inserted and removed. I'm thinking of cases where collaborative editing app and so forth needs to sync data with a remote server. In that case, the script wants to figure out what inserted or removed where. * For characterdata you don't get the old or new value of the node. We could also simply add the before/after values here as there shouldn't be as much serialization overhead involved. For editing purposes, seeing old and new value is essential. As has been previously mentioned, providing the old value comes with significant overhead. For your usecase it seems like this is overhead that you'll need to live with, but for many others it might not be. Agreed. We could solve this by making it configurable if you want the old values or not. For example by having separate notification types that contain before and after values. Though note that providing the after-values rarely seems useful as you can simply get them as needed from the DOM. As for childlists, the only sane solution I can think of would be to provide what the whole childlist looked like before modifications started. Yeah, that sounds like a reasonable approach here. I'll think about use cases where we want after-value. I'm kind of guessing what editor authors want to have from my limited experience getting feedbacks from them but I'll contact folks I know and see if I can get more concrete use cases. - Ryosuke
Re: Mutation events replacement
On 7/19/11 7:18 PM, Ryosuke Niwa wrote: For editing purposes, it's also crucial to know from/to where nodes are removed/inserted. It seems like adding an offset trivially solves this problem without much overhead. I'm not convinced about without much overhead. In general, adding an offset is O(N) in number of childnodes in many existing implementations that can be improved, but only at the cost of more memory or performance elsewhere. Again, it'll be very useful to have old and new values for editing purposes. Although I have a reservation as to whether we should do for style or not because calling mutation listeners every time script modifies some style property will be quite expensive as it requires serializing CSSStyleDeclaration. Yes, that is _exactly_ the problem. I have a related question. If the same node is inserted and removed over and over again (say 100 times) in the same task, what notification(s) will be delivered at the end of the task? -Boris
Re: Mutation events replacement
On 7/19/11 4:01 PM, Jonas Sicking wrote: [ { target: node1, type: childlist, added: [a, b, c, d], removed: [x, y] }, { target: node1, type: attributes, changed: [class, bgcolor, href] }, { target: node2, type: characterdata }, { target: node3, type: childlist, added: [r, s, t, x], removed: [z] } ] I don't see the advantage of having one array element per target/type combination. Why not just one entry per modified node, and put all changes in the same object? So the first two entries in the array above would become something like: { target: node1, nodesAdded: [a,b,c,d], nodesRemoved:[x,y], attributesChanged: [class,bgcolor,href]} node.addMutationListener(listener, { childlist: true, attributes: true, characterdata: true }); Is the second argument optional? What's the default? * If you call addMutationListener with the same listener multiple times any new flags are added to the existing registration. So node.addMutationListener(listener, { attributes: true }); node.addMutationListener(listener, { childlist: true }); is equivalent to node.addMutationListener(listener, { childlist: true, attributes: true }); This seems awkward to me, unless removeMutationListener is modified to take the same second argument as well so that notification type flags could be individually removed. Finally, I still think it is worth thinking about trying to model the distinction between removing nodes from the document tree and moving them (atomically) within the tree. David
Re: Mutation events replacement
On 9/07/11 1:12 AM, Ryosuke Niwa wrote: On Fri, Jul 8, 2011 at 5:21 AM, Sean Hogan shogu...@westnet.com.au mailto:shogu...@westnet.com.au wrote: - MathJax (http://mathjax.org) is a JS lib that facilitates putting math onto the web by converting LaTeX or MathML markup in a page to HTML. By default MathJax triggers off the onload event to run this conversion on the page. When content containing math is dynamically added to the page, MathJax must be called manually to convert the new content. A DOM insertion listener could potentially be used to handle this conversion automatically. - A similar use-case is element augmentation too complex for CSS :before and :after - ARIA support in JS libs currently involves updating aria-attributes to be appropriate to behavior the lib is implementing. Attribute mutation listeners would allow an inverse approach - behaviors being triggered off changes to aria-attributes. - DOM insertion and removal listeners could facilitate the implementation of automatically updating Table-of-* (Headings / Images / etc). It seems like all 3 use cases here can be implemented by observers that are called AFTER the fact, and do not requiere any events or callbacks before mutation. I agree, but it's just a list of the top of my head - I was merely trying to assist with the request for use-cases. An obvious advantage of callbacks that occur BEFORE mutation is that they can be used to implement post-mutation notifications. The reverse is impossible.
Re: Mutation events replacement
On 8/07/11 8:28 AM, Jonas Sicking wrote: On Thu, Jul 7, 2011 at 3:21 PM, John J Barton johnjbar...@johnjbarton.com wrote: Jonas Sicking wrote: We are definitely short on use cases for mutation events in general which is a problem. 3. Client side dynamic translation. Intercept mutations and replace or extend them. This could be for user tools like scriptish or stylish, dev tools to inject marks or code, or for re-engineering complex sites for newer browser features. I don't fully understand this. Can you give more concrete examples? - MathJax (http://mathjax.org) is a JS lib that facilitates putting math onto the web by converting LaTeX or MathML markup in a page to HTML. By default MathJax triggers off the onload event to run this conversion on the page. When content containing math is dynamically added to the page, MathJax must be called manually to convert the new content. A DOM insertion listener could potentially be used to handle this conversion automatically. - A similar use-case is element augmentation too complex for CSS :before and :after - ARIA support in JS libs currently involves updating aria-attributes to be appropriate to behavior the lib is implementing. Attribute mutation listeners would allow an inverse approach - behaviors being triggered off changes to aria-attributes. - DOM insertion and removal listeners could facilitate the implementation of automatically updating Table-of-* (Headings / Images / etc).
Re: Mutation events replacement
On Thu, Jul 7, 2011 at 6:21 PM, John J Barton johnjbar...@johnjbarton.com wrote: 1. Graphical breakpoints. The user marks some DOM element or attribute to trigger break. The debugger inserts mutation listeners to watch for the event that causes that element/attribute to be created/modified. Then the debugger re-executes some code sequence and halts when the appropriate listener is entered. Placing the listeners high in the tree and analyzing all of the events is easier than trying to precisely add a listener since the tree will be modified during re-execution. 2. Graphical tracing. Recording all or part of the DOM creation. For visualization or analysis tools. See for example Firebug's HTML panel with options Highlight Changes, Expand Changes, or Scroll Changes into View. 3. Client side dynamic translation. Intercept mutations and replace or extend them. This could be for user tools like scriptish or stylish, dev tools to inject marks or code, or for re-engineering complex sites for newer browser features. Most if not all of your use cases can be done by hooking the functions you're worried about at the engine level. You can do any of the following: 1. replace functions on object prototypes 2. instrument the js code before it executes 3. single step the js code and then instrument it as it executes You could also just wait for this to be implemented and arrange for a *private* non DOM API which would allow you to get hooks which do things like any of the above. Note that JSD historically has had a before method call callback. And iirc someone was supposed to provide it again for the cases where the JIT broke this. A before method call callback easily enables you to check its name against a table and decide that you need to store values, there's also a paired after callback, during which you can do the rest of your work. If these don't work, you can file a bug against the API and hopefully someone will work on it. -- sadly, that won't be me.
Re: Mutation events replacement
On 8/07/11 10:21 PM, Sean Hogan wrote: On 8/07/11 8:28 AM, Jonas Sicking wrote: On Thu, Jul 7, 2011 at 3:21 PM, John J Barton johnjbar...@johnjbarton.com wrote: Jonas Sicking wrote: We are definitely short on use cases for mutation events in general which is a problem. 3. Client side dynamic translation. Intercept mutations and replace or extend them. This could be for user tools like scriptish or stylish, dev tools to inject marks or code, or for re-engineering complex sites for newer browser features. I don't fully understand this. Can you give more concrete examples? A couple of comments on these use-cases: - MathJax (http://mathjax.org) is a JS lib that facilitates putting math onto the web by converting LaTeX or MathML markup in a page to HTML. By default MathJax triggers off the onload event to run this conversion on the page. When content containing math is dynamically added to the page, MathJax must be called manually to convert the new content. A DOM insertion listener could potentially be used to handle this conversion automatically. - A similar use-case is element augmentation too complex for CSS :before and :after The previous cases respond to content being inserted into the page by (potentially) adding more content. Ideally these additional insertions wouldn't trigger additionally mutation listeners. I guess the current event system facilitates this with stopPropagation(). - ARIA support in JS libs currently involves updating aria-attributes to be appropriate to behavior the lib is implementing. Attribute mutation listeners would allow an inverse approach - behaviors being triggered off changes to aria-attributes. As has been mentioned, listening for attribute mutations is horrendously inefficient because your handler has to receive every mutation, even if only interested in one attribute. - DOM insertion and removal listeners could facilitate the implementation of automatically updating Table-of-* (Headings / Images / etc).
Re: Mutation events replacement
On Fri, Jul 8, 2011 at 5:21 AM, Sean Hogan shogu...@westnet.com.au wrote: - MathJax (http://mathjax.org) is a JS lib that facilitates putting math onto the web by converting LaTeX or MathML markup in a page to HTML. By default MathJax triggers off the onload event to run this conversion on the page. When content containing math is dynamically added to the page, MathJax must be called manually to convert the new content. A DOM insertion listener could potentially be used to handle this conversion automatically. - A similar use-case is element augmentation too complex for CSS :before and :after - ARIA support in JS libs currently involves updating aria-attributes to be appropriate to behavior the lib is implementing. Attribute mutation listeners would allow an inverse approach - behaviors being triggered off changes to aria-attributes. - DOM insertion and removal listeners could facilitate the implementation of automatically updating Table-of-* (Headings / Images / etc). It seems like all 3 use cases here can be implemented by observers that are called AFTER the fact, and do not requiere any events or callbacks before mutation. - Ryosuke
Re: Mutation events replacement
On Fri, Jul 8, 2011 at 5:21 AM, Sean Hogan shogu...@westnet.com.au wrote: On 8/07/11 8:28 AM, Jonas Sicking wrote: On Thu, Jul 7, 2011 at 3:21 PM, John J Barton johnjbar...@johnjbarton.com wrote: Jonas Sicking wrote: We are definitely short on use cases for mutation events in general which is a problem. 3. Client side dynamic translation. Intercept mutations and replace or extend them. This could be for user tools like scriptish or stylish, dev tools to inject marks or code, or for re-engineering complex sites for newer browser features. I don't fully understand this. Can you give more concrete examples? - MathJax (http://mathjax.org) is a JS lib that facilitates putting math onto the web by converting LaTeX or MathML markup in a page to HTML. By default MathJax triggers off the onload event to run this conversion on the page. When content containing math is dynamically added to the page, MathJax must be called manually to convert the new content. A DOM insertion listener could potentially be used to handle this conversion automatically. - A similar use-case is element augmentation too complex for CSS :before and :after - ARIA support in JS libs currently involves updating aria-attributes to be appropriate to behavior the lib is implementing. Attribute mutation listeners would allow an inverse approach - behaviors being triggered off changes to aria-attributes. - DOM insertion and removal listeners could facilitate the implementation of automatically updating Table-of-* (Headings / Images / etc). Do any of these require synchronous callbacks? Do any of these use mutation events today? / Jonas
Re: Mutation events replacement
On Fri, Jul 8, 2011 at 6:55 AM, Sean Hogan shogu...@westnet.com.au wrote: On 8/07/11 10:21 PM, Sean Hogan wrote: - ARIA support in JS libs currently involves updating aria-attributes to be appropriate to behavior the lib is implementing. Attribute mutation listeners would allow an inverse approach - behaviors being triggered off changes to aria-attributes. As has been mentioned, listening for attribute mutations is horrendously inefficient because your handler has to receive every mutation, even if only interested in one attribute. This is a limitation of current mutation events. We don't have to repeat this mistake. Allowing a script to listen for changes to a specific attribute is a big low-hanging fruit. ~TJ
Re: Mutation events replacement
Hi All, I've finally caught up on all the emails in this thread. Here are my impressions so far. I don't think John J Barton's proposal to fire before mutation notifications is doable. Trying to make all APIs that would, or could, mutate the DOM throw or otherwise fail would be much too complex to implement and too surprising use. The list of APIs that we'd have to black-list would be far too long and would be heavily dependent on implementation strategies. We could take the approach workers take and make such scripts execute in a wholly different context where the harmful APIs simply aren't accessible. However such a context would, like workers, not have access to any of the normal functions or variables that the page normally uses. So I don't really see what you would do in such a context? I.e. what use cases would be solved if all you can do is run calculations, but not otherwise synchronously communicate with the outside world. And how would you tell such a notification which nodes are being modified since you can't pass references to the nodes as Nodes are one of the APIs that we don't want to expose. Additionally, how would you even register the callback that should be called when the notification fires? Note that workers always load a wholly new javascript file, there is no way to pass function references to it. In short before spending more time on this, I'd like to see a comprehensive proposal, including a description of the use cases it solves and how it solves them. I strongly doubt that this approach is practical. I really like Rafael's proposal to pass a list of mutations that has happened to the notification callbacks. This has the advantage that scripts get *all* the changes that has happened at once, making it possible to make decisions based on all changes made, rather than piece-wise getting the information in separate callbacks. It also has the advantage that we can provide much more detailed information without having to make multiple calls from C++ to JS which is good for performance. For example it seems very doable to provide lists of all nodes that has been removed and added while still keeping performance reasonable. I'll write up a proposal based on this idea. Others should feel free to beat me to it :) I'm less convinced of the part of Rafael's proposal that changes *when* notifications are called. If I understand the proposal, the idea is to not fire any notifications directly when the mutation happens, but instead all notifications are as soon as control is returned to the event loop. I.e. it's basically fully asynchronous, except that instead of adding a task to the end of the event queue, one is added to the beginning of it. Rafael, please do correct me if I'm wrong on the above. The main concern that I have with this proposal is that it's so different from mutation events that it might not satisfy the same use cases. Consider a widget implementation that currently observes the DOM using mutation events and makes it possible to write code like: myWidgetBackedElement.appendChild(someNode); myWidgetBackedElement.someFunction(); where someFunction depends on state which is updated by the mutation event handler. Such a widget implementation is simply not doable with these semi-asynchronous callbacks. On the other hand, maybe this isn't a big deal. We are definitely short on use cases for mutation events in general which is a problem. Finally, I actually do think it would be possible to create an API which allow pages to batch notifications. Something like this should work: document.notificationBatch(function() { element1.appendChild(someNode); element2.setAttribute(foo, bar); }); This would batch all notifications while inside the batch. However, I'm not convinced we should add such an API, at least in version one. And definitely not until there are proper use cases. Mutation events sort have have the ability to batch notifications by calling stopImmediatePropagation on events when they are fired and then manually firing events once the batch is done. Is this something people have done? Can anyone point to examples? Additionally, we'd have to decide what to do if someone spins the event loop inside a batch, for example by calling alert() or use synchronous XHR. / Jonas
Re: Mutation events replacement
On Thu, Jul 7, 2011 at 12:18 PM, Jonas Sicking jo...@sicking.cc wrote: I don't think John J Barton's proposal to fire before mutation notifications is doable. I concur. Being synchronous was one of the reasons why the existing DOM mutation events don't work. We shouldn't adding yet-another synchronous event here. In short before spending more time on this, I'd like to see a comprehensive proposal, including a description of the use cases it solves and how it solves them. I strongly doubt that this approach is practical. Totally agreed. I really like Rafael's proposal to pass a list of mutations that has happened to the notification callbacks. This has the advantage that scripts get *all* the changes that has happened at once, making it possible to make decisions based on all changes made, rather than piece-wise getting the information in separate callbacks. It also has the advantage that we can provide much more detailed information without having to make multiple calls from C++ to JS which is good for performance. For example it seems very doable to provide lists of all nodes that has been removed and added while still keeping performance reasonable. Enthusiastically agreed. I'll write up a proposal based on this idea. Others should feel free to beat me to it :) Nice! Looking forward to it. The main concern that I have with this proposal is that it's so different from mutation events that it might not satisfy the same use cases. Consider a widget implementation that currently observes the DOM using mutation events and makes it possible to write code like: myWidgetBackedElement.appendChild(someNode); myWidgetBackedElement.someFunction(); where someFunction depends on state which is updated by the mutation event handler. Such a widget implementation is simply not doable with these semi-asynchronous callbacks. Right. But on the other hand, if this code were to run inside a mutation observer, it won't work in your proposal either. So the questions is whether writing a function that depends on state updated by the mutation observer without a mutation observer, and then later calling it inside a mutation observer happens frequently enough to annoy developers or not. On the other hand, maybe this isn't a big deal. We are definitely short on use cases for mutation events in general which is a problem. Agreed. We probably need more real-world use cases. - Ryosuke
Re: Mutation events replacement
On Thu, Jul 7, 2011 at 1:58 PM, Ryosuke Niwa rn...@webkit.org wrote: Right. But on the other hand, if this code were to run inside a mutation observer, it won't work in your proposal either. So the questions is whether writing a function that depends on state updated by the mutation observer without a mutation observer, and then later calling it inside a mutation observer happens frequently enough to annoy developers or not. I meant to say *writing a function that depends on state updated by the mutation observer outside mutation observers, and later calling it inside a mutation observer* - Ryosuke
Re: Mutation events replacement
On 07/07/2011 10:18 PM, Jonas Sicking wrote: Hi All, I've finally caught up on all the emails in this thread. Here are my impressions so far. I don't think John J Barton's proposal to fire before mutation notifications is doable. Trying to make all APIs that would, or could, mutate the DOM throw or otherwise fail would be much too complex to implement and too surprising use. The list of APIs that we'd have to black-list would be far too long and would be heavily dependent on implementation strategies. We could take the approach workers take and make such scripts execute in a wholly different context where the harmful APIs simply aren't accessible. However such a context would, like workers, not have access to any of the normal functions or variables that the page normally uses. So I don't really see what you would do in such a context? I.e. what use cases would be solved if all you can do is run calculations, but not otherwise synchronously communicate with the outside world. And how would you tell such a notification which nodes are being modified since you can't pass references to the nodes as Nodes are one of the APIs that we don't want to expose. Additionally, how would you even register the callback that should be called when the notification fires? Note that workers always load a wholly new javascript file, there is no way to pass function references to it. In short before spending more time on this, I'd like to see a comprehensive proposal, including a description of the use cases it solves and how it solves them. I strongly doubt that this approach is practical. I really like Rafael's proposal to pass a list of mutations that has happened to the notification callbacks. This has the advantage that scripts get *all* the changes that has happened at once, making it possible to make decisions based on all changes made, rather than piece-wise getting the information in separate callbacks. It also has the advantage that we can provide much more detailed information without having to make multiple calls from C++ to JS which is good for performance. For example it seems very doable to provide lists of all nodes that has been removed and added while still keeping performance reasonable. I'll write up a proposal based on this idea. Others should feel free to beat me to it :) I agree that having list of mutations is a good thing. I'm less convinced of the part of Rafael's proposal that changes *when* notifications are called. If I understand the proposal, the idea is to not fire any notifications directly when the mutation happens, but instead all notifications are as soon as control is returned to the event loop. I.e. it's basically fully asynchronous, except that instead of adding a task to the end of the event queue, one is added to the beginning of it. And I also agree with this. I don't quite see the reason to postpone notification to happen basically after all the other code in JS stack has run. Having a queue of notifications which are handled after outermost mutation should be quite easy to understand. Also, that way converting code from mutation events to mutation listeners should be easier. Rafael, please do correct me if I'm wrong on the above. The main concern that I have with this proposal is that it's so different from mutation events that it might not satisfy the same use cases. Consider a widget implementation that currently observes the DOM using mutation events and makes it possible to write code like: myWidgetBackedElement.appendChild(someNode); myWidgetBackedElement.someFunction(); where someFunction depends on state which is updated by the mutation event handler. Such a widget implementation is simply not doable with these semi-asynchronous callbacks. On the other hand, maybe this isn't a big deal. We are definitely short on use cases for mutation events in general which is a problem. Finally, I actually do think it would be possible to create an API which allow pages to batch notifications. Something like this should work: document.notificationBatch(function() { element1.appendChild(someNode); element2.setAttribute(foo, bar); }); This would batch all notifications while inside the batch. However, I'm not convinced we should add such an API, at least in version one. And definitely not until there are proper use cases. Mutation events sort have have the ability to batch notifications by calling stopImmediatePropagation on events when they are fired and then manually firing events once the batch is done. Is this something people have done? Can anyone point to examples? Additionally, we'd have to decide what to do if someone spins the event loop inside a batch, for example by calling alert() or use synchronous XHR. / Jonas
Re: Mutation events replacement
On Thu, Jul 7, 2011 at 1:58 PM, Ryosuke Niwa rn...@webkit.org wrote: On Thu, Jul 7, 2011 at 12:18 PM, Jonas Sicking jo...@sicking.cc wrote: I don't think John J Barton's proposal to fire before mutation notifications is doable. I concur. Being synchronous was one of the reasons why the existing DOM mutation events don't work. We shouldn't adding yet-another synchronous event here. In short before spending more time on this, I'd like to see a comprehensive proposal, including a description of the use cases it solves and how it solves them. I strongly doubt that this approach is practical. Totally agreed. I really like Rafael's proposal to pass a list of mutations that has happened to the notification callbacks. This has the advantage that scripts get *all* the changes that has happened at once, making it possible to make decisions based on all changes made, rather than piece-wise getting the information in separate callbacks. It also has the advantage that we can provide much more detailed information without having to make multiple calls from C++ to JS which is good for performance. For example it seems very doable to provide lists of all nodes that has been removed and added while still keeping performance reasonable. Enthusiastically agreed. I'll write up a proposal based on this idea. Others should feel free to beat me to it :) Nice! Looking forward to it. The main concern that I have with this proposal is that it's so different from mutation events that it might not satisfy the same use cases. Consider a widget implementation that currently observes the DOM using mutation events and makes it possible to write code like: myWidgetBackedElement.appendChild(someNode); myWidgetBackedElement.someFunction(); where someFunction depends on state which is updated by the mutation event handler. Such a widget implementation is simply not doable with these semi-asynchronous callbacks. Right. But on the other hand, if this code were to run inside a mutation observer, it won't work in your proposal either. So the questions is whether writing a function that depends on state updated by the mutation observer without a mutation observer, and then later calling it inside a mutation observer happens frequently enough to annoy developers or not. On the other hand, maybe this isn't a big deal. We are definitely short on use cases for mutation events in general which is a problem. Right. Olli Jonas, I'd really like to understand your thinking about this. Are we not understanding something about your proposal? If accommodating the above is a goal, it seems like the only option is to have mutation events be fully synchronous. I.e. It doesn't seem acceptable to encourage a widget author to expose an API that depends on never being called inside a mutation callback and/or prevents it from being used as a building block for a higher-level abstraction. Agreed. We probably need more real-world use cases. - Ryosuke
Re: Mutation events replacement
On 07/08/2011 12:29 AM, Olli Pettay wrote: On 07/07/2011 10:18 PM, Jonas Sicking wrote: Hi All, I've finally caught up on all the emails in this thread. Here are my impressions so far. I don't think John J Barton's proposal to fire before mutation notifications is doable. Trying to make all APIs that would, or could, mutate the DOM throw or otherwise fail would be much too complex to implement and too surprising use. The list of APIs that we'd have to black-list would be far too long and would be heavily dependent on implementation strategies. We could take the approach workers take and make such scripts execute in a wholly different context where the harmful APIs simply aren't accessible. However such a context would, like workers, not have access to any of the normal functions or variables that the page normally uses. So I don't really see what you would do in such a context? I.e. what use cases would be solved if all you can do is run calculations, but not otherwise synchronously communicate with the outside world. And how would you tell such a notification which nodes are being modified since you can't pass references to the nodes as Nodes are one of the APIs that we don't want to expose. Additionally, how would you even register the callback that should be called when the notification fires? Note that workers always load a wholly new javascript file, there is no way to pass function references to it. In short before spending more time on this, I'd like to see a comprehensive proposal, including a description of the use cases it solves and how it solves them. I strongly doubt that this approach is practical. I really like Rafael's proposal to pass a list of mutations that has happened to the notification callbacks. This has the advantage that scripts get *all* the changes that has happened at once, making it possible to make decisions based on all changes made, rather than piece-wise getting the information in separate callbacks. It also has the advantage that we can provide much more detailed information without having to make multiple calls from C++ to JS which is good for performance. For example it seems very doable to provide lists of all nodes that has been removed and added while still keeping performance reasonable. I'll write up a proposal based on this idea. Others should feel free to beat me to it :) I agree that having list of mutations is a good thing. I'm less convinced of the part of Rafael's proposal that changes *when* notifications are called. If I understand the proposal, the idea is to not fire any notifications directly when the mutation happens, but instead all notifications are as soon as control is returned to the event loop. I.e. it's basically fully asynchronous, except that instead of adding a task to the end of the event queue, one is added to the beginning of it. And I also agree with this. I don't quite see the reason to postpone notification to happen basically after all the other code in JS stack has run. Especially since the asynchronous-like approach behaves strangely when spin the event loop is needed. Having a queue of notifications which are handled after outermost mutation should be quite easy to understand. Also, that way converting code from mutation events to mutation listeners should be easier. Rafael, please do correct me if I'm wrong on the above. The main concern that I have with this proposal is that it's so different from mutation events that it might not satisfy the same use cases. Consider a widget implementation that currently observes the DOM using mutation events and makes it possible to write code like: myWidgetBackedElement.appendChild(someNode); myWidgetBackedElement.someFunction(); where someFunction depends on state which is updated by the mutation event handler. Such a widget implementation is simply not doable with these semi-asynchronous callbacks. On the other hand, maybe this isn't a big deal. We are definitely short on use cases for mutation events in general which is a problem. Finally, I actually do think it would be possible to create an API which allow pages to batch notifications. Something like this should work: document.notificationBatch(function() { element1.appendChild(someNode); element2.setAttribute(foo, bar); }); This would batch all notifications while inside the batch. However, I'm not convinced we should add such an API, at least in version one. And definitely not until there are proper use cases. Mutation events sort have have the ability to batch notifications by calling stopImmediatePropagation on events when they are fired and then manually firing events once the batch is done. Is this something people have done? Can anyone point to examples? Additionally, we'd have to decide what to do if someone spins the event loop inside a batch, for example by calling alert() or use synchronous XHR. / Jonas
Re: Mutation events replacement
On Thu, Jul 7, 2011 at 2:32 PM, Rafael Weinstein rafa...@google.com wrote: On Thu, Jul 7, 2011 at 1:58 PM, Ryosuke Niwa rn...@webkit.org wrote: On Thu, Jul 7, 2011 at 12:18 PM, Jonas Sicking jo...@sicking.cc wrote: I don't think John J Barton's proposal to fire before mutation notifications is doable. I concur. Being synchronous was one of the reasons why the existing DOM mutation events don't work. We shouldn't adding yet-another synchronous event here. In short before spending more time on this, I'd like to see a comprehensive proposal, including a description of the use cases it solves and how it solves them. I strongly doubt that this approach is practical. Totally agreed. I really like Rafael's proposal to pass a list of mutations that has happened to the notification callbacks. This has the advantage that scripts get *all* the changes that has happened at once, making it possible to make decisions based on all changes made, rather than piece-wise getting the information in separate callbacks. It also has the advantage that we can provide much more detailed information without having to make multiple calls from C++ to JS which is good for performance. For example it seems very doable to provide lists of all nodes that has been removed and added while still keeping performance reasonable. Enthusiastically agreed. I'll write up a proposal based on this idea. Others should feel free to beat me to it :) Nice! Looking forward to it. The main concern that I have with this proposal is that it's so different from mutation events that it might not satisfy the same use cases. Consider a widget implementation that currently observes the DOM using mutation events and makes it possible to write code like: myWidgetBackedElement.appendChild(someNode); myWidgetBackedElement.someFunction(); where someFunction depends on state which is updated by the mutation event handler. Such a widget implementation is simply not doable with these semi-asynchronous callbacks. Right. But on the other hand, if this code were to run inside a mutation observer, it won't work in your proposal either. So the questions is whether writing a function that depends on state updated by the mutation observer without a mutation observer, and then later calling it inside a mutation observer happens frequently enough to annoy developers or not. On the other hand, maybe this isn't a big deal. We are definitely short on use cases for mutation events in general which is a problem. Right. Olli Jonas, I'd really like to understand your thinking about this. Are we not understanding something about your proposal? If accommodating the above is a goal, it seems like the only option is to have mutation events be fully synchronous. I.e. It doesn't seem acceptable to encourage a widget author to expose an API that depends on never being called inside a mutation callback and/or prevents it from being used as a building block for a higher-level abstraction. It's definitely the case that APIs will behave differently inside the mutation notification, and that the code example showed above would not work from inside the notification. Basically I'm asking people to tread carefully inside mutation notifications and only do the minimal amount of data gathering. So yes, my proposal only solves the usecase outside mutation handlers. However this is arguably better than never solving the use case as in your proposal. I'm sure people will end up writing buggy code, but ideally this will be found and fixed fairly easily as the behavior is consistent. We are at least giving people the tools needed to implement the synchronous behavior. / Jonas
Re: Mutation events replacement
Jonas Sicking wrote: We are definitely short on use cases for mutation events in general which is a problem. 1. Graphical breakpoints. The user marks some DOM element or attribute to trigger break. The debugger inserts mutation listeners to watch for the event that causes that element/attribute to be created/modified. Then the debugger re-executes some code sequence and halts when the appropriate listener is entered. Placing the listeners high in the tree and analyzing all of the events is easier than trying to precisely add a listener since the tree will be modified during re-execution. 2. Graphical tracing. Recording all or part of the DOM creation. For visualization or analysis tools. See for example Firebug's HTML panel with options Highlight Changes, Expand Changes, or Scroll Changes into View. 3. Client side dynamic translation. Intercept mutations and replace or extend them. This could be for user tools like scriptish or stylish, dev tools to inject marks or code, or for re-engineering complex sites for newer browser features. jjb
Re: Mutation events replacement
On Thu, Jul 7, 2011 at 3:21 PM, John J Barton johnjbar...@johnjbarton.com wrote: Jonas Sicking wrote: We are definitely short on use cases for mutation events in general which is a problem. 1. Graphical breakpoints. The user marks some DOM element or attribute to trigger break. The debugger inserts mutation listeners to watch for the event that causes that element/attribute to be created/modified. Then the debugger re-executes some code sequence and halts when the appropriate listener is entered. Placing the listeners high in the tree and analyzing all of the events is easier than trying to precisely add a listener since the tree will be modified during re-execution. Debuggers can use internal APIs and doesn't need to be limited to using APIs exposed to web pages. In fact, in this case you probably want to use an internal API anyway as to ensure that the debugger steps in before any other of the mutation listeners have executed. In gecko you can for example use the nsIMutationObserver notification to implement this. 2. Graphical tracing. Recording all or part of the DOM creation. For visualization or analysis tools. See for example Firebug's HTML panel with options Highlight Changes, Expand Changes, or Scroll Changes into View. See above. 3. Client side dynamic translation. Intercept mutations and replace or extend them. This could be for user tools like scriptish or stylish, dev tools to inject marks or code, or for re-engineering complex sites for newer browser features. I don't fully understand this. Can you give more concrete examples? / Jonas
Re: Mutation events replacement
Rafael Weinstein wrote: On Thu, Jul 7, 2011 at 1:58 PM, Ryosuke Niwa rn...@webkit.org wrote: On Thu, Jul 7, 2011 at 12:18 PM, Jonas Sicking jo...@sicking.cc wrote: I don't think John J Barton's proposal to fire before mutation notifications is doable. I concur. Being synchronous was one of the reasons why the existing DOM mutation events don't work. We shouldn't adding yet-another synchronous event here. However, my proposal need not be synchronous in the sense that is important here: 'before' mutation listeners need not able to mutate, only cancel. So it's not yet another synchronous event. Developers would use their handler to build a new mutation event and fire it on the next turn: it' s essentially asynchronous. In short before spending more time on this, I'd like to see a comprehensive proposal, including a description of the use cases it solves and how it solves them. I strongly doubt that this approach is practical. There are lots of reasons why 'before' events may not be practical, including lack of enthusiasm on the part of the implementors. You folks are the experts, I'm just trying to contribute another point of view. Thus I want to point out that for the critical issue of preventing mutation listeners from mutating, all you have to do to Jonas' algorithm is prepend: 0. If notifyingCallbacks is set to true, throw MutationNotAllowedInBeforeMutationCallbacks. You don't have to to any thing to create a read-only DOM API because you already track all possible DOM modifications. The clean-up from the throw is similar to the cancel and not different from any other clean-up you have to do if the mutation listener fails. This is of course not a comprehensive proposal. I'm perfectly fine if you choose not to respond because you want to close off this discussion and I thank you for the replies so far. jjb
Re: Mutation events replacement
On 7/7/11 6:28 PM, Jonas Sicking wrote: In gecko you can for example use the nsIMutationObserver notification to implement this. Maybe you can, and maybe not. Those fire at unsafe times and are limited in what you can do from them. Arbitrary debugging doesn't fit the bill. But yes, in general debuggers can use internal APIs with higher trust in the callee as needed. The question is whether they can work within the resulting constraints. -Boris
Re: Mutation events replacement
On Thu, Jul 7, 2011 at 3:43 PM, John J Barton johnjbar...@johnjbarton.comwrote: On Thu, Jul 7, 2011 at 1:58 PM, Ryosuke Niwa rn...@webkit.org wrote: On Thu, Jul 7, 2011 at 12:18 PM, Jonas Sicking jo...@sicking.cc wrote: I don't think John J Barton's proposal to fire before mutation notifications is doable. I concur. Being synchronous was one of the reasons why the existing DOM mutation events don't work. We shouldn't adding yet-another synchronous event here. However, my proposal need not be synchronous in the sense that is important here: 'before' mutation listeners need not able to mutate, only cancel. So it's not yet another synchronous event. Developers would use their handler to build a new mutation event and fire it on the next turn: it' s essentially asynchronous. Being able to cancel is dangerous enough for me. There are lots of reasons why 'before' events may not be practical, including lack of enthusiasm on the part of the implementors. You folks are the experts, I'm just trying to contribute another point of view. Thus I want to point out that for the critical issue of preventing mutation listeners from mutating, all you have to do to Jonas' algorithm is prepend: 0. If notifyingCallbacks is set to true, throw MutationNotAllowedInBeforeMuta**tionCallbacks. Implementing this feature is excessively hard and time-consuming for many implementors as far as I know. - Ryosuke
Re: Mutation events replacement
On 07/08/2011 01:43 AM, John J Barton wrote: Rafael Weinstein wrote: On Thu, Jul 7, 2011 at 1:58 PM, Ryosuke Niwa rn...@webkit.org wrote: On Thu, Jul 7, 2011 at 12:18 PM, Jonas Sicking jo...@sicking.cc wrote: I don't think John J Barton's proposal to fire before mutation notifications is doable. I concur. Being synchronous was one of the reasons why the existing DOM mutation events don't work. We shouldn't adding yet-another synchronous event here. However, my proposal need not be synchronous in the sense that is important here: 'before' mutation listeners need not able to mutate, only cancel. So it's not yet another synchronous event. Developers would use their handler to build a new mutation event and fire it on the next turn: it' s essentially asynchronous. In short before spending more time on this, I'd like to see a comprehensive proposal, including a description of the use cases it solves and how it solves them. I strongly doubt that this approach is practical. There are lots of reasons why 'before' events may not be practical, including lack of enthusiasm on the part of the implementors. You folks are the experts, I'm just trying to contribute another point of view. Thus I want to point out that for the critical issue of preventing mutation listeners from mutating, all you have to do to Jonas' algorithm is prepend: 0. If notifyingCallbacks is set to true, throw MutationNotAllowedInBeforeMutationCallbacks. I don't understand how this could really work. Just as an example: What if the mutation listener spins event loop which ends up touching parser so that it tries to insert new content to the document. That mutation wouldn't be allowed. What should be done to that data which the parser can't add to the document? You don't have to to any thing to create a read-only DOM API because you already track all possible DOM modifications. The clean-up from the throw is similar to the cancel and not different from any other clean-up you have to do if the mutation listener fails. This is of course not a comprehensive proposal. I'm perfectly fine if you choose not to respond because you want to close off this discussion and I thank you for the replies so far. jjb
Re: Mutation events replacement
On Thu, Jul 7, 2011 at 3:43 PM, John J Barton johnjbar...@johnjbarton.com wrote: In short before spending more time on this, I'd like to see a comprehensive proposal, including a description of the use cases it solves and how it solves them. I strongly doubt that this approach is practical. There are lots of reasons why 'before' events may not be practical, including lack of enthusiasm on the part of the implementors. You folks are the experts, I'm just trying to contribute another point of view. Thus I want to point out that for the critical issue of preventing mutation listeners from mutating, all you have to do to Jonas' algorithm is prepend: 0. If notifyingCallbacks is set to true, throw MutationNotAllowedInBeforeMutationCallbacks. You don't have to to any thing to create a read-only DOM API because you already track all possible DOM modifications. The clean-up from the throw is similar to the cancel and not different from any other clean-up you have to do if the mutation listener fails. This is of course not a comprehensive proposal. I'm perfectly fine if you choose not to respond because you want to close off this discussion and I thank you for the replies so far. This is more comprehensive than anything else so far ;-) Unfortunately being able to mutate the DOM itself isn't enough. We also need to prevent a whole host of other things, such as performing synchronous XHR, navigating a document, calling alert() or showModalDialog(), likely setting document.domain, setting scroll positions etc. The list will be long and implementation dependent. This is because all these things can indirectly mutate the DOM. So more comprehensive is still needed. And unfortunately you'd likely need to do research into each browser implementation and see what invariants they depend on and which APIs can change those invariants. This is extra hard given that there are at least two non-open-source implementations out there. / Jonas
Re: Mutation events replacement
Olli Pettay wrote: On 07/08/2011 01:43 AM, John J Barton wrote: Rafael Weinstein wrote: On Thu, Jul 7, 2011 at 1:58 PM, Ryosuke Niwa rn...@webkit.org wrote: On Thu, Jul 7, 2011 at 12:18 PM, Jonas Sicking jo...@sicking.cc wrote: I don't think John J Barton's proposal to fire before mutation notifications is doable. I concur. Being synchronous was one of the reasons why the existing DOM mutation events don't work. We shouldn't adding yet-another synchronous event here. However, my proposal need not be synchronous in the sense that is important here: 'before' mutation listeners need not able to mutate, only cancel. So it's not yet another synchronous event. Developers would use their handler to build a new mutation event and fire it on the next turn: it' s essentially asynchronous. In short before spending more time on this, I'd like to see a comprehensive proposal, including a description of the use cases it solves and how it solves them. I strongly doubt that this approach is practical. There are lots of reasons why 'before' events may not be practical, including lack of enthusiasm on the part of the implementors. You folks are the experts, I'm just trying to contribute another point of view. Thus I want to point out that for the critical issue of preventing mutation listeners from mutating, all you have to do to Jonas' algorithm is prepend: 0. If notifyingCallbacks is set to true, throw MutationNotAllowedInBeforeMutationCallbacks. I don't understand how this could really work. Just as an example: What if the mutation listener spins event loop which ends up touching parser so that it tries to insert new content to the document. I would like to learn what you mean here. The only way I know how to suspend an event and spin a new one is via the debugger API. Is that the case you are concerned with? That mutation wouldn't be allowed. What should be done to that data which the parser can't add to the document? Discard, same as any exception, not a special case. jjb You don't have to to any thing to create a read-only DOM API because you already track all possible DOM modifications. The clean-up from the throw is similar to the cancel and not different from any other clean-up you have to do if the mutation listener fails. This is of course not a comprehensive proposal. I'm perfectly fine if you choose not to respond because you want to close off this discussion and I thank you for the replies so far. jjb