WICG / cq-usecases

Use cases and requirements for standardizing element queries.
Other
184 stars 24 forks source link

Element Query race conditions #9

Open jonathantneal opened 10 years ago

jonathantneal commented 10 years ago

@davatron5000 is cataloging the CSS properties that may trigger race conditions for element queries. I’ve put them into a list to share with you:

https://gist.github.com/jonathantneal/5996a3e9b33d7a6afa5d

alanmoo commented 9 years ago

I know this falls under the technical aspects of EQ's, which we're nowhere near yet, but is there some sort of precedent or compatibility issue in which saying that CSS properties that trigger layout will be ignored in element queries? (Though I'd assume compatibility is moot since the query itself will be ignored entirely similar to the way in which media queries are ignored on older browsers.)

tabatkins commented 9 years ago

That makes it worthless, since the whole point is responding to the size of the container, and often sizing yourself appropriately.

jehoshua02 commented 9 years ago

Or did you mean, for example, ignoring setting of width of an element in a width element query on the same element?

My opinions:

To me that makes sense to avoid recursive element query problems.

But setting the width on an inner child element is perfectly valid. On Nov 3, 2014 6:13 PM, "Alan Mooiman" notifications@github.com wrote:

I know this falls under the technical aspects of EQ's, which we're nowhere near yet, but is there some sort of precedent or compatibility issue in which saying that CSS properties that trigger layout will be ignored in element queries? (Though I'd assume compatibility is moot since the query itself will be ignored entirely similar to the way in which media queries are ignored on older browsers.)

— Reply to this email directly or view it on GitHub https://github.com/ResponsiveImagesCG/eq-usecases/issues/9#issuecomment-61578529 .

alanmoo commented 9 years ago

@jehoshua02, that is what I was thinking of. Clearly it needs to be fleshed out more, but I wanted to get the ball rolling on how this might be handled, as it's probably one of the bigger technical issues EQ's will encounter.

jonathanKingston commented 9 years ago

I was thinking of this today actually - then found this issue. As a quick idea I thought you could perhaps block properties of the element with the query that matched the query.

For example (I picked the most non css syntax possible to show the example not the code):

div `min-width: 800` {
  width: 200px;
  color: red;
  img {
    width: 150px;
  }
}

So when the element became over 800 wide only red would be applied to the div but the img would become 150px wide.

I'm sure you are likely to tell me very quickly however that inner elements matter just as much as will cause reflow which could cause text to force the parent to be wider for example.

Perhaps however that could be mitigated with a bottom up recalculation of dom nodes of an elements child nodes to reduce the chance of an infinite loop chain effect. Also I suggest at any point the width of the child nodes causing the parent element to resize outside of the media query bounds from within the same rule then the execution stops and the parser treats it as invalid so never is ran again.

This also would need a mapping of rules that are blocked like max-width bans any width based rule etc.

I'm not suggesting this is right just thought I would share my initial thoughts on the issue.

yoavweiss commented 9 years ago

What we've discussed in the latest call was that having an element's style dependent on that element's style is circular by definition, and not something we should be supporting. What we're after is styling elements based on properties of a "special" containing element (which dimensions can't be impacted by its descendants).

jonathanKingston commented 9 years ago

@yoavweiss I read that in another post somewhere. My initial worry was does that scale if every element was 'special'. However that certainly does simplify the problem.

yoavweiss commented 9 years ago

I'm not a fan of making every element 'special'. See https://www.youtube.com/watch?v=A8I9pYCl9AQ

I think that we need EQs to serve as module boundaries, so you can develop things relative to the module. If you really must have an element react to its own dimensions, add a wrapper around it, and declare the wrapper as "special".

jonathanKingston commented 9 years ago

Heh yeah that's why I wasn't calling it special.

I'm thinking of every web component able to have this behaviour if it chose. In my templates instead of writing html I aim to just write in application specific web components where the components contain the html itself. Like you say these are completely self contained modules that the template author has power of the box size and the parameters passed in.

This might be a little extreme even for fans of web components however from what I have found it removes the worry of when to write web components and when to write markup.

Anyway hopefully that demonstrates why I think 'special' is bad and likely people will treat it as the norm.

PaulSpr commented 9 years ago

This might be off-topic, but thinking about Element Queries I assume that the "components" aren't really self contained, but that they inherit CSS of the rest of the website. That's the general consensus right? The way Jonathan describes this makes it sound a bit like some sort of iframe with self contained HTML & CSS.

Regarding size: Setting width must still be possible at least in percentages. I could see some cases in which setting a width, or max-width, yourself (if you really need an element to be a certain size, which is probably when it is in a certain spot in the website like a sidebar or footer) could be what you want, although this probably isn't the best solution in that case.

If an element is a certain size, it's very clear which EQ should be applied. That's simple and expected behaviors, but if any element within that element makes the element bigger than potentially other EQ's might be applied. Is it too short sighted that this is on the developer? Just like floats going haywire now?

jonathanKingston commented 9 years ago

@PaulSpr that's a great question and I have seen the feature mentioned as an iframe. This is similar to the behaviour of shadow dom. This is how I was thinking but also like the shadow dom I was expecting the parent to have control over what was exposed from the shadow dom.

The second issue is that yes floats can go haywire but they couldn't cause a potential infinate loop of changing widths.

jehoshua02 commented 9 years ago

This is a case I think I would ignore and leave it up to developers to not do dumb things.

Consider this:

.my-element (width < 500) {
  width: 500;
}

Doing something like this is silly and no web developer should ever do something like this. I wouldn't waste any time writing guards against stupid stuff like this into the browser or spec.

jonathanKingston commented 9 years ago

@jehoshua02 I'm a fail hard sort of programmer too but user agents could not just crash though so some error handling would have to happen. Also it would allow third party exceptions to add another vector to wreak havoc on their client sites.

PaulSpr commented 9 years ago

I did some checks with a browser inspector. If you change the width of the body it won't switch apply extra media queries of you exceed your breakpoints. This got me wondering.

A solution is to match EQ to the dimensions the element would logically have if it didn't apply any weird forces, like a block element with width: auto. That is probably a predictable method, but sometimes not logical. If you measure an element it could be as large as an EQ that it would trigger while not doing that :S

It's also problematic from when looking at height, that is almost always determined by the forces within an element pushing it's bottom border down. Maybe treat width and height differently?

And what if for some reason your element has negative margins on the left and right?

Argh, headaches. This stuff is hard.

tabatkins commented 9 years ago

@jehoshua02 Sorry, but that's impossible. Just saying "don't do it" doesn't work; you still need to define what it does. After all, it'll do something - if it crashes, that's exploitable, so browsers wouldn't crash; if it doesn't crash, then it has some particular (undefined) effect, and devs will end up depending on that behavior. Since it's undefined, browsers will initially do different things, and they'll end up having to reverse-engineer each other to match behavior, and everything will be terrible, and lo fire shall rain down from the heavens and blast this damned earth.

Or we can avoid all of that by defining what it means (or defining that it doesn't work at all).

tabatkins commented 9 years ago

@jonathanKingston

I was thinking of this today actually - then found this issue. As a quick idea I thought you could perhaps block properties of the element with the query that matched the query.

Nope, doesn't work either. :/ As you note, children also have an effect - that's why the EQ container is going to have to ignore its children for the purpose of determining its own dimensions. But also, any communication channel opens up the possibility for loops. Check this example for toggle-* properties. You can widen the loop to include two or more property/selector combos, so that no individual style rule violates the "no related properties inside a block with this selector" rule, but all combined they do.

That is, imagine two properties, foo and bar, which respectively affect two pseudo-classes, :foo and :bar. You have a rule that you can't use foo in any style rule using a :foo selector, and same for bar and :bar, but then:

:foo {
  bar: on;
}
:bar {
  foo: off;
}
* {
  foo: on;
  bar: off;
}

If foo is on, it triggers the first rule, which turns bar on. That triggers the second rule, which turns foo off. But if foo is off, the first rule no longer matches, so there's nothing turning bar on anymore. But if bar is off, the second rule no longer matches, so there's nothing forcing foo off. But that means foo is on, so the first rule matches, which turns bar on...

Avoiding circularity in declarative languages is a tricky business.

jonathanKingston commented 9 years ago

@tabatkins I fear an example like this would come along. Does my other comment around this feature being more than just a special case cause issues?

My other comment around allowing the child to impact the parent would be possible so long as the user agent rejects the side impacts of applying further queries within this query. I get however that's potential to be messy as you stated however.

jehoshua02 commented 9 years ago

... and lo fire shall rain down from the heavens and blast this damned earth.

@tabatkins Okay, okay, you're right. We gotta define what it'll do.

Wilto commented 9 years ago

Wait, go back to the “fire rain” thing? That doesn’t sound so bad. I mean, I’ve seen worse standards.

bradkemper commented 6 years ago

@jehoshua02

Consider this:

.my-element (width < 500) { width: 500; }

I don’t think you would have raw declarations inside the query. It would need rules with selectors, just like media queries. So, more like this:

.my-element (width < 500px) {
  .my-element .descendent {  width: 500px; }
}

At which point, the fact that you used .my-element in the query would make it special, so that its descendent would not affect the width of .my-element, regardless of its display property, position, or anything else that would normally cause shrink/grow wrapping.

bradkemper commented 6 years ago

So in other words, since width is a feature being checked in the element query, its own width would be calculated as though all its descendents had zero width and zero min-width. But it’s height would be calculated normally, because height, min-height, and max-height were not features of the query.

ZeeCoder commented 6 years ago

What do you mean as if the children had zero width? What if I have loads of inline blocks, where their size depends on the children's width, and say, I change the bg based on that? Children should definitely be able to affect the width of a container.

But even assuming we accept those constraints, you still not escape circularity:

.container { font-size: 500px; }
.container (height > 200px) {
  font-size: 10px;
}

The above would cause circularity even if we apply the font-size rules on a descendant.

matthew-dean commented 6 years ago

What seems to often be missed in this circularity argument with element/container queries is that CSS ALREADY HAS THIS PROBLEM. This is easily demonstrated. https://codepen.io/matthewdean/pen/LdvVZZ

Hover your mouse over the word "out". At least in Chrome, you will get an infinite resizing loop. So I don't understand why spec authors, developers, and browser vendors get so stuck on the issue of circularity. It exists and it has always existed from day 1 in CSS. Any selector state which can change the appearance of an element which changes its behavior which resets its state can have this happen. No one was complaining about circularity in :hover before now, so why is this even a thing?? I don't get this at all. Container queries can have circularity. So what? You're the developer. You can take easy steps to make sure they don't. So as far as:

We gotta define what it'll do.

No, we don't. Why? No one defined what :hover circularity will do. As far as I can tell, what it should do is continuously redraw infinitely, according to current browser behavior. Just like if you write an infinite loop in JavaScript, the answer to "what it'll do" and what it SHOULD do is LOOP INFINITELY. You don't have to solve this in a spec, what you have to do is educate developers on avoiding infinite loops, just like we've done with CSS and JavaScript from the beginning. In fact, developers are probably so used to being taught patterns that naturally avoid :hover circularity that they probably didn't even know it existed. Avoiding :hover circularity is so strongly built into our design patterns that it's effectively a non-issue, so MUCH of a non-issue that it's an UNKNOWN ISSUE. This would/will be the exact same case with container queries.

</rant>

eeeps commented 6 years ago

Hi, @matthew-dean!

My current understanding is, :hover loops are ① regrettable https://twitter.com/tabatkins/status/739873184065806336 ② not nearly as bad as element query loops would be https://twitter.com/tabatkins/status/882797782028832772 because at least they do render sometimes (rather than being caught in infinite layout, forever).

This would/will be the exact same case with container queries.

I've never created a :hover loop, but I have created many accidental element query loops, as I've experimented in this space. I’m not alone, either https://github.com/marcj/css-element-queries/issues/223 (note that – because of the way that the polyfill works – this example paints and flickers too. A true layout-algorithm-level loop would never finish layout, or paint at all).

matthew-dean commented 6 years ago

My current understanding is, :hover loops are ① regrettable ... ② not nearly as bad as element query loops would be...because at least they do render sometimes (rather than being caught in infinite layout, forever).

🤔 Okay fair enough. I understand that it's not exactly equivalent.

Pardon my frustration. Like every CSS developer, I'm just frustrated that container/element queries don't already exist; which is not a statement against anyone in particular. I just feel like the impetus to implement is just greater than the potential roadblock of circularity. And I also feel like circularity is not, as far as problems go, that big of a challenge to solve. If there's some particular behaviour that is more intuitive and desirable than the layout engine rendering nothing because a layout loop is detected, then that seems solvable.

Incidentally, wouldn't this be more akin to width: fit-content with something that is extrinsically sized? As in: a container can be set to wrap to the width of its child, and a child can be set to a width of 100%; i.e. the full width of its parent. They container and the child have a dependency on each other, but resolve to the algorithm min(max-content, max(min-content, fill-available)). (Which: to be fair, as a side note, both Chrome and Safari fail to do this, currently - https://bugs.chromium.org/p/chromium/issues/detail?id=814991.)

Let me channel my frustration. Infinite loops are probably not the answer. Instead, how can I help make container queries happen?

andykirk commented 6 years ago

Hi @matthew-dean - I share your frustration, which is why I got involved in this.

As this repo is about getting the use-cases document off the ground, if you’d like to help, you could have a look through the open issues and see where you could contribute.

Also, it seems to me the document as it stands lacks a wide variety of use-cases[1], so you could maybe document the actual problems you’re facing where container queries are the solution? Maybe create some simplified code-pens or similar demonstrating these problems and post them to a relevant issue (or create a new one)?

Cheers

  1. There’s only one, and it’s a classic one, but it would be good to some very different and real ones too, IMO.
tabatkins commented 6 years ago

circularity is okay because CSS already has :hover

I wrote up the canonical reason why this isn't true over on the CSSWG wiki: https://wiki.csswg.org/faq#selectors-that-depend-on-layout

matthew-dean commented 6 years ago

@andykirk Thanks! Will do as soon as I have time. @tabatkins Thanks for that link. Will take a look. I guess re: circularity, one thing I seem to have heard often as a reason to NOT have container queries is circularity, and what I'm trying to say is that it's not an insurmountable problem, or a reason in and of itself not to implement. But yes, I am getting that the type of circularity is more significant than :hover. Thanks.