Amphtml: Intent to implement: partially drop restriction on `style` attributes

Created on 31 Oct 2017  ·  70Comments  ·  Source: ampproject/amphtml

Some restrictions have to be preserved, but most can be dropped at this time, I believe.

Some considerations:

  1. !important should be prohibited as with stylesheets (should be done already for SVG).
  2. url() values (e.g. for background-image, etc) should be rewritable by cache (should be done already for SVG).
  3. The only allowed values of position are static, relative (and absolute?).
  4. Based on some future optimizations we want to do, we should probably also disallow top:X, left:X, right:X, bottom:X. Though we may be able to remove this restriction.
  5. SSR should be able to work with publisher-authored style attributes.

The only real restrictions comparing to the current state of things are position and top/bottom/etc restrictions. The question is whether, given these restrictions, this is still a good idea to implement.

/cc @Gregable @honeybadgerdontcare @jpettitt

INTENT TO IMPLEMENT High Priority Bug caching runtime

Most helpful comment

@jridgewell @nainar @dvoytenko I did a quick test and my conclusion is it does not block rendering for the whole document which is the big difference vs CSS in head. Chrome seems to progressively render and paint as document is streamed. In the following example, the first box is rendered immediately, the second box has a huge inline CSS that takes a bit to arrive, so that one and the next one are blocked.

untitled

https://storage.googleapis.com/ampconf-76504.appspot.com/test.html

Maybe we should put limit per element instead of an overall limit.

All 70 comments

Even with the position and top/bottom/etc restriction I think this is well worth doing. It significantly reduces the complexity of converting existing content, particularly article bodies with journalist authored html.

What will we do with mustache templates and amp-bind attributes here? Would we want to run a CSS parser in javascript runtime?

I recommend that we do not allow style attributes inside a mustache templates or as amp-bind attributes.

I also suggest that we count the bytes in these style attributes toward the amp-custom byte limit, otherwise we'll see folks making things worse by stuffing style attributes full of rules to avoid the byte size limit.

Templates are fine. amp-bind probably not at first, but could consider separately.

Byte size, however, is not relevant here. The reason why we want limit on CSS style up top is to ensure that we start rendering body faster. This is because, all CSS styles parsed, even those that are never used or used at the very bottom of the document, blocks rendering of the whole document. Style attributes actually allow us to achieve this goal better since they are by definition scoped and thus we start rendering faster.

Actually, template sanitizer needs to be updated to remove !important and other rules.

Based on some future optimizations we want to do, we should probably also disallow top:X, left:X, right:X, bottom:X. Though we may be able to remove this restriction.

Allowing top/left/right/bottom styles would unblock something that we at Pinterest have been stuck on for a while. For the current implementation of our pin grid in AMP, we calculate the position of each pin (for multiple viewports) server side and use that to generate a CSS class for each pin.

for example:

.Pin__27 {
  left: 728px;
  top: 958px;
}
.Pin__28 {
  left: 182px;
  top: 988px;
}
.Pin__29 {
  left: 364px;
  top: 988px;
}
.Pin__30 {
  left: 910px;
  top: 1023px;
}
.Pin__31 {
  left: 0px;
  top: 1123px;
}
...

There are two major drawbacks to this approach that inline styling of top/left attributes could help to address.

First, this adds a ton of CSS to the style tag, which means we are always operating dangerously close the 50k limit. And, since most of these pins are below the fold initially, that styling does not need to block the initial page render.

Second, because we need to calculate the pin positions up front, we can't do anything with dynamic content. We'd like to start using <amp-list> and fetch additional and/or personalized content after the initial page load, but without some sort of custom styling per-item, this is not possible.

Ideally, this would come with some sort of way to update styles (amp-bind?) when viewport size changes, since this wouldn't be compatible with media queries.

If you decide to continue disallowing top/left/right/bottom attributes, I would love to discuss other possible ways to solve our grid problem (maybe additional style tags in amp-list or mustache templates?)

see:
https://www.pinterest.com/amp/explore/highland-cattle/

/cc @alabiaga

Is it possible to release this early under an experiment flag? We have some work that is blocked on this change that we would love to get started on ... /cc @ericlindley-g

/cc @jridgewell

Ok, so there's an issue with this on the runtime side. Up until this, we've considered element.style to be a pristine record of _runtime's_ changes to the element's style. Specifically, we can set a value, then unset it to get back to the publisher supplied style state.

<style>
  .Pin__27 {
    left: 728px;
    top: 958px;
  }
</style>
<script>
  pin27.style.top = '10px';
  pin27.offsetTop; // => 10
  pin27.style.top = '';
  pin27.offsetTop; // => 958
</script>

This style (:rimshot:) of mutate then unset is used extensively in FixedLayer, dom#toggle, and I'm sure a few other places.

So it's possible to allow this, but under the current AMP usage, we would would either have to due a runtime transformation or refactor a lot of very difficult code (FixedLayer).


The runtime transformation we can do is:

  • Detect inline styles on an element

    • Cut style attribute

    • Add a uniq attribute to the element (something like -i-amphtml-inline-style-${id++})

    • Create a new style element, add copied styles to it with selector [-i-amphtml-inline-style-0]

    • Inject style element into head.

This "simulates" inline styles, while preserving the runtime expectations on element.style object.

Thanks for your input Justin. I will work on this runtime transformation
and will keep you up to date with its progress.

On Tue, Feb 20, 2018, 6:12 PM Justin Ridgewell notifications@github.com
wrote:

Ok, so there's an issue with this on the runtime side. Up until this,
we've considered element.style to be a pristine record of runtime's
changes to the element's style. Specifically, we can set a value, then
unset it to get back to the publisher supplied style state.

This style of mutate then unset is used extensively in FixedLayer,
dom#toggle, and I'm sure a few other places.

So it's possible to allow this, but under the current AMP usage, we would
would either have to due a runtime transformation or refactor a lot of very

difficult code (FixedLayer).

The runtime transformation we can do is:

  • Detect inline styles on an element

    • Cut style attribute

    • Add a uniq attribute to the element (something like

      -i-amphtml-inline-style-${id++}

    • Create a new style element, add copied styles to it with selector

      [-i-amphtml-inline-style-0]

    • Inject style element into head.

This "simulates" inline styles, while preserving the runtime expectations
on element.style object.


You are receiving this because you were assigned.
Reply to this email directly, view it on GitHub
https://github.com/ampproject/amphtml/issues/11881#issuecomment-367154160,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAeKUPta5AUPJdaB6S7IAhTAY_bGIVHbks5tW1FngaJpZM4QNEjN
.

Inject style element into head.

@jridgewell my understanding is that the 50k CSS limit was meant to prevent a hard block in the render path. The styles that we'd like to inline are for content below the fold. Would adding an arbitrary amount of inline styling lead to performance issues? or do you mean to suggest that the inline styles would be injected into the head _after_ initial render?

or do you mean to suggest that the inline styles would be injected into the head after initial render?

These would be after initial render. We could discuss SSR (doing the above algorithm on server) with the AMP Cache devs, but it would have to fall under the 50k limit.

In the end, it'd probably mean still forbidding the style attribute (which the browser would control) and instead giving you an amp-style (or whatever name) that we could control when it applies.

amp-style won't degrade gracefully. Maybe we can split this work into stages, e.g. for first pass, rewrite style to '' for fixed elements in FixedLayer#setup and output a user error.

@choumx any thoughts on whether this could support media queries? IIRC, inline styles don't normally support media queries, but since it looks like this is actually going to be implemented as a style tag, that isn't necessarily a constraint.

alternatively, we would scoped style tags be a possible alternative?

it looks like this is actually going to be implemented as a style tag

We're trying to implement this using the normal style attribute for now. The idea is we want graceful fallback and to avoid diverging from HTML if possible.

would scoped style tags be a possible alternative?

Hmm, why doesn't putting them in <style amp-custom> work?

We're trying to implement this using the normal style attribute for now. The idea is we want graceful fallback and to avoid diverging from HTML if possible.

makes sense. 👍

for context, <style amp-custom> see: https://github.com/ampproject/amphtml/issues/11881#issuecomment-351826881. we're using the <style amp-custom> right now, but it is preventing us from positioning content (with position: absolute) that loads after the initial render of the page (with amp-list, for instance).

Security: Need to integrate a CSS parser (e.g. Caja) for dynamic templates like amp-mustache.

RULE #4 - CSS Escape And Strictly Validate Before Inserting Untrusted Data into HTML Style Property Values

https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.234_-_CSS_Escape_And_Strictly_Validate_Before_Inserting_Untrusted_Data_into_HTML_Style_Property_Values

Correction: No need for CSS parser since JS execution from CSS is not possible on modern browsers.

Hi folks,

Just to clarify a few things here. CSS can be added to a page in three ways:

  1. External stylesheet - Not paint blocking in Chrome (As of M66 - Chrome parses external stylesheets only as they are applied on elements)
  2. <style> tag - Render blocking - needs to be parsed to see what style the LayoutTree will take on.
  3. style attribute - Render blocking (for same reason as <style> tag) - has higher specificity as compared to any style expressed in <style> tag.

Also, since the reason for the 50kb CSS limit is to encourage developers to only deploy CSS they need and help speed up time to First Paint. I think the styles expressed using inline styles should count towards the limit. Maybe with a slight increase in the limit?

@nainar why would we want to consider inline styles against the 50k limit? In the use cases I am imagining, these styles would be applied to elements below the fold initially, and so would not have to block initial render. Additionally, as mentioned above, we need to allow inline styles on elements in templates ... not sure how to incorporate those styles into the 50k limit.

I don't believe it's possible to determine a priori whether a given style is above or below the fold.

We did discuss children of <template> elements and agree that those should not apply to the 50KB limit.

I don't believe it's possible to determine a priori whether a given style is above or below the fold.

Sure. I guess what I am proposing is that adding inline styling to an element could block or delay render for that element. That would still be useful for us--we would just have to be careful about only using this for content below the fold or other secondary content that doesn't need to be available on initial render. An inline style feature with size limitations would probably not be useful to us.

For static content, it should still help a bit since you'll save bytes on the CSS selectors e.g. .Pin__27, etc. Aren't you guys planning to use amp-list eventually too?

Aren't you guys planning to use amp-list eventually too?

yes. we are hoping to use inline styles in amp-list templates. that would get around the size limit.

would love to see a way to get more styles on the initial html, too. my feeling is that secondary content and content below the initial fold should have different requirements on what is and isn't acceptable in terms of blocking render.

Dropping some notes from an offline meeting I had with @choumx , @alabiaga , @honeybadgerdontcare:

The work to do here includes:

  • [ ] Verify url() values are rewritten by AMP cache (this already works in SVG's case, so likely no change).
  • [ ] Server Side Rendering will add !important to properties it sets.
  • [ ] Validator ensures that !important and <!-- not present in style attributes. This can be a regex.
  • [ ] Validator will validate css correctly parsing (loosely, for example, won't validate what are considered valid CSS properties) the same as it does with stylesheets.
  • [ ] Validator will disallow position:sticky / position:fixed properties in style attributes, but continue to allow them in <style> tag.
  • [ ] Validator will implement a 50k limit for the sum of <style amp-custom> and style attributes. First iteration will count attributes in <svg> and <template> tags and children. We will verify that the new <svg> constraint doesn't break a significant number of pages.
  • [ ] Later iteration will exclude <template> tags and children from the 50k limit.

@Gregable For server side rendering, what properties are set that require !important? Would merely increased specificity be sufficient, or do they really need to be !important?

server-side rendering is doing what the Runtime would do, setting several properties such as display, height and width based on how the layout should be applied.

this:

Validator will implement a 50k limit for the sum of <style amp-custom> and style attributes. First iteration will count attributes in <svg> and <template> tags and children. We will verify that the new <svg> constraint doesn't break a significant number of pages.

combined with this:

Later iteration will exclude <template> tags and children from the 50k limit.

could lead to an abuse of