- From: Kornel Lesiński <kornel@geekhood.net>
- Date: Sat, 14 Sep 2013 22:22:06 +0100
- To: "Simon Pieters" <simonp@opera.com>
- Cc: www-style <www-style@w3.org>
On Sat, 14 Sep 2013 19:17:56 +0100, Simon Pieters <simonp@opera.com> wrote: > I don't understand what you mean by "JS applications" I mean pages that have no useful markup in <body> and create entire markup with JavaScript and data fetched from AJAX API or localStorage/WebSQL/IndexedDB. These websites are already taking heavy performance hit on first load, can't be accelerated with preload scanner, and have CSS loaded long before final HTML markup is created, for example: http://m.flickr.com >> If this rule triggers syntax error in external stylesheets I think it >> would be acceptable compromise, but it's not pretty :/ > > OK, good. I think that is acceptable also. It doesn't necessarily need > to be a syntax error, it could be parsed fine but just have no effect, > either way. I'm worried that even if variables work in <style> authors will try to use them in external stylesheets, and it may be impossible to add support for variables in external stylesheets later. >>> Making the image load block on external stylesheets is not acceptable. >>> I think we should design a solution that enables the browser to >>> download the correct resource as soon as it sees it without blocking >>> on another resource. >> >> This solution enables non-blocking loading. It just doesn't force it. > > Behavior should be well-defined. For ease of implementation I'm suggesting following simple algorithm that IMHO makes behavior well-defined: 1. If a variable is not defined then var(name) is false (does not match). 2. If the variable is defined at any point later, then media queries using that variable must be re-evaluated and may start matching. The same way browser keeps track of media queries that use min-width: and re-evaluates them when viewport size changes, the browser would keep track of media queries using variables and re-evaluate them whenever variables become defined. Case #1: <link rel="stylesheet"...> <picture> <source media="var(foo)"...> <source media="var(bar)"...> </picture> <source> selection algorithm is run in preload scanner and no media matches, no image loaded. When browser encounters @var-media foo:... and @var-media bar: it needs to re-run source selection and check which one matches now and load a matching source. For performance the browser should re-evaluate <source> with variables after all @var-media rules in a stylesheet are parsed (basically always schedule re-evaluation of <picture> sources on next tick instead of doing it synchronously). e.g. if external stylesheet has: @var-media bar: all; @var-media foo: all; it would be wasteful if parsing of @var-media synchronously triggered re-evaluation of <picture> and caused second source to immediately match just to switch source on the next line of CSS. That is performance optimization, does not change final result of the algorithm, just avoids having unnecessary intermediate states. Case #2: <style> @var-media foo: not all; @var-media bar: all; </style> <picture> <source media="var(foo)" ...> <source media="var(bar)" ...> </picture> Preload scanner knows to download second <source> immediately. Case #3: <style> @var-media foo: not all; @var-media bar: all; </style> <link rel="stylesheet"...> <picture> <source media="var(foo)" ...> <source media="var(bar)" ...> </picture> Preload scanner knows to download second <source> immediately. If external stylesheet redefines foo or bar, picture selection algorithm has to be re-run and another image may be loaded instead. If inline <style> and external stylesheets define variables in conflicting ways and CSS is not cached (or UA loads it async anyway), then race condition could cause a wasted download, but the same, correct image would be displayed eventually. Case #4: <picture> <source media="var(foo)" ...> <source media="var(bar)" ...> </picture> No image is ever loaded. Case #5: <link rel="stylesheet"...> <picture> <source media="var(foo)" ...> <source ...> </picture> Selection algorithm in preload scanner will match the second source and start loading it. If stylesheet loads later and defines var(foo), then source selection algorithm will be re-run and if var(foo) matches then first source will be loaded and displayed instead. If stylesheet was cached and var(foo) that matches was defined when preload scanner reached the <picture> then the first source will be loaded and displayed (same visible result like in case of delayed stylesheet load, but avoiding wasted download). So I think behavior is well-defined. Case #2 works with preload scanner perfectly. Case #1 is what some authors may prefer, and works reasonably well (with expected latency). Cases #3, #4, #5 are bad performance practices/authoring errors, but still result in well-defined predictable behavior. To avoid performance hit of case #5 I suggest deferring source selection until CSS is loaded *if* the source selection algorithm encounters a media query with an undefined variable. Here's a draft of simplified <picture> source selection algorithm. I'm not going to try to write the algorithm in Hixie style. Here's JS-ish pseudocode instead: function sourceSelection(picture) { var mq; for(var i=0; i < picture.childNodes.length; i++) { var child = picture.childNodes[i]; if (child.tagName != 'SOURCE' || child.namespaceURI != xhtml_namespace) continue; if (child.getAttribute('media')) { var mq = matchMedia(child.getAttribute('media')); mq.addEventListener(sourceSelection); // re-run selection algorithm whenever MQ changes // marked line, see below if (!mq.matches) continue; } if (child.getAttribute('type')) { if (!isSupportedMimeType(child.getAttribute('type'))) continue; } return child; } return null; } In short: choose the first <source> child that has matching media and supported type (missing attributes pass the test). If no matching <source> is found then it's a broken image (like <img> without src). And to prevent potential wasted download in Case #5 outlined above, replace "// marked line, see below" with: if (!CSSisLoaded && mq.cannotFullyEvaluateYet) { addEventListener('CSSisLoaded', sourceSelection); // re-run selection algorithm when CSS is loaded return null; } By "CSSisLoaded" I mean state when browser decides the page is ready to be rendered for the first time (all browsers have some form of delaying first page render until external CSS is loaded [or times out]). It's only an optimisation, so details are not important for interoperability. By mq.cannotFullyEvaluateYet I mean that the media query contains a MQ variable that is not defined, or any other case when browser is not ready to evaluate the media query yet (e.g. MQ refers to viewport size in an iframe that has unknown size). If browser decides CSS is not loaded yet and MQ cannot be evaluated yet then it should not load any source yet and re-run source selection algorithm when either CSS is loaded or MQ can be evaluated (e.g. variable becomes defined, viewport size becomes known, etc.). When CSS is loaded and variable is undefined, then MQ won't match and algorithm proceeds as normal. When MQ can be fully evaluated (regardless of whether CSS is loaded), then algorithm proceeds as normal. Also the algorithm is scheduled to run on the next tick whenever child nodes of <picture> are mutated. Asynchronism answers difficult questions like "what happens when <source> node is inserted or removed while source is being selected". Answer: It won't happen. Algorithm is stateless, idempotent, and appears atomic to JavaScript. >> I don't mind giving authors choice of losing preload scanner if they >> prefer all media queries to be in stylesheets. > > I do mind giving authors a subtle performance footgun. That's a footgun under assumption that: - authors will overwhelmingly choose to use variables in external stylesheets only (and if they do, then that'll prove it's a very desirable feature ;) - pushing of CSS with HTML via SPDY/HTTP2 or inlining FEO proxies or page preprocessors won't be common enough to mitigate this - affected pages won't be hiding image markup from preload scanner by using JS-based templating ("MVC" JS apps) or overuse of Web Components (<html><my-cool-app/></html>) <http://tomdale.net/2013/09/progressive-enhancement-is-dead/> - and that 1 RTT delay to start fetching images when CSS is not cached is a big problem So my hope is that it won't become a common footgun. Some pages will get increased latency, but that still may be an overall win if easier maintenance convinces authors to use smaller media-specific images instead of taking other easy route and serving largest <img> to all clients. Since it's a simple declarative markup, servies like YSlow or PageSpeed can advise authors to use faster markup where necessary and tools can fix it automatically. And browser still have to wait with rendering until external CSS is loaded. There's strong incentive to fix that, and tools that solve this problem (via inlining or HTTP/2 push) will make external MQ variables zero cost as well. >> 1. It makes a CSS feature use the HTML syntax. > > I think this is a weak argument, and it's not obviously a "CSS feature" > any more than e.g. media queries or selectors. > >> 2. It closes door on using external file for breakpoints ever > > No, it doesn't. We can still enable the feature in external files in the > future. I've meant <meta> syntax. Allowing some <meta>-based syntax first and later adding a different, nicer syntax in external stylesheets would be possible, but unnecessarily ugly. >> 3. Not all applications need preload scanner. For example app.ft.com is >> a JS-based offline app. It needs responsive images, but not preload >> scanner. It would be shame if it had to use worse syntax and less >> maintainable solution to enable optimization it does not need. > > As far as I can tell the issue can still happen for offline apps if it > uses an external stylesheet. While technically it could, it would have no impact on user experience, since wasted download simply cannot happen. > 4. It might be desirable to be able to declare scoped media query > variables in <body> for components or something, which could piggyback > on <style scoped>. AFAIK there have been mixed opinions whether <style scoped> should have scoped @-rules. If scoping of MQ variables isn't difficult to implement, that's great. -- regards, Kornel
Received on Saturday, 14 September 2013 21:22:55 UTC