Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork3.4k
Stylo hacking guide
Stylo is an attempt at integrating Servo's styling system into Firefox (specifically, Firefox's rendering engine called Gecko). This means that things like CSS parsing, cascading, etc are all handled by Servo's code, in parallel (providing some nice performance wins).
This is a process involving a long build step and some large downloads, so it's recommended that you initiate this beforehand.
The Stylo code is in themozilla-central Mercurial repo, but is not built by default.
First, clone the mozilla-central repo. It might be beneficial to cloneservo as well. If you want to try the more complicated workflow, weusegit-cinnabar (git clone hg::https://hg.mozilla.org/mozilla-central)with the mercurial repo (which lets you work with mercurial repos using git).
Create amozconfig file if it doesn't already exist, and add the following line:
ac_add_options --enable-styloSet up the dependenciesfor servo. Also, run./mach bootstrapin your stylo clone to get the Gecko dependencies.Rust needs to beinstalled as well; It is recommended to install it fromrustup.rs with the stabletoolchain.
Once all the dependencies are sorted out, run./mach build in the stylo clone.
Once it builds, you should be able to run it with./mach run --disable-e10s https://en.wikipedia.org (or some other site)
Note: By default you get a non-optimized build with debug symbols, so expect it to be slow.These options can bechanged in themozconfig file if you need to.
The Stylo clone contains a copy of Servo in theservo/ directory. Any changes to that code will require making a pull request toServo on github, however it is also okay to make a pull request tostylo-flat instead.
Most stylo issues marked for newcomers will require implementing some CSS property and/or theglue code that copies it over to Gecko. This document will focus on the code involved in those tasks.
components/style is where almostall of this code lives. All file paths in this document will be relative to this folder.
Most properties live insideproperties/longhand.("longhands" are CSS properties likebackground-color, whereasbackground is a "shorthand" and can be used to specify many longhands at once). These files areall Mako templates — Mako is aPython template library for code generation.
Each CSS property is implemented inside a<helpers:longhand> tag. For example,here is the code for thecolumn-width property. It controls how the property value is stored, how toparse/serialize it, and how to convert between the"specified" and"computed" forms.
properties/gecko.mako.rscontains the code that copies the value for the properties over to the Gecko side. For example, thecode which handlescolumn-width ishere.
There are some generated bindings incomponents/style/gecko/generated/. If you think you need to touch those files,you don't.
Those files are there so Servo can build Stylo without pulling mozilla-central, but on normal mozilla-central builds, those files are generated at build time, and should be onobj/dist/rust_bindings/style/. If you make changes to the servo repo that depend on those changes to build, you'll need to update thegenerated/ files on your PR, so that Servo CI passes.
A lot of the properties are just a choice of keywords. Let's takeobject-fitas an example.
The code for it ishere.
${helpers.single_keyword("object-fit","fill contain cover none scale-down",products="gecko",animatable=False)}
That's it! This will generate all the code needed to parse the keyword and handle its values. Theexpanded code will look something like the code forcolumn-width above. This also generates thecode for copying the property over to Gecko.
Let's unpack this a bit. What it's saying is thatobject-fit is a keyword property. Itcan take one offill | contain | cover | none | scale-down as values. As thespecandMDN page say, the default/initial value isfill,sofill is the first one in the list. The order of the rest doesn't matter.The spec/docs also say that its animation is "discrete", which means that if you try to animate this propertyit will be the first value att < 0.5 and the second att > 0.5. In Servo we useanimatable=Falsefor this.
Now, Servo doesn't actually know how to handle this property in layout. We could add code for that,but that can get pretty complicated. So in many cases we have properties withproducts="gecko",meaning that they're only parsed inside Stylo. Most new properties you implement will be like that.
page-break-inside is a slightly more complicated property. It's implementedhere.
${helpers.single_keyword("page-break-inside","auto avoid",products="gecko",gecko_ffi_name="mBreakInside",gecko_constant_prefix="NS_STYLE_PAGE_BREAK",animatable=False)}
It's clear from the invocation that this is a gecko-only non-animatable property with the values "auto" and "avoid",where the default value is "auto".
But there are some extragecko_ fields there. What do they mean?
To learn this, lets revisitobject-fit — Recall that the code for copying the propertyover to Gecko is autogenerated. On the Gecko side, this is stored insidensStylePosition,insidemObjectFit.These values areNS_STYLE_OBJECT_FIT_* constants definedhere.
When generating the code for theobject-fit keyword, this wasguessed by the code generation. It knew thatobject-fitwasprobably stored inside a field with the namemObjectFit, and its values probably mapped toNS_STYLE_OBJECT_FIT_constants. So it generated code that mapped Servo-sideobject-fit enum values to these constants, and stored theminsidemObjectFit. (The style struct that it was stored in,nsStylePosition, was known because of the fileobject-fit was defined in — each file inproperties/longhands/ corresponds to a Gecko style struct.)
The generated code for copying over to Gecko looks like
#[allow(non_snake_case)]pubfnset_object_fit(&mutself,v: longhands::object_fit::computed_value::T){use properties::longhands::object_fit::computed_value::TasKeyword;let result =match v{Keyword::fill => structs::NS_STYLE_OBJECT_FIT_FILLasu8,Keyword::contain => structs::NS_STYLE_OBJECT_FIT_CONTAINasu8,Keyword::cover => structs::NS_STYLE_OBJECT_FIT_COVERasu8,Keyword::none => structs::NS_STYLE_OBJECT_FIT_NONEasu8,Keyword::scale_down => structs::NS_STYLE_OBJECT_FIT_SCALE_DOWNasu8,};self.gecko.mObjectFit = result;}#[allow(non_snake_case)]pubfncopy_object_fit_from(&mutself,other:&Self){self.gecko.mObjectFit = other.gecko.mObjectFit;}
However,page-break-inside does not have guessable field names. It is stored inmBreakInside(notmPageBreakInside), usingNS_STYLE_PAGE_BREAK_*(notNS_STYLE_PAGE_BREAK_INSIDE_*).
So, we find out the correct field name, and the correct prefix for the constants, and explicitlyspecify them in thesingle_keyword() call.
When working on one of these, if you can't find the style struct or field name, don't hesitate toask. Usually the easy issue will mention these, but first try not specifying these things, and if itdoesn't compile, only then look for the actual field name.
Once you have made these changes, build with./mach build, and run. Test out the property —usually there are some testcases on theMDN documentation for the CSS property in question. Take screenshots of the testcases running in stylo.
Make a pull request. This can be done in two ways — one is to make one to the stylo-flatrepository itself. Manishearth can review the PR there, and once done, graft it over to the main Servo repository.
Alternatively, you can make the pull request directly to Servo. To do this,make a single commit out ofyour changes, and make a patch out of it usinggit format-patch @^ servo --relative=servo. This willcreate a.patch file. In a Servo clone, rungit am /path/to/patch, and it should create a commit foryou containing the same changes. From there,make a pull request to the Servo repository. Thismethod is preferred, but it's okay to do it the other way too.
Be sure to include the screenshots in your pull request! (and a link to the code behind the testcaseif it wasn't the MDN one). We have automated tests as well, but right now Stylo doesn't implementenough to get reliable test results, so the relevant reftests may fail due to unrelated issues.
Unlike the above properties, some properties take values instead of keywords. Many of those share a basic type in their implementation, and so they have some common code for their initial values, parsing, serialization and the gecko glue. For example, thebackground-color longhand property takescolor. So do the other longhand properties likeborder-top-color (or any otherborder-<side>-color),text-decoration-color, etc.
In such cases, we define the types along with their necessary methods in the modules insidevalues/specified andvalues/computed and use them to ease with writing the parsing/serialization code.
Now,background-color takes onlycolor as input and nothing else. The necessary code for parsingcolor as a specified value is implementedhere, whereas for the computed value, it's been re-exportedhere. So, we can make use of this directly to write the longhand parsing and serialization code. Please read theproperties hacking guide for more about this part.
The code for implementingbackground-color ishere.
${helpers.predefined_type("background-color","CSSColor","::cssparser::Color::RGBA(::cssparser::RGBA { red: 0., green: 0., blue: 0., alpha: 0. }) /* transparent */",animatable=True)}
Let's break this up. Firstly,predefined_type is a helper method very similar tosimple_keyword - only difference is that it generates the code for properties that make use of predefined types for their values. The first argument says how the property is parsed (i.e., what the CSS parser should expect), the second one is the type to which its value should be parsed to. Now, in order for this to work, we should've already implemented the necessary traits for the type (which, as said above, has already been done forCSSColor, and hence the name "predefined" type). The next argument is the initial value, followed byanimatable, which has the same meaning as explained insimple_keyword method.
For easy issues, you often don't have to worry about all this, we'll mention it in the issue for you. But, this is useful to know if you plan to fiddle around the glue code.
In the gecko glue,we have a nice setup for auto-generating the value setting methods for these types. This will call theimpl_colordefined up top, and it will generate a code something like this.
#[allow(unreachable_code)]#[allow(non_snake_case)]pubfnset_background_color(&mutself,v: longhands::background_color::computed_value::T){use cssparser::Color;let result =match v{Color::RGBA(rgba) =>convert_rgba_to_nscolor(&rgba),Color::CurrentColor =>0,};self.gecko.mBackgroundColor = result;}#[allow(non_snake_case)]pubfncopy_background_color_from(&mutself,other:&Self){let color = other.gecko.mBackgroundColor;self.gecko.mBackgroundColor = color;;}
This makes some assumptions about the gecko side. We assume that these simple predefined types exist as astruct field in gecko (with a camel case name, prefixed with"m", as inmBorderSpacing forborder-spacing). But sometimes, this doesn't work out very well. For instance, let's takeborder-top-color as an example. In Gecko, this is alinked list. So, we have to explicitly specify the node to which the border color has to be applied. In code generation, this is implemented by directly callingimpl_colorwith custom arguments.
We don't need to worry about the complicated code generated for this. The point is that if a struct field has a different name, it can always be specified in the method with the key,gecko_ffi_name=mFoobar. This will also be mentioned in the easy bug.
TODO. Ask in IRC if you need help with this.
When your code is complete and you're ready to submit a PR, first make sure the following commands succeed:
/mach test-tidy(checks some style guidelines)./mach build -d(to make sure your code builds in Servo mode)./mach build-geckolib(to make sure your code builds in Gecko mode)./mach test-unit -p style(unit tests for the style crate)./mach test-stylo(unit tests for Gecko integration)
See alsoContributing for more general (not Stylo-specific) information on submitting PRs, and thestylo etherpad for details of how Stylo patches are merged into mozilla-central.