radkovo / jStyleParser

jStyleParser is a CSS parser written in Java. It has its own application interface that is designed to allow an efficient CSS processing in Java and mapping the values to the Java data types. It parses CSS 2.1 style sheets into structures that can be efficiently assigned to DOM elements. It is intended be the primary CSS parser for the CSSBox library. While handling errors, it is user agent conforming according to the CSS specification.
http://cssbox.sourceforge.net/jstyleparser/
GNU Lesser General Public License v3.0
92 stars 49 forks source link

Detecting invalidation cases efficiently #17

Open hrj opened 9 years ago

hrj commented 9 years ago

Use-case

In the browser, when the mouse is hovered over an element, we need to find out whether that element has a hover style and then invalidate the layout+renderer accordingly.

Question

Is there a good / optimal way to do this? If not, can it be added?

Suggestion

One way could be: the analyser has an API that returns a list of applicable rules for an element. The caller can then walk the rules to see if any rule depends on a specified pseudo-element and hence requires invalidation.

radkovo commented 9 years ago

Hi, I am sorry for the delay. I have some deadlines in other projects this month so I am not able to fully focus on this issue now. But I think your suggestion is very good. For each element, we could maintain some list of pseudo classes that may influence its style. Then, we could decide which elements need to be recomputed. If you have some more specific solution in mind, feel free to add the necessary support to the existing functions.

hrj commented 9 years ago

Thanks for the reply @radkovo. I will try to come up with some code for this. Here's my plan:

The current API for getting an element's style is: DirectAnalyser.getElementStyle(element, pseudoDeclaration, mediaQuery).

This computes everything and returns the NodeData for the element.

I propose creating smaller, more atomic APIs:

:one: DirectAnalyser.getElementApplicableRules(element, pseudoDeclaration, mediaQuery) :two: DirectAnalyser.getElementStyle(applicableRules, element) :three: DirectAnalyser.hasPseudoStyle(element, applicableRules, ancestor, pseudoDeclaration)

The current .getElementStyle() could be implemented using :one: and :two:.

:three: will be a new API that returns true if an element's style is affected by a pseduoDeclaration. I will explain the ancestor parameter shortly.

This kind of separation will help optimise multiple cases in a browser:

When a element is hovered over by mouse or when its attributes change, there is no need to find the applicable rules again; the previously computed list can be cached and passed to :two:

Now, about the ancestor parameter in :three:. It is a little complex to explain. Imagine the following html:

<div id="gp">
    Grand Parent
    <div id="parent">
         Parent
        <div id="child">Child</div>
    </div>
</div>

And the following CSS

#gp:hover #child {background: red }

That is the child's background should be red when mouse is hovered over #gp. To analyse such scenarios, the ancestor element is useful. When mouse enters #gp element, the call to :three: will check whether :hover style is affected for #child, with ancestor = #gp element.


Please let me know if you have any feedback. I will begin implementation if this makes sense.

radkovo commented 9 years ago

Hi, it makes perfect sense to me, I agree. Just one thing: in the last example, how do we determine which ancestor to use? E.g. when mouse enters #gp and #parent simultaneously, which one do you use as the ancestor?

hrj commented 9 years ago

when mouse enters #gp and #parent simultaneously, which one do you use as the ancestor?

Glad you asked; I hadn't considered that scenario!

I imagine that it would result in two calls, one with #gp as ancestor and the other with #parent. Alternatively, the API could accept a list of ancestors. That choice probably depends on how the caller is structured. (In the current design of gngr, making multiple calls is a natural fit but accepting a list of ancestors is a better / more general solution).

Does that make sense? and do you have a preference for it?

radkovo commented 9 years ago

That seems reasonable. I have no strong preference about it but I mean separate calls are general enough and it's not necessary to setup a collection for making the call. I was thinking about another alternative: implementing a method like

List<Element> DirectAnalyser.getPseudoAncestors(element, pseudoDeclaration)

that would return all the ancestors affected by the given pseudo declaration. Or simply just without specifying the pseudo (i.e. considering any pseudo) and you could do the filtering later. It would return an empty list when the element style does not depend on any pseudo. However, I haven't not thought about how expensive it would be to maintain such a list for every element. What do you think?

hrj commented 9 years ago

@radkovo Sorry for the late reply. Hadn't found time to think deeply about this, until now.

I am not sure if the API you proposed (getPseudoAncestors) would be beneficial for the general case. In general, the changes in styles (triggered by changes in DOM) propagate down the hierarchy. (Cascade down). So it is easier to update from the ancestor down to the descendants. To make full use of your API, one would need to update all the descendants whenever a DOM change happens, and then check if any ancestors match. (There might be more sophisticated ways of updating only a subset of descendants; I have not thought beyond simple algorithms).

However, your API might be suitable for the relational selector where changes can flow from descendants to ancestors. I personally think that the relational selector will be very useful. But it is still a draft as of now.

hrj commented 9 years ago

@radkovo Ignore my previous comment. It was rather pointless. The API you have proposed is fine; except the memory requirement would be high (as you rightly pointed out). Perhaps, the ancestor data can be cached for only a part of the DOM tree (for example, in the case of :hover, the part underneath the current cursor position). Even then, there will be a small overhead of reserving a pointer in the element, and the cost of ensuring it is invalidated whenever the DOM changes.

In the first cut, I will try to implement a non-caching approach (the one I proposed initially). I have implemented the getElementApplicableRules today. I am working on the hasPseudoStyle API now.