Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement CSS OM spec #5

Open
devongovett opened this issue Nov 19, 2021 · 7 comments
Open

Implement CSS OM spec #5

devongovett opened this issue Nov 19, 2021 · 7 comments
Labels
enhancement New feature or request

Comments

@devongovett
Copy link
Member

devongovett commented Nov 19, 2021

This could be the JS API for manipulating CSS, rather than inventing our own.

@devongovett devongovett added the enhancement New feature or request label Dec 12, 2021
@devongovett devongovett changed the title Implement CSS Typed OM spec Implement CSS OM spec Apr 4, 2022
@lucacasonato
Copy link
Contributor

This seems like a great idea. We want to do the same for Deno (implement CSSStyleSheet and friends). See denoland/deno#13898.

I did a little investigating on this last week, and came to the conclusion that we'd likely want to use rust-cssparser, which led me to look into how parcel-css does CSS parsing internally, which then led me to the conclusion that the code in this repo is already 80% there towards implementing CSSOM.

The primary things I have found that need to be tackled to make this work:

  • Instead of just being able to parse and serialize entire sheets, one must also be able to parse serialize individual attributes / rules / media tags etc. This means Parse and ToCss would need to be exposed for more attributes.
  • Make sure the internal representation is high granularity enough for what CSSOM requires. Ie does one need to be able to differentiate between #ff0000 and rgb(255, 0, 0)?
  • Confirm that the way the printer serializes values matches the CSSOM spec (https://andreubotella.com/csswg-auto-build/cssom-1/#serializing-css-values).

@devongovett
Copy link
Member Author

Instead of just being able to parse and serialize entire sheets, one must also be able to parse serialize individual attributes / rules / media tags etc. This means Parse and ToCss would need to be exposed for more attributes.

This makes sense. I think we can expose Parse / ToCss. That also means exposing Printer, which has some internal stuff in it at the moment that I would like the freedom to change in the future without causing breaking changes. So I'll probably do some refactoring there first.

Make sure the internal representation is high granularity enough for what CSSOM requires. Ie does one need to be able to differentiate between #ff0000 and rgb(255, 0, 0)?

Yeah. Firefox/Servo retains a reference to the original color string as written in addition to the parsed RGBA value. We could either do that, or perhaps have a different parsing mode that has enum variants for each color type. The current representation is optimized for memory usage which I found to have a big impact on performance, so we'll want to test a bit here.

Confirm that the way the printer serializes values matches the CSSOM spec

I think it should mostly, at least with minify set to false. One way to confirm this would be to run WPT. I think Deno has a way to do this already?

@devongovett
Copy link
Member Author

Another thing I found hard when attempting this before (#10) was that CSSOM is a mutable, reference-based API, which is a little at odds with the rust borrow checking system. For example, you can have a rule that is not attached to a stylesheet, or removed from a parsed stylesheet but retained by the JS garbage collector. One way to solve that would be to use Rc/RefCell for each rule, but this impacts performance in our main use case which is minification/compilation of entire stylesheets. So we'll need to figure out a way to have both. Any ideas there would be useful!

@lucacasonato
Copy link
Contributor

lucacasonato commented Apr 4, 2022

So for the Deno, we don't actually have any "live" bindings between Rust and JS. All our communication with Rust happens through "ops" which are essentially JS -> Rust RPC calls.

To implement this we'd have a couple of op_css_parse_stylesheet / op_css_parse_rule etc calls that return static representations of the parsed CSS structures. In JS we then take those static representations and bind them together into the CSSOM API with all of it's live bindings. If we want to do something like serialize a rule, we have two options: either do it ahead of time, during the initial parse, or we have a seperate op_css_serialize_rule op that takes the static representation of a rule as arguments and returns a string. The CSS ops are essentially pure functions, and all state is held in JS.

All that is to say, that for Deno's use case we don't actually need a full Rust implementation of CSSOM with live bindings and references and stuff. We only need the parse and serialize primitives. For a nice Rust API though, you'd probably need Rc<RefCell<>>'s indeed though.

@devongovett
Copy link
Member Author

Ah that's a nice idea... I'm not sure a full Rust implementation of CSSOM is necessary since the exposed structs here are already higher granularity. I was mainly looking at exposing it for the JS API. But implementing it in JS and using lower level bindings might be a nice idea.

@lucacasonato
Copy link
Contributor

lucacasonato commented Apr 4, 2022

Just dug into this some more, and just the existing parsers will not quite be enough I think. Parse a rule does not have an implementation in parcel_css AFAICT (only Parse a list of rules). parse_one_rule is what I was looking for :-)

@devongovett
Copy link
Member Author

Experimented with this a bit more on my side. A few complexities that will need to be handled:

  • Shorthand properties can be retrieved even when not explicitly set, if all longhands that they include are set. See https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue
  • Setting properties that are shorthands also affects the longhands, and visa versa. See https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setproperty. We will need some metadata about which properties are related to one another.
  • Complexities with logical properties also potentially affecting physical properties.
  • Properties must be ordered. At the moment a DeclarationBlock splits declarations into two lists: one important and one normal. That reduces memory usage by avoiding an extra boolean per declaration, but you lose ordering information. Perhaps we can use smallbitvec for this like Firefox.
  • How to deal with lifetimes in Property and values when setting individual properties. Need to store the underlying string values somewhere as well at the moment in case something in the parsed value references them. Would be nice to only do this when necessary, or have an Owned version.

@devongovett devongovett mentioned this issue Apr 27, 2022
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants