Closed Bug 518524 Opened 16 years ago Closed 4 years ago

Enh: CSS Mutation Events or Hooks

Categories

(Core :: CSS Parsing and Computation, enhancement)

enhancement
Not set
normal

Tracking

()

RESOLVED INACTIVE

People

(Reporter: kpdecker, Unassigned)

Details

Attachments

(1 file)

User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 GTB5 Build Identifier: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 In order to support monitoring of application created CSS mutations we need to have some sort of DOM event or XPCOM hook that allows us to monitor these changes within the system. This request is for the Firediff project. Reproducible: Always Steps to Reproduce: Enhancement
To clarify, by application created CSS mutations, I mean any call to the CSS mutator methods (i.e. CSSStyleDeclaration.setProperty, etc). In my mind the ideal solution would be to have these notifications implemented as events that are accessible to the DOM itself, but I am not sure how this fits into standards and compatibility with other browsers.
We can always add Moz* events. That said, what is it you want out of these notifications, exactly?
These events can support three things: Debugger UI update. In a live web page the css can change at anytime, the debugger has to keep in sync. We can only do that by polling on an interval or by the user calling for refresh. Diff maintainence. Similarly we want to track diffs. In addition to timeliness, the events would help use sort the diffs, eg by source (don't know what is possible, but we'd like to identify diffs by application component). Break on update. If the CSS change was triggered by JS we'd like to stop the execution.
That doesn't quite answer my question. My question is when you want the notifications (e.g. do they have to be synchronous?) and what information you want out of each notification. Sounds like you need something synchronous-like for "break on update". Would you want to stop execution before the update happened or after? How important is this break on update functonality? It might need jseng support to do it "well".
If JS is doing the update, we'd like the event to be synchronous. In that case we can deal with the break-on-update. (We have just added break on: object property change, DOM mutation, error, XHR request/response). On the JS handler we run a function with one line "debugger" then trap the debug event and throw out stack frames before we show the user. Works 'well' enough. If JS is not doing the update, we'd rather not specify the timing (to avoid constraining the implementation) other than delivery before another user/developer controllable event (js function call, network or i/o event, others?). The event should give us the nsIDOMwindow being updated (or a way to get that from the args), the old and new style data. Kevin can you be more specific, even sketch out an API?
> delivery before another user/developer controllable event So in other words, synchronous. > The event should give us the nsIDOMwindow being updated That's not available in some CSS cases (e.g. some stylesheets apply to all windows). > the old and new style data Those are never both available at the same time, really. We could add extra code to save the old data in cases when your stuff is registered, but it would be a fair amount of work, especially to do so without regressing performance when your stuff is NOT registered.
Attached file Possible Event API
Window Lookup: This can be done using the parent traversal from any style node (The media list event has been augmented to allow for this). This assumes that the defaultView property works the way that I expect and will be valid in all cases that we are working with. This doesn't work for the system and user agent cases, but most of those can be ignored in Firebug, IMO. Performance Overhead: Without knowing how the Mozilla events system is implemented I can't comment on the exact performance requirements of these events, but they do not seem like they would have much overhead compared to the events that are currently implemented for DOM mutation events. I've attached an outline of a possible event API. This design is based on the DOM mutation spec and does not take items such as performance and the features that are actually implemented in current versions of Firefox into account. At a minimum Firediff needs to have the functionality implemented by the MozCSSRuleMutation and MozCSSPropertyModified events, with access to the original data prior to the mutation, as defined in the attached file. Without these I'm concerned that there will not be enough information to provide a good user experience.
> DOM mutation events. We don't fire those unless there's an event listener registered, because doing so is too expensive performance-wise. When we do fire them, we do not provide the old style information, because of the performance overhead. See bug 338679.
To be clear, I appreciate the detailed writeup. I'll comment on it more once I have a chance to think things through a bit.
Oh, and I'm not sure how you'd get from a CSSStyleDeclaration (e.g. in the inline style case) to a Window, exactly... You'd almost certainly need to include the relevant declaration owner (style rule or node) in the event.
(In reply to comment #8) > > DOM mutation events. > > We don't fire those unless there's an event listener registered, because doing > so is too expensive performance-wise. Ok, we won't expect any events on our listener unless we register it ;-) > When we do fire them, we do not provide > the old style information, because of the performance overhead. See bug > 338679. Is there any problem with firing the event just *before* changing the value? I guess the overhead comes from storing the old value in case the mutation event will fire. If we fire before then we have two things working for us 1) we need only one test on event listener registration and 2) we could just let the js code look up the current value. But we'd have to know that the change cannot fail, maybe this is not possible.
(In reply to comment #10) > Oh, and I'm not sure how you'd get from a CSSStyleDeclaration (e.g. in the > inline style case) to a Window, exactly... You'd almost certainly need to > include the relevant declaration owner (style rule or node) in the event. To be honest this case was one that I did not consider as this is not useful for the Firediff use case. I assumed that attr changed events for the style property would be sufficient for this, although hearing about bug 338679 brings me some concern about how well this will work :( If this is something that we want to support (and can with reasonable perf) then the proposed API will need to be modified to allow this. (In reply to comment #11) > Is there any problem with firing the event just *before* changing the value? How would the new value be accessible at the javascript level at event execution time? If the event was fired before the modification then it seems like any attempts to access the object will return the state prior to the mutation.
(In reply to comment #12) > (In reply to comment #11) > > Is there any problem with firing the event just *before* changing the value? > > How would the new value be accessible at the javascript level at event > execution time? If the event was fired before the modification then it seems > like any attempts to access the object will return the state prior to the > mutation. The new value would come via the API you describe; the old value would come from the object.
So some more thoughts on the api and such: 1) My main criterion for something like this is that there be 0 measurable performance impact unless the CSS is being actively debugged. Doing that while providing both before and after values will take some ... interesting ... surgery on the CSS code. 2) Anything that involves synchronously running script as described here will need to either be done very carefully or involve significant changes to css code and maybe data structures to allow script execution at all sorts of weird places. 3) DOM events fired at a web page and visible to untrusted script are bad, from your point of view, because once that happens there are no guarantees that the previous/new values are correct, unless your event listener is registered as a capturing listener on chrome event handler (i.e. the <browser> in Firefox) level or higher. Anything else allows content listeners to fire between the event being dispatched and your listener firing. 4) How do you envision this working in the multi-process world? 5) The proposed setup doesn't seem to handle shorthand properties at all well (though maybe it's just underspecified for those). 6) The proposed setup doesn't handle setting .cssText well, as far as I can tell. Certainly not if said handling involves firing multiple events (whether or not they're visible to web content, but especially if they are). Basically, it seems like it can handle some simple cases, can't handle complicated cases, and would require significant changes to the css code to implement. Maybe you're willing to put in the time for those changes, of course... What will a typical use-case here look like? It is more like "let me know if this property in this rule" changes, or more like "I want to reconstruct everything that's changed since this page was loaded"?
(In reply to comment #3) > These events can support three things: > Debugger UI update. In a live web page the css can change at anytime, the > debugger has to keep in sync. We can only do that by polling on an interval or > by the user calling for refresh. Is what you're interested in here showing changes in the computed style, which style rules matched, or the styles inside the rules that matched? Or some combination? The proposed events in the attachment only get you the third. > Diff maintainence. Similarly we want to track diffs. In addition to > timeliness, the events would help use sort the diffs, eg by source (don't know > what is possible, but we'd like to identify diffs by application component). I don't understand what you mean by this. What sort of diffs?
(In reply to comment #14) > So some more thoughts on the api and such: ... > 4) How do you envision this working in the multi-process world? The same way we'll have to do multi-process generally: an in-process service will register a listener, serialize the results for an out-of-process UI, and possibly take other actions (eg breakpoints) if previously configured to do so.
(In reply to comment #14) > What will a typical use-case here look like? It is more like "let me know if > this property in this rule" changes, or more like "I want to reconstruct > everything that's changed since this page was loaded"? (breakpoints) The use case that we don't know how to do any other way is "which JS call caused this style change?". A synchronous event when the change occurs will allow us to control the JS side. (diffs) Maintaining a diff list is the second case. Changes can come in from the network (stylesheet load), from in-page JS, or from extension JS (user actions in Firebug or other editors). Currently we can watch our own changes and record them (for undo, export, re-apply), but in-page code can change our changes and other tools can make changes. Without an event we have to re-validate our diffs and if the diff fails we have know way to tell the user "oh, the foo.js just change your text back to opacity 0%." (refresh) Finally we should style results in the UI based on reading out values from the platform. Currently we don't have anyway to know if the value change.
(In reply to comment #14) > 1) My main criterion for something like this is that there be 0 measurable > performance impact unless the CSS is being actively debugged. Doing that > while providing both before and after values will take some ... interesting > ... surgery on the CSS code. Fair enough. > 3) DOM events fired at a web page and visible to untrusted script are bad, > from > your point of view, because once that happens there are no guarantees that > the previous/new values are correct, unless your event listener is > registered as a capturing listener on chrome event handler (i.e. the > <browser> in Firefox) level or higher. Anything else allows content > listeners to fire between the event being dispatched and your listener > firing. I'm not sure what you are getting at here. Are you saying that an event listener could modify the object in question? > 5) The proposed setup doesn't seem to handle shorthand properties at all well > (though maybe it's just underspecified for those). True, the exact events thrown for these cases are not speced. This should be thought over to see what makes the most sense as in writing this I came up with possible use cases for both modified events on the shorthand property and the base properties. > 6) The proposed setup doesn't handle setting .cssText well, as far as I can > tell. Certainly not if said handling involves firing multiple events > (whether or not they're visible to web content, but especially if they > are). There was a quick note on this for the style declaration case. "for CSSStyleDeclaration.cssText, multiple events may be fired if multiple properties are modified by a single call." The other cssText cases are associated with singular events such as the MozRuleMutationEvent. In this case two events would be thrown, a delete and a create for the rule. > What will a typical use-case here look like? It is more like "let me know if > this property in this rule" changes, or more like "I want to reconstruct > everything that's changed since this page was loaded"? Unfortunately for firediff the use case is reconstruct everything, which does make the problem much more complicated than a "something changed" system.
> Are you saying that an event listener could modify the object in question? Yes, of course. That's one of the main problems with DOM mutation events... > There was a quick note on this for the style declaration case. Right; the problem is what to do if one of those event listeners modifies the CSSDeclaration. > Unfortunately for firediff the use case is reconstruct everything And even more unfortunately that's not the only use case. You want to be able to allow reconstructing everything, to breakpoint on modifications, and so forth... but you want to do all this with code that fundamentally can't be trusted to get details right (e.g. since you might breakpoint, you'll be spinning the event loop and then all bets are off). This means that the code calling you has to be very carefully architected to not trust any state persisting correctly across you being notified. And that's a big project even without that code being performance-sensitive...
(In reply to comment #19) > > Unfortunately for firediff the use case is reconstruct everything > > And even more unfortunately that's not the only use case. You want to be able > to allow reconstructing everything, to breakpoint on modifications, and so > forth... but you want to do all this with code that fundamentally can't be > trusted to get details right (e.g. since you might breakpoint, you'll be > spinning the event loop and then all bets are off). This means that the code > calling you has to be very carefully architected to not trust any state > persisting correctly across you being notified. And that's a big project even > without that code being performance-sensitive... Ok, well how about a stream of text: change identifier, rule identifier, old rule new rule that is only emitted if the event listener is registered, and async emitted at the point where you return to js. That means there is a one test penalty for normal path, and any issues about state across js invocation are handled already. If I want to break on a specific rule change I have to analyze the stream, but ok. Getting the identifiers to work in the face of multiple changes could be a bit harder but it's just shifting the work around, firediff already has that problem. I suppose if I do an AJAX call that adds a style sheet then my js call will be followed by CSS parsing, then a just a potentially long list of events on the return. But fine, I'll take the hit if I need the result.
I don't think it's reasonable to fire these events for initial stylesheet parsing in any case (other than perhaps firing the event when the sheet is added to the document, or is enabled if it's actually being parsed). I guess we could rearchitect a bunch of the cssloader and cssdeclaration stuff about being able to produce output like in comment 20 and return it off script runners. Or something. I can't think of a sane way to do the "rule identifier" stuff, exactly, though.
(In reply to comment #21) > I guess we could rearchitect a bunch of the cssloader and cssdeclaration stuff > about being able to produce output like in comment 20 and return it off script > runners. Or something. I can't think of a sane way to do the "rule > identifier" stuff, exactly, though. Ok so the bottom line is this enhancement is too hard?
This enhancement is hard if all the different requirements need to be satisfied. "too" is a value judgment and depends on what other work needs doing. From my point of view, equivalent effort on my part, say, can get us pretty noticeable wins in layout performance and code clarity. That won't help firediff any, though.
Status: UNCONFIRMED → RESOLVED
Closed: 4 years ago
Resolution: --- → INACTIVE
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: