Here's a problem I'm currently thinking about and would like input on. I discussed it with Matthias on #gtk+ today and he suggested I'd reach out to more people. I've included the log below and formatted it for clarity so that it reads like a QA-style interview. I hope it is not too inconvenient to read.
<Company> the question we're trying to answer is this: What CSS values does GtkStyleContext return?
<Company> this question is relevant because every time we change the CSS tree, we need to (potentially) update the value
<Company> 3.0.0 used the approach of immediately updating everything once the css tree changed somehow
<Company> so if you did: context.add_class("a"); context.add_class("b"); context.add_class("c");
<Company> it would recompute the CSS 3x
<Company> which gets real slow really fast
<Company> in 3.4 or so I changed that to always return a stale value
<Company> until we validated the style via gtk_container_idle_sizer() (when the frame clock runs)
<Company> so from that point on everything was fast, but the values were sometimes incorrect
<mclasen> wouldn't you want to mark the values as stale, and force a recompute when somebody asks for a value ?
<Company> ideally, you would
<Company> the reason I did not do that was the style-updated signal
<Company> when should that be emitted?
<Company> if we emit it once a context gets invalid, we go back to 3.0 performance
<Company> because every widget queries values in the style-updated handler...
<mclasen> you could do some freeze/thaw to batch the 3 emissions from 3 add_class calls
<Company> I don't want to add freeze/thaw calls though, everybody would get those wrong
<mclasen> who needs to listen to style-updated anyway ?
<mclasen> other than the gtk redraw machinery
<Company> mostly just that
<Company> GtkWidget needs to update the pango context
<Company> GtkImage needs to release the cached surface
<Company> things like that
<Company> and of course we need to queue_draw() or queue_resize()
<Company> here's another caveat: computing the CSS values requires computing the CSS values of the parent
<mclasen> is that on the person changing the style context, or do we do that automatically ?
<Company> GtkWidget::style-updated does that
<Company> based on the GtkCssAffects settings of the changed style properties
<mclasen> my mental model of this is that css values are used when drawing happens, so they need to be current at that point
<mclasen> and drawing happens when the frame clock comes around to tell us
<Company> yes
<Company> when the frame clock triggers, we (1) recompute styles, (2) size_allocate everything pending, (3) draw
<mclasen> whether css values are up-to-date at other points in the frame cycle should not be important
<mclasen> except for those 3rd party users who randomly poke at style contexts...
<Company> but it is important
<Company> otherwise you get shrinking gnome terminals and white firefox text
<Company> the gnome terminal case is actually rather complex to get right with the "should not be important" assumption
<Company> because it needs to compute a bunch of values based on multiple different widgets
<Company> and it needs to compute those before size allocation happens
<Company> in short: It needs to happen in style_updated() at the latest - but which style-updated, when it depends on multiple widgets?
<halfline> can't you just give the right value any time the user asks for it (updating the cache for that property if it's stale), but also keep style-updated where it happens now ?
<Company> that's one thing we could do
<Company> there's many solutions to the problem(s)
<Company> I haven't reached the point yet where want to talk about solutions
<Company> so far I'm trying to list the problem(s) :)
<mclasen> 'before size allocation' sounds like a global thing though ? like a point in the frame cycle
<mclasen> as long as we keep information about which styles are up-to-date, doing on-the-spot validation for people who call gtk_style_context_get_foo outside of regular frame-based drawing may just fine ?
<mclasen> at least, only the people doing the poking are paying the price of extra recomputations then
<Company> lemme do a short jsfiddle to show you how the web does it
<Company>
http://jsfiddle.net/nbt2myzb/1/<Company> I hope that example is simple enough
<Company> you wanna understand why uncommenting that 1 line of JS makes the animation restart
<Company> (disclaimer: I'm using firefox, no idea if webkit does things differently somehow)
<mclasen> does it ?
<mclasen> I seem to get blue when I click, which slowly fades to yellow
<mclasen> no restarts that I can observe
<Company> uncomment the line in the JS
<Company> then press the "run" button
<mclasen> ah, ok
<mclasen> now it restarts
<Company> you understand why it does that now?
<mclasen> no, why does it do that ?
<mclasen> I see that you are causing it to get the computed value of the color when I click
<mclasen> but how that relates to restarting the animation is not clear to me
<Company> right
<Company> getting the color causes a recompute of the CSS
<Company> but the "animate" class isn't set, so the style has no animation
<Company> so all running animations get deleted
<Company> then we add the animate class again, so the animation is created again
<Company> of course, recreation means restart
<mclasen> a bit surprising
<Company> yes
<Company> getting a value can have side effects
<Company> otoh: You always get the correct CSS value for the current state of the CSS tree
<mclasen> why is it correct,though ?
<mclasen> wouldn't you expect to get the value of the color at the point you happen to be in in the aninmation ?
<mclasen> ah no, you animate background, not color
<Company> no, because you removed the animate class right above
<Company> so the value is not animated when you query it
<Company> (you can use x.backgroundColor if you want to)
<mclasen> right
<mclasen> so, whats the upshot for our situation - discourage poking at css values, because recomputing them will have ugly side effects ?
<mclasen> where 'discourage' could include dropping ::style-updated
<Company> our situation gets even more funky
<Company> because our situation also has the question: "when do we emit style-updated"?
<Company> we cannot drop it
<mclasen> or only emit it at strategic times
<Company> we have to emit queue_resize() at some point ;)
<mclasen> clearly
<Company> here's an updated version:
http://jsfiddle.net/nbt2myzb/2/<mclasen> on the web, you don't have a ::style-updated equivalent, right ?
<Company> I haven't found it
<mclasen> I mean, we need to connect style changes to our size allocate machinery, but do we have to let apps hook onto that ?
<Company> if we want to report correct values (and I think we want to) we can't do anything about restarting animations or other such side effects
<Company> the only thing we can influence is at what point we emit style_updated
<Company> we clearly do not want to emit it when the style gets invalidated
<Company> we might want to emit it when the style gets validated
<Company> or we might want to emit it only when the next frame happens
<Company> if we do it when the style gets validated, getting a property might actually have the side effect of emitting a signal - or multiple signals, because validating a style requires validating the parent style
<mclasen> is that so unusual ?
<mclasen> you are getting a _computed_ value, so you shouldn't be too surprised that some computation happens...
<mclasen> maybe call it compute_value() instead of get_computed_value() ?
<Company> that a getter emits a signal? That is very unusual I think
<Company> especially because those getters are called from render functions and other places
<Company> say, if you start a dnd operation, you render text, which will update the labels's style which will update the toplevel's style which will cause I don't know what GtkWindow::style-updated does
<Company> all just because we rendered a drag icon for text
<Company> now granted, all of this would have run with the next frame clock update anyway, but the fact that it's happening earlier might be scary
<Company> the 2nd option is problematic, too
<Company> because if we only emit style-updated at frame boundaries
<Company> the widget will not invalidate its own caches until that point
<Company> so you will start the drag operation with old fonts (because we only update the pango context in style-updated)
<mclasen> why does that drag operation have to render out-of-frame ? because we pass the rendered icon to the compositor ?
<Company> we render the icon in the dnd event handler
<Company> this is all mostly hypothetical btw, because nobody sane changes the widget font when dnd starts
<Company> it's just the most realistic example i can come up with that illustrates potential problems
<mclasen> not so sure, actually - we had proposals to do cut/paste in nautilus by keeping grayed out 'ghosts' of the selected files in the list until they get dropped elsewhere
<Company> here's another possibile idea:
<Company> We could add a GtkWidget::style-invalidated signal
<Company> and then we have 2 signals: One when styles go invalid and one when styles go valid again
<Company> not sure if this buys us anything, but we could add it
<mclasen> and if you react to the invalidated signal by getting some value, you'll cause the updated signal to be emitted ?
<Company> yeah
<Company> and you kill performance
<mclasen> who would use the one vs the other, and what would it buy us ?
<Company> the idea would be to use the invalidate signal to drop caches
<Company> so that say GtkImage drops the cached surface
<Company> the "rendering dnd images" example again
<Company> you can't just render the cached image, you need to first make sure the CSS values didn't change
<Company> of, course you could have an emit_style_updated_if_necessary() function, too
<mclasen> gtk_style_context_validate () ?
<Company> yeah, like that
<Company> depending on when we emit style-updated, gtk_style_context_get_color() might be good enough for that purpose
<Company> of course, if someone does context.remove_class ("foo"); context.add_class ("foo"); you don't wanna drop caches
<Company> or rather: you'd like if you didn't do that
<Company> and GtkWidget::style-invalidated would not handle that
<Company> and that happening is a common theme
<Company> with my_widget_update_style() { remove_all_styles (); apply_relevant_styles(); }
<Company> and then call my_widget_update_style() from various setters that may or may not change the actual styles
<Company> I'll leave you with that for now - if you have more questions or an opinion into which solution you prefer, please get back to me
<Company> otherwise I'll make up my mind at some point (I don't have an opinion yet)