Requests for comment/ResourceLoader CSS Extensions

This page proposes the idea of adding a module that does more work parsing css in modules (and perhaps in inline styles as well for some cases) and handles various forms of syntactic sugar in it to make css in MediaWiki more useful.

Request for comment (RFC)
ResourceLoader CSS Extensions
Component General
Creation date
Author(s) Daniel Friesen
Document status declined
Decline, on advice from Trevor Parscal -- Tim Starling (talk) 06:03, 17 July 2013 (UTC)[reply]

It's proposed that the code that does the css parsing is implemented to run in multiple ways. Notably for example when encountering css it cannot understand (due to a css hack, etc...) the implementation should have two modes, one which will leave the rule or property as-is so that hacks will still work and another which will discard the css it cannot understand. Additionally it should have functionality that allows us to declare that every selector in a body of css should be prefixed with a certain selector. These things along with some other restrictions may make it possible for us to allow limited css embedded into WikiText editable by any user. Leading to the potential for painless css attached directly to templates without any mess of inline styles,

Note while we're doing this parsing we may take advantage of the fact that we'll be able to count selectors. Besides the maximum number of separate stylesheets IE also has another stupid limit on the number of selectors in a single stylesheet. Hence trying to efficiently concatenate css and stop that issue can create another. Since we're parsing the css there may be some way to try and save that information in a way we can use to separate concatenated css out when it gets too big.

Goals edit

  • Make writing css a little easier with nesting and selector grouping
  • Make modern standardized css just work in their shortest sanest form by automatically adding vendor prefixes needed for compatibility
  • Allow uploaded images on the wiki to be used inside css in a way that is well integrated into the wiki, reliable, and secure enough to allow for use inside of WikiText instead of only in site css.
  • Keep all enhancements compatible with existing css so that we can turn it on by default on all css files and safely offer the functionality within site and user css.
  • Make css secure enough to allow enough css support to have css embedded into WikiText such as infobox templates, message boxes, and layouts like the main page while being completely publicly editable.

Why not SASS or LESS? edit

  • SASS and LESS will incur two layers of processing cost. Neither support our automated LTR/RTL-flipping and @noflip (processed by CSSJanus) or @embed (processed by CSSMin) so we would need to have our CSS processed several times (once by SASS or LESS, by CSSJanus and CSSMin). While custom CSS processing code has the possibility to implement the same functionality as CSSJanus and usurp it doing both jobs at once. (LESS even seams to strip out css comments, meaning after processing we don't even have our @noflip or @embed rules to give to CSSJanus or CSSMin; among also breaking comment based IE hacks, including the IE5:Mac one). Using our own code also lets us attempt to squash the IE maxiumum selectors issue.
  • SASS and LESS cannot give us -mw-file which allows background-images to come from wiki uploads complete with thumbnail generation and dependency linking to File: pages. Even if you manage to make something close it won't be safe for use in WikiText.
  • SASS and LESS are both languages of their own, they are not written to simply enhance css, but to sugar it with their syntax and are limited in a few ways we wouldn't want:
    • LESS at least is tripped up by things it can't parse. You have to add explicit escaping in order to write an IE filter. In other words LESS is incompatible with existing css without conversion that should not be necessary.
    • As far as I know, neither language has a sane syntax to express the case where you want to write a ::selection that needs to be completely duplicated into an extra ::-moz-selection because you can't use ::selection, ::-moz-selection because of css error handling rules.
    • Neither have the concept of understanding css properties. They won't fix your vendor prefixes. So you have to write messy mixins and functions and use those instead. Which is insane since you go into some completely separate syntax just to express what was supposed to be a single css property. This also means that you may not be able to do some things that are possible in the real property (most examples of these two with border-radius are simple functions that dump your input into properties; they don't take into account differences in syntax between implementations and hence can't support things like uneven elliptical radii). And of course also means that css authors will have to memorize which properties need prefixes and require mixins/functions and which properties don't; Instead of just being able to write their actual css and have the engine fix the properties that need vendor prefixes for them.
  • SASS and LESS just add syntax. They don't make it possible to securely allow css to be made editable by any user.

Syntax edit

The following sections detail various syntax extensions intended to be supported in css loaded by resource loader, and some (noted in the section) intended to be supported in inline style tags as well.

@-mw-extendedcss edit

These css extensions define an @-block that may be used for some of our css extensions. Some of our css extensions may be in conflict with normal css that has simply been included into ResourceLoader but not written to take advantage of these features. In those situations it's useful to have a block that will allow us to enable some features only within the block.

@-mw-extendedcss {
  /* ... */
}

Hierarchy edit

A nested & syntax is defined to allow complex nested css to be written simpler:

#foo {
  background: black;
  & a {
    color: black;
    text-decoration: none;
    &:hover, &:active {
      text-decoration: underline;
    }
  }
}

This syntax is not randomly invented just for Resource Loader. The syntax comes from the css3-hierarchies spec. These rules will likely be implemented with the same parsing rules as the spec and converted to multiple rules with full selectors in the output css.

The & isn't legal in old css so this shouldn't be in conflict with any existing css so this extension will be available without the need for the extended css block.

:matches() edit

A subset of the css selectors4 :matches() selector will be implemented. selectors4 does not allow combinatorial selectors inside :matches() like :matches(div a) so it's only usable in cases we can expand to full rules anyways. However in some cases when the css3-namespace syntax is used in :matches() it can be tricky to turn into a full selector. Considering it's not even usable yet and we have no need for it, any :matches() including css3-namespace syntax will be left as is instead of being converted into multiple full css selectors and may trigger the code to duplicate the rule's contents to ensure that any normal selectors are not broken by the inclusion of a new selector current browser cannot parse.

#foo :matches(h1, h2, h3, h4, h5) {
  font-size: 1em;
}
#foo a:matches(.selected, :active, :hover) {
  text-decoration: underline;
}

Like the & this should not be in use in old css so this will be available without the need for the extended css block.

-mw-file( name ) edit

A -mw-file( 'Name.png' ) syntax is defined for any place that accepts a url(). The use of it expands to a url() pointing to an uploaded file. I am unsure whether we want to use -mw-file( 'Name.png', 10 ); or -mw-thumb( 'Name.png', 10 ); for the thumbnails.

-mw-file can't be in conflict with any existing css so it is available without the extended css block. Additionally due to how useful this can be it will also be usable inside of inline css. Because -mw-file does not suffer from the vulnerabilities of url() it will be permitted inside of WikiText where url() is stripped out.

bawolf also adds that using this syntax we can also register image dependencies to pages using it inline. Stylesheets too. And the use of the thumbnail syntax rather than normal urls also allows us to make sure thumbnails are generated on wikis without 404 thumbnail handlers.

Automatic vendor compatibility edit

Various new css properties need multiple vendor prefixes in order to work in all supported browsers. The actual writing of this by hand is worthlessly verbose and prone to error. Part of the css extension intent is to allow a standard property to be written normally like border-radius: 5px; and then have all the vendor prefixes implicitly added.

It's possible for these to conflict with old css so they are only enabled inside of an extended css block. That however may possibly be reviewed and changed. With actual parsing it's possible that we could just kill off old stuff that's not necessary and expand it right. As long as no-one is abusing vendor prefixes to give different browsers different things.

Note that due to the buggy differences in implementation of display: box; between WebKit and Gecko (the previous replaced css3-box) and the deprecation in favor of a new css3-flexbox spec we won't be expanding the flexible box model properties. Note that this does not apply to box-sizing which is noted in other specs and we WILL be supporting vendor prefixes for.

Additionally while filter and behaviors can often be used to give IE some css3 features these often have side effects and in some cases are incompatible with each other (eg: you can't use a border radius behavor and a gradient filter on the same element). Because of potential inconsistencies and other issues we can't generate IE compatible filters in a reliable way.

Vendor compatibility inside selectors is also intended. Right now the ::selection selector comes in ::selection and ::-moz-selection. The important thing being that because browsers are supposed to drop entire rules they don't understand, when you use a new selector like these you have to duplicate your entire css rule.

In other words, this css inside Resource Loader:

@-mw-extendedcss {
  ::selection {
    border-radius: 5px;
  }
}

Will automatically become

::selection {
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;
  border-radius: 5px;
}
::-moz-selection {
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;
  border-radius: 5px;
}

;) though we could probably make it smarter:

::selection {
  -webkit-border-radius: 5px;
  border-radius: 5px;
}
::-moz-selection {
  -moz-border-radius: 5px;
  border-radius: 5px;
}

I haven't thought of it yet, but we may introduce a syntax that will let you declare that you want a rule duplicated to separate selectors for compatibility. In other words a way that will let you write .foo .bar, .hello > .world { color: red; } but ensure that they are separated into two duplicated rules so that the > selector does not cause the .foo .bar to not work in IE6.

-mw-embed edit

It's proposed that we deprecate (but still support) /* @embed */ in favor of -mw-embed(). /* @embed */ background-image: url(images/foo.png); would become background-image: -mw-embed( url(images/foo.png) );. The syntax would naturally also work with -mw-file in the form background-image: -mw-embed( -mw-file('Foo.png') );.

Part of the reason for this is to drop a comment hack abusing comments for something they're not in favor of a proper css extending syntax. But additionally this has the advantage of properly supporting some of css' advanced features such as this:

.foo {
  background-image: -mw-file('Background.jpg'), -mw-embed( url(images/icon.gif) );
}

This bit of css includes a background with two images. Something defined by css3-background and already implemented by practically every modern browser (IE9+, and for every browser every version reasonable to expect to be used). In this case we want the icon to be embedded but don't want the large .jpg to be embedded into the stylesheet. Using the -mw-embed syntax instead of /* @embed */ allows us to have only the icon embedded.

We would likely migrate the embedding code from our minification code into our css parsing code. We would likely want to do this so that -mw-file+-mw-embed combinations can be efficiently implemented in a location that understands both the url of the file as well as the real non-hack way to fetch the content.

calc() edit

calc() is defined by css3-values it allows units to be defined by mathmatical expression. calc()'s real power is the ability to do things like calc( 100% + 1px ) which are impossible to do outside the browser. However calc() can also be useful with simple expressions for example when you want to set a width of 500px minus the border width of 1px on two sides. Instead of calculating this yourself and writing width: 498px; you could just write width: calc(500px - 1px * 2); and have it calculated. This may be important if you are using that same value of 500px all over your code in multiple spots referring to the same thing. If you later decide to use a different size you only have to change the 500px value to the new size everywhere instead of being forced to recalculate the value of every css property.

The plan here is to have ResourceLoader pre-process any simple calculation that can be done beforehand outside of the browser so that we are able to use calc() at least to write css based on what we intend intend of writing what is essentially compiled output. (If we introduce variables this will also be inevitably requested.)

... edit

If you have an idea for another piece of syntax to add please start a discussion on the talkpage. Please note that our intention here is to extend css, not to create another language like sass or less. Hence any syntax must be based on css and the things it allows within it's error correcting syntax.

Parsing notes edit

  • We should try to work in understanding of css hacks instead of choking on them or simply ignoring them.
    • Tricks like * at the start of rules should be taken into account. On the data side we should properly parse *background-color: red; as a property with name "background-color" and value "red" grouped with an annotation { hack: "*-hack", target: { browser: "IE", version: { upto: 7 } } since the * hack only works in IE up to and including IE7.
      • Parsing css this way will allow us to handle custom features such as -mw-file() or other completely custom css properties and support them even within css hacks. At the same time the target: and understanding of the behavior of css hacks will additionally allow us to avoid additional css. (For example say -mw-test is a custom property that expands to -moz-test and -ms-test, -ms-test, and -ms-test2 and -ms-test is supported by IE up to IE7 while IE8 supports -ms-test2. If we found *-mw-test: foo; we could expand this to -ms-test: foo; because we understand that it is a -mw-test rule but it has the *-hack applied to it and hence only applies to IE7. Because Gecko wouldn't parse it we'd skip the -moz- rule, and because we know that IE8 also won't parse it we can skip the -ms-test2.)