- 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