<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.3">Jekyll</generator><link href="https://www.azabani.com/feed/tag/home.xml" rel="self" type="application/atom+xml" /><link href="https://www.azabani.com/" rel="alternate" type="text/html" /><updated>2025-12-24T03:25:33+00:00</updated><id>https://www.azabani.com/feed/tag/home.xml</id><title type="html">/diːˈlaːn(.)ˌʕaːzəbaːniː/</title><entry><title type="html">Web engine CI on a shoestring budget</title><link href="https://www.azabani.com/2025/12/18/shoestring-web-engine-ci.html" rel="alternate" type="text/html" title="Web engine CI on a shoestring budget" /><published>2025-12-18T10:00:00+00:00</published><updated>2025-12-18T10:00:00+00:00</updated><id>https://www.azabani.com/2025/12/18/shoestring-web-engine-ci</id><content type="html" xml:base="https://www.azabani.com/2025/12/18/shoestring-web-engine-ci.html">&lt;p&gt;Servo is a greenfield web browser engine that supports many platforms. Automated testing for the project requires building Servo for all of those platforms, plus several additional configurations, and running nearly two million tests including the entire Web Platform Tests. How do we do all of that in under half an hour, &lt;em&gt;without&lt;/em&gt; a hyperscaler budget for compute and an entire team to keep it all running smoothly, and securely enough to run untrusted code from contributors?

&lt;p&gt;We’ve answered these questions by building a CI runner orchestration system for GitHub Actions that we can run on our own servers, using ephemeral virtual machines for security and reproducibility. We also discuss how we implemented graceful fallback from self-hosted runners to GitHub-hosted runners, the lessons we learned in automating image rebuilds, and how we could port the system to other CI platforms like Forgejo Actions.

&lt;p&gt;This is a transcript post for a talk I gave internally at Igalia.

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0033.jpg&quot; alt=&quot;Web engine CI on a shoestring budget
delan azabani (she/her)
azabani.com
November 2025&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;Let&apos;s talk about how Servo can have fast CI for not a lot of money.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0034.jpg&quot; alt=&quot;Servo&apos;s situation
- Servo currently uses GitHub Actions (GHA) quite heavily
    - Many platforms: Linux, Windows, macOS, Android, and OpenHarmony
    - Many tests: Web Platform Tests (50K+ tests, 1.8M+ subtests), WebGPU CTS, devtools, unit tests…
    - Many configurations: MSRV, libservo, linting, release, benchmarking…
- GHA is a frustrating CI service with baffling limitations&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;Servo is a greenfield web browser engine. And being a web browser engine, it has some pretty demanding requirements for its CI setup.

&lt;p&gt;We have to build and run Servo for many platforms, including three desktop platforms and two mobile platforms.

&lt;p&gt;We have to run many, many tests, the main bulk of which is the entire Web Platform Tests suite, which is almost 2 million subtests. We also have several smaller test suites as well, like the WebGPU tests and the DevTools tests and so on.

&lt;p&gt;We have to build Servo in many different configurations for special needs. So we might build Servo with the oldest version of Rust that we still support, just to make sure that still works. We might build Servo as a library in the same way that it would be consumed by embedders. We have to lint the codebase. We have to build with optimizations for nightly and monthly releases. We have to build with other optimizations for benchmarking work, and so on.

&lt;p&gt;And as you&apos;ll see throughout this talk, we do this on GitHub and GitHub Actions, but GitHub Actions is a &lt;em&gt;very&lt;/em&gt; frustrating CI service, and it has many baffling limitations. And as time goes on, I think Servo being on GitHub and GitHub Actions will be more for the network effects we had early on than for any particular merits of these platforms.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0035.jpg&quot; alt=&quot;GitHub-hosted runners
- GitHub provides their own runners
- Essential for glue logic and small workloads
- Painful for building and testing a browser engine
    - The gratis runners are tiny and slow
    - The paid runners are very expensive
    - Need more tools or deps? Install them every time
    - Caching is a joke, not suitable for incremental builds&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;On GitHub Actions, GitHub provides their own first-party runners.

&lt;p&gt;And these runners are very useful for small workloads, as well as the logic that coordinates workloads. So this would include things like taking a tryjob request for &quot;linux&quot;, and turning that into a run that just builds Servo for Linux. Or you might get a try job request for &quot;full&quot;, and we&apos;ll turn that into a run that builds Servo for all of the platforms and runs all of the tests.

&lt;p&gt;But for a project of our scale, these runners really fall apart once you get into any workload beyond that.

&lt;p&gt;They have runners that are free of charge. They are very tiny, resource constrained, and it seems GitHub tries to cram as many of these runners onto each server as they possibly can.

&lt;p&gt;They have runners that you can pay for, but they&apos;re very very expensive. And I believe this is because you not only pay a premium for like hyperscaler cloud compute rates, but you also pay a premium on top of that for the convenience of having these runners where you can essentially just flip a switch and get faster builds. So they really make you pay for this.

&lt;p&gt;Not only that, but using GitHub hosted runners you can&apos;t really customize the image that runs on the runners besides being able to pull in containers, which is also kind of slow and not useful all the time. If there are tools and dependencies that you need that aren&apos;t in those images, you need to install them every single time you run a job or a workflow, which is such a huge waste of time, energy, and money, no matter whose money it is.

&lt;p&gt;There are also some caching features on GitHub Actions, but they&apos;re honestly kind of a joke. The caching performs really poorly, and there&apos;s not a lot you can cache with them. So in general, they&apos;re not really suitable for doing things like incremental builds.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0036.jpg&quot; alt=&quot;Alternatives considered
- SaaS providers: expensive, often no Win and/or macOS
- RunsOn: less expensive, AWS-only, no macOS support
- &amp;quot;Proxy&amp;quot; jobs: would compete for concurrent job limit
    - Tricky to do without losing access to GHA ecosystem
- Self-host a whole CI service
    - Built-in container orchestration, but virtual machines?
    - More ops burden: CI service now on the critical path&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;So we have all these slow builds, and they&apos;re getting slower, and we want to make them faster. So we considered several alternatives.

&lt;p&gt;The first that comes to mind are these third-party runner providers. These are things like Namespace, Warpbuild, Buildjet. There&apos;s so many of them. The caveat with these is that they&apos;re almost always... almost as expensive per hour as GitHub&apos;s first-party runners. And I think this is because they try to focus on providing features like better caching, that allow you to accumulate less hours on their runners. And in doing so, they don&apos;t really have any incentive to also compete on the hourly rate.

&lt;p&gt;There is one exception: there&apos;s a system called RunsOn. It&apos;s sort of an off-the-shelf thing that you can grab this software and operate it yourself, but you do have to use AWS. So it&apos;s very tightly coupled to AWS. And both of these alternatives, they often lack support for certain platforms on their runners. Many of them are missing Windows or missing macOS or missing both. And RunsOn is missing macOS support, and probably won&apos;t get macOS support for the foreseeable future.

&lt;p&gt;We considered offloading some of the work that these free GitHub hosted runners do onto our own servers with, let&apos;s call them like, &quot;proxy jobs&quot;. The idea would be that you&apos;d still use free GitHub hosted runners, but you&apos;d do the work remotely on another server. The trouble with this is that then you&apos;re still using these free GitHub hosted runners, which take up your quota of your maximum concurrent free runners.

&lt;p&gt;And it&apos;s also tricky to do this without losing access to the broader ecosystem of prebuilt Actions. So these Actions are like steps that you can pull in that will let you do useful things like managing artifacts and installing dependencies and so on. But it&apos;s one thing to say, okay, my workload is this shell script, and I&apos;m going to run it remotely now. That&apos;s easy enough to do, but it&apos;s a lot harder to say, okay, well, I&apos;ve got this workflow that has a bunch of scripts and a bunch of external Actions made by other people, and I&apos;m going to run all of this remotely. I&apos;m going to make sure that all of these Actions are also compatible with being run remotely. That&apos;s a lot trickier. That said, you should probably avoid relying too heavily on this ecosystem anyway. It&apos;s a great way to get locked into the platform, and working with YAML honestly really sucks.

&lt;p&gt;So we could set up an entire CI service. There are some CI services like Jenkins, Bamboo... honestly, most CI services nowadays have support for built-in container orchestration. So they can spin up containers for each runner, for each job, autonomously. And while they can do containers, none of them have really solved the problem of virtual machine orchestration out of the box. And this is a problem for us, because we want to use virtual machines for that security and peace of mind, which I&apos;ll explain in more detail a little bit later. And seeing as we lack the dedicated personnel to operate an entire CI service and have that be on the critical path — we didn&apos;t want to have someone on call for outages — this was probably not the best option for us at the moment.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0037.jpg&quot; alt=&quot;Self-hosted runners
- Self-hosted runners are better!
- Give the runners as much RAM and CPU as we want
- Custom build environments tailored to the project
    - Bake in whatever tools we want
    - Bake in a prebuilt Servo for incremental builds&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;What we decided to do was set up some self-hosted runners.

&lt;p&gt;These solve most of our problems, primarily because we can throw as much hardware and resources at these runners as we want. We can define the contention ratios.

&lt;p&gt;And better yet, we can also customize the images that the runners use. By being able to customize the runner images, we can move a lot of steps and a lot of work that used to be done on every single workflow run, and do it only once, or once per build. We only rebuild the runner images, say, once a week. So that significantly saves... it cuts down on the amount of work that we have to do.

&lt;p&gt;This is not just installing tools and dependencies, but it&apos;s also enabling incremental builds quite powerfully, which we&apos;ll see a little bit later on.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0038.jpg&quot; alt=&quot;How much faster?
- mach try full workflow: 61m30s → 25m47s (−58%)
- linux-unit-tests job: 34m29s → 3m15s (−90%)
- windows-unit-tests job: 59m14s → 8m4s (−86%)
- lint job: 11m54s → 2m25s (−79%)
- wpt jobs: 25m35s → 20m50s (−18%)
    - But we also went from 20 runners → 3 runners&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;How much faster is this system with the self-hosted runners? Well, it turns out, quite a lot faster.

&lt;p&gt;We&apos;ve got this typical workflow that you use when you&apos;re testing your commits, when you&apos;re making a pull request. And if you kick off a tryjob like this, several of the jobs that we&apos;ve now offloaded onto self-hosted runners are now taking 70, 80, even 90% less time than they did on GitHub hosted runners, which is excellent.

&lt;p&gt;And even for the web platform tests, we found more modest time savings with the capacity that we&apos;ve allocated to them. But as a result, even though the savings are a bit more modest, what&apos;s worth highlighting here is that we were able to go from twenty runners — we had to parallelise this test suite across twenty runners before — and now we only need three.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0039.jpg&quot; alt=&quot;What makes our system unique
- Augments the built-in CI service of the forge
- Almost transparent user experience
- Linux, Windows, and macOS runners
- Graceful fallback to GitHub-hosted runners
- Secure enough for a large public project
- Completely self-hosted, so it&apos;s dirt cheap^

^ operating expenses, not necessarily labour&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;Some things that make our system unique and that we&apos;re pretty proud of — and these things apply to all versions of our system, which we&apos;ve been developing over the last 12 to 18 months.

&lt;p&gt;The first is that we build on top of the native CI service of the Git forge. So in this case, it&apos;s GitHub and GitHub Actions. It could be Forgejo and Forgejo Actions in the future, and we&apos;re working on that already.

&lt;p&gt;We also want to give users more or less a transparent user experience, the idea being that users should not notice any changes in their day-to-day work, besides the fact that their builds have gotten faster. And I think for the most part, we have achieved that.

&lt;p&gt;Our system supports all of the platforms that GitHub Actions supports for their runners, including Linux, Windows, and macOS, and we could even support some of the other platforms that Forgejo Actions supports in the future, including BSD.

&lt;p&gt;We have the ability to set up a job so that it can try to use self-hosted runners if they&apos;re available, but fall back to GitHub hosted runners if there&apos;s none available for whatever reason, like maybe they&apos;re all busy for the foreseeable future, or the servers are down or something like that. We have the ability to fall back to GitHub hosted runners, and this is something that was quite complicated to build, actually. And we have a whole section of this talk explaining how that works, because this is something that&apos;s not actually possible with the feature set that GitHub provides in their CI system.

&lt;p&gt;It&apos;s secure enough for a large public project like Servo, where we don&apos;t necessarily know all of our contributors all that well personally. And this is in large part because we use virtual machines, instead of just containers, for each run and for each job. My understanding is that it is possible to build a system like this securely with containers, but in practice it&apos;s a lot harder to get that right as a security boundary than if you had the benefit of a hypervisor.

&lt;p&gt;And of course it is all completely self-hosted, which makes it about as cheap as it gets, because your operating expenses are really just the costs of bare metal compute.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0040.jpg&quot; alt=&quot;Completely self-hosted
so it&apos;s dirt cheap^
- We spend 312 EUR/month on general-purpose runners
- On comparable GitHub runners: 1421–2500 EUR/month
- On comparable third-party runners: 503–1077 EUR/month

^ operating expenses, not necessarily labour&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;Now, how cheap is that? Well, in our deployment in Servo, we spend about 300 EUR per month on servers that do general-purpose runners, and these handle most of our workload.

&lt;p&gt;If we were to compare that to if we were running on GitHub-hosted runners, there&apos;d be almost an order of magnitude increase in costs, somewhere like 1400 EUR to over 2000 EUR per month if we were doing the same work on GitHub-hosted runners.

&lt;p&gt;There are also significant increases if we went with third-party runner providers as well, although not quite as much.

&lt;p&gt;But in truth, this is actually kind of an unfair comparison, because it assumes that we would need the same amount of hours between if we were running on self-hosted runners or if we were running on GitHub hosted runners. And something that you&apos;ll see throughout this talk is that this is very much not the case. We spend so many fewer hours running our jobs, because we have to do so much less work on them. So in reality, the gap between our expenses with these two approaches would actually be a lot wider.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0041.jpg&quot; alt=&quot;Three ways to use runners
- Mandatory self-hosted: (with natural queueing)
    - runs-on: self-hosted-image:servo-ubuntu2204
- Graceful fallback to GitHub-hosted:
    - Decision job: POST https://monitor/select-runner
    - runs-on: ${{ needs.decision.outputs.label }}
- Graceful fallback plus queueing:
    - Decision job: POST https://queue/select-runner
    - runs-on: ${{ needs.decision.outputs.label }}&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;There are three ways to use our CI runner system.

&lt;p&gt;The first one is the way that GitHub designed self-hosted runners to be used. The way they intend you to use self-hosted runners is that when you define a job, you use this &lt;code&gt;runs-on&lt;/code&gt; setting to declare what kind of runners your job should run on. And you can use labels for GitHub hosted runners, or you can define arbitrary labels for your own runners and select those instead. But you have to choose one or the other in advance. And if you do that, you can&apos;t have any kind of fallback, which was a bit of a drawback for us, especially early on. One benefit of this, though, is that you get the natural ability to queue up jobs. Because if, at the time of queuing up a new job, if there&apos;s no self-hosted runners available yet, the job will stay in a queued state until a self-hosted runner becomes available. And it&apos;s nice that you essentially get that for free. &lt;em&gt;But&lt;/em&gt; you have no ability to have this kind of graceful fallback.

&lt;p&gt;So we built some features to allow you to do graceful fallback. And how this works is that each of the servers that operates these runners has a web API as well. And you can hit that web API to check if there are runners available and reserve them. Reserving them is something you have to do if you&apos;re doing graceful fallback. But I&apos;ll explain that in more detail a bit later on. And in doing so, because you have a job that can check if there are runners available, you can now parameterize the &lt;code&gt;runs-on&lt;/code&gt; setting and decide &quot;I want to use a self-hosted runner, or a GitHub hosted runner&quot;. It&apos;s unclear if this is going to be possible on Forgejo Actions yet, so we may have to add that feature, but it&apos;s certainly possible on GitHub Actions.

&lt;p&gt;Now, the downside of this is that you do lose the ability, that natural ability, to queue up jobs, and I&apos;ll explain why that is a bit later. But in short, we have a queue API that mitigates this problem, because you can hit the queue API, and it can have a full view of the runner capacity, and either tell you to wait, or forward your request once capacity becomes available.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0042.jpg&quot; alt=&quot;Faster checkouts
- No repo checkout unless you run actions/checkout
    - But servo/servo has 130K+ files and 6K+ directories
    - This is especially slow on Windows and macOS
- Bake a repo checkout into every runner image
- Replace actions/checkout with our own action:
    git fetch --depth=1 origin $commit
    git switch --detach
    git reset --hard FETCH_HEAD&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;Some things that you can do with our system, that you can&apos;t do with GitHub hosted runners. One of them is check out the repo significantly faster.

&lt;p&gt;Something about GitHub Actions and how it works is that, if you run a job, you run a workflow in a repo, you don&apos;t actually get a checkout of the repo. You don&apos;t get a clone of the repo out of the box, unless you explicitly add a step that does a checkout. And this is fine for the most part, it works well enough for most users and most repos.

&lt;p&gt;But the Servo repo has over 130,000 files across 6,000 directories, and that&apos;s just the &lt;em&gt;tracked&lt;/em&gt; files and directories. And as a result, even if we use things like shallow clones, there&apos;s just kind of no getting around the fact that cloning this repo and checking it out is just &lt;em&gt;slow&lt;/em&gt;. It&apos;s unavoidably slow.

&lt;p&gt;And it&apos;s &lt;em&gt;especially&lt;/em&gt; slow on Windows and macOS, where the filesystem performance is honestly often pretty poor compared to Linux. So we want to make our checkouts faster.

&lt;p&gt;Well, what we can do is, we can actually move the process of cloning and checking out the repo from the build process, and move that into the image build process. So we only do it once, when we&apos;re building the runner images.

&lt;p&gt;Then what we can do is go into the jobs that run on self-hosted runners, and switch out the stock checkout action with our own action. And our own action will just use the existing clone of the repo, it will fetch the commit that we actually need to build on, then switch to it, and check it out.

&lt;p&gt;And as a result, we can check out the Servo repo pretty reliably in a couple of seconds, instead of having to check out the entire repo from scratch.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0043.jpg&quot; alt=&quot;Incremental builds
- Cargo supports incremental builds
- We&apos;re now baking a repo checkout into every image
- Why not build Servo and bake that in too?
- Not perfect — some compilation units get false rebuilds
- Probably don&apos;t use this for release artifacts&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;Something that flows on from that, though, is that if we are baking a copy of the Servo repo into our runner images, well, what if we just bake a copy of the built artifacts as well? Like, why don&apos;t we just build Servo, and bake that into the image?

&lt;p&gt;And this will allow us to do incremental builds, because Servo is a Rust project, and we use Cargo, and Cargo supports incremental builds. As a result, by doing this, when you run a job on our CI system, most of the time we only have to rebuild a handful of crates that have changed, and not have to rebuild all of Servo from scratch.

&lt;p&gt;Now, this is not perfect. Sometimes we&apos;ll have some compilation units that get falsely rebuilt, but this works well enough, for the most part, that it&apos;s actually a significant time savings.

&lt;p&gt;I also probably wouldn&apos;t trust this for building artifacts that you actually want to release in a finished product, just because of the kinds of bugs that we&apos;ve seen in Cargo&apos;s incremental build support.

&lt;p&gt;But for everything else, just your typical builds where you do like commit checks and such, this is very very helpful.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0044.jpg&quot; alt=&quot;Servo&apos;s deployment
- Five servers on Hetzner^
    - 3x AX102 (Zen 4 16c/32t, 128G RAM) = 312 EUR/month
    - 2x AX42 (Zen 3 8c/16t, 64G RAM) = 92 EUR/month
- NixOS + libvirt + KVM + ZFS
- Custom orchestration

^ not including OpenHarmony runners&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;Servo has this system deployed, and it&apos;s had it deployed for at least the last year or so.

&lt;p&gt;Nowadays we have three servers which are modestly large, and we use these for the vast majority of our workload. We also have two smaller servers that we use for specific benchmarking tasks.

&lt;p&gt;The stack on these servers, if you could call it that, is largely things that I personally was very familiar with, because I built this. So we&apos;ve got NixOS for config management, the hypervisor is libvirt and Linux KVM, and the storage is backed by ZFS. The process of actually building the images, and managing the lifecycle of the virtual machines though, is done with some custom orchestration tooling that we&apos;ve written.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0045.jpg&quot; alt=&quot;How does it work?
- Monitor service orchestrates the runners
    - Rebuilds virtual machine images
    - Spawns virtual machines for runners
    - Registers runners with CI service API
    - Labels runners when asked to reserve them
      (optional, but required for graceful fallback)
- Queue service allows queueing with fallback (optional)&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;This tooling consists of two services. The monitor service, which runs on every server that operates self-hosted runners, and the queue service, which is single and global.

&lt;p&gt;So the monitor service does the vast majority of the work. It rebuilds virtual machine images, these templates. It clones the templates into virtual machines for each runner, for each job. It registers the runners with the CI service using its API, so that it can receive jobs. And it can also label the runners to tie them to specific jobs when asked to reserve them. This is optional, but it is a required step if we&apos;re doing graceful fallback.

&lt;p&gt;Now, with graceful fallback, you do lose the ability to naturally queue up jobs. So what we&apos;ve put on top of that is a single queue service that sits in front of the cluster, and it essentially acts as a reverse proxy. It&apos;s quite thin and simple, and there&apos;s a single one of them, not one per server, for the same reason as the general principle of, like, when you go to a supermarket, it&apos;s more efficient to have a single large queue, a single long queue of customers that gets dispatched to a bunch of shopkeepers. That&apos;s more efficient than having a sort of per-shopkeeper queue, especially when it comes to, like, moving jobs dynamically in response to the availability.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0046.jpg&quot; alt=&quot;Graceful fallback&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;So we&apos;ve got a whole section here about how graceful fallback works. I might cut this from shorter versions of the talk, but let&apos;s jump into it.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0047.jpg&quot; alt=&quot;Graceful fallback
- Every job has to choose a runner label in advance
    runs-on: ubuntu-latest     # GitHub-hosted
    runs-on: self-hosted-image:servo-ubuntu2204
- Once you choose the runner label, there&apos;s no turning back
- Borrowing a built-in label does not prioritise self-hosted runners over GitHub-hosted runners
- So there&apos;s no way to fall back… or is there?&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;On GitHub Actions, every job has this &lt;code&gt;runs-on&lt;/code&gt; setting, that defines what kind of runner it needs to get assigned to. It has to define this in advance, before the job runs.

&lt;p&gt;And annoyingly, once you choose, &quot;I want to run on a GitHub hosted runner&quot; or &quot;I want to run on a self-hosted runner&quot;, once you decide that, there&apos;s no turning back. Now, if you&apos;ve decided that my job needs to run on a self-hosted runner, it can &lt;em&gt;only&lt;/em&gt; run on a self-hosted runner. And now the outcome of your job, and the outcome of your workflow, now depends on that job actually &lt;em&gt;getting&lt;/em&gt; a self-hosted runner with the matching criteria and succeeding. There&apos;s no way for that to become optional anymore.

&lt;p&gt;And you might say, okay, well, maybe I can work around this by spinning up my self-hosted runners, but just giving them the same labels that GitHub assigns to their runners, maybe it&apos;ll do something smart? Like maybe... it will run my self-hosted runners if they&apos;re available and fall back. But no, the system has no sense of priority and you can&apos;t even define any sense of priority of like, I want to try this kind of runner and fall back to another kind of runner. This is simply not possible with the GitHub Action system.

&lt;p&gt;So it may seem like it&apos;s impossible to do graceful fallback, but we found a way.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0048.jpg&quot; alt=&quot;Decision jobs
- Prepend a job that chooses a runner label
    1.  let label = runner available?
           | yes =&gt; [self-hosted label]
           | no  =&gt; [GitHub-hosted label]
    2.  $ echo &amp;quot;label=${label}&amp;quot; | tee -a $GITHUB_OUTPUT
- Use the step output in runs-on
    runs-on: ${{ needs.decision.outputs.label }}
- But two decisions must not be made concurrently&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;What we can do is add a decision job, for each workload job that may need to run on self-hosted runners. And we prepend this job, and its job is to choose a runner label.

&lt;p&gt;So how it essentially works is: it checks if a runner is available, and based on that, either chooses a self-hosted label or a GitHub hosted label. And it chooses it and sets it in an output. And this output can get pulled in... in our workload job, the job that actually does our work, because now you can parameterize the &lt;code&gt;runs-on&lt;/code&gt; setting, so that it takes the value from this previous decision job.

&lt;p&gt;Unfortunately, it seems like this may not be possible in Forgejo Actions just yet, so we might have to develop support for that, but it&apos;s certainly possible in GitHub Actions today, and it has been for quite a while.

&lt;p&gt;The one caveat with this approach is that you need to be careful to only do this decision process one at a time. You should not do two instances of this process concurrently and interleave them.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0049.jpg&quot; alt=&quot;Decisions must be serialised
- Stack Overflow and GitHub answers are inherently racy:
    any idle runners? —(TOCTOU)→ commit to self-hosted
- We can reserve a runner by applying a unique label to it!
    - GH API: add custom label: reserved-for:&lt;UUIDv4&gt;
    -   runs-on: ${{ needs.decision.outputs.label }}
        runs-on: 6826776b-5c18-4ef5-8129-4644a698ae59
- Initially do this directly in the decision job (servo#33081)&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;The reason for this is something you&apos;ll see if you think about a lot of the answers that you get on Stack Overflow and the GitHub forums. If you look up, &quot;how do I solve this problem? How do I define a job where I can fall back from self-hosted runners to GitHub hosted runners?&quot;

&lt;p&gt;And most of the answers there, they&apos;ll have a problem where they check if there are any runners that are available, &lt;em&gt;and then&lt;/em&gt;, they will make the decision, committing to either a self-hosted runner or a GitHub hosted runner. The trouble is that in between, if another decision job comes in and tries to make the same kind of decision, they can end up &quot;earmarking&quot; the same runner for two jobs. But each runner is only meant to run one job, and it &lt;em&gt;can&lt;/em&gt; only run one job, so one of the jobs will get left without a runner.

&lt;p&gt;So we can start to fix this by actually reserving the runners when we&apos;re doing graceful fallback. And how we&apos;ve done it so far, is that we&apos;ve used the GitHub Actions API to label the runner when we want to reserve it, and we label it with a unique ID. Then the workload job can put that unique ID, that label, in its &lt;code&gt;runs-on&lt;/code&gt; setting. Instead of a general runner label, it can tie itself to this specific, uniquely identified runner label.

&lt;p&gt;And we did it this way initially, because it allowed us to do it inside the decision job, at first. I think in the future, we will have to move away from this, because on Forgejo Actions, the way runner labels work is quite different. They&apos;re not something that you can sort of update after the fact. In fact, they&apos;re actually kind of defined by the runner process. So this approach for reserving runners won&apos;t work on Forgejo Actions. We&apos;ll probably have to do that internally on the runner servers. But in the meantime, we use labeling. Yeah, so at first we did this inside the decision job.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0050.jpg&quot; alt=&quot;Decisions must be serialised
- Labelling runners requires a privileged GitHub API token
- Even with reservation, decisions must still be serialised:
    runner not yet reserved? —(TOCTOU)→ label the runner
    - But hey, at least we have job concurrency… right?
    - Wrong: runs will fail under even modest contention :(&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;There are some problems with this. One of them is that now the decision job has to have a GitHub token that has permissions to manage runners and update their labels. And this is something that we&apos;d like to avoid if possible, because we&apos;d like our jobs to have least privilege that they need to do their work.

&lt;p&gt;And something I didn&apos;t mention is that reserving runners in this way doesn&apos;t actually solve the problem on its own, because you&apos;ve now transformed the problem to being, okay, we&apos;re going to check if the runner is not yet reserved. We&apos;re going to check if there&apos;s an unreserved runner, &lt;em&gt;and then&lt;/em&gt;, we&apos;re going to label the runner. But in between, there&apos;s a moment where another process doing the same thing could make the same decision. And as a result, if we just did this, we could end up with a situation where one runner gets assigned two unique labels, but it can only fulfill one of them. So we have that same problem again.

&lt;p&gt;So you might say, okay, well, it looks like GitHub Actions has this neat job concurrency feature. I mean, they say you can use this to define a job in a way where only one of them will run at a time, and you can&apos;t run them concurrently, so let&apos;s try using that to solve this problem.

&lt;p&gt;What you&apos;ll find, though, is that if you try to solve the problem with job concurrency, as soon as there&apos;s even the slightest bit of contention, you&apos;ll just have your runs starting to fail spuriously, and you&apos;ll have to keep rerunning your jobs, and it&apos;ll be so much more annoying.

&lt;p&gt;And the reason for this is that, if you look more closely at the docs, job concurrency essentially has a queue limited to one job. So at a maximum, you can have one job, one instance of the job that&apos;s running, one instance of the job that&apos;s queued, and then if another instance of the job comes in while you have one running and one queued, then those extra jobs just get cancelled, and the build fails. So unfortunately, job concurrency does not solve this problem.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0051.jpg&quot; alt=&quot;Decisions must be serialised
- So move decisions out of the decision jobs (servo#33315)
- But what happens if reserved runners fail to materialise?
- You can limit in_progress time in GHA, but not queued time&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;So to solve these problems, what we did is we moved that decision and runner reservation process out of the decision jobs, and into the servers that operate the runners themselves. And we do this with an API that runs on the servers.

&lt;p&gt;One smaller problem you might notice though, is that there&apos;s still a possibility that you could reserve a runner, but then after you&apos;ve reserved the runner, it might fail to actually run. And this has become a lot less likely in our system, in our experience nowadays, now that we&apos;ve ironed out most of the bugs, but it can still happen from time to time, usually due to infrastructure or network connectivity failures.

&lt;p&gt;And we wanted to solve this by setting a time limit on how long a job can be &lt;code&gt;queued&lt;/code&gt; for, because if it can&apos;t actually get a runner in practice, it will get stuck in that &lt;code&gt;queued&lt;/code&gt; state indefinitely. But unfortunately, while we can set a time limit on how long a job can &lt;em&gt;run for&lt;/em&gt; once it&apos;s been assigned, we can&apos;t actually set a time limit on how long a job can be &lt;code&gt;queued&lt;/code&gt; for.

&lt;p&gt;So we have to rely on the default built-in limit for all jobs, where I think there&apos;s like a limit of a day or two? So like, 24 or 48 hours or so, for how long a job can be &lt;code&gt;queued&lt;/code&gt;? And this is just a really long time. So as a result, whenever this happens, you essentially have to cancel the job manually, or if you don&apos;t have permission to do that, you have to go &lt;em&gt;ask&lt;/em&gt; someone who &lt;em&gt;has&lt;/em&gt; permission, to go and cancel your job for you, which is really annoying.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0052.jpg&quot; alt=&quot;Timeout jobs
- Watchdog for your workload job, ensuring it gets a runner
    1.  Wait a short amount of time (e.g. 120 seconds)
    2.  Query the CI service API for the workload job
    3.  If the job is still queued, cancel the run
- Only run this when you actually use a self-hosted runner:
    if: ${{ fromJSON(needs.decision.outputs.is-self-hosted) }}&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;So we solved that using timeout jobs. A timeout job is a sibling to your workload job, and it acts like a watchdog, and it ensures that it actually got a runner when you expect to get a self-hosted runner.

&lt;p&gt;And how that works is, we wait a short amount of time, just like a minute or two, which should be long enough for the runner to actually start running the job, and then we query the API of the CI service to check if the workload job actually started running, or if it&apos;s still in that &lt;code&gt;queued&lt;/code&gt; state. If it&apos;s still queued after two minutes, we cancel the run.

&lt;p&gt;Unfortunately, we can&apos;t just cancel the job run. We do have to cancel the whole workflow run, which is quite annoying. But, you know, it&apos;s GitHub, nothing is surprising anymore.

&lt;p&gt;Thankfully, we only have to run this when we actually get a self-hosted runner, so we can make it conditional. But over time, in Servo&apos;s deployment, we have actually stopped using these, to free up some of those GitHub-hosted runner resources.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0053.jpg&quot; alt=&quot;Uniquely identifying jobs
- How do we know the run id of the workload job?
    - Jobs can be instantiated many times via workflow calls
- The only supported job relationship is needs
    - Workload job needs decision job
    - Timeout job needs decision job
    - Timeout job can&apos;t needs workload job
- needs relationships are not exposed in the API&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;One challenge that comes in making these timeout jobs is identifying the workload jobs uniquely, so we can look up whether it&apos;s still &lt;code&gt;queued&lt;/code&gt; or whether it&apos;s started running. There are unique IDs for each job run. These are just like an incrementing number, and you&apos;d think we&apos;d be able to use this number to look up the workload job uniquely and robustly.

&lt;p&gt;Unfortunately, you can&apos;t know the run ID of the job [correction 2025-12-24] &lt;del&gt;until it starts, and it may not ever start... or at least you may not know it&lt;/del&gt; until the workflow runs, and there can be many instances of the job in the workflow because of workflow calls. Workflow calls are a feature that essentially allows you to inline the contents of a workflow in another as many times as you like. And as a result, you can have multiple copies, multiple instances of a job that run independently within one workflow run. So we definitely need a way to uniquely look up our instance of the workload job.

&lt;p&gt;The trouble is that the only job relationship you can do in GitHub Actions is a &lt;code&gt;needs&lt;/code&gt; relationship, and that&apos;s inappropriate for our situation here, because we can say that the workload job &lt;code&gt;needs&lt;/code&gt; the decision job, we can say the timeout job &lt;code&gt;needs&lt;/code&gt; the decision job — and in fact we do both of these, we &quot;need&quot; to do both of these — but we can&apos;t say that the timeout job &lt;code&gt;needs&lt;/code&gt; the workload job, because of how &lt;code&gt;needs&lt;/code&gt; works.

&lt;p&gt;How &lt;code&gt;needs&lt;/code&gt; works is that if job A &lt;code&gt;needs&lt;/code&gt; job B, then job B has to actually get assigned a runner, and run, and complete its run — it has to finish — before job A can even start. And in this situation, we&apos;re making a timeout job to catch situations where the workload job never ends up running, so if we expressed a &lt;code&gt;needs&lt;/code&gt; relationship between them, then the timeout job would never run, in these cases at least.

&lt;p&gt;And even if we could express a &lt;code&gt;needs&lt;/code&gt; relationship between jobs, like maybe we could walk the job tree, and go from the timeout job, through the decision job via the &lt;code&gt;needs&lt;/code&gt; relationship, and then walk back down to the workload job using the same kind of &lt;code&gt;needs&lt;/code&gt; relationship... unfortunately, none of these &lt;code&gt;needs&lt;/code&gt; relationships are actually exposed in the API for a running workflow. So like, they&apos;re used for scheduling, but when you actually go and query the API, you can&apos;t tell what job &lt;code&gt;needs&lt;/code&gt; what other job. They&apos;re just three jobs, and they&apos;re unrelated to one another.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0054.jpg&quot; alt=&quot;Uniquely identifying jobs
- Tie them together by putting the &lt;UUIDv4&gt; in the name:
    name: Linux [${{ needs.decision.outputs.unique-id }}]
    name: Linux [6826776b-5c18-4ef5-8129-4644a698ae59]
- Query the CI service API for all jobs in the workflow run
- Check the status of the job whose name contains
    [${{ needs.decision.outputs.unique-id }}]
- Yes, really, we have to string-match the name :)))&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;So how we had to end up solving this is, we had to tie these jobs together by generating a unique ID, a UUID, and putting it in the friendly name, like the display name of the job, like this.

&lt;p&gt;And to query the CI service to find out if that job is still queued, we need to query it for the whole workflow run, and just look at all of the jobs, and then find the job whose name contains that unique ID.

&lt;p&gt;Then we can check the &lt;code&gt;status&lt;/code&gt;, and see if it&apos;s still &lt;code&gt;queued&lt;/code&gt;. This is really, really silly. Yes, we really do have to string match the name, which is bananas! But this is GitHub Actions, so this is what we have to do.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0055.jpg&quot; alt=&quot;Tokenless API
- Monitor API requires access to secrets in the workflow
    - All pull_request_target runs have access to secrets
        - …but you generally don&apos;t want to use it anyway
    - Most pull_request runs do not have access to secrets
- How do we prove the request is genuine and authorised, if we can&apos;t authenticate with a token?&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;One thing I didn&apos;t mention is that being able to reserve runners needs to be kind of a privileged operation, because we don&apos;t just want an arbitrary client on the internet to be able to erroneously or maliciously reserve runners. Even if they may not be able to do a whole lot with those runners, they can still deny service.

&lt;p&gt;So to use the monitor API to do graceful fallback and to request and reserve a runner, normally we would require knowledge of some kind of shared secret, like an API token, and that&apos;s what we&apos;ve done for most of the life of this system.

&lt;p&gt;The trouble with this is that there are many kinds of workflow runs that don&apos;t have access to the secrets defined in the repo. A big one is &lt;code&gt;pull_request&lt;/code&gt; runs. Most of the time, &lt;code&gt;pull_request&lt;/code&gt; runs don&apos;t have access to secrets defined in the repo. And there is another kind of run called a &lt;code&gt;pull_request_target&lt;/code&gt; run, and those do have access to secrets, but they also have some pretty gnarly security implications that mean that in general, you wanna avoid using these for pull requests anyway.

&lt;p&gt;So if you&apos;re stuck with &lt;code&gt;pull_request&lt;/code&gt; runs for your pull requests, does that mean that you can&apos;t use self-hosted runners? How do we allow &lt;code&gt;pull_request&lt;/code&gt; runs to request and reserve runners in a way that it can prove that its request is genuine and authorized?
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0056.jpg&quot; alt=&quot;Tokenless API
- Upload an artifact representing the request!
- Hit the monitor API
    - /select-runner ?unique_id &amp;qualified_repo &amp;run_id
    - (the profile_key is in the artifact)
- Important: delete the artifact, so it can&apos;t be reused (and set the minimum auto-delete, in case that fails)&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;What we do is we use artifacts. We upload a small artifact that encodes the details of the request and publish the artifact against the run. So in the artifact, we&apos;d say, &quot;I want two Ubuntu runners&quot; or &quot;one Windows runner&quot; or something like that.

&lt;p&gt;And then we would hit the monitor API, we hit a different endpoint that just says, go to this repo, go to this run ID, and then check the artifacts! You&apos;ll see my request there! And this does not require an API token, it&apos;s not a privileged operation. What&apos;s privileged is publishing the artifact, and that&apos;s unforgeable; the only entity who can publish the artifact is the workflow itself.

&lt;p&gt;All we have to do then, all we have to be careful to do, is to delete the artifact after we reserve the runner, so that it can&apos;t be replayed by a malicious client. And in the event that deleting the artifact fails, we can also set the minimum auto-delete period of, I think at the moment it&apos;s 24 hours, just in case that fails.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0057.jpg&quot; alt=&quot;Global queue
- Fallback happens immediately if no runners are available
- But if GitHub-hosted runs take 5x as long as self-hosted, we can wait up to 80% of that time and still win
- Run a queue service that allows jobs to wait for capacity
- Decision jobs hit the queue API instead of the monitor API
- Queue API says &amp;quot;wait&amp;quot; with HTTP 503 + &apos;Retry-After&apos;
- Queue API then proxies the reservation to a monitor API &quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;Graceful fallback normally means that you lose the ability to naturally queue jobs for self-hosted runners. And this happens because when you hit the API, requesting, you know, are there any available runners, please reserve one for me. If there aren&apos;t any available at the time of the request, we will immediately fall back to running on a GitHub hosted runner.

&lt;p&gt;But the thing is, is that our self-hosted runners are generally so much faster! A GitHub hosted run might take five times as long as the equivalent self-hosted run, and if that was the case, it would actually be beneficial to wait up to 80% of that time, and we&apos;d still probably save time.

&lt;p&gt;So to increase the utilization of our self-hosted runners, what we can do is run a small queue service that sits in front of the monitors, and it essentially acts as a reverse proxy. It will take the same kind of requests for reserving runners as before, but it will have a global view of the availability and the runner capacity at any given moment.

&lt;p&gt;And based on that, it will either respond to the client saying, go away and please try again later, or it will forward the request onto one of the monitors based on the available capacity.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0058.jpg&quot; alt=&quot;Runner images&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;We also have some lessons here about how to automate building virtual machine images, essentially. And these are lessons that we&apos;ve learned over the last year or two.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0059.jpg&quot; alt=&quot;Runner images
- GitHub uses Packer for their stock runner images
- Our monitor service manages image rebuilds
- Initially kicked off manually, now fully automated (#6)
    - Driven by Rust with reflink copies (#32)
- Mounting images to inject data is no longer viable (#30)
    - macOS has no usable FS with Linux write support
    - Get tools and deps from the monitor&apos;s web server (#32)&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;GitHub Actions uses Packer for their first-party runner images, so they use Packer to build those images. And our monitor service also automates building the images, but we don&apos;t use Packer.

&lt;p&gt;Initially, we had a handful of scripts that we just kicked off manually whenever we needed to update our images, but we&apos;ve now fully automated the process. And we&apos;ve done this using some modules in our monitor service, so there&apos;s some high-level Rust code that drives these image rebuilds, and it even uses reflink copies to take advantage of copy-on-write time savings and space savings with ZFS.

&lt;p&gt;Now, one of the complications we ran into when building images, over the past year or so, is that we used to pull a sort of clever trick where, we would do as much of the process of configuring the virtual machine image on the host, actually, as possible, rather than doing configuration inside the guest, and having to spin up the guest so that we can configure it. And we were able to do this by essentially mounting the root file system of the guest image, on the host, and then injecting files, like injecting tools and scripts and other things that are needed by the image and needed to configure the image.

&lt;p&gt;But we stopped being able to do this eventually because, well, essentially because of macOS. macOS has no file system that&apos;s usable for building Servo that can also be mounted on Linux with write support. Because like, just think about them, right? We&apos;ve got HFS+, which Linux can write to but only if there&apos;s no journaling and you can&apos;t install macOS on HFS+ without journaling. There&apos;s APFS, which Linux has no support for. There&apos;s exFAT, which has no support for symlinks, so a lot of tools like uv will break. There&apos;s NTFS, which we thought would be our savior, but when we tried to use it, we ran into all sorts of weird build failures, which we believe are due to some kind of filesystem syncing race condition or something like that, so NTFS was unusable as well.

&lt;p&gt;In the end, what we had to do is, if a guest image needed any tools or dependencies, it had to download them from a web server on the host.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0060.jpg&quot; alt=&quot;Runner images
- Consistent approach to automating operating systems
    - OS installation: whatever is easiest
    - Config bootstrap: native config management (if any)
        - Use it as little as possible
    - Image config: native scripting
    - Runner boot: native scripting&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;We eventually settled on a consistent approach to automating the process of installing the OSes and configuring them for each runner template.

&lt;p&gt;And the approach that we used was to install the OS using whatever method is easiest, and to bootstrap the config using any native config management system, if there is one included with the operating system.

&lt;p&gt;But once we&apos;ve kicked off that process, we then use the native config management as little as possible. And we do this because a lot of the config management tools that are built into these operating systems are quite quirky and annoying, and they are built for needs that we don&apos;t have, the primary need being that they can manage the configuration of a system over time, keeping it up to date with any changes. The thing about these runner images, though, is that each runner image only needs to get built once, it only needs to get configured once, and then after after that it&apos;ll be cloned for each runner, and then it will only run once, and this kind of sort of &quot;one shot&quot; use case is something that&apos;s kind of overkill with config management systems.

&lt;p&gt;We can just do it with the usual scripting and automation facilities of the operating system. How does that look like in practice?
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0061.jpg&quot; alt=&quot;Linux runners
- OS installation: prebuilt Ubuntu cloud images
- Config bootstrap: cloud-init config
    - Use it as little as possible (systemd journal to tty7, netplan config, curl and run next stage)
- Image config: bash script
- Runner boot: same bash script&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;Well for Linux, we install the OS using pre-built Ubuntu cloud images, that we just download from the mirrors.

&lt;p&gt;We bootstrap the config using cloud-init, which is such a painful... it&apos;s so painful to use cloud-init. We use it because it&apos;s included with the operating system, so that means it&apos;s the fastest possible way to get started.

&lt;p&gt;We use it as little as possible: we just configure the logs to go to a TTY, we configure the network, so we can connect to the network, and then once we&apos;re on the network, we just curl and run a bash script which does the rest.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0062.jpg&quot; alt=&quot;Windows runners
- OS installation: autounattend.xml (generator)
- Config bootstrap: same autounattend.xml
    - Use it as little as possible
    - Create elevated scheduled task to curl and run next stage
    - Install NetKVM driver, do some reg imports, reboot
- Image config: PowerShell script
- Runner boot: same PowerShell script&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;The same goes for Windows.

&lt;p&gt;We install the operating system using an automated answers file called autounattend.xml. There&apos;s a &lt;a href=&quot;https://schneegans.de/windows/unattend-generator/&quot;&gt;nice little generator here&lt;/a&gt; which you can use if you don&apos;t want to have to set up a whole Windows server to set up unattended installations. You generate that XML file, and you can also use that XML file to bootstrap the config.

&lt;p&gt;Again we use it as little as possible, because writing automations as XML elements is kind of a pain. So we essentially just set up a scheduled task to run the next stage of the config, we install the network driver, we import some registry settings, and we reboot. That&apos;s it. The rest of it is done with a PowerShell script.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0063.jpg&quot; alt=&quot;macOS runners
- OS installation: by hand :( but only once
- Config bootstrap: curl|sh by hand :( but only once
    - Use it as little as possible (zsh script)
    - Enable SSH, enable autologin, enable sudo NOPASSWD
    - Install a LaunchAgent to curl and run next stage
    - Disable broken session restore feature in Terminal.app
- Image config and runner boot: zsh script&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;The same goes for macOS.

&lt;p&gt;Now, unfortunately, installing the OS and bootstrapping the config does have to be done by hand. And this is because if you want to automate a macOS installation, your two options, more or less, are enterprise device management solutions, which cost a lot of money and mean that you have to have a macOS server around to control and orchestrate the servers. But if you don&apos;t want to use one of those enterprise solutions, what most open systems that are faced with this problem end up doing is to throw OpenCV at the problem. I&apos;ve seen several projects use OpenCV to OCR the setup wizard, which is... it&apos;s certainly a bold strategy. It&apos;s not really for me.

&lt;p&gt;What I decided to do instead is just install the OS by hand, and pipe &lt;code&gt;curl&lt;/code&gt; into &lt;code&gt;sh&lt;/code&gt; to kick off the config management process. And this is something that we only really have to do once, because we do it once, and then we take a snapshot of it, and then we never have to do it again, at least until the next version of macOS comes out.

&lt;p&gt;So this bootstrap script just does a handful of minimal things: it enables automatic login, it sets up a LaunchAgent to ensure that we can run our own code on each boot, and then it does a handful of other things which it honestly doesn&apos;t really have to do in this script. We could probably do these things in the &lt;code&gt;zsh&lt;/code&gt; script which we then &lt;code&gt;curl&lt;/code&gt; and run. And that&apos;s where the remainder of the work is done.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0064.jpg&quot; alt=&quot;Future directions
- Decoupling the system from Servo
- macOS arm64 runners (#64)
- Support for Forgejo Actions (#94)
- Support for other CI services?
- Dynamic runner counts / autoscaling
- Hot runners with memory ballooning
- microVM runners?&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;So looking forward, some things that we&apos;d like to do with this system.

&lt;p&gt;The first is to decouple it from Servo. So we built this CI system quite organically over the past 12 to 18 months, and we built it around Servo&apos;s needs specifically, but we think this system could be more broadly useful for other projects. We&apos;ll just have to abstract away some of the Servo specific bits, so that it can be more easily used on other projects, and that&apos;s something we&apos;re looking into now.

&lt;p&gt;Something else that we&apos;ll have to do sooner or later is add support for macOS runners on Apple Silicon, that is ARM64, and the reason we have to do this is that macOS 26, which is the most recent version of macOS that came out in September, that&apos;s just a couple months ago, that is the last version of macOS that will support x86 CPUs. And at the moment, our macOS runners run on x86 CPUs, on the host and in the guest.

&lt;p&gt;This is a little bit complicated because at the moment, our macOS runners actually run on Linux hosts, using a Linux-KVM-based sort of Hackintosh-y kind of solution. And there is no counterpart for this for arm64 hosts and guests, and I&apos;m not sure there ever will be one. So we&apos;re going to have to port the system so that it can run on macOS hosts, so we can use actual Mac hardware for this, which is easy enough to do, and that&apos;s in progress.

&lt;p&gt;But we&apos;re also going to have to port the system so it can run with other hypervisors. And this is because, although libvirt supports macOS hosts, the support for the macOS Hypervisor framework and Virtualization framework is not mature enough to actually run macOS guests in libvirt. And I&apos;m not sure how long that will take to develop, so in the meantime, we&apos;ve been looking at porting the system so that when you&apos;re on a Mac, you can run with UTM instead, and that&apos;s been working fairly well so far.

&lt;p&gt;We&apos;re also looking at porting the system so that it can run with Forgejo Actions and not just GitHub Actions. So Forgejo Actions is an open alternative to GitHub Actions that tries to be loosely compatible with GitHub Actions, and in our experience, from experimentation so far, we found that it mostly &lt;em&gt;is&lt;/em&gt; loosely compatible. We think we&apos;ll only have to make some fairly minor changes to our system to make it work on both CI systems.

&lt;p&gt;That said, this CI system could potentially be more broadly applicable to other CI services as well, because virtual machine orchestration is something that we haven&apos;t really seen any CI services have a great built-in solution for. So if this interests you and you want to use it on your project on some other CI service, then we&apos;d appreciate knowing about that, because that could be something we would explore next.

&lt;p&gt;The remaining ideas are things that I think we could look into to make our runners more efficient.

&lt;p&gt;The big one is autoscaling. So at the moment when you set up a server to operate some self-hosted runners, you essentially have to statically pre-configure how many runners of each kind of runner you want to be kept operating. And this has worked well enough for us for the most part, but it does mean that there&apos;s some kind of wasted resources sometimes, when the moment-to-moment needs of the jobs that are queued up aren&apos;t well fitted to the composition of your runner configuration. So if we had the ability to dynamically respond to demand, or some kind of autoscaling, I think we could improve our runner utilization rates a little bit, and sort of get more out of the same amount of runner capacity, the same amount of server capacity.

&lt;p&gt;There&apos;s a couple ideas here, also, about reducing boot times for the runners, which can be quite helpful if you have a big backlog of jobs queued up for these servers, and this is because time spent booting up each runner, each virtual machine, is time that cannot be spent doing real work.

&lt;p&gt;So two ways we can think of to reduce these boot times are, to have hot spares ready to go, the idea being that, if you can spin up more runners than you actually intend to run concurrently, and just have them sitting around, then you can kind of amortize the boot times, and sort of get the boot process process out of the way. And the way you do this is by spinning up a whole bunch of runners, say maybe you spin up like twenty runners, even though you only intend to run four of them concurrently.

&lt;p&gt;And what you do is you give these runners a token amount of RAM to start with. You give them like one gig of RAM instead of 16 or 32 gigs of RAM. And then when a job comes in, and you actually want to assign the runner out so that it can do the work, then you dynamically increase the RAM from one gig, or that token amount, to the actual amount, like 16 gigs or 32 gigs. And this should be fairly easy to do in practice. This is actually supported in libvirt using a feature known as memory ballooning. But there are some minor caveats, like you do lose the ability to do certain optimizations, like you can&apos;t do huge pages backing on the memory anymore. But for the most part, this should be fairly technically simple to implement.

&lt;p&gt;Something that could be more interesting in the longer term is microVMs, things like Firecracker, which as I understand it, these microVMs can sort of take the concept of paravirtualization to its logical extreme. And what it means is that on kernels that support being run as microVMs, you can boot them in like one or two seconds, instead of 20 or 30 or 60 seconds. And this could save a great deal of time, at least for jobs that run on Linux and BSD. I don&apos;t know if I said Linux and macOS, but I meant Linux and BSD.
&lt;/section&gt;

&lt;hr&gt;

&lt;section&gt;
&lt;div class=&quot;_slide&quot;&gt;
&lt;img src=&quot;/images/servo-ci/mpv-shot0065.jpg&quot; alt=&quot;github.com/servo/ci-runners
Slides: go.daz.cat/3tdhp&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;_spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;So yea, we now have a system that we use to speed up our builds in Servo&apos;s CI, and it works fairly well for us. And we think that it&apos;s potentially useful for other projects as well.

&lt;p&gt;So if you&apos;re interested to find out more, or you&apos;re interested to find out how you can use the system in your own projects, go to our GitHub repo at &lt;a href=&quot;https://github.com/servo/ci-runners&quot;&gt;servo/ci-runners&lt;/a&gt;, or you can go &lt;a href=&quot;https://go.daz.cat/3tdhp&quot;&gt;here for a link to the slides&lt;/a&gt;. Thanks!

&lt;style&gt;
article &gt; section:not(#specificity) {
	position: relative;
	display: flex;
	flex-flow: column nowrap;
	padding: 1em 0;
}
article &gt; section:not(#specificity) &gt; * {
	flex: 0 0 auto;
}
article &gt; section:not(#specificity) &gt; ._slide {
	max-height: 50vh;
	flex: 0 0 auto;
	position: sticky;
	top: 0;
	text-align: center;
}
article &gt; section:not(#specificity) &gt; ._slide &gt; img {
	max-width: 100%;
	max-height: 50vh;
}
article &gt; section:not(#specificity) &gt; ._spacer {
	flex: 1 0 1em;
}
&lt;/style&gt;</content><author><name></name></author><category term="home" /><category term="igalia" /><summary type="html">Servo is a greenfield web browser engine that supports many platforms. Automated testing for the project requires building Servo for all of those platforms, plus several additional configurations, and running nearly two million tests including the entire Web Platform Tests. How do we do all of that in under half an hour, without a hyperscaler budget for compute and an entire team to keep it all running smoothly, and securely enough to run untrusted code from contributors?</summary></entry><entry><title type="html">Generative AI in Servo</title><link href="https://www.azabani.com/2025/04/11/generative-ai-in-servo.html" rel="alternate" type="text/html" title="Generative AI in Servo" /><published>2025-04-11T12:00:00+00:00</published><updated>2025-04-11T12:00:00+00:00</updated><id>https://www.azabani.com/2025/04/11/generative-ai-in-servo</id><content type="html" xml:base="https://www.azabani.com/2025/04/11/generative-ai-in-servo.html">&lt;p&gt;&lt;a href=&quot;https://servo.org&quot;&gt;Servo&lt;/a&gt; has shown that we can build a browser with a modern, parallel layout engine in a fraction of the cost of the big incumbents, thanks to our &lt;a href=&quot;https://www.rust-lang.org&quot;&gt;powerful&lt;/a&gt; &lt;a href=&quot;https://rust-analyzer.github.io/book/&quot;&gt;tooling&lt;/a&gt;, our &lt;a href=&quot;https://servo.zulipchat.com/&quot;&gt;strong community&lt;/a&gt;, and our &lt;a href=&quot;https://book.servo.org/&quot;&gt;thorough documentation&lt;/a&gt;.
But we can, and should, build Servo without generative AI tools like GitHub Copilot.&lt;/p&gt;

&lt;aside&gt;
  &lt;p&gt;This post is my personal opinion, not necessarily representative of Servo or my colleagues at Igalia.
I hope it makes a difference.&lt;/p&gt;
&lt;/aside&gt;

&lt;p&gt;I’m the lead author of our &lt;a href=&quot;https://servo.org/blog/&quot;&gt;monthly updates&lt;/a&gt; and the &lt;a href=&quot;https://book.servo.org&quot;&gt;Servo book&lt;/a&gt;, a member of the &lt;a href=&quot;https://github.com/servo/project/blob/6dcfe4a26b034e0dccad2f4a31c1d797abcc8c82/governance/tsc/README.md&quot;&gt;Technical Steering Committee&lt;/a&gt;, and a coauthor of &lt;a href=&quot;https://book.servo.org/contributing.html#ai-contributions&quot;&gt;our current AI policy&lt;/a&gt; (&lt;a href=&quot;https://github.com/servo/book/blob/d4c87ea7646ce43b354aa5c37dea674e830d5edf/src/contributing.md#ai-contributions&quot;&gt;permalink&lt;/a&gt;).
That policy was inspired by &lt;a href=&quot;https://wiki.gentoo.org/wiki/Project:Council/AI_policy&quot;&gt;Gentoo’s AI policy&lt;/a&gt;, and has in turn inspired the AI policies of &lt;a href=&quot;https://discourse.gnome.org/t/loupe-no-longer-allows-generative-ai-contributions/27327&quot;&gt;Loupe&lt;/a&gt; and &lt;a href=&quot;https://mastodon.social/@whitequark/114303833444527216&quot;&gt;Amaranth&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Recently the TSC voted in favour of &lt;a href=&quot;https://github.com/servo/servo/discussions/36379&quot;&gt;two proposals&lt;/a&gt; that relax our ban on AI contributions.
This was a mistake, and it was also a mistake to wait until &lt;em&gt;after&lt;/em&gt; we had made our decision to seek community feedback (see &lt;a href=&quot;#on-governance&quot;&gt;§ On governance&lt;/a&gt;).
&lt;a href=&quot;#your-feedback&quot;&gt;§ Your feedback&lt;/a&gt; made it clear that those proposals are the wrong way forward for Servo.&lt;/p&gt;

&lt;aside&gt;
  &lt;p&gt;&lt;strong&gt;Correction (2025-04-12)&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;A previous version of this post highlighted a logic error in the &lt;a href=&quot;https://github.com/servo/servo/discussions/36379&quot;&gt;AI-assisted patch&lt;/a&gt; we used as a basis for those two proposals.
This error was made in a non-AI-assisted part of the patch.&lt;/p&gt;
&lt;/aside&gt;

&lt;p&gt;I call on the TSC to &lt;strong&gt;explicitly reaffirm that generative AI tools like Copilot are not welcome in Servo&lt;/strong&gt;, and make it clear that we &lt;strong&gt;intend to keep it that way indefinitely&lt;/strong&gt;, in both our policy and the community, so we can start rebuilding trust.
It’s not enough to say oops, sorry, we will not be moving forward with these proposals.&lt;/p&gt;

&lt;p&gt;Like any logic written by humans, this policy does have some unintended consequences.
Our intent was to ban AI tools that generate bullshit &lt;a href=&quot;https://link.springer.com/content/pdf/10.1007/s10676-024-09775-5.pdf&quot;&gt;[a]&lt;/a&gt; in inscrutable ways, including GitHub Copilot and ChatGPT.
But there are other tools that use &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Transformer_(deep_learning_architecture)&amp;amp;oldid=1284020707&quot;&gt;similar&lt;/a&gt; &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Deep_learning&amp;amp;oldid=1283714111&quot;&gt;underlying&lt;/a&gt; &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Neural_network_(machine_learning)&amp;amp;oldid=1282432692&quot;&gt;technology&lt;/a&gt; in more useful and less problematic ways (see &lt;a href=&quot;#potential-exceptions&quot;&gt;§ Potential exceptions&lt;/a&gt;).
Reviewing these tools for use in Servo should be a &lt;strong&gt;community-driven process&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We should not punish contributors for honest mistakes, but we should &lt;strong&gt;make our policy easier to follow&lt;/strong&gt;.
Some ways to do this include documenting the tools that are known to be allowed and not allowed, documenting how to turn off features that are not allowed, and giving contributors a way to declare that they’ve read and followed the policy.&lt;/p&gt;

&lt;p&gt;The declaration would be a good place to provide a dated link to the policy, giving contributors the best chance to understand the policy and knowingly follow it (or violate it).
This is not perfect, and it won’t always be easy to enforce, but it should give contributors and maintainers a foundation of trust.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;potential-exceptions&quot;&gt;Potential exceptions&lt;/h2&gt;

&lt;p&gt;Proposals for exceptions should start in the community, and should focus on a specific tool used for a specific purpose.
If the proposal is for a specific &lt;em&gt;kind&lt;/em&gt; of tool, it must come with concrete examples of &lt;em&gt;which&lt;/em&gt; tools are to be allowed.
Much of the harm being caused by generative AI in the world around us comes from people using open-ended tools that are not fit for any purpose, or even treating them like they are &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Artificial_general_intelligence&amp;amp;oldid=1284985284&quot;&gt;AGI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The goal of these discussions would be to understand:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the underlying challenges faced by contributors&lt;/li&gt;
  &lt;li&gt;how effective the tool is for the purpose&lt;/li&gt;
  &lt;li&gt;how well the tool and purpose mitigate the issues in the policy&lt;/li&gt;
  &lt;li&gt;whether there are any existing or alternative solutions&lt;/li&gt;
  &lt;li&gt;whether those solutions have problems that need to be addressed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes the purpose may need to be constrained to mitigate the issues in the policy.
Let’s look at a couple of examples.&lt;/p&gt;

&lt;p&gt;For some tasks like &lt;strong&gt;speech recognition&lt;/strong&gt; &lt;a href=&quot;https://arxiv.org/pdf/2212.04356&quot;&gt;[b]&lt;/a&gt; and &lt;strong&gt;machine translation&lt;/strong&gt; &lt;a href=&quot;https://research.google/blog/recent-advances-in-google-translate/&quot;&gt;[c]&lt;/a&gt; &lt;a href=&quot;https://aclanthology.org/2020.amta-research.9.pdf&quot;&gt;[d]&lt;/a&gt;, tools with large language models and transformers are the state of the art (other than humans).
This means those tools may be probabilistic tools, and strictly speaking, they may be &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Generative_artificial_intelligence&amp;amp;oldid=1284756817&quot;&gt;&lt;em&gt;generative AI&lt;/em&gt;&lt;/a&gt; tools, because the models they use are &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Generative_model&amp;amp;oldid=1264858524&quot;&gt;&lt;em&gt;generative models&lt;/em&gt;&lt;/a&gt;.
Generative AI does not necessarily mean “AI that generates bullshit in inscrutable ways”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speech recognition&lt;/strong&gt; can be used in a variety of ways.
If plumbed into ChatGPT, it will have all of the same problems as ChatGPT.
If used for automatic captions, it can make videos and calls accessible to people that can’t hear well (myself included), but it can also infantilise us by &lt;a href=&quot;https://ericwbailey.website/published/swearing-and-automatic-captions/&quot;&gt;censoring profanities&lt;/a&gt; and make serious errors that &lt;a href=&quot;https://www.consumerreports.org/disability-rights/auto-captions-often-fall-short-on-zoom-facebook-and-others-a9742392879/&quot;&gt;cause real harm&lt;/a&gt;.
If deployed for that purpose by an online video platform, it can undermine the labour of human transcribers and lower the overall quality of captions.&lt;/p&gt;

&lt;p&gt;If used as an input method, it would be a clear win for accessibility.
My understanding of speech input tools is that they have a clear (if configurable) mapping from the things you say to the text they generate or &lt;a href=&quot;https://www.cursorless.org/docs/&quot;&gt;the edits they make&lt;/a&gt;, so they may be a good fit.&lt;/p&gt;

&lt;p&gt;In that case, &lt;em&gt;maintainer burden&lt;/em&gt; and &lt;em&gt;correctness and security&lt;/em&gt; would not be an issue, because the author is in complete control of what they write.
&lt;em&gt;Copyright issues&lt;/em&gt; seem less of a concern to me, since these tools operate on such a small scale (words and symbols) that they are unlikely to reproduce a copyrightable amount of text verbatim, but I am not a lawyer.
As for &lt;em&gt;ethical issues&lt;/em&gt;, these tools are generally trained once then run on the author’s device.
When used as an input method, they are not being used to undermine labour or justify layoffs.
I’m not sure about the process of training their models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Machine translation&lt;/strong&gt; can also be used in a variety of ways.
If deployed by a language learning app, it can ruin the quality of your core product, but hey, then you can &lt;a href=&quot;https://www.washingtonpost.com/technology/2024/01/10/duolingo-ai-layoffs/&quot;&gt;lay off&lt;/a&gt; those pesky human translators.
If used to localise your product, your users will finally be able to &lt;a href=&quot;https://www.neowin.net/news/accept-essential-biscuits-windows-11-calls-zip-files-postcode-files-in-uk-english/&quot;&gt;compress to postcode file&lt;/a&gt;.
If used to localise your docs, it can make your docs worse than useless unless you &lt;a href=&quot;https://www.reddit.com/r/rust/comments/1jtw560/comment/mm6e34l/&quot;&gt;take very careful precautions&lt;/a&gt;.
What if we allowed contributors to use machine translation to communicate with each other, but not in code commits, documentation, or any other work products?&lt;/p&gt;

&lt;p&gt;Deployed carelessly, they will waste the reader’s time, and undermine the labour of actual human translators who would otherwise be happy to contribute to Servo.
If constrained to collaboration, it would still be far from perfect, but it may be worthwhile.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Maintainer burden&lt;/em&gt; should be mitigated, because this won’t change the amount or kind of text that needs to be reviewed.
&lt;em&gt;Correctness and security&lt;/em&gt; too, because this won’t change the text that can be committed to Servo.
I can’t comment on the &lt;em&gt;copyright issues&lt;/em&gt;, because I am not a lawyer.
The &lt;em&gt;ethical issues&lt;/em&gt; may be significantly reduced, because this use case wasn’t a market for human translators in the first place.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;your-feedback&quot;&gt;Your feedback&lt;/h2&gt;

&lt;p&gt;I appreciate the feedback you gave &lt;a href=&quot;https://floss.social/@servo/114296977894869359&quot;&gt;on the Fediverse&lt;/a&gt;, &lt;a href=&quot;https://bsky.app/profile/servo.org/post/3lma3ru5jok2y&quot;&gt;on Bluesky&lt;/a&gt;, and &lt;a href=&quot;https://www.reddit.com/r/rust/comments/1jtw560&quot;&gt;on Reddit&lt;/a&gt;.
I also appreciate the comments &lt;a href=&quot;https://github.com/servo/servo/discussions/36379&quot;&gt;on GitHub&lt;/a&gt; from several people who were more on the &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12752620&quot;&gt;favouring&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12752951&quot;&gt;side&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12754434&quot;&gt;of&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12770260&quot;&gt;the&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12775011&quot;&gt;proposal&lt;/a&gt;, even though we reached different conclusions in most cases.
One comment argued that it’s possible to use AI autocomplete safely by accepting the completions &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12756971&quot;&gt;one word at a time&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That said, the overall consensus in our community was overwhelmingly clear, including among many of those who were in favour of the proposals.
None of the benefits of generative AI tools are worth the cost in community goodwill &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12770260&quot;&gt;[e]&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Much of the dissent on GitHub was already covered by our existing policy, but there were quite a few arguments worth highlighting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speech-to-text input&lt;/strong&gt;
is ok &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12776344&quot;&gt;[f]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12756111&quot;&gt;[g]&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Machine translation&lt;/strong&gt;
is generally not useful or effective for technical writing &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12751548&quot;&gt;[h]&lt;/a&gt; &lt;a href=&quot;https://www.reddit.com/r/rust/comments/1jtw560/comment/mlxty67&quot;&gt;[i]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12752411&quot;&gt;[j]&lt;/a&gt;.
It can be, if some precautions are taken &lt;a href=&quot;https://www.reddit.com/r/rust/comments/1jtw560/comment/mm6e34l/&quot;&gt;[k]&lt;/a&gt;.
It may be less ethically encumbered than generative AI tools &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12752707&quot;&gt;[l]&lt;/a&gt;.
Client-side machine translation is ok &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12753281&quot;&gt;[m]&lt;/a&gt;.
Machine translation for collaboration is ok &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12759143&quot;&gt;[n]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12756111&quot;&gt;[o]&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The proposals.&lt;/strong&gt;
Proposal 1 is ill-defined &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12754818&quot;&gt;[p]&lt;/a&gt;.
Proposal 2 has an ill-defined distinction between autocompletes and “full” code generation &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12753114&quot;&gt;[q]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12752843&quot;&gt;[r]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12753041&quot;&gt;[s]&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Documentation&lt;/strong&gt;
is just as technical as code &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12767895&quot;&gt;[u]&lt;/a&gt;.
Wrong documentation is worse than no documentation &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12751560&quot;&gt;[v]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12753041&quot;&gt;[w]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12769041&quot;&gt;[x]&lt;/a&gt;.
Good documentation requires human context &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12751548&quot;&gt;[y]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12752411&quot;&gt;[z]&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Copilot&lt;/strong&gt;
is not a good tool for answering questions &lt;a href=&quot;https://github.com/servo/book/pull/27&quot;&gt;[ab]&lt;/a&gt;.
It isn’t even that good of a programming tool &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12752073&quot;&gt;[ac]&lt;/a&gt;.
Using it may be incompatible with the DCO &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12752073&quot;&gt;[ad]&lt;/a&gt;.
Using it could make us depend on Microsoft to protect us against legal liability &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12752938&quot;&gt;[ae]&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Correctness.&lt;/strong&gt;
Generative AI code is wrong at an alarming rate &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12754116&quot;&gt;[af]&lt;/a&gt;.
Generative AI tools will lie to us with complete confidence &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12753566&quot;&gt;[ag]&lt;/a&gt;.
Generative AI tools (and users of those tools) cannot explain their reasoning &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12752823&quot;&gt;[ah]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12776304&quot;&gt;[ai]&lt;/a&gt;.
Humans as supervisors are ill-equipped to deal with the subtle errors that generative AI tools make &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12752707&quot;&gt;[aj]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12753375&quot;&gt;[ak]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12756059&quot;&gt;[al]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12766312&quot;&gt;[am]&lt;/a&gt;.
Even experts can easily be misled by these tools &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12752411&quot;&gt;[an]&lt;/a&gt;.
Typing is not the hard part of programming &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12752823&quot;&gt;[ao]&lt;/a&gt;, as even some of those in favour &lt;a href=&quot;https://medium.com/@polyglot_factotum/ai-is-not-a-better-ide-e395db9da063&quot;&gt;have said&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;If I could offload that part of the work to copilot, I would be left with more energy for the challenging part.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Project health.&lt;/strong&gt;
Partially lifting the ban will create uncertainty that increases maintainer burden for all contributions &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12753041&quot;&gt;[ap]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12776304&quot;&gt;[aq]&lt;/a&gt;.
Becoming dependent on tools with non-free models is risky &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12752620&quot;&gt;[ar]&lt;/a&gt;.
Generative AI tools may not be fair use &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12755626&quot;&gt;[as]&lt;/a&gt; → &lt;a href=&quot;https://suchir.net/fair_use.html&quot;&gt;[at]&lt;/a&gt;.
Outside of Servo, people have spent so much time cleaning up after LLM-generated mess &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12752843&quot;&gt;[au]&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Material.&lt;/strong&gt;
Servo contributor refuses to spend time cleaning up after LLM-generated mess &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12752707&quot;&gt;[av]&lt;/a&gt;.
Others will stop donating &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12752707&quot;&gt;[aw]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12753442&quot;&gt;[ax]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12753939&quot;&gt;[ay]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12754804&quot;&gt;[az]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12754889&quot;&gt;[ba]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12755244&quot;&gt;[bb]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12758397&quot;&gt;[bc]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12759143&quot;&gt;[bd]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12763689&quot;&gt;[be]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12755031&quot;&gt;[bf]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12756035&quot;&gt;[bg]&lt;/a&gt;, will stop contributing &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12752707&quot;&gt;[bh]&lt;/a&gt;, will not start donating &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12754139&quot;&gt;[bi]&lt;/a&gt;, will not start contributing &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12753271&quot;&gt;[bj]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12755031&quot;&gt;[bk]&lt;/a&gt;, or will not start promoting &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12753271&quot;&gt;[bl]&lt;/a&gt; the project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Broader context.&lt;/strong&gt;
Allowing AI contributions is a bad signal for the project’s relationship with the broader AI movement &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12752415&quot;&gt;[bm]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12752707&quot;&gt;[bn]&lt;/a&gt; &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12757753&quot;&gt;[bo]&lt;/a&gt;.
The modern AI movement is backed by overwhelming capital interests, and must be opposed equally strongly &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12752707&quot;&gt;[bp]&lt;/a&gt;.
People often “need” GitHub or Firefox, but no one “needs” Servo, so we can and should be held to a higher standard &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12752707&quot;&gt;[bq]&lt;/a&gt;.
Rejection of AI is only credible if the project rejects AI contributions &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12752707&quot;&gt;[br]&lt;/a&gt;.
We can attract funding from AI-adjacent parties without getting into AI ourselves &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12752823&quot;&gt;[bs]&lt;/a&gt;, though that may be easier said than done &lt;a href=&quot;https://github.com/servo/servo/discussions/36379#discussioncomment-12775861&quot;&gt;[bt]&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;on-governance&quot;&gt;On governance&lt;/h2&gt;

&lt;p&gt;Several people have raised concerns about how Servo’s governance could have led to this decision, and some have even suspected foul play.
But like most discussions in the TSC, most of the discussion around AI contributions happened async on Zulip, and we didn’t save anything special for the synchronous monthly public calls.
As a result, whenever the discussion overflowed the sync meeting, we just continued it internally, so the public minutes were missing the vast majority of the discussion (and the decisions).
These decisions should probably have happened in public.&lt;/p&gt;

&lt;p&gt;Our decisions followed the TSC’s usual process, with a strong preference for resolving disagreements by consensus rather than by voting, but we didn’t have any consistent structure for moving from one to the other.
This may have made the decision process prone to being blocked and dominated by the most persistent participants.&lt;/p&gt;

&lt;p&gt;Contrast this with decision making within Igalia, where we also prefer consensus before voting, but the consensus process is always used to inform &lt;em&gt;proposals&lt;/em&gt; that are drafted by &lt;em&gt;more than one person&lt;/em&gt; and then &lt;em&gt;always voted on&lt;/em&gt;.
Most polls are “yes” or “no” by majority, and only a few polls for the most critical matters allow vetoing.
This ensures that proposals have meaningful support before being considered, and if only one person is strongly against something, they are heard but they generally can’t single-handedly block the decision with debate.&lt;/p&gt;

&lt;aside&gt;
  &lt;p&gt;The rules are actually more complex than just by majority.
There’s clear advice on what “yes”, “no”, and “abstain” actually mean, they take into account abstaining and undecided voters, there are set time limits and times to contact undecided voters, and they provide for a way to abort a poll if the wording of the proposal is ill-formed.&lt;/p&gt;

  &lt;p&gt;We had twenty years to figure out all those details, and one of the improvements above only landed a couple of months ago.&lt;/p&gt;
&lt;/aside&gt;

&lt;p&gt;We also didn’t have any consistent structure for community consultation, so it wasn’t clear how or when we should seek feedback.
A public RFC process may have helped with this, and would also help us collaborate on and document other decisions.&lt;/p&gt;

&lt;p&gt;More personally, I did not participate in the extensive discussion in January and February that helped move consensus in the TSC towards allowing the non-code and Copilot exceptions until &lt;a href=&quot;https://github.com/servo/project/blob/6dcfe4a26b034e0dccad2f4a31c1d797abcc8c82/governance/tsc/tsc-2025-02-24.md#ai-policy-review&quot;&gt;fairly late&lt;/a&gt;.
Some of that was because I was on leave, including for the vote on the initial Copilot “experiments”, but most of it was that I didn’t have the bandwidth.
Doing politics is hard, exhausting work, and there’s only so much of it you can do, even when you’re not wearing three other hats.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;a href=&quot;/images/generative-ai-in-servo.jpg&quot;&gt;&lt;img alt=&quot;a white and grey cat named Diffie, poking her head out through a sliding door&quot; title=&quot;a white and grey cat named Diffie, poking her head out through a sliding door&quot; src=&quot;/images/generative-ai-in-servo.jpg&quot; style=&quot;width: 100%;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;</content><author><name></name></author><category term="home" /><category term="igalia" /><summary type="html">Servo has shown that we can build a browser with a modern, parallel layout engine in a fraction of the cost of the big incumbents, thanks to our powerful tooling, our strong community, and our thorough documentation. But we can, and should, build Servo without generative AI tools like GitHub Copilot.</summary></entry><entry><title type="html">Meet the CSS highlight pseudos</title><link href="https://www.azabani.com/2022/09/01/meet-the-css-highlight-pseudos.html" rel="alternate" type="text/html" title="Meet the CSS highlight pseudos" /><published>2022-09-01T15:00:00+00:00</published><updated>2022-09-01T15:00:00+00:00</updated><id>https://www.azabani.com/2022/09/01/meet-the-css-highlight-pseudos</id><content type="html" xml:base="https://www.azabani.com/2022/09/01/meet-the-css-highlight-pseudos.html">&lt;p&gt;A year and a half ago, I was asked to help upstream a Chromium patch allowing authors to recolor &lt;span class=&quot;_spelling&quot;&gt;spelling&lt;/span&gt; and &lt;span class=&quot;_grammar&quot;&gt;grammar&lt;/span&gt; errors in CSS.
At the time, I didn’t realise that this was part of a far more ambitious effort to reimagine spelling errors, grammar errors, text selections, and more as a coherent system that didn’t yet exist as such in any browser.
That system is known as the &lt;em&gt;highlight pseudos&lt;/em&gt;, and this post will focus on the design of said system and its consequences for authors.&lt;/p&gt;

&lt;p&gt;This is the third part of a series (&lt;a href=&quot;/2021/05/17/spelling-grammar.html&quot;&gt;part one&lt;/a&gt;, &lt;a href=&quot;/2021/12/16/spelling-grammar-2.html&quot;&gt;part two&lt;/a&gt;) about Igalia’s work towards making the CSS highlight pseudos a reality.&lt;/p&gt;

&lt;aside&gt;
  &lt;p&gt;&lt;strong&gt;Update (2024-04-29):&lt;/strong&gt;&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;added &lt;a href=&quot;#applicable-properties&quot;&gt;details about applicable properties&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;removed &lt;a href=&quot;#accessing-global-constants&quot;&gt;§ &lt;em&gt;Accessing global constants&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;added &lt;a href=&quot;#custom-properties&quot;&gt;§ &lt;em&gt;Custom properties&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/aside&gt;

&lt;style&gt;
article { --cr-highlight: #3584E4; --cr-highlight-aC0h: #3584E4C0; }
article figure &gt; img { max-width: 100%; }
article figure &gt; figcaption { max-width: 30rem; margin-left: auto; margin-right: auto; }
article pre, article code { font-family: Inconsolata, monospace, monospace; }
article aside, article blockquote { font-size: 0.75em; max-width: 30rem; }
article aside { margin-left: 0; padding-left: 1rem; border-left: 3px double rebeccapurple; }
article blockquote { margin-left: 3rem; }
article blockquote:before { margin-left: -2rem; }

._spelling, ._grammar { text-decoration-thickness: /* iOS takes 0 literally */ 1px; text-decoration-skip-ink: none; }
._spelling { text-decoration: /* not a shorthand on iOS */ underline; text-decoration-style: wavy; text-decoration-color: red; }
._grammar { text-decoration: /* not a shorthand on iOS */ underline; text-decoration-style: wavy; text-decoration-color: green; }
._example { border: 2px dotted rebeccapurple; }
._example * + *, ._hpdemo * + * { margin-top: 0; }

._checker { position: relative; margin-left: auto; margin-right: auto; }
._checker:focus { outline: none; }
._checker::before { display: flex; align-items: center; justify-content: center; position: absolute; top: 0; bottom: 0; left: 0; right: 0; width: 100%; font-size: 7em; color: transparent; background: transparent; content: &quot;▶&quot;; }
._checker:not(:focus)::before { color: rebeccapurple; background: #66339940; }

._checker tbody th { text-align: left; }
._checker ._live::selection, ._checker ._live *::selection { color: currentColor; background: transparent; }
._checker:not(:focus) ._live &gt; div { visibility: hidden; }
._checker:not([data-phase=done]):not(#specificity) ._live &gt; div,
._checker:not([data-phase=done]):not(#specificity) ._live &gt; div * { color: transparent; }
._checker:not([data-phase=done]):not(#specificity) ._live &gt; div::selection,
._checker:not([data-phase=done]):not(#specificity) ._live &gt; div *::selection { color: transparent; }
._checker:not([data-phase=done]):not(#specificity) ._live &gt; div::highlight(checker),
._checker:not([data-phase=done]):not(#specificity) ._live &gt; div *::highlight(checker),
._checker:not([data-phase=done]):not(#specificity) ._live &gt; div::highlight(lower),
._checker:not([data-phase=done]):not(#specificity) ._live &gt; div *::highlight(lower) { color: transparent; }
._checker ._live &gt; div { width: 5em; }
._checker ._live &gt; div { position: relative; line-height: 1; }
._checker ._live &gt; div &gt; span { position: absolute; margin: 0; padding-top: calc((1.5em - 1em) / 2); width: 5em; }

/*
    ::highlight() [end-to-end test]
    = no, if the pseudo selector is broken and/or no active highlight
    = yes, if the pseudo selector works and highlight is active
*/
._checker ._custom
    :nth-child(2) { color: transparent; background: transparent; }
._checker ._custom
    :nth-child(1)::highlight(checker) { color: transparent; }
._checker ._custom
    :nth-child(2)::highlight(checker) { color: CanvasText; background: Canvas; }

/*
    ::highlight() [selector]
    = no, if the pseudo selector is unsupported
    = yes, if the pseudo selector is supported
    • highlight not active, only for selector list validity
*/
._checker ._chps
    :nth-child(2) { color: transparent; }
._checker ._chps
    :nth-child(1), :not(*)::highlight(checker) { color: transparent; }
._checker ._chps
    :nth-child(2), :not(*)::highlight(checker) { color: CanvasText; }

/*
    ::highlight() [API]
    = no, if the API is missing or broken
    = yes, if the API is present and working
*/
._checker ._cha {}

/*
    ::spelling-error [end-to-end test]
    = no, if the pseudo selector is broken and/or no active highlight
    = yes, if the pseudo selector works and highlight is active
*/
._checker [spellcheck]
    :nth-child(2) { color: transparent; background: transparent; }
._checker [spellcheck]
    :nth-child(1)::spelling-error { color: transparent; }
._checker [spellcheck]
    :nth-child(2)::spelling-error { color: CanvasText; background: Canvas; }
._checker [spellcheck]
    *::spelling-error { text-decoration: none; }

/*
    Highlight inheritance (::highlight)
    = no, if var() inherits from originating element
    = yes, if var() ignores originating element and uses fallback
*/
._checker ._hih
    { color: transparent; background: transparent; }
._checker ._hih::highlight(checker)
    { --t: transparent; --x: CanvasText; --y: Canvas; }
._checker ._hih :nth-child(1)::highlight(checker)
    { color: var(--t, CanvasText); background: var(--t, Canvas); }
._checker ._hih :nth-child(2)::highlight(checker)
    { color: var(--x, transparent); background: var(--y, transparent); }

/*
    Highlight inheritance (::selection)
    = no, if var() inherits from originating element
    = yes, if var() ignores originating element and uses fallback
*/
._checker ._his
    { color: transparent; background: transparent; }
._checker ._his::selection
    { --t: transparent; --x: CanvasText; --y: Canvas; }
._checker ._his :nth-child(1)::selection
    { color: var(--t, CanvasText); background: var(--t, Canvas); }
._checker ._his :nth-child(2)::selection
    { color: var(--x, transparent); background: var(--y, transparent); }

/*
    Highlight overlay painting
    = no, if currentColor takes color from originating element only
    = yes, if currentColor takes color from next active highlight
    • lower highlight “yes” is hidden by ‘-webkit-text-fill-color’
*/
._checker ._hop
    { color: transparent; background: transparent; }
._checker ._hop :nth-child(1) { color: CanvasText; }
._checker ._hop :nth-child(1)::highlight(lower) { color: transparent; }
._checker ._hop :nth-child(1)::highlight(checker) { color: currentColor; }
._checker ._hop :nth-child(2) { color: transparent; }
._checker ._hop :nth-child(2)::highlight(lower) { color: CanvasText; -webkit-text-fill-color: transparent; }
._checker ._hop :nth-child(2)::highlight(checker) { color: currentColor; -webkit-text-fill-color: currentColor; }

._table { font-size: 0.75em; }
._table td, ._table th { vertical-align: top; border: 1px solid black; }
._table td:not(._tight), ._table th:not(._tight) { padding: 0.5em; }
._tight picture, ._tight img { vertical-align: top; }
._compare * + *, ._tight * + *, ._gifs * + * { margin-top: 0; }
._compare { max-width: 100%; border: 1px solid rebeccapurple; }
._compare &gt; div { max-width: 100%; position: relative; touch-action: pinch-zoom; --cut: 50%; }
._compare &gt; div &gt; * { vertical-align: top; max-width: 100%; }
._compare &gt; div &gt; :nth-child(1) { position: absolute; clip: rect(auto, auto, auto, var(--cut)); }
._compare &gt; div &gt; :nth-child(2) { position: absolute; width: var(--cut); height: 100%; border-right: 1px solid rebeccapurple; }
._compare &gt; div &gt; :nth-child(2):before { content: var(--left-label); color: rebeccapurple; font-size: 0.75em; position: absolute; right: 0.5em; }
._compare &gt; div &gt; :nth-child(2):after { content: var(--right-label); color: rebeccapurple; font-size: 0.75em; position: absolute; left: calc(100% + 0.5em); }
._sum td:first-of-type { padding-right: 1em; }
._gifs { position: relative; display: flex; flex-flow: column nowrap; }
._gifs &gt; video { transition: opacity 0.125s linear; }
._gifs &gt; button { transition: 0.125s linear; transition-property: color, background-color; }
._gifs._paused &gt; video { opacity: 0.5; }
._gifs._paused &gt; button { color: rebeccapurple; background: #66339940; }
._gifs &gt; button { position: absolute; top: 0; bottom: 0; left: 0; right: 0; width: 100%; font-size: 7em; color: transparent; background: transparent; content: &quot;▶&quot;; }
._gifs &gt; button:focus-visible { outline: 0.25rem solid #663399C0; outline-offset: -0.25rem; }

._commits { position: relative; }
._commits &gt; :first-child { position: absolute; right: -0.1em; height: 100%; border-right: 0.2em solid rgba(102,51,153,0.5); }
._commits &gt; :last-child { position: relative; padding-right: 0.5em; }
* + ._commit, ._commit * + * { margin-top: 0; }
._commit { line-height: 2; margin-right: -1.5em; text-align: right; }
._commit &gt; img { width: 2em; vertical-align: middle; }
._commit &gt; a { padding-right: 0.5em; text-decoration: none; color: rebeccapurple; }
._commit &gt; a &gt; code { font-size: 1em; }
._commit-none &gt; a { color: rgba(102,51,153,0.5); }
&lt;/style&gt;

&lt;h2 id=&quot;contents&quot;&gt;Contents&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#what-are-they&quot;&gt;What are they?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#can-i-use-them&quot;&gt;Can I use them?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#how-do-i-use-them&quot;&gt;How do I use them?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#how-do-they-work&quot;&gt;How do they work?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#gotchas&quot;&gt;Gotchas&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#removing-decorations-and-shadows&quot;&gt;Removing decorations and shadows&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#accessing-global-constants&quot;&gt;Accessing global constants&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#spec-issues&quot;&gt;Spec issues&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;what-are-they&quot;&gt;What are they?&lt;/h2&gt;

&lt;p&gt;CSS has four highlight pseudos and an open set of author-defined custom highlight pseudos.
They have their roots in ::selection, which was a rudimentary and non-standard, but widely supported, way of styling text and images selected by the user.&lt;/p&gt;

&lt;p&gt;The built-in highlights are ::selection for user-selected content, ::target-text for linking to text fragments, ::spelling-error for misspelled words, and ::grammar-error for text with grammar errors, while the custom highlights are known as ::highlight(&lt;em&gt;x&lt;/em&gt;) where &lt;em&gt;x&lt;/em&gt; is the author-defined highlight name.&lt;/p&gt;

&lt;h2 id=&quot;can-i-use-them&quot;&gt;Can I use them?&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/::selection&quot;&gt;::selection&lt;/a&gt; has long been supported by all of the major browsers, and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/::target-text&quot;&gt;::target-text&lt;/a&gt; shipped in Chromium 89.
But for most of that time, no browser had yet implemented the more robust highlight pseudo system in the &lt;a href=&quot;https://drafts.csswg.org/css-pseudo/&quot;&gt;CSS pseudo spec&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;::highlight() and the custom highlight API shipped in Chromium 105, thanks to the work by members&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; of the Microsoft Edge team.
They are also available in Safari 14.1 (including iOS 14.5) as an experimental feature (Highlight API).
You can enable that feature in the Develop menu, or for iOS, under Settings &amp;gt; Safari &amp;gt; Advanced.&lt;/p&gt;

&lt;aside&gt;
  &lt;p&gt;Safari’s support currently has a couple of quirks, as of TP 152.
Range is not supported for custom highlights yet, only StaticRange, and the Highlight constructor has a bug where it requires passing exactly one range, ignoring any additional arguments.
To create a Highlight with no ranges, first create one with a dummy range, then call the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clear&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;delete&lt;/code&gt; methods.&lt;/p&gt;
&lt;/aside&gt;

&lt;p&gt;Chromium 105 also implements the vast majority of the new highlight pseudo system.
This includes highlight overlay painting, which was enabled for all highlight pseudos, and highlight inheritance, which was enabled for ::highlight() only.&lt;/p&gt;

&lt;p&gt;Chromium 108 includes ::spelling-error and ::grammar-error as an experimental feature, together with the new ‘text-decoration-line’ values ‘spelling-error’ and ‘grammar-error’.
Chromium 111 enables highlight inheritance for ::selection and ::target-text as an experimental feature, in addition to ::highlight() and the spelling and grammar pseudos (which always use highlight inheritance).
You can enable these features at&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;chrome://flags/#enable-experimental-web-platform-features&lt;/p&gt;
&lt;/blockquote&gt;

&lt;aside&gt;
  &lt;p&gt;Chromium’s support also currently has some bugs, as of r1041796.
Notably, highlights don’t yet work under ::first-line and ::first-letter&lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;, ‘text-shadow’ is &lt;a href=&quot;https://crbug.com/1350475&quot;&gt;not yet enabled&lt;/a&gt; for ::highlight(), computedStyleMap &lt;a href=&quot;https://crbug.com/1099874&quot;&gt;results are wrong&lt;/a&gt; for ‘currentColor’, and highlights that split ligatures (e.g. for complex scripts) only render accurately in ::selection&lt;sup id=&quot;fnref:2:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;/aside&gt;

&lt;p&gt;Click the table below to see if your browser supports these features.&lt;/p&gt;

&lt;pre id=&quot;debug&quot; hidden=&quot;&quot; style=&quot;position: fixed; color: white; background: black; left: 0; top: 0; right: 0; margin: 0; white-space: pre-wrap;&quot;&gt;act: &lt;span id=&quot;debug_active&quot;&gt;&lt;/span&gt;
sel: &lt;span id=&quot;debug_selection&quot;&gt;&lt;/span&gt;
cha: &lt;span id=&quot;debug_cha&quot;&gt;&lt;/span&gt;
&lt;span id=&quot;debug_count&quot; hidden=&quot;&quot;&gt;&lt;/span&gt;&lt;/pre&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;&lt;div class=&quot;flex column_bag&quot;&gt;
&lt;table class=&quot;_table _checker&quot; contenteditable=&quot;&quot; spellcheck=&quot;false&quot; data-phase=&quot;fresh&quot;&gt;
    &lt;thead&gt;&lt;tr&gt;
        &lt;th&gt;&lt;/th&gt;&lt;th&gt;yours&lt;/th&gt;&lt;th&gt;Chromium&lt;/th&gt;&lt;th&gt;Safari&lt;/th&gt;&lt;th&gt;Firefox&lt;/th&gt;
    &lt;/tr&gt;&lt;/thead&gt;
    &lt;!--
        Safari 14.0.3 (iOS 14.4.2): -selector (H)
        Safari 14.1.2 (macOS 11): +selector (ab)
        Safari 15.6? (iOS 15.6.1): +selector (ab)
        Safari 15.6.1 (macOS 11): +selector (ab)
        Safari TP 152 (macOS 12.5.1): +selector (ab)
            (16.0, WebKit 17615.1.2.3)
    --&gt;
    &lt;tr&gt;&lt;th&gt;Custom highlights&lt;/th&gt;
        &lt;td class=&quot;_live&quot;&gt;&lt;div class=&quot;_custom&quot;&gt;&lt;span&gt;no&lt;/span&gt;&lt;span&gt;yes&lt;/span&gt;&lt;/div&gt;&lt;/td&gt;
        &lt;td&gt;105&lt;/td&gt;&lt;td&gt;14.1*&lt;/td&gt;&lt;td&gt;?&lt;/td&gt;
    &lt;/tr&gt;&lt;tr&gt;&lt;th&gt;• ::highlight()&lt;/th&gt;
        &lt;td class=&quot;_live&quot;&gt;&lt;div class=&quot;_chps&quot;&gt;&lt;span&gt;no&lt;/span&gt;&lt;span&gt;yes&lt;/span&gt;&lt;/div&gt;&lt;/td&gt;
        &lt;td&gt;105&lt;/td&gt;&lt;td&gt;14.1*&lt;/td&gt;&lt;td&gt;?&lt;/td&gt;
    &lt;/tr&gt;&lt;tr&gt;&lt;th&gt;• CSSOM API&lt;/th&gt;
        &lt;td class=&quot;_live&quot;&gt;&lt;div class=&quot;_cha&quot;&gt;&lt;span&gt;no&lt;/span&gt;&lt;span&gt;yes&lt;/span&gt;&lt;/div&gt;&lt;/td&gt;
        &lt;td&gt;105&lt;/td&gt;&lt;td&gt;14.1* (ab)&lt;/td&gt;&lt;td&gt;?&lt;/td&gt;
    &lt;/tr&gt;&lt;tr&gt;&lt;th&gt;::spelling-error&lt;/th&gt;
        &lt;td class=&quot;_live&quot;&gt;&lt;div spellcheck=&quot;true&quot; lang=&quot;en&quot;&gt;&lt;span&gt;no&lt;/span&gt;&lt;span&gt;yes&lt;/span&gt;&lt;/div&gt;&lt;/td&gt;
        &lt;td&gt;108*&lt;/td&gt;&lt;td&gt;?&lt;/td&gt;&lt;td&gt;?&lt;/td&gt;
    &lt;/tr&gt;&lt;tr&gt;&lt;th&gt;Highlight overlay painting&lt;/th&gt;
        &lt;td class=&quot;_live&quot;&gt;&lt;div class=&quot;_hop&quot;&gt;&lt;span&gt;no&lt;/span&gt;&lt;span&gt;yes&lt;/span&gt;&lt;/div&gt;&lt;/td&gt;
        &lt;td&gt;105&lt;/td&gt;&lt;td&gt;?&lt;/td&gt;&lt;td&gt;?&lt;/td&gt;
    &lt;/tr&gt;&lt;tr&gt;&lt;th&gt;Highlight inheritance (::selection)&lt;/th&gt;
        &lt;td class=&quot;_live&quot;&gt;&lt;div class=&quot;_his&quot;&gt;&lt;span&gt;no&lt;/span&gt;&lt;span&gt;yes&lt;/span&gt;&lt;/div&gt;&lt;/td&gt;
        &lt;td&gt;111*&lt;/td&gt;&lt;td&gt;?&lt;/td&gt;&lt;td&gt;?&lt;/td&gt;
    &lt;/tr&gt;&lt;tr&gt;&lt;th&gt;Highlight inheritance (::highlight)&lt;/th&gt;
        &lt;td class=&quot;_live&quot;&gt;&lt;div class=&quot;_hih&quot;&gt;&lt;span&gt;no&lt;/span&gt;&lt;span&gt;yes&lt;/span&gt;&lt;/div&gt;&lt;/td&gt;
        &lt;td&gt;105&lt;/td&gt;&lt;td&gt;?&lt;/td&gt;&lt;td&gt;?&lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;
&lt;div class=&quot;gap&quot;&gt;&lt;/div&gt;
&lt;aside&gt;
        &lt;ul&gt;
          &lt;li&gt;* = experimental (can be enabled in UI)&lt;/li&gt;
          &lt;li&gt;S = ::highlight() unsupported in querySelector&lt;/li&gt;
          &lt;li&gt;C = CSS.highlights missing or setlike (&lt;a href=&quot;https://www.w3.org/TR/2020/WD-css-highlight-api-1-20201208/&quot;&gt;older API from 2020&lt;/a&gt;)&lt;/li&gt;
          &lt;li&gt;H = new Highlight() missing&lt;/li&gt;
          &lt;li&gt;a = StaticRange only (no support for Range)&lt;/li&gt;
          &lt;li&gt;b = new Highlight() requires exactly one range argument&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/aside&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;

&lt;script&gt;
    let checkerTimer = null;

    // selectionchange events can get stuck in infinite loops if they get
    // normalised in a way that fixCheckerSelectionIfNeeded doesn’t expect
    const selectionchangeTimes = [...Array(10)].map(_ =&gt; null);

    const checker = document.querySelector(&quot;._checker&quot;);
    const counts = new Map;

    function debug_active() {
        if (document.querySelector(&quot;#debug&quot;).hidden)
            return;
        const debug = document.querySelector(&quot;#debug_active&quot;);
        debug.textContent = document.activeElement;
    }

    function debug_selection() {
        if (document.querySelector(&quot;#debug&quot;).hidden)
            return;
        const debug = document.querySelector(&quot;#debug_selection&quot;);
        const sel = getSelection();
        debug.textContent =
            `${sel.anchorOffset} ${format(sel.anchorNode)}`
            + `\n     `
            + `${sel.focusOffset} ${format(sel.focusNode)}`;
        function format(node) {
            if (node == null || node.nodeValue == null)
                return &quot;&quot;;
            if (node.nodeValue.length &lt; 30)
                return `${node.nodeName} &quot;${node.nodeValue}&quot;`;
            return ` &quot;${node.nodeName} ${node.nodeValue.slice(0,27)}&quot;...`;
        }
    }

    function debug_count(eventType) {
        if (document.querySelector(&quot;#debug&quot;).hidden)
            return;
        console.log(eventType);
        counts.set(eventType, (counts.get(eventType) ?? 0) + 1);
        const debug = document.querySelector(&quot;#debug_count&quot;);
        debug.textContent = &quot;&quot;;
        for (const [i, n] of counts)
            debug.textContent += (debug.textContent ? &quot;    &quot; : &quot;cou:&quot;)
                + ` • ${n} x ${i}\n`;
    }

    function debug_cha(message) {
        if (document.querySelector(&quot;#debug&quot;).hidden)
            return;
        const debug = document.querySelector(&quot;#debug_cha&quot;);
        debug.textContent = message;
    }

    checker.addEventListener(&quot;focus&quot;, ({target}) =&gt; {
        debug_count(&quot;focus&quot;);
        if (target.dataset.phase == &quot;fresh&quot;) {
            target.dataset.phase = &quot;spell&quot;;
            checkerTimer = setTimeout(finish, 250);
            const range = new Range;
            range.selectNodeContents(target.querySelector(&quot;[spellcheck]&quot;));
            getSelection().removeAllRanges();
            getSelection().addRange(range);
            if (this.internals)
                internals.setMarker(document, range, &quot;spelling&quot;);
        } else if (target.dataset.phase == &quot;spell&quot;) {
            clearTimeout(checkerTimer);
            checkerTimer = setTimeout(finish, 250);
        } else {
            finish();
        }

        function finish() {
            target.dataset.phase = &quot;done&quot;;
            checkerTimer = null;

            const selector = (() =&gt; {
                try {
                    return !document.querySelector(&quot;:not(*)::highlight(checker)&quot;);
                } catch (e) {}
                return false;
            })();
            const collection = !!(this.CSS &amp;&amp; CSS.highlights &amp;&amp; CSS.highlights.set);
            const ctor = !!this.Highlight;
            const staticRangesOnly = ctor ? (() =&gt; {
                try {
                    const range = new Range;
                    range.selectNodeContents(document.body);
                    return !new Highlight(range);
                } catch (e) {}
                try {
                    const range = new StaticRange({
                        startOffset: 0, endOffset: 0,
                        startContainer: document.body,
                        endContainer: document.body,
                    });
                    return !!new Highlight(range);
                } catch (e) {}
                return null;
            })() : null;
            const ctorTakesExactlyOneRange = ctor ? (() =&gt; {
                try {
                    const foo = new StaticRange({
                        startOffset: 0, endOffset: 0,
                        startContainer: document.body,
                        endContainer: document.body,
                    });
                    const bar = new StaticRange({
                        startOffset: 1, endOffset: 1,
                        startContainer: document.body,
                        endContainer: document.body,
                    });
                    switch (new Highlight(foo, bar).size) {
                        case 1: return true;
                        case 2: return false;
                    }
                } catch (e) {}
                return null;
            })() : null;

            checker.querySelector(&quot;._cha&quot;).textContent = (() =&gt; {
                if (selector &amp;&amp; collection &amp;&amp; ctor &amp;&amp; !staticRangesOnly &amp;&amp; !ctorTakesExactlyOneRange)
                    return &quot;yes&quot;;
                if (!selector || !collection || !ctor) {
                    let result = &quot;no (&quot;;
                    result += !selector ? &quot;S&quot; : &quot;&quot;;
                    result += !collection ? &quot;C&quot; : &quot;&quot;;
                    result += !ctor ? &quot;H&quot; : &quot;&quot;;
                    return result + &quot;)&quot;;
                }
                if (staticRangesOnly || ctorTakesExactlyOneRange) {
                    let result = &quot;buggy (&quot;;
                    result += staticRangesOnly ? &quot;a&quot; : &quot;&quot;;
                    result += ctorTakesExactlyOneRange ? &quot;b&quot; : &quot;&quot;;
                    return result + &quot;)&quot;;
                }
            })();

            try {
                if (this.CSS &amp;&amp; CSS.highlights) {
                    const hop = new StaticRange({
                        startOffset: 0, endOffset: 2,
                        startContainer: checker.querySelector(&quot;._hop&quot;),
                        endContainer: checker.querySelector(&quot;._hop&quot;),
                    });
                    const custom = new StaticRange({
                        startOffset: 0, endOffset: 2,
                        startContainer: checker.querySelector(&quot;._custom&quot;),
                        endContainer: checker.querySelector(&quot;._custom&quot;),
                    });
                    const hih = new StaticRange({
                        startOffset: 0, endOffset: 2,
                        startContainer: checker.querySelector(&quot;._hih&quot;),
                        endContainer: checker.querySelector(&quot;._hih&quot;),
                    });

                    CSS.highlights.set(&quot;lower&quot;, new Highlight(hop));

                    // work around Safari bug where ctor takes exactly one range
                    // (beware that having hop highlighted by lower but not by
                    // checker causes false “yes”, because Safari does not seem
                    // to support ‘-webkit-text-fill-color’ in highlights)
                    const h = new Highlight(hop, custom, hih);
                    CSS.highlights.set(&quot;checker&quot;, h);
                    if (CSS.highlights.get(&quot;checker&quot;).size == 1) {
                        h.add(custom);
                        h.add(hih);
                    }
                }
            } catch (e) {
                debug_cha(&quot;ex: &quot; + e + &quot;\n&quot; + e.stack + &quot;\n&quot; + this.Highlight);
            }
            fixCheckerSelectionIfNeeded();
        }
    });

    checker.addEventListener(&quot;blur&quot;, ({target}) =&gt; {
        if (target.dataset.phase == &quot;done&quot;)
            getSelection().removeAllRanges();
    });

    checker.addEventListener(&quot;click&quot;, ({target}) =&gt; {
        if (target.dataset.phase != &quot;done&quot;)
            return;
        debug_count(&quot;click&quot;);
        fixCheckerSelectionIfNeeded();
    });

    checker.addEventListener(&quot;beforeinput&quot;, event =&gt; {
        event.preventDefault();
    });

    document.addEventListener(&quot;selectionchange&quot;, event =&gt; {
        const now = performance.now();
        const front = selectionchangeTimes.shift();
        selectionchangeTimes.push(now);
        if (now - front &lt; 1000)
            return;
        debug_count(&quot;selectionchange&quot;);
        if (checker.dataset.phase != &quot;done&quot;)
            return;
        debug_active();
        if (document.activeElement != checker)
            return;
        fixCheckerSelectionIfNeeded();
    });

    function fixCheckerSelectionIfNeeded() {
        debug_count(&quot;fix&quot;);
        const row = checker.querySelector(&quot;._his&quot;);
        const sel = getSelection();
        let anchorOk = false, focusOk = false;
        for (let node = row; node != null; node = node.firstChild)
            if (sel.anchorNode == node &amp;&amp; sel.anchorOffset == 0)
                anchorOk = true;
        for (let node = row; node != null; node = node.lastChild)
            if (sel.focusNode == node &amp;&amp; sel.focusOffset == (
                    node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length))
                focusOk = true;
        if (anchorOk &amp;&amp; focusOk)
            return;
        debug_selection();
        const his = new Range;
        his.selectNodeContents(row);
        getSelection().removeAllRanges();
        getSelection().addRange(his);
    }
&lt;/script&gt;

&lt;h2 id=&quot;how-do-i-use-them&quot;&gt;How do I use them?&lt;/h2&gt;

&lt;p&gt;While you can write rules for highlight pseudos that target all elements, as was commonly done for pre-standard ::selection, selecting specific elements can be more powerful, allowing descendants to cleanly override highlight styles.&lt;/p&gt;

&lt;figure&gt;
  &lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;flex column_bag&quot;&gt;
      &lt;div class=&quot;_example&quot; style=&quot;width: max-content; font-size: 2em; color: white;&quot;&gt;
    &lt;span style=&quot;color: white; background: black;&quot;&gt;the fox jumps over the dog&lt;/span&gt;
    &lt;div&gt;
        &lt;span style=&quot;color: white; background: darkred;&quot;&gt;(the &lt;/span&gt;&lt;sup style=&quot;color: white; background: darkred;&quot;&gt;quick&lt;/sup&gt;&lt;span style=&quot;color: white; background: darkred;&quot;&gt; fox, mind you)&lt;/span&gt;
    &lt;/div&gt;
&lt;/div&gt;
      &lt;div class=&quot;gap&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;:root::selection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;black&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;aside&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;::selection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;darkred&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;the fox jumps over the dog
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;aside&amp;gt;&lt;/span&gt;
        (the &lt;span class=&quot;nt&quot;&gt;&amp;lt;sup&amp;gt;&lt;/span&gt;quick&lt;span class=&quot;nt&quot;&gt;&amp;lt;/sup&amp;gt;&lt;/span&gt; fox, mind you)
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/aside&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;Previously the same code would yield…&lt;/p&gt;

&lt;figure&gt;
  &lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;_example&quot; style=&quot;width: max-content; font-size: 2em; color: white;&quot;&gt;
    &lt;span style=&quot;color: white; background: var(--cr-highlight);&quot;&gt;the fox jumps over the dog&lt;/span&gt;
    &lt;div&gt;
        &lt;span style=&quot;color: white; background: darkred;&quot;&gt;(the &lt;/span&gt;&lt;sup style=&quot;color: white; background: var(--cr-highlight);&quot;&gt;quick&lt;/sup&gt;&lt;span style=&quot;color: white; background: darkred;&quot;&gt; fox, mind you)&lt;/span&gt;
    &lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;figcaption&gt;
    &lt;p&gt;(in older browsers)&lt;/p&gt;

    &lt;p&gt;Notice how &lt;em&gt;none&lt;/em&gt; of the text is white on black, because there are always other elements (body, p, aside, sup) between the root and the text.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;…unless you also selected the descendants of :root and aside:&lt;/p&gt;

&lt;figure&gt;
  &lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;:root::selection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;:root&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;::selection&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;/* (or just ::selection) */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;black&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;aside&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;::selection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;aside&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;::selection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;green&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;Note that a bare ::selection rule still means *::selection, and like any universal rule, it can interfere with inheritance when mixed with non-universal highlight rules.&lt;/p&gt;

&lt;figure&gt;
  &lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;flex column_bag&quot;&gt;
      &lt;div class=&quot;_example&quot; style=&quot;width: max-content; font-size: 2em; color: white;&quot;&gt;
    &lt;span style=&quot;color: white; background: black;&quot;&gt;the fox jumps over the dog&lt;/span&gt;
    &lt;div&gt;
        &lt;span style=&quot;color: white; background: darkred;&quot;&gt;(the &lt;/span&gt;&lt;sup style=&quot;color: white; background: black;&quot;&gt;quick&lt;/sup&gt;&lt;span style=&quot;color: white; background: darkred;&quot;&gt; fox, mind you)&lt;/span&gt;
    &lt;/div&gt;
&lt;/div&gt;
      &lt;div class=&quot;gap&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;::selection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;black&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;aside&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;::selection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;darkred&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;the fox jumps over the dog
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;aside&amp;gt;&lt;/span&gt;
        (the &lt;span class=&quot;nt&quot;&gt;&amp;lt;sup&amp;gt;&lt;/span&gt;quick&lt;span class=&quot;nt&quot;&gt;&amp;lt;/sup&amp;gt;&lt;/span&gt; fox, mind you)
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/aside&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;figcaption&gt;
    &lt;p&gt;sup::selection &lt;em&gt;would have&lt;/em&gt; inherited ‘darkred’ from aside::selection, but the universal ::selection rule matches it directly, so it becomes black.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;::selection is primarily controlled by user input, though pages can both read and write the active ranges via the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Selection&quot;&gt;Selection&lt;/a&gt; API with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection&quot;&gt;getSelection()&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;::target-text is activated by navigating to a URL ending in a &lt;a href=&quot;https://wicg.github.io/scroll-to-text-fragment/#the-fragment-directive&quot;&gt;fragment directive&lt;/a&gt;, which has its own syntax embedded in the #fragment. For example:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#foo:~:text=bar&lt;/code&gt; targets #foo and highlights the first occurrence of “bar”&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#:~:text=the,dog&lt;/code&gt; highlights the first range of text from “the” to “dog”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;::spelling-error and ::grammar-error are controlled by the user’s spell checker, which is only used where the user can input text, such as with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;textarea&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;contenteditable&lt;/code&gt;,
subject to the &lt;a href=&quot;https://html.spec.whatwg.org/multipage/interaction.html#attr-spellcheck&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spellcheck&lt;/code&gt;&lt;/a&gt; attribute (which also affects grammar checking).
For privacy reasons, pages can’t read the active ranges of these highlights, despite being visible to the user.&lt;/p&gt;

&lt;p&gt;::highlight() is controlled via the &lt;a href=&quot;https://drafts.csswg.org/css-highlight-api-1/#highlight&quot;&gt;Highlight&lt;/a&gt; API with &lt;a href=&quot;https://drafts.csswg.org/css-highlight-api-1/#intro-ex&quot;&gt;CSS.highlights&lt;/a&gt;.
CSS.highlights is a &lt;em&gt;maplike&lt;/em&gt; object, which means the interface is the same as a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map&quot;&gt;Map&lt;/a&gt; of strings (highlight names) to Highlight objects.
Highlight objects, in turn, are &lt;em&gt;setlike&lt;/em&gt; objects, which you can use like a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set&quot;&gt;Set&lt;/a&gt; of &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Range&quot;&gt;Range&lt;/a&gt; or &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/StaticRange&quot;&gt;StaticRange&lt;/a&gt; objects.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;flex column_bag&quot;&gt;
      &lt;div class=&quot;_example&quot; style=&quot;width: max-content; font-size: 3em;&quot;&gt;
    &lt;span style=&quot;background: yellow;&quot;&gt;Hello&lt;/span&gt;, world!
&lt;/div&gt;
      &lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;::highlight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;yellow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;foo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Highlight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;CSS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;highlights&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// maplike&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;range&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setStart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;firstChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setEnd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;firstChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// setlike&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;Hello, world!&lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;You can use &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle&quot;&gt;getComputedStyle()&lt;/a&gt; to query resolved highlight styles under a particular element.
Regardless of which parts (if any) are highlighted, the styles returned are as if the given highlight is active and all other highlights are inactive.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;::selection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#00FF00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;::highlight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#FF00FF&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;getSelection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;removeAllRanges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;getSelection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;selectAllChildren&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;style&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getComputedStyle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;::highlight(foo)&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;backgroundColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;Hello, world!&lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;figcaption&gt;
    &lt;p&gt;This code always prints “rgb(255, 0, 255)”, even though only ::selection is active.&lt;/p&gt;
  &lt;/figcaption&gt;&lt;/figure&gt;

&lt;h2 id=&quot;how-do-they-work&quot;&gt;How do they work?&lt;/h2&gt;

&lt;p&gt;Highlight pseudos are defined as pseudo-elements, but they actually have very little in common with other pseudo-elements like ::before and ::first-line.&lt;/p&gt;

&lt;p&gt;Unlike other pseudos, they generate &lt;em&gt;highlight overlays&lt;/em&gt;, not boxes, and these overlays are like layers over the original content.
Where text is highlighted, a highlight overlay can add backgrounds and text shadows, while the text proper and any other decorations are “lifted” to the very top.&lt;/p&gt;

&lt;style&gt;@import url(/images/hpdemo.css);&lt;/style&gt;

&lt;script src=&quot;/images/hpdemo.js&quot;&gt;&lt;/script&gt;

&lt;figure&gt;&lt;div class=&quot;_demo _hpdemo&quot; data-_demo=&quot;_hpdemo&quot; style=&quot;--w: var(--inner-width); user-select: none; cursor: pointer;&quot;&gt;
    &lt;script type=&quot;text/x-choreography&quot;&gt;
        q   q   q   q   q   q
        0   1   2   2   3   3
    &lt;/script&gt;
    &lt;div&gt;&lt;main style=&quot;--n: 7;&quot;&gt;
        &lt;div class=&quot;q&quot; style=&quot;outline: 3px dotted #00000070; background: #70700038;&quot;&gt;
            &lt;span&gt;quikc brown&lt;span style=&quot;color: initial;&quot;&gt; fox&lt;/span&gt;&lt;/span&gt;
            &lt;label&gt;originating element&lt;/label&gt;
        &lt;/div&gt;
        &lt;div class=&quot;q&quot; style=&quot;outline: 3px dotted #00000070; background: #A8000038;&quot;&gt;
            &lt;span&gt;&lt;span style=&quot;color: initial; text-decoration: underline; text-decoration-style: wavy; text-decoration-color: red;&quot;&gt;qui&lt;/span&gt;kc brown fox&lt;/span&gt;
            &lt;label&gt;::spelling-error&lt;/label&gt;
        &lt;/div&gt;
        &lt;div class=&quot;q&quot; style=&quot;outline: 3px dotted #00000070; background: #66339938;&quot;&gt;
            &lt;span&gt;quikc &lt;span style=&quot;background: #D070D0C0;&quot;&gt;br&lt;span&gt;own&lt;/span&gt;&lt;/span&gt; fox&lt;/span&gt;
            &lt;label&gt;::target-text&lt;/label&gt;
        &lt;/div&gt;
        &lt;div class=&quot;q&quot;&gt;
            &lt;span&gt;quikc &lt;span&gt;br&lt;span style=&quot;color: initial;&quot;&gt;own&lt;/span&gt;&lt;/span&gt; fox&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;q&quot; style=&quot;outline: 3px dotted #00000070; background: #3838C038;&quot;&gt;
            &lt;span&gt;qui&lt;span style=&quot;background: #3838C0C0;&quot;&gt;&lt;span&gt;kc&lt;/span&gt; br&lt;/span&gt;own fox&lt;/span&gt;
            &lt;label&gt;::selection&lt;/label&gt;
        &lt;/div&gt;
        &lt;div class=&quot;q&quot;&gt;
            &lt;span&gt;qui&lt;span style=&quot;color: initial;&quot;&gt;&lt;span style=&quot;text-decoration: underline; text-decoration-style: wavy; text-decoration-color: red;&quot;&gt;kc&lt;/span&gt; br&lt;/span&gt;own fox&lt;/span&gt;
        &lt;/div&gt;
    &lt;/main&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;script&gt;
    const hpdemo = {
        update() {
            const t = this.tFunction();
            if (t == this.t) return;
            this.t = t;
            this.state = _hpdemo(this.state, this.root, this.t);
        },
        tFunction() {
            if (hpdemo.clicked) return 1 - hpdemo.t;
            const rect = this.root.getBoundingClientRect();
            const y = rect.top + (rect.bottom - rect.top) / 2;
            return Number(y &lt; innerHeight / 2);
        },
        state: {},
        root: document.querySelector(&quot;._hpdemo&quot;),
        t: null,
        clicked: false,
    };
    hpdemo.update();
    addEventListener(&quot;scroll&quot;, () =&gt; {
        if (hpdemo.clicked) return;
        hpdemo.update();
    });
    hpdemo.root.addEventListener(&quot;click&quot;, () =&gt; {
        hpdemo.clicked = true;
        hpdemo.update();
    });
&lt;/script&gt;

&lt;p&gt;You can think of highlight pseudos as &lt;em&gt;innermost&lt;/em&gt; pseudo-elements that always exist at the bottom of any tree of elements and other pseudos, but unlike other pseudos, they don’t inherit their styles from that element tree.&lt;/p&gt;

&lt;p&gt;Instead each highlight pseudo forms its own inheritance tree, parallel to the element tree.
This means body::selection inherits from html::selection, not from ‘body’ itself.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;At this point, you can probably see that the highlight pseudos are quite different from the rest of CSS, but there are also several special cases and rules needed to make them a coherent system.&lt;/p&gt;

&lt;p&gt;For the typical appearance of &lt;span class=&quot;_spelling&quot;&gt;spelling&lt;/span&gt; and &lt;span class=&quot;_grammar&quot;&gt;grammar&lt;/span&gt; errors, highlight pseudos need to be able to add their own decorations, and they need to be able to leave the underlying foreground color unchanged.
Highlight inheritance happens separately from the element tree, so we need some way to refer to the underlying foreground color.&lt;/p&gt;

&lt;p&gt;That escape hatch is to set ‘color’ itself to ‘currentColor’, which is the default if nothing in the highlight tree sets ‘color’.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;flex column_bag&quot;&gt;
      &lt;div class=&quot;_example&quot; style=&quot;width: max-content; font-size: 3em;&quot;&gt;
    quick → &lt;span class=&quot;_spelling&quot;&gt;quikc&lt;/span&gt;
    &lt;br /&gt;
    &lt;span style=&quot;color: rebeccapurple;&quot;&gt;quick → &lt;span class=&quot;_spelling&quot;&gt;quikc&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;
      &lt;div class=&quot;gap&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;:root::spelling-error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;/* color: currentColor; */&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;text-decoration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;red&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wavy&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;underline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;aside&gt;
  &lt;p&gt;This is a bit of a special case within a special case.&lt;/p&gt;

  &lt;p&gt;You see, ‘currentColor’ is usually defined as “the computed value of ‘color’”, but the way I like to think of it is “don’t change the foreground color”, and most color-valued properties like ‘text-decoration-color’ default to this value.&lt;/p&gt;

  &lt;p&gt;For ‘color’ itself that wouldn’t make sense, so we instead define ‘color:currentColor’ as equivalent to ‘color:inherit’, which still fits that mental model.
But for highlights, that definition would no longer fit, so we redefine it as being the ‘color’ of the next active highlight below.&lt;/p&gt;
&lt;/aside&gt;

&lt;p&gt;To make highlight inheritance actually useful for &lt;span class=&quot;_spelling&quot;&gt;‘text-decoration’&lt;/span&gt; and &lt;span style=&quot;background: yellow;&quot;&gt;‘background-color’&lt;/span&gt;, &lt;em&gt;all properties are inherited&lt;/em&gt; in highlight styles, even those that are not usually inherited.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;flex row_bag&quot;&gt;
      &lt;div class=&quot;_example&quot; style=&quot;width: max-content; font-size: 3em;&quot;&gt;
    &lt;sup style=&quot;background-color: yellow;&quot;&gt;quick&lt;/sup&gt;&lt;span style=&quot;background-color: yellow;&quot;&gt; fox&lt;/span&gt;
&lt;/div&gt;
      &lt;div class=&quot;gap&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;aside&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;::selection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;yellow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;aside&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;sup&amp;gt;&lt;/span&gt;quick&lt;span class=&quot;nt&quot;&gt;&amp;lt;/sup&amp;gt;&lt;/span&gt; fox
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/aside&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;&lt;a name=&quot;applicable-properties&quot;&gt;&lt;/a&gt;Only a &lt;a href=&quot;https://drafts.csswg.org/css-pseudo/#highlight-styling&quot;&gt;handful of properties&lt;/a&gt; are settable in highlight styles, for performance and privacy reasons.
In general, properties that you can’t set in highlight styles come from the &lt;em&gt;originating element&lt;/em&gt;, which is to say from the non-highlight styles.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;flex row_bag&quot;&gt;
      &lt;div class=&quot;_example&quot; style=&quot;width: max-content; font-size: 3em; padding-right: 0.5em;&quot;&gt;
    &lt;span style=&quot;text-shadow: 0.25em 0.25lh lightblue;&quot;&gt;A&lt;/span&gt;&lt;big style=&quot;font-size: 2em; line-height: 1.5; text-shadow: 0.25em 0.25lh lightblue;&quot;&gt; A&lt;/big&gt;
&lt;/div&gt;
      &lt;div class=&quot;gap&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;:root::selection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;text-shadow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.25em&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.25&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lh&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;lightblue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;transparent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;big&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2em&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;line-height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;aside&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;sup&amp;gt;&lt;/span&gt;quick&lt;span class=&quot;nt&quot;&gt;&amp;lt;/sup&amp;gt;&lt;/span&gt; fox
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/aside&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;&lt;figcaption&gt;
    &lt;p&gt;When selected, the &amp;lt;big&amp;gt; is still 2em/1, and the selection shadow takes that into account, even though you are not allowed to set ‘font-size’ or ‘line-height’ in :root::selection.&lt;/p&gt;
  &lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;This would conflict with the usual rules&lt;sup id=&quot;fnref:3&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; for decorating boxes, because descendants would get two decorations, one propagated and one inherited.
We resolved this by making decorations added by highlights not propagate to any descendants.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;flex row_bag&quot;&gt;
      &lt;div class=&quot;_example&quot; style=&quot;width: max-content; font-size: 3em;&quot;&gt;
    &lt;div style=&quot;position: relative; color: transparent;&quot;&gt;
        &lt;div style=&quot;position: absolute; bottom: 0; text-decoration: underline; text-decoration-color: blue; text-decoration-thickness: 0.25rem; text-decoration-skip: none; text-decoration-skip-ink: none;&quot;&gt;
            &lt;span style=&quot;font-size: 0.75em;&quot;&gt;quick&lt;/span&gt; fox
        &lt;/div&gt;
        &lt;div style=&quot;position: absolute; bottom: 0; color: CanvasText;&quot;&gt;
            &lt;sup&gt;quick&lt;/sup&gt; fox
        &lt;/div&gt;
        &lt;!-- sizer --&gt;
        &lt;sup&gt;quick&lt;/sup&gt; fox
    &lt;/div&gt;
    &lt;div&gt;
        &lt;sup class=&quot;_spelling&quot;&gt;quikc&lt;/sup&gt; &lt;span class=&quot;_spelling&quot;&gt;fxo&lt;/span&gt;
    &lt;/div&gt;
&lt;/div&gt;
      &lt;div class=&quot;gap&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;.blue&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;text-decoration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;blue&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;underline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;:root::spelling-error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;text-decoration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;red&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wavy&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;underline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;blue&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;sup&amp;gt;&lt;/span&gt;quick&lt;span class=&quot;nt&quot;&gt;&amp;lt;/sup&amp;gt;&lt;/span&gt; fox
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;contenteditable&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;spellcheck&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;lang=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;en&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;sup&amp;gt;&lt;/span&gt;quikc&lt;span class=&quot;nt&quot;&gt;&amp;lt;/sup&amp;gt;&lt;/span&gt; fxo
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;&lt;figcaption&gt;
    &lt;p&gt;The blue decoration &lt;em&gt;propagates&lt;/em&gt; to the sup element from the decorating box, so there should be a single line at the normal baseline.
On the other hand, the spelling decoration is &lt;em&gt;inherited&lt;/em&gt; by sup::spelling-error, so there should be separate lines for “quikc” and “fxo” at their respective baselines.&lt;/p&gt;
  &lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;Unstyled highlight pseudos generally don’t change the appearance of the original content, so the default ‘color’ and ‘background-color’ in highlights are ‘currentColor’ and ‘transparent’ respectively, the latter being the property’s initial value.
But two highlight pseudos, ::selection and ::target-text, have UA default foreground and background colors.&lt;/p&gt;

&lt;p&gt;For compatibility with ::selection in older browsers, the UA default ‘color’ and ‘background-color’ (e.g. white on blue) is only used if &lt;em&gt;neither&lt;/em&gt; were set by the author.
This rule is known as &lt;em&gt;paired cascade&lt;/em&gt;, and for consistency it also applies to ::target-text.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;&lt;div class=&quot;flex&quot;&gt;&lt;table class=&quot;_sum&quot;&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;span style=&quot;color: white; background: var(--cr-highlight);&quot;&gt;default on default&lt;/span&gt;&lt;span style=&quot;color: rebeccapurple;&quot;&gt; plus more text&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;+&lt;/td&gt;&lt;td&gt;
            &lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rebeccapurple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;::selection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;yellow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;            &lt;/div&gt;
          &lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;=&lt;/td&gt;&lt;td&gt;&lt;span style=&quot;color: rebeccapurple; background: yellow;&quot;&gt;currentColor on yellow&lt;/span&gt;&lt;span style=&quot;color: rebeccapurple;&quot;&gt; plus more text&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;It’s common for selected text to almost invert the original text colors, turning &lt;span style=&quot;color: black; background: white;&quot;&gt;black on white&lt;/span&gt; into &lt;span style=&quot;color: white; background: var(--cr-highlight);&quot;&gt;white on blue&lt;/span&gt;, for example.
To guarantee that the original decorations remain as legible as the text when highlighted, which is especially important for decorations with semantic meaning (e.g. &lt;span style=&quot;text-decoration: line-through;&quot;&gt;line-through&lt;/span&gt;), originating decorations are recolored to the highlight ‘color’.
This doesn’t apply to decorations added by highlights though, because that would break the typical appearance of &lt;span class=&quot;_spelling&quot;&gt;spelling&lt;/span&gt; and &lt;span class=&quot;_grammar&quot;&gt;grammar&lt;/span&gt; errors.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;flex column_bag&quot;&gt;
      &lt;div class=&quot;_example&quot; style=&quot;width: max-content; font-size: 3em;&quot;&gt;
    &lt;div&gt;
        do
        &lt;span style=&quot;text-decoration: line-through; text-decoration-color: darkred;&quot;&gt;not&lt;/span&gt;
        buy bread
    &lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: white; background: darkblue;&quot;&gt;
        do
        &lt;span style=&quot;text-decoration: line-through;&quot;&gt;not&lt;/span&gt;
        buy bread
    &lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
      &lt;div class=&quot;gap&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;del&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;text-decoration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;darkred&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;line-through&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;::selection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;darkblue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    do &lt;span class=&quot;nt&quot;&gt;&amp;lt;del&amp;gt;&lt;/span&gt;not&lt;span class=&quot;nt&quot;&gt;&amp;lt;/del&amp;gt;&lt;/span&gt; buy bread
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;&lt;figcaption&gt;
    &lt;p&gt;This line-through decoration becomes white like the rest of the text when selected, even though it was explicitly set to ‘darkred’ in the original content.&lt;/p&gt;
  &lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;The default style rules for highlight pseudos might look something like this.
Notice the new ‘spelling-error’ and ‘grammar-error’ decorations, which authors can use to imitate native spelling and grammar errors.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;:root::selection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Highlight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HighlightText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;:root::target-text&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Mark&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MarkText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;:root::spelling-error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;text-decoration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spelling-error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;:root::grammar-error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;text-decoration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grammar-error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;figcaption&gt;
    &lt;p&gt;This doesn’t completely describe ::selection and ::target-text, due to paired cascade.&lt;/p&gt;
  &lt;/figcaption&gt;&lt;/figure&gt;

&lt;hr /&gt;

&lt;p&gt;The way the highlight pseudos have been designed naturally leads to some limitations.&lt;/p&gt;

&lt;h2 id=&quot;gotchas&quot;&gt;Gotchas&lt;/h2&gt;

&lt;h3 id=&quot;removing-decorations-and-shadows&quot;&gt;Removing decorations and shadows&lt;/h3&gt;

&lt;p&gt;Older browsers with ::selection tend to treat it purely as a way to &lt;em&gt;change&lt;/em&gt; the original content’s styles, including text shadows and other decorations.
&lt;a href=&quot;https://css-tricks.com/almanac/selectors/s/selection/&quot;&gt;Some tutorial content&lt;/a&gt; has even been written to that effect:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;One of the most helpful uses for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;::selection&lt;/code&gt; is turning off a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text-shadow&lt;/code&gt; during selection.
A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text-shadow&lt;/code&gt; can clash with the selection’s background color and make the text difficult to read.
Set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text-shadow: none;&lt;/code&gt; to make text clear and easy to read during selection.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Under the spec, highlight pseudos can no longer remove or really change the original content’s decorations and shadows.
Setting these properties in highlight pseudos to values other than ‘none’ &lt;em&gt;adds&lt;/em&gt; decorations and shadows to the overlays when they are active.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;del&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;text-decoration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;line-through&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;text-shadow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2px&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;red&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;::highlight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;undelete&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;text-decoration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;text-shadow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;figcaption&gt;
    &lt;p&gt;This code means that ::highlight(undelete) adds no decorations or shadows, not that it removes the line-through and red shadow when &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;del&lt;/code&gt; is highlighted.&lt;/p&gt;
  &lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;While the new :has() selector might appear to offer a solution to this problem, pseudo-element selectors are not allowed in :has(), at least not yet.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;del&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;:has&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;::highlight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;undelete&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;text-decoration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;text-shadow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;figcaption&gt;
    &lt;p&gt;This code does not work.&lt;/p&gt;
  &lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;Removing shadows that might clash with highlight backgrounds (as suggested in the tutorial above) will no longer be as necessary anyway, since highlight backgrounds now paint &lt;em&gt;on top of&lt;/em&gt; the original text shadows.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;flex row_bag&quot;&gt;
      &lt;div class=&quot;_example&quot; style=&quot;width: max-content; font-size: 2em; font-weight: bold; padding-right: 0.25em;&quot;&gt;
    &lt;div style=&quot;position: relative; color: transparent;&quot;&gt;
        &lt;div style=&quot;position: absolute; bottom: 0; text-shadow: 0.25em 0.25em magenta;&quot;&gt;
            &lt;span style=&quot;color: white; background: var(--cr-highlight);&quot;&gt;Faultlore&lt;/span&gt;
        &lt;/div&gt;
        &lt;!-- sizer --&gt;
        Faultlore
    &lt;/div&gt;
    &lt;div style=&quot;position: relative; color: transparent;&quot;&gt;
        &lt;div style=&quot;position: absolute; bottom: 0; text-shadow: 0.25em 0.25em magenta;&quot;&gt;
            &lt;span style=&quot;color: white; background: var(--cr-highlight-aC0h);&quot;&gt;Faultlore&lt;/span&gt;
        &lt;/div&gt;
        &lt;!-- sizer --&gt;
        Faultlore
    &lt;/div&gt;
&lt;/div&gt;
      &lt;div class=&quot;gap&quot;&gt;&lt;/div&gt;
      &lt;p&gt;&lt;a href=&quot;https://gankra.github.io&quot;&gt;→&lt;/a&gt;&lt;/p&gt;
      &lt;div class=&quot;gap&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;_example&quot; style=&quot;width: max-content; font-size: 2em; font-weight: bold; padding-right: 0.25em;&quot;&gt;
    &lt;div style=&quot;position: relative; color: transparent;&quot;&gt;
        &lt;div style=&quot;position: absolute; bottom: 0; text-shadow: 0.25em 0.25em magenta;&quot;&gt;
            Faultlore
        &lt;/div&gt;
        &lt;div style=&quot;position: absolute; bottom: 0;&quot;&gt;
            &lt;span style=&quot;color: white; background: var(--cr-highlight);&quot;&gt;Faultlore&lt;/span&gt;
        &lt;/div&gt;
        &lt;!-- sizer --&gt;
        Faultlore
    &lt;/div&gt;
    &lt;div style=&quot;position: relative; color: transparent;&quot;&gt;
        &lt;div style=&quot;position: absolute; bottom: 0; text-shadow: 0.25em 0.25em magenta;&quot;&gt;
            Faultlore
        &lt;/div&gt;
        &lt;div style=&quot;position: absolute; bottom: 0;&quot;&gt;
            &lt;span style=&quot;color: white; background: var(--cr-highlight-aC0h);&quot;&gt;Faultlore&lt;/span&gt;
        &lt;/div&gt;
        &lt;!-- sizer --&gt;
        Faultlore
    &lt;/div&gt;
&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;If you still want to ensure those shadows don’t clash with highlights in older browsers, you can set ‘text-shadow’ to ‘none’, which is harmless in newer browsers.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;::selection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;text-shadow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;figcaption&gt;
    &lt;p&gt;This rule might be helpful for older browsers, but note that like any universal rule, it can interfere with inheritance of ‘text-shadow’ when combined with more specific rules.&lt;/p&gt;
  &lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;As for line decorations, if you’re really determined, you can work around this limitation by using ‘-webkit-text-fill-color’, &lt;a href=&quot;https://compat.spec.whatwg.org/#the-webkit-text-fill-color&quot;&gt;a standard property&lt;/a&gt; (believe it or not) that controls the foreground fill color of text&lt;sup id=&quot;fnref:4&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;::highlight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;undelete&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;transparent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;-webkit-text-fill-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CanvasText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;figcaption&gt;
    &lt;p&gt;This hack hides any original decorations (in visual media), because those decorations are recolored to the highlight ‘color’, but it might change the text color too.&lt;/p&gt;
  &lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;Fun fact: because of ‘-webkit-text-fill-color’ and &lt;a href=&quot;https://compat.spec.whatwg.org/#the-webkit-text-stroke&quot;&gt;its stroke-related siblings&lt;/a&gt;, it isn’t always possible for highlight pseudos to avoid changing the foreground colors of text, at least not without out-of-band knowledge of what those colors are.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;flex column_bag&quot;&gt;
      &lt;div class=&quot;flex column_bag&quot;&gt;
    &lt;div class=&quot;_example&quot; style=&quot;width: max-content; font-size: 3em; color: blue;&quot;&gt;
        the
        &lt;em style=&quot;-webkit-text-fill-color: yellow; -webkit-text-stroke: 1px green;&quot;&gt;
            quick
            fox
        &lt;/em&gt;
    &lt;/div&gt;
    &lt;div class=&quot;gap&quot;&gt;&lt;/div&gt;
    ↓
    &lt;div class=&quot;gap&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;_example&quot; style=&quot;width: max-content; font-size: 3em; color: blue;&quot;&gt;
        the
        &lt;em style=&quot;-webkit-text-fill-color: yellow; -webkit-text-stroke: 1px green;&quot;&gt;
            &lt;span class=&quot;_spelling&quot; style=&quot;-webkit-text-fill-color: currentColor; -webkit-text-stroke: 0 currentColor;&quot;&gt;quikc&lt;/span&gt;
            fox
        &lt;/em&gt;
    &lt;/div&gt;
&lt;/div&gt;
      &lt;div class=&quot;gap&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;blue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;em&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;-webkit-text-fill-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;yellow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;-webkit-text-stroke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1px&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;green&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;:root::spelling-error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;/* default styles */&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;currentColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;-webkit-text-fill-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;currentColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;-webkit-text-stroke-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;currentColor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;text-decoration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spelling-error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;em&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;::spelling-error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;/* styles needed to preserve text colors */&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;-webkit-text-fill-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;yellow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;-webkit-text-stroke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1px&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;green&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;&lt;figcaption&gt;
    &lt;p&gt;When a word in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;em&lt;/code&gt; is misspelled, it will become blue like the rest of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;p&lt;/code&gt;, unless the fill and stroke properties are set in ::spelling-error accordingly.&lt;/p&gt;
  &lt;/figcaption&gt;&lt;/figure&gt;

&lt;h3 id=&quot;accessing-global-constants&quot;&gt;Accessing global constants&lt;/h3&gt;

&lt;details&gt;
  &lt;summary&gt;&lt;strong&gt;Update (2024-04-29):&lt;/strong&gt; this section is no longer true (see &lt;a href=&quot;#custom-properties&quot;&gt;§ &lt;em&gt;Custom properties&lt;/em&gt;&lt;/a&gt;), but you can click here to read what I wrote originally.&lt;/summary&gt;

  &lt;p&gt;&lt;del&gt;Highlight pseudos also don’t automatically have access to custom properties set in the element tree, which can make things tricky if you have a design system that exposes a color palette via custom properties on :root.&lt;/del&gt;&lt;/p&gt;

  &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
      &lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;:root&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;py&quot;&gt;--primary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#420420&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;py&quot;&gt;--secondary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#C0FFEE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;py&quot;&gt;--accent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#663399&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;::selection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;--accent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;--secondary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;      &lt;/div&gt;
    &lt;/div&gt;&lt;figcaption&gt;
      &lt;p&gt;This code does not work.&lt;/p&gt;
    &lt;/figcaption&gt;&lt;/figure&gt;

  &lt;p&gt;&lt;del&gt;You can work around this by adding selectors for the necessary highlight pseudos to the rule defining the constants, or if the necessary highlight pseudos are unknown, by rewriting each constant as a custom @property rule.&lt;/del&gt;&lt;/p&gt;

  &lt;figure&gt;
    &lt;div class=&quot;scroll&quot;&gt;
      &lt;div class=&quot;flex&quot;&gt;
        &lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;:root&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;:root::selection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;py&quot;&gt;--primary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#420420&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;py&quot;&gt;--secondary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#C0FFEE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;py&quot;&gt;--accent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#663399&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
        &lt;div class=&quot;gap&quot;&gt;&lt;/div&gt;
        &lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;@property&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;--primary&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;py&quot;&gt;initial-value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#420420&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;py&quot;&gt;syntax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;inherits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@property&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;--secondary&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;py&quot;&gt;initial-value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#C0FFEE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;py&quot;&gt;syntax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;inherits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@property&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;--accent&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;py&quot;&gt;initial-value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#663399&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;py&quot;&gt;syntax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;inherits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/figure&gt;
&lt;/details&gt;

&lt;h3 id=&quot;custom-properties&quot;&gt;Custom properties&lt;/h3&gt;

&lt;p&gt;You can &lt;em&gt;use&lt;/em&gt; custom properties in highlight styles, but you will not be able to set or override them there.
Custom property values come from the nearest originating element.&lt;/p&gt;

&lt;p&gt;This is unfortunate, but allowing you to set custom properties in highlight styles broke a lot of existing content on the web (and existing advice on Stack Overflow).
For more details, see these posts by my colleague Stephen Chenney:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/schenney/the-css-highlight-inheritance-model/&quot;&gt;The CSS Highlight Inheritance Model&lt;/a&gt; (January 2024)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blogs.igalia.com/schenney/css-custom-properties-in-highlight-pseudos/&quot;&gt;CSS Custom Properties in Highlight Pseudos&lt;/a&gt; (April 2024)&lt;/li&gt;
  &lt;li&gt;(and the CSSWG issues, &lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/6641&quot;&gt;#6641&lt;/a&gt; and &lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/9909&quot;&gt;#9909&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;figure&gt;
  &lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;flex column_bag&quot;&gt;
      &lt;div class=&quot;_example&quot; style=&quot;width: max-content; font-size: 2em; color: black;&quot;&gt;
    &lt;span style=&quot;color: black; background: lightgreen;&quot;&gt;the fox jumps over the dog&lt;/span&gt;
    &lt;div&gt;
        &lt;span style=&quot;color: black; background: yellow;&quot;&gt;(the &lt;/span&gt;&lt;sup style=&quot;color: black; background: yellow;&quot;&gt;quick&lt;/sup&gt;&lt;span style=&quot;color: black; background: yellow;&quot;&gt; fox, mind you)&lt;/span&gt;
    &lt;/div&gt;
&lt;/div&gt;
      &lt;div class=&quot;gap&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;::selection&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;/* = *::selection (universal) */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c&quot;&gt;/* using --selection-color in ::selection is ok... */&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;--selection-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;/* 🙆‍♀️ */&lt;/span&gt;

        &lt;span class=&quot;c&quot;&gt;/* ...but you will no longer be allowed to set it! */&lt;/span&gt;
        &lt;span class=&quot;py&quot;&gt;--selection-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;red&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;/* 🙅‍♀️ */&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;py&quot;&gt;--selection-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;lightgreen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;aside&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;py&quot;&gt;--selection-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;yellow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;the fox jumps over the dog
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;aside&amp;gt;&lt;/span&gt;
        (the &lt;span class=&quot;nt&quot;&gt;&amp;lt;sup&amp;gt;&lt;/span&gt;quick&lt;span class=&quot;nt&quot;&gt;&amp;lt;/sup&amp;gt;&lt;/span&gt; fox, mind you)
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/aside&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/figure&gt;

&lt;h3 id=&quot;spec-issues&quot;&gt;Spec issues&lt;/h3&gt;

&lt;p&gt;While the design of the highlight pseudos has mostly settled, there are still some unresolved issues to watch out for.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;how to use spelling and grammar decorations with the UA default colors (&lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/7522&quot;&gt;#7522&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;values of non-applicable properties, e.g. ‘text-shadow’ with em units (&lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/7591&quot;&gt;#7591&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;the meaning of underline- and emphasis-related properties in highlights (&lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/7101&quot;&gt;#7101&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;whether ‘-webkit-text-fill-color’ and friends are allowed in highlights (&lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/7580&quot;&gt;#7580&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;some browsers “tweak” the colors or alphas set in highlight styles (&lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/6853&quot;&gt;#6853&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;how the highlight pseudos are supposed to interact with SVG (&lt;a href=&quot;https://github.com/w3c/svgwg/issues/894&quot;&gt;svgwg#894&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;what-now&quot;&gt;What now?&lt;/h2&gt;

&lt;p&gt;The highlight pseudos are a radical departure from older browsers with ::selection, and have some significant differences with CSS as we know it.
Now that we have some experimental support, we want &lt;em&gt;your&lt;/em&gt; help to play around with these features and help us make them as useful and ergonomic as possible before they’re set in stone.&lt;/p&gt;

&lt;p&gt;Special thanks to &lt;a href=&quot;https://twitter.com/regocas&quot;&gt;Rego&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/briankardell&quot;&gt;Brian&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/meyerweb&quot;&gt;Eric&lt;/a&gt; (Igalia), &lt;a href=&quot;https://twitter.com/frivoal&quot;&gt;Florian&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/fantasai&quot;&gt;fantasai&lt;/a&gt; (CSSWG), &lt;a href=&quot;https://twitter.com/ecbos_&quot;&gt;Emilio&lt;/a&gt; (Mozilla), and &lt;a href=&quot;https://twitter.com/dandclark1&quot;&gt;Dan&lt;/a&gt; for their work in shaping the highlight pseudos (and this post).
We would also like to thank &lt;a href=&quot;https://www.bloomberg.com/company/&quot;&gt;Bloomberg&lt;/a&gt; for sponsoring this work.&lt;/p&gt;

&lt;hr /&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Dan, Fernando, Sanket, Luis, Bo, and anyone else I missed. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;See &lt;a href=&quot;https://codepen.io/dazabani13/full/KKqzOJp&quot;&gt;this demo&lt;/a&gt; for more details. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:2:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/6829#issuecomment-1098255113&quot;&gt;CSSWG discussion&lt;/a&gt; also found that decorating box semantics are undesirable for decorations added by highlights anyway. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:4&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;This is actually the case everywhere the WHATWG compat spec applies, at all times. If you think about it, the only reason why setting ‘color’ to ‘red’ makes your text red is because ‘-webkit-text-fill-color’ defaults to ‘currentColor’. &lt;a href=&quot;#fnref:4&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name></name></author><category term="home" /><category term="igalia" /><summary type="html">A year and a half ago, I was asked to help upstream a Chromium patch allowing authors to recolor spelling and grammar errors in CSS. At the time, I didn’t realise that this was part of a far more ambitious effort to reimagine spelling errors, grammar errors, text selections, and more as a coherent system that didn’t yet exist as such in any browser. That system is known as the highlight pseudos, and this post will focus on the design of said system and its consequences for authors.</summary></entry><entry><title type="html">Chromium spelling and grammar, part 2</title><link href="https://www.azabani.com/2021/12/16/spelling-grammar-2.html" rel="alternate" type="text/html" title="Chromium spelling and grammar, part 2" /><published>2021-12-16T12:30:00+00:00</published><updated>2021-12-16T12:30:00+00:00</updated><id>https://www.azabani.com/2021/12/16/spelling-grammar-2</id><content type="html" xml:base="https://www.azabani.com/2021/12/16/spelling-grammar-2.html">&lt;p&gt;Modern web browsers can help users with their word processing needs by drawing squiggly lines under possible &lt;span class=&quot;_spelling&quot;&gt;spelling&lt;/span&gt; or &lt;span class=&quot;_grammar&quot;&gt;&lt;span&gt;grammar&lt;/span&gt;&lt;/span&gt; errors in their input.
CSS will give authors more control over when and how they appear, with the new ::spelling- and ::grammar-error pseudo-elements, and spelling- and grammar-error text decorations.
&lt;a href=&quot;/2021/05/17/spelling-grammar.html&quot;&gt;Since part 1&lt;/a&gt; in May, we’ve done a fair bit of work in both Chromium and the CSSWG towards making this possible.&lt;/p&gt;

&lt;style&gt;
article figure &gt; img { max-width: 100%; }
article figure &gt; figcaption { max-width: 30rem; margin-left: auto; margin-right: auto; }
article pre, article code { font-family: Inconsolata, monospace, monospace; }
article blockquote { max-width: 27rem; margin-inline: auto; }
article blockquote &gt; footer { text-align: right; }
article &gt; /* gross and fragile hack */ :not(img):not(hr):not(blockquote):before { width: 13em; display: block; overflow: hidden; content: &quot;&quot;; }
._demo { font-style: italic; font-weight: bold; color: rebeccapurple; }
._spelling, ._grammar { text-decoration-thickness: /* iOS takes 0 literally */ 1px; text-decoration-skip-ink: none; }
._spelling { text-decoration: /* not a shorthand on iOS */ underline; text-decoration-style: wavy; text-decoration-color: red; }
._grammar { text-decoration: /* not a shorthand on iOS */ underline; text-decoration-style: wavy; text-decoration-color: green; }
._table { font-size: 0.75em; }
._table td, ._table th { vertical-align: top; border: 1px solid black; }
._table td:not(._tight), ._table th:not(._tight) { padding: 0.5em; }
._tight picture, ._tight img { vertical-align: top; }
._compare * + *, ._tight * + *, ._gifs * + * { margin-top: 0; }
._compare { max-width: 100%; border: 1px solid rebeccapurple; }
._compare &gt; div { max-width: 100%; position: relative; touch-action: pinch-zoom; --cut: 50%; }
._compare &gt; div &gt; * { vertical-align: top; max-width: 100%; }
._compare &gt; div &gt; :nth-child(1) { position: absolute; clip: rect(auto, auto, auto, var(--cut)); }
._compare &gt; div &gt; :nth-child(2) { position: absolute; width: var(--cut); height: 100%; border-right: 1px solid rebeccapurple; }
._compare &gt; div &gt; :nth-child(2):before { content: var(--left-label); color: rebeccapurple; font-size: 0.75em; position: absolute; right: 0.5em; }
._compare &gt; div &gt; :nth-child(2):after { content: var(--right-label); color: rebeccapurple; font-size: 0.75em; position: absolute; left: calc(100% + 0.5em); }
._sum td:first-of-type { padding-right: 1em; }
._gifs { position: relative; display: flex; flex-flow: column nowrap; }
._gifs &gt; video { transition: opacity 0.125s linear; }
._gifs &gt; button { transition: 0.125s linear; transition-property: color, background-color; }
._gifs._paused &gt; video { opacity: 0.5; }
._gifs._paused &gt; button { color: rebeccapurple; background: #66339940; }
._gifs &gt; button { position: absolute; top: 0; bottom: 0; left: 0; right: 0; width: 100%; font-size: 7em; color: transparent; background: transparent; content: &quot;▶&quot;; }
._gifs &gt; button:focus-visible { outline: 0.25rem solid #663399C0; outline-offset: -0.25rem; }

._commits { position: relative; }
._commits &gt; :first-child { position: absolute; right: -0.1em; height: 100%; border-right: 0.2em solid rgba(102,51,153,0.5); }
._commits &gt; :last-child { position: relative; padding-right: 0.5em; }
* + ._commit, ._commit * + * { margin-top: 0; }
._commit { line-height: 2; margin-right: -1.5em; text-align: right; }
._commit &gt; img { width: 2em; vertical-align: middle; }
._commit &gt; a { padding-right: 0.5em; text-decoration: none; color: rebeccapurple; }
._commit &gt; a &gt; code { font-size: 1em; }
._commit-none &gt; a { color: rgba(102,51,153,0.5); }
&lt;/style&gt;

&lt;p&gt;The client funding this work had an internal patch that allowed you to change the colors of those squiggly lines, and our job was to upstream it.
The patch itself was pretty simple, but turning that into an upstream feature is a much bigger can of worms.
So far, we’ve landed over 30 patches, including dozens of new web platform tests, opened 8 spec issues, and run into some gnarly bugs going back to at least 2009.&lt;/p&gt;

&lt;p&gt;Check out &lt;a href=&quot;https://bucket.daz.cat/work/igalia/0/&quot;&gt;our project index&lt;/a&gt; for a complete list of demos, tests, patches, and issues.
For more details about the CSS highlight pseudos in particular, check out &lt;a href=&quot;https://www.youtube.com/watch?v=Vh2niGIqtOc&quot;&gt;my BlinkOn 15 talk&lt;/a&gt;, including the &lt;a href=&quot;https://bucket.daz.cat/work/igalia/0/29.html&quot;&gt;highlight painting visualiser&lt;/a&gt;.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;&lt;div class=&quot;flex&quot;&gt;
    &lt;iframe class=&quot;local-video&quot; width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube-nocookie.com/embed/Vh2niGIqtOc&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;&lt;/div&gt;&lt;figcaption&gt;
    (&lt;a class=&quot;_demo&quot; href=&quot;https://www.azabani.com/talks/2021-11-17-css-highlight-pseudos/&quot;&gt;slides&lt;/a&gt;)
&lt;/figcaption&gt;&lt;/figure&gt;

&lt;h2 id=&quot;contents&quot;&gt;Contents&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#impl-status&quot;&gt;Implementation status&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#charlie&quot;&gt;Charlie’s lawyerings&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#squiggly-lines&quot;&gt;Squiggly lines&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#platform-conventions&quot;&gt;Platform “conventions”&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#precise-wavy-decorations&quot;&gt;Precise wavy decorations&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#phase-locked-decorations&quot;&gt;Phase-locked decorations&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#bézier-bounding-box&quot;&gt;Bézier bounding box&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#cover-me&quot;&gt;Cover me!&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#highlight-inheritance&quot;&gt;Highlight inheritance&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#blink-style-101&quot;&gt;Blink style 101&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#blink-style-102&quot;&gt;How pseudo-elements work&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#status-quo&quot;&gt;Status quo&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#storing-highlight-styles&quot;&gt;Storing highlight styles&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#single-pass-resolution&quot;&gt;Single-pass resolution&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#multi-pass-resolution&quot;&gt;Multi-pass resolution&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#pathology-in-legacy&quot;&gt;Pathology in legacy&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#paired-cascade&quot;&gt;Paired cascade&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#fixing-tests&quot;&gt;Who’s got green?&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#what-now&quot;&gt;What now?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;impl-status&quot;&gt;Implementation status&lt;/h2&gt;

&lt;p&gt;Chromium 96 includes a rudimentary version of highlight inheritance, with support for ::highlight in Chromium 98 (&lt;a href=&quot;https://crrev.com/c/3237158&quot;&gt;Fernando Fiori&lt;/a&gt;).
This is currently behind a Blink feature:&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;--enable-blink-features=HighlightInheritance
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;Adding to our initial support for ::{spelling,grammar}-error, we’ve since made progress on the new {spelling,grammar}-error decorations.
While they are accepted but ignored in Chromium 96, you’ll be able to &lt;em&gt;see&lt;/em&gt; them in Chromium 98, with our early paint support.&lt;/p&gt;

&lt;p&gt;Chromium 96 also makes it possible to change the color of native squiggly lines by setting ‘text-decoration-color’ on either of the new pseudo-elements.
This feature, and the features above, are behind another flag:&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;--enable-blink-features=CSSSpellingGrammarErrors
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;h2 id=&quot;charlie&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=qcderLXiwa8&quot;&gt;C&lt;/a&gt;harlie’s &lt;del&gt;bird&lt;/del&gt; spec lawyerings&lt;/h2&gt;

&lt;p&gt;I’ve learned a lot of things while working on this project.
One interesting lesson was that no matter how clearly a feature is specified, and how much discussion goes into spec details, half the questions won’t become apparent until someone starts building it.&lt;/p&gt;

&lt;p&gt;&lt;img width=&quot;300&quot; height=&quot;300&quot; src=&quot;/images/spammar2-charlie.jpg&quot; class=&quot;flight&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;What happens when both highlight and originating content define text shadows? What if multiple highlights do the same? What order do we paint these shadows in? (&lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/3932&quot;&gt;#3932&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;What happens to the originating content’s decorations when highlighted? What happens when highlights define their own decorations? Which decorations get recolored to the foreground color for clarity? What’s the painting order? Does it even mean anything for a highlight to set ‘text-decoration-color’ only? (&lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/6022&quot;&gt;#6022&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Some browsers invert the author’s ::selection background based on contrast with the foreground color. Should this be allowed, or does it do more harm than good? (&lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/6150&quot;&gt;#6150&lt;/a&gt;)
    &lt;ul&gt;
      &lt;li&gt;What about other “tweaks”? What if a browser needs to force translucency to make its selection highlighting &lt;em&gt;work&lt;/em&gt;? (&lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/6853&quot;&gt;#6853&lt;/a&gt;)&lt;/li&gt;
      &lt;li&gt;How do we even write reftests if they &lt;em&gt;are&lt;/em&gt; allowed? (no issue)&lt;/li&gt;
      &lt;li&gt;While we’re talking about testing, how do we even test ::{spelling,grammar}-error without a way to guarantee that some text is treated as an error? (&lt;a href=&quot;https://github.com/web-platform-tests/wpt/issues/30863&quot;&gt;wpt#30863&lt;/a&gt;)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;How does paired cascade work? Does “use” mean used value? Which properties are “highlight colors”? Do we really mean ::selection only, and color and background-color only? What does it mean for a highlight color to have been “specified by the author”? Does the user origin stylesheet count as “specified”? Do unset and revert count as “specified”? Does unset mean inherit even when the property is not normally inherited? (&lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/6386&quot;&gt;#6386&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Should custom properties be allowed? What about variable references? Do we force non-inherited custom properties to become inherited like we do for non-custom properties? Should we provide a better way to set custom properties in a way that affects highlight pseudos? (&lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/6264&quot;&gt;#6264&lt;/a&gt;, &lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/6641&quot;&gt;#6641&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;What if existing content relies on implicitly inheriting a highlight foreground color when setting background-color explicitly, or vice versa? Do we need to accommodate this for compat? (&lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/6774&quot;&gt;#6774&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;The spec effectively recommends that ::{spelling,grammar}-error (and &lt;em&gt;requires&lt;/em&gt; that ::highlight) force the text color to black by default. Surely we want to &lt;em&gt;not change&lt;/em&gt; the color by default? (&lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/6779&quot;&gt;#6779&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Does color:currentColor point to the next &lt;em&gt;active&lt;/em&gt; highlight overlay below, or are inactive highlights included too? What happens when the author tries to getComputedStyle with ::selection? (&lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/6818&quot;&gt;#6818&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Do decorations “propagate” to descendants in highlights like they would normally? How do we reconcile that with highlight inheritance? How do we ensure that “decorating box” semantics aren’t broken? (&lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/6829&quot;&gt;#6829&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;squiggly-lines&quot;&gt;Squiggly lines&lt;/h2&gt;

&lt;div class=&quot;_commits&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;

    &lt;p&gt;Since landing ‘text-decoration-color’ support for the new pseudos, my colleague Rego has taken the lead on the rest of the core spelling and grammar features, starting with the new ‘text-decoration-line’ values.&lt;/p&gt;

    &lt;p&gt;Currently, when setting ‘text-decoration-color’ on the pseudos, we change the color, but ‘text-decoration-line’ is still ‘none’, which doesn’t really make sense.
This might sound like it required gross hacks, but the style system just gives us a blob of properties, where ‘color’ and ‘line’ are independent.
All of the logic that &lt;em&gt;uses&lt;/em&gt; them is in paint and layout.&lt;/p&gt;

    &lt;div class=&quot;_commit&quot;&gt;&lt;a href=&quot;https://crrev.com/c/3162169&quot;&gt;&lt;code&gt;CL:3162169&lt;/code&gt;&lt;/a&gt;&lt;img width=&quot;40&quot; height=&quot;40&quot; src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

    &lt;p&gt;We started by adding the new values to the stylesheet parser.
While highlight painting still needs a lot more work before we can do so, the idea is that eventually the pseudos and decorations will meet in the default stylesheet.&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;::spelling-error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;text-decoration-line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spelling-error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;::grammar-error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;text-decoration-line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grammar-error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;&lt;/figure&gt;

    &lt;div class=&quot;_commit&quot;&gt;&lt;a href=&quot;https://crrev.com/c/3194336&quot;&gt;&lt;code&gt;CL:3194336&lt;/code&gt;&lt;/a&gt;&lt;img width=&quot;40&quot; height=&quot;40&quot; src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

    &lt;p&gt;Something that’s often neglected in CSS tests is &lt;em&gt;dynamic&lt;/em&gt; testing, which checks that the rendering updates correctly when styles are changed by JavaScript, since the easiest and most common way to write a rendering test involves no scripting at all.&lt;/p&gt;

    &lt;p&gt;In this case, only ::selection had dynamic tests, and only ::selection actually worked correctly, so we then fixed the other pseudos.&lt;/p&gt;

    &lt;div class=&quot;_commit&quot;&gt;&lt;a href=&quot;https://crrev.com/c/3177663&quot;&gt;&lt;code&gt;CL:3177663&lt;/code&gt;&lt;/a&gt;&lt;img width=&quot;40&quot; height=&quot;40&quot; src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

    &lt;h3 id=&quot;platform-conventions&quot;&gt;Platform “conventions”&lt;/h3&gt;

    &lt;p&gt;Blink’s squiggly lines look quite different to anything CSS can achieve with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wavy&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotted&lt;/code&gt; decorations, and they’re painted on unrelated codepaths (&lt;a href=&quot;/2021/05/17/spelling-grammar.html#cjk-css-unification&quot;&gt;more details&lt;/a&gt;).
We want to unify these codepaths, to make them easier to maintain and help us integrate them with CSS, but this creates a few complications.&lt;/p&gt;

    &lt;p&gt;The CSS codepath naïvely paints as many bézier curves as needed to span the necessary width, but the squiggly codepath has always painted a single rectangle with a cached texture, which is probably more efficient.
This texture used to be a hardcoded bitmap, but even when we made the decorations &lt;a href=&quot;https://codereview.chromium.org/2674003002&quot;&gt;scale with the user’s dpi&lt;/a&gt;, we still kept the same technique, so the approach we use for CSS decorations might be too slow.&lt;/p&gt;

    &lt;p&gt;Another question is the actual appearance of spelling and grammar decorations.
We don’t necessarily want to make them &lt;em&gt;identical&lt;/em&gt; to the default &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wavy&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotted&lt;/code&gt; decorations, because it might be nice to tell when, say, a wavy-decorated word is misspelled.&lt;/p&gt;

    &lt;p&gt;We also want to conform to platform conventions where possible, and you would think there’s at least a consistent convention for macOS… but not exactly.
One thing that’s clear is that gradients are no longer conventional.&lt;/p&gt;

    &lt;figure style=&quot;image-rendering: pixelated;&quot;&gt;
&lt;div class=&quot;scroll&quot;&gt;
&lt;table class=&quot;_table&quot;&gt;
    &lt;thead&gt;
        &lt;tr&gt;
            &lt;th colspan=&quot;4&quot;&gt;macOS (compare &lt;a class=&quot;_demo&quot; href=&quot;https://bucket.daz.cat/work/igalia/0/0.html?color=red&amp;amp;style=dotted&amp;amp;line=underline&amp;amp;thickness=3px&amp;amp;ink=none&quot;&gt;demo&lt;sub&gt;0&lt;/sub&gt;&lt;/a&gt;)&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td class=&quot;_tight&quot; style=&quot;vertical-align: bottom;&quot;&gt;&lt;a href=&quot;/images/spammar2-safari.png&quot;&gt;&lt;img width=&quot;170&quot; height=&quot;90&quot; src=&quot;/images/spammar2-safari.png&quot; /&gt;&lt;/a&gt;&lt;/td&gt;
            &lt;td class=&quot;_tight&quot; style=&quot;vertical-align: bottom;&quot;&gt;&lt;a href=&quot;/images/spammar2-notes.png&quot;&gt;&lt;img width=&quot;90&quot; height=&quot;39&quot; src=&quot;/images/spammar2-notes.png&quot; /&gt;&lt;/a&gt;&lt;/td&gt;
            &lt;td class=&quot;_tight&quot; style=&quot;vertical-align: bottom;&quot;&gt;&lt;a href=&quot;/images/spammar2-textedit.png&quot;&gt;&lt;img width=&quot;53&quot; height=&quot;28&quot; src=&quot;/images/spammar2-textedit.png&quot; /&gt;&lt;/a&gt;&lt;/td&gt;
            &lt;td class=&quot;_tight&quot; style=&quot;vertical-align: bottom;&quot;&gt;&lt;a href=&quot;/images/spammar2-keynote.png&quot;&gt;&lt;img width=&quot;96&quot; height=&quot;39&quot; src=&quot;/images/spammar2-keynote.png&quot; /&gt;&lt;br /&gt;&lt;img width=&quot;96&quot; height=&quot;39&quot; src=&quot;/images/spammar2-keynote@t.png&quot; /&gt;&lt;/a&gt;&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
    &lt;tfoot&gt;
        &lt;tr&gt;&lt;th&gt;Safari&lt;/th&gt;&lt;th&gt;Notes&lt;/th&gt;&lt;th&gt;TextEdit&lt;/th&gt;&lt;th&gt;Keynote&lt;/th&gt;&lt;/tr&gt;
    &lt;/tfoot&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/figure&gt;

    &lt;div class=&quot;_commit&quot;&gt;&lt;a href=&quot;https://crrev.com/c/3139819&quot;&gt;&lt;code&gt;CL:3139819&lt;/code&gt;&lt;/a&gt;&lt;img width=&quot;40&quot; height=&quot;40&quot; src=&quot;/images/badapple-commit-up.svg&quot; /&gt;&lt;/div&gt;

    &lt;p&gt;But anyway, if we’re adding new decoration values that mimic the native ones, which codepath do we paint them with?
We decided to go down the CSS route — leaving native squiggly lines untouched for now — and take this time to refactor and extend those decoration painters for the needs of spelling and grammar errors.&lt;/p&gt;

    &lt;div class=&quot;_commit _commit-none&quot;&gt;&lt;a href=&quot;https://crrev.com/c/3275457&quot;&gt;&lt;code&gt;CL:3275457&lt;/code&gt;&lt;/a&gt;&lt;img width=&quot;40&quot; height=&quot;40&quot; src=&quot;/images/badapple-commit-none.svg&quot; /&gt;&lt;/div&gt;

    &lt;div class=&quot;_commit _commit-none&quot;&gt;&lt;a href=&quot;https://crrev.com/c/3284869&quot;&gt;&lt;code&gt;CL:3284869&lt;/code&gt;&lt;/a&gt;&lt;img width=&quot;40&quot; height=&quot;40&quot; src=&quot;/images/badapple-commit-none.svg&quot; /&gt;&lt;/div&gt;

    &lt;div class=&quot;_commit _commit-none&quot;&gt;&lt;a href=&quot;https://crrev.com/c/3290417&quot;&gt;&lt;code&gt;CL:3290417&lt;/code&gt;&lt;/a&gt;&lt;img width=&quot;40&quot; height=&quot;40&quot; src=&quot;/images/badapple-commit-none.svg&quot; /&gt;&lt;/div&gt;

    &lt;div class=&quot;_commit _commit-none&quot;&gt;&lt;a href=&quot;https://crrev.com/c/3291658&quot;&gt;&lt;code&gt;CL:3291658&lt;/code&gt;&lt;/a&gt;&lt;img width=&quot;40&quot; height=&quot;40&quot; src=&quot;/images/badapple-commit-none.svg&quot; /&gt;&lt;/div&gt;

    &lt;div class=&quot;_commit&quot;&gt;&lt;a href=&quot;https://crrev.com/c/3297885&quot;&gt;&lt;code&gt;CL:3297885&lt;/code&gt;&lt;/a&gt;&lt;img width=&quot;40&quot; height=&quot;40&quot; src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

    &lt;h3 id=&quot;precise-wavy-decorations&quot;&gt;Precise wavy decorations&lt;/h3&gt;

    &lt;p&gt;To that end, one of the biggest improvements we’ve landed is making wavy decorations start and stop exactly where needed, rather than falling short.
This includes the new spelling and grammar decoration values, other than on macOS.&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;&lt;div class=&quot;flex&quot; style=&quot;flex-direction: column;&quot;&gt;&lt;div class=&quot;_gifs _paused&quot;&gt;
    &lt;!-- ffmpeg -y -video_size 384x216 -framerate 60 -f x11grab -i :0+15,307 \%03d.png --&gt;
    &lt;!-- convert -delay 2 *.png -layers Optimize +map foo.gif --&gt;
    &lt;!-- # can skip gif and 50/60 fps conversion with ffmpeg -pattern_type glob --&gt;
    &lt;!-- ( i=images/foo; ffmpeg -y -i $i.gif -vf &apos;setpts=50/60*PTS&apos; -r 60 -pix_fmt yuv420p -vcodec libx264 -crf 17 $i.mp4 ) --&gt;
    &lt;!-- ( i=images/foo; ffmpeg -y -i $i.gif -vf &apos;setpts=50/60*PTS&apos; -r 60 -pix_fmt yuv420p -vcodec libvpx -crf 10 -b:v 1M $i.webm ) --&gt;
    &lt;!-- &lt;img width=&quot;384&quot; height=&quot;216&quot; src=&quot;/images/spammar2-w0.gif&quot;&gt; --&gt;
    &lt;!-- &lt;img width=&quot;384&quot; height=&quot;216&quot; src=&quot;/images/spammar2-w1.gif&quot;&gt; --&gt;
    &lt;video loop=&quot;&quot; playsinline=&quot;&quot; tabindex=&quot;-1&quot; width=&quot;384&quot; height=&quot;216&quot; poster=&quot;/images/spammar2-w0.png&quot;&gt;&lt;source src=&quot;/images/spammar2-w0.mp4&quot; /&gt;&lt;source src=&quot;/images/spammar2-w0.webm&quot; /&gt;&lt;/video&gt;
    &lt;video loop=&quot;&quot; playsinline=&quot;&quot; tabindex=&quot;-1&quot; width=&quot;384&quot; height=&quot;216&quot; poster=&quot;/images/spammar2-w1.png&quot;&gt;&lt;source src=&quot;/images/spammar2-w1.mp4&quot; /&gt;&lt;source src=&quot;/images/spammar2-w1.webm&quot; /&gt;&lt;/video&gt;
    &lt;button type=&quot;button&quot; aria-label=&quot;play&quot;&gt;▶&lt;/button&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;figcaption&gt;
    Wavy decorations under ‘letter-spacing’, top version 96, bottom version 97 (&lt;a class=&quot;_demo&quot; href=&quot;https://bucket.daz.cat/work/igalia/0/0.html?color=%2300C000&amp;amp;style=wavy&amp;amp;line=underline&amp;amp;thickness=auto&amp;amp;ink=none&amp;amp;trySpellcheck=1&amp;amp;wm=horizontal-tb&amp;amp;marquee&amp;amp;overlay&quot;&gt;&lt;strong&gt;demo&lt;sub&gt;0&lt;/sub&gt;&lt;/strong&gt;&lt;/a&gt;).
&lt;/figcaption&gt;&lt;/figure&gt;

    &lt;div class=&quot;_commit&quot;&gt;&lt;a href=&quot;https://crrev.com/c/3237072&quot;&gt;&lt;code&gt;CL:3237072&lt;/code&gt;&lt;/a&gt;&lt;img width=&quot;40&quot; height=&quot;40&quot; src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

    &lt;div class=&quot;_commit _commit-none&quot;&gt;&lt;a href=&quot;https://crrev.com/c/3264203&quot;&gt;&lt;code&gt;CL:3264203&lt;/code&gt;&lt;/a&gt;&lt;img width=&quot;40&quot; height=&quot;40&quot; src=&quot;/images/badapple-commit-none.svg&quot; /&gt;&lt;/div&gt;

    &lt;p&gt;You may have noticed that the decorations in that last example sometimes extend to the right of “h”.
This is working as expected: ‘letter-spacing’ adds a space &lt;em&gt;after&lt;/em&gt; letters, not &lt;em&gt;between&lt;/em&gt; them, &lt;a href=&quot;https://www.w3.org/TR/css-text-3/#letter-spacing-property&quot;&gt;even though it &lt;span style=&quot;font-variant: small-caps;&quot;&gt;Really Should Not&lt;/span&gt;&lt;/a&gt;.
I tried wrapping the last letter of each word in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;span&lt;/code&gt;, but then the letter appears to have its own decoration, out of phase with the rest of the word.
This is because Blink lacks &lt;em&gt;phase-locked decorations&lt;/em&gt;.&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;&lt;div class=&quot;flex&quot;&gt;&lt;div class=&quot;_gifs _paused&quot;&gt;
    &lt;video loop=&quot;&quot; playsinline=&quot;&quot; tabindex=&quot;-1&quot; width=&quot;384&quot; height=&quot;216&quot; poster=&quot;/images/spammar2-w4.png&quot;&gt;&lt;source src=&quot;/images/spammar2-w4.mp4&quot; /&gt;&lt;source src=&quot;/images/spammar2-w4.webm&quot; /&gt;&lt;/video&gt;
    &lt;button type=&quot;button&quot; aria-label=&quot;play&quot;&gt;▶&lt;/button&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;

  &lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;phase-locked-decorations&quot;&gt;Phase-locked decorations&lt;/h2&gt;

&lt;p&gt;Blink uses an inheritance hack to propagate decorations from parents to children, rather than properly implementing the concept of &lt;a href=&quot;https://www.w3.org/TR/2019/CR-css-text-decor-3-20190813/#line-decoration&quot;&gt;&lt;em&gt;decorating box&lt;/em&gt;&lt;/a&gt;.
In other words, we paint two independent decorations, whereas we &lt;em&gt;should&lt;/em&gt; paint one decoration that spans the entire word.
This has been the cause of &lt;a href=&quot;https://github.com/web-platform-tests/interop-2022/issues/23&quot;&gt;a lot of bugs&lt;/a&gt;, and is widely regarded as a bad move.&lt;/p&gt;

&lt;p&gt;Note that we don’t actually have to paint the decoration in a single pass, we only have to render &lt;em&gt;as if&lt;/em&gt; that was the case.
For example, when testing the same change in Firefox, the decoration appears to jitter near the last letter, which suggests that the decoration is probably being painted separately for that element.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;&lt;div class=&quot;flex&quot;&gt;&lt;div class=&quot;_gifs _paused&quot;&gt;
    &lt;video loop=&quot;&quot; playsinline=&quot;&quot; tabindex=&quot;-1&quot; width=&quot;384&quot; height=&quot;216&quot; poster=&quot;/images/spammar2-w5.png&quot;&gt;&lt;source src=&quot;/images/spammar2-w5.mp4&quot; /&gt;&lt;source src=&quot;/images/spammar2-w5.webm&quot; /&gt;&lt;/video&gt;
    &lt;button type=&quot;button&quot; aria-label=&quot;play&quot;&gt;▶&lt;/button&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;Gecko goes above and beyond with this, even synchronising &lt;em&gt;separate&lt;/em&gt; decorations introduced under the same block, which allows authors to make it look like their decorations change color partway through.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;img width=&quot;300&quot; height=&quot;90&quot; src=&quot;/images/spammar2-phase0.png&quot; srcset=&quot;/images/spammar2-phase0.png 2x&quot; /&gt;
    &lt;img width=&quot;256&quot; height=&quot;90&quot; src=&quot;/images/spammar2-phase1.png&quot; srcset=&quot;/images/spammar2-phase1.png 2x&quot; /&gt;
&lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;A related problem in the highlight painting space is that the spec calls for “recoloring” originating decorations to the highlight foreground color.
By making these decorations “lose their color”, we avoid situations where a decoration becomes illegible when highlighted, despite being legible in its original context.&lt;/p&gt;

&lt;p&gt;I’ve &lt;a href=&quot;https://crrev.com/c/2903387&quot;&gt;partially implemented&lt;/a&gt; this for ::selection in Chromium 95, by adding a special case that splits originating decorations into two clipped paints with different colors — though not yet the &lt;em&gt;correct&lt;/em&gt; colors — while carefully keeping them in phase.&lt;/p&gt;

&lt;figure&gt;
&lt;div class=&quot;_compare&quot; style=&quot;--left-label: &apos;actual&apos;; --right-label: &apos;ref3&apos;; width: 275px; margin: 0 auto;&quot;&gt;&lt;img width=&quot;275&quot; height=&quot;150&quot; src=&quot;/images/spammar2-split0.png&quot; /&gt;&lt;img width=&quot;275&quot; height=&quot;150&quot; src=&quot;/images/spammar2-split1.png&quot; /&gt;&lt;/div&gt;
&lt;figcaption&gt;
    &lt;p&gt;&lt;a href=&quot;https://wpt.live/css/css-pseudo/highlight-painting-004.html&quot;&gt;highlight-painting-004&lt;/a&gt; and &lt;a href=&quot;https://wpt.live/css/css-pseudo/highlight-painting-004-ref3.html&quot;&gt;-ref3&lt;/a&gt;, version 97. In this test, the originating element has a red underline, while ::selection introduces a purple line-through. The underline needs to become blue in the highlighted part, to match the ::selection ‘color’, but for now, we match its ‘text-decoration-color’.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;To paint the highlighted part of the decoration, we clip the canvas to a rectangle as wide as the background, and paint the decoration in full.
To paint the rest, we clip “out” the canvas to the same rectangle, which means we don’t touch anything &lt;em&gt;inside&lt;/em&gt; the rectangle.&lt;/p&gt;

&lt;p&gt;But how &lt;em&gt;tall&lt;/em&gt; should that rectangle be? Short answer: infinity.&lt;/p&gt;

&lt;h3 id=&quot;bézier-bounding-box&quot;&gt;Bézier bounding box&lt;/h3&gt;

&lt;p&gt;Long answer: Skia doesn’t let us clip to an infinitely tall rectangle, so it depends on several things, including ‘text-decoration-thickness’, ‘text-underline-offset’, and in the case of wavy decorations, the amplitude of the bézier curves.&lt;/p&gt;

&lt;p&gt;In the code, there was a pretty diagram that illustrated the four relevant points to each “wave” repeated in the decoration.
Clearly, it suggested that the pattern &lt;em&gt;in that example&lt;/em&gt; was bounded by the control points, but I had no idea whether this was true for &lt;em&gt;all&lt;/em&gt; cubic béziers, my terrible search engine skills failed me again, and I don’t like assuming.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/*                   controlPoint1
 *                         +
 *
 *
 *                  . .
 *                .     .
 *              .         .
 * (x1, y1) p1 +           .            + p2 (x2, y2)
 *                          .         .
 *                            .     .
 *                              . .
 *
 *
 *                         +
 *                   controlPoint2
 */
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;To avoid getting stuck on those questions for too long, and because I genuinely didn’t know how to determine the amplitude of a bézier curve, I went with three times the background height.
This should be Good Enough™ for most content, but you can easily break it with, say, a very large ‘text-underline-offset’.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;&lt;div class=&quot;flex&quot;&gt;
    &lt;img width=&quot;275&quot; height=&quot;200&quot; src=&quot;/images/spammar2-clip.png&quot; srcset=&quot;/images/spammar2-clip.png 2x&quot; /&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;Weeks later, I stumbled upon a video by Freya Holmér &lt;a href=&quot;https://www.youtube.com/watch?v=aVwxzDHniEw&amp;amp;t=665&quot;&gt;answering that very question&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;So, how do we get [the bounding] box?&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;The naïve solution is to simply use the control points of the bézier curve.&lt;/strong&gt; This can be good enough, but what we &lt;em&gt;really&lt;/em&gt; want is the “tight bounding box”; in some cases, the difference between the two is &lt;em&gt;huge&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For now, the code still clips to a fixed three times the background height, but at least we now have some ideas for how to properly measure these decorations:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;use the minimum and maximum &lt;em&gt;y&lt;/em&gt; values of the control points (naïve)&lt;/li&gt;
  &lt;li&gt;find &lt;em&gt;better&lt;/em&gt; min and max &lt;em&gt;y&lt;/em&gt; values by evaluating the derivative at its zeros&lt;/li&gt;
  &lt;li&gt;use a dedicated function for this purpose like SkDCubic::convexHull?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;cover-me&quot;&gt;Cover me!&lt;/h3&gt;

&lt;p&gt;Writing the reference pages for that test was also a fun challenge.
When written the obvious way, Blink would actually fail, because in general we make no attempt to keep &lt;em&gt;any&lt;/em&gt; decoration paints in phase.&lt;/p&gt;

&lt;figure&gt;
&lt;div class=&quot;_compare&quot; style=&quot;--left-label: &apos;ref1&apos;; --right-label: &apos;ref3&apos;; width: 275px; margin: 0 auto;&quot;&gt;&lt;img width=&quot;275&quot; height=&quot;150&quot; src=&quot;/images/spammar2-split2.png&quot; /&gt;&lt;img width=&quot;275&quot; height=&quot;150&quot; src=&quot;/images/spammar2-split3.png&quot; /&gt;&lt;/div&gt;
&lt;figcaption&gt;
    &lt;p&gt;&lt;a href=&quot;https://wpt.live/css/css-pseudo/highlight-painting-004-ref1.html&quot;&gt;highlight-painting-004-ref1&lt;/a&gt; and &lt;a href=&quot;https://wpt.live/css/css-pseudo/highlight-painting-004-ref3.html&quot;&gt;-ref3&lt;/a&gt;, version 96.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The ref that Blink ended up matching has five layers.
Each layer contains the word “quick” in full with any decorations spanning the whole word, but only part of the layer is shown.
This is achieved by an elaborate system of positioned “covers” and “hiders”: the former clips a layer from the right with a white rectangle, while the latter clips a layer from the left by way of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;right:0&lt;/code&gt; wrapped in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;overflow:hidden&lt;/code&gt;.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;&lt;div class=&quot;flex&quot;&gt;
    &lt;a href=&quot;/images/spammar2-ref.jpg&quot;&gt;&lt;img width=&quot;432&quot; height=&quot;256&quot; src=&quot;/images/spammar2-ref.jpg&quot; srcset=&quot;/images/spammar2-ref.jpg 2x&quot; /&gt;&lt;/a&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;Wanna know the best part though?
&lt;a href=&quot;https://wpt.live/css/css-pseudo/highlight-painting-004-ref1.html&quot;&gt;All&lt;/a&gt; &lt;a href=&quot;https://wpt.live/css/css-pseudo/highlight-painting-004-ref2.html&quot;&gt;three&lt;/a&gt; &lt;a href=&quot;https://wpt.live/css/css-pseudo/highlight-painting-004-ref3.html&quot;&gt;refs&lt;/a&gt; are identical in Firefox.
Someday, hopefully, this will also be true for Blink.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;highlight-inheritance&quot;&gt;Highlight inheritance&lt;/h2&gt;

&lt;p&gt;Presto (Opera), uniquely, supported inheritance for ::selection before it was cool, by mapping those styles to synthesised (internal) ‘selection-color’ and ‘selection-background’ properties that were marked as inherited.&lt;/p&gt;

&lt;p&gt;Blink also has internal properties for things like &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/:visited&quot;&gt;:visited links&lt;/a&gt; and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/@media/forced-colors&quot;&gt;forced colors&lt;/a&gt;, where we need to keep track of both “original” and “new” colors.
This works well enough, but internal properties add a great deal of complexity to the code that applies and consumes styles.
Now that there are multiple highlight pseudos, supporting a lot more than just ‘color’ and ‘background-color’, this complexity is hard to justify.&lt;/p&gt;

&lt;p&gt;To understand the approach we went with, let’s look at how CSS works in Chromium.&lt;/p&gt;

&lt;h3 id=&quot;blink-style-101&quot;&gt;Blink style 101&lt;/h3&gt;

&lt;p&gt;CSS is managed by Blink’s style system, which at its highest level consists of the &lt;em&gt;engine&lt;/em&gt;, the &lt;em&gt;resolver&lt;/em&gt;, and the &lt;em&gt;ComputedStyle&lt;/em&gt; data structure.
The engine maintains all of the style-related state for a document, including all of its stylesheet rules &lt;em&gt;and&lt;/em&gt; the information needed to recalculate styles efficiently when the document changes.
The resolver’s job is to calculate styles for some element, writing the results to a new ComputedStyle object.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;img width=&quot;407&quot; height=&quot;167&quot; src=&quot;/images/spammar2-x0.png&quot; srcset=&quot;/images/spammar2-x0.png 2x&quot; /&gt;
&lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;ComputedStyle itself is also interesting.
Blink recognises over 600 properties, including internal properties, shorthands (like ‘margin’), and aliases (like ‘-webkit-transform’), so most of the fields and methods are actually generated (ComputedStyleBase) with the help of some Python scripts.&lt;/p&gt;

&lt;p&gt;These fields are “sharded” into &lt;em&gt;field groups&lt;/em&gt;, so we can &lt;a href=&quot;https://en.wikipedia.org/wiki/Copy-on-write&quot;&gt;efficiently reuse&lt;/a&gt; style data from ancestors and previous resolver outputs.
Some of these field groups are human-defined, like “surround” for all of the margin/border/padding properties, but there are also several &lt;em&gt;raredata&lt;/em&gt; groups generated from property popularity stats.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;img width=&quot;587&quot; height=&quot;293&quot; src=&quot;/images/spammar2-x1.png&quot; srcset=&quot;/images/spammar2-x1.png 2x&quot; /&gt;
&lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;When resolving styles, we usually clone an &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/initial_value&quot;&gt;“empty”&lt;/a&gt; ComputedStyle, then we copy over the inherited properties from the parent to this fresh new object.
Many of these live in the “inherited” field group, so all we need to do for them is copy a single pointer.
At this point, we have the parent’s inherited properties, and everything else as initial values, so if the element doesn’t have any rules of its own, we’re more or less done.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;img width=&quot;527&quot; height=&quot;334&quot; src=&quot;/images/spammar2-x2.png&quot; srcset=&quot;/images/spammar2-x2.png 2x&quot; /&gt;
&lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;Otherwise, we search for matching rules, &lt;a href=&quot;https://www.w3.org/TR/css-cascade-4/#cascading&quot;&gt;sort all of their declarations&lt;/a&gt; by things like specificity, then &lt;em&gt;apply&lt;/em&gt; the winning declarations by overwriting various ComputedStyle fields.
If the field we’re overwriting is in a field group, we need to clone the field group too, to avoid clobbering someone else’s styles.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;img width=&quot;527&quot; height=&quot;190&quot; src=&quot;/images/spammar2-x3.png&quot; srcset=&quot;/images/spammar2-x3.png 2x&quot; /&gt;
&lt;/div&gt;&lt;/figure&gt;

&lt;h3 id=&quot;blink-style-102&quot;&gt;Blink style 102: pseudo-elements&lt;/h3&gt;

&lt;p&gt;For ordinary elements, as well as pseudo-elements with a clear place in the DOM tree (e.g. ::before, ::marker), we resolve styles as part of &lt;em&gt;style&lt;/em&gt;’s regular tree traversal.
We start by updating :root’s styles, then any children affected by the update, and so on.
But for other pseudos we usually use a “lazy” approach, where we don’t bother resolving styles unless they are needed by a later phase of the rendering process, like layout or paint.&lt;/p&gt;

&lt;p&gt;Let’s say we’re resolving styles for some ordinary element.
When we’re searching for matching rules, if we find one that &lt;em&gt;actually&lt;/em&gt; matches our ::selection, we make a note in our &lt;em&gt;pseudo bits&lt;/em&gt; saying we’ve seen rules for that pseudo, but otherwise ignore the rule.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;img width=&quot;467&quot; height=&quot;107&quot; src=&quot;/images/spammar2-y0.png&quot; srcset=&quot;/images/spammar2-y0.png 2x&quot; /&gt;
&lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;Once we’re in &lt;em&gt;paint&lt;/em&gt;, if the user has selected some text, then we need to know our ::selection styles, so we check our pseudo bits.
If the ::selection bit was set, we call our &lt;em&gt;resolver&lt;/em&gt; with a special request for pseudo styles, then cache the result into a vector inside the originating element’s ComputedStyle.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;img width=&quot;547&quot; height=&quot;97&quot; src=&quot;/images/spammar2-y1.png&quot; srcset=&quot;/images/spammar2-y1.png 2x&quot; /&gt;
&lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;This is how ::selection used to work, and at first I tried to keep it that way.&lt;/p&gt;

&lt;div class=&quot;_commits&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;

    &lt;h3 id=&quot;status-quo&quot;&gt;Status quo&lt;/h3&gt;

    &lt;p&gt;My initial solution was to make &lt;em&gt;paint&lt;/em&gt; pass in a custom inheritance parent with its style request.
Normally pseudo styles inherit from the originating element, but here they would inherit from the parent’s highlight styles, which we would obtain recursively.
Then in the resolver, if we’re dealing with a highlight, we copy non-inherited properties too.&lt;/p&gt;

    &lt;p&gt;On the surface, this worked, but to make it &lt;em&gt;correct&lt;/em&gt;, we had to work around an optimisation where the resolver would bail out early if there were no matching rules.
Worse still, we had to bypass the pseudo cache entirely.
While we already had to do so under :window-inactive, the performance penalty there was at least pretty contained.&lt;/p&gt;

    &lt;div class=&quot;_commit&quot;&gt;&lt;a href=&quot;https://crrev.com/c/2850068/7&quot;&gt;&lt;code&gt;PS7&lt;/code&gt;&lt;/a&gt;&lt;img width=&quot;40&quot; height=&quot;40&quot; src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

    &lt;p&gt;If we copy over the parent’s inherited properties as usual, and for highlights, copy the non-inherited properties too, that more or less means we’re copying &lt;em&gt;all&lt;/em&gt; the fields, so why not do away with that and just clone the parent’s ComputedStyle?&lt;/p&gt;

    &lt;div class=&quot;_commit&quot;&gt;&lt;a href=&quot;https://crrev.com/c/2850068/7..10&quot;&gt;&lt;code&gt;PS10&lt;/code&gt;&lt;/a&gt;&lt;img width=&quot;40&quot; height=&quot;40&quot; src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

    &lt;p&gt;The pseudo cache is only designed for pseudos whose styles won’t need to change between the originating element’s style updates.
For most pseudos, this is true anyway, as long as we bypass the cache under pseudo-classes like :window-inactive.&lt;/p&gt;

    &lt;p&gt;These caches are rarely actually cleared, but when the next update happens, the whole ComputedStyle — including the cache — gets discarded.
Caching results with custom inheritance parents is usually frowned upon, because changing the parent you inherit your styles from can yield different styles.
But for highlights, we will always have the same parent throughout an update cycle, so surely we can use the cache here?&lt;/p&gt;

    &lt;div class=&quot;_commit&quot;&gt;&lt;a href=&quot;https://crrev.com/c/2850068/10..13&quot;&gt;&lt;code&gt;PS13&lt;/code&gt;&lt;/a&gt;&lt;img width=&quot;40&quot; height=&quot;40&quot; src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

    &lt;p&gt;…well, yes and no.&lt;/p&gt;

    &lt;p&gt;Given an element that inherits a bunch of highlight styles, the initial styles are correct.
But when those inherited values change in some ancestor, our highlight styles fail to update!
This is a classic &lt;em&gt;cache invalidation&lt;/em&gt; bug.
Our invalidation system wasn’t even the problem — it’s just unaware of lazily resolved styles in pseudo caches.
This is usually fine, because most pseudos inherit from the originating element, but not here.&lt;/p&gt;

    &lt;h3 id=&quot;storing-highlight-styles&quot;&gt;Storing highlight styles&lt;/h3&gt;

    &lt;p&gt;With the pseudo cache being unsuitable for highlight styles, we needed some other way of storing them.
Only a handful of properties are allowed in highlight styles, so why not make a dedicated type with only those fields?&lt;/p&gt;

    &lt;p&gt;The declarations and basic methods for CSS properties are entirely generated, so let’s write some new templates…&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-jinja highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;macro&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;declare_highlight_class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;fields&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;field_templates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;%}&lt;/span&gt;
class &lt;span class=&quot;cp&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;}}&lt;/span&gt; : public RefCounted&lt;span class=&quot;nt&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;}}&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt; {
 public:
  static scoped_refptr&lt;span class=&quot;nt&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;}}&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt; Create() { /* ... */ }
  scoped_refptr&lt;span class=&quot;nt&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;}}&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt; Copy() const { /* ... */ }
  bool operator==(const &lt;span class=&quot;cp&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;}}&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;amp;&lt;/span&gt; other) const { /* ... */ }
  bool operator!=(const &lt;span class=&quot;cp&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;}}&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;amp;&lt;/span&gt; other) const { /* ... */ }
  &lt;span class=&quot;cp&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;field&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;fields&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%}&lt;/span&gt;
  &lt;span class=&quot;cp&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;declare_storage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;}}&lt;/span&gt;
  &lt;span class=&quot;cp&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;endfor&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%}&lt;/span&gt;
  &lt;span class=&quot;cp&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;field&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;fields&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%}&lt;/span&gt;
  &lt;span class=&quot;cp&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;field_templates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;field.field_template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;decl_public_methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;field.without_group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;indent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;}}&lt;/span&gt;
  &lt;span class=&quot;cp&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;endfor&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%}&lt;/span&gt;
 private:
  &lt;span class=&quot;cp&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;}}&lt;/span&gt;();
  CORE_EXPORT &lt;span class=&quot;cp&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;}}&lt;/span&gt;(const &lt;span class=&quot;cp&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;}}&lt;/span&gt;&lt;span class=&quot;ni&quot;&gt;&amp;amp;);&lt;/span&gt;
};
&lt;span class=&quot;cp&quot;&gt;{%&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;endmacro&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;&lt;/figure&gt;

    &lt;p&gt;…then use them in the ComputedStyleBase template.&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-jinja highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;declare_highlight_class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;StyleHighlightData&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;computed_style.all_fields&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;attribute&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;name&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;selectattr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;valid_for_highlight&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;field_templates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;indent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;&lt;/figure&gt;

    &lt;div class=&quot;_commit&quot;&gt;&lt;a href=&quot;https://crrev.com/c/2850068/13..16&quot;&gt;&lt;code&gt;PS16&lt;/code&gt;&lt;/a&gt;&lt;img width=&quot;40&quot; height=&quot;40&quot; src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

    &lt;p&gt;Trouble is, all of the methods that apply and serialise property values — and there are &lt;em&gt;hundreds&lt;/em&gt; of them — take a ComputedStyle, not some other type.&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blink&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Color&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ColorIncludingFallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visited_link&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ComputedStyle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CSSValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CSSValueFromComputedStyleInternal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ComputedStyle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LayoutObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;allow_visited_style&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;&lt;/figure&gt;

    &lt;p&gt;Combined with the fact that our copy-on-write field groups mitigate a lot of the wasted memory (well hopefully anyway), we quickly abandoned this dedicated type.&lt;/p&gt;

    &lt;div class=&quot;_commit&quot;&gt;&lt;a href=&quot;https://crrev.com/c/2850068/16..25&quot;&gt;&lt;code&gt;PS25&lt;/code&gt;&lt;/a&gt;&lt;img width=&quot;40&quot; height=&quot;40&quot; src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

    &lt;!-- &lt;div class=&quot;_commit _commit-none&quot;&gt;&lt;a href=&quot;https://crrev.com/c/2850068/24..25&quot;&gt;&lt;code&gt;PS25&lt;/code&gt;&lt;/a&gt;&lt;img width=&quot;40&quot; height=&quot;40&quot; src=&quot;/images/badapple-commit-none.svg&quot;&gt;&lt;/div&gt; --&gt;

    &lt;p&gt;We then optimised the top-level struct a bit, saving a few pointer widths by moving the four highlight style pointers into a separate type, but this was still less than ideal.
We were widening ComputedStyle by one pointer, but the vast majority of web content doesn’t use highlight pseudos at all, and ComputedStyle and ComputedStyleBase are very sensitive to size changes.
To give you an idea of how much it matters, Blink even throws a compile-time error if the size inadvertently changes!&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SameSizeAsComputedStyleBase&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;SameSizeAsComputedStyleBase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Alias&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pointers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Alias&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bitfields&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
 &lt;span class=&quot;nl&quot;&gt;private:&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pointers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bitfields&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SameSizeAsComputedStyle&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SameSizeAsComputedStyleBase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                 &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RefCounted&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SameSizeAsComputedStyle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;SameSizeAsComputedStyle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Alias&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;own_pointers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
 &lt;span class=&quot;nl&quot;&gt;private:&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;own_pointers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ASSERT_SIZE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ComputedStyle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SameSizeAsComputedStyle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;&lt;/figure&gt;

    &lt;p&gt;To move highlights out of the top-level and into a &lt;em&gt;raredata&lt;/em&gt; group, we had to get rid of all the fancy generated code and Just write a plain struct, which has the added benefit of making the code easier to read.
Luckily, we were only using that code to loop through the four highlight pseudos at this point, not dozens or hundreds of properties.&lt;/p&gt;

    &lt;p&gt;Then all we needed was a bit of JSON to tell the code generator to add an “extra” field, &lt;em&gt;and&lt;/em&gt; find an appropriate field group for us (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;*&quot;&lt;/code&gt;).
Because this field is not for a popular CSS property, or a property at all really, it automatically goes in a &lt;em&gt;raredata&lt;/em&gt; group.&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;HighlightData&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;inherited&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;field_template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;external&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;type_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;StyleHighlightData&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;include_paths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;third_party/blink/renderer/core/style/style_highlight_data.h&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;default_value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;wrapper_pointer_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;DataRef&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;field_group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;computed_style_custom_functions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;initial&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;getter&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;setter&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;resetter&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;&lt;/figure&gt;

    &lt;div class=&quot;_commit&quot;&gt;&lt;a href=&quot;https://crrev.com/c/2850068/25..35&quot;&gt;&lt;code&gt;PS35&lt;/code&gt;&lt;/a&gt;&lt;img width=&quot;40&quot; height=&quot;40&quot; src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

    &lt;h3 id=&quot;single-pass-resolution&quot;&gt;Single-pass resolution&lt;/h3&gt;

    &lt;p&gt;With our new storage ready, we now needed to actually write to it.
We want to resolve highlight styles as part of the regular style update cycle, so that they can eventually benefit from style invalidation.&lt;/p&gt;

    &lt;p&gt;Looking at the resolver, I thought wow, there does seem to be a lot of redundant work being done when resolving highlight styles in a separate request, so why not weave highlight resolution into the resolver while we’re at it?&lt;/p&gt;

    &lt;div class=&quot;_commit&quot;&gt;&lt;a href=&quot;https://crrev.com/c/2850068/35..36&quot;&gt;&lt;code&gt;PS36&lt;/code&gt;&lt;/a&gt;&lt;img width=&quot;40&quot; height=&quot;40&quot; src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;@@ third_party/blink/renderer/core/css/css_selector.h @@&lt;/span&gt;
   enum RelationType {
&lt;span class=&quot;gi&quot;&gt;+    kHighlights,
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@@ third_party/blink/renderer/core/css/css_selector.cc @@&lt;/span&gt;
       case kShadowSlot:
&lt;span class=&quot;gi&quot;&gt;+      case kHighlights:
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@@ third_party/blink/renderer/core/css/element_rule_collector.h @@&lt;/span&gt;
   MatchedRule(const RuleData* rule_data,
               unsigned style_sheet_index,
               const CSSStyleSheet* parent_style_sheet,
&lt;span class=&quot;gi&quot;&gt;+              absl::optional&amp;lt;PseudoId&amp;gt; highlight)
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@@ third_party/blink/renderer/core/css/resolver/match_result.h @@&lt;/span&gt;
   void AddMatchedProperties(
       const CSSPropertyValueSet* properties,
       unsigned link_match_type = CSSSelector::kMatchAll,
       ValidPropertyFilter = ValidPropertyFilter::kNoFilter,
&lt;span class=&quot;gi&quot;&gt;+      absl::optional&amp;lt;PseudoId&amp;gt; highlight = absl::nullopt);
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@@ ... @@&lt;/span&gt;
   const MatchedPropertiesVector&amp;amp; GetMatchedProperties(
&lt;span class=&quot;gi&quot;&gt;+      absl::optional&amp;lt;PseudoId&amp;gt; highlight) const {
+    DCHECK(!highlight || highlight_matched_properties_.Contains(*highlight));
+    return highlight ? *highlight_matched_properties_.at(*highlight)
&lt;/span&gt;                      : matched_properties_;
&lt;span class=&quot;p&quot;&gt;@@ ... @@&lt;/span&gt;
   MatchedPropertiesVector matched_properties_;
&lt;span class=&quot;gi&quot;&gt;+  HeapHashMap&amp;lt;PseudoId, Member&amp;lt;MatchedPropertiesVector&amp;gt;&amp;gt;
+      highlight_matched_properties_;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@@ third_party/blink/renderer/core/css/resolver/style_cascade.h @@&lt;/span&gt;
   void Apply(CascadeFilter = CascadeFilter());
&lt;span class=&quot;gi&quot;&gt;+  void ApplyHighlight(PseudoId);
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@@ third_party/blink/renderer/core/css/resolver/style_cascade.cc @@&lt;/span&gt;
 const CSSValue* ValueAt(const MatchResult&amp;amp; result,
&lt;span class=&quot;gi&quot;&gt;+                        absl::optional&amp;lt;PseudoId&amp;gt; highlight,
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@@ ... @@&lt;/span&gt;
 const TreeScope&amp;amp; TreeScopeAt(const MatchResult&amp;amp; result,
&lt;span class=&quot;gi&quot;&gt;+                             absl::optional&amp;lt;PseudoId&amp;gt; highlight,
&lt;/span&gt;                              uint32_t position) {
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;&lt;/figure&gt;

    &lt;blockquote&gt;
      &lt;p&gt;In general we must find a less intrusive way to implement this. We can not have |highlight| params on everything.&lt;/p&gt;

      &lt;footer&gt;— &lt;cite&gt;andruud&lt;/cite&gt;, Blink &lt;em&gt;style&lt;/em&gt; owner&lt;/footer&gt;
    &lt;/blockquote&gt;

    &lt;p&gt;You know what? Fair enough.&lt;/p&gt;

    &lt;div class=&quot;_commit _commit-none&quot;&gt;&lt;a href=&quot;https://crrev.com/c/2850068/36..37&quot;&gt;&lt;code&gt;⭯ PS35&lt;/code&gt;&lt;/a&gt;&lt;img width=&quot;40&quot; height=&quot;40&quot; src=&quot;/images/badapple-commit-none.svg&quot; /&gt;&lt;/div&gt;

    &lt;h3 id=&quot;multi-pass-resolution&quot;&gt;Multi-pass resolution&lt;/h3&gt;

    &lt;p&gt;Element::Recalc{,Own}Style are pretty big friends of the style system.
They drive the style update cycle by determining how the tree has changed, making a resolver request for the element, and determining which descendants also need to be updated.&lt;/p&gt;

    &lt;p&gt;This makes them the perfect place to update highlight styles.
All we need to do is make an additional resolver request for each highlight pseudo, store it in the highlight data, and bob’s your uncle.&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;StyleRecalcChange&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Element&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RecalcOwnStyle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;StyleRecalcChange&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;change&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;StyleRecalcContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;style_recalc_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_style&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;StyleHighlightData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;highlights&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_style&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MutableHighlightData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_style&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HasPseudoElementStyle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kPseudoIdSelection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;ComputedStyle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ParentComputedStyle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HighlightData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Selection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;StyleRequest&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kPseudoIdSelection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;highlights&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SetSelection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;StyleForPseudoElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;style_recalc_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// kPseudoIdTargetText&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// kPseudoIdSpellingError&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// kPseudoIdGrammarError&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// SetComputedStyle(new_style);&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;&lt;/figure&gt;

    &lt;div class=&quot;_commit&quot;&gt;&lt;a href=&quot;https://crrev.com/c/2850068/37..43&quot;&gt;&lt;code&gt;PS43&lt;/code&gt;&lt;/a&gt;&lt;img width=&quot;40&quot; height=&quot;40&quot; src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

    &lt;h3 id=&quot;pathology-in-legacy&quot;&gt;Pathology in legacy&lt;/h3&gt;

    &lt;p&gt;So far, I had been writing this patch as a &lt;em&gt;replacement&lt;/em&gt; for the old inheritance logic, but since we decided to defer highlight inheritance for ::highlight to a later patch, we had to undelete the old behaviour and switch between them with a Blink feature.&lt;/p&gt;

    &lt;p&gt;Another reason for the feature gate was performance.
Of the pages in the wild already using highlight pseudos, most of them probably use universal ::selection rules, if only because of how useless the old model was for more complex use cases.&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;::selection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;lime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;green&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;&lt;/figure&gt;

    &lt;p&gt;But &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;::selection&lt;/code&gt; isn’t magic — it literally means &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*::selection&lt;/code&gt;, which makes the rule match everywhere in the ::selection tree.
When highlight inheritance is enabled, that means we end up cloning highlight styles for each descendant, only to apply the &lt;em&gt;same&lt;/em&gt; property values, which wastes time and memory.&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;img width=&quot;448&quot; height=&quot;378&quot; src=&quot;/images/spammar2-z0.png&quot; srcset=&quot;/images/spammar2-z0.png 2x&quot; /&gt;
&lt;/div&gt;&lt;figcaption&gt;
The reality is a bit more complicated than this, because ‘color’ and ‘background-color’ are actually in field groups that would also need to be cloned.
&lt;/figcaption&gt;&lt;/figure&gt;

    &lt;p&gt;Under the old model, where lack of inheritance made this necessary, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*::selection&lt;/code&gt; rules suffered from roughly the same problem, but the lazy style resolution meant that time and memory was only wasted on the elements &lt;em&gt;directly containing&lt;/em&gt; selected content.&lt;/p&gt;

    &lt;p&gt;As a result, this will need to be fixed before we can enable the feature for everyone.&lt;/p&gt;

    &lt;div class=&quot;_commit&quot;&gt;&lt;a href=&quot;https://crrev.com/c/2850068/43..51&quot;&gt;&lt;code&gt;PS51&lt;/code&gt;&lt;/a&gt;&lt;img width=&quot;40&quot; height=&quot;40&quot; src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

    &lt;h3 id=&quot;paired-cascade&quot;&gt;Paired cascade&lt;/h3&gt;

    &lt;p&gt;Next we tried to reimplement &lt;em&gt;paired cascade&lt;/em&gt;.
For compatibility reasons, ::selection has special logic for the browser’s default ‘color’ and ‘background-color’ (e.g. white on blue), where we only use those colors if &lt;em&gt;neither&lt;/em&gt; of them were set by the author.
Otherwise, they default to initial values, usually black on transparent.&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;&lt;div class=&quot;flex&quot;&gt;&lt;table class=&quot;_sum&quot;&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;span style=&quot;color: white; background: #3584e4;&quot;&gt;default on default&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;+&lt;/td&gt;&lt;td&gt;
                &lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;::selection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;yellow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;                &lt;/div&gt;
              &lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;=&lt;/td&gt;&lt;td&gt;&lt;span style=&quot;color: black; background: yellow;&quot;&gt;initial on yellow&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;

    &lt;p&gt;The spec says so in a mere 22 words:&lt;/p&gt;

    &lt;blockquote&gt;
      &lt;p&gt;The UA must use its own highlight colors for ::selection only when neither color nor background-color has been specified by the author.&lt;/p&gt;
    &lt;/blockquote&gt;

    &lt;p&gt;Brevity is a good thing, and this seemed clear enough to me in the past.
But once I actually had to implement it, I had questions about almost every word (&lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/6386&quot;&gt;#6386&lt;/a&gt;).
While they aren’t &lt;em&gt;entirely&lt;/em&gt; resolved, we’ve been getting pretty close over the last few weeks.&lt;/p&gt;

    &lt;div class=&quot;_commit&quot;&gt;&lt;a href=&quot;https://crrev.com/c/2850068/51..52&quot;&gt;&lt;code&gt;PS52&lt;/code&gt;&lt;/a&gt;&lt;img width=&quot;40&quot; height=&quot;40&quot; src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

    &lt;h3 id=&quot;fixing-tests&quot;&gt;Who’s got green&lt;a href=&quot;https://www.youtube.com/watch?v=ul6VV8XW9xw&quot;&gt;?&lt;/a&gt;&lt;/h3&gt;

    &lt;p&gt;Much of the remaining work was to fix test failures and other bugs.
These included crashes under legacy layout, since we only implemented this for LayoutNG, and functional changes leaking out of the feature gate.
One of the reftest failures was also interesting to deal with.
Let’s minimise it and take a look.&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;charset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;active selection and background-color (basic)&lt;span class=&quot;nt&quot;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;main&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;fuchsia&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;red&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;::selection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;green&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Pass if text is fuchsia on green, not fuchsia on red.
&lt;span class=&quot;nt&quot;&gt;&amp;lt;main&amp;gt;&lt;/span&gt;Selected Text&lt;span class=&quot;nt&quot;&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;script&amp;gt;&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;/* selectNodeContents(main); */&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;&lt;/figure&gt;

    &lt;p&gt;In the past, the “Selected Text” would render as fuchsia on green, and the test passes.
But under highlight inheritance it fails, rendering as initial (black) on green, because we now inherit styles in a tree for each pseudo, not from the originating element.&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;&lt;div class=&quot;flex&quot;&gt;&lt;div&gt;
&lt;span style=&quot;color: fuchsia; background: green;&quot;&gt;Selected Text&lt;/span&gt;
→
&lt;span style=&quot;color: black; background: green;&quot;&gt;Selected Text&lt;/span&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;

    &lt;p&gt;So if the test is wrong, then how do we fix it?
Well… it depends on the &lt;em&gt;intent&lt;/em&gt; of the test, at least if we want to Do The Right Thing and preserve that.
Clearly the &lt;em&gt;primary&lt;/em&gt; intent of the test is ‘background-color’, given the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;title&amp;gt;&lt;/code&gt;, but tests can also have secondary, less explicit intents.
In this case, the flavour text&lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; even mentions fuchsia!&lt;/p&gt;

    &lt;p&gt;It might have helped if the test had a &lt;a href=&quot;https://web-platform-tests.org/writing-tests/reftest-tutorial.html#writing-the-test-file&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;meta name=assert&amp;gt;&lt;/code&gt;&lt;/a&gt;, an optional field dedicated to conveying intent, but probably not.
Most of the assert tags I’ve seen are poorly written anyway, being a more or less verbose adaptation of the title or flavour text, and there’s a good chance that the intent for fuchsia (if any) was simply to inherit it from the originating element, so we would still need to invent a new intent.&lt;/p&gt;

    &lt;p&gt;We could change the reference to initial (black) on green, which would serve as a secondary test that we &lt;em&gt;don’t&lt;/em&gt; inherit from the originating element, or remove the existing ‘color’, which would serve as a secondary test for &lt;a href=&quot;#paired-cascade&quot;&gt;paired cascade&lt;/a&gt;.
But I didn’t think it through that far at the time, so I gave ::selection a new ‘color’, achieving neither.&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; main::selection {
&lt;span class=&quot;gi&quot;&gt;+ color: aqua;
&lt;/span&gt;  background: green; }
 &amp;lt;/style&amp;gt;
 &amp;lt;p&amp;gt;Pass if text is
&lt;span class=&quot;gd&quot;&gt;- fuchsia
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+ aqua
&lt;/span&gt; on green, not fuchsia on red.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;&lt;/figure&gt;

    &lt;p&gt;Because the selected and unselected text colors were now different, I created &lt;em&gt;another&lt;/em&gt; test failure, though only under legacy layout.
The reference for this test was straightforward: aqua on green, no mention of fuchsia.
This makes sense on the surface, given that all of the text under test was selected.&lt;/p&gt;

    &lt;p&gt;In this case, the tip of the “t” was crossing the right edge of the selection as ink overflow, and were carefully painting the overflow in the unselected color.
The test would have failed under LayoutNG too, if not for an optimisation that skips this technique when everything is selected.
Let me illustrate with an exaggerated example:&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;img width=&quot;167&quot; height=&quot;102&quot; src=&quot;/images/spammar2-ink-overflow.png&quot; srcset=&quot;/images/spammar2-ink-overflow.png 2x&quot; /&gt;
&lt;/div&gt;&lt;/figure&gt;

    &lt;p&gt;This behaviour is generally considered desirable, at least when there are unselected characters, so Blink isn’t exactly &lt;em&gt;wrong&lt;/em&gt; here.
It’s definitely possible to make the active-selection tests account for this — and the tools to do so already exist in the Web Platform Tests — but I don’t have the time to pursue this right now.&lt;/p&gt;

    &lt;div class=&quot;_commit&quot;&gt;&lt;a href=&quot;https://crrev.com/c/2850068/52..76&quot;&gt;&lt;code&gt;PS76&lt;/code&gt;&lt;/a&gt;&lt;img width=&quot;40&quot; height=&quot;40&quot; src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

  &lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;what-now&quot;&gt;What now?&lt;/h2&gt;

&lt;p&gt;After the holidays, we plan to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Resolve the remaining &lt;a href=&quot;#charlie&quot;&gt;spec issues&lt;/a&gt;.&lt;/strong&gt; These issues are critical for finishing highlight inheritance and allowing highlights to add their own decorations.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Port ::selection’s painting logic to the other highlights.&lt;/strong&gt; We might even use this as an opportunity to &lt;a href=&quot;https://docs.google.com/document/d/1Rfelx4qv-RhQYHUJ74QBU5MjEbmb9Wol9gvyjkywqgE&quot;&gt;roll ::selection into the marker system&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other work needed before we can ship the spelling and grammar features:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Ship highlight inheritance.&lt;/strong&gt; This includes addressing any spec resolutions, fixing the performance issues, and &lt;a href=&quot;https://docs.google.com/document/d/1eJn5QIX4JFGackDYmdLxWXEmTDkSGj_ZGz5XY4uCKbY&quot;&gt;adding devtools support&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Integrate spelling and grammar errors with decoration painting (&lt;a href=&quot;https://crbug.com/1257553&quot;&gt;bug 1257553&lt;/a&gt;).&lt;/li&gt;
  &lt;li&gt;Make automated testing possible for spelling and grammar errors (&lt;a href=&quot;https://github.com/web-platform-tests/wpt/issues/30863&quot;&gt;wpt#30863&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Special thanks to &lt;a href=&quot;https://twitter.com/regocas&quot;&gt;Rego&lt;/a&gt;, Frédéric (Igalia), &lt;a href=&quot;https://twitter.com/runeLi&quot;&gt;Rune&lt;/a&gt;, andruud (Google), &lt;a href=&quot;https://twitter.com/frivoal&quot;&gt;Florian&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/fantasai&quot;&gt;fantasai&lt;/a&gt; (CSSWG), &lt;a href=&quot;https://twitter.com/ecbos_&quot;&gt;Emilio&lt;/a&gt; (Mozilla), and Fernando (Microsoft).
We would also like to thank &lt;a href=&quot;https://www.bloomberg.com/company/&quot;&gt;Bloomberg&lt;/a&gt; for sponsoring this work.&lt;/p&gt;

&lt;hr /&gt;

&lt;script&gt;
    (() =&gt; {
        function click({ currentTarget: x }) {
            x.classList.toggle(&apos;_paused&apos;);
            x.querySelectorAll(&quot;video&quot;).forEach(v =&gt; {
                v.paused ? v.play() : v.pause();
            });
        }
        document.querySelectorAll(&quot;._gifs&quot;).forEach(x =&gt; {
            x.addEventListener(&quot;click&quot;, click);
        });
    })();

    [...document.querySelectorAll(&quot;._compare&quot;)].forEach(x =&gt; {
        const p = x.firstChild;
        const q = x.lastChild;

        const inner = document.createElement(&quot;div&quot;);
        x.prepend(inner);
        inner.append(p, q);
        p.after(document.createElement(&quot;div&quot;));

        inner.style.setProperty(&quot;--cut&quot;, `${inner.getBoundingClientRect().width / 2}px`);

        inner.addEventListener(&quot;mousemove&quot;, event =&gt; {
            inner.style.setProperty(&quot;--cut&quot;, `${event.offsetX}px`);
        });

        inner.addEventListener(&quot;touchmove&quot;, event =&gt; {
            inner.style.setProperty(&quot;--cut&quot;, `${event.targetTouches.item(0).clientX - inner.getBoundingClientRect().left}px`);
        });
    });
&lt;/script&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;This is an automated reftest, so the instructions in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;p&amp;gt;&lt;/code&gt; have no effect on the outcome. &lt;a href=&quot;https://web-platform-tests.org/writing-tests/reftests.html#writing-a-good-reftest&quot;&gt;We require them anyway&lt;/a&gt;, because they add a bit of redundancy that helps humans understand and verify the test’s assertions. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name></name></author><category term="home" /><category term="igalia" /><summary type="html">Modern web browsers can help users with their word processing needs by drawing squiggly lines under possible spelling or grammar errors in their input. CSS will give authors more control over when and how they appear, with the new ::spelling- and ::grammar-error pseudo-elements, and spelling- and grammar-error text decorations. Since part 1 in May, we’ve done a fair bit of work in both Chromium and the CSSWG towards making this possible.</summary></entry><entry><title type="html">Group sharing on a FreeBSD home server</title><link href="https://www.azabani.com/2021/09/24/freebsd-home-group-sharing.html" rel="alternate" type="text/html" title="Group sharing on a FreeBSD home server" /><published>2021-09-24T15:30:00+00:00</published><updated>2021-09-24T15:30:00+00:00</updated><id>https://www.azabani.com/2021/09/24/freebsd-home-group-sharing</id><content type="html" xml:base="https://www.azabani.com/2021/09/24/freebsd-home-group-sharing.html">&lt;p&gt;My partner and I share a home server for our storage needs, running on FreeBSD 12 with ZFS.
We have our own users, delan and aria, and a group (delanria) that &lt;em&gt;in theory&lt;/em&gt; we can use for common areas, like our software collection.
Services like torrent clients and media libraries have their own users too, and all of these users need to write to things others have created.
This was easier said than done.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#unix-permissions-101&quot;&gt;Unix permissions 101&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#ownership-and-creation&quot;&gt;Ownership and creation&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#shared-storage&quot;&gt;Shared storage&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#login-classes&quot;&gt;Login classes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#fixing-sudo&quot;&gt;Fixing sudo(8)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#fixing-services&quot;&gt;Fixing services&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#fixing-samba&quot;&gt;Fixing samba(8)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#funny-execute-bits&quot;&gt;Funny execute bits&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#what-did-we-learn&quot;&gt;What did we learn?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;unix-permissions-101&quot;&gt;Unix permissions 101&lt;/h2&gt;

&lt;p&gt;Files are owned by a user (the owner) and a group (the group).
Users belong to groups, and each group they join grants them access to any files owned by that group.
Processes run as a user (euid) and a group (egid), more or less.&lt;/p&gt;

&lt;p&gt;Traditional permissions for files consist of twelve bits: nine for whether the owning user (u), owning group (g), and others (o) can read (r), write (w), or execute (x); three for controlling execution behaviour.
Of the latter three, setuid (u+s) makes the file run with its owning user as euid, setgid (g+s) does that for owning group and egid, and sticky (t) was mostly &lt;a href=&quot;https://en.wikipedia.org/wiki/Sticky_bit#History&quot;&gt;only used historically&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Directories repurpose the execute bits for “search”, which essentially means whether you can blindly access children (subject to the children’s permissions).
This is distinct from the read bits, which control whether you can list children, and write bits, for creating and deleting children.
They also repurpose the sticky bit for “restricted deletion”, where children can only be deleted by their owners, rather than anyone who can write to the directory (useful for /tmp).&lt;/p&gt;

&lt;p&gt;This permission model frankly sucks.
It’s clear that these twelve bits are a messy and leaky abstraction over filesystem access rights.
But I’ve never used ACL:s outside of Windows, and I don’t intend to change that any time soon.
I know that ls(1) indicates ACL:s with a plus (drwxrwxrwx+), that there are apparently “POSIX” and “NFS” flavours of ACL:s, and… that’s pretty much it.&lt;/p&gt;

&lt;h3 id=&quot;ownership-and-creation&quot;&gt;Ownership and creation&lt;/h3&gt;

&lt;p&gt;When you create a file, it’s owned by you (euid), but the owning group depends.
Unfortunately, sometimes it’s your “current” group (egid), which is controlled by newgrp(1) and defaults to your “login” group, which is usually something like “users” or “staff” or a group with the same name as your user.&lt;/p&gt;

&lt;p&gt;FreeBSD makes group ownership of directories easy, because new files always inherit the owning group, so that “sometimes” is never! But on Linux, they’re only inherited when the parent is setgid, and otherwise take their owning group from you (egid).&lt;/p&gt;

&lt;p&gt;Unix requires the nine main permission bits upfront when creating a file or directory, and convention is for programs that don’t know or care about these bits to give 666&lt;sub&gt;8&lt;/sub&gt; (u+rw, g+rw, o+rw) or 777&lt;sub&gt;8&lt;/sub&gt; (u+rwx, g+rwx, o+rwx) respectively.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// create a directory (u+rwx, g+rwx, o+rwx)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;mkdir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;path/to/foo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mo&quot;&gt;0777&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// create an executable file (u+rwx, g+rwx, o+rwx)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;creat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;path/to/bar&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mo&quot;&gt;0777&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// create a non-executable file (u+rw, g+rw, o+rw)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;creat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;path/to/baz&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mo&quot;&gt;0666&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;The bits are then filtered by the &lt;em&gt;umask&lt;/em&gt;, an environmental setting that almost always defaults to 022&lt;sub&gt;8&lt;/sub&gt; (g-w, o-w).
This is where the common permissions of 644&lt;sub&gt;8&lt;/sub&gt; (u+rw, g+r, o+r) for files and 755&lt;sub&gt;8&lt;/sub&gt; (u+rwx, g+rx, o+rx) for directories comes from.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ls -l path/to
drwxr-xr-x  [...]  foo
drwxr-xr-x  [...]  bar
drw-r--r--  [...]  baz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;h2 id=&quot;shared-storage&quot;&gt;Shared storage&lt;/h2&gt;

&lt;p&gt;Let’s say we have some groups.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ rg delan,aria /etc/group
wheel:*:0:root,delan,aria
delanria:*:1003:delan,aria
sonarr:*:351:delan,aria
radarr:*:352:delan,aria
_sabnzbd:*:350:delan,aria,sonarr,radarr
qbittorrent:*:850:delan,aria,sonarr,radarr
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;Let’s also say I have a notes directory and a software directory, both of which are shared with my partner including write permissions (g+w).&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ls -la /ocean/notes
drwxrwxr-x  [...]  delan  delanria  [...]  .

$ ls -la /ocean/software
drwxrwxr-x  [...]  delan  delanria  [...]  .
drwxrwxr-x  [...]  delan  delanria  [...]  accounting
drwxrwxr-x  [...]  delan  delanria  [...]  benchmarks
drwxrwxr-x  [...]  delan  delanria  [...]  drivers
                                           ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;When adding a new note, or adding a category of software, the immediate problem we run into is that a umask of 022&lt;sub&gt;8&lt;/sub&gt; (g-w, o-w) makes the new directory group-read-only (g+rx).&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;delan@storage$ echo bread &amp;gt; /ocean/notes/buy
delan@storage$ mkdir /ocean/software/fonts
aria@storage$ echo soul &amp;gt; /ocean/notes/sell
aria@storage$ mkdir /ocean/software/games

$ ls -ld /ocean/notes/{,buy,sell}
drwxrwxr-x  [...]  delan  delanria  [...]  .
-rw-r--r--  [...]  delan  delanria  [...]  buy
-rw-r--r--  [...]  aria   delanria  [...]  sell

$ ls -ld /ocean/software/{,fonts,games}
drwxrwxr-x  [...]  delan  delanria  [...]  .
drwxr-xr-x  [...]  delan  delanria  [...]  fonts
drwxr-xr-x  [...]  aria   delanria  [...]  games
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;This prevents the other partner from changing notes or adding software, unless we periodically “fix up” the permissions in common areas.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;delan@storage$ echo out &amp;gt;&amp;gt; /ocean/notes/sell
zsh: permission denied: /ocean/notes/sell

aria@storage$ cd /ocean/software/fonts
aria@storage$ curl -sSO https://fonts.example.com/iosevka.otf
curl: (23) Failure writing output to destination

$ chmod -R g+w /ocean/{notes,software}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;h2 id=&quot;login-classes&quot;&gt;Login classes&lt;/h2&gt;

&lt;p&gt;We can avoid these problems by setting our umask to 002&lt;sub&gt;8&lt;/sub&gt; (o-w), which is controlled by each user’s login class.
In my login.conf(5), many settings are defined only in the “default” login class, including the umask, which are directly inherited by the other classes.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ cat /etc/login.conf
default:\
	:passwd_format=sha512:\
	:...:\
	:umask=022:
standard:\
	:tc=default:
daemon:\
	:...:\
	:tc=default:
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;Put a pin in the line with “passwd_format”, we’ll need that later.&lt;/p&gt;

&lt;p&gt;If we change the umask setting and rebuild, logging in yields the expected umask, fixing the scenarios above… except when switching users with sudo(8).
Switching users with su(1) &lt;em&gt;via&lt;/em&gt; sudo(8) works as expected, so what gives?&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# vim /etc/login.conf
# cap_mkdb /etc/login.conf
# su -l delan
delan@storage$ umask
002

delan@storage$ sudo -iu aria umask
022

delan@storage$ sudo su -l aria -c &apos;umask&apos;
002
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;h2 id=&quot;fixing-sudo&quot;&gt;Fixing sudo(8)&lt;/h2&gt;

&lt;p&gt;At first, the only way we can get the expected umask when switching users with sudo(8) is to explicitly ask for a login class, such as the user’s default class:&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;delan@storage$ sudo -iu aria -c - umask
002
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;This is because of defaults for three &lt;a href=&quot;https://man.freebsd.org/sudoers(5)&quot;&gt;sudoers(5) settings&lt;/a&gt;: use_loginclass, umask_override, and umask.
These settings mean that sudo(8) forms the new umask as follows.&lt;/p&gt;

&lt;p&gt;use_loginclass is off, so we start by taking our umask from the environment in which sudo was invoked, in this case, 002&lt;sub&gt;8&lt;/sub&gt;.
umask_override is off, so our next step will do a bitwise OR, rather than replacing our umask entirely.
The umask setting is 022&lt;sub&gt;8&lt;/sub&gt;, so our final umask is 002&lt;sub&gt;8&lt;/sub&gt; OR 022&lt;sub&gt;8&lt;/sub&gt;, which is… well… 022&lt;sub&gt;8&lt;/sub&gt;.&lt;/p&gt;

&lt;p&gt;To fix this, we can turn use_loginclass on, which usually&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; takes our initial umask from the user’s default login class, or turn the umask setting off, which tells sudo(8) not to modify that initial umask.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Defaults use_loginclass  # option 1: use_loginclass on
Defaults !umask          # option 2: umask off

# option 3: umask off, but in a way that makes no sense
# (seriously, sudo authors, why did you add this case?
#  this just makes it impossible to actually set 0777!)
Defaults umask=0777
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;h2 id=&quot;fixing-services&quot;&gt;Fixing services&lt;/h2&gt;

&lt;p&gt;According to the &lt;a href=&quot;https://man.freebsd.org/rc.subr(8)&quot;&gt;rc.subr(8) manual&lt;/a&gt;, services ostensibly use the “daemon” login class by default.
But despite setting the umask in all of our login classes to 002&lt;sub&gt;8&lt;/sub&gt; (o-w), services like &lt;em&gt;qbittorrent&lt;/em&gt; and &lt;em&gt;sabnzbd&lt;/em&gt; continue to create things group-read-only.
Once again, what gives?&lt;/p&gt;

&lt;p&gt;As the manual says, the ${name}_login_class is used with ${name}_limits.
The former points to a login class containing our initial set of resource limits, and the latter overrides those limits, by way of limits(1).
Indeed, if we look under the hood, the login class is &lt;em&gt;only&lt;/em&gt; ever used in the arguments passed to limits(1).&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ rg _login_class /etc/rc.subr
786:#   ${name}_login_class n   Login class to use, else &quot;daemon&quot;.
969:        _prepend=\$${name}_prepend  _login_class=\${${name}_login_class:-daemon} \
1124:                   _doit=&quot;$_cd limits -C $_login_class $_limits $_doit&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;But resource limits aren’t the only settings defined by login classes, which as we saw earlier, also says things like “passwd_format is sha512”!
So this begs the question: is umask considered a resource limit for the purposes of limits(1)?&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;root@storage$ umask 000; ulimit 69
root@storage$ limits sh -c &apos;umask; ulimit&apos;
0000
69

root@storage$ limits -C daemon sh -c &apos;umask; ulimit&apos;
0000
unlimited
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;No.
In fact, not only is the umask of the “daemon” login class not consulted when running a service, but rc(8) and init(8) themselves don’t even run in a login class.
You can see the former for yourself by adding a couple of lines to /etc/rc and rebooting.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ head -2 /etc/rc
#!/bin/sh
umask=$(umask)          # add this

$ tail -2 /etc/rc
echo &quot;umask is $umask&quot;  # add this
exit 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;As for the latter, you can write a script that spews out the umask, then reboot and tell loader(8) to tell init(8) to immediately exec that script.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sudo chmod +x /root/umask
$ cat /root/umask
#!/bin/sh
while :; do umask; done

loader&amp;gt; set init_exec=/root/umask
loader&amp;gt; boot
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;In both cases, the umask is 022&lt;sub&gt;8&lt;/sub&gt; (g-w, o-w).
This is because login classes aren’t magic, nor are they omnipotent!
The only processes subject to them are those spawned by login(1), or things like login(1)&lt;sup id=&quot;fnref:3&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;, such as su(1), sudo(8), or sshd(8).&lt;/p&gt;

&lt;p&gt;All other processes ultimately inherit their umask from the “kernel” process (pid 0), whose umask is hardcoded to, you guessed it, 022&lt;sub&gt;8&lt;/sub&gt; (g-w, o-w).&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// sys/kern/init_main.c&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;proc0_init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dummy&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__unused&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;c1&quot;&gt;// [...]&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p_pd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pdinit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;c1&quot;&gt;// [...]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// sys/kern/kern_descrip.c&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pwddesc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;pdinit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pwddesc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pdp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;keeplock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;c1&quot;&gt;// [...]&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;newpdp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pd_cmask&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CMASK&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;c1&quot;&gt;// [...]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// sys/sys/param.h&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#define	CMASK	022		&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;/* default file mask: S_IWGRP|S_IWOTH */&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;So getting back on task, how do we run our services with another umask?
One way might be to add a line setting the umask to the beginning of /etc/rc, but this is rather drastic, and the security of this… smells questionable.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://forums.freebsd.org/threads/setting-umask-on-daemon.75069/&quot;&gt;The solution I’ve settled on&lt;/a&gt; is to sneak a umask command into the per-service rc.conf(5) for specific services.
This works because in rc.subr(5), load_rc_config executes /etc/rc.conf “if it has not yet been read in” (whatever that means), then executes /etc/rc.conf.d/foo if it exists.
Most of the time, these files contain variables only, but they’re just shell scripts.
It’s shell scripts all the way down.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ls -l /etc/rc.conf.d
-rw-r--r--  [...]  _umask
lrwxr-xr-x  [...]  qbittorrent -&amp;gt; _umask
lrwxr-xr-x  [...]  radarr -&amp;gt; _umask
lrwxr-xr-x  [...]  sabnzbd -&amp;gt; _umask
lrwxr-xr-x  [...]  sonarr -&amp;gt; _umask

$ cat /etc/rc.conf.d/_umask
umask 002
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;h2 id=&quot;fixing-samba&quot;&gt;Fixing samba(8)&lt;/h2&gt;

&lt;p&gt;That rc.conf(5) hack doesn’t work for samba(8), where the umask that applies to things created by clients is controlled by internal configuration, just like sudo(8).
In this case, “create mode” and “directory mode” are the settings to change.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[global]
create mode = 0775     # like umask 002
directory mode = 0775  # like umask 002
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;While we’re at it, if you want to set execute bits on all new files, you can use “force create mode”.
Aria likes this, but I’m not so sure.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[global]
# force create mode = 0111  # like ugo+x
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;h2 id=&quot;funny-execute-bits&quot;&gt;Funny execute bits&lt;/h2&gt;

&lt;p&gt;At this point, it looked like we were done, but something caught my eye when Aria created some files for testing.
The files had owning user execute (u+x), but not the other two execute bits.
For the last bloody time, what gives?&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ls -l
-rwxrw-r--  [...]  aria  delanria  [...]  foo.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;Turns out there’s an old samba(8) feature that repurposes the three execute bits for the three legacy DOS attributes respectively: archive, system, hidden.
The setting for the first execute bit (“map archive”) is on by default, and Windows had created the file with the archive bit on, hence owning user execute (u+x)!&lt;/p&gt;

&lt;p&gt;Nowadays extended attributes are a better way to store those attributes, which is on by default as “store dos attributes”.
But I was worried that I would need to do something messy like vfs_streams_xattr(8), remembering that FreeBSD and ZFS don’t support xattrs, at least not until FreeBSD 13.
After all, ZFS says that they’re not supported!&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;root@storage$ zfs set xattr=on ocean
property &apos;xattr&apos; not supported on FreeBSD: permission denied
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;Turns out &lt;a href=&quot;https://forums.freebsd.org/threads/state-of-zfs-xattr-support-in-freebsd.55418/&quot;&gt;that error&lt;/a&gt; &lt;a href=&quot;https://unix.stackexchange.com/q/266913&quot;&gt;is misleading&lt;/a&gt;, and xattrs more or less work fine.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;root@storage$ lsextattr user foo.txt
foo.txt DOSATTRIB

(archive bit only)
root@storage$ getextattr -x user DOSATTRIB foo.txt
foo.txt 00 00 04 00 04 00 00 00 51 00 00 00 20
&amp;gt; 00 00 00 44 a8 2c de 2d b1 d7 01 44 a8 2c de
&amp;gt; 2d b1 d7 01

(archive + system + hidden)
root@storage$ getextattr -x user DOSATTRIB foo.txt
foo.txt 00 00 04 00 04 00 00 00 51 00 00 00 23
&amp;gt; 00 00 00 44 a8 2c de 2d b1 d7 01 44 a8 2c de
&amp;gt; 2d b1 d7 01

(none of those attributes)
root@storage$ getextattr -x user DOSATTRIB foo.txt
foo.txt 00 00 04 00 04 00 00 00 51 00 00 00 00
&amp;gt; 00 00 00 44 a8 2c de 2d b1 d7 01 44 a8 2c de
&amp;gt; 2d b1 d7 01
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;To top it all off, the smb.conf(5) manual says that “store dos attributes” &lt;em&gt;should&lt;/em&gt; have automatically disabled the execute-bit-based attribute mapping, but they actually don’t.
I guess the manual was wrong.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[global]
map archive = no
map system = no
map hidden = no
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;h2 id=&quot;what-did-we-learn&quot;&gt;What did we learn?&lt;/h2&gt;

&lt;p&gt;FreeBSD gives us owning group inheritance for free, but inheriting the group-writable bit requires changing the umask.
This can be done for human use by way of the login classes in login.conf(5), with special tweaks needed for sudo(8), but services only use login classes for resource limits, not the umask, which has a hardcoded default of 022&lt;sub&gt;8&lt;/sub&gt; (g-w, o-w).&lt;/p&gt;

&lt;p&gt;Most services can have the desired umask set imperatively in /etc/rc.conf.d, but samba(8) needs to be configured with its own “create mask” and “directory mask” settings.&lt;/p&gt;

&lt;p&gt;When in doubt, read the source code.&lt;/p&gt;

&lt;hr /&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;If the umask setting is &lt;em&gt;explicitly&lt;/em&gt; set (other than to turn it off), then the initial umask is always taken from the invoking environment, not login classes as you might expect from turning use_loginclass on. This includes explicitly setting it to 022&lt;sub&gt;8&lt;/sub&gt;, and yes, that means your 022&lt;sub&gt;8&lt;/sub&gt; and the default 022&lt;sub&gt;8&lt;/sub&gt; are different. sudo(8) is complicated. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;More precisely, things that call &lt;a href=&quot;https://man.freebsd.org/setusercontext(8)&quot;&gt;setusercontext(8)&lt;/a&gt; or &lt;a href=&quot;https://man.freebsd.org/setusercontext(8)&quot;&gt;setclasscontext(8)&lt;/a&gt; with flags containing LOGIN_SETUMASK. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name></name></author><category term="home" /><summary type="html">My partner and I share a home server for our storage needs, running on FreeBSD 12 with ZFS. We have our own users, delan and aria, and a group (delanria) that in theory we can use for common areas, like our software collection. Services like torrent clients and media libraries have their own users too, and all of these users need to write to things others have created. This was easier said than done.</summary></entry><entry><title type="html">Chromium spelling and grammar features</title><link href="https://www.azabani.com/2021/05/17/spelling-grammar.html" rel="alternate" type="text/html" title="Chromium spelling and grammar features" /><published>2021-05-17T10:30:00+00:00</published><updated>2021-05-17T10:30:00+00:00</updated><id>https://www.azabani.com/2021/05/17/spelling-grammar</id><content type="html" xml:base="https://www.azabani.com/2021/05/17/spelling-grammar.html">&lt;p&gt;Back in September, I wrote about &lt;a href=&quot;/2020/09/27/my-internship-with-igalia.html&quot;&gt;my wonderful internship&lt;/a&gt; with Igalia’s web platform team.
I’m thrilled to have since joined Igalia full-time, starting in the very last week of last year.
My first project has been implementing the new CSS spelling and grammar features in Chromium.
Life has been pretty hectic since Aria and I moved back to Perth, but more on that in another post.
For now, let’s step back and review our progress.&lt;/p&gt;

&lt;style&gt;
article &gt; figure &gt; img { max-width: 100%; }
article &gt; figure &gt; figcaption { max-width: 30rem; margin-left: auto; margin-right: auto; }
article &gt; pre, article &gt; code { font-family: Inconsolata, monospace, monospace; }
.local-demo { font-style: italic; font-weight: bold; color: rebeccapurple; }
.local-spelling, .local-grammar { text-decoration-thickness: 0; text-decoration-skip-ink: none; }
.local-spelling { text-decoration: red wavy underline; }
.local-grammar { text-decoration: green wavy underline; }
.local-table { font-size: 0.75em; }
.local-table td, .local-table th { vertical-align: top; border: 1px solid black; }
.local-table td:not(.local-tight), .local-table th:not(.local-tight) { padding: 0.5em; }
.local-tight picture, .local-tight img { vertical-align: top; }
.local-compare * + *, .local-tight * + * { margin-top: 0; }
.local-compare { max-width: 100%; border: 1px solid rebeccapurple; }
.local-compare &gt; div { max-width: 100%; position: relative; touch-action: pinch-zoom; --cut: 50%; }
.local-compare &gt; div &gt; * { vertical-align: top; max-width: 100%; }
.local-compare &gt; div &gt; :nth-child(1) { position: absolute; clip: rect(auto, auto, auto, var(--cut)); }
.local-compare &gt; div &gt; :nth-child(2) { position: absolute; width: var(--cut); height: 100%; border-right: 1px solid rebeccapurple; }
.local-compare &gt; div &gt; :nth-child(2):before { content: &quot;actual&quot;; color: rebeccapurple; font-size: 0.75em; position: absolute; right: 0.5em; }
.local-compare &gt; div &gt; :nth-child(2):after { content: &quot;ref&quot;; color: rebeccapurple; font-size: 0.75em; position: absolute; left: calc(100% + 0.5em); }
&lt;/style&gt;

&lt;p&gt;The squiggly lines that indicate possible &lt;span class=&quot;local-spelling&quot;&gt;spelling&lt;/span&gt; or &lt;span class=&quot;local-grammar&quot;&gt;grammar&lt;/span&gt; errors have been a staple of word processing on computers for decades.
But on the web, these indicators are powered by the browser, which doesn’t always have the information needed to place and render them most appropriately.
For example, authors might want to provide their own grammar checker (placement), or tweak colors to improve contrast (rendering).&lt;/p&gt;

&lt;p&gt;To address this, the CSS pseudo and text decoration specs have defined new pseudo-elements ::spelling-error and ::grammar-error, allowing authors to style those indicators, and new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text-decoration-line&lt;/code&gt; values &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spelling-error&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grammar-error&lt;/code&gt;, allowing authors to mark up their text with the same kind of decorations as native indicators.&lt;/p&gt;

&lt;h2 id=&quot;contents&quot;&gt;Contents&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#current-status&quot;&gt;Current status&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#cjk-css-unification&quot;&gt;CSS unification&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#fifteen-years-in-the-making&quot;&gt;Fifteen years in the making&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#highlight-painting&quot;&gt;Highlight painting&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#shadows-and-backgrounds&quot;&gt;Shadows and backgrounds&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#shadow-clipping&quot;&gt;Shadow clipping&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#vertical-vertigo&quot;&gt;Vertical vertigo&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#cursed&quot;&gt;Cursed&lt;/a&gt; &lt;!-- and [screaming](#-aaaaaaaaaaaaa) --&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#processing-model&quot;&gt;Processing model&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#stay-tuned&quot;&gt;Stay tuned!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;current-status&quot;&gt;Current status&lt;/h2&gt;

&lt;p&gt;I’ve sent &lt;a href=&quot;https://groups.google.com/a/chromium.org/g/blink-dev/c/8UEcRJViPEU/m/YZml0HGxCQAJ&quot;&gt;an Intent to Prototype&lt;/a&gt;, as well as requests for positions from &lt;a href=&quot;https://github.com/mozilla/standards-positions/issues/470&quot;&gt;Mozilla&lt;/a&gt; and &lt;a href=&quot;https://lists.webkit.org/pipermail/webkit-dev/2021-January/031660.html&quot;&gt;Apple&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I’ve landed &lt;a href=&quot;https://crrev.com/c/2606878&quot;&gt;a patch&lt;/a&gt; that paves the way for ::spelling-error + ::grammar-error support internally, and I’m hopefully(!) around halfway done with implementing both the new painting rules and the new processing model.&lt;/p&gt;

&lt;p&gt;The spec updates, led by Florian Rivoal, were largely done by the end of 2017.
As the first impl of both the features themselves &lt;em&gt;and&lt;/em&gt; much of the underlying highlight specs, there were always going to be questions and rough edges to be clarified.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/2474&quot;&gt;Two&lt;/a&gt; &lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/3932&quot;&gt;issues&lt;/a&gt; were raised before we even started, I’ve since sent in &lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/6022&quot;&gt;another&lt;/a&gt; &lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/6264&quot;&gt;two&lt;/a&gt;, and I’ll need to raise at least two more by the time we’re done.
I’ve also landed &lt;a href=&quot;https://crrev.com/c/2624328&quot;&gt;three&lt;/a&gt; &lt;a href=&quot;https://crrev.com/c/2670609&quot;&gt;WPT&lt;/a&gt; &lt;a href=&quot;https://crrev.com/c/2706442&quot;&gt;patches&lt;/a&gt;, including &lt;a href=&quot;https://wpt.live/css/css-pseudo/highlight-painting-001.html&quot;&gt;three&lt;/a&gt; &lt;a href=&quot;https://wpt.live/css/css-pseudo/highlight-painting-002.html&quot;&gt;new&lt;/a&gt; &lt;a href=&quot;https://wpt.live/css/css-pseudo/highlight-painting-003.html&quot;&gt;tests&lt;/a&gt; and fixes for countless more.&lt;/p&gt;

&lt;figure&gt;
&lt;div class=&quot;local-compare&quot; style=&quot;width: 300px; margin: 0 auto;&quot;&gt;&lt;img src=&quot;/images/spammar-6.png&quot; /&gt;&lt;img src=&quot;/images/spammar-7.png&quot; /&gt;&lt;/div&gt;
&lt;figcaption&gt;
    &lt;p&gt;&lt;a href=&quot;https://wpt.live/css/css-pseudo/highlight-painting-003.html&quot;&gt;highlight-painting-003.html&lt;/a&gt;&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;In the course of my work on these features, I’ve already fixed at least &lt;a href=&quot;https://crbug.com/474335&quot;&gt;two&lt;/a&gt; &lt;a href=&quot;https://crbug.com/1078474&quot;&gt;other&lt;/a&gt; bugs that weren’t of my own creation, and reported four more:&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;&lt;table class=&quot;local-table&quot;&gt;
&lt;tr&gt;&lt;th&gt;&lt;a href=&quot;https://crbug.com/1171741&quot;&gt;1171741&lt;/a&gt;&lt;/th&gt;&lt;td&gt;Selecting text causes emphasis marks to be painted twice&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th&gt;&lt;a href=&quot;https://crbug.com/1172177&quot;&gt;1172177&lt;/a&gt;&lt;/th&gt;&lt;td&gt;Erroneous viewport-size-dependent clipping of some text shadows&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th&gt;&lt;a href=&quot;https://crbug.com/1176649&quot;&gt;1176649&lt;/a&gt;&lt;/th&gt;&lt;td&gt;text-shadow paints with incorrect offset for vertical scripts in vertical writing modes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th&gt;&lt;a href=&quot;https://crbug.com/1180068&quot;&gt;1180068&lt;/a&gt;&lt;/th&gt;&lt;td&gt;text-shadow erroneously paints over text proper in mixed upright/sideways fragments&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;&lt;/div&gt;&lt;/figure&gt;

&lt;h2 id=&quot;cjk-css-unification&quot;&gt;&lt;del&gt;CJK&lt;/del&gt; CSS unification&lt;/h2&gt;

&lt;p&gt;My colleague Rego noticed that the squiggly lines for spelling and grammar errors look slightly different to a naïve &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;red&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;green wavy underline&lt;/code&gt;.
How, why, and should we unify squiggly and wavy lines?
Some further investigation revealed that the two kinds of decorations are drawn very differently with completely separate code paths.&lt;/p&gt;

&lt;figure&gt;
&lt;div class=&quot;scroll&quot;&gt;
&lt;table class=&quot;local-table&quot;&gt;
    &lt;thead&gt;
        &lt;tr&gt;
            &lt;th colspan=&quot;2&quot;&gt;non-macOS (&lt;a class=&quot;local-demo&quot; href=&quot;https://bucket.daz.cat/work/igalia/0/0.html?color=red&amp;amp;style=wavy&amp;amp;line=underline&amp;amp;thickness=0&amp;amp;ink=none&quot;&gt;demo&lt;sub&gt;0&lt;/sub&gt;&lt;/a&gt;)&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td class=&quot;local-tight&quot;&gt;&lt;a href=&quot;/images/spammar-0.png&quot;&gt;&lt;img src=&quot;/images/spammar-0@t.png&quot; /&gt;&lt;/a&gt;&lt;/td&gt;
            &lt;td class=&quot;local-tight&quot;&gt;&lt;a href=&quot;/images/spammar-1.png&quot;&gt;&lt;img src=&quot;/images/spammar-1@t.png&quot; /&gt;&lt;/a&gt;&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
    &lt;tfoot&gt;
        &lt;tr&gt;&lt;th&gt;100%&lt;/th&gt;&lt;th&gt;200%&lt;/th&gt;&lt;/tr&gt;
    &lt;/tfoot&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;figcaption&gt;
    &lt;p&gt;Left (bolder text): nearest &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wavy&lt;/code&gt; decorations.
&lt;br /&gt;Right (lighter text): native squiggly lines.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The case for unifying squiggly and wavy lines became a lot more complicated too.
For example, our squiggly lines are actually dots on macOS.
More specifically, they are round dots with an alpha gradient, matching the platform’s native controls.
These details are beyond what can be expressed in terms of a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotted underline&lt;/code&gt;, so if we were to unify by making squiggly lines equivalent to such a decoration, we would lose that benefit.&lt;/p&gt;

&lt;figure&gt;
&lt;div class=&quot;scroll&quot;&gt;
&lt;table class=&quot;local-table&quot;&gt;
    &lt;thead&gt;
        &lt;tr&gt;
            &lt;th colspan=&quot;2&quot;&gt;macOS (&lt;a class=&quot;local-demo&quot; href=&quot;https://bucket.daz.cat/work/igalia/0/0.html?color=red&amp;amp;style=dotted&amp;amp;line=underline&amp;amp;thickness=3px&amp;amp;ink=none&quot;&gt;demo&lt;sub&gt;0&lt;/sub&gt;&lt;/a&gt;)&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td class=&quot;local-tight&quot;&gt;&lt;a href=&quot;/images/spammar-2.png&quot;&gt;&lt;img src=&quot;/images/spammar-2@t.png&quot; /&gt;&lt;/a&gt;&lt;/td&gt;
            &lt;td class=&quot;local-tight&quot;&gt;&lt;a href=&quot;/images/spammar-3.png&quot;&gt;&lt;img src=&quot;/images/spammar-3@t.png&quot; /&gt;&lt;/a&gt;&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
    &lt;tfoot&gt;
        &lt;tr&gt;&lt;th&gt;100%&lt;/th&gt;&lt;th&gt;200%&lt;/th&gt;&lt;/tr&gt;
    &lt;/tfoot&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;figcaption&gt;
&lt;figcaption&gt;
      &lt;p&gt;Left (bolder text): nearest &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotted&lt;/code&gt; decorations.
&lt;br /&gt;Right (lighter text): native squiggly lines.&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The spec doesn’t require that spelling-error and grammar-error lines be expressible in terms of other decoration lines, so unification won’t block shipping.
I decided it would be best to revisit this once I landed some patches and familiarised myself with the code.&lt;/p&gt;

&lt;h2 id=&quot;fifteen-years-in-the-making&quot;&gt;Fifteen years in the making&lt;/h2&gt;

&lt;p&gt;::spelling-error and ::grammar-error are defined as &lt;em&gt;highlight pseudo-elements&lt;/em&gt;, together with ::selection and ::target-text.
The spec’s processing model and rendering rules are both very different to how ::selection (or ::target-text) has been implemented in any browser so far.
Now that we’re implementing more than just the first couple of pseudos, we really ought to comply with the new spec, which complicates our job somewhat.&lt;/p&gt;

&lt;p&gt;I’ll talk about ::selection a fair bit below, because most of the spec discussion I found happened before the others were defined, going back as far as 2006.
Highlight pseudos like ::selection are tricky because they aren’t &lt;em&gt;tree-abiding&lt;/em&gt;: the selected parts of the document aren’t generally a child of any one element.&lt;/p&gt;

&lt;p&gt;But even then, how hard could it be?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;What &lt;em&gt;is&lt;/em&gt; ::selection? How does it interact with other pseudo-elements? Is it a singleton, or does each element have a ::selection pseudo-element? How do we reconcile the ::selection “tree”, if any, with the element tree?&lt;/li&gt;
  &lt;li&gt;Can child ::selection styles override parent ::selection styles? What about the child’s “real element” styles? How exactly do parent ::selection styles propagate to child ::selection styles? Do we use a tweaked cascade or tweaked inheritance?&lt;/li&gt;
  &lt;li&gt;What happens when authors specify ::selection styles that affect layout? What about styles that rely on how ::selection relates to the element tree, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;outline&lt;/code&gt; or translucent &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background-color&lt;/code&gt;?&lt;/li&gt;
  &lt;li&gt;What happens when child ::selection styles specify only &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;color&lt;/code&gt; or only &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background-color&lt;/code&gt; but not both? Does the other inherit as usual? If we want a special case tying these two properties together, how does it interact with other properties?&lt;/li&gt;
  &lt;li&gt;Does the ::selection &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background-color&lt;/code&gt; paint over text, or under it? What about “replaced” content like images? If we paint over text, do we need to make the author’s color translucent, and if so, how?&lt;/li&gt;
  &lt;li&gt;Is text in the ::selection &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;color&lt;/code&gt; painted in addition to, or instead of, the same text in its original &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;color&lt;/code&gt;? What about &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background-color&lt;/code&gt;?&lt;/li&gt;
  &lt;li&gt;Can the default UA stylesheet describe the platform’s ::selection style? How?&lt;/li&gt;
  &lt;li&gt;How naughty were browsers that implemented ::selection without a -vendor-prefix before it was standardised? Are vendor prefixes even a good idea?&lt;/li&gt;
  &lt;li&gt;Most importantly, how do we introduce a new processing model and rendering rules without breaking existing content?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For answers to most of these questions, check out my &lt;a class=&quot;local-demo&quot; href=&quot;https://bucket.daz.cat/work/igalia/0/5.html&quot;&gt;notes&lt;sub&gt;5&lt;/sub&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;By the time I started to understand the problem space, two weeks had passed.&lt;/p&gt;

&lt;figure&gt;
    &lt;img src=&quot;/images/spammar-charlie.jpg&quot; /&gt;
    &lt;figcaption&gt;Pretty intense for my very first foray into www-style!&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;highlight-painting&quot;&gt;Highlight painting&lt;/h2&gt;

&lt;p&gt;The current spec isolates each highlight pseudo into an “overlay”, and allows each of them to have independent backgrounds, shadows, and other decorations.&lt;/p&gt;

&lt;p&gt;Like other browsers, Chromium implemented an older model, where matching ::selection rules are only used to &lt;em&gt;change&lt;/em&gt; things like the text color and shadows (except for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background-color&lt;/code&gt;, which has always been independent).&lt;/p&gt;

&lt;p&gt;But the closer I looked, the deeper the problems ran.&lt;/p&gt;

&lt;h3 id=&quot;shadows-and-backgrounds&quot;&gt;Shadows and backgrounds&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;everyone’s shadow code is complete made-up horseshit but mostly i blame the fact that someone decided to add ‘shadow’ to the (very small!) special list of styles ::selection could modify&lt;/p&gt;

  &lt;p&gt;— Gankra, &lt;a href=&quot;https://twitter.com/Gankra_/status/1351020287790358530&quot;&gt;2021&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I whipped up a quick &lt;a class=&quot;local-demo&quot; href=&quot;https://bucket.daz.cat/work/igalia/0/3.html&quot;&gt;demo&lt;sub&gt;3&lt;/sub&gt;&lt;/a&gt; with some backgrounds and shadows, and the result was… not good.
“So the originating text shadow (yellow) paints over the ::selection background (grey), except when it paints under, and sometimes it even paints over the text (black)?
Why is the ::selection shadow clipped to the ::selection background?
&lt;em&gt;What?”&lt;/em&gt;&lt;/p&gt;

&lt;figure&gt;
&lt;div class=&quot;local-compare&quot; style=&quot;width: 300px; margin: 0 auto;&quot;&gt;&lt;img src=&quot;/images/spammar-4.png&quot; /&gt;&lt;img src=&quot;/images/spammar-5.png&quot; /&gt;&lt;/div&gt;
&lt;figcaption&gt;
    &lt;p&gt;&lt;a href=&quot;https://wpt.live/css/css-pseudo/highlight-painting-001.html&quot;&gt;highlight-painting-001.html&lt;/a&gt; (based on &lt;a class=&quot;local-demo&quot; href=&quot;https://bucket.daz.cat/work/igalia/0/3.html&quot;&gt;demo&lt;sub&gt;3&lt;/sub&gt;&lt;/a&gt;)&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Some of these were easier to fix than others.
To fix backgrounds, we essentially push the code that paints the background waaaaay down NG­Text­Fragment­Painter, so that it’s before painting the selected text but after pretty much everything else.
We then fix shadows similarly, reordering the text paints from “before with shadows, after with shadows, selected with shadows” to an order that keeps shadows behind text.&lt;/p&gt;

&lt;p&gt;These initial fixes are now live in Chromium 90, but we still need to deal with the ::selection shadow clipping.
What’s up with that?&lt;/p&gt;

&lt;h3 id=&quot;shadow-clipping&quot;&gt;Shadow clipping&lt;/h3&gt;

&lt;p&gt;The weird shadow clipping was a side effect of how we ensured that the ::selection text color changes &lt;em&gt;exactly&lt;/em&gt; where the ::selection background starts:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;we clip out and paint the selected text in original color, then&lt;/li&gt;
  &lt;li&gt;we clip (in) and paint the selected text in ::selection color.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is useful for both subtle reasons, like ink overflow…&lt;/p&gt;

&lt;figure&gt;
&lt;div class=&quot;scroll&quot;&gt;
&lt;table class=&quot;local-table&quot;&gt;
    &lt;thead&gt;
        &lt;tr&gt;
            &lt;th colspan=&quot;2&quot;&gt;&lt;a class=&quot;local-demo&quot; href=&quot;https://bucket.daz.cat/work/igalia/0/6.html?t=d%C3%AElan&amp;amp;wm=horizontal-tb&amp;amp;tcu=none&amp;amp;fs=italic&amp;amp;p=0&amp;amp;q=1&amp;amp;minimal&quot;&gt;demo&lt;sub&gt;6&lt;/sub&gt;&lt;/a&gt;&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td class=&quot;local-tight&quot;&gt;&lt;picture&gt;
                &lt;source srcset=&quot;/images/spammar-8.png 2x&quot; /&gt;
                &lt;img src=&quot;/images/spammar-8.png&quot; /&gt;
            &lt;/picture&gt;&lt;/td&gt;
            &lt;td class=&quot;local-tight&quot;&gt;&lt;picture&gt;
                &lt;source srcset=&quot;/images/spammar-8@q.png 1x&quot; /&gt;
                &lt;img src=&quot;/images/spammar-8@q.png&quot; /&gt;
            &lt;/picture&gt;&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;…and not so subtle reasons, like allowing the user to clearly and precisely select graphemes in ligature-heavy languages like Sorani.
In this example, &lt;span lang=&quot;ckb&quot;&gt;یلا&lt;/span&gt; is three letters (&lt;span lang=&quot;kmr&quot;&gt;îla&lt;/span&gt;), but only two glyphs.
This isn’t explicitly required by any spec, but it’s definitely intentional.&lt;/p&gt;

&lt;figure&gt;
&lt;div class=&quot;scroll&quot;&gt;
&lt;table class=&quot;local-table&quot;&gt;
    &lt;thead&gt;
        &lt;tr&gt;
            &lt;th colspan=&quot;2&quot;&gt;&lt;a class=&quot;local-demo&quot; href=&quot;https://bucket.daz.cat/work/igalia/0/6.html?t=%D8%AF%DB%8C%D9%84%D8%A7%D9%86&amp;amp;wm=horizontal-tb&amp;amp;tcu=none&amp;amp;fs=normal&amp;amp;p=2&amp;amp;q=3&amp;amp;minimal&quot;&gt;demo&lt;sub&gt;6&lt;/sub&gt;&lt;/a&gt;&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td class=&quot;local-tight&quot;&gt;&lt;picture&gt;
                &lt;source srcset=&quot;/images/spammar-9.png 2x&quot; /&gt;
                &lt;img src=&quot;/images/spammar-9.png&quot; /&gt;
            &lt;/picture&gt;&lt;/td&gt;
            &lt;td class=&quot;local-tight&quot;&gt;&lt;picture&gt;
                &lt;source srcset=&quot;/images/spammar-9@q.png 1x&quot; /&gt;
                &lt;img src=&quot;/images/spammar-9@q.png&quot; /&gt;
            &lt;/picture&gt;&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;If you use Chromium, you may notice that the ref for that demo appears to select more text.
What we’re really doing with ::selection painting is &lt;em&gt;pretending&lt;/em&gt; that ligatures are divisible into horizontal parts and &lt;em&gt;guessing&lt;/em&gt; how wide each part is.
Current font technology just doesn’t provide the metadata to do this more “correctly”.&lt;/p&gt;

&lt;p&gt;Firefox always allows splitting ligature styles, including with real elements, and there are &lt;a href=&quot;https://gankra.github.io/blah/text-hates-you/#style-can-change-mid-ligature&quot;&gt;at least two good arguments&lt;/a&gt; in favour of this approach.
Chromium has (reasonably) decided that while the technique is ok for ::selection, perhaps even desirable, it’s &lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=1147859#c9&quot;&gt;not the way to go for ordinary markup&lt;/a&gt;.&lt;/p&gt;

&lt;figure&gt;
&lt;div class=&quot;scroll&quot;&gt;
&lt;table class=&quot;local-table&quot;&gt;
    &lt;thead&gt;
        &lt;tr&gt;
            &lt;th&gt;&lt;a class=&quot;local-demo&quot; href=&quot;https://bucket.daz.cat/work/igalia/0/4.html&quot;&gt;demo&lt;sub&gt;4&lt;/sub&gt;&lt;/a&gt;&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td class=&quot;local-tight&quot;&gt;&lt;picture&gt;
                &lt;source srcset=&quot;/images/spammar-a.png 2x&quot; /&gt;
                &lt;img src=&quot;/images/spammar-a.png&quot; /&gt;
            &lt;/picture&gt;&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;But anyway, back to the point at hand.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text-shadow&lt;/code&gt; means “paint the text again, under the text proper, with these colors and offsets”.
We want to clip the ::selection shadow for the same reasons we clip the text proper in ::selection color, but the coordinates need to be offset for each shadow.
That we &lt;em&gt;don’t&lt;/em&gt; is the bug here.&lt;/p&gt;

&lt;figure&gt;
&lt;div class=&quot;scroll&quot;&gt;
&lt;table class=&quot;local-table&quot;&gt;
    &lt;thead&gt;
        &lt;tr&gt;
            &lt;th&gt;&lt;a class=&quot;local-demo&quot; href=&quot;https://bucket.daz.cat/work/igalia/0/6.html?t=quick&amp;amp;wm=horizontal-tb&amp;amp;tcu=none&amp;amp;fs=normal&amp;amp;p=1&amp;amp;q=4&amp;amp;noyellow&amp;amp;scbug&quot;&gt;demo&lt;sub&gt;6&lt;/sub&gt;&lt;/a&gt;&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td class=&quot;local-tight&quot;&gt;&lt;picture&gt;
                &lt;source srcset=&quot;/images/spammar-b.png 2x&quot; /&gt;
                &lt;img src=&quot;/images/spammar-b.png&quot; /&gt;
            &lt;/picture&gt;&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;figcaption&gt;When painting the ::selection shadow (blue), we need to clip the canvas to the dotted line, but we were actually clipping to the solid line.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Consensus seems to be that &lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/3932&quot;&gt;not doing so is undesirable&lt;/a&gt;, and in theory, fixing this would be straightforward, but in practice… 😵‍💫&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;The first confounding factor was that NG­Text­Fragment­Painter and NG­Text­Painter were… a tangled mess.
Even the owners weren’t sure this was the most helpful architecture:&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// TODO(layout-dev): Does this distinction make sense?&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CORE_EXPORT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NGTextPainter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TextPainterBase&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;Years of typographical features have been duct-taped on without a systemic approach to managing complexity, including decorations, shadows, ellipses, background clipping, RTL text, vertical text, ruby text, emphasis marks, print rendering, drag-and-drop rendering, selections, highlights, “markers”, and SVG features like stroke and fill.&lt;/p&gt;

&lt;p&gt;A third of the logic was in Text­Painter­Base, so good luck not breaking legacy.
Shadows were painted with a now-deprecated Skia feature called a Draw­Looper, which allows you to repeat a procedure a bunch of times with different tweaks, such as canvas transformations and color changes.
It’s almost specifically designed for shadows, but it’s technically possible to repeat procedures that have nothing to do with drawing text.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// SkCanvas* canvas;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// SkPaint paint;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// SkScalar x, y;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// sk_sp&amp;lt;SkTextBlob&amp;gt; blob;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// sk_sp&amp;lt;SkDrawLooper&amp;gt; looper;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;looper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;canvas&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;paint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SkCanvas&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SkPaint&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// procedure to be looped&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;drawTextBlob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;My solution was based on the observation that loopers draw offset shadows by “moving” the canvas with a transform before each iteration, but transforming the canvas only affects &lt;em&gt;subsequent&lt;/em&gt; operations.
We were clipping the canvas once, before running the looper, but if we could somehow reclip the canvas after each transform, the clip region would “move” together with each shadow, and we wouldn’t even need to change the coordinates!&lt;/p&gt;

&lt;p&gt;I prototyped a fix that seemed to handle everything I threw at it, and informed by the challenges that involved, I also refactored out the code for selections, highlights, and markers.
Stephen and I decided that adding clipping as a fixed function to Draw­Looper made more sense than adding it to the procedure.
At the time, this was true.&lt;/p&gt;

&lt;figure&gt;
&lt;div class=&quot;scroll&quot;&gt;
&lt;table class=&quot;local-table&quot;&gt;
    &lt;thead&gt;
        &lt;tr&gt;
            &lt;th colspan=&quot;2&quot;&gt;&lt;a class=&quot;local-demo&quot; href=&quot;https://bucket.daz.cat/work/igalia/0/6.html?t=%D8%AF%DB%8C%D9%84%D8%A7%D9%86&amp;amp;wm=vertical-rl&amp;amp;tcu=none&amp;amp;fs=normal&amp;amp;p=1&amp;amp;q=4&quot;&gt;demo&lt;sub&gt;6&lt;/sub&gt;&lt;/a&gt;&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td class=&quot;local-tight&quot;&gt;&lt;picture&gt;
                &lt;source srcset=&quot;/images/spammar-c.png 1x&quot; /&gt;
                &lt;img src=&quot;/images/spammar-c.png&quot; /&gt;
            &lt;/picture&gt;&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;figcaption&gt;
    &lt;p&gt;The prototype made my most complex test case (at the time) pass, with the exception of ink overflow color, which was a limitation of my ref (&lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=1147859#c11&quot;&gt;both renderings are acceptable&lt;/a&gt;).&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I then took a couple weeks off to move to Perth.&lt;/p&gt;

&lt;h3 id=&quot;vertical-vertigo&quot;&gt;Vertical vertigo&lt;/h3&gt;

&lt;p&gt;“Wait… isn’t the original purpose of vertical writing modes, you know, vertical &lt;em&gt;scripts&lt;/em&gt;? I wonder if those work as well as horizontal scripts being rotated sideways…”&lt;/p&gt;

&lt;p&gt;“…what? Let’s see what they look like &lt;em&gt;without&lt;/em&gt; my patch…”&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“…what?”&lt;/em&gt;&lt;/p&gt;

&lt;figure&gt;
&lt;div class=&quot;scroll&quot;&gt;
&lt;table class=&quot;local-table&quot;&gt;
    &lt;thead&gt;
        &lt;tr&gt;
            &lt;th colspan=&quot;2&quot;&gt;&lt;a class=&quot;local-demo&quot; href=&quot;https://bucket.daz.cat/work/igalia/0/6.html?wm=vertical-rl&amp;amp;t=%E4%BD%A0%E5%A5%BD&amp;amp;range=1,2&quot;&gt;demo&lt;sub&gt;6&lt;/sub&gt;&lt;/a&gt;&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td class=&quot;local-tight&quot;&gt;&lt;picture&gt;
                &lt;source srcset=&quot;/images/spammar-d.png 2x&quot; /&gt;
                &lt;img src=&quot;/images/spammar-d.png&quot; /&gt;
            &lt;/picture&gt;&lt;/td&gt;
            &lt;td class=&quot;local-tight&quot;&gt;&lt;picture&gt;
                &lt;source srcset=&quot;/images/spammar-e.png 2x&quot; /&gt;
                &lt;img src=&quot;/images/spammar-e.png&quot; /&gt;
            &lt;/picture&gt;&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;figcaption&gt;
    &lt;p&gt;Left: vertical script in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vertical-rl&lt;/code&gt;, with patch.
&lt;br /&gt;Right: same test case, without patch.&lt;/p&gt;

    &lt;p&gt;Notice how the shadows are offset in the wrong direction.
They should be painted southeast of the text proper, but were being painted northeast.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;When painting a text fragment with a vertical &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;writing-mode&lt;/code&gt;, we rotate the canvas by 90° cw (or ccw for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sideways-lr&lt;/code&gt;).
This is good for horizontal scripts like Latin or Sorani, because they usually need to be painted sideways.&lt;/p&gt;

&lt;aside&gt;
  &lt;p&gt;Except when &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text-orientation&lt;/code&gt; is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;upright&lt;/code&gt;, which overrides the usual behaviour.&lt;/p&gt;
&lt;/aside&gt;

&lt;p&gt;But for vertical scripts like Han, we usually need to keep the canvas unrotated.
A single text fragment can contain text in multiple scripts, so we actually achieve this by rotating the canvas &lt;em&gt;back&lt;/em&gt; for the parts in vertical scripts.&lt;/p&gt;

&lt;aside&gt;
  &lt;p&gt;Except when &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text-orientation&lt;/code&gt; is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sideways&lt;/code&gt;, which overrides the usual behaviour.&lt;/p&gt;

  &lt;p&gt;Note that the way &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text-orientation&lt;/code&gt; is defined means that none of its values are actually supposed to affect the rendering of &lt;em&gt;vertical-only&lt;/em&gt; scripts like Mongolian.
I would suggest not thinking about this too hard.&lt;/p&gt;
&lt;/aside&gt;

&lt;p&gt;So far so good right?&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;This is what we were doing when painting text with vertical scripts and shadows (example limited to a single script and single shadow for simplicity):&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Let &lt;em&gt;space&lt;/em&gt; be our original “physical” coordinate space&lt;/li&gt;
  &lt;li&gt;Let &lt;em&gt;offset&lt;/em&gt; be the shadow’s offset in &lt;em&gt;space&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;Let &lt;em&gt;selection&lt;/em&gt; be the selection rect coordinates in &lt;em&gt;space&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;Vertical writing mode, so rotate canvas by 90°, yielding &lt;em&gt;space′&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;Let &lt;em&gt;offset′&lt;/em&gt; be the result of mapping &lt;em&gt;offset&lt;/em&gt; into &lt;em&gt;space′&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;Let &lt;em&gt;selection′&lt;/em&gt; be the result of mapping &lt;em&gt;selection&lt;/em&gt; into &lt;em&gt;space′&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Old:&lt;/strong&gt; clip the canvas to &lt;em&gt;selection′&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;Configure a Draw­Looper that will:
    &lt;ul&gt;
      &lt;li&gt;move the canvas by &lt;em&gt;offset′&lt;/em&gt;&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;New:&lt;/strong&gt; clip the canvas to &lt;em&gt;selection′&lt;/em&gt;&lt;/li&gt;
      &lt;li&gt;draw the text for the shadow&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Vertical script, so rotate canvas back by 90°, yielding &lt;em&gt;space″&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;Run the Draw­Looper, which carries out the steps above&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The looper is told to move and clip the canvas to &lt;em&gt;offset′&lt;/em&gt; and &lt;em&gt;selection′&lt;/em&gt;, which are coordinates in &lt;em&gt;space′&lt;/em&gt;, but when it eventually tries to do that, the canvas is in &lt;em&gt;space″&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;offset′&lt;/em&gt; being in the wrong space is why shadows have always been painted in the wrong place for vertical scripts.
By reordering the clip to &lt;em&gt;selection′&lt;/em&gt; so it happens after the rotation to &lt;em&gt;space″&lt;/em&gt;, we were now clipping the canvas to the wrong coordinates, which in turn made the text invisible in our &lt;a class=&quot;local-demo&quot; href=&quot;https://bucket.daz.cat/work/igalia/0/6.html?wm=vertical-rl&amp;amp;t=%E4%BD%A0%E5%A5%BD&amp;amp;range=1,2&quot;&gt;demo&lt;sub&gt;6&lt;/sub&gt;&lt;/a&gt;!&lt;/p&gt;

&lt;h3 id=&quot;cursed&quot;&gt;Cursed&lt;/h3&gt;

&lt;p&gt;Fixing this again proved harder than it seemed on the surface, because text painting in Chromium involves the coordination of four components: &lt;em&gt;paint&lt;/em&gt;, &lt;em&gt;shaping&lt;/em&gt;, &lt;em&gt;cc&lt;/em&gt;, and &lt;em&gt;Skia&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In &lt;em&gt;paint&lt;/em&gt;, the text painters are given a “fragment” of text to be painted in a given style.
They know the writing mode, because that’s part of the style, but they know very little about the text itself.
The first rotation (for the vertical writing mode) happens here, and we configure the Draw­Looper here (except for its procedure, which we pass in &lt;em&gt;shaping&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;In &lt;em&gt;shaping&lt;/em&gt;, we find the best glyphs for each character, and determine what scripts the text fragment is made of, then split the text into “blobs”.
The second rotation (for the vertical script) happens here, and we throw in a skew transform too if the text we’re painting is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;oblique&lt;/code&gt; (or fake &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;italic&lt;/code&gt;, which is again known only to &lt;em&gt;shaping&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;In &lt;em&gt;cc&lt;/em&gt;, we expose a &lt;em&gt;Skia&lt;/em&gt;-like API that can either dispatch to &lt;em&gt;Skia&lt;/em&gt; immediately or collect operations into a queue for later.
Draw­Looper is in the process of being moved here, because the &lt;em&gt;Skia&lt;/em&gt; maintainers don’t want it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Skia&lt;/em&gt; provides a stateful canvas, which more or less creates visible output.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;With each canvas transform, existing coordinates need to be remapped into the new space before they can be used again, and we were doing them &lt;em&gt;imperatively&lt;/em&gt; in two different components.
Worse still, while layout (ng) — the phase that happens before &lt;em&gt;paint&lt;/em&gt; — uses the type system to enforce correct handling of coordinates (e.g. Physical­Offset, Logical­Rect), the same is not true for &lt;em&gt;paint&lt;/em&gt; onwards.&lt;/p&gt;

&lt;p&gt;Everything is in Physical­Rect and friends, often erroneously, or in “untyped” coordinates like Float­Rect or Sk­Rect.
In one case, a Physical­Offset is used in both physical and non-physical (rotated for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;writing-mode&lt;/code&gt;) spaces, to refer to two &lt;em&gt;different&lt;/em&gt; points at &lt;em&gt;different&lt;/em&gt; corners of the text.
Here… let me illustrate.&lt;/p&gt;

&lt;figure&gt;
&lt;div class=&quot;scroll&quot;&gt;
    &lt;picture&gt;
        &lt;source srcset=&quot;/images/spammar-f.png 1x&quot; /&gt;
        &lt;img src=&quot;/images/spammar-f.png&quot; /&gt;
    &lt;/picture&gt;
&lt;/div&gt;
&lt;figcaption&gt;
    &lt;p&gt;When painting horizontal text in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vertical-rl&lt;/code&gt;, we rotate the canvas 90° cw around &lt;em&gt;A&lt;/em&gt; so that the text’s left descent corner lands on &lt;em&gt;B&lt;/em&gt;.
The left ascent corner moves from &lt;em&gt;B&lt;/em&gt; to &lt;em&gt;C&lt;/em&gt;.&lt;/p&gt;

    &lt;p&gt;That single variable was used to intentionally refer to both &lt;em&gt;B&lt;/em&gt; and &lt;em&gt;C&lt;/em&gt; at different times in a function, because the coordinates for &lt;em&gt;B&lt;/em&gt; in &lt;em&gt;space&lt;/em&gt; happen to be numerically the same as those for &lt;em&gt;C&lt;/em&gt; in &lt;em&gt;space′&lt;/em&gt;.
aaaa­aaaA­AAAA­AAAA­AAAA-&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&quot;-aaaaaaaaaaaaa&quot;&gt;-AAAAAAAAAAAAA&lt;/h3&gt;

&lt;p&gt;To be fair, each of these flaws has a reasonable explanation.&lt;/p&gt;

&lt;p&gt;Layout is a confusing place where we constantly need to deal with different coordinate spaces, so ideally we would iron everything out so that paint can work purely in physical space.
&lt;em&gt;Half the point&lt;/em&gt; of types like Logical­Rect is to provide getters and setters for concepts like “inline start” and “block end”.&lt;/p&gt;

&lt;p&gt;For most of the things we paint, this is ok, even desirable.
Rects like ::selection backgrounds &lt;em&gt;must&lt;/em&gt; be painted in physical space, so we can round the coordinates to integers for crisp edges.
Text is the only exception: the history of computer typography means that vertical text is, to some extent, seen internally as rotated horizontal text.&lt;/p&gt;

&lt;p&gt;Draw­Looper is handy for painting shadows, and it might&lt;sup&gt;[citation needed]&lt;/sup&gt; even reduce serialisation overhead in &lt;em&gt;cc&lt;/em&gt;.
But the way we currently configure them, baking coordinates into them before shaping, makes it even harder to handle vertical text correctly.&lt;/p&gt;

&lt;p&gt;Last but not least, Chromium’s pre-standard text painting order was “all rects for highlights and markers first, then all texts”.
This made the imperative canvas rotations &lt;em&gt;almost&lt;/em&gt; acceptable, if you ignore the shadow bugs, because we didn’t need to rotate the canvas back and forth nearly as many times.&lt;/p&gt;

&lt;p&gt;Once I moved to Perth, I spent over three weeks trying to find a systemic solution to these problems, but I just wasn’t getting anywhere meaningful.
In the interests of working a bit more breadth-first and avoiding burnout, I’ve shelved highlight painting for now.&lt;/p&gt;

&lt;h2 id=&quot;processing-model&quot;&gt;Processing model&lt;/h2&gt;

&lt;p&gt;Let’s return to how computed styles for highlight selectors should work.&lt;/p&gt;

&lt;p&gt;The consensus was that parent ::selection styles should &lt;em&gt;somehow&lt;/em&gt; propagate to the ::selection styles of their children, so authors can use their existing CSS skills to define both general ::selection styles &lt;em&gt;and&lt;/em&gt; more specific styles under certain elements.
This was unlike all existing implementations, where the only selector that worked the way you would expect was &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;::selection&lt;/code&gt;, that is to say, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*::selection&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At first, that “somehow” was by tweaking the &lt;a href=&quot;https://www.w3.org/TR/css-cascade-4/#cascade-sort&quot;&gt;cascade&lt;/a&gt; to take parent ::selection rules into account.
Emilio raised &lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/2474&quot;&gt;performance concerns&lt;/a&gt; with this, so the spec was changed, instead tweaking &lt;a href=&quot;https://www.w3.org/TR/css-cascade-4/#inheriting&quot;&gt;inheritance&lt;/a&gt; to make ::selection styles inherit from parent ::selection styles (and never from originating or “real” elements).&lt;/p&gt;

&lt;p&gt;This is what I’m working on now.
I’ve got a patch that gets most of the way, first by fixing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inherit&lt;/code&gt;, then by fixing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unset&lt;/code&gt;, then with a couple more fixes for styles where the cascade doesn’t yield any value, but there are still a few kinks ahead:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;impl work has raised at least three questions that need CSSWG clarification;&lt;/li&gt;
  &lt;li&gt;we need to optimise it, maybe more than before, to avoid perf regressions;&lt;/li&gt;
  &lt;li&gt;we still need to check if style invalidation works correctly; and&lt;/li&gt;
  &lt;li&gt;we probably want new devtools features to visualise highlight inheritance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;stay-tuned&quot;&gt;Stay tuned!&lt;/h2&gt;

&lt;p&gt;Beyond my colleagues at Igalia, special thanks go to Stephen, &lt;a href=&quot;https://twitter.com/runeLi&quot;&gt;Rune&lt;/a&gt;, Koji (Google), and &lt;a href=&quot;https://twitter.com/ecbos_&quot;&gt;Emilio&lt;/a&gt; (Mozilla) for putting up with all of my questions, not to mention Florian and fantasai from the CSSWG, plus &lt;a href=&quot;https://twitter.com/Gankra_&quot;&gt;Gankra&lt;/a&gt; (Mozilla) for her writing about text rendering, which has proved both inspiring and reassuring.&lt;/p&gt;

&lt;script&gt;
    [...document.querySelectorAll(&quot;.local-compare&quot;)].forEach(x =&gt; {
        const p = x.firstChild;
        const q = x.lastChild;

        const inner = document.createElement(&quot;div&quot;);
        x.prepend(inner);
        inner.append(p, q);
        p.after(document.createElement(&quot;div&quot;));

        inner.style.setProperty(&quot;--cut&quot;, `${inner.getBoundingClientRect().width / 2}px`);

        inner.addEventListener(&quot;mousemove&quot;, event =&gt; {
            inner.style.setProperty(&quot;--cut&quot;, `${event.offsetX}px`);
        });

        inner.addEventListener(&quot;touchmove&quot;, event =&gt; {
            inner.style.setProperty(&quot;--cut&quot;, `${event.targetTouches.item(0).clientX - inner.getBoundingClientRect().left}px`);
        });
    });
&lt;/script&gt;</content><author><name></name></author><category term="home" /><category term="igalia" /><summary type="html">Back in September, I wrote about my wonderful internship with Igalia’s web platform team. I’m thrilled to have since joined Igalia full-time, starting in the very last week of last year. My first project has been implementing the new CSS spelling and grammar features in Chromium. Life has been pretty hectic since Aria and I moved back to Perth, but more on that in another post. For now, let’s step back and review our progress.</summary></entry><entry><title type="html">nonymous + bore(1): DNS toys for Rust</title><link href="https://www.azabani.com/2021/01/03/nonymous-bore.html" rel="alternate" type="text/html" title="nonymous + bore(1): DNS toys for Rust" /><published>2021-01-03T21:00:00+00:00</published><updated>2021-01-03T21:00:00+00:00</updated><id>https://www.azabani.com/2021/01/03/nonymous-bore</id><content type="html" xml:base="https://www.azabani.com/2021/01/03/nonymous-bore.html">&lt;p&gt;I’ve been writing a DNS implementation in Rust.
This project started out as a vehicle for learning Rust, but the more I learned, the more challenging goals I was able to set, to the point where I can see its potential to become useful in its own right.
Here’s a post about what I’ve learned so far while writing &lt;a href=&quot;https://crates.io/crates/nonymous&quot;&gt;nonymous&lt;/a&gt;, an embedded-friendly DNS library with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#![no_std]&lt;/code&gt; and no-alloc support, and &lt;a href=&quot;https://crates.io/crates/bore&quot;&gt;bore(1)&lt;/a&gt;, a CLI tool for sending DNS queries.&lt;/p&gt;

&lt;style&gt;
.local-commit-container { margin-right: -1.6em; padding-right: 1.4em; border-right: 0.2em solid rgba(102,51,153,0.5); }
* + .local-commit, .local-commit * + * { margin-top: 0; }
.local-commit { display: flex; flex-flow: row nowrap; justify-content: right; line-height: 2; margin-right: -2.5em; width: max-content; margin-left: auto; text-align: right; }
.local-commit &gt; img { width: 2em; vertical-align: middle; }
.local-commit &gt; a { flex: 1 0 15ch; padding: 0 1ch; text-decoration: none; color: rebeccapurple; }
.local-commit &gt; a &gt; code { font-size: 1em; }
.local-commit-none &gt; a { color: rgba(102,51,153,0.5); }
.local-commit-section .local-commit-sticky { position: sticky; bottom: 0; }
.local-commit-section .local-commit-sticky &gt; a { background: white; }
pre, code { font-family: Inconsolata, monospace, monospace; }
article pre &gt; code &gt; b, article pre &gt; code &gt; b * { font-weight: inherit; background: black; color: white; }
article pre &gt; code &gt; i, article pre &gt; code &gt; i * { font-style: inherit; color: #808080; }
article table td {
    border: 1px solid black;
    padding: 0.5em;
}
&lt;/style&gt;

&lt;blockquote&gt;
  &lt;p&gt;most people don’t realise the opposite of anonymous is nonymous&lt;/p&gt;

  &lt;footer&gt;— &lt;cite&gt;&lt;a href=&quot;https://twitter.com/mangopdf&quot;&gt;the hacker known as “Alex”&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/mangopdf/status/1343673102337654784&quot;&gt;2020&lt;/a&gt;&lt;/cite&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;p&gt;Rust already has &lt;a href=&quot;https://github.com/bluejekyll/trust-dns&quot;&gt;a mature DNS implementation&lt;/a&gt; that I’ve heard wonderful things about, and there’s a &lt;em&gt;long&lt;/em&gt; way to go before &lt;a href=&quot;https://crates.io/crates/nonymous&quot;&gt;nonymous&lt;/a&gt; approaches anything resembling feature-complete or production-ready.
But &lt;a href=&quot;https://crates.io/crates/bore&quot;&gt;bore(1)&lt;/a&gt; is useful enough that I actually reach for it in 90% of the situations I would have previously used dig(1)…&lt;/p&gt;

&lt;figure&gt;
  &lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;$ bore daz.cat any&lt;/span&gt;
;; 172.19.128.1:53 (172.19.128.39:55722)
;; NoError #49916 Query 1 11 0 3 flags qr rd ra
;; EDNS(0) UDP 4096 flags

;; question section
; daz.cat. IN ANY

;; answer section
daz.cat. 3600 IN SOA daria.daz.cat. delan.azabani.com. 2020082100 600 60 1814400 60
daz.cat. 3600 IN NS daria.daz.cat.
daz.cat. 3600 IN NS ns2.he.net.
daz.cat. 3600 IN NS ns2.afraid.org.
daz.cat. 3600 IN CAA 128 issue &quot;letsencrypt.org&quot;
daz.cat. 3600 IN CAA 128 issuewild &quot;;&quot;
daz.cat. 3600 IN CAA 0 iodef &quot;mailto:delan@azabani.com&quot;
daz.cat. 3600 IN A 107.191.57.160
daz.cat. 3600 IN AAAA 2001:19f0:5800:8a45:ec4:7aff:fe15:d8a2

;; authority section

;; additional section
daria.daz.cat. 3600 IN AAAA 2403:5800:7300:6300:cccc:ffff:feee:8001
daria.daz.cat. 3600 IN A 180.150.30.255
; EDNS OPT RR was here&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;…and some situations that the incumbent struggles with, like dumping, replaying, and debugging messages.&lt;/p&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;$ bore bore.test ns --encode | tee query.dns | xd&lt;/span&gt;
40 C1 01 00  00 01 00 00  00 00 00 01  04 62 6F 72  @┴☺��☺�����☺♦bor  0
65 04 74 65  73 74 00 00  02 00 01 00  00 29 10 00  e♦test��☻�☺��)��  10
00 00 00 00  00 00                                  ������            20
&lt;span class=&quot;c&quot;&gt;
$ &amp;lt; query.dns bore --load --dump | tee response.dns | xd&lt;/span&gt;
40 C1 85 80  00 01 00 02  00 00 00 01  04 62 6F 72  @┴àÇ�☺�☻���☺♦bor  0
65 04 74 65  73 74 00 00  02 00 01 C0  0C 00 02 00  e♦test��☻�☺└��☻�  10
01 00 00 0E  10 00 02 00  00 C0 0C 00  02 00 01 00  ☺�����☻��└��☻�☺�  20
00 0E 10 00  00 00 00 29  10 00 00 00  00 00 00 00  �������)��������  30
&lt;span class=&quot;c&quot;&gt;
$ bore --decode &amp;lt; examples/badrdata.dns&lt;/span&gt;
;; NoError #0 Query 1 3 0 0 flags qr rd ra

;; question section
; . IN NS

;; answer section
. 13 IN NS a.root-servers.net.
. 13 IN NS \# 0
. 13 IN NS \# 2 C0 45

;; authority section

;; additional section&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;contents&quot;&gt;Contents&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#what-is-dns-anyway&quot;&gt;What is DNS anyway?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Decoder designs
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#naïve-decoders&quot;&gt;Naïve decoders&lt;/a&gt; (&lt;a href=&quot;#hg-v0&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hg-v0&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#zero-copy-views&quot;&gt;Zero-copy views&lt;/a&gt; (&lt;a href=&quot;#hg-v1&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hg-v1&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;#hg-v2&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hg-v2&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;#hg-v3&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hg-v3&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;#hg-v4&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hg-v4&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#rdata-views&quot;&gt;RDATA views&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Encoder designs
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#early-builders&quot;&gt;Early builders&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#state-machines&quot;&gt;State machines&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#open questions&quot;&gt;Open questions&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#next-steps&quot;&gt;Next steps&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;what-is-dns-anyway&quot;&gt;What is DNS anyway?&lt;/h2&gt;

&lt;p&gt;DNS is a distributed database that stores information in a hierarchy of names.
The most familiar example of these is IP addresses (the information) and hostnames (the names).
This is how your browser knows to contact 107.191.57.160 when you go to &lt;a href=&quot;https://opacus.daz.cat&quot;&gt;opacus.daz.cat&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When we build a DNS implementation, that might mean:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;a protocol decoder, to understand incoming DNS messages&lt;/li&gt;
  &lt;li&gt;a protocol encoder, to serialise outgoing DNS messages&lt;/li&gt;
  &lt;li&gt;a query tool like dig(1), to send queries and print responses&lt;/li&gt;
  &lt;li&gt;a resolver, which contacts authorities to answer queries&lt;/li&gt;
  &lt;li&gt;an authority, which maintains information about a domain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s explore the challenges behind the first two.&lt;/p&gt;

&lt;hr /&gt;

&lt;div class=&quot;local-commit-container&quot;&gt;

  &lt;!-- git log --reverse --abbrev=13 --pretty=tformat:&apos;&lt;div class=&quot;local-commit local-commit-none&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/%H&quot;&gt;&lt;code&gt;%h&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-none.svg&quot;&gt;&lt;/div&gt;%n%ad    %s%n&apos; --&gt;

  &lt;!-- &lt;div class=&quot;local-commit&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/c223c4eef1971f8eefdb3fea996536677c39f396&quot;&gt;&lt;code&gt;c223c4eef1971&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot;&gt;&lt;/div&gt; --&gt;

  &lt;div class=&quot;local-commit-section&quot; id=&quot;hg-v0&quot;&gt;

    &lt;!-- FIXME jekyll option? --&gt;
    &lt;h2 id=&quot;naïve-decoders&quot;&gt;Naïve decoders&lt;/h2&gt;

    &lt;p&gt;My initial approach was based around a trait that would describe a type that we can instantiate from something we can &lt;a href=&quot;https://doc.rust-lang.org/std/io/trait.Read.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Read&lt;/code&gt;&lt;/a&gt;.
After all, the network is just like a stream that you pipe into your program… right?&lt;/p&gt;

    &lt;figure&gt;
      &lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Decode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;&apos;static&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Sized&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/figure&gt;

    &lt;p&gt;So if we defined a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Message&lt;/code&gt; type that represents a message, we could then define how to parse one out of an octet stream.&lt;/p&gt;

    &lt;figure&gt;
      &lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cd&quot;&gt;/// ```rust&lt;/span&gt;
&lt;span class=&quot;cd&quot;&gt;/// let mut source = &amp;amp;b&quot;\x13\x13\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&quot;[..];&lt;/span&gt;
&lt;span class=&quot;cd&quot;&gt;/// let message = Message::decode(&amp;amp;mut source)?;&lt;/span&gt;
&lt;span class=&quot;cd&quot;&gt;/// ```&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Header&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;qr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;opcode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;aa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Read&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Decode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MessageError&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MessageError&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;header&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;

        &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/figure&gt;

    &lt;p&gt;This approach has a few problems.
The most obvious one is that &lt;a href=&quot;https://doc.rust-lang.org/std/io/trait.Read.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Read&lt;/code&gt;&lt;/a&gt; isn’t available in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#![no_std]&lt;/code&gt;, but this wouldn’t be too hard to work around with a shim trait.&lt;/p&gt;

    &lt;p&gt;A deeper problem is that parsing DNS messages in one pass without random access is incompatible with &lt;a href=&quot;https://tools.ietf.org/html/rfc1035#section-4.1.4&quot;&gt;&lt;strong&gt;message compression&lt;/strong&gt;&lt;/a&gt;, which allows names &lt;a href=&quot;https://tools.ietf.org/html/rfc3597#section-4&quot;&gt;in some places&lt;/a&gt; to “point” to labels somewhere else in the message.
For example, this message represents &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a.root-servers.net.&lt;/code&gt; in full, then reuses part of that with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b.&lt;/code&gt; followed by “go to 1Eh for the rest”:&lt;/p&gt;

    &lt;figure&gt;
&lt;div class=&quot;scroll&quot;&gt;&lt;picture&gt;
    &lt;source srcset=&quot;/images/nonymous-bore-compression@1x.png 1x, /images/nonymous-bore-compression@2x.png 2x&quot; /&gt;
    &lt;img src=&quot;/images/nonymous-bore-compression@2x.png&quot; /&gt;
&lt;/picture&gt;&lt;/div&gt;
&lt;/figure&gt;

    &lt;p&gt;The solution I reached for here was, in retrospect, very inelegant: a pair of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Read&lt;/code&gt; adapters that allow the caller to read behind or ahead (respectively) of the current position in the underlying stream.&lt;/p&gt;

    &lt;figure&gt;
      &lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Rewind&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Read&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;inner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Peek&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Read&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;inner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;future&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Read&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Read&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Rewind&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Read&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Read&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Peek&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Read&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Rewind&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;rewind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Option&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Peek&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Cursor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Read&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Peek&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;peek&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;
      &lt;figcaption&gt;
        &lt;p&gt;Note that many of the names of types and other symbols have been changed to make this post more clear and consistent. For example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Rewind&lt;/code&gt; was actually called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Elephant&lt;/code&gt;(?!), and I actually flip-flopped between &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Consume&lt;/code&gt;.&lt;/p&gt;
      &lt;/figcaption&gt;
    &lt;/figure&gt;

    &lt;div class=&quot;local-commit local-commit-sticky&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/tag/hg-v0&quot;&gt;&lt;code&gt;hg-v0&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

  &lt;/div&gt;

  &lt;div class=&quot;local-commit-section&quot; id=&quot;hg-v1&quot;&gt;

    &lt;p&gt;This approach only made sense under the premise that we should be able to stream DNS messages from a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Read&lt;/code&gt; into the decoder, a premise that I clung to because I thought we might not know how long a message is without decoding it.&lt;/p&gt;

    &lt;p&gt;As it turns out, this isn’t actually a problem for DNS as used with its two most common transports.
For UDP, each datagram contains exactly one message, and datagrams are inherently of fixed length.
For TCP, streams can convey many messages, but the sender has to prefix each message with its length.&lt;/p&gt;

    &lt;p&gt;With that cleared up, I decided that this kind of “streaming” decoder wasn’t worth the effort, and I went back to the drawing board.&lt;/p&gt;

    &lt;hr /&gt;

    &lt;h3 id=&quot;zero-copy-views&quot;&gt;Zero-copy views&lt;/h3&gt;

    &lt;p&gt;While I was at the drawing board, I also started developing some ideas that would pave the way for zero-copy decoding.&lt;/p&gt;

    &lt;p&gt;Looking back at the old &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Header&lt;/code&gt; design below, notice how we painstakingly unpack everything from each field into neat little Rust fields?
Each thing we unpack involves some copying that adds precious instructions to the critical path.&lt;/p&gt;

    &lt;figure&gt;
      &lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Header&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;qr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;opcode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;aa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/figure&gt;

    &lt;p&gt;What if we could walk through a DNS message as quickly as possible, doing only the work that’s absolutely necessary to reach the end of the message?
This turns out to be an interesting problem to solve, because most of the message is of &lt;strong&gt;unknown length&lt;/strong&gt;.
A protocol element of unknown length means that its length can only be known by descending into, and walking through, that protocol element.&lt;/p&gt;

    &lt;p&gt;This is distinct from other elements of &lt;strong&gt;variable length&lt;/strong&gt;, where the length can be determined from surrounding information, but don’t worry about this just yet.
Let’s consider this overview of DNS protocol elements.
Walking through the header is easy — skip 12 octets — but the rest of the message is of unknown length.&lt;/p&gt;

    &lt;figure&gt;
&lt;div class=&quot;scroll&quot;&gt;&lt;img src=&quot;/images/nonymous-bore-message0.svg&quot; /&gt;&lt;/div&gt;
&lt;/figure&gt;

    &lt;p&gt;This is because each section is of unknown length.
Even if questions and records were of known but variable length, there’s a variable number of them in each section.&lt;/p&gt;

    &lt;figure&gt;
&lt;div class=&quot;scroll&quot;&gt;&lt;img src=&quot;/images/nonymous-bore-message1.svg&quot; /&gt;&lt;/div&gt;
&lt;/figure&gt;

    &lt;p&gt;To make matters worse, questions and records themselves are of unknown length anyway.
Notice that rdata is a good example of an element of known but variable length.&lt;/p&gt;

    &lt;figure&gt;
&lt;div class=&quot;scroll&quot;&gt;&lt;img src=&quot;/images/nonymous-bore-message2.svg&quot; /&gt;&lt;/div&gt;
&lt;/figure&gt;

    &lt;p&gt;At the end of the day, the root cause is that names themselves are of unknown length.
While labels are of known but variable length, there’s a variable number of them in each name.
The length of a label depends on a couple of different things, and this has surprisingly interesting implications for extensibility&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

    &lt;figure&gt;
&lt;div class=&quot;scroll&quot;&gt;&lt;img src=&quot;/images/nonymous-bore-message3.svg&quot; /&gt;&lt;/div&gt;
&lt;/figure&gt;

    &lt;p&gt;The crux of my approach to zero-copy decoding is that walking to the end of a message in this way is, on some level, proof that the message is structurally sound.
When that proof succeeds, we want to return some type that represents the proof.
This is what I call a &lt;strong&gt;view&lt;/strong&gt;, and it allows the caller to interrogate the message &lt;em&gt;efficiently&lt;/em&gt;, because many of their “questions” can be made infallible&lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;, and &lt;em&gt;confidently&lt;/em&gt;, because we’ve proven that those infallible “questions” are truly infallible (panic-free).&lt;/p&gt;

    &lt;p&gt;A view under this definition can be a unit type (no fields), but in practice, we should also include any information that the caller can use to answer their “questions” &lt;em&gt;even more&lt;/em&gt; efficiently.
To keep our design embedded-friendly, let’s avoid the need for a separate allocation by limiting ourselves to constant space.&lt;/p&gt;

    &lt;p&gt;For records, that’s easy enough: one slice over the whole message (for compressed names), plus where the record starts in the message, and where the fixed part starts, or equivalent.&lt;/p&gt;

    &lt;figure&gt;
&lt;div class=&quot;scroll&quot;&gt;&lt;img src=&quot;/images/nonymous-bore-record.svg&quot; /&gt;&lt;/div&gt;
&lt;/figure&gt;

    &lt;figure&gt;
      &lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;flex&quot;&gt;
          &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Record&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;          &lt;/div&gt;
          &lt;div class=&quot;gap&quot;&gt;&lt;/div&gt;
          &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/figure&gt;

    &lt;p&gt;As for messages, I think the most useful information we can return in constant space is a slice over the whole message, plus slices indicating where each section starts, to give question and record iterators what they need to know to start immediately.&lt;/p&gt;

    &lt;figure&gt;
      &lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;flex&quot;&gt;
          &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Header&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;qd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;an&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;          &lt;/div&gt;
          &lt;div class=&quot;gap&quot;&gt;&lt;/div&gt;
          &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Header&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/figure&gt;

    &lt;p&gt;If we require the caller to provide the whole message upfront, we can dispense with all of that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Read&lt;/code&gt; goop and ask for two slices (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;[u8]&lt;/code&gt;): one with the part of the message that this decoder should focus on, and one over the whole message for compressed names.&lt;/p&gt;

    &lt;figure&gt;
      &lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Ok((the view, slice over the remaining input))&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ViewResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;View&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Sized&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ViewResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/figure&gt;

    &lt;p&gt;To speed up our decoding of compressed names, let’s cache the set of pointer destinations that are known to be good.&lt;/p&gt;

    &lt;!-- for ~~*secure* message decompression, in the face of pointers that form a cycle.~~
These malformed pointers pose a serious denial-of-service risk that we’ve known about for [over] [twenty] [years].
The most obvious way to mitigate this is to remember which pointer destinations we’ve already jumped to while decoding an individual name, then bail out if we’ve been asked to jump to the same place twice.

[over]: https://www.kb.cert.org/vuls/id/23495/
[twenty]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2000-0333
[years]: https://nvd.nist.gov/vuln/detail/CVE-2000-0333 --&gt;

    &lt;figure&gt;
      &lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Seen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;View&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Sized&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ViewResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;
      &lt;figcaption&gt;
        &lt;p&gt;Note that this version actually used a type called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Slice&lt;/code&gt;, but unlike the one in &lt;a href=&quot;#hg-v2&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hg-v2&lt;/code&gt;&lt;/a&gt;, it was just an alias for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;[u8]&lt;/code&gt;.&lt;/p&gt;
      &lt;/figcaption&gt;
    &lt;/figure&gt;

    &lt;div class=&quot;local-commit local-commit-sticky&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/tag/hg-v1&quot;&gt;&lt;code&gt;hg-v1&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

  &lt;/div&gt;

  &lt;div class=&quot;local-commit-section&quot; id=&quot;hg-v2&quot;&gt;

    &lt;p&gt;Now let’s add some error handling, and while we’re at it, replace &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;start&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;source&lt;/code&gt; with a single type that represents a subslice that maintains a reference to the whole slice.&lt;/p&gt;

    &lt;figure&gt;
      &lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// rust-lang/rust#27186&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;whole&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// TODO replace once slice_index_methods is stable&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RangeBounds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// ([p..q], len ≤ q-p) -&amp;gt; ([p..p+len], [p+len..q])&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SliceError&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// ([p..q], offset) -&amp;gt; [offset..]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;jump&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SliceError&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ViewResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;View&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;View&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Sized&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ViewResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/figure&gt;

    &lt;figure&gt;
      &lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;flex&quot;&gt;
          &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;qd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;an&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;opt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Option&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Extension&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Extension&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;          &lt;/div&gt;
          &lt;div class=&quot;gap&quot;&gt;&lt;/div&gt;
          &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Record&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fixed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rdata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/figure&gt;

    &lt;div class=&quot;local-commit local-commit-sticky&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/tag/hg-v2&quot;&gt;&lt;code&gt;hg-v2&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

  &lt;/div&gt;

  &lt;div class=&quot;local-commit-section&quot; id=&quot;hg-v3&quot;&gt;

    &lt;p&gt;I was unsatisfied by the size of my view types, both before and after introducing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Slice&lt;/code&gt;.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Record&lt;/code&gt;, for example, went from being 8 pointers long (four &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;[u8]&lt;/code&gt; fields, each having &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*const u8&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;usize&lt;/code&gt;) to 16 pointers long (four &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Slice&lt;/code&gt; fields, each having &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;[u8]&lt;/code&gt; and two &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;usize&lt;/code&gt;).
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Message&lt;/code&gt; was already pretty heavy at 14 pointers long, but now it was 28 pointers long.&lt;/p&gt;

    &lt;p&gt;The compiler doesn’t (or can’t) always avoid copying these values when we pass them around, so I would often see memmoves of 64 or even 224 bytes (amd64), and they were becoming difficult to ignore in my benchmarks.
This was getting out of hand.&lt;/p&gt;

    &lt;p&gt;The good news was that most of these details were either redundant, or could be inferred from other details in constant time.
12 out of 28 pointer widths in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Message&lt;/code&gt; were redundant copies of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*const u8&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;usize&lt;/code&gt; that defined the extent of the whole message.
Of the remaining 16 pointer widths, at least four can be inferred in constant time, by exploiting the fact that the header and four sections “touch” each other in memory:&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.qd.start&lt;/code&gt; = &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.slice.start&lt;/code&gt; + 12&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.an.start&lt;/code&gt; = &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.qd.stop&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ns.start&lt;/code&gt; = &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.an.stop&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ar.start&lt;/code&gt; = &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ns.stop&lt;/code&gt;&lt;/li&gt;
    &lt;/ul&gt;

    &lt;p&gt;Let’s redefine this in terms of a starting offset and four section lengths:&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;let &lt;em&gt;offset&lt;/em&gt; = &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.slice.start&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;let &lt;em&gt;qd_len&lt;/em&gt; = &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.qd.stop&lt;/code&gt; − &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.qd.start&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;let &lt;em&gt;an_len&lt;/em&gt; = &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.an.stop&lt;/code&gt; − &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.an.start&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;let &lt;em&gt;ns_len&lt;/em&gt; = &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ns.stop&lt;/code&gt; − &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ns.start&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;let &lt;em&gt;ar_len&lt;/em&gt; = &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ar.stop&lt;/code&gt; − &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ar.start&lt;/code&gt;&lt;/li&gt;
    &lt;/ul&gt;

    &lt;p&gt;We can still determine where all the sections are in constant time:&lt;/p&gt;

    &lt;figure&gt;
      &lt;ul style=&quot;list-style: &apos;⇒ &apos;;&quot;&gt;
&lt;li&gt;qd section starts at &lt;i&gt;offset&lt;/i&gt; + 12&lt;/li&gt;
&lt;li&gt;an section starts at &lt;i&gt;offset&lt;/i&gt; + 12 + &lt;i&gt;qd_len&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;ns section starts at &lt;i&gt;offset&lt;/i&gt; + 12 + &lt;i&gt;qd_len&lt;/i&gt; + &lt;i&gt;an_len&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;ar section starts at &lt;i&gt;offset&lt;/i&gt; + 12 + &lt;i&gt;qd_len&lt;/i&gt; + &lt;i&gt;an_len&lt;/i&gt; + &lt;i&gt;ns_len&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
    &lt;/figure&gt;

    &lt;p&gt;I took this to its logical extreme, cutting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Message&lt;/code&gt; down to 8 pointers long (6–20 shorter) and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Record&lt;/code&gt; to just 3 (5–13 shorter).
Two pointer widths in each case were saved by eliminating the slice reference over the whole message.
The caller already has a copy of this reference, because that’s where it came from in the first place!&lt;/p&gt;

    &lt;figure&gt;
      &lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ViewResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Range&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;View&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;View&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Sized&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Range&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Option&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Seen&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ViewResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/figure&gt;

    &lt;figure&gt;
      &lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;flex&quot;&gt;
          &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;qd_len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;an_len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ns_len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ar_len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;opt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Option&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Extension&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Extension&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;inner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Record&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;          &lt;/div&gt;
          &lt;div class=&quot;gap&quot;&gt;&lt;/div&gt;
          &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Record&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;name_len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rdata_len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/figure&gt;

    &lt;div class=&quot;local-commit local-commit-sticky&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/tag/hg-v3&quot;&gt;&lt;code&gt;hg-v3&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

  &lt;/div&gt;

  &lt;div class=&quot;local-commit-section&quot; id=&quot;hg-v4&quot;&gt;

    &lt;p&gt;Removing references to the message buffer was exciting at first — look ma, no lifetimes!
But it was a step too far, because as a result, the caller had to pass that reference to every method that interrogates their messages.
Not only was this awkward, but now Rust can no longer ensure that our “infallible” methods are actually interrogating the correct buffer!
Pass in the wrong buffer and we’ll panic, or worse, blindly return nonsense.&lt;/p&gt;

    &lt;p&gt;Let’s put that reference back into each view.
We can limit the cost to just one &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;[u8]&lt;/code&gt; (two pointer widths) in each view by avoiding composition, creating views for inner protocol elements on the fly.&lt;/p&gt;

    &lt;figure&gt;
      &lt;div class=&quot;scroll&quot;&gt;&lt;div class=&quot;flex&quot;&gt;
          &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Record&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rdata_len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;          &lt;/div&gt;
          &lt;div class=&quot;gap&quot;&gt;&lt;/div&gt;
          &lt;p&gt;→&lt;/p&gt;
          &lt;div class=&quot;gap&quot;&gt;&lt;/div&gt;
          &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Record&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;name_len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rdata_len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Record&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;_&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.offset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.name_len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;          &lt;/div&gt;
        &lt;/div&gt;&lt;/div&gt;
    &lt;/figure&gt;

    &lt;div class=&quot;local-commit local-commit-sticky&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/tag/hg-v4&quot;&gt;&lt;code&gt;hg-v4&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

  &lt;/div&gt;

  &lt;div class=&quot;local-commit-section&quot; id=&quot;0.0.0&quot;&gt;

    &lt;h2 id=&quot;rdata-views&quot;&gt;RDATA views&lt;/h2&gt;

    &lt;p&gt;Each view has a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Display&lt;/code&gt; implementation that prints the protocol element in a zone-or-dig(1)-like format, from labels all the way up to entire messages.
This is how bore(1) does most of its formatting work.&lt;/p&gt;

    &lt;p&gt;There are views for each record type’s rdata too, and these views walk through protocol elements and prove structural soundness in the same way.
The key difference here is that these views only run when needed, like when the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Display&lt;/code&gt; formatter for a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Record&lt;/code&gt; calls the method below.
Because records have an rdlength field, we can treat rdata as an opaque blob while we’re on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Message&lt;/code&gt; view’s critical path.&lt;/p&gt;

    &lt;p&gt;The main limitation of this method is that it returns a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Box&lt;/code&gt;, which requires alloc.
I’m sure there’s a way to rework this for no-alloc support, but so far I’ve only really thought about this piece of the puzzle for long enough to get bore(1) working.&lt;/p&gt;

    &lt;figure&gt;
      &lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;rdata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;dyn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Rdata&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&apos;_&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RdataError&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.rdata_offset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stop&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.rdata_len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.r&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Soa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Mx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Txt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;257&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Caa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// RFC 3597 § 4&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;CompressibleName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;InAddress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;28&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;InAaaa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Unknown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;
      &lt;figcaption&gt;
        &lt;p&gt;Confession: the logic for incompressible names isn’t actually implemented yet, so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Name&lt;/code&gt; is effectively &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CompressibleName&lt;/code&gt;.
This is why I’ve been able to erroneously use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Name&lt;/code&gt; in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Question&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Record&lt;/code&gt; views.&lt;/p&gt;
      &lt;/figcaption&gt;
    &lt;/figure&gt;

    &lt;div class=&quot;local-commit&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/e74ceb3cba72c75af3cfd8d9d51914b86e07ac40&quot;&gt;&lt;code&gt;e74ceb3cba72c&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-up.svg&quot; /&gt;&lt;/div&gt;
    &lt;div class=&quot;local-commit&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/8548d9fdd3e6422f35f386c55eb52781772375aa&quot;&gt;&lt;code&gt;8548d9fdd3e64&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

    &lt;h2 id=&quot;early-builders&quot;&gt;Early builders&lt;/h2&gt;

    &lt;p&gt;My earliest DNS encoder actually boiled down to bore(1) — &lt;a href=&quot;https://twitter.com/dazabani/status/1302656089402404865&quot;&gt;née scoop(1)&lt;/a&gt; — throwing together a message from hardcoded parts, if that even counts.
From there I built my encoders incrementally, using the query tool as a guide for what to work on next.&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// unbound(8) requires RD (no cache snooping) by default&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;header&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;b&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\x01\x00\x00\x01\x00\x00\x00\x00\x00\x01&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;question&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;b&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\0\x00\x02\x00\x01&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;opt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;b&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\0\x00\x29\x10\x00\x00\x00\x00\x00\x00\x00&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;.iter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;.copied&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;.chain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.iter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.copied&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;.chain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;question&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.iter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.copied&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;.chain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;opt&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.iter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.copied&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;.collect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;&lt;/figure&gt;

    &lt;div class=&quot;local-commit&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/30ebc50b6f1126ce1afaab8216c9b3d756ae212b&quot;&gt;&lt;code&gt;30ebc50b6f112&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

    &lt;p&gt;One by one, I replaced hardcoded parts with actual encoders, until there were none left.&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qtype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qtype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;NS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;unreachable!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;A&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qtype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qtype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// unbound(8) requires RD (no cache snooping) by default&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;header&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.rd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.qdcount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.arcount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;.qd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Question&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;qname&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;qtype&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;qclass&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;.opt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Extension&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;.emit_to_vec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;&lt;/figure&gt;

    &lt;div class=&quot;local-commit local-commit-none&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/b75227c5b20e3628ff283bbe4685070ed7260438&quot;&gt;&lt;code&gt;b75227c5b20e3&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-none.svg&quot; /&gt;&lt;/div&gt;
    &lt;div class=&quot;local-commit&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/463335b20a7c5fbdfd7398114d0f6aa3deb6b62b&quot;&gt;&lt;code&gt;463335b20a7c5&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

    &lt;p&gt;Each of these encoders was backed by the same kind of “naïve” type as in my &lt;a href=&quot;#naïve-decoders&quot;&gt;early decoders&lt;/a&gt;.
They exposed a thin builder API that didn’t enforce any kind of structural soundness, like the header’s qdcount reflecting the number of questions in the message.&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;&lt;div class=&quot;flex&quot;&gt;
          &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;qd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Question&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;an&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Record&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Record&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Record&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;opt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Option&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Extension&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;          &lt;/div&gt;
          &lt;div class=&quot;gap&quot;&gt;&lt;/div&gt;
          &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Header&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;qdcount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ancount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;nscount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;arcount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;          &lt;/div&gt;
        &lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;qd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Question&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.qd&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Header&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;qdcount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.qdcount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;&lt;/figure&gt;

    &lt;div class=&quot;local-commit local-commit-sticky&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/tag/0.0.0&quot;&gt;&lt;code&gt;0.0.0&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;
    &lt;div class=&quot;local-commit local-commit-none&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/ddc6231c4ed02251222e55c4e4da45e448e536c0&quot;&gt;&lt;code&gt;ddc6231c4ed02&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-none.svg&quot; /&gt;&lt;/div&gt;
    &lt;div class=&quot;local-commit local-commit-none&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/b00b8b30028e7f33e872da54e9eeba0fedd33c52&quot;&gt;&lt;code&gt;b00b8b30028e7&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-none.svg&quot; /&gt;&lt;/div&gt;
    &lt;div class=&quot;local-commit local-commit-none&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/tag/0.0.1&quot;&gt;&lt;code&gt;0.0.1&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-none.svg&quot; /&gt;&lt;/div&gt;

  &lt;/div&gt;

  &lt;div class=&quot;local-commit-section&quot; id=&quot;0.0.2&quot;&gt;

    &lt;h2 id=&quot;state-machines&quot;&gt;State machines&lt;/h2&gt;

    &lt;p&gt;Clearly that wasn’t the kind of quality and safety we would expect of a library.
The encoder types also heavily relied on multiple layers of allocations, which made them awkward to adapt to no-alloc targets.&lt;/p&gt;

    &lt;p&gt;Thus began an incredible journey&lt;sup id=&quot;fnref:3&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; that took me ten months (albeit in my spare time):&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
&lt;img src=&quot;/images/nonymous-bore-journey.png&quot; /&gt;
&lt;/div&gt;&lt;/figure&gt;

    &lt;p&gt;I had two key ideas around where I wanted to go with the new encoders.&lt;/p&gt;

    &lt;p&gt;We can avoid intermediate copying and separate allocations by writing protocol elements directly into the buffer that will actually get sent.
The deeply variable-length nature of DNS complicates this, but we can avoid opening that can of worms by writing them &lt;em&gt;in order&lt;/em&gt;, so let’s see how long we can survive with this requirement.&lt;/p&gt;

    &lt;p&gt;We can use session types — essentially a state machine in the type system — to enforce as much structural soundness as possible at compile time.
Perhaps we could even use them to force library consumers to write protocol elements in order.
I owe the inspiration behind this to &lt;a href=&quot;https://twitter.com/killercup&quot;&gt;Pascal Hertleif&lt;/a&gt; (&lt;a href=&quot;https://deterministic.space/elegant-apis-in-rust.html#session-types&quot;&gt;Elegant Library APIs in Rust&lt;/a&gt;), and especially &lt;a href=&quot;https://twitter.com/a_hoverbear&quot;&gt;Ana Hobden&lt;/a&gt; (&lt;a href=&quot;https://hoverbear.org/blog/rust-state-machine-pattern/&quot;&gt;Pretty State Machine Patterns in Rust&lt;/a&gt;).&lt;/p&gt;

    &lt;p&gt;The design I worked with for most of the way was an adaptation of Ana’s &lt;a href=&quot;https://hoverbear.org/blog/rust-state-machine-pattern/#generically-sophistication&quot;&gt;Generically Sophistication&lt;/a&gt; pattern.
I defined a generic container that wraps our state types, providing our shared “context” fields like output buffer (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sink&lt;/code&gt;) and &lt;a href=&quot;https://tools.ietf.org/html/rfc6891#section-6.2.3&quot;&gt;maximum payload size&lt;/a&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;limit&lt;/code&gt;).&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Emit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Sink&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;sink&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Option&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NonZeroU16&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;inner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;&lt;/figure&gt;

    &lt;p&gt;I then defined a state for each protocol element, as well as types for any substates it might have, using generics in the same way.&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Q&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;section&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Q&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;QdSection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AnSection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NsSection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ArSection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;&lt;/figure&gt;

    &lt;p&gt;State-dependent methods, including state transitions, are implemented on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Emit&amp;lt;...&amp;gt;&lt;/code&gt; types of the appropriate states.&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;macro_rules!&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transitions&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$machine:ident&lt;/span&gt;.&lt;span class=&quot;nv&quot;&gt;$field:ident&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$from:ident&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$to:ident&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Sink&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;From&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Emit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$machine&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Emit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$machine&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$to&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Emit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$machine&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$machine&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$to&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;transitions!&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.section&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;QdSection&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AnSection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;QdSection&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NsSection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;QdSection&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ArSection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;AnSection&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NsSection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;AnSection&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ArSection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;NsSection&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ArSection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;&lt;/figure&gt;

    &lt;p&gt;When we transition from encoding a protocol element to encoding one of its parts, we wrap the old state in the new state using generics, forming a stack that remembers the state to return to.
If you think these type signatures are barely comprehensible (and I certainly do), you probably wouldn’t want to see the helper functions behind which I tucked the heavy lifting of these “downward” state transitions.&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Record&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Q&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Q&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RecordName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RecordData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Sink&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Emit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AnSection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cd&quot;&gt;/// Start building a new [`Record`] on the end of the message’s answer section.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;record&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Emit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Record&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AnSection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RecordName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MessageError&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.an_increment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.child&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Record&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;&lt;/figure&gt;

    &lt;div class=&quot;local-commit&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/42ec3b432289c5b9bf623ba4d4b5a33d0eef8f6a&quot;&gt;&lt;code&gt;42ec3b432289c&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;
    &lt;div class=&quot;local-commit local-commit-none&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/7b30adb4e2561eae34903e1c65f7bac0fe8d3d63&quot;&gt;&lt;code&gt;7b30adb4e2561&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-none.svg&quot; /&gt;&lt;/div&gt;

    &lt;p&gt;At this point, we had a working prototype that could make some useful guarantees, and as complicated as the internals were, the bore(1) code consuming it almost looked like an ordinary builder chain!&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// FIXME EDNS OPT RR&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// unbound(8) requires RD (no cache snooping) by default&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;Emit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;py&quot;&gt;.child&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;.id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;.rd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;.question&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;.qname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;.labels&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;.finish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;.finish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qtype&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qclass&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;&lt;/figure&gt;

    &lt;div class=&quot;local-commit&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/d55670718f597857a0e930f3b07277018cbab7ec&quot;&gt;&lt;code&gt;d55670718f597&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;
    &lt;div class=&quot;local-commit local-commit-none&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/7370009af1b7b9b0342085b2b16f8277f8c5edde&quot;&gt;&lt;code&gt;7370009af1b7b&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-none.svg&quot; /&gt;&lt;/div&gt;
    &lt;div class=&quot;local-commit local-commit-none&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/41a8da0a04f292d418c69e35c11d8ce93b073673&quot;&gt;&lt;code&gt;41a8da0a04f29&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-none.svg&quot; /&gt;&lt;/div&gt;

    &lt;p&gt;I then reworked all of my type signatures to own a sink, rather than mutably borrow one, and relinquish it back to the caller once encoding is finished (successfully or otherwise).
I feel like I decided to do this for more important reasons than look-ma-no-lifetimes, but I genuinely don’t remember.&lt;/p&gt;

    &lt;div class=&quot;local-commit&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/75e5599e0fb6a498bf1b14ad6f23a1c4260f4d70&quot;&gt;&lt;code&gt;75e5599e0fb6a&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

    &lt;p&gt;The main consequence of my design that I was unhappy about was that the whole encoder API would get rendered on the same rustdoc page, because all of our state-dependent methods were implemented on various flavours of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Emit&amp;lt;...&amp;gt;&lt;/code&gt;.&lt;/p&gt;

    &lt;figure&gt;
&lt;a href=&quot;/images/nonymous-bore-emit0.png&quot;&gt;&lt;img src=&quot;/images/nonymous-bore-emit0.png&quot; width=&quot;50%&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;/images/nonymous-bore-emit1.png&quot;&gt;&lt;img src=&quot;/images/nonymous-bore-emit1.png&quot; width=&quot;50%&quot; /&gt;&lt;/a&gt;
&lt;figcaption&gt;
        &lt;p&gt;To find out how to build a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Message&lt;/code&gt;, you can’t go to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Message&lt;/code&gt;, because all of the docs are in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Emit&lt;/code&gt;. Does this look like a clear and inviting API to you?&lt;/p&gt;
      &lt;/figcaption&gt;&lt;/figure&gt;

    &lt;p&gt;I spent a very long time trying to find an elegant way around this.
I never ended up asking for advice on the Discord servers, because where would I even start?
I felt like I would have to explain so much to justify my predicament that it was beyond overwhelming.
Regardless of whether my fear was proportional or exaggerated (or scars from Stack Overflow&lt;sup id=&quot;fnref:4&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;), the outcome was the same.&lt;/p&gt;

    &lt;p&gt;I think I came to the conclusion that there wouldn’t be a good solution until Rust lands a new feature like &lt;a href=&quot;https://github.com/rust-lang/rust/issues/44874&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;arbitrary_self_types&lt;/code&gt;&lt;/a&gt;, which would allow me to move my state-dependent methods to an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;impl&lt;/code&gt; on the element type:&lt;/p&gt;

    &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
        &lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Sink&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Q&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Q&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cd&quot;&gt;/// Write the given value to the qr field in the message header.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;qr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Emit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/div&gt;&lt;/figure&gt;

    &lt;p&gt;I eventually worked around this by duplicating some code, splitting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Emit&lt;/code&gt; into a separate type for each protocol element.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Emit&lt;/code&gt; became &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FooBuilder&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Foo&lt;/code&gt; became &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FooBuildee&lt;/code&gt;, and so on, and I renamed most of the other traits to evoke one of these two terms.&lt;/p&gt;

    &lt;div class=&quot;local-commit&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/51175861ea760cc0b233e186e7639873b34e5b8c&quot;&gt;&lt;code&gt;51175861ea760&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;
    &lt;div class=&quot;local-commit local-commit-none&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/22e62e3c3cb324645e11b163c589bbecdcd926a8&quot;&gt;&lt;code&gt;22e62e3c3cb32&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-none.svg&quot; /&gt;&lt;/div&gt;
    &lt;div class=&quot;local-commit local-commit-none&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/cd538edc15ba3b6eafac2bf5166a40fa9a00a8df&quot;&gt;&lt;code&gt;cd538edc15ba3&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-none.svg&quot; /&gt;&lt;/div&gt;
    &lt;div class=&quot;local-commit local-commit-sticky&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/tag/0.0.2&quot;&gt;&lt;code&gt;0.0.2&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

  &lt;/div&gt;

&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;open-questions&quot;&gt;Open questions&lt;/h2&gt;

&lt;p&gt;I’m in two minds about the designs I arrived at.
On the one hand, I’m pleased that I’ve finally managed to come up with something that works, satisfies all of my self-imposed requirements, and lands somewhere in the vicinity of user-friendly.
At the same time, I’m unsatisfied with the complexity of the encoder side, and worried that someday, when I learn more about DNS, my whole approach will be rendered unusable.&lt;/p&gt;

&lt;p&gt;I’ve only written the state machines for barely more than the core protocol elements in &lt;a href=&quot;https://tools.ietf.org/html/rfc1035&quot;&gt;RFC 1035&lt;/a&gt;, and it’s already testing the limits of what I can maintain.
I’ve bought some time by extracting some boilerplate into &lt;a href=&quot;https://doc.rust-lang.org/reference/macros-by-example.html&quot;&gt;macros by example&lt;/a&gt;, like the code that defines simple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;From&lt;/code&gt; transitions, or the code that juggles &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Builder&lt;/code&gt; types and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Buildee&lt;/code&gt; types, but I’m not sure how much further I can go with that.&lt;/p&gt;

&lt;p&gt;I wonder if this is the kind of metaprogramming situation where I ought to write some &lt;a href=&quot;https://doc.rust-lang.org/reference/procedural-macros.html#derive-macros&quot;&gt;custom derive macros&lt;/a&gt;?
I’ve managed to procrastinate learning about proc macros until today (and counting), so this is not a rhetorical question.&lt;/p&gt;

&lt;p&gt;As for my fear that this will all be rendered unusable someday?
That stems from the fact that I’ve been navigating all of these design questions as a first-time DNS implementer.
While I think I understand DNS reasonably well &lt;em&gt;as a sysadmin&lt;/em&gt;, that doesn’t really answer the more intricate questions, like how do &lt;em&gt;my&lt;/em&gt; interface decisions affect &lt;em&gt;your&lt;/em&gt; ability to implement DNS algorithms or endpoints well?&lt;/p&gt;

&lt;p&gt;Perhaps I could have avoided this by writing more DNS components, like a resolver or an authority, before I imposed these challenging requirements on myself.
But given that I would then be &lt;em&gt;definitely&lt;/em&gt; &lt;a href=&quot;https://github.com/bluejekyll/trust-dns&quot;&gt;reimplementing the wheel&lt;/a&gt;, that isn’t nearly as exciting, is it?&lt;/p&gt;

&lt;h2 id=&quot;next-steps&quot;&gt;Next steps&lt;/h2&gt;

&lt;p&gt;If nothing else, bore(1) seems like one promising fruit of my endeavour, so I’m keen to implement a bunch more of the features I rely on day-to-day:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;conversion of IP addresses to PTR queries under in-addr.arpa. + ip6.arpa.&lt;/li&gt;
  &lt;li&gt;trace mode (simulating an iterative resolver with non-rd queries)&lt;/li&gt;
  &lt;li&gt;Windows system resolver support (already implemented &lt;a href=&quot;https://bitbucket.org/delan/nonymous/src/hg-v2/src/platform/windows.rs&quot;&gt;in hg-v2&lt;/a&gt;!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’m not sure where to go with nonymous though, and I think that’s because I have no idea how promising it is as a project that other people might find useful someday.
Depending on the answer, my next steps might be anything between “full speed ahead, higher layers, rigorous conformance, the whole shebang” and “retire the project and focus on bore(1)”.
But as a first-time DNS implementer without any actual embedded experience, the odds are stacked towards the latter end of the spectrum.&lt;/p&gt;

&lt;p&gt;In any case, hacking on nonymous has been loads of fun, and it was definitely worthwhile for me personally.
It’s probably where I tried out like half of my new Rust knowledge — especially all the cool things I had read about — for the first time:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;releasing a library and &lt;a href=&quot;https://bitbucket.org/delan/nonymous/src/0.0.2/RELEASES.md&quot;&gt;communicating changes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;designing a good API (&lt;a href=&quot;https://rust-lang.github.io/api-guidelines/about.html&quot;&gt;API guidelines&lt;/a&gt;, &lt;a href=&quot;https://deterministic.space/elegant-apis-in-rust.html&quot;&gt;elegant API design&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;session types and &lt;a href=&quot;https://deterministic.space/elegant-apis-in-rust.html&quot;&gt;compile-time state machines&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;lots and lots of macros by example (&lt;a href=&quot;https://danielkeep.github.io/tlborm/book/index.html&quot;&gt;TLBORM&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;FFI and Windows system programming (&lt;a href=&quot;https://bitbucket.org/delan/nonymous/src/hg-v2/src/platform/windows.rs&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PlatformResolvers&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unsafe&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MaybeUninit&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#![forbid(unsafe_code)]&lt;/code&gt; (&lt;a href=&quot;https://bitbucket.org/delan/nonymous/commits/518504698ed3509c3b56b4d299a59231038f7559&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Seen&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;profiling (&lt;a href=&quot;https://crates.io/crates/cpuprofiler&quot;&gt;cpuprofiler&lt;/a&gt;, &lt;a href=&quot;https://blog.anp.lol/rust/2016/07/24/profiling-rust-perf-flamegraph/&quot;&gt;perf + flamegraph&lt;/a&gt;) and benchmarking&lt;/li&gt;
  &lt;li&gt;figuring out CI and test coverage for a complex Rust project&lt;/li&gt;
  &lt;li&gt;and probably a whole bunch more!&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;There are a few different &lt;a href=&quot;https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-10&quot;&gt;label types&lt;/a&gt;, and the encoded length of a label depends on this — normal labels are effectively &lt;a href=&quot;https://en.wikipedia.org/wiki/String_(computer_science)#Length-prefixed&quot;&gt;Pascal strings&lt;/a&gt;, compressed labels are two octets long, and so on. Only half of the label type “namespace” is currently in use, so in theory the other half is open for future innovation.&lt;/p&gt;
      &lt;p&gt;What’s &lt;em&gt;interesting&lt;/em&gt; is that &lt;a href=&quot;https://tools.ietf.org/html/rfc6891#section-5&quot;&gt;it ended up being effectively impossible&lt;/a&gt; to invent new label types, because we didn’t think to define a way for &lt;em&gt;old&lt;/em&gt; software to know how long &lt;em&gt;new&lt;/em&gt; label types are (e.g. &lt;a href=&quot;https://en.wikipedia.org/wiki/Type-length-value&quot;&gt;TLV&lt;/a&gt;), so they can at least decode the rest of the message. As a result, new label types can’t be deployed until everyone that might encounter them updates their software, which is a massive chicken-and-egg situation.&lt;/p&gt;
      &lt;p&gt;Extensibility of internet protocols is a surprisingly hard problem (at least if &lt;a href=&quot;https://en.wikipedia.org/wiki/Robustness_principle&quot;&gt;Postel&lt;/a&gt; is all you’ve heard of), and we’ve had to learn how to do it the hard way. Check out &lt;a href=&quot;https://www.imperialviolet.org/2016/05/16/agility.html&quot;&gt;Cryptographic Agility&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/agl__&quot;&gt;Adam Langley&lt;/a&gt; for the lessons SSL taught us. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;An “infallible” function is one that can’t fail, always returning successfully, rather than returning a Result or panicking. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;But not an &lt;a href=&quot;https://ourincrediblejourney.tumblr.com&quot;&gt;Incredible Journey™&lt;/a&gt;. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:4&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;I was a &lt;a href=&quot;https://stackoverflow.com/users/330644&quot;&gt;very active user&lt;/a&gt; around a decade ago, but I eventually grew bored of the site, to the point where I don’t even go there when I have a question. I think this is because the site’s culture and governance made it devolve into a place that rewards unhealthy behaviours like racing to be the first answer, closing questions as duplicate/off-topic, and wielding “XY problem” like an aggressive Maslow’s hammer. &lt;a href=&quot;#fnref:4&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name></name></author><category term="home" /><category term="rust" /><summary type="html">I’ve been writing a DNS implementation in Rust. This project started out as a vehicle for learning Rust, but the more I learned, the more challenging goals I was able to set, to the point where I can see its potential to become useful in its own right. Here’s a post about what I’ve learned so far while writing nonymous, an embedded-friendly DNS library with #![no_std] and no-alloc support, and bore(1), a CLI tool for sending DNS queries.</summary></entry><entry><title type="html">Introducing xd(1)</title><link href="https://www.azabani.com/2020/11/15/xd.html" rel="alternate" type="text/html" title="Introducing xd(1)" /><published>2020-11-15T19:00:00+00:00</published><updated>2020-11-15T19:00:00+00:00</updated><id>https://www.azabani.com/2020/11/15/xd</id><content type="html" xml:base="https://www.azabani.com/2020/11/15/xd.html">&lt;p&gt;&lt;a href=&quot;https://crates.io/crates/xd&quot;&gt;xd(1)&lt;/a&gt; is a tool that dumps binary input in a more human-readable format.
There are countless tools that fit this description, including od(1), hexdump(1), xxd(1), and a bunch of Rust crates in the same vein, but this one has a new trick up its sleeve.&lt;/p&gt;

&lt;p&gt;A column with printable ASCII bytes shown as exactly that, and everything else as a dot, is such a strong convention that it basically defines the visual identity of hex-dumping tools.
It’s not a bad convention too — the unobtrusive dots for C0 controls and high bytes make ASCII text stand out, as if you had installed strings(1) on your pattern-matching neurons.
But imagine what kinds of patterns you could spot in binary data, if only there was a unique glyph for each of the other 161 possible bytes?&lt;/p&gt;

&lt;p&gt;The features released so far are pretty minimal beyond this very idea, but my hope is for xd(1) to eventually join the likes of rg(1) and fd(1) as another product of Rust’s renaissance of best-in-class solutions to CLI problems.&lt;/p&gt;

&lt;p&gt;Modern character encodings like UTF-8 won’t work for a bunch of reasons, including the fact that they’re variable-length, which would break the one-to-one relationship between bytes and glyphs that we’re going for.
Many common single-byte character sets like ISO-8859-1 or Windows-1252 won’t either, because they tend to leave 00h–1Fh and 80h–9Fh undefined to accommodate the C0 and C1 controls, which aren’t the simple printable characters we’re after.&lt;/p&gt;

&lt;p&gt;I’ll let Raymond take it from here:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Is there a code page that matches ASCII for the first 128 values and can round trip arbitrary bytes through Unicode?&lt;/p&gt;

  &lt;p&gt;You may find yourself looking for such a code page when you have a chunk of binary data with embedded ASCII text. You want to be able to dig out and even manipulate the ASCII text, and treat the non-ASCII parts as mysterious characters that have no meaning, but you need to be able to convert them back into the original bytes.&lt;/p&gt;

  &lt;p&gt;[…]&lt;/p&gt;

  &lt;p&gt;Okay, I’ll cut to the chase. The code page I use for this sort of thing is code page 437. Every bytes is defined and maps to a unique Unicode code point, and it agrees with ASCII for the first 128 values.&lt;/p&gt;

  &lt;footer&gt;— &lt;cite&gt;Raymond Chen’s &lt;a href=&quot;https://devblogs.microsoft.com/oldnewthing/20200831-00/?p=104142&quot;&gt;&lt;i&gt;The Old New Thing&lt;/i&gt; post&lt;/a&gt;&lt;/cite&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;figure&gt;
    &lt;a href=&quot;/images/xd-dots.png&quot;&gt;&lt;img src=&quot;/images/xd-dots.png&quot; width=&quot;100%&quot; /&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;In particular, we use a mapping of code page 437 to Unicode that &lt;em&gt;includes&lt;/em&gt; the dingbats and other symbols that lived in the ROM at 01h–1Fh, plus a tweak that replaces U+0000 NULL with U+2400 SYMBOL FOR NULL at 00h.
The end result is a reversible text representation that’s fairly visually distinct&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;h2 id=&quot;learning-how-to-ship&quot;&gt;Learning how to ship&lt;/h2&gt;

&lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
    &lt;pre&gt;&lt;code&gt;[2016-01-04 00:36:31] &amp;lt;aedomsan&amp;gt;
what are you working on delan?

[2016-01-04 00:39:47] &amp;lt;delan&amp;gt;
aedomsan: a tool like od/hexdump/xxd where every byte has a glyph instead of dots everywhere

[five months later]&lt;!-- [2016-01-04 00:39:47] &amp;lt;delan&gt; http://puu.sh/mic8J/6abdf90ce6.png --&gt;

[2016-06-18 19:34:48] &amp;lt;Jaci&amp;gt; 
delan you still got that hexview program?

[2016-06-19 18:33:35] &amp;lt;delan&amp;gt;
yeah Jaci, I’m still working on it&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;This is actually my third attempt at executing this idea in this form&lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;I first wrote &lt;a href=&quot;https://bitbucket.org/delan/xd/commits/branch/prototype.c&quot;&gt;a version of this tool&lt;/a&gt; in C, before I even got into Rust, and it was pretty elaborate.
There were &lt;a href=&quot;https://bitbucket.org/delan/xd/commits/b01eb4396a235fe6c85d30fc24ca497b60f3018c#Largv.cT10&quot;&gt;custom format strings&lt;/a&gt; powered by &lt;a href=&quot;https://bitbucket.org/delan/xd/commits/b01eb4396a235fe6c85d30fc24ca497b60f3018c#Lcontext.cT72&quot;&gt;a nearly-incomprehensible hand-rolled parser&lt;/a&gt;, a cute little &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--help&lt;/code&gt; system that would essentially popen(3) a pager like less(1) and &lt;a href=&quot;https://bitbucket.org/delan/xd/commits/b01eb4396a235fe6c85d30fc24ca497b60f3018c#Lmain.cT73&quot;&gt;pipe documentation text from inside the program&lt;/a&gt;, and the project even taught me how to &lt;a href=&quot;https://bitbucket.org/delan/xd/commits/41d60216d694de579844b8ba9259834e753693ec&quot;&gt;use fuzzing to fix bugs&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;A couple of years later, &lt;a href=&quot;https://bitbucket.org/delan/xd/commits/branch/prototype.rs&quot;&gt;I ported it to Rust&lt;/a&gt; as my first foray into the language.
I kept many of the more… useful features (the popen(3)-to-pager trick was more &lt;em&gt;cute&lt;/em&gt; than &lt;em&gt;useful&lt;/em&gt;), learned to &lt;a href=&quot;https://crates.io/crates/nom&quot;&gt;use parser combinators&lt;/a&gt; with the new &lt;a href=&quot;https://bitbucket.org/delan/xd/commits/d804e1cdd0dd5a4a7c5279624c592e3b8d44b4a5#Lsrc/format.rsT138&quot;&gt;custom format parser&lt;/a&gt;, and wrote enough functionality that I could start &lt;a href=&quot;https://bitbucket.org/delan/xd/commits/e387383e193e2afd1a54e8a437cadc57595f0594?at=prototype.rs#Lsrc/options.rsT85&quot;&gt;emulating the competition&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The problem was that I hadn’t really &lt;em&gt;released&lt;/em&gt; many of my projects before, and I was more than happy to perpetually avoid dealing with &lt;em&gt;that&lt;/em&gt; can of worms in favour of using the project as a training ground for learning how to build new things.
Rust certainly helped — unlike C, it has a fantastic &lt;a href=&quot;https://doc.rust-lang.org/cargo/&quot;&gt;package manager&lt;/a&gt; and a &lt;a href=&quot;https://crates.io&quot;&gt;place to put your packages&lt;/a&gt; — and that’s where &lt;a href=&quot;https://crates.io/crates/ing2ynab&quot;&gt;almost&lt;/a&gt; &lt;a href=&quot;https://crates.io/crates/nonymous&quot;&gt;everything&lt;/a&gt; &lt;a href=&quot;https://crates.io/crates/pledge&quot;&gt;else&lt;/a&gt; I’ve managed to release (or help release) has gone.&lt;/p&gt;

&lt;p&gt;But the lesson that really made this project see the light of day?
To build something that has any broader impact, you can’t just experiment forever, rewriting your project over and over (but better this time) and trying to perfect a feature set that will blow everyone out of the water on version zero.&lt;/p&gt;

&lt;p&gt;At the end of the day, you’ve got to actually turn it into a thing that’s useful to someone (even just you or your friends), be it a package they can install, &lt;a href=&quot;https://charming.daz.cat&quot;&gt;a service&lt;/a&gt; they can use, a &lt;a href=&quot;https://www.youtube.com/watch?v=hMGM6s1Qw_Q&quot;&gt;video&lt;/a&gt; and &lt;a href=&quot;/2020/06/29/bad-apple-for-taskmgr.html&quot;&gt;writeup&lt;/a&gt; they can enjoy, or &lt;em&gt;something&lt;/em&gt;.
And from there, you can iterate.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Though not perfect, for example, if you’re looking at the various spaces (20h FFh), middle dots (07h F9 FAh), squares (DCh FEh), box drawing characters, or that U+207F (FCh) that inexplicably isn’t superscript (ⁿ) in my terminal font. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;It’s my fourth in general, if you count the time I convinced my colleague at $dayjob to use this technique to display untrusted paths in customer backups (&lt;a href=&quot;https://bucket.daz.cat/rewind/path.html&quot;&gt;and wrote a tool&lt;/a&gt; to help with that). &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name></name></author><category term="home" /><category term="rust" /><summary type="html">xd(1) is a tool that dumps binary input in a more human-readable format. There are countless tools that fit this description, including od(1), hexdump(1), xxd(1), and a bunch of Rust crates in the same vein, but this one has a new trick up its sleeve.</summary></entry><entry><title type="html">Output version control for static sites</title><link href="https://www.azabani.com/2020/10/03/output-version-control-for-static-sites.html" rel="alternate" type="text/html" title="Output version control for static sites" /><published>2020-10-03T13:00:00+00:00</published><updated>2020-10-03T13:00:00+00:00</updated><id>https://www.azabani.com/2020/10/03/output-version-control-for-static-sites</id><content type="html" xml:base="https://www.azabani.com/2020/10/03/output-version-control-for-static-sites.html">&lt;p&gt;I use &lt;a href=&quot;https://jekyllrb.com&quot;&gt;Jekyll&lt;/a&gt;, a static site generator that’s most notable for powering &lt;a href=&quot;https://pages.github.com&quot;&gt;GitHub Pages&lt;/a&gt;.
Two years ago, I noticed that the permalinks for several of my blog posts were broken by a Jekyll bug after I upgraded to Ruby 2.4.
I’ve since written a Git-based system to stop this kind of breakage from ever reappearing, which should be useful to anyone who uses a static site generator and wants to avoid corruption or inadvertent changes.&lt;/p&gt;

&lt;h2 id=&quot;the-bug&quot;&gt;The bug&lt;/h2&gt;

&lt;p&gt;You can read more about what happened in &lt;a href=&quot;https://github.com/delan/www.azabani.com/commit/e9b43f305e16341003ec1710d09567359178fc1f&quot;&gt;the commit&lt;/a&gt; where I first wrote the system, but in short, Jekyll used to suffer from &lt;a href=&quot;https://github.com/jekyll/jekyll/pull/6697&quot;&gt;a bug&lt;/a&gt; where the day a post gets filed under can vary depending on Ruby version… because time zones.&lt;/p&gt;

&lt;p&gt;I was using an affected version, and when I upgraded to Ruby 2.4 with a new OS release, ten posts got “moved” to the next day.
It took me five months to notice this, and search engines had already adjusted to the damage.&lt;/p&gt;

&lt;p&gt;I migrated my site from Jekyll 3.0.1 to 3.8.3, which wasn’t too hard thanks to the project’s &lt;a href=&quot;https://jekyllrb.com/philosophy/#4-stability&quot;&gt;stability philosophy&lt;/a&gt;, but I also created some &lt;a href=&quot;http://sebastians-pamphlets.com/google-and-yahoo-treat-undelayed-meta-refresh-as-301-redirect/&quot;&gt;static redirects&lt;/a&gt; to keep the broken permalinks working, in case any backlinks were made during that time.&lt;/p&gt;

&lt;figure&gt;
  &lt;figcaption&gt;&lt;code&gt;_layouts/legacy.html&lt;/code&gt;&lt;/figcaption&gt;
  &lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;charset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;http-equiv=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Refresh&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;content=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;0;
    URL={{ site.url }}{{ site.baseurl }}{{ content | strip_newlines }}&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;figcaption&gt;&lt;code&gt;2014/01/03/forcing-single-timezone-jekyll.html&lt;/code&gt;&lt;/figcaption&gt;
  &lt;div class=&quot;scroll&quot;&gt;
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;---
layout: legacy
---
{% post_url 2014-01-02-forcing-single-timezone-jekyll %}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/div&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;the-system&quot;&gt;The system&lt;/h2&gt;

&lt;p&gt;But anyway, back to the system.
The idea behind output version control is to track the actual files that get served, so when we want to generate and deploy a new version of our site, we can see how the site would actually change.&lt;/p&gt;

&lt;p&gt;The whole system is powered by a makefile, which sets up a special Git repository at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_staging&lt;/code&gt; that tracks the site generator’s output.
This repo is unrelated to any Git repo we might be using for the &lt;em&gt;source&lt;/em&gt; of the site, so it will never get confused by branches or refactoring.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_staging&lt;/code&gt; lets you examine the changes that would be made by deploying your site.
If you’re happy with the changes, we clone the repo to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_production&lt;/code&gt;, so to use this system, reconfigure your web server to serve your site from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_production/_site&lt;/code&gt;, rather than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_site&lt;/code&gt; or wherever your site generator writes to.&lt;/p&gt;

&lt;p&gt;Let’s read it together.
I like to put &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.POSIX:&lt;/code&gt; &lt;a href=&quot;https://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html#tag_20_76_13_04&quot;&gt;on the first line&lt;/a&gt; out of habit, though strictly speaking our use of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.PHONY:&lt;/code&gt; makes this &lt;a href=&quot;https://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html#tag_20_76_03&quot;&gt;not a portable makefile&lt;/a&gt; anyway.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BUNDLE&lt;/code&gt; lets us override how we run &lt;a href=&quot;https://bundler.io&quot;&gt;Bundler&lt;/a&gt;, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make BUNDLE=bundle24&lt;/code&gt; on OpenBSD for example.&lt;/p&gt;

&lt;div class=&quot;language-makefile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nl&quot;&gt;.POSIX&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;BUNDLE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; bundle
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When we &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make dry&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make&lt;/code&gt; with no targets, generate the site into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_site&lt;/code&gt;, copy &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_site&lt;/code&gt; to the output repo’s working tree, check if the index is dirty, and copy the working tree to the index.
Our use of the index is interesting here.
By checking and bailing out if the index is dirty, we’re forced to either deploy or reject any previously staged changes, which might prevent accidental data loss.&lt;/p&gt;

&lt;div class=&quot;language-makefile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nl&quot;&gt;dry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;_staging&lt;/span&gt;
	&lt;span class=&quot;nv&quot;&gt;$(BUNDLE)&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;jekyll build
	rsync &lt;span class=&quot;nt&quot;&gt;-a&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--delete&lt;/span&gt; _site _staging
	git &lt;span class=&quot;nt&quot;&gt;-C&lt;/span&gt; _staging diff &lt;span class=&quot;nt&quot;&gt;--cached&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--quiet&lt;/span&gt;
	git &lt;span class=&quot;nt&quot;&gt;-C&lt;/span&gt; _staging add _site
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When we &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make examine&lt;/code&gt;, display the staged changes as a diff between HEAD and the index, so we can decide whether we’re happy with them.&lt;/p&gt;

&lt;div class=&quot;language-makefile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nl&quot;&gt;examine&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;_staging&lt;/span&gt;
	git &lt;span class=&quot;nt&quot;&gt;-C&lt;/span&gt; _staging diff &lt;span class=&quot;nt&quot;&gt;--cached&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When we &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make deploy&lt;/code&gt;, commit the changes as the new HEAD, then update the clone at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_production&lt;/code&gt; to reflect them.&lt;/p&gt;

&lt;div class=&quot;language-makefile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nl&quot;&gt;deploy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;_staging _production&lt;/span&gt;
	git &lt;span class=&quot;nt&quot;&gt;-C&lt;/span&gt; _staging commit &lt;span class=&quot;nt&quot;&gt;--allow-empty&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--allow-empty-message&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&apos;&lt;/span&gt;
	git &lt;span class=&quot;nt&quot;&gt;-C&lt;/span&gt; _production pull
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When we &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make reject&lt;/code&gt;, throw away the staged changes by resetting the index to HEAD.
In this situation, as well as when &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make dry&lt;/code&gt; bails out, the working tree could still be dirty.
While we could clean it with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--hard&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git clean -dffx&lt;/code&gt;, we don’t &lt;em&gt;need&lt;/em&gt; to, because we only ever copy the working tree to the index after completely rewriting it with rsync(1).&lt;/p&gt;

&lt;div class=&quot;language-makefile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nl&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;_staging&lt;/span&gt;
	git &lt;span class=&quot;nt&quot;&gt;-C&lt;/span&gt; _staging reset
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To set up &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_staging&lt;/code&gt; for the first time, create the directory and make it a Git repo.
To set up &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_production&lt;/code&gt; for the first time, clone &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_staging&lt;/code&gt; as a Git repo.&lt;/p&gt;

&lt;div class=&quot;language-makefile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nl&quot;&gt;_staging&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
	&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;$@&apos;&lt;/span&gt;
	&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;$@&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; git init

&lt;span class=&quot;nl&quot;&gt;_production&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
	git clone _staging _production
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Never skip the commands under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dry&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;examine&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deploy&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reject&lt;/code&gt; based on modified times, even if there happens to be a file with one of those names in the site’s sources.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.PHONY:&lt;/code&gt; isn’t yet in the &lt;a href=&quot;https://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html&quot;&gt;POSIX spec&lt;/a&gt;, but it has widespread support, including the GNU and OpenBSD versions.&lt;/p&gt;

&lt;div class=&quot;language-makefile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nl&quot;&gt;.PHONY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;dry examine deploy reject&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s all there is to it!
Is this the perfect system?
No, but it suffices for my needs.&lt;/p&gt;

&lt;h3 id=&quot;limitations&quot;&gt;Limitations&lt;/h3&gt;

&lt;p&gt;If your site’s output has non-deterministic parts, those parts can make the output of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make examine&lt;/code&gt; noisy.
The one example I’ve encountered is &lt;a href=&quot;https://github.com/jekyll/jekyll-feed&quot;&gt;jekyll-feed&lt;/a&gt;’s &amp;lt;updated&amp;gt; tags, when I added Atom feeds &lt;a href=&quot;https://github.com/delan/www.azabani.com/commit/113a37f7e712ddc626f04039474e723620f33b3f&quot;&gt;a few months ago&lt;/a&gt;.
You could probably fix this with &lt;a href=&quot;https://git-scm.com/docs/gitattributes#_performing_text_diffs_of_binary_files&quot;&gt;textconv or an external diff command&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We can, of course, access older versions of our site’s output with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git -C _production&lt;/code&gt;, but the system as described doesn’t store the inputs that they were generated from (or even hints as to where we can find them, like commit hashes).
Feel free to implement this, but note that your site’s sources aren’t necessarily the only relevant input!
For example, the inputs that broke my permalinks were my Ruby and Jekyll versions.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_production&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_staging&lt;/code&gt; will store an unbounded amount of history, so if you need to reclaim space, you’ll want to use something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git rebase -i --root&lt;/code&gt; to squash old commits followed by a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git gc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There’s still a lot of unnecessary overhead.
The initial clone to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_production&lt;/code&gt; uses hard links but subsequent pulls don’t, so we should probably use rsync(1) rather than cloning &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_staging&lt;/code&gt;.
When the output advances to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_staging&lt;/code&gt; and then to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_production&lt;/code&gt;, we can also delete the redundant copies in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_site&lt;/code&gt; and then in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_staging&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There’s no easy way for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make examine&lt;/code&gt; to accept additional arguments, such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--word-diff&lt;/code&gt;, that are then passed to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git diff --cached&lt;/code&gt;.
If we used a shell script instead of a makefile, this wouldn’t be a problem:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;shift

&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$action&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;examine&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; git &lt;span class=&quot;nt&quot;&gt;-C&lt;/span&gt; _staging diff &lt;span class=&quot;nt&quot;&gt;--cached&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$@&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;;;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;esac&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;b-side-incremental-builds&quot;&gt;B-side: incremental builds&lt;/h2&gt;

&lt;p&gt;I’ve got a thing for using secondary Git repositories in project tooling.
When I worked at Atlassian, I wrote an incremental build system for &lt;a href=&quot;https://maven.apache.org&quot;&gt;Maven&lt;/a&gt; using a similar technique.
Maven doesn’t support incremental builds out of the box, but &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mvn -pl x,y,...&lt;/code&gt; tells &lt;a href=&quot;https://maven.apache.org/guides/mini/guide-multiple-modules.html#the-reactor&quot;&gt;the reactor&lt;/a&gt; to only try to build a subset of our project’s modules. If we could figure out which modules need to be rebuilt, we can implement coarse-grained incremental builds!&lt;/p&gt;

&lt;p&gt;In this case, the secondary Git repo, which I call a “shadow repo”, tracks the inputs rather than the outputs.
But Delan, you ask, doesn’t the primary repo already do that?
Kind of, but the user (i.e. one of my colleagues) is constantly messing around with it, and it’s difficult to implement robust incremental builds atop a repo that we don’t control, because we have no way of knowing what the inputs were &lt;em&gt;during the last successful build&lt;/em&gt;.
The system I replaced tried anyway, and it got confused by everything from dirty indexes and working trees to the user switching branches.&lt;/p&gt;

&lt;p&gt;My new system roughly worked as follows.
We create the shadow repo, which is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--bare&lt;/code&gt; to avoid the redundant working tree.
To interact with it, we point &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GIT_DIR&lt;/code&gt; to the shadow repo and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GIT_WORK_TREE&lt;/code&gt; to the real repo’s working tree.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/sh&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-eu&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;git rev-parse &lt;span class=&quot;nt&quot;&gt;--show-toplevel&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;exit &lt;/span&gt;66
git init &lt;span class=&quot;nt&quot;&gt;--bare&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$top&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/.shadow/repository&quot;&lt;/span&gt;

shadow&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;GIT_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$top&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/.shadow/repository&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;GIT_WORK_TREE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$top&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; git &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$@&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Before each build attempt, we copy everything except build outputs to the index, thanks to the project’s own gitignore(5), and get a list of paths that have changed since the last successful build (HEAD).&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;shadow add &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$top&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
shadow status &lt;span class=&quot;nt&quot;&gt;-z&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We then convert the list of paths to a set of Maven modules, by finding their nearest ancestors containing a pom.xml, then pass that set to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mvn -am -pl&lt;/code&gt;.
I no longer have any of the original source code, so I’ll leave this as an exercise for the reader.&lt;/p&gt;

&lt;p&gt;If and only if the build succeeds, we commit the new build inputs.
We get those from the index, which also prevents false negatives in the next build attempt, were the user to have edited any files during the build process.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;commit&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    shadow commit &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&apos;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--allow-empty&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--allow-empty-message&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$@&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

commit &lt;span class=&quot;nt&quot;&gt;--amend&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; commit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;closing-thoughts&quot;&gt;Closing thoughts&lt;/h2&gt;

&lt;p&gt;I hope you found that interesting!
Perhaps you’ll even see Git in a new light, finding ways to use the stupid content tracker™ other than version control for source code.&lt;/p&gt;

&lt;p&gt;I’ve been afraid to check if anyone has done this before, because I felt like if I was &lt;em&gt;aware&lt;/em&gt; of any prior art, I would lose my enthusiasm for solving this problem and writing about it.
But now that I’m done, that no longer matters.
Let me know if great minds think alike!&lt;/p&gt;</content><author><name></name></author><category term="home" /><category term="jekyll" /><summary type="html">I use Jekyll, a static site generator that’s most notable for powering GitHub Pages. Two years ago, I noticed that the permalinks for several of my blog posts were broken by a Jekyll bug after I upgraded to Ruby 2.4. I’ve since written a Git-based system to stop this kind of breakage from ever reappearing, which should be useful to anyone who uses a static site generator and wants to avoid corruption or inadvertent changes.</summary></entry><entry><title type="html">My internship with Igalia</title><link href="https://www.azabani.com/2020/09/27/my-internship-with-igalia.html" rel="alternate" type="text/html" title="My internship with Igalia" /><published>2020-09-27T09:00:00+00:00</published><updated>2020-09-27T09:00:00+00:00</updated><id>https://www.azabani.com/2020/09/27/my-internship-with-igalia</id><content type="html" xml:base="https://www.azabani.com/2020/09/27/my-internship-with-igalia.html">&lt;p&gt;I was looking for a job late last year when I saw &lt;a href=&quot;https://twitter.com/andywingo/status/1190917731312439296&quot;&gt;a tweet&lt;/a&gt; about a place called &lt;a href=&quot;https://www.igalia.com/about/&quot;&gt;Igalia&lt;/a&gt;.
The more I learned about them, the more interested I became, and before long I &lt;a href=&quot;https://www.igalia.com/jobs/web_platform_engineer&quot;&gt;applied to join&lt;/a&gt; their Web Platform team.
I didn’t have enough experience for a permanent position, but they &lt;em&gt;did&lt;/em&gt; offer me a place in their &lt;a href=&quot;https://www.igalia.com/coding-experience/&quot;&gt;Coding Experience&lt;/a&gt; program, which as far as I can tell is basically an internship, and I thoroughly enjoyed it.
Here’s an overview of what I did and what I learned.&lt;/p&gt;

&lt;style&gt;
figure { text-align: center; }
figure img { max-width: 100%; }
&lt;/style&gt;

&lt;h2 id=&quot;contents&quot;&gt;Contents&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#why-igalia&quot;&gt;Why Igalia?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#ſtylesheet&quot;&gt;ſtylesheet&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#fixing-the-bug&quot;&gt;Fixing the bug&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#writing-some-tests&quot;&gt;Writing some tests&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#mathml-tasks&quot;&gt;MathML tasks&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#momaxsize&quot;&gt;mo@maxsize&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#stixgeneral&quot;&gt;STIXGeneral&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#padding--border--margin&quot;&gt;padding + border + margin&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#acknowledgements&quot;&gt;Acknowledgements&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;why-igalia&quot;&gt;Why Igalia?&lt;/h2&gt;

&lt;p&gt;There’s a wide range of work I can do as a computer programmer, but the vast majority of it seems to be in closed-source web applications, as an employee with a limited voice in the decisions that affect my work.&lt;/p&gt;

&lt;p&gt;At the time, all of my work since I graduated had been exactly that, or in builds and releases for said applications.
That was interesting enough for a while, but I wanted to make a bigger impact, work on something I actually cared about of my own volition, and ideally move towards getting paid to do systems programming.&lt;/p&gt;

&lt;p&gt;Igalia appeals to me, with their focus on open-source projects, systems programming, and standards work.
Even better, as a field, the web platform has been my one true love, and building things on it is how I got into programming over 15 years ago.
But what cements their place as my “dream job” is &lt;em&gt;how&lt;/em&gt; they work: as a distributed &lt;a href=&quot;https://en.wikipedia.org/wiki/Worker_cooperative&quot;&gt;worker’s cooperative&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What I mean by “distributed” is that members can work from anywhere in the world, paid in a way that fairly adjusts for location, and in whatever setting they thrive in (such as home).
This alone was huge, as someone who can’t sustainably work in an office five days a week, had to move 4000 km away from home to do so, and had just left an employer that was actively hostile to remote work.&lt;/p&gt;

&lt;p&gt;Andy Wingo (author of &lt;a href=&quot;https://twitter.com/andywingo/status/1190917731312439296&quot;&gt;that tweet&lt;/a&gt;) offers some insight into the “worker’s cooperative” part in &lt;a href=&quot;https://wingolog.org/archives/2013/06/05/no-master&quot;&gt;these&lt;/a&gt; &lt;a href=&quot;https://wingolog.org/archives/2013/06/13/but-that-would-be-anarchy&quot;&gt;three&lt;/a&gt; &lt;a href=&quot;https://wingolog.org/archives/2013/06/25/time-for-money&quot;&gt;posts&lt;/a&gt;.
Igalia’s rough goal here, as far as I can tell, is that everyone gets a voice in deciding what the collective works on and how (to the extent that those decisions affect them), equal ownership of the business, and equivalent pay modulo effort and cost of living.
This appeals to me &lt;a href=&quot;/notes/anarchism101.html&quot;&gt;as an anarchist&lt;/a&gt;, but also as a worker that has often been on the receiving end of unethical work, poor working conditions, and lack of autonomy.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;ſtylesheet&quot; style=&quot;font-family: Symbola;&quot;&gt;ſtylesheet&lt;/h2&gt;

&lt;p&gt;One goal of my internship was to help the Web Platform team with their MathML work, but I was also there to familiarise myself with working on the web platform, and my first task was purely for the latter.&lt;/p&gt;

&lt;p&gt;Many parts of the web platform have case-insensitive keywords that control an API or language feature, like &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-rel&quot;&gt;link@rel&lt;/a&gt; (the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;link rel=&quot;...&quot;&amp;gt;&lt;/code&gt; attribute), but thanks to Unicode, there’s more than one level of case-insensitivity.
Unicode case-insensitivity &lt;a href=&quot;https://unicode.org/faq/casemap_charprop.html#13&quot;&gt;won’t break&lt;/a&gt; backwards compatibility of web content over time, but to improve interoperability and simplify implementations, things like the HTML spec tend to explicitly call for ASCII case-insensitivity, at least for keywords that are nominally ASCII.&lt;/p&gt;

&lt;p&gt;That makes &lt;a href=&quot;https://en.wikipedia.org/wiki/Blink_(browser_engine)&quot;&gt;Blink’s&lt;/a&gt; widespread use of Unicode case-insensitivity in these situations a bug, and my job was to fix that bug, which sounds simple enough, until you realise that doing so is technically a breaking change.
You see, there are already a couple of non-ASCII characters that can introduce esoteric ways to write many of those keywords.&lt;/p&gt;

&lt;p&gt;More importantly, the web platform is almost&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; unique in that breaking existing content is, in general, not allowed.
But this time a breaking change was unavoidable, like any time where an implementation is fixed to align with the standard, or some behaviour is standardised after incompatible implementations appear.
There might be content out there that relies on something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;link rel=&quot;ſtylesheet&quot;&amp;gt;&lt;/code&gt; because it worked on Chromium.&lt;/p&gt;

&lt;p&gt;There are &lt;a href=&quot;https://www.chromium.org/blink/platform-predictability/compat-tools&quot;&gt;a few ways&lt;/a&gt; to minimise the impact of these breaking changes, like adding analytics to browsers to count how many pages would be affected, or searching archives of web content, but in this case we decided the risk was low enough that I could simply fix the bug and write some tests.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.chromestatus.com/feature/5734362161086464&quot;&gt;Chrome Platform Status entry&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.google.com/document/d/1uZ0wMBF63eLJNbW3yz1oGK7qkO3VqRUwdmNAbiLanN8&quot;&gt;intent to remove&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://groups.google.com/a/chromium.org/d/topic/blink-dev/sFOpNuQ91UU&quot;&gt;blink-dev thread&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://bucket.daz.cat/crbug-627682.html&quot;&gt;analysis of deprecated call sites&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://crbug.com/627682&quot;&gt;issue 627682: tracking bug for deprecated string operations&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://crbug.com/1060477&quot;&gt;issue 1060477: HTMLElement::ApplyAlignmentAttributeToStyle&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://crbug.com/1060495&quot;&gt;issue 1060495: HiddenInputType::AppendToFormData&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://crbug.com/1060499&quot;&gt;issue 1060499: &amp;lt;param name=”src” value=”…”&amp;gt; + &amp;lt;object data=”…”&amp;gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://crrev.com/c/1997014&quot;&gt;CL 1997014: Element#insertAdjacentElement + Element#insertAdjacentText&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://crrev.com/c/2015875&quot;&gt;CL 2015875: DeprecatedEqual: safe subset part 1/2 (NFC)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://crrev.com/c/2032654&quot;&gt;CL 2032654: DeprecatedEqual: safe subset part 2/2 (NFC)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://crrev.com/c/2032655&quot;&gt;CL 2032655: DeprecatedEqual: HTML attribute values (including WPT)&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;https://github.com/web-platform-tests/wpt/pull/22064&quot;&gt;web-platform-tests/wpt#22064: new web platform tests (automatic PR)&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://crrev.com/c/2106983&quot;&gt;CL 2106983: DeprecatedEqual: @import + @charset&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://crrev.com/c/2108441&quot;&gt;CL 2108441: DeprecatedEqual: all other ASCII literals&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://crrev.com/c/2113394&quot;&gt;CL 2113394: DeprecatedEqual: all other ASCII constants&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://crrev.com/c/2114510&quot;&gt;CL 2114510: DeprecatedLower: where only compared with ASCII&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://crrev.com/c/2121937&quot;&gt;CL 2121937: simplify MapDataParamToSrc (NFC)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;fixing-the-bug&quot;&gt;Fixing the bug&lt;/h3&gt;

&lt;p&gt;It’s hard to get a usable &lt;a href=&quot;https://microsoft.github.io/language-server-protocol/&quot;&gt;LSP&lt;/a&gt; setup going for a project as big as a browser.
I switched between &lt;a href=&quot;https://github.com/MaskRay/ccls&quot;&gt;ccls&lt;/a&gt; and &lt;a href=&quot;https://clangd.llvm.org&quot;&gt;clangd&lt;/a&gt; a bunch of times, but I never quite got either working too well.
My main machine is also getting pretty long in the tooth, which made indexing take forever and updating my branches expensive.&lt;/p&gt;

&lt;p&gt;I considered writing an LSP client that would allow me to kick off an index on one of Igalia’s 128-thread build boxes without an editor, but I eventually settled on using &lt;a href=&quot;https://source.chromium.org/&quot;&gt;Chromium Code Search&lt;/a&gt; to jump around and investigate things.
Firefox similarly has &lt;a href=&quot;https://searchfox.org&quot;&gt;Searchfox&lt;/a&gt;&lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;, but WebKit doesn’t yet have a public counterpart&lt;sup id=&quot;fnref:3&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;I was looking for callers of three deprecated functions, but not all of them were relevant to the bug, and not all of &lt;em&gt;those&lt;/em&gt; needed tests, and so on.
To help me analyse and categorise all of the potential call sites, I wrote some pretty intricate regular expressions for Sublime Text 2.
This one finds all callers of DeprecatedEqualIgnoringCase, with two arguments, where one of them is an ASCII literal that wouldn’t need new tests (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;skSK&lt;/code&gt;):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;(?x-i)
(?&amp;lt;escape&amp;gt;\\[&apos;&quot;?\\abfnrtv]){0}
(?&amp;lt;literal&amp;gt;&quot;(?:(?=[ -~])[^&quot;skSK]|(?&amp;amp;escape))*&quot;){0}
(?&amp;lt;any&amp;gt;(?:[^(),]|(\((?:[^()]*|(?-1))\)))*+){0}
DeprecatedEqualIgnoringCase
(\s*\(\s*+(?:
    (?&amp;amp;literal)\s*,\s*+(?&amp;amp;any)
    |(?&amp;amp;any)\s*,\s*+(?&amp;amp;literal)
)\s*\))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After &lt;a href=&quot;https://chromium-review.googlesource.com/c/chromium/src/+/1997014&quot;&gt;my first patch&lt;/a&gt;, which I wrote by hand, I also used those to do the actual replacing, maintaining &lt;a href=&quot;https://bucket.daz.cat/crbug-627682.html&quot;&gt;a huge analysis&lt;/a&gt; of all the cases that remained after &lt;a href=&quot;https://chromium-review.googlesource.com/c/chromium/src/+/2015875&quot;&gt;my second patch&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;writing-some-tests&quot;&gt;Writing some tests&lt;/h3&gt;

&lt;p&gt;Each of the major engines has its own web content tests, and automated tests are strongly preferred over manual tests if at all possible.
All of the tests I wrote were automated, and most were &lt;strong&gt;&lt;a href=&quot;https://web-platform-tests.org&quot;&gt;Web Platform Tests&lt;/a&gt;&lt;/strong&gt;, which are especially cool because they’re a shared suite of web content tests that can be run on any browser.
Chromium and Firefox even automatically upstream changes to their vendored WPT trees!&lt;/p&gt;

&lt;p&gt;Many of my tests were for values of HTML attributes whose &lt;strong&gt;invalid value default&lt;/strong&gt; was a different state to the keyword’s state.
In these cases, I didn’t even need to assert anything about the attribute’s actual behaviour!
All I had to do was write a tag, read the attribute in JavaScript, and check if the value we get back corresponds to the intended feature (bad) or the invalid value default (good).&lt;/p&gt;

&lt;figure&gt;&lt;a href=&quot;/images/igalia-0.png&quot;&gt;&lt;img src=&quot;/images/igalia-0.png&quot; /&gt;&lt;/a&gt;&lt;/figure&gt;

&lt;p&gt;Some legacy HTML attributes are now specified in terms of CSS “presentational hints”, so I checked the results of &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle&quot;&gt;getComputedStyle&lt;/a&gt; for those, but the coolest tests I learned to write were &lt;strong&gt;reftests&lt;/strong&gt;.
Very few web platform features guarantee that every user agent on every platform will render them identically down to the pixel, and over time, unrelated platform changes can affect a test’s expected rendering.
Both of these things are ok, but they make it impractical for tests to compare web content against screenshots.
Reftests consist of a test page that uses the feature being tested, and a reference page that should look the same without using the feature.
The reference page is like a screenshot, but it’s subject to all of the same variables as the test page, such as font rendering.&lt;/p&gt;

&lt;figure&gt;&lt;a href=&quot;/images/igalia-1.png&quot;&gt;&lt;img src=&quot;/images/igalia-1.png&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;/images/igalia-2.png&quot;&gt;&lt;img src=&quot;/images/igalia-2.png&quot; /&gt;&lt;/a&gt;&lt;/figure&gt;

&lt;p&gt;Ever heard of the &lt;a href=&quot;https://www.acidtests.org&quot;&gt;Acid Tests&lt;/a&gt;?
&lt;a href=&quot;http://acid2.acidtests.org&quot;&gt;Acid2&lt;/a&gt; is more or less a reftest, because it has &lt;a href=&quot;http://acid2.acidtests.org/reference.html&quot;&gt;a reference page&lt;/a&gt; that only uses a screenshot for the platform-independent parts.
&lt;a href=&quot;http://acid1.acidtests.org&quot;&gt;Acid1&lt;/a&gt; uses &lt;a href=&quot;https://www.w3.org/Style/CSS/Test/CSS1/current/sec5526c.gif&quot;&gt;a screenshot&lt;/a&gt; of the whole test, hence “except font rasterization and form widgets”.&lt;/p&gt;

&lt;p&gt;I had a lot of fun writing my &lt;a href=&quot;http://wpt.live/html/semantics/forms/the-input-element/hidden-charset-case-sensitive.html&quot;&gt;two&lt;/a&gt; &lt;a href=&quot;http://wpt.live/html/semantics/forms/the-textarea-element/wrap-enumerated-ascii-case-insensitive.html&quot;&gt;form-related&lt;/a&gt; tests, because I actually had to submit forms to observe those features’ behaviour.
WPT has server-side testing infrastructure that can help with this, and for such tests, I would need to spin up the provided web server or run the finished product with &lt;a href=&quot;http://wpt.live&quot;&gt;wpt.live&lt;/a&gt;&lt;sup id=&quot;fnref:4&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;In both cases, I avoided the need for that with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;form method=&quot;GET&quot;&amp;gt;&lt;/code&gt; that targets an iframe, plus a helper page that sends its query string back to the test page.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;mathml-tasks&quot;&gt;MathML tasks&lt;/h2&gt;

&lt;p&gt;MathML was meant to be the native language for mathematics on the web, and that’s still true today, but two decades later, browser support &lt;a href=&quot;https://aperiodical.com/2013/11/dark-days-for-mathml-support-in-browsers/&quot;&gt;still has a long way to go&lt;/a&gt;.
There are several reasons for this, notably including &lt;a href=&quot;https://web.archive.org/web/20141214030114/http://www.maths-informatique-jeux.com/blog/frederic/?post%2F2013%2F10%2F12%2FFunding-MathML-Developments-in-Gecko-and-WebKit&quot;&gt;the largely volunteer-driven development&lt;/a&gt; of MathML and its implementations, but over the last few years, Igalia has helped change that on three fronts: &lt;a href=&quot;https://mathml.igalia.com&quot;&gt;writing a Chromium implementation&lt;/a&gt;, improving the Firefox and WebKit implementations, and &lt;a href=&quot;https://www.w3.org/community/mathml4/&quot;&gt;improving the specs themselves&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.w3.org/TR/MathML3/&quot;&gt;MathML 3&lt;/a&gt; was made a Recommendation in 2014, and like any spec, it has shortcomings that only subsequent experience could identify.
Proposals by the &lt;a href=&quot;https://www.w3.org/community/mathml4/&quot;&gt;MathML Refresh CG&lt;/a&gt; like &lt;a href=&quot;https://mathml-refresh.github.io/mathml-core/&quot;&gt;MathML Core&lt;/a&gt; are trying to address them in a bunch of ways, like simplifying the spec, setting clearer expectations around rendering, and redefining features in terms of better-supported CSS constructs.
My remaining tasks touched on some of these.&lt;/p&gt;

&lt;h3 id=&quot;momaxsize&quot;&gt;mo@maxsize&lt;/h3&gt;

&lt;p&gt;Moving onto WebKit, my next task was to remove some dead code.
Past versions of MathML specify a very complex &amp;lt;mstyle&amp;gt; with its own inheritance system that’s incompatible with CSS, as well as several attributes that were rarely if ever used by authors, both of which are a burden on implementors.&lt;/p&gt;

&lt;p&gt;One of those attributes was mstyle@maxsize, which would serve as the default mo@maxsize instead of infinity.
With the former removed from the spec, there was no longer a need for an explicit infinity value, so I removed the code for that.&lt;/p&gt;

&lt;p&gt;It turns out WebKit never got around to implementing mstyle@maxsize anyway, so there was no functional change.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/mathml-refresh/mathml/issues/1&quot;&gt;mathml-refresh/mathml#1: simplify the mstyle element&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/mathml-refresh/mathml/issues/107&quot;&gt;mathml-refresh/mathml#107: remove explicit mo@maxsize = infinity&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://trac.webkit.org/changeset/259785&quot;&gt;r259785: remove mo@maxsize value “infinity” (NFC)&lt;/a&gt; (&lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=202720&quot;&gt;bug 202720&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;stixgeneral&quot;&gt;STIXGeneral&lt;/h3&gt;

&lt;p&gt;There’s a lot of MathML content that gets rendered like any other text, but stretchy and large operators are a bit more involved than just drawing a single glyph at a single size.
A well-known example of a stretchy operator is square root notation, which consists of a &lt;strong&gt;radical&lt;/strong&gt; (the squiggly part) and a &lt;strong&gt;vinculum&lt;/strong&gt; (the overline part) that stretches to cover the expression being rooted.&lt;/p&gt;

&lt;math display=&quot;block&quot;&gt;
    &lt;msqrt&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;/msqrt&gt;
    &lt;mo&gt;=&lt;/mo&gt;
    &lt;msqrt&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;/msqrt&gt;
    &lt;msqrt&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;/msqrt&gt;
    &lt;!-- &lt;mspace width=&quot;1em&quot;/&gt;
    &lt;mo&gt;∀&lt;/mo&gt;
    &lt;mi&gt;x&lt;/mi&gt;&lt;mo&gt;:&lt;/mo&gt;
    &lt;mi&gt;x&lt;/mi&gt;&lt;mo&gt;∈&lt;/mo&gt;&lt;mi mathvariant=&quot;double-struck&quot;&gt;R&lt;/mi&gt;
    &lt;mo&gt;∧&lt;/mo&gt;
    &lt;mi&gt;x&lt;/mi&gt;&lt;mo&gt;≥&lt;/mo&gt;&lt;mn&gt;0&lt;/mn&gt; --&gt;
&lt;/math&gt;

&lt;p&gt;Traditionally this was achieved by knowing where the glyphs for the separate parts lived in each font, so we could stretch and draw them independently.
Unicode assignments for stretchy operator parts helped, but that wasn’t enough to yield ideal rendering, because many fonts use Private Use Area characters for some operators, and ordinary fonts don’t give applications the necessary tools to control mathematical layout precisely.&lt;/p&gt;

&lt;figure&gt;&lt;a href=&quot;/images/igalia-a.png&quot;&gt;&lt;img src=&quot;/images/igalia-a.png&quot; /&gt;&lt;/a&gt;&lt;/figure&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/typography/opentype/spec/math&quot;&gt;OpenType MATH tables&lt;/a&gt; eventually solved this problem, but that meant Firefox essentially had three code paths: one for OpenType MATH fonts, one with font-specific operator data, and one generic Unicode path for all other fonts.
That second one adds a lot of complexity, and there was only one font left with its own operator data: STIXGeneral.&lt;/p&gt;

&lt;p&gt;The goal was ultimately to remove that code path, dropping support for the font.
That sounded easy enough until we realised that STIXGeneral remains preinstalled on macOS, as the only stock mathematics font, to this day.&lt;/p&gt;

&lt;p&gt;My task here was to add a feature flag that disables the code path on nightly builds, and gather data around how many pages would be affected.
The patch was straightforward, with one change to allow &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Document::WarnOnceAbout&lt;/code&gt; to work with parameterised l10n messages, and I wrote a cute little data URL test page for the warning messages.&lt;/p&gt;

&lt;pre style=&quot;white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;data:text/html;base64,PCFkb2N0eXBlIGh0bWw+CjxtZXRhIGNoYXJzZXQ9InV0Zi04Ij4KPHN0eWxlPgogIG1hdGg6Zmlyc3Qtb2YtdHlwZSB7CiAgICBmb250LWZhbWlseTogTGF0aW4gTW9kZXJuIE1hdGg7CiAgfQogIG1hdGggewogICAgZm9udC1mYW1pbHk6IFNUSVhHZW5lcmFsLCBMYXRpbiBNb2Rlcm4gTWF0aDsKICB9Cjwvc3R5bGU+CjxtYXRoIGRpc3BsYXk9ImJsb2NrIiBtYXRoc2l6ZT0iN2VtIj4KICA8bW8+4oiRPC9tbz48bW8gZGlzcGxheXN0eWxlPSJmYWxzZSI+4oiRPC9tbz4KPC9tYXRoPgo8YnV0dG9uIHR5cGU9ImJ1dHRvbiI+U1RJWEdlbmVyYWw8L2J1dHRvbj4KPHNjcmlwdD4KICBkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCJidXR0b24iKS5hZGRFdmVudExpc3RlbmVyKCJjbGljayIsICh7IHRhcmdldCB9KSA9PiB7CiAgICBjb25zdCBzb3VyY2UgPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCJtYXRoIik7CiAgICB0YXJnZXQuYWZ0ZXIoc291cmNlLmNsb25lTm9kZSh0cnVlKSk7CiAgfSk7Cjwvc2NyaXB0Pgo=&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Turning the feature flag on broke a test though, and I couldn’t for the life of me reproduce it locally.
Fred and I tried every possible strategy we could imagine short of &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Mozilla/QA/Running_automated_tests/TaskCluster_interactive_session&quot;&gt;interactively debugging CI&lt;/a&gt;, on and off for six weeks, but it looked like the flaky behaviour involved some sort of race against &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@font-face&lt;/code&gt; loading.
Eventually we gave up and disabled the feature flag just for that test, and I landed my patch.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://groups.google.com/g/mozilla.dev.tech.mathml/c/PlVCil2X598/m/LfLuZfSVKyYJ&quot;&gt;mozilla.dev.tech.mathml: original context&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://groups.google.com/g/mozilla.dev.platform/c/ufT7Oc42MEc/m/xiOlQxIECQAJ&quot;&gt;mozilla.dev.platform: intent to deprecate&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.fxsitecompat.dev/en-CA/docs/2020/stretching-mathml-operators-with-stix-general-fonts-have-been-deprecated/&quot;&gt;Firefox Site Compatibility note&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1648335&quot;&gt;bug 1648335: STIXGeneral pref gate breaks semantics-1.xhtml&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://phabricator.services.mozilla.com/D73833&quot;&gt;D73833: STIXGeneral use counter and deprecation warning&lt;/a&gt; (&lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1630935&quot;&gt;bug 1630935&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://phabricator.services.mozilla.com/D77067&quot;&gt;D77067: refactor FontFamilyName + FontFamilyList + nsMathMLChar (NFC)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;padding--border--margin&quot;&gt;padding + border + margin&lt;/h3&gt;

&lt;p&gt;Another way to improve the relationship between MathML and CSS has been defining how existing CSS constructs from the HTML world, including the box model properties, apply to MathML content.
In this case, the consensus was that these properties would “inflate” the &lt;strong&gt;content box&lt;/strong&gt; as necessary, making the element occupy more space.&lt;/p&gt;

&lt;p&gt;Existing implementations in WebKit and Firefox didn’t really handle them at all because it wasn’t in the spec, so the last task I had time for was to change that.&lt;/p&gt;

&lt;p&gt;A modern browser starts by parsing documents into an &lt;strong&gt;element tree&lt;/strong&gt;, which is also exposed to authors as the DOM, but when it comes to rendering, that tree is converted to a &lt;strong&gt;layout tree&lt;/strong&gt;, which represents the boxes to be drawn in a hierarchy of position/size influence.
The layout tree consists of &lt;strong&gt;layout nodes&lt;/strong&gt; (Chromium), &lt;strong&gt;renderer nodes&lt;/strong&gt; (WebKit), or &lt;strong&gt;frame nodes&lt;/strong&gt; (Firefox), but these all refer to the same concept.&lt;/p&gt;

&lt;p&gt;I started with Firefox and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mspace&quot;&gt;&amp;lt;mspace&amp;gt;&lt;/a&gt; because that was the only element that could not contain children.
&amp;lt;mspace&amp;gt; represents, well, a space.
It has attributes for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;width&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;height&lt;/code&gt; (height above the baseline), and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;depth&lt;/code&gt; (height below the baseline), each of which can be negative to bring surrounding elements closer together.&lt;/p&gt;

&lt;p&gt;I found the element’s frame node and noticed this method:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;void nsMathMLmspaceFrame::Reflow(nsPresContext* aPresContext,
                                 ReflowOutput&amp;amp; aDesiredSize,
                                 const ReflowInput&amp;amp; aReflowInput,
                                 nsReflowStatus&amp;amp; aStatus) {
  // [...]

  mBoundingMetrics = nsBoundingMetrics();
  mBoundingMetrics.width = mWidth;
  mBoundingMetrics.ascent = mHeight;
  mBoundingMetrics.descent = mDepth;
  mBoundingMetrics.leftBearing = 0;
  mBoundingMetrics.rightBearing = mBoundingMetrics.width;

  aDesiredSize.SetBlockStartAscent(mHeight);
  aDesiredSize.Width() = std::max(0, mBoundingMetrics.width);
  aDesiredSize.Height() = aDesiredSize.BlockStartAscent() + mDepth;

  // [...]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Reflow is the process of traversing the layout tree and figuring out the positions and sizes of all of its nodes, and in Firefox that involves a depth-first tree of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nsIFrame::Reflow&lt;/code&gt; calls, starting from the &lt;strong&gt;initial containing block&lt;/strong&gt;.
An &amp;lt;mspace&amp;gt; frame never has children, so our reflow logic was more or less to take the three attributes, then return a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReflowOutput&lt;/code&gt; that tells the parent we need that much space.&lt;/p&gt;

&lt;p&gt;To handle padding and border, we add that to our desired size.
“Physical” here means the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nsMargin&lt;/code&gt; in terms of absolute directions like left and right, as opposed to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LogicalMargin&lt;/code&gt; in terms of &lt;strong&gt;flow-relative&lt;/strong&gt; directions, which are aware of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;direction&lt;/code&gt; (LTR + RTL) and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;writing-mode&lt;/code&gt; (horizontal + vertical + sideways).
We want to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LogicalMargin&lt;/code&gt; in most situations, but MathML Core is &lt;a href=&quot;https://mathml-refresh.github.io/mathml-core/#css-styling&quot;&gt;&lt;em&gt;currently&lt;/em&gt; strictly &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;horizontal-tb&lt;/code&gt;&lt;/a&gt; and sums of left and right are inherently &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;direction&lt;/code&gt;-safe, so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nsMargin&lt;/code&gt; was the way to go here.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;auto borderPadding = aReflowInput.ComputedPhysicalBorderPadding();
aDesiredSize.Width() = std::max(0, mBoundingMetrics.width) + borderPadding.LeftRight();
aDesiredSize.Height() = aDesiredSize.BlockStartAscent() + mDepth + borderPadding.TopBottom();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That was enough to pass the &amp;lt;mspace&amp;gt; cases in the Web Platform Tests, but &lt;a href=&quot;https://bucket.daz.cat/07d7eb508eaab690.html&quot;&gt;the test page&lt;/a&gt; I had put together to play around with my patch yielded both good news and bad news.
Let’s look at &lt;a href=&quot;https://bucket.daz.cat/21b093f316aa04d9.html&quot;&gt;the reference&lt;/a&gt;, which uses &amp;lt;div&amp;gt; elements and flexbox rather than MathML.&lt;/p&gt;

&lt;figure&gt;&lt;a href=&quot;/images/igalia-3.png&quot;&gt;&lt;img src=&quot;/images/igalia-3.png&quot; /&gt;&lt;/a&gt;&lt;/figure&gt;

&lt;p&gt;The good news was that Firefox already drew borders, or at least border colours, even though the layout of them was all wrong.&lt;/p&gt;

&lt;figure&gt;&lt;a href=&quot;/images/igalia-4.png&quot;&gt;&lt;img src=&quot;/images/igalia-4.png&quot; /&gt;&lt;/a&gt;&lt;/figure&gt;

&lt;p&gt;The bad news was that while my patch made each element look Bigger Than Before, the baselines were misaligned.
More importantly, the &amp;lt;mspace&amp;gt; elements and even the whole &amp;lt;math&amp;gt; elements still overlapped each other… almost as if… their parents were unaware of how much space they needed when positioning them!&lt;/p&gt;

&lt;figure&gt;&lt;a href=&quot;/images/igalia-5.png&quot;&gt;&lt;img src=&quot;/images/igalia-5.png&quot; /&gt;&lt;/a&gt;&lt;/figure&gt;

&lt;p&gt;I fixed the first two problems by adding the padding and border to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nsBoundingMetrics&lt;/code&gt; as well, because that controls the sizes and positions of MathML content.
That left the overlapping of the &amp;lt;math&amp;gt; elements, because while they &lt;em&gt;contain&lt;/em&gt; MathML content, they themselves are HTML content as far as their ancestors are concerned.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;auto borderPadding = aReflowInput.ComputedPhysicalBorderPadding();
mBoundingMetrics.width = mWidth + borderPadding.LeftRight();
mBoundingMetrics.ascent = mHeight + borderPadding.Side(eSideTop);
mBoundingMetrics.descent = mDepth + borderPadding.Side(eSideBottom);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;figure&gt;&lt;a href=&quot;/images/igalia-6.png&quot;&gt;&lt;img src=&quot;/images/igalia-6.png&quot; /&gt;&lt;/a&gt;&lt;/figure&gt;

&lt;p&gt;It turns out that in Firefox, MathML frames also need to report their width to their parent via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nsMathMLContainerFrame::MeasureForWidth&lt;/code&gt;.
With the &amp;lt;mspace&amp;gt; counterpart updated, plus the WPT &lt;strong&gt;expectations&lt;/strong&gt; files updated to mark the &amp;lt;mspace&amp;gt; test cases as passing, my patch was ready to land.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/* virtual */
nsresult nsMathMLmspaceFrame::MeasureForWidth(DrawTarget* aDrawTarget,
                                              ReflowOutput&amp;amp; aDesiredSize) {
  // [...]

  auto offsets = IntrinsicISizeOffsets();
  mBoundingMetrics.width = mWidth + offsets.padding + offsets.border;

  // [...]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;figure&gt;&lt;a href=&quot;/images/igalia-7.png&quot;&gt;&lt;img src=&quot;/images/igalia-7.png&quot; /&gt;&lt;/a&gt;&lt;/figure&gt;

&lt;p&gt;I also put together &lt;a href=&quot;https://bucket.daz.cat/d0c44db2dd05c7e5.html&quot;&gt;a test page&lt;/a&gt; (&lt;a href=&quot;https://bucket.daz.cat/31ec55671c10bddc.html&quot;&gt;reference&lt;/a&gt;) for the interaction between negative mspace@width and padding, which more or less rendered as expected, but it potentially revealed a bug in the layout of &amp;lt;math&amp;gt; elements that are flex items.
My guess is that flex items use a code path that clamps negative sizes to zero at some point, like we have to do in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReflowOutput&lt;/code&gt;, resulting in excess space for the item.&lt;/p&gt;

&lt;figure&gt;
    &lt;a href=&quot;/images/igalia-8.png&quot;&gt;&lt;img src=&quot;/images/igalia-8.png&quot; /&gt;&lt;/a&gt;
    &lt;figcaption&gt;Reftest for padding with negative mspace@width: reference page, without patch, with patch.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Margins were trickier to implement because, with Firefox and MathML content at least, the positions of elements are the parent’s responsibility to calculate.
I spent a &lt;em&gt;very&lt;/em&gt; long time reading &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nsMathMLContainerFrame&lt;/code&gt;, which is the base implementation for most MathML parents, and eventually figured out where and how to handle margins.
With a patch that updates &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RowChildFrameIterator&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Place&lt;/code&gt;, and &lt;a href=&quot;https://bucket.daz.cat/05499718d719a59b.html&quot;&gt;yet another test page&lt;/a&gt; (&lt;a href=&quot;https://bucket.daz.cat/31ec55671c10bddc.html&quot;&gt;reference&lt;/a&gt;) that passed with my patch, we were close to having a template for the remaining MathML elements!&lt;/p&gt;

&lt;figure&gt;
    &lt;a href=&quot;/images/igalia-9.png&quot;&gt;&lt;img src=&quot;/images/igalia-9.png&quot; /&gt;&lt;/a&gt;
    &lt;figcaption&gt;Reftest for margin: reference page, without patch, with patch.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;You can see my approach over at &lt;a href=&quot;https://phabricator.services.mozilla.com/D87594&quot;&gt;D87594&lt;/a&gt;, but the patch needed reworking and I ran out of time before I could land it.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/mathml-refresh/mathml/issues/14&quot;&gt;mathml-refresh/mathml#14: padding + border + margin&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1658135&quot;&gt;bug 1658135: &amp;lt;math&amp;gt; layout changes depending on presence of &amp;lt;mrow&amp;gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://phabricator.services.mozilla.com/D86471&quot;&gt;D86471: implement padding/border layout for &amp;lt;mspace&amp;gt;&lt;/a&gt; (&lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1658121&quot;&gt;bug 1658121&lt;/a&gt;)
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;https://github.com/web-platform-tests/wpt/pull/25505&quot;&gt;web-platform-tests/wpt#25505: workaround for bug 1658135 (automatic PR)&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://phabricator.services.mozilla.com/D87594&quot;&gt;D87594: implement margin for nsMathMLContainerFrame children&lt;/a&gt; (&lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1663867&quot;&gt;bug 1663867&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://bucket.daz.cat/07d7eb508eaab690.html&quot;&gt;reftest for padding + border on &amp;lt;mspace&amp;gt;&lt;/a&gt; (&lt;a href=&quot;https://bucket.daz.cat/21b093f316aa04d9.html&quot;&gt;reference page&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://bucket.daz.cat/d0c44db2dd05c7e5.html&quot;&gt;reftest for padding with negative mspace@width&lt;/a&gt; (&lt;a href=&quot;https://bucket.daz.cat/4e8f5c01f4642893.html&quot;&gt;reference page&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://bucket.daz.cat/05499718d719a59b.html&quot;&gt;reftest for margin on &amp;lt;mspace&amp;gt;&lt;/a&gt; (&lt;a href=&quot;https://bucket.daz.cat/31ec55671c10bddc.html&quot;&gt;reference page&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements&lt;/h2&gt;

&lt;p&gt;This internship was incredibly valuable.
While I was only able to finish the first trimester for mental health reasons, over the last nine months I’ve learned C++, learned how the web platform and browser engines work, gained ample experience reading specs, worked with countless people in the open-source community, and contributed to three major engines plus the Web Platform Tests.&lt;/p&gt;

&lt;p&gt;Were I able to continue, I would also look forward to (&lt;a href=&quot;https://github.com/whatwg/html/pull/3072&quot;&gt;more&lt;/a&gt;) experience contributing to specs, and probably helping Igalia with their &lt;a href=&quot;https://mathml.igalia.com&quot;&gt;MathML in Chromium&lt;/a&gt; project.
In any case, my time with the collective has only strengthened my desire to someday join full-time.&lt;/p&gt;

&lt;p&gt;Thanks to Caitlin for her advice and support, Eva and Javier and Pablo for getting me settled in so quickly, Manuel and Fred and Rob from the Web Platform team, and Yoav and Emilio for their help on the Chromium and Firefox parts of my work.&lt;/p&gt;

&lt;hr /&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Windows is the other major platform that does this. Check out &lt;em&gt;The Old New Thing&lt;/em&gt; by Raymond Chen to learn more. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Searchfox more or less &lt;a href=&quot;https://billmccloskey.wordpress.com/2016/06/07/searchfox/&quot;&gt;supersedes&lt;/a&gt; MXR and DXR. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Igalia has a Searchfox-based WebKit code browser, and I found it useful, but it’s not yet ready for public consumption. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:4&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;See also &lt;a href=&quot;https://wpt.fyi&quot;&gt;wpt.fyi&lt;/a&gt;, which tracks results of each test case across major browsers. &lt;a href=&quot;#fnref:4&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name></name></author><category term="home" /><category term="igalia" /><summary type="html">I was looking for a job late last year when I saw a tweet about a place called Igalia. The more I learned about them, the more interested I became, and before long I applied to join their Web Platform team. I didn’t have enough experience for a permanent position, but they did offer me a place in their Coding Experience program, which as far as I can tell is basically an internship, and I thoroughly enjoyed it. Here’s an overview of what I did and what I learned.</summary></entry><entry><title type="html">Farewell, car</title><link href="https://www.azabani.com/2020/08/15/farewell-car.html" rel="alternate" type="text/html" title="Farewell, car" /><published>2020-08-15T20:00:00+00:00</published><updated>2020-08-15T20:00:00+00:00</updated><id>https://www.azabani.com/2020/08/15/farewell-car</id><content type="html" xml:base="https://www.azabani.com/2020/08/15/farewell-car.html">&lt;p&gt;It’s been two years since I lost my car to a kangaroo.
My partner and I went on a road trip from Sydney to Canberra on a whim, but we didn’t reach our destination.
We were fine, but the kangaroo sadly wasn’t.
Let our story be a lesson to you: don’t drive out of town at night without a roo bar.&lt;/p&gt;

&lt;style&gt;
figure { text-align: center; }
&lt;/style&gt;

&lt;figure&gt;
&lt;a href=&quot;/images/IMG_2786.JPG&quot;&gt;&lt;img src=&quot;/images/IMG_2786.JPG&quot; width=&quot;50%&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;/images/IMG_3173.JPG&quot;&gt;&lt;img src=&quot;/images/IMG_3173.JPG&quot; width=&quot;28.125%&quot; /&gt;&lt;/a&gt;
&lt;figcaption&gt;Left: the night of the crash (includes GPS EXIF tags). Right: a few months later.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Remarkably the car still kinda worked after we had it towed, enough for it to limp around on some quiet streets near my house at the time, but it was far from roadworthy.
I kept it out of the rain at first, but I soon learned that I couldn’t really afford to fix all of the damage, barring some sort of miraculous wrecker dive (for which I had no way to get to, no way to carry the spoils home, and no tools to install).
We moved to Strathfield, but left the car parked on the same street in Arncliffe.
The next wet season came around, but this time I neglected the matter, sealing its fate.&lt;/p&gt;

&lt;p&gt;This car wasn’t my first, but it might as well have been, because I had my first for &lt;a href=&quot;https://twitter.com/dazabani/status/635052216882098177&quot;&gt;less than eight weeks&lt;/a&gt;.
I guess I’ve got a knack for owning and wrecking cars based on the &lt;a href=&quot;https://en.wikipedia.org/wiki/Ford_C1_platform&quot;&gt;Ford C1&lt;/a&gt;.
The BK family has a special place in my heart, on multiple levels.
At its peak, three of my friends and my best friend’s brother owned one too.&lt;/p&gt;

&lt;p&gt;This vessel carried me through my P plates, as far as Perth, Adelaide, Sydney, Canberra, Melbourne, and the Gold Coast.
It took me and both of the partners I’ve had on road trips far and wide, to countless &lt;a href=&quot;https://www.comssa.org.au&quot;&gt;ComSSA&lt;/a&gt; LAN and &lt;a href=&quot;https://www.rflan.org&quot;&gt;RFLAN&lt;/a&gt; and &lt;a href=&quot;https://avalanchegaming.org&quot;&gt;avaLANche&lt;/a&gt; parties, and it hauled me and all of my stuff from Perth to Sydney when I graduated.&lt;/p&gt;

&lt;figure&gt;
&lt;a href=&quot;/images/IMG_1043.JPG&quot;&gt;&lt;img src=&quot;/images/IMG_1043.JPG&quot; width=&quot;28.125%&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;/images/IMG_1879.JPG&quot;&gt;&lt;img src=&quot;/images/IMG_1879.JPG&quot; width=&quot;50%&quot; /&gt;&lt;/a&gt;
&lt;figcaption&gt;Left: SA/Vic border on National Highway A8. Right: eastern end of the Nullarbor plain.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I poured a lot of love into this car, including custom plates, &lt;a href=&quot;https://twitter.com/dazabani/status/814004872399814656&quot;&gt;good tyres&lt;/a&gt;, and &lt;a href=&quot;https://twitter.com/dazabani/status/858521417985310720&quot;&gt;18″ RX-8 rims&lt;/a&gt; from someone in Canberra.
I really liked the stock aesthetic, and in general I think aftermarket rims are tacky.&lt;/p&gt;

&lt;p&gt;My friend Nathaniel took me wrecker diving for a centre console from a &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Mazda3&amp;amp;oldid=966457055#2007&quot;&gt;facelift&lt;/a&gt; and installed it for me, giving me an aux jack.
The best part was that my stock &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Mazda3&amp;amp;oldid=966457055#2006&quot;&gt;pre-facelift&lt;/a&gt; head unit — fancy internal 6-stack CD player that I never used and all — supported it natively!
You see, the tape deck, in models that had one, was connected to the head unit with a glorified aux cord.
We plugged it in, held down two buttons to reconfigure the thing (if I remember correctly), and it worked like a tape deck.&lt;/p&gt;

&lt;p&gt;I forgive you for &lt;a href=&quot;https://twitter.com/dazabani/status/814649305218723840&quot;&gt;losing air conditioning&lt;/a&gt; just two nights before I crossed the Nullarbor, for the very first time, in the middle of summer.
I forgive you for your flaky dealer-fitted cruise control (drive-by-wire was introduced in the facelift) that barely worked the first time, dying completely before my round trip back home the next year.&lt;/p&gt;

&lt;p&gt;But most of all, I’ll miss you.&lt;/p&gt;

&lt;figure&gt;
&lt;a href=&quot;/images/CURAUGBUcAA2k6Y.jpg&quot;&gt;&lt;img src=&quot;/images/CURAUGBUcAA2k6Y.jpg&quot; width=&quot;50%&quot; /&gt;&lt;/a&gt;
&lt;figcaption&gt;November 2015 to July 2018.&lt;/figcaption&gt;
&lt;/figure&gt;</content><author><name></name></author><category term="home" /><category term="self" /><summary type="html">It’s been two years since I lost my car to a kangaroo. My partner and I went on a road trip from Sydney to Canberra on a whim, but we didn’t reach our destination. We were fine, but the kangaroo sadly wasn’t. Let our story be a lesson to you: don’t drive out of town at night without a roo bar.</summary></entry><entry><title type="html">Bad Apple!! for taskmgr</title><link href="https://www.azabani.com/2020/06/29/bad-apple-for-taskmgr.html" rel="alternate" type="text/html" title="Bad Apple!! for taskmgr" /><published>2020-06-29T19:00:00+00:00</published><updated>2020-06-29T19:00:00+00:00</updated><id>https://www.azabani.com/2020/06/29/bad-apple-for-taskmgr</id><content type="html" xml:base="https://www.azabani.com/2020/06/29/bad-apple-for-taskmgr.html">&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Bad_Apple!!&quot;&gt;Bad Apple!!&lt;/a&gt; is a Touhou song whose &lt;a href=&quot;https://www.youtube.com/watch?v=FtutLA63Cp8&quot;&gt;music video&lt;/a&gt; has been described as &lt;a href=&quot;https://twitter.com/Clipsey5/status/1276287929447579648&quot;&gt;“the doom of music videos”&lt;/a&gt;, because it’s been played on &lt;a href=&quot;https://twitter.com/marcan42/status/1273964930618646528&quot;&gt;everything&lt;/a&gt; from &lt;a href=&quot;https://twitter.com/Reif_FHI/status/1069150346289659904&quot;&gt;string lights&lt;/a&gt; to &lt;a href=&quot;https://twitter.com/marcan42/status/1042376140512538625&quot;&gt;oscilloscopes&lt;/a&gt;.
This post is about that video, writing an improved “video player” for Task Manager’s CPU graphs, and using virtual machines to push my feeble hardware to the limit.&lt;/p&gt;

&lt;style&gt;
/* :root { font-size: 20px; } */
article { hyphens: auto; }
.local-video { max-width: 100%; }
.local-commit-container { margin-right: -1.6em; padding-right: 1.4em; border-right: 0.2em solid rgba(102,51,153,0.5); }
* + .local-commit, .local-commit * + * { margin-top: 0; }
.local-commit { line-height: 2; margin-right: -2.5em; text-align: right; }
.local-commit &gt; img { width: 2em; vertical-align: middle; }
.local-commit &gt; a { padding-right: 0.5em; text-decoration: none; color: rebeccapurple; }
.local-commit &gt; a &gt; code { font-size: 1em; }
.local-commit-none &gt; a { color: rgba(102,51,153,0.5); }
&lt;/style&gt;

&lt;figure&gt;
    &lt;p&gt;&lt;iframe class=&quot;local-video&quot; width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube-nocookie.com/embed/hMGM6s1Qw_Q&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;
    &lt;figcaption&gt;I definitely cherry-picked that thumbnail, not gonna lie.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;prior-art&quot;&gt;Prior art&lt;/h2&gt;

&lt;p&gt;The idea started in March, with a &lt;a href=&quot;https://twitter.com/marcan42/status/1273957984243027968&quot;&gt;video&lt;/a&gt; that appeared to play it on an AMD 3990X, but that one &lt;a href=&quot;https://twitter.com/marcan42/status/1273957984243027968&quot;&gt;turned out to be “fake”&lt;/a&gt;, at least in the sense that it relied heavily on &lt;a href=&quot;https://en.wikipedia.org/wiki/Adobe_After_Effects&quot;&gt;video editing&lt;/a&gt;.
More recently, &lt;a href=&quot;https://twitter.com/kbeckmann/status/1275835614529806348&quot;&gt;@kbeckmann&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/winocm/status/1276037359503466497&quot;&gt;@winocm&lt;/a&gt; played it over 64 and 128 processors, which at first inspired me to replicate what they did, and pretty soon I wanted to bring something new to the table.&lt;/p&gt;

&lt;p&gt;The biggest obstacle for me was hardware.
The “shaded tiles” style of CPU graphs only kick in when you have 64 or more processors, but my main machine &lt;a href=&quot;https://ark.intel.com/content/www/us/en/ark/products/80915/intel-xeon-processor-e3-1276-v3-8m-cache-3-60-ghz.html&quot;&gt;only has eightish&lt;/a&gt;, and my laptop &lt;a href=&quot;https://ark.intel.com/content/www/us/en/ark/products/191045/intel-core-i7-9750h-processor-12m-cache-up-to-4-50-ghz.html&quot;&gt;only has twelvish&lt;/a&gt;.
There’s a whole spectrum of approaches I could choose from:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Using video editing to render an imitation (already done)&lt;/li&gt;
  &lt;li&gt;Modifying taskmgr’s memory to feed it fake activity numbers&lt;/li&gt;
  &lt;li&gt;Using a virtual machine to Download More Cores&lt;/li&gt;
  &lt;li&gt;Just recording it on real hardware (already done)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’m really into virtualisation, so I decided to solve this with libvirt and KVM.
While I could probably ask &lt;a href=&quot;https://www.igalia.com&quot;&gt;Igalia&lt;/a&gt; to let me run a guest on one of their 128-thread build boxes, I was interested in a challenge, driven by the same urge as when I &lt;a href=&quot;/2015/08/06/modern-openbsd-home-router.html&quot;&gt;built a home router&lt;/a&gt; out of an old Pentium III&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.
That turned out to be challenging and interesting, and I’m surprised I was able to mitigate enough of the problems that the result was worth sharing.&lt;/p&gt;

&lt;h2 id=&quot;getting-started&quot;&gt;Getting started&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://bucket.daz.cat/e768982969270172.xml&quot;&gt;My main Windows install&lt;/a&gt; is a libvirt guest with GPU passthrough, so at first I tried the path of least resistance.
I cranked up the CPU topology, rebooted, and… it struggled, taking several minutes to reach the login screen.&lt;/p&gt;

&lt;p&gt;I figured that such an extreme overcommitment might be fine &lt;em&gt;after&lt;/em&gt; boot, and the only problem was booting that way, so I tried CPU hotplug, but nothing happened.
While Windows Server apparently got an update that fixed hotplug, and Windows 10 also received that update, that didn’t mean that Windows 10 supports hotplug.&lt;/p&gt;

&lt;figure&gt;
    &lt;a href=&quot;/images/badapple-1.png&quot;&gt;&lt;img src=&quot;/images/badapple-1.png&quot; width=&quot;100%&quot; /&gt;&lt;/a&gt;
    &lt;figcaption&gt;What I would have seen if there was any justice in this world.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I put together a Windows Server 2019 guest, and at last I could use CPU hotplug!
But the guest also booted fine with the overcommitment in place (or at least it does now, if I’ve got my timeline wrong).
Perhaps the sheer lack of crap it had to load, by virtue of being a fresh install &lt;em&gt;and&lt;/em&gt; Windows Server, did the trick.&lt;/p&gt;

&lt;p&gt;I turned off the malware scanner for good measure.&lt;/p&gt;

&lt;h2 id=&quot;the-black-screen-phase&quot;&gt;The black screen phase&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://bitbucket.org/delan/badapple.rs&quot;&gt;My video player&lt;/a&gt; has its roots in @kbeckmann’s &lt;a href=&quot;https://gist.github.com/kbeckmann/41254cc559ee4917913e522cc529a4e5&quot;&gt;bad_cpu.py&lt;/a&gt;, so let’s start there.
The latter spawns a process for each “pixel” of the CPU graph, each of which sets its &lt;a href=&quot;https://en.wikipedia.org/wiki/Processor_affinity&quot;&gt;affinity&lt;/a&gt; to one CPU, then spins and sleeps as a kind of &lt;a href=&quot;https://en.wikipedia.org/wiki/Pulse-width_modulation&quot;&gt;pulse-width modulation&lt;/a&gt; over CPU activity, just like a microwave or (some types of) dimmer switch.&lt;/p&gt;

&lt;p&gt;Sadly bad_cpu.py doesn’t scale beyond 64 processors without modifications, because of how the Windows API communicates affinity.
Since the dawn of time, affinity has been represented as a &lt;a href=&quot;https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/interrupt-affinity-and-priority#about-kaffinity&quot;&gt;register-width bit mask&lt;/a&gt;.
Windows is big on backwards compatibility, so when support for more than 64 (or 32) processors was introduced, the developers introduced the concept of &lt;a href=&quot;https://docs.microsoft.com/en-us/windows/win32/procthread/processor-groups&quot;&gt;processor groups&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://pypi.org/project/psutil/&quot;&gt;The library we were using&lt;/a&gt; didn’t provide a way to change processor group affinity, &lt;a href=&quot;https://pypi.org/project/pywin32/&quot;&gt;Python’s bindings for the Windows API&lt;/a&gt; didn’t include &lt;a href=&quot;https://docs.microsoft.com/en-us/windows/win32/api/processtopologyapi/nf-processtopologyapi-setthreadgroupaffinity&quot;&gt;SetThreadGroupAffinity&lt;/a&gt;, and even if it did, what if the interpreter creates other threads than the one GetCurrentThread would give us a handle for?&lt;/p&gt;

&lt;div class=&quot;local-commit-container&quot;&gt;

  &lt;!-- git log --reverse --abbrev=13 --pretty=tformat:&apos;&lt;div class=&quot;local-commit&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/badapple.rs/commits/%H&quot;&gt;&lt;code&gt;%h&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot;&gt;&lt;/div&gt;%n%ad    %s%n&apos; --&gt;

  &lt;p&gt;That’s when I decided to Rewrite It In Rust. As far as Windows was concerned, this initially involved three things:&lt;/p&gt;

  &lt;ol&gt;
    &lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getlogicalprocessorinformationex&quot;&gt;GetLogicalProcessorInformationEx&lt;/a&gt; to find out what the processor groups are&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-initializeprocthreadattributelist&quot;&gt;InitializeProcThreadAttributeList&lt;/a&gt; and &lt;a href=&quot;https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute&quot;&gt;UpdateProcThreadAttribute&lt;/a&gt; to wrap each processor group affinity value in some fancy paper and a bow&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createremotethreadex&quot;&gt;CreateRemoteThreadEx&lt;/a&gt; to create each pixel thread with its affinity value&lt;/li&gt;
  &lt;/ol&gt;

  &lt;p&gt;I struggled with steps one and two for most of Friday.&lt;/p&gt;

  &lt;p&gt;GetLogicalProcessorInformationEx is named after &lt;a href=&quot;https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getlogicalprocessorinformation&quot;&gt;GetLogicalProcessorInformation&lt;/a&gt;, a similar function that doesn’t support processor groups.
The latter had an example, from which I learned that it writes an array of fixed-size structures, so I assumed that this was also true for the former, but when I tried calling the function, I got nonsense.
It wasn’t until I found &lt;a href=&quot;https://github.com/GPUOpen-LibrariesAndSDKs/cpu-core-counts/blob/7c2329aa7109c4d26f83d44f9a422524a63dac82/windows/ThreadCount-Win7.cpp&quot;&gt;this usage in the wild&lt;/a&gt; that I learned the newer function writes an array of &lt;em&gt;variable-size&lt;/em&gt; structures!&lt;/p&gt;

  &lt;p&gt;While it was clear that our affinity values had to outlive the attribute lists that wrapped them, it was unclear whether the attribute lists had to outlive the threads they were used to create.
I eventually &lt;a href=&quot;https://bitbucket.org/delan/badapple.rs/src/e18f7dbf69c07fe153f5536847bf69a9e03d5777/src/windows.rs?at=trunk#lines-53:59&quot;&gt;tried to do both&lt;/a&gt;, but I don’t think my approach guaranteed that they wouldn’t move.
I couldn’t figure out how to &lt;a href=&quot;https://doc.rust-lang.org/std/pin/struct.Pin.html&quot;&gt;Pin&lt;/a&gt; them, nor was I even sure whether that was the appropriate tool.&lt;/p&gt;

  &lt;div class=&quot;local-commit&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/badapple.rs/commits/c8cba094f63d3e3746de271bb787ae1fedcad1ac&quot;&gt;&lt;code&gt;c8cba094f63d3&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

  &lt;div class=&quot;local-commit local-commit-none&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/badapple.rs/commits/7d01d8501f5fbefb0e7992acc86b698dd43d98cd&quot;&gt;&lt;code&gt;7d01d8501f5fb&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-none.svg&quot; /&gt;&lt;/div&gt;

  &lt;p&gt;By the end of Friday, I got some test patterns going, but I was disappointed to see that the percentages were distorted downwards, making pixels too light, when too many pixels needed to be dark.
Migrating the virtual machine to my laptop helped, but not enough.&lt;/p&gt;

  &lt;figure&gt;
    &lt;a href=&quot;/images/badapple-2.png&quot;&gt;&lt;img src=&quot;/images/badapple-2.png&quot; width=&quot;50%&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;/images/badapple-3.png&quot;&gt;&lt;img src=&quot;/images/badapple-3.png&quot; width=&quot;50%&quot; /&gt;&lt;/a&gt;
    &lt;figcaption&gt;12/240 versus 120/240 pixels at 50%.&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;div class=&quot;local-commit&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/badapple.rs/commits/bd5e068fbba66093f232ff8f374c9df574c6caf8&quot;&gt;&lt;code&gt;bd5e068fbba66&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

  &lt;div class=&quot;local-commit local-commit-none&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/badapple.rs/commits/74dbca8ca6ae387c3f467ebcda2302de546f2930&quot;&gt;&lt;code&gt;74dbca8ca6ae3&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-none.svg&quot; /&gt;&lt;/div&gt;

  &lt;div class=&quot;local-commit local-commit-none&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/badapple.rs/commits/78ef5a58cf994fe69228de49b038fbb9daa637bb&quot;&gt;&lt;code&gt;78ef5a58cf994&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-none.svg&quot; /&gt;&lt;/div&gt;

  &lt;div class=&quot;local-commit&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/badapple.rs/commits/1a837711be0d46188bb8cccd2faf3240bde14552&quot;&gt;&lt;code&gt;1a837711be0d4&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

  &lt;div class=&quot;local-commit local-commit-none&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/badapple.rs/commits/32cddabdc67712dfb1cda0f3b87d2d0e2670312c&quot;&gt;&lt;code&gt;32cddabdc6771&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-none.svg&quot; /&gt;&lt;/div&gt;

  &lt;p&gt;From there, implementing external video input was easy, probably the easiest part of the project.
Slurp up the file, throw in a couple of loops with Rust’s wonderful &lt;a href=&quot;https://doc.rust-lang.org/std/primitive.slice.html#method.chunks_exact&quot;&gt;ChunksExact&lt;/a&gt;, and dole out each pixel to the appropriate vector.&lt;/p&gt;

  &lt;div class=&quot;local-commit&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/badapple.rs/commits/55dade1bcfcac5752ff669f14db45e7afe8bc789&quot;&gt;&lt;code&gt;55dade1bcfcac&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

  &lt;h2 id=&quot;performance-tuning&quot;&gt;Performance tuning&lt;/h2&gt;

  &lt;p&gt;Having seen disappointing results from both bad_cpu.py and my new player, at first I looked beyond the code for solutions.
The ideas I came up with fell into three buckets:&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;Messing around with schedulers&lt;/strong&gt;, be it on the guest (&lt;a href=&quot;https://www.microsoftpressstore.com/articles/article.aspx?p=2233328&amp;amp;seqNum=7&quot;&gt;α&lt;/a&gt; &lt;a href=&quot;http://recoverymonkey.org/2007/08/17/processor-scheduling-and-quanta-in-windows-and-a-bit-about-unixlinux/&quot;&gt;β&lt;/a&gt; &lt;a href=&quot;https://docs.microsoft.com/en-us/windows/win32/procthread/multimedia-class-scheduler-service&quot;&gt;γ&lt;/a&gt; &lt;a href=&quot;https://docs.microsoft.com/en-us/windows/win32/procthread/platform-work-queue-api&quot;&gt;δ&lt;/a&gt;) or the host (&lt;a href=&quot;https://www.kernel.org/doc/Documentation/scheduler/sched-rt-group.txt&quot;&gt;ε&lt;/a&gt;). I didn’t know enough to come up with &lt;em&gt;informed&lt;/em&gt; ideas, so I didn’t get very far.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Optimising the virtual machine&lt;/strong&gt;, based on the same advice that I heed from &lt;a href=&quot;https://discord.gg/f63cXwH&quot;&gt;the VFIO community&lt;/a&gt; and &lt;a href=&quot;https://wiki.archlinux.org/index.php/PCI_passthrough_via_OVMF&quot;&gt;their resources&lt;/a&gt;. libvirt’s defaults are generally top notch, but it still leaves room for tweaks like switching to 1 GiB static huge pages and removing the memory balloon. That said, nothing had an effect to write home about.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Pinning the guest threads&lt;/strong&gt;, which I’ve pulled into its own dot point because it &lt;em&gt;did&lt;/em&gt; have a notable effect. &lt;em&gt;Massive&lt;/em&gt; and &lt;em&gt;negative&lt;/em&gt;. This is a common tweak for normal guests that aren’t oversubscribed, but here? The guest performance tanked like it did when I tried to overcommit my main install, and CPU hotplug just delayed the tanking. Even without nohz_full (which I later learned &lt;a href=&quot;https://www.kernel.org/doc/Documentation/timers/NO_HZ.txt&quot;&gt;was a silly idea&lt;/a&gt; when overcommitting pins). Even without rcu_nocbs. Even without isolcpus or &lt;a href=&quot;https://www.codeblueprint.co.uk/2019/10/08/isolcpus-is-deprecated-kinda.html&quot;&gt;cset shield&lt;/a&gt;.&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;I also ran into a bunch of limits while figuring out how far I could push my hardware.&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;32 (&lt;a href=&quot;https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/interrupt-affinity-and-priority#about-kaffinity&quot;&gt;KAFFINITY&lt;/a&gt; on i686-pc-windows-gnu, &lt;a href=&quot;https://rustup.rs&quot;&gt;rustup&lt;/a&gt;’s reasonable default)&lt;/li&gt;
    &lt;li&gt;64 (&lt;a href=&quot;https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/interrupt-affinity-and-priority#about-kaffinity&quot;&gt;KAFFINITY&lt;/a&gt; on x86_64-pc-windows-msvc, which I switched to)&lt;/li&gt;
    &lt;li&gt;255 (pc-q35-4.2 without iommu@eim)&lt;/li&gt;
    &lt;li&gt;288 (pc-q35-4.2 with iommu@eim)&lt;/li&gt;
    &lt;li&gt;64 sockets (Windows limitation?)&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;To go beyond 255 logical processors and reach the 288 limit, we needed iommu@eim, which also needed iommu@intremap and the qemu ioapic.&lt;/p&gt;

  &lt;figure&gt;&lt;div class=&quot;scroll&quot;&gt;
      &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;domain&amp;gt;
  &amp;lt;features&amp;gt;
    &amp;lt;ioapic driver=&quot;qemu&quot;/&amp;gt;
  &amp;lt;/features&amp;gt;
  &amp;lt;devices&amp;gt;
    &amp;lt;iommu model=&quot;intel&quot;&amp;gt;
      &amp;lt;driver intremap=&quot;on&quot; eim=&quot;on&quot;/&amp;gt;
    &amp;lt;/iommu&amp;gt;
  &amp;lt;/devices&amp;gt;
&amp;lt;/domain&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;      &lt;/div&gt;
    &lt;/div&gt;&lt;/figure&gt;

  &lt;p&gt;For what it’s worth, by the time I recorded the video, I ended up with &lt;a href=&quot;/static/badapple.xml&quot;&gt;this configuration&lt;/a&gt;.&lt;/p&gt;

  &lt;h2 id=&quot;high-frame-rate&quot;&gt;High Frame Rate™&lt;/h2&gt;

  &lt;p&gt;Task Manager has a bunch of update speeds, but the fastest is only twice a second.
The good news is that we can also press F5 to update as often as we want.
Once I added a thread that calls SendInput after every frame, we could play the video at 4 fps.&lt;/p&gt;

  &lt;div class=&quot;local-commit&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/badapple.rs/commits/20e17d39c0c98469a68cb3f6fd8d9bd10254c686&quot;&gt;&lt;code&gt;20e17d39c0c98&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

  &lt;p&gt;Well… kind of.
&lt;a href=&quot;https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendinput&quot;&gt;SendInput&lt;/a&gt; can only press F5 in the foreground window, so I had to Alt+Tab every time I ran the program.
&lt;a href=&quot;https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendmessage&quot;&gt;SendMessage&lt;/a&gt; would &lt;a href=&quot;https://stackoverflow.com/questions/1220820&quot;&gt;probably be better&lt;/a&gt;, but I later &lt;a href=&quot;https://bitbucket.org/delan/badapple.rs/commits/096347fca5582ac28ce502f8bf8d51728ce7fdad&quot;&gt;worked around that&lt;/a&gt; with &lt;a href=&quot;https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-findwindoww&quot;&gt;FindWindowW&lt;/a&gt; and &lt;a href=&quot;https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setforegroundwindow&quot;&gt;SetForegroundWindow&lt;/a&gt;.&lt;/p&gt;

  &lt;div class=&quot;local-commit&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/badapple.rs/commits/096347fca5582ac28ce502f8bf8d51728ce7fdad&quot;&gt;&lt;code&gt;096347fca5582&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-up.svg&quot; /&gt;&lt;/div&gt;

  &lt;div class=&quot;local-commit local-commit-none&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/badapple.rs/commits/7c6e17d450517d02a29603235c54ea2a45958ad4&quot;&gt;&lt;code&gt;7c6e17d450517&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-none.svg&quot; /&gt;&lt;/div&gt;

  &lt;p&gt;From here on, I turn off automatic updates, because they split our carefully timed refresh cycles into two corrupted updates.&lt;/p&gt;

  &lt;p&gt;Making the player press F5 between frames also ensures that Task Manager’s cycles are &lt;em&gt;in phase&lt;/em&gt; with the frames (albeit with a small error between F5 and refresh).
This might not seem like a problem at first, much like the number of days from X January to X February is always the number of days in January, but when we’re too far out of phase, the dark pixel in a dark-to-light transition won’t be dark enough (and vice versa).&lt;/p&gt;

  &lt;figure&gt;
    &lt;a href=&quot;/images/badapple-4.png&quot;&gt;&lt;img src=&quot;/images/badapple-4.png&quot; width=&quot;100%&quot; /&gt;&lt;/a&gt;
    &lt;figcaption&gt;When the refresh is 20% late, the 80% pixel becomes 70%.&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;p&gt;There are limits to this technique.
The higher the frame rate, the more of our CPU time gets consumed by Task Manager, &lt;a href=&quot;https://twitter.com/winocm/status/1276314798758588418&quot;&gt;until the tiles become wildly inaccurate&lt;/a&gt;.&lt;/p&gt;

  &lt;h2 id=&quot;timing-is-everything&quot;&gt;Timing is everything&lt;/h2&gt;

  &lt;p&gt;By this point, I was noticing more and more severe timing problems.
The simple approach taken by bad_cpu.py was perfect for real hardware, but on an overcommitted virtual machine, that didn’t cut it.
There was excessive visual noise, and when I started playing the original video side-by-side on another machine, I noticed that our playback finished around 30 seconds late.&lt;/p&gt;

  &lt;p&gt;My first thought was that &lt;strong&gt;&lt;a href=&quot;https://doc.rust-lang.org/std/time/struct.Instant.html&quot;&gt;Instant&lt;/a&gt;&lt;/strong&gt;, despite being &lt;a href=&quot;https://en.wikipedia.org/wiki/Monotonic_function&quot;&gt;monotonic&lt;/a&gt;, is “not guaranteed to be steady”, such that “some seconds may be longer than others”.
But on Windows, it’s based on &lt;a href=&quot;https://docs.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancecounter&quot;&gt;QueryPerformanceCounter&lt;/a&gt;, which provides &lt;a href=&quot;https://docs.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps&quot;&gt;much stricter guarantees&lt;/a&gt;.
QPC uses the best available counter on a given machine (e.g. RDTSC, RDTSCP, HPET, ACPI PM timer) plus &lt;a href=&quot;/images/badapple-magic.jpg&quot;&gt;its own magic&lt;/a&gt;&lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; to create timestamps unaffected by system time, &lt;a href=&quot;https://en.wikipedia.org/wiki/Dynamic_frequency_scaling&quot;&gt;dynamic frequency scaling&lt;/a&gt;, multiple processors, and hypervisor conditions (assuming a reasonable hypervisor).
These timestamps are even meaningful across threads (± 1 tick)!&lt;/p&gt;

  &lt;p&gt;I then worried that our F5 thread was getting delayed by all of the busier pixel threads.
I didn’t measure this one, and I doubt it was a huge problem unless we’re on a frame that’s almost completely filled, but I used &lt;strong&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadpriority&quot;&gt;SetThreadPriority&lt;/a&gt;&lt;/strong&gt; to keep the pixel threads out of the way.
I would have preferred to set the threads’ priorities at creation time with something like a &lt;a href=&quot;https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute&quot;&gt;ProcThreadAttributeList&lt;/a&gt;, but that didn’t seem to be possible.
THREAD_MODE_BACKGROUND_BEGIN looked attractive, but upon closer inspection, that’s an orthogonal setting for “resource” (I/O) scheduling, which we never do during playback, so I chose THREAD_PRIORITY_IDLE.&lt;/p&gt;

  &lt;div class=&quot;local-commit&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/badapple.rs/commits/9883355e6e8643a34a713df33d01e3a5efc995cd&quot;&gt;&lt;code&gt;9883355e6e864&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

  &lt;hr /&gt;

  &lt;p&gt;One of the biggest problems turned out to be &lt;strong&gt;keeping the pixel threads in sync&lt;/strong&gt;.
Each pixel thread started its playback as soon as it could execute.
I used an &lt;a href=&quot;https://doc.rust-lang.org/std/sync/atomic/struct.AtomicUsize.html&quot;&gt;AtomicUsize&lt;/a&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch_add(1, Relaxed)&lt;/code&gt; to measure the time from right before the first CreateRemoteThreadEx to the start of the last pixel thread to gain execution, and it was a massive 150–250 milliseconds!
I struggled to imagine which of Windows’ &lt;a href=&quot;https://docs.microsoft.com/en-us/windows/win32/sync/synchronization-objects&quot;&gt;synchronisation primitives&lt;/a&gt; would be an efficient solution here, but Rust came to the rescue with &lt;a href=&quot;https://doc.rust-lang.org/std/sync/struct.Barrier.html&quot;&gt;Barrier&lt;/a&gt;, which blocks waiters until a given number of waiters are waiting.
That number is one for every pixel thread plus one for the F5 thread, and I added waits to the start of both functions’ main loops.&lt;/p&gt;

  &lt;p&gt;With all of the threads in sync, I eliminated the sleeps from the pixel threads, elevating the importance of the F5 thread to what I’ve since called the “clock” thread.
The pixel threads’ main loop was wait → spin → sleep, but the clock thread was wait → sleep → F5, so skipping directly to the wait was effectively a sleep.&lt;/p&gt;

  &lt;div class=&quot;local-commit&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/badapple.rs/commits/9992c03d6068fb69f7f9af811339d966d4f37067&quot;&gt;&lt;code&gt;9992c03d6068f&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

  &lt;p&gt;This improved the picture quality, but playback still consistently finished too late.
Given the simple algorithm “spin for darkness / fps, sleep for (1 − darkness) / fps”, this kind of problem is always present (if negligible) even on real hardware.
Calculating how long to sleep for is very fast but not instant, and sleeping for some amount of time doesn’t guarantee that the scheduler will return us to execution in that time.&lt;/p&gt;

  &lt;p&gt;The first thing I tried was to adjust the sleep time to catch up with (or if necessary, wait for) the &lt;strong&gt;nominal&lt;/strong&gt; time we should start the next frame, based on when playback started.
I called the difference between what a naïve sleep would give us and the nominal time &lt;strong&gt;lateness&lt;/strong&gt;.
This helped at very low resolutions where I disabled most of the pixels on the canvas, but what I otherwise found was that our adjusted sleep was always zero.&lt;/p&gt;

  &lt;div class=&quot;local-commit&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/badapple.rs/commits/7f5562e540a681e91fad96d64711b0d4b1cdadd7&quot;&gt;&lt;code&gt;7f5562e540a68&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

  &lt;div class=&quot;local-commit local-commit-none&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/badapple.rs/commits/e18f7dbf69c07fe153f5536847bf69a9e03d5777&quot;&gt;&lt;code&gt;e18f7dbf69c07&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-none.svg&quot; /&gt;&lt;/div&gt;

  &lt;div class=&quot;local-commit local-commit-none&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/badapple.rs/commits/6e3d0ddabd45ddf803bee482f128fe3a9e4811a4&quot;&gt;&lt;code&gt;6e3d0ddabd45d&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-none.svg&quot; /&gt;&lt;/div&gt;

  &lt;div class=&quot;local-commit local-commit-none&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/badapple.rs/commits/30161548913f7d44dd5a019642ea25331da5b022&quot;&gt;&lt;code&gt;30161548913f7&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-none.svg&quot; /&gt;&lt;/div&gt;

  &lt;figure&gt;
    &lt;a href=&quot;/images/badapple-5.png&quot;&gt;&lt;img src=&quot;/images/badapple-5.png&quot; width=&quot;100%&quot; /&gt;&lt;/a&gt;
    &lt;figcaption&gt;There was so much lateness that we couldn’t keep up.&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;p&gt;This was actually bad news and good news at the same time.
The bad news was that we would need to do more to fix our timing problems, but the good news?
&lt;strong&gt;Even when we effectively removed sleeps, the picture was still recognisable,&lt;/strong&gt; and if anything, the change helped us with our “washed out” distortion.
I was worried that this might not be the case, because if you look at each pixel thread in isolation and ignore the Barrier, wouldn’t all spin and no sleep yield 100% activity?&lt;/p&gt;

  &lt;p&gt;I now believe — but correct me if I’m wrong — the percentages are based on how much of each wall second (or other period) a given processor is busy for, and when a guest processor is preempted against its will, that time doesn’t count, like how you don’t experience the time you’re asleep&lt;sup id=&quot;fnref:3&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;.
This would explain the distortion that occurs when a guest processor &lt;em&gt;can’t&lt;/em&gt; run because the host processors are contended!&lt;/p&gt;

  &lt;hr /&gt;

  &lt;p&gt;One thing that I was surprised to see made &lt;em&gt;zero&lt;/em&gt; difference was switching from a debug build to a release build.
It ended up being so irrelevant to this workload that while I put &lt;code&gt;--release&lt;/code&gt; in the README, I forgot to use it when recording the video!&lt;/p&gt;

  &lt;div class=&quot;local-commit&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/badapple.rs/commits/7a9150a4e5e9c5b8aedefebb9d93207bf4ae63be&quot;&gt;&lt;code&gt;7a9150a4e5e9c&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

  &lt;p&gt;But the technique that made the biggest difference to our timing problems was &lt;strong&gt;dropping frames&lt;/strong&gt;.
When we’re running more than 1 / fps late, not even a 100% empty frame gives us enough room for sleep adjustment that we’ll catch up completely by the end of the frame.
Figuring out how the clock thread could safely communicate this to the pixel threads was difficult, but I decided to publish the next frame index with a &lt;a href=&quot;https://doc.rust-lang.org/std/sync/struct.RwLock.html&quot;&gt;RwLock&lt;/a&gt;, which I would describe as Rust’s borrowing rule plus the ability to wait.&lt;/p&gt;

  &lt;p&gt;Strictly speaking that wasn’t enough, because while the Barrier syncs up all of the threads at the start of each frame, we have no way of knowing when the write acquisition (clock thread) happens relative to the read acquisitions (pixel threads).
Did it happen before all of them, after all of them, or somewhere in between?
Either of the first two would probably be fine if only we could make it &lt;em&gt;consistent&lt;/em&gt;, but somewhere in between is very bad.
If that happens, some pixels will draw their part of the &lt;em&gt;old&lt;/em&gt; frame and others the &lt;em&gt;new&lt;/em&gt; frame, which sounds like the relatively minor problem known as horizontal tearing… until you realise that the read acquisitions don’t happen in scanline order.&lt;/p&gt;

  &lt;p&gt;To prevent that, I added a second wait on the Barrier to both functions’ main loops, dividing each frame into two phases based on the next frame index: read-only and write-only.
In the read-only phase, all threads read that variable (and do the rest of their work), then in the write-only phase, the clock thread writes the new index.
I’m sure there are better tools for the job though — it relies on me writing code that obeys my own rule, without any compile-time or runtime enforcement.&lt;/p&gt;

  &lt;div class=&quot;local-commit&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/badapple.rs/commits/156c45ea6c6e3d2c2970ed8c485df0a8cc0d0dbb&quot;&gt;&lt;code&gt;156c45ea6c6e3&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

  &lt;div class=&quot;local-commit&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/badapple.rs/commits/5065d9566d7b6e0ca3feea7a6719df81aa42cbc2&quot;&gt;&lt;code&gt;5065d9566d7b6&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-dot.svg&quot; /&gt;&lt;/div&gt;

  &lt;div class=&quot;local-commit local-commit-none&quot;&gt;&lt;a href=&quot;https://bitbucket.org/delan/badapple.rs/commits/8a2d3d2288aa8ee0d1cb9d360eb33544d964e353&quot;&gt;&lt;code&gt;8a2d3d2288aa8&lt;/code&gt;&lt;/a&gt;&lt;img src=&quot;/images/badapple-commit-none.svg&quot; /&gt;&lt;/div&gt;

&lt;/div&gt;

&lt;h2 id=&quot;the-present-tense&quot;&gt;The present tense&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://bitbucket.org/delan/badapple.rs&quot;&gt;I wrote a video player&lt;/a&gt; for taskmgr, and it now works well enough to play Bad Apple!! on an 11x6 canvas with only a laptop and the power of virtualisation.
Picture fidelity and frame rates are far better during the black-on-white shots, but at least &lt;em&gt;some&lt;/em&gt; of the white-on-black shots managed to yield more than a complete blur.&lt;/p&gt;

&lt;p&gt;Playback at 4 fps or even 10 fps is now possible, which is an improvement that even the Real Hardware folks can enjoy, but if I could drive 66 pixels with only 12 hardware threads, imagine what we could do with 64 or even 128!&lt;/p&gt;

&lt;hr /&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://bitbucket.org/delan/daria.daz.cat&quot;&gt;I still use that router&lt;/a&gt; at home today, almost five years later, and I’m only planning to decommission it because I recently upgraded to gigabit internet, which the poor thing just can’t keep up with. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;This includes workarounds for lying firmware, handling of unsynchronised TSC values across processors, and much more. I would seriously recommend reading Microsoft’s &lt;a href=&quot;https://docs.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps&quot;&gt;documentation about this&lt;/a&gt;, it’s very interesting! &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;I don’t think this is actually true, but I’m sticking with the analogy. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name></name></author><category term="home" /><category term="rust" /><summary type="html">Bad Apple!! is a Touhou song whose music video has been described as “the doom of music videos”, because it’s been played on everything from string lights to oscilloscopes. This post is about that video, writing an improved “video player” for Task Manager’s CPU graphs, and using virtual machines to push my feeble hardware to the limit.</summary></entry><entry><title type="html">An open letter to Bitbucket about Mercurial</title><link href="https://www.azabani.com/2020/06/07/open-letter-to-bitbucket.html" rel="alternate" type="text/html" title="An open letter to Bitbucket about Mercurial" /><published>2020-06-07T09:00:00+00:00</published><updated>2020-06-07T09:00:00+00:00</updated><id>https://www.azabani.com/2020/06/07/open-letter-to-bitbucket</id><content type="html" xml:base="https://www.azabani.com/2020/06/07/open-letter-to-bitbucket.html">&lt;p&gt;In August 2019, the Bitbucket team announced that &lt;a href=&quot;https://bitbucket.org/blog/sunsetting-mercurial-support-in-bitbucket&quot;&gt;they would “sunset” Mercurial&lt;/a&gt; on 1 June 2020 (now 1 July 2020) by removing all Mercurial repositories and associated content.
I urge them to reconsider that approach with this open letter.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Hi.
I’ve enjoyed using Bitbucket for a &lt;a href=&quot;https://bitbucket.org/delan&quot;&gt;long time&lt;/a&gt;, though I’m pretty sure I joined after you &lt;a href=&quot;https://bitbucket.org/blog/bitbucket-now-rocks-git&quot;&gt;added Git support&lt;/a&gt;, and I’m not afraid to &lt;a href=&quot;https://github.com/rust-lang/crates.io/pull/1934&quot;&gt;make other sites enjoy you too&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The merits of discontinuing &lt;em&gt;active&lt;/em&gt; Mercurial support are their own can of worms.
I don’t really have a strong opinion there one way or another, and I’m sure you’ve heard more than enough about that in &lt;a href=&quot;https://community.atlassian.com/t5/x/x/ba-p/1155380&quot;&gt;your forum thread&lt;/a&gt;, which received so many replies that I couldn’t read them all.&lt;/p&gt;

&lt;p&gt;I have a passing interest in Mercurial (thanks to you), but that’s nothing compared to how much I care about digital preservation, that is to say, the long-term health of everything we create with our computers.&lt;/p&gt;

&lt;p&gt;On this basis, I believe your approach is irresponsible.
I don’t have all the answers here, but there’s a lot of middle ground between “full Mercurial support forever” and “delete fucking everything”.
You can do better.&lt;/p&gt;

&lt;p&gt;Let’s explore some of that middle ground, but first, an appeal.&lt;/p&gt;

&lt;h2 id=&quot;if-nothing-else&quot;&gt;If nothing else…&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://www.archiveteam.org&quot;&gt;Archive Team&lt;/a&gt; will start building a best-effort archive soon, &lt;a href=&quot;https://www.archiveteam.org/index.php?title=Bitbucket&quot;&gt;with or without your help&lt;/a&gt;.
Please be nice to them, don’t block them, and have someone on your team take point on any questions or problems that come up.
You can reach them in #kickthebucket on &lt;a href=&quot;https://hackint.org&quot;&gt;hackint&lt;/a&gt;, or &lt;a href=&quot;/about/&quot;&gt;hit me up&lt;/a&gt; and I can pass on some contact details.&lt;/p&gt;

&lt;p&gt;If you’re not going to change your mind, it would be best to reach out to the people who live and breathe online digital preservation, above or at the &lt;a href=&quot;https://archive.org/about/contact.php&quot;&gt;Internet Archive&lt;/a&gt;, and find out how you can make their work easier.
I would imagine that this could mean anything from passing on dumps of the underlying data to setting up redirects to an archive, but the opinions in this letter are my own.&lt;/p&gt;

&lt;h2 id=&quot;why-you-should-care&quot;&gt;Why you should care&lt;/h2&gt;

&lt;p&gt;Leaving it up to third-party archivists to figure out how to scrape everything from a production service is far from ideal.
When this happens, it might not be easy (or even possible) for users to remove or supersede their archived content.
There are things that can’t be scraped of course, including private repos and some metadata like environment variables, but with some collaboration, there might be ways to preserve those safely too.&lt;/p&gt;

&lt;p&gt;Placing the burden of migration on authors, while convenient and flexible, will effectively destroy the works of authors who aren’t around to defend them.
No author stays an active user forever, every author dies someday, and not all works will find a new maintainer before one of those things happen.&lt;/p&gt;

&lt;p&gt;All of the recommended migration options destroy issues, pull requests, downloads, and Pipelines results, or at least &lt;a href=&quot;https://www.w3.org/Provider/Style/URI&quot;&gt;all of the links&lt;/a&gt; to them.
The raw commits are no substitute for the knowledge and history in that content.
Said archivists are working on &lt;a href=&quot;https://github.com/clach04/bitbucket_tools&quot;&gt;some&lt;/a&gt; &lt;a href=&quot;https://github.com/philipstarkey/bitbucket-hg-exporter&quot;&gt;more complete tools&lt;/a&gt;, but even if they’re complete &lt;em&gt;tomorrow&lt;/em&gt;, only you can prevent link rot.&lt;/p&gt;

&lt;h2 id=&quot;build-with-heart-and-balance&quot;&gt;Build with heart and balance&lt;/h2&gt;

&lt;p&gt;There’s a difference between obligation and responsibility.
In the world &lt;a href=&quot;https://en.wikipedia.org/wiki/Capitalism&quot;&gt;as we know it&lt;/a&gt;, your only strict obligations are to those who have paid you to do something.&lt;/p&gt;

&lt;p&gt;When you build a medium for people to collaborate and share their works with the world, you have responsibilities to those works.
You can think of this as the price of inviting free users to build your platform and establish your place as a major competitor to that other service with the five-limbed feline mascot.&lt;/p&gt;

&lt;p&gt;Our cultural output over the last century is in unprecedented danger of being lost forever, and yes, that includes code.
This isn’t a foregone conclusion though, and you have the power to shut down your service in a way that avoids destruction.&lt;/p&gt;

&lt;h2 id=&quot;more-middle-ground&quot;&gt;More middle ground&lt;/h2&gt;

&lt;p&gt;You could step down to read-only access, potentially by converting the content to a static HTML format.
No one says read-only has to mean seamless integration with your evolving frontend, and if anything, everyone wins if you do it in a way that relieves you of having to think of compatibility with each change.&lt;/p&gt;

&lt;p&gt;You could convert the repos to Git in place.
If you do nothing, authors are going to have to convert and/or relocate their repos anyway, but no matter what they choose, they lose all of their issues and other non-code content.
You are in a unique position to keep this content alive, as well as keep commit links working by, for example, redirecting Mercurial hashes to Git hashes.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://code.google.com/archive/about&quot;&gt;Google Code&lt;/a&gt; operated in the same space from 2006 to 2015, and even mentioned you in their &lt;a href=&quot;https://opensource.googleblog.com/2015/03/farewell-to-google-code.html&quot;&gt;farewell post&lt;/a&gt;.
When that service shut down, every project was archived, including code, issues, wikis, and downloads.
Links were kept alive, like &lt;a href=&quot;https://code.google.com/p/xtideuniversalbios/&quot;&gt;this project link&lt;/a&gt;.
Authors were given a way (albeit not a very convenient one) to remove archived projects, or set up redirects, after the archive date.&lt;/p&gt;

&lt;p&gt;None of these options are easy, but I trust you to do the right thing and try.&lt;/p&gt;</content><author><name></name></author><category term="home" /><summary type="html">In August 2019, the Bitbucket team announced that they would “sunset” Mercurial on 1 June 2020 (now 1 July 2020) by removing all Mercurial repositories and associated content. I urge them to reconsider that approach with this open letter.</summary></entry><entry><title type="html">Ad vigendum per aspera</title><link href="https://www.azabani.com/2018/10/10/ad-vigendum-per-aspera.html" rel="alternate" type="text/html" title="Ad vigendum per aspera" /><published>2018-10-10T05:13:13+00:00</published><updated>2018-10-10T05:13:13+00:00</updated><id>https://www.azabani.com/2018/10/10/ad-vigendum-per-aspera</id><content type="html" xml:base="https://www.azabani.com/2018/10/10/ad-vigendum-per-aspera.html">&lt;p&gt;The last four years have fucked with me in ways out of which I’ve only
really started clawing myself over the last year or so. I went from
feeling like I could do anything and improve the lives of the people
around me, to feeling like an unreliable piece of shit whose only
skills were botching my responsibilities and disappointing the people
I knew. I gave up on learning to play the hand I was dealt, neglected
the people I love, and grew to cope with all of my problems the only
way I knew: by ignoring them. But the scales are finally starting to
tip in my favour, and it’s time for me to end my exile.&lt;/p&gt;

&lt;h2 id=&quot;2016&quot;&gt;2016&lt;/h2&gt;

&lt;!-- ### February 2016 --&gt;

&lt;p&gt;I quit my internship, like the last two, because I refused to continue
taking money when I couldn’t think clearly enough to do even half of
the work I should’ve been doing. Three internships — 2014, 2015, 2016
— and three early resignations. This was a huge blow: my teaching job
was the only one of the four jobs I had ever been hired for that I
could hold down for more than a few months, but I immediately
dismissed it as an outlier and started believing this was the just
deserts for my intrinsic incompetence.&lt;/p&gt;

&lt;p&gt;I learned that I have ADHD, a condition whose symptoms would later be
described as comparable to early dementia (Callahan et al. &lt;a href=&quot;https://twitter.com/FioraAeterna/status/987823600735404033&quot;&gt;via
@FioraAeterna&lt;/a&gt;), and started treating it with stimulants, which
went a long way towards helping me through my final year.&lt;/p&gt;

&lt;aside class=&quot;references&quot;&gt;&lt;cite&gt;Callahan, B. L., Bierstone, D., Stuss,
D. T., &amp;amp; Black, S. E. (2017). Adult ADHD: Risk Factor for Dementia or
Phenotypic Mimic? &lt;i&gt;Frontiers in Aging Neuroscience&lt;/i&gt;,
&lt;i&gt;9&lt;/i&gt;, 260. &lt;a href=&quot;https://doi.org/10.3389/fnagi.2017.00260&quot;&gt;doi:10.3389/fnagi.2017.00260&lt;/a&gt;&lt;/cite&gt;&lt;/aside&gt;

&lt;!-- ### December 2016 --&gt;

&lt;p&gt;I crossed the finish line without failing a single unit (though a
couple were close). I still lived with my parents, so unbeknownst to
me, my life was about to get way harder, and this gave me a false
sense of confidence in the completeness of stimulant therapy alone.&lt;/p&gt;

&lt;h2 id=&quot;2017&quot;&gt;2017&lt;/h2&gt;

&lt;!-- ### January 2017 --&gt;

&lt;p&gt;I fucked off to Sydney to start my new job, leaving way too many loose
ends untied. I had underdelivered on the handover that I promised the
new ComSSA committee, and to add insult to injury, went unreachable
for months at a time, and would end up waiting a year before making a
serious attempt to deliver the rest.&lt;/p&gt;

&lt;!-- ### March 2017 --&gt;

&lt;p&gt;A couple of months in, work sent me to my alma mater to shill our
graduate program, together with the two colleagues that I graduated
with. I procrastinated writing my slide deck until my taxi ride there,
but unsatisfied with the mere punishment of presenting bad slides, my
utterly broken time management delivered me to the venue so late that
my part should’ve started and ended by the time I arrived, forcing my
colleague to improvise it.&lt;/p&gt;

&lt;p&gt;Without the skills I needed to exercise agency over my cursed brain,
my life quickly deteriorated and I floundered at work. I spent what
felt like every waking hour either at work or struggling to get ready
for work or ensuring that I could make it to work on time, but for all
my effort I received a mediocre performance review and no promotion.&lt;/p&gt;

&lt;!-- ### December 2017 --&gt;

&lt;p&gt;Mum suggested that we plan my time in Perth when I went home for the
holidays. This was pretty foreign to me — planning wasn’t something I
had ever done of my own volition — but so was the result, which marked
the start of my belated learning of the truth behind the “drugs
&lt;em&gt;and&lt;/em&gt; therapy” advice that every Mental Illness 101 includes.&lt;/p&gt;

&lt;h2 id=&quot;2018&quot;&gt;2018&lt;/h2&gt;

&lt;!-- ### June? 2018 --&gt;

&lt;p&gt;One of my closest friends invited me to his wedding. I prepared my
outfit and gift and I was happy with the fruits of my preparation,
but I left some of it to the morning of the day. I rushed to show up
on time but misread my invitation, mistaking the reception place for
the wedding place, and I missed the wedding.&lt;/p&gt;

&lt;p&gt;I’ve started getting the hang of my limitations and adapting to them
with appropriate tools. I remind myself to do things with timers,
alarms, or calendar events, depending on how long I’m deferring them
by. I’m learning how to use org-mode and Trello as structured
extensions of my memory, rather than write-only black holes like
Sublime Text buffers. These are just a couple of examples, and ADHD
management is definitely a subject worth exploring further, but that’s
a job for another day.&lt;/p&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
  &lt;p&gt;I’ve turned away when I knew I made a mistake
&lt;br /&gt;Instead of dealing with it&lt;/p&gt;

  &lt;p&gt;I’ve wasted good chances I’ve had in this life
&lt;br /&gt;That other people won’t even get
&lt;br /&gt;I put off setting things right with Andy
&lt;br /&gt;And now he’s dead&lt;/p&gt;

  &lt;footer&gt;— &lt;cite&gt;&lt;a href=&quot;https://genius.com/Pat-the-bunny-run-from-whats-comfortable-lyrics&quot;&gt;Run From What’s Comfortable&lt;/a&gt; by Pat The Bunny&lt;/cite&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;p&gt;Reading &lt;a href=&quot;https://gekk.info/articles/adhd.html&quot;&gt;this essay about ADHD&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/gravislizard&quot;&gt;@gravislizard&lt;/a&gt; was a crucial
moment that brought me to tears, and I think it’s just as useful an
introduction to what I grapple with every day. Pretty much all of the
experiences he wrote about resonate with me, but there’s one that I’ll
highlight shortly, because it helps explain &lt;a href=&quot;https://www.facebook.com/dazabani/posts/632393646929135&quot;&gt;something I wrote a
couple of years ago&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;To all who have sent me a thing and are still waiting for a response
(be it for a day or for ten months), I’m sorry. I don’t mean to be
rude.&lt;/p&gt;

  &lt;p&gt;The short version of my excuse goes like this. Every time I reply to
a message, I feel like I’m faced with two more to which I haven’t,
and I feel overwhelmed, so I cope with that feeling by ignoring the
cause. As the problem grows, I feel more &lt;strong&gt;anxious about the
thought of saying “sorry for the late response” with a straight
face, when all this time, I knew how long you’ve been waiting, and
you know how long I’ve kept you waiting&lt;/strong&gt;, so I ignore it. I
haven’t learned how to break a task into manageable pieces, and if I
can’t solve a problem thoroughly in one sitting or a few, I ignore
it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What might not make sense is why I would see replying to a message as
a difficult task (though I’ve conflated difficult with big there), but
as he explains, it can be really hard for me to follow through and
finish my replies between getting &lt;a href=&quot;https://twitter.com/gravislizard/status/1009918919883145217&quot;&gt;lost in the woods&lt;/a&gt; and straight
up forgetting, but I needed to realise how neglectful it was for me to
concede defeat:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Suppose I’m at work and I think, “I need to send an email to
&amp;lt;people&amp;gt; about &amp;lt;issue&amp;gt;.” Before I can do anything, I’m
already thinking about the issue.&lt;/p&gt;

  &lt;p&gt;I try to write the email, but as I’m writing it, questions fill my
head. I try to push them aside and just concentrate on the initial
task, send a simple email, but the questions keep hounding me. I
have to stop and get answers to them or I can’t think about the
email. And if I can’t get answers, I’m stuck. I just can’t proceed.
&lt;strong&gt;I end up staring at the empty compose window for ten
minutes, writing the first sentence over and over,&lt;/strong&gt; because
my mind is so far out of the game I can’t even do basic grammar.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;ADHD’s also a motherfucker. get texted at 8AM “wanna hang out
tonight”, &lt;strong&gt;read it, forget to reply until 3PM.&lt;/strong&gt;&lt;/p&gt;

  &lt;footer&gt;— &lt;cite&gt;&lt;a href=&quot;https://twitter.com/gravislizard&quot;&gt;@gravislizard&lt;/a&gt; on &lt;a href=&quot;https://twitter.com/gravislizard/status/947220366656602112&quot;&gt;31 December 2017&lt;/a&gt;&lt;/cite&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;I’m not saying you owe people a timely reply to their texts, right?
hey check this out: i absolutely am. &lt;strong&gt;like hey it’s your
friendships but when you don’t reply to people it actually does hurt
them.&lt;/strong&gt; source: me spending the entire day feeling like shit
on both sides of this&lt;/p&gt;

  &lt;footer&gt;— &lt;cite&gt;&lt;a href=&quot;https://twitter.com/gravislizard&quot;&gt;@gravislizard&lt;/a&gt; on &lt;a href=&quot;https://twitter.com/gravislizard/status/947221557000028160&quot;&gt;31 December 2017&lt;/a&gt;&lt;/cite&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you reach out to me, &lt;strong&gt;you should hear back from me by the
end of the day&lt;/strong&gt; (or the next at worst). I’ve made commitments
like this with a few of the people in my life, and those have been
much easier said than done, so how will this be any different?&lt;/p&gt;

&lt;p&gt;I’ll try to reply in chunks that mirror my thought processes, rather
than set out to write one perfect message that’s completely devoid of
uncertainty and unknowns. The latter encourages the very kind of
“depth-first search” that will leave me lost in the woods, and it’s
not really how conversations are supposed to work.&lt;/p&gt;

&lt;p&gt;I’ll get comfortable with admitting that from time to time I’m too
cooked to respond properly. Even if it means you might need to suspend
your disbelief, because you’ve only sent me a funny video and how hard
can a response to that be? Accepting that some of my 503 errors might
make me look silly is better than working myself up to an extended
panic attack that I’ll cope with by ignoring you for three months.&lt;/p&gt;

&lt;p&gt;I’ll remind myself that — like any goal I want to convert to a habit —
some number of failures will be inevitable, and they’re a signal that
I should look for ways to tweak my approach and make it more reliable,
not that my whole effort was pointless and I should put it in the bin.
As obvious as this sounds, it wasn’t obvious to me until a year ago,
before which I believed I couldn’t make or break a habit for longer
than three days.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;One thing that hasn’t helped my state of mind over the years was the
knowledge that I’m trans while feeling unable to do anything about it,
but I’m elated to say I’ve finally started my transition (they/them
for now, but don’t panic if you drop a he/him here and there).
Conveniently enough &lt;span lang=&quot;ckb&quot;&gt;دیلان&lt;/span&gt; (&lt;span lang=&quot;kmr&quot;&gt;dîlan&lt;/span&gt;) &lt;a href=&quot;https://en.wiktionary.org/wiki/Appendix:Kurdish_given_names&quot;&gt;isn’t exclusively masculine&lt;/a&gt;, and &lt;a href=&quot;https://glosbe.com/ku/en/d%C3%AElan&quot;&gt;one
source&lt;/a&gt; even describes it as a woman’s name, so I’m pretty
comfortable with my name.&lt;/p&gt;

&lt;p&gt;Shout out to Adam, Alex, Alex, Aria, Brock, Carmel, Cyra, Emily, Jaci,
James, Jason, Josh, Kieran, Krystal, Luke, Mike, Mike, Millie, Sandro,
Scott, my parents, and anyone who hasn’t given up on me by now. I
wouldn’t be here without your support.&lt;/p&gt;

&lt;p&gt;I can see the light at the end of the tunnel. Will you meet me there?&lt;/p&gt;</content><author><name></name></author><category term="home" /><category term="self" /><summary type="html">The last four years have fucked with me in ways out of which I’ve only really started clawing myself over the last year or so. I went from feeling like I could do anything and improve the lives of the people around me, to feeling like an unreliable piece of shit whose only skills were botching my responsibilities and disappointing the people I knew. I gave up on learning to play the hand I was dealt, neglected the people I love, and grew to cope with all of my problems the only way I knew: by ignoring them. But the scales are finally starting to tip in my favour, and it’s time for me to end my exile.</summary></entry><entry><title type="html">Choosing a software licence</title><link href="https://www.azabani.com/2015/12/21/choosing-a-software-licence.html" rel="alternate" type="text/html" title="Choosing a software licence" /><published>2015-12-21T14:00:00+00:00</published><updated>2015-12-21T14:00:00+00:00</updated><id>https://www.azabani.com/2015/12/21/choosing-a-software-licence</id><content type="html" xml:base="https://www.azabani.com/2015/12/21/choosing-a-software-licence.html">&lt;p&gt;The licence that one releases their software under is often a topic
that’s given less thought then it perhaps deserves. I’ve been
releasing my code under the MIT (Expat) licence for as long as I can
remember, but I thought it might be prudent to take a closer look at
what’s out there in the wild world of software licensing.&lt;/p&gt;

&lt;h2 id=&quot;teal-deer&quot;&gt;Teal deer&lt;/h2&gt;

&lt;p&gt;Unless otherwise specified, neither this page, nor any of the pages
that I’m about to link to, necessarily constitute Proper Legal
Advice™. If your project matters enough that the choice of its
licence may have a significant effect, please hire an actual lawyer.
This means that you should definitely steer clear of Lionel Hutz.&lt;/p&gt;

&lt;p&gt;If you want to choose a licence, and you want to make the most
reasonable choice you can in the least possible time, use GitHub’s
&lt;a href=&quot;http://choosealicense.com/&quot;&gt;Choose a License&lt;/a&gt;. For most scenarios, the choice basically
boils down to Expat, Apache 2.0, or the GPL.&lt;/p&gt;

&lt;p&gt;If you’re looking for a comprehensive reference on the pros and cons
of the licence you’re considering, check out the Free Software
Foundation’s &lt;a href=&quot;https://www.gnu.org/licenses/license-list.html&quot;&gt;Various Licenses and Comments about Them&lt;/a&gt;, which
is only occasionally didactic about copyleft licensing. You may also
find &lt;a href=&quot;https://tldrlegal.com/&quot;&gt;TLDRLegal&lt;/a&gt; and OSS Watch’s &lt;a href=&quot;http://oss-watch.ac.uk/apps/licdiff/&quot;&gt;Licence
differentiator&lt;/a&gt; useful.&lt;/p&gt;

&lt;h2 id=&quot;licence-compatibility&quot;&gt;Licence compatibility&lt;/h2&gt;

&lt;p&gt;Whether or not two licences are “compatible” is important for when
programs of those licences are combined, derived, or linked together,
but the details depend on who you ask. For some, compatibility is
achieved when works of the given licences may be combined to form a
larger software project, while for others, compatibility also
requires that one of the constituent works’ licences be able to
“dominate”, licensing the entire derived work. Compatibility is
generally &lt;a href=&quot;http://www.eolevent.eu/sites/default/files/EOLE%202008%20%E2%80%94%20Philippe%20Laurent%20%E2%80%94%20The%20GPLv3%20and%20Compatibility%20Issues.pdf&quot;&gt;&lt;em&gt;not&lt;/em&gt; a symmetric relation&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;the-public-domain&quot;&gt;The public domain&lt;/h2&gt;

&lt;p&gt;Placing your work in the public domain is simple, in theory, as doing
so should relinquish your ownership and all of your rights over said
work. Very few jursdictions, however, provide a practical way to do
this, and in some jurisdictions, you may not be allowed to do this at
all! For people in those places, your work will effectively continue
to be “all rights reserved”, rendering it unusable for them.&lt;/p&gt;

&lt;p&gt;Enter &lt;a href=&quot;https://creativecommons.org/about/cc0&quot;&gt;CC0&lt;/a&gt;. It’s essentially a licence that attempts to waive
all rights over a given work in a given jurisdiction, and failing
that, places the work under a licence with the most permissive terms
allowed in that jurisdiction.&lt;/p&gt;

&lt;p&gt;CC0 is usually referred to as a “tool” instead of a “licence”,
because where a work may be placed in the public domain, CC0 no
longer bears the role of a licence. Unlike most Creative Commons
licences, CC0 is suitable for use with any kind of work, &lt;a href=&quot;https://creativecommons.org/faq/#can-i-apply-a-creative-commons-license-to-software&quot;&gt;including
software&lt;/a&gt;. The FSF prefers CC0 for releasing works into the
public domain.&lt;/p&gt;

&lt;h2 id=&quot;the-novelty-licences&quot;&gt;The novelty licences&lt;/h2&gt;

&lt;p&gt;Other attempts to solve the issue of placing works in the public
domain, include Banlu Kemiyatorn and Sam Hocevar’s Do What the Fuck
You Want to Public License, or WTFPL for short. While on the surface,
writing a licence to informally relinquish all rights seems like a
reasonable solution, the &lt;a href=&quot;http://opensource.org/minutes20090304&quot;&gt;Open Source Initiative&lt;/a&gt; has noted
that without a clear and explicit grant of rights, the WTFPL is no
more universal than any other lone attempt to place a work in the
public domain.&lt;/p&gt;

&lt;p&gt;By lacking any explicit legal terms, the WTFPL also fails to disclaim
authors’ warranties and liabilities for their software, protections
that are provided by virtually all serious free and open licences. As
such, while “novelty” licences like the WTFPL are certainly amusing,
they’re not a wise choice for any significant projects.&lt;/p&gt;

&lt;h2 id=&quot;the-copyleft-licences&quot;&gt;The copyleft licences&lt;/h2&gt;

&lt;p&gt;In an effort to ensure that free and open works remain so, even after
they’re distributed or modified to create derivative works,
“copyleft” licences like the GNU General Public License were created.
Richard Stallman described the concept succinctly in his &lt;em&gt;GNU
Manifesto&lt;/em&gt;: “no distributor [of the GNU operating system] will be
allowed to restrict its further redistribution”.&lt;/p&gt;

&lt;p&gt;When distributing software released under a copyleft licence, whether
or not any modifications have been made, the source &lt;em&gt;must&lt;/em&gt; be
included or otherwise made readily available along with any binaries
or other forms of the software.&lt;/p&gt;

&lt;p&gt;Some examples of copyleft licences include the GNU GPL family, the
Mozilla Public License family, as well as Sun Microsystems’
derivatives of the MPL, the Sun Public License and the Common
Development and Distribution License.&lt;/p&gt;

&lt;p&gt;While version 2.0 of the MPL has been updated to explicitly introduce
compatibility with some of the GPL licences, the SPL and the CDDL are
based on version 1.0 and 1.1 of the MPL, and this is — at least in
part — why they’re incompatible with the GPL.&lt;/p&gt;

&lt;h2 id=&quot;the-permissive-licences&quot;&gt;The permissive licences&lt;/h2&gt;

&lt;p&gt;Free and open licences which are not copyleft are usually referred to
as “permissive” licences. Like all free and open licences, permissive
licences adhere to the Free Software Definition, and programs
licensed under a permissive licence grant anyone the freedom to:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Run the program in any manner, and for any purpose;&lt;/li&gt;
  &lt;li&gt;Study how the program works, and change it to suit one’s needs;&lt;/li&gt;
  &lt;li&gt;Redistribute copies (of the original program) to others; and&lt;/li&gt;
  &lt;li&gt;Distribute copies of one’s modified versions to others.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Permissive licences almost always specify a common set of additional
terms:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The authors disclaim all warranties and liabilities for their software;&lt;/li&gt;
  &lt;li&gt;Copies and derivative works must credit the original authors; and&lt;/li&gt;
  &lt;li&gt;Copies and derivatives must retain the original copyright notices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are loads of permissive licences out there, including but not
limited to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The MIT licences (Expat, X11, and XFree86);&lt;/li&gt;
  &lt;li&gt;The BSD licences (“prior”, “original”, “revised”, “simplified”, and FreeBSD);&lt;/li&gt;
  &lt;li&gt;The University of Illinois/NCSA licence (UIUC or NCSA for short);&lt;/li&gt;
  &lt;li&gt;The ISC licences (with or without the FSF’s clarification); and&lt;/li&gt;
  &lt;li&gt;The Apache licences (versions 1.0, 1.1, and 2.0).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They’re all worth mentioning here, because they share a handful of
subtle differences.&lt;/p&gt;

&lt;h2 id=&quot;advertising-and-endorsement-clauses&quot;&gt;Advertising and endorsement clauses&lt;/h2&gt;

&lt;p&gt;Being permissive licences, the MIT, BSD, NCSA, ISC, and Apache
families of licences are nearly identical in their spirit and terms.&lt;/p&gt;

&lt;p&gt;Chronologically speaking, there are five licences that have been used
in the BSD and its derivatives, although only the last three are
really relevant for modern usage:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The “prior” BSD licence (1988), as used by 4.3BSD-Tahoe;&lt;/li&gt;
  &lt;li&gt;The “original” BSD licence (1990), with four clauses;&lt;/li&gt;
  &lt;li&gt;The “revised” BSD licence (1999), with three clauses;&lt;/li&gt;
  &lt;li&gt;The “simplified” BSD licence, with two clauses; and&lt;/li&gt;
  &lt;li&gt;The FreeBSD licence, which also has two clauses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The earliest two versions of the licence are roughly equivalent, and
they have clauses that are known as the “advertising clauses”, which
require that any advertising for products or services that contain
licensed code must include an acknowledgement of its authors.&lt;/p&gt;

&lt;p&gt;You might correctly imagine that this requirement can prove very
unwieldy, very quickly, and as such, they aren’t approved by the OSI,
nor are they compatible with many popular licences like the GPL. The
“revised” version removes the advertising clause, making it far
less troublesome and incompatible with other free and open licences.&lt;/p&gt;

&lt;p&gt;All three of these versions, however, include another clause that
requires permission prior to using the names of a work’s authors and
contributors to “endorse or promote” derivative works. Contrary to my
initial assumption, this doesn’t preclude the “revised” version from
compatibility with the GPL, nor from being considered free and open.&lt;/p&gt;

&lt;p&gt;The “simplified” BSD licence removes this “endorsement” clause too,
leaving behind a fairly concise permissive licence, and the FreeBSD
licence is based on this version, with the addition of a paragraph
stating that the “views and conclusions” of the individual authors
don’t represent the FreeBSD Project as a whole.&lt;/p&gt;

&lt;p&gt;As for the MIT family of licences:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The Expat licence is similar to the “simplified” BSD licence;&lt;/li&gt;
  &lt;li&gt;The X11 licence is similar to the “revised” BSD licence; and&lt;/li&gt;
  &lt;li&gt;The XFree86 licence is similar to the “original” BSD licence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, the X11 licence is like the Expat licence plus an
endorsement clause, and the XFree86 licence is like the X11 licence
plus an advertising clause.&lt;/p&gt;

&lt;p&gt;The NCSA licence, put simply, is based on the “revised” BSD licence,
with some minor improvements in wording drawn from the X11 licence to
improve precision.&lt;/p&gt;

&lt;p&gt;Version 1.0 of the Apache licence is similar to the “original” BSD
licence by virtue of having an advertising clause, while version 1.1
removes the clause, making it vaguely like the “revised” BSD licence.
These versions of the Apache licence weren’t really designed for
general use by other projects however, and their strict and specific
prohibitions on the use of Apache-related names, along with their
incompatibility with the GPL because of these, make them rather
unattractive options.&lt;/p&gt;

&lt;p&gt;Finally and most elegantly, the ISC licences are “functionally
equivalent” to the Expat and “simplified” BSD licences, but thanks
to the Berne convention, they’re over 30% less wordy than Expat, and
over 40% less wordy than the BSD two-clause!&lt;/p&gt;

&lt;h2 id=&quot;patent-grants-and-retaliation-clauses&quot;&gt;Patent grants and retaliation clauses&lt;/h2&gt;

&lt;p&gt;Most of the permissive licences we’ve discussed so far were written
before software patents were widespread. With these licences, anyone
who uses a program that’s covered by one or more patents may risk
being the target of litigation from patent holders.&lt;/p&gt;

&lt;p&gt;There are two kinds of clauses that a software licence can use to
help mitigate this risk: grant clauses &lt;em&gt;grant&lt;/em&gt; users a licence to use
any necessary patents, and retaliation clauses &lt;em&gt;revoke&lt;/em&gt; these patent
licences from anyone who initiates litigation.&lt;/p&gt;

&lt;p&gt;Enter version 2.0 of the Apache licence. This licence contains both
a grant clause and a retaliation clause, making it widely recommended
as the most robust licence with respect to its protections against
patent litigation. The retaliation clause alone subsequently made its
way into the GPL 3.0 and the MPL 2.0.&lt;/p&gt;

&lt;h2 id=&quot;the-creative-commons-licences&quot;&gt;The Creative Commons licences&lt;/h2&gt;

&lt;p&gt;Mainly geared towards artistic and other creative works, the Creative
Commons family of licences give authors a variety of choices about
how their works may be used. Regardless of the choices that a given
author makes, anyone may — at the very least — distribute any work
that’s in the Creative Commons worldwide, but:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The work must not be modified, regardless of its distribution;&lt;/li&gt;
  &lt;li&gt;Attribution for the authors of the work must be preserved; and&lt;/li&gt;
  &lt;li&gt;The distribution must not take place for commercial purposes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From here, authors may tweak the terms of the licence by:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Allowing the commercial distribution of their work; and/or&lt;/li&gt;
  &lt;li&gt;Allowing derivative works:
    &lt;ul&gt;
      &lt;li&gt;Provided that they use the same licence; or&lt;/li&gt;
      &lt;li&gt;Regardless of the resultant licence.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s not a good idea to use any of the Creative Commons licences,
except for CC0, for &lt;a href=&quot;https://creativecommons.org/faq/#can-i-apply-a-creative-commons-license-to-software&quot;&gt;software projects&lt;/a&gt;, because they lack
specific terms that are desirable for software licensing, and they’re
incompatible with most free and open software licences, among other
reasons.&lt;/p&gt;

&lt;h2 id=&quot;when-readable-source-isnt-open-source&quot;&gt;When readable source isn’t open source&lt;/h2&gt;

&lt;p&gt;That one is allowed to read the source code of a work alone doesn’t
imply that the FSF will consider its licence “free”, nor will the OSI
necessarily call its licence “open”. In the case of the OSI, the
availability and legibility of source code merely forms a part of one
of the ten criteria that the Open Source Definition comprises.&lt;/p&gt;

&lt;p&gt;One example of such a licence is the TrueCrypt licence, which not
only has excessive restrictions on how licensed works may be derived
from or distributed, but also forbids anyone who doesn’t understand
the licence from using a licensed work, which is a clear violation of
freedom 0 of the Free Software Definition.&lt;/p&gt;

&lt;p&gt;Another licence that fits the bill here is the JSON licence, which
Douglas Crockford uses for many of his popular programs such as
JSLint and JSMin. It’s identical to the Expat licence, with the
exception of an additional sentence: “The Software shall be used for
Good, not Evil.”&lt;/p&gt;

&lt;p&gt;While many people may agree that this additional “restriction” is
clearly tongue-in-cheek, as it’d be ineffective at preventing any
actual evildoers from using a licensed work to do their evils, it
makes the JSON licence a nightmare to use for any meaningful project.&lt;/p&gt;

&lt;p&gt;The subjective nature of evil could potentially put users of a
licensed work at risk of being a target of litigation without their
knowledge, and this restriction is also a clear violation of freedom
0, making it incompatible with essentially all free and open
licences.&lt;/p&gt;

&lt;h2 id=&quot;colloquial-ambiguities&quot;&gt;Colloquial ambiguities&lt;/h2&gt;

&lt;p&gt;Just as the Creative Commons, GPL, MPL, and Apache licences must be
disambiguated with a version and possibly some licensing settings,
the “MIT licence” and “BSD licence” need to be qualified with which
one of the three or five variants they encompass is being discussed
respectively.&lt;/p&gt;

&lt;p&gt;Referring to the NCSA licence isn’t at all ambiguous, and the only
ambiguity when discussing the ISC licence is the clarifying
replacement of “and distribute” with “and/or distribute” to allow the
licence to be approved by the FSF, because some projects like OpenBSD
continue to use the old wording.&lt;/p&gt;

&lt;h2 id=&quot;vanity-and-aesthetics&quot;&gt;Vanity and aesthetics&lt;/h2&gt;

&lt;p&gt;It’s also interesting to see that these free and open licences are
usually named after:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A software project: GNU GPL, MPL, Apache, BSD, Expat, X11, XFree86;&lt;/li&gt;
  &lt;li&gt;A university: MIT, UIUC/NCSA;&lt;/li&gt;
  &lt;li&gt;Another organisation: Creative Commons, ISC; or&lt;/li&gt;
  &lt;li&gt;Its contents: WTFPL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While you could chalk it up to me being a little bit weird, I have a
slight but irrational aversion to preferring licences that fall into
the first two categories.&lt;/p&gt;

&lt;h2 id=&quot;relicensing-my-work&quot;&gt;Relicensing my work&lt;/h2&gt;

&lt;p&gt;At the exceedingly rare risk of these being foreshadowing words, I
don’t see myself being the target of patent litigation any time soon,
and I don’t feel like a copyleft licence provides a meaningful
benefit for my code over a permissive one.&lt;/p&gt;

&lt;p&gt;I think I’ll relicense my projects under the ISC licence over the
next week or two — not that it matters, as virtually nobody actually
uses the code I’ve written, but because it’s cute as fuck &lt;em&gt;and&lt;/em&gt; it
shares its semantics with the Expat licence that I’m currently using.&lt;/p&gt;</content><author><name></name></author><category term="home" /><summary type="html">The licence that one releases their software under is often a topic that’s given less thought then it perhaps deserves. I’ve been releasing my code under the MIT (Expat) licence for as long as I can remember, but I thought it might be prudent to take a closer look at what’s out there in the wild world of software licensing.</summary></entry><entry><title type="html">The modern OpenBSD home router</title><link href="https://www.azabani.com/2015/08/06/modern-openbsd-home-router.html" rel="alternate" type="text/html" title="The modern OpenBSD home router" /><published>2015-08-06T17:00:00+00:00</published><updated>2015-08-06T17:00:00+00:00</updated><id>https://www.azabani.com/2015/08/06/modern-openbsd-home-router</id><content type="html" xml:base="https://www.azabani.com/2015/08/06/modern-openbsd-home-router.html">&lt;p&gt;It’s no secret that most consumer routers ship with software that’s
flaky at best, and prohibitively insecure at worst. While I’ve had good
experiences with OpenWrt and pfSense, I wanted to build a router from
the ground up, both to understand the stack and to have something to
tinker with. I found many solid tutorials out there, but few of them
covered the intricacies of both PPP and IPv6. Here’s what I’ve learned.&lt;/p&gt;

&lt;h2 id=&quot;choosing-the-hardware&quot;&gt;Choosing the hardware&lt;/h2&gt;

&lt;p&gt;The hardware itself is not very modern at all. In the interest of a
challenge, I chose the oldest computer that could reasonably replace
the Billion 7800N that I was using. Meet the IBM Aptiva, model 2194,
whose tiny 95 watt power supply drives some hardware that’s nearly old
as myself:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cpu0: Intel Pentium III (&quot;GenuineIntel&quot; 686-class) 602 MHz
bios0: vendor IBM version &quot;PTKT09AUS&quot; date 05/15/2000
bios0: IBM 219443A
ral0 at pci1 dev 3 function 0 &quot;Ralink RT2561S&quot; rev 0x00: irq 11, address 00:21:29:e2:c6:03
em0 at pci1 dev 4 function 0 &quot;Intel 82541GI&quot; rev 0x05: irq 5, address 00:1b:21:56:16:9c
em1 at pci1 dev 5 function 0 &quot;Intel 82541GI&quot; rev 0x05: irq 3, address 00:1b:21:56:1b:86
spdmem0 at iic0 addr 0x50: 128MB SDRAM non-parity PC133CL2
spdmem1 at iic0 addr 0x51: 128MB SDRAM non-parity PC133CL2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After upgrading the memory’s speed and capacity, choosing Ethernet
cards was easy. Almost all of mine were based on a Realtek RTL81xx or
Intel 82451PI controller, both of which have excellent support with
OpenBSD. The Realtek-based cards I owned had an empty socket for a PXE
ROM, or no socket at all, so I opted for a pair of Intel PRO/1000 GT
Desktop cards instead. A quick test with nc(1) shows that I can only
push about 27 MiB/s through the router before the CPU becomes a
bottleneck.&lt;/p&gt;

&lt;p&gt;Finding a wireless card was more difficult, even though the spare cards
I had only supported 802.11g. Of those, only one has a driver that
supports hostap mode:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;SMC SMCWPCI-G (Qualcomm Atheros AR5005G(S) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;168C:001A&lt;/code&gt;): no driver&lt;/li&gt;
  &lt;li&gt;Netgear WPN311 V1H2 Rev. A3 (Atheros AR5005G(S) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;168C:001A&lt;/code&gt;): no driver&lt;/li&gt;
  &lt;li&gt;Netgear WG311v3 Rev. A1 (Marvell Libertas 88W8335 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;11AB:1FAA&lt;/code&gt;): malo(4) ✗&lt;/li&gt;
  &lt;li&gt;Cisco Linksys WMP54G ver. 4.1 (Ralink RT2561S &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1814:0301&lt;/code&gt;): ral(4) ✓&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conveniently, unlike Marvell, Ralink has also allowed OpenBSD to freely
distribute the required firmware without necessitating the use of
fw_update(1).&lt;/p&gt;

&lt;p&gt;I’m still connected to the outside world through an ADSL service with
&lt;a href=&quot;http://www.internode.on.net/&quot;&gt;Internode&lt;/a&gt;. Because internal modems for anything newer than G.992.1
are hard to come by, I simply dug out an old Netgear DG834Gv2 and put
it into bridge mode.&lt;/p&gt;

&lt;p&gt;To put the DG834Gv2 into bridge mode, head to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/setup.cgi?next_file=mode.htm&lt;/code&gt;
on the device, then ensure that the ADSL parameters are correct and
wireless is disabled. The 7800N would not only have been a waste of
fancy hardware, but doing the same on that made it unreachable over
IPv4, whereas the DG834Gv2 remained reachable. On the other hand, the
7800N can lie to your DSLAM or MSAN about the SNR of an ADSL connection.&lt;/p&gt;

&lt;p&gt;Having configured the hardware and installed OpenBSD 5.7 without any
problems, connecting to the Internet soon became my next task.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;post_image_full&quot; src=&quot;/images/router.jpg&quot; alt=&quot;[Photograph of the router with an IBM KB-8923]&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;interface-layout&quot;&gt;Interface layout&lt;/h2&gt;

&lt;p&gt;Behind every fancy router is a bridge, and a bridge is what I made:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;% cat /etc/hostname.bridge0
add vether0
add em0
add em1
add ral0
up
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;em0&lt;/code&gt; goes to the DG834Gv2, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;em1&lt;/code&gt; goes to a D-Link DGS-1008D, a dumb
switch, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ral0&lt;/code&gt; hosts the wireless network, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vether0&lt;/code&gt; serves two
purposes: not only does it decouple whether or not IPv4 and IPv6 will
work from whether or not a particular physical interface is up, but it
also yields a stable interface identifier that’s independent of any
physical interface.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;% cat /etc/hostname.em0
up

% cat /etc/hostname.em1
up

% cat /etc/hostname.ral0
media autoselect mode 11g mediaopt hostap
nwid deLAN
wpakey hunter13
wpaprotos wpa2
wpaakms psk
wpaciphers ccmp
wpagroupcipher ccmp
up

% cat /etc/hostname.vether0
inet 172.19.1.1 255.255.0.0
inet6 eui64

% sh /etc/netstart em0 em1 ral0 vether0 bridge0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;reaching-the-outside-world&quot;&gt;Reaching the outside world&lt;/h2&gt;

&lt;p&gt;As far as I know, Internode is the only residential ISP in Australia
that provides native IPv6 without the use of any transition mechanisms.
While some consumer routers like the 7800N Just Work™ in this regard,
others don’t, like the Netgear WNDR3700v2, for which the best
connectivity reachable with stock firmware is 6to4, because it
otherwise erroneously tries to establish two separate PPP sessions —
one for each protocol.&lt;/p&gt;

&lt;p&gt;Looking downwards, the stack of protocols sent over an Internode ADSL
connection, sometimes known as PPPoEoA for short, is &lt;a href=&quot;https://www.farside.org.uk/200903/ipoeoatm&quot;&gt;rather complex&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Transport layer or application layer payload&lt;/li&gt;
  &lt;li&gt;IPv4 packet header or IPv6 packet header [&lt;a href=&quot;https://tools.ietf.org/html/rfc791&quot;&gt;RFC 791&lt;/a&gt;, &lt;a href=&quot;https://tools.ietf.org/html/rfc2460&quot;&gt;2460&lt;/a&gt;]&lt;/li&gt;
  &lt;li&gt;PPP packet header (2 octets, type 0021&lt;sub&gt;16&lt;/sub&gt; or
0057&lt;sub&gt;16&lt;/sub&gt;) [&lt;a href=&quot;https://tools.ietf.org/html/rfc1661&quot;&gt;RFC 1661&lt;/a&gt;, &lt;a href=&quot;https://tools.ietf.org/html/rfc1332&quot;&gt;1332&lt;/a&gt;, &lt;a href=&quot;https://tools.ietf.org/html/rfc5072&quot;&gt;5072&lt;/a&gt;]&lt;/li&gt;
  &lt;li&gt;PPPoE header (6 octets) [&lt;a href=&quot;https://tools.ietf.org/html/rfc2516&quot;&gt;RFC 2516&lt;/a&gt; § 4]&lt;/li&gt;
  &lt;li&gt;Ethernet II frame (18 octets, EtherType 8863&lt;sub&gt;16&lt;/sub&gt; or
8864&lt;sub&gt;16&lt;/sub&gt;) [&lt;a href=&quot;https://standards.ieee.org/about/get/802/802.3.html&quot;&gt;IEEE 802.3-2012&lt;/a&gt;]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From here below, the ADSL side of the DG834Gv2 continues:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;LLC Encapsulation for Bridged Protocols (10 octets)
[&lt;a href=&quot;https://tools.ietf.org/html/rfc2684&quot;&gt;RFC 2684&lt;/a&gt; § 5.2]:
    &lt;ul&gt;
      &lt;li&gt;IEEE 802 SNAP header (5 octets, OUI 00:80:C2&lt;sub&gt;16&lt;/sub&gt;,
PID 0001&lt;sub&gt;16&lt;/sub&gt;) [&lt;a href=&quot;https://standards.ieee.org/about/get/802/802.html&quot;&gt;IEEE 802&lt;/a&gt;]&lt;/li&gt;
      &lt;li&gt;IEEE 802.2 LLC header (3 octets, AA:AA:03&lt;sub&gt;16&lt;/sub&gt;)
[&lt;a href=&quot;https://standards.ieee.org/about/get/802/802.2.html&quot;&gt;IEEE 802.2&lt;/a&gt;]&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;AAL5 CPCS-PDU trailer (8 octets)
[&lt;a href=&quot;https://tools.ietf.org/html/rfc2684&quot;&gt;RFC 2684&lt;/a&gt; § 4, &lt;a href=&quot;https://www.itu.int/rec/T-REC-I.363.5/en&quot;&gt;ITU-T Rec. I.363.5&lt;/a&gt;]&lt;/li&gt;
  &lt;li&gt;ATM cell header (5 octets)&lt;/li&gt;
  &lt;li&gt;ADSL2+ physical layer [&lt;a href=&quot;https://www.itu.int/rec/T-REC-G.992.5/en&quot;&gt;ITU-T Rec. G.992.5&lt;/a&gt;]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Alternatively, the Ethernet side of the DG834Gv2 continues:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Ethernet packet (8 octets) [&lt;a href=&quot;https://standards.ieee.org/about/get/802/802.3.html&quot;&gt;IEEE 802.3-2012&lt;/a&gt;]&lt;/li&gt;
  &lt;li&gt;Ethernet interpacket gap (12 octets) [&lt;a href=&quot;https://standards.ieee.org/about/get/802/802.3.html&quot;&gt;IEEE 802.3-2012&lt;/a&gt;]&lt;/li&gt;
  &lt;li&gt;100BASE-TX physical layer [&lt;a href=&quot;https://standards.ieee.org/about/get/802/802.3.html&quot;&gt;IEEE 802.3-2012&lt;/a&gt;]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;IPv4 addresses are obtained with the PPP Internet Protocol Control
Protocol [&lt;a href=&quot;https://tools.ietf.org/html/rfc1332&quot;&gt;RFC 1332&lt;/a&gt;]. This includes dynamic and static
addresses, but does not include routed subnets. IPv6 is where the
situation becomes more confusing. The IPv6 Control Protocol
[&lt;a href=&quot;https://tools.ietf.org/html/rfc5072&quot;&gt;RFC 5072&lt;/a&gt;] is only used to negotiate a unique interface
identifier for the client, because PPP is only concerned with
link-local communication between a pair of peers. Even if no globally
routable prefixes are assigned, once this negotiation is complete,
IPv6 traffic to the ISP peer is technically possible, although not very
useful of course.&lt;/p&gt;

&lt;p&gt;After some hours hacking away because the OpenBSD 5.7 manual page for
pppoe(4) was incorrect, I reached this configuration:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;% cat /etc/hostname.pppoe0
!/sbin/ifconfig em0 up
inet 0.0.0.0 255.255.255.255 NONE \
	pppoedev em0 \
	authproto chap \
	authname &apos;azabani@internode.on.net&apos; \
	authkey hunter2
dest 0.0.0.1
inet6 eui64
!/sbin/route add 0.0.0.0/0 -ifp pppoe0 0.0.0.1
!/sbin/route add ::/0 -ifp pppoe0 fe80::

% sh /etc/netstart pppoe0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The two errors were subtle but fatal. Placing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inet6 eui64&lt;/code&gt; before the
PPPoE parameters inadvertently brings the interface up, after which the
parameters can’t be changed. Viable solutions include placing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;down&lt;/code&gt;
just after &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inet6 eui64&lt;/code&gt;, or simply moving &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inet6 eui64&lt;/code&gt; after the
PPPoE parameters. Because of a peculiarity with route(8),
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add default fe80::&lt;/code&gt; doesn’t work either unless the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-inet6&lt;/code&gt; option is
specified, because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.0.0.0/0&lt;/code&gt; is assumed, and incompatible address
families ensue. I’ve since &lt;a href=&quot;https://www.azabani.com/patch/3/message.txt&quot;&gt;sent&lt;/a&gt; a &lt;a href=&quot;https://www.azabani.com/patch/3/patch.txt&quot;&gt;patch&lt;/a&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tech@openbsd.org&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;From here, either or both of two paths can be taken to obtain a globally
routable IPv6 prefix: NDP Router Solicitation [&lt;a href=&quot;https://tools.ietf.org/html/rfc4861&quot;&gt;RFC 4861&lt;/a&gt;] for the
dynamic /64, or DHCPv6 IA_PD [&lt;a href=&quot;https://tools.ietf.org/html/rfc3633&quot;&gt;RFC 3633&lt;/a&gt;] for the static /56.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;% ifconfig pppoe0 inet6 autoconf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;One command is required to start sending Router Solicitation messages,
and ifconfig(8) handles it now that rtsold(8) has been removed. Because
[&lt;a href=&quot;https://tools.ietf.org/html/rfc4861&quot;&gt;RFC 4861&lt;/a&gt;] doesn’t specify whether or not routers are allowed
to send Router Solicitation messages, OpenBSD errs on the side of caution,
and will not send them if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;net.inet6.ip6.forwarding&lt;/code&gt; is enabled.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wide-dhcpv6&lt;/code&gt; package provides dhcp6c(8) for DHCPv6 IA_PD, or
prefix delegation. Note that even though the /56 prefix is static, the
prefix delegation process &lt;a href=&quot;https://jeremy.visser.name/2009/06/why-dynamic-ipv6-subnet-allocations-for-home-users-are-evil/#comment-4808&quot;&gt;must still occur&lt;/a&gt; so that
Internode can update its routing tables, something that I spent half a
day scratching my head at when I tried to cut corners.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;% pkg_add wide-dhcpv6

% cat /etc/rc.d/dhcp6c
#!/bin/sh

daemon=&quot;/usr/local/sbin/dhcp6c&quot;

. /etc/rc.d/rc.subr

rc_reload=NO

rc_cmd $1

% cat /etc/dhcp6c.conf
interface pppoe0 {
	send ia-pd 0;
	send domain-name-servers;
	send rapid-commit;
};

id-assoc pd {
	prefix-interface vether0 {
		sla-id 0;
		sla-len 8;
	};
};

% echo &apos;dhcp6c_flags=pppoe0&apos; | tee -a /etc/rc.conf.local
dhcp6c_flags=pppoe0

% echo &apos;!/etc/rc.d/dhcp6c restart&apos; | tee -a /etc/hostname.pppoe0
!/etc/rc.d/dhcp6c restart

% /etc/rc.d/dhcp6c restart
dhcp6c(ok)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;turning-a-client-into-a-router&quot;&gt;Turning a client into a router&lt;/h2&gt;

&lt;p&gt;Most of the work involved in configuring routing involves pf(4) and
pf.conf(5), but I left that until last. Before that I set the DNS
search domain and resolvers to sane defaults, and enabled packet
forwarding for both IPv4 and IPv6.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;% cat /etc/resolv.conf
search home.daz.cat
nameserver 192.231.203.132
nameserver 192.231.203.3

% cat /etc/sysctl.conf
net.inet.ip.forwarding=1
net.inet6.ip6.forwarding=1

% xargs sysctl &amp;lt; /etc/sysctl.conf
net.inet.ip.forwarding: 0 -&amp;gt; 1
net.inet6.ip6.forwarding: 0 -&amp;gt; 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;From there I moved to DHCP, something that most people are familiar with.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;% cat /etc/dhcpd.conf
subnet 172.19.0.0 netmask 255.255.0.0 {
	range 172.19.2.1 172.19.2.254;
	default-lease-time 3600;
	max-lease-time 604800;
	option routers 172.19.1.1;
	option domain-name-servers 192.231.203.132, 192.231.203.3;
	option domain-name &quot;home.daz.cat&quot;;
}

% echo &apos;dhcpd_flags=vether0&apos; | tee -a /etc/rc.conf.local
dhcpd_flags=vether0

% /etc/rc.d/dhcpd restart
dhcpd(ok)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As for IPv6, rtadvd(8) goes a long way by sending Router Advertisement
messages, propagating the globally routable IPv6 prefix where it’s
available. I’m leaving clients to generate their own interface
identifiers using SLAAC [&lt;a href=&quot;https://tools.ietf.org/html/rfc4862&quot;&gt;RFC 4862&lt;/a&gt;, &lt;a href=&quot;https://tools.ietf.org/html/rfc4941&quot;&gt;4941&lt;/a&gt;] instead of running
a stateful DHCPv6 [&lt;a href=&quot;https://tools.ietf.org/html/rfc3315&quot;&gt;RFC 3315&lt;/a&gt;] server. In its stateless form,
DHCPv6 is only necessary to advertise IPv6 DNS servers, which I’ll get
around to doing eventually.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;% echo &apos;rtadvd_flags=vether0&apos; | tee -a /etc/rc.conf.local
rtadvd_flags=vether0

% /etc/rc.d/rtadvd restart
rtadvd(ok)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;pf.conf(5) is where I spent a few full days of my time. I paid careful
attention to RFCs when I was deciding which address blocks to drop
traffic from [&lt;a href=&quot;https://tools.ietf.org/html/rfc6890&quot;&gt;RFC 6890&lt;/a&gt;] and which ICMPv6 message types and
codes to allow through the router [&lt;a href=&quot;https://tools.ietf.org/html/rfc4890&quot;&gt;RFC 4890&lt;/a&gt;].&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;### ~~~ Interface layout ~~~ ###

# em0: 802.3ab to ADSL modem (172.19.0.1/16)
# em1: 802.3ab to internal switch
# ral0: 802.11g in hostap mode
# vether0: persists addresses 172.19.1.1/16 and 2001:44b8:6116:1c00::eui64/64
# bridge0: Ethernet bridge over all of the above
# pflog0: target interface for blocked packets
# pppoe0: PPPoE session over em0

### ~~~ Constants and variables ~~~ ###

# All addresses associated with this host
self = &quot;{ (egress), (vether0) }&quot;

# RFC 6890: Special-Purpose IP Address Registries:
# https://www.iana.org/assignments/iana-ipv4-special-registry/
# https://www.iana.org/assignments/iana-ipv6-special-registry/

# Included below are all address blocks with either Forwardable = False,
# Global = False, or both, but excluding 2001::/23 because it is often
# superseded by more specific allocations, as of 2015-08-05.

table &amp;lt;martians&amp;gt; const { \
	0.0.0.0/8, \
	10.0.0.0/8, \
	100.64.0.0/10, \
	127.0.0.0/8, \
	169.254.0.0/16, \
	172.16.0.0/12, \
	192.0.0.0/24, \
	192.0.2.0/24, \
	192.168.0.0/16, \
	198.18.0.0/15, \
	198.51.100.0/24, \
	203.0.113.0/24, \
	240.0.0.0/4, \
	255.255.255.255/32, \
	::1/128, \
	::/128, \
	::ffff:0:0/96, \
	100::/64, \
	2001::/32, \
	2001:2::/48, \
	2001:db8::/32, \
	fc00::/7, \
	fe80::/10 \
}

### ~~~ Default rules ~~~ ###

# Never touch loopback interfaces
set skip on lo

# Normalise packets, especially IPv4 DF and Identification
match in all scrub (no-df random-id)

# Limit the MSS on PPPoE to 1440 octets
match on pppoe0 scrub (max-mss 1440)

# Block all packets by default, logging them to pflog0
block log

### ~~~ Link-scoped services ~~~ ###

# DHCPv6 client: make IA_PD requests and receive responses to them
pass out quick on egress inet6 proto udp from (egress) to ff02::1:2 port dhcpv6-server
pass in quick on egress inet6 proto udp to (egress) port dhcpv6-client

### ~~~ Bulk pass rules ~~~ ###

# Pass all traffic on internal interfaces
# vether0 is necessary here, but bridge0 is not
pass quick on { vether0 em0 em1 ral0 }

# Pass all outbound IPv6 traffic
pass out quick on egress inet6 from { egress, (vether0:network) } modulate state

# Pass all outbound IPv4 traffic from this host
pass out quick on egress inet from egress modulate state

# NAT all outbound IPv4 traffic from the rest of our network
pass out quick on egress inet from (vether0:network) nat-to (egress) modulate state

### ~~~ Block undesirable traffic ~~~ ###

# These rules must not precede the DHCPv6 client or NAT rules above
block log quick on egress from { no-route, urpf-failed, &amp;lt;martians&amp;gt; }
block log quick on egress to { no-route, &amp;lt;martians&amp;gt; }

### ~~~ Pass some ICMP and ICMPv6 traffic ~~~ ####

# Pass all inbound ICMP echo requests
pass in quick on egress inet proto icmp icmp-type echoreq

# RFC 4890: Recommendations for Filtering ICMPv6 Messages in Firewalls
pass quick on egress inet6 proto icmp6 icmp6-type { 1, 2, 128, 129 }
pass quick on egress inet6 proto icmp6 icmp6-type 3 code 0
pass quick on egress inet6 proto icmp6 icmp6-type 3 code 1
pass quick on egress inet6 proto icmp6 icmp6-type 4 code 0
pass quick on egress inet6 proto icmp6 icmp6-type 4 code 1
pass quick on egress inet6 proto icmp6 icmp6-type 4 code 2

### ~~~ Open services on this router ~~~ ###

# OpenSSH server
pass in on egress proto { tcp, udp } to $self port ssh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;watch-this-space&quot;&gt;Watch this space!&lt;/h2&gt;

&lt;p&gt;Future tasks which I’m yet to complete include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Configuring ntpd(8)&lt;/li&gt;
  &lt;li&gt;Advertising IPv6 DNS servers with dhcp6s(8)&lt;/li&gt;
  &lt;li&gt;Hosting an authoritative DNS zone for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;home.daz.cat&lt;/code&gt; with nsd(8)&lt;/li&gt;
  &lt;li&gt;Caching DNS queries with unbound(8)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find the &lt;a href=&quot;https://bitbucket.org/delan/daria.daz.cat&quot;&gt;root overlay&lt;/a&gt; for my router on Bitbucket.&lt;/p&gt;</content><author><name></name></author><category term="home" /><category term="openbsd" /><summary type="html">It’s no secret that most consumer routers ship with software that’s flaky at best, and prohibitively insecure at worst. While I’ve had good experiences with OpenWrt and pfSense, I wanted to build a router from the ground up, both to understand the stack and to have something to tinker with. I found many solid tutorials out there, but few of them covered the intricacies of both PPP and IPv6. Here’s what I’ve learned.</summary></entry><entry><title type="html">On gender identity</title><link href="https://www.azabani.com/2015/04/06/on-gender-identity.html" rel="alternate" type="text/html" title="On gender identity" /><published>2015-04-06T11:00:00+00:00</published><updated>2015-04-06T11:00:00+00:00</updated><id>https://www.azabani.com/2015/04/06/on-gender-identity</id><content type="html" xml:base="https://www.azabani.com/2015/04/06/on-gender-identity.html">&lt;p&gt;Human gender and sexuality are immensely complex concepts due to our
complexity as a species. Despite this, it’s quite simple to handle
gender identity in an everyday setting. You can initially guess
someone’s gender, because you’ll probably be correct most of the time.
Otherwise, accept any corrections or simply ask them how they wish to
be referred, and respect their identity. If you deliberately misgender
a person because they don’t pass your personal threshold of physical
characteristics to be worthy of their gender, you’re not just being
rude, but you’re also espousing an excessively simplified and
internally inconsistent conceptual understanding of gender.&lt;/p&gt;

&lt;p&gt;Perhaps the most pernicious idea which comes from this is that where a
transgender person is not genuinely a member of the gender with which
they identify until they undergo sex reassignment surgery. It’s petty
because there are virtually no valid reasons to care about someone’s
genitalia outside of a sexual, reproductive, or medical context. The
procedure or lack thereof should make no difference to how a person
will interact with most other people. Some transgender people may not
be able to undergo this surgery due to medical, financial, or other
constraints, while others may not even want to do so at all. This may
be for any reason, but the decision is ultimately a personal one.&lt;/p&gt;

&lt;p&gt;This reasoning extends to other elements of gender transitioning, such
as hormone replacement therapy, changes to physical or social
expressions, adherence to any number of gender roles, sexual
orientation or behaviour, and the use of pronouns. Each of these is an
optional personal choice for the individual involved, and any or all of
them may be subject to financial, social, legal, or other limitations.
Using a checklist of these elements to determine whether or not a
person deserves to openly identify as their gender is as absurd as
using their physical characteristics to determine their gender, a
social construct which is purely psychological in nature.&lt;/p&gt;

&lt;p&gt;Let’s take a look, by example, at why refusing to refer to someone
using the gender with which they identify is indicative of a flawed
understanding of gender. What of a biological female who identifies as
a woman, but expresses many masculine characteristics due to choice,
heredity, or otherwise? What of her inverse, where a man who is born
male is simply very androgynous or even feminine? What of her converse
or her contrapositive, where a transgender person undergoes few or no
elements of transitioning, resulting in a gender identity which remains
different to their outward expressions of gender? What of an intersex,
agender, or genderqueer person? How about a person who identifies with
a gender which may be prevalent in their culture, but does not fall
within the typical gender binary or other framework of yours?&lt;/p&gt;

&lt;p&gt;The intersection of biological sex with our set of diverse and
intricate cultures makes any perceived ability to compartmentalise
people into neat little boxes impossible. Given our position as a
sapient, introspective, and in some ways advanced species, a personal
ideal of mine would be to see more humans and their cultures transcend
the existence of the concept of gender altogether, while simultaneously
relegating biological sex to cease its influence beyond its own
inherent purview, that is to say, the sexual and reproductive.&lt;/p&gt;

&lt;p&gt;Until then we’re mostly stuck with the gender categories that are
prevalent in our local cultures, but nobody should have to do anything
or look a certain way to be qualified to identify with their gender of
choice. You may not have an interest in gender identity, and that’s
fine, but if you respect someone, let alone care about them, be sure to
refer to them in alignment with their wishes — it’s truly that simple.&lt;/p&gt;</content><author><name></name></author><category term="home" /><summary type="html">Human gender and sexuality are immensely complex concepts due to our complexity as a species. Despite this, it’s quite simple to handle gender identity in an everyday setting. You can initially guess someone’s gender, because you’ll probably be correct most of the time. Otherwise, accept any corrections or simply ask them how they wish to be referred, and respect their identity. If you deliberately misgender a person because they don’t pass your personal threshold of physical characteristics to be worthy of their gender, you’re not just being rude, but you’re also espousing an excessively simplified and internally inconsistent conceptual understanding of gender.</summary></entry><entry><title type="html">The Curtin Courses Handbook</title><link href="https://www.azabani.com/2014/11/14/curtin-courses-handbook.html" rel="alternate" type="text/html" title="The Curtin Courses Handbook" /><published>2014-11-14T11:00:00+00:00</published><updated>2014-11-14T11:00:00+00:00</updated><id>https://www.azabani.com/2014/11/14/curtin-courses-handbook</id><content type="html" xml:base="https://www.azabani.com/2014/11/14/curtin-courses-handbook.html">&lt;p&gt;Almost every university has a handbook which should serve as an authoritative
source regarding which units a student needs to complete to be able to
graduate. If you are a computing student at Curtin University, this is not the
case.&lt;/p&gt;

&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;

&lt;p&gt;Be forewarned that this is a huge tangential rant.&lt;/p&gt;

&lt;p&gt;2013 arrived with a significant refactoring of courses at Curtin University.
Many (but not all) science, commerce and engineering courses were combined into
courses representing each faculty &lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. Each of the old courses became majors
in the new system, and what were previously majors are now known as ‘streams’.&lt;/p&gt;

&lt;p&gt;Because none of the content had actually changed, only three ends were achieved
by this process. Students would find it more difficult to search for a relevant
degree with &lt;a href=&quot;http://tisc.edu.au/&quot;&gt;TISC&lt;/a&gt; and the structure of courses was made
significantly more confusing, but at least Curtin’s perceived student counts
for each ‘course’ would increase markedly.&lt;/p&gt;

&lt;p&gt;Students enrolling in 2015 will be greeted with a mass renaming of all of their
courses and units, purely because StudentOne, the software used to manage
enrolments, was sold to a university in the eastern states of Australia. To
reduce their software maintenance costs, the software’s new owners have
mandated their own unit and course naming schema.&lt;/p&gt;

&lt;p&gt;The three defining characteristics of Curtin’s implementation of this new
system are more prominent unique ‘unit codes’ like COMP3001, the lack of
numbers after unit names, and unit names which are unnecessarily and
independently unique. For instance, ‘Design and Analysis of Algorithms 300’
becomes COMP3001 ‘Design and Analysis of Algorithms’.&lt;/p&gt;

&lt;p&gt;These rules are followed religiously except when they’re not followed at all.
The unimaginative bureaucrats at Curtin couldn’t reconcile the new schema with
pairs of units that run across a whole year, like ‘Software Engineering Project
320/330/401/402’, which became ISAD3000/3001/4000/4001 ‘Software Engineering
Project 1/2/A/B’.&lt;/p&gt;

&lt;p&gt;On the other hand, if you’re a student learning the Chinese language, what were
logically named ‘Chinese 111/112/211/212/311/312/321/322’ are now the following
pointless behemoth of slightly varied unit names:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;CHIN1000 (v.1) Beginning Chinese&lt;/li&gt;
  &lt;li&gt;CHIN1001 (v.1) Beginning Chinese Developing&lt;/li&gt;
  &lt;li&gt;CHIN2001 (v.1) Pre-Intermediate Chinese&lt;/li&gt;
  &lt;li&gt;CHIN2002 (v.1) Pre-Intermediate Chinese Developing&lt;/li&gt;
  &lt;li&gt;CHIN3000 (v.1) Intermediate Chinese&lt;/li&gt;
  &lt;li&gt;CHIN3001 (v.1) Intermediate Chinese Developing&lt;/li&gt;
  &lt;li&gt;CHIN3002 (v.1) Advanced Chinese&lt;/li&gt;
  &lt;li&gt;CHIN3003 (v.1) Advanced Chinese Developing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I think I’ve made my point. The powers that be often do very silly things.&lt;/p&gt;

&lt;h2 id=&quot;whats-wrong-with-the-handbook&quot;&gt;What’s wrong with the handbook?&lt;/h2&gt;

&lt;p&gt;This is a whole new kettle of fish.&lt;/p&gt;

&lt;p&gt;Luke is a student &lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; who wants to start studying computing in 2015. Luke
needs to enrol in four distinct but overlapping entities:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://handbook.curtin.edu.au/courses/31/319373.html&quot;&gt;B-SCNCE v.1 Bachelor of Science (Science)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://handbook.curtin.edu.au/courses/31/318932.html&quot;&gt;STRU-CMPPM v.1 Computing Pre-major (BSc Science)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://handbook.curtin.edu.au/courses/31/318935.html&quot;&gt;MJRU-COMPT v.1 Computing Major (BSc Science)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://handbook.curtin.edu.au/courses/31/318938.html&quot;&gt;STRU-CMPSC v.1 Computer Science Stream (BSc Science)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank the Department of Computing for fighting to ensure that degrees are
awarded ‘Bachelor of Science (Computing)’ because this is Kafkaesque. While the
pre-major is surprisingly correct, the major and stream, which constitute the
remainder of the course, are where it all goes downhill. Let’s wear the shoes
of Luke’s frustration.&lt;/p&gt;

&lt;p&gt;Luke is scheduled to enrol in COMP2003 during year 2 semester 1.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/handbook2.png&quot; alt=&quot;COMP2003 being displayed under year 2 semester 1&quot; /&gt;&lt;/p&gt;

&lt;p&gt;What Luke doesn’t realise is that COMP2003 &lt;a href=&quot;http://handbook.curtin.edu.au/units/31/315540.html&quot;&gt;only ever runs in semester
2&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/handbook3.png&quot; alt=&quot;COMP2003 not running in semester 1&quot; /&gt;&lt;/p&gt;

&lt;p&gt;For Luke’s third year, the handbook requires enrolment in COMP3004 and
COMP3005:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/handbook4.png&quot; alt=&quot;COMP3004 and COMP3005 listed under year 3&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Doing so would be impossible as the unit doesn’t exist except in Sri Lanka:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/handbook5.png&quot; alt=&quot;COMP3004 and COMP3005 being available in Sri Lanka&quot; /&gt;&lt;/p&gt;

&lt;p&gt;What does exist are ISAD3000 and ISAD3001, which are incorrectly listed as
&lt;a href=&quot;http://handbook.curtin.edu.au/courses/31/318922.html&quot;&gt;being specific&lt;/a&gt; to the Software Engineering Stream, even though
everyone in the Computing Major must complete them to graduate:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/handbook6.png&quot; alt=&quot;ISAD3000 and ISAD3001 shown in Software Engineering&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If Luke were to follow the handbook and StudentOne when enrolling in units
every semester, I daresay that he won’t even be cleared to graduate after three
years of hard work, especially given the misinformation that he must choose
between COMP3001 and a final year project, when both are actually mandatory.&lt;/p&gt;

&lt;h2 id=&quot;who-do-i-trust&quot;&gt;Who do I trust?&lt;/h2&gt;

&lt;p&gt;In a hypothetical world where Department of Computing staff don’t work
tirelessly to mitigate the enrolment problems of egregiously misled students,
it’d be a wonder that students manage to even graduate with a computing degree
from Curtin University.&lt;/p&gt;

&lt;p&gt;If you want an authoritative, correct indication of your course structure, pick
up a leaflet from the table outside David Cooper’s office at 314.343, just
opposite the reception window for the School of Electrical Engineering and
Computing.&lt;/p&gt;

&lt;h2 id=&quot;pedantry&quot;&gt;Pedantry&lt;/h2&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;The hierarchy of learning areas is faculty, then school, then
department, but faculty is not quite the correct term here, because
there is a &lt;a href=&quot;http://scieng.curtin.edu.au/&quot;&gt;Faculty of Science and Engineering&lt;/a&gt; and the
&lt;a href=&quot;http://business.curtin.edu.au/about/index.cfm&quot;&gt;Curtin Business School&lt;/a&gt; (a faculty) which awards degrees
other than commerce. ‘Faculty’ is too big, while ‘school’ is too small. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Luke is hypothetical of course, but I know at least five people with
this name who have done computing at Curtin, so Luke is probably a good
choice. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name></name></author><category term="home" /><summary type="html">Almost every university has a handbook which should serve as an authoritative source regarding which units a student needs to complete to be able to graduate. If you are a computing student at Curtin University, this is not the case.</summary></entry><entry><title type="html">First experiences with OpenBSD</title><link href="https://www.azabani.com/2014/08/09/first-experiences-with-openbsd.html" rel="alternate" type="text/html" title="First experiences with OpenBSD" /><published>2014-08-09T08:00:00+00:00</published><updated>2014-08-09T08:00:00+00:00</updated><id>https://www.azabani.com/2014/08/09/first-experiences-with-openbsd</id><content type="html" xml:base="https://www.azabani.com/2014/08/09/first-experiences-with-openbsd.html">&lt;p&gt;Tired of the sluggishness of Windows on my laptop and interested in
experimenting with a Unix-like that I haven&apos;t tried before, I gave
OpenBSD a second shot after a &lt;a
href=&quot;http://comments.gmane.org/gmane.os.openbsd.misc/193097&quot;&gt;brief stint&lt;/a&gt;
on my netbook a couple of years back. Coming from exclusively using Gentoo,
this seemed like a natural choice due to its minimalism and the shared roots
between ports and portage. Reading &lt;a
href=&quot;https://www.over-yonder.net/~fullermd/rants/bsd4linux/01&quot;&gt;Matthew D.
Fuller&apos;s comparison&lt;/a&gt; of BSD and Linux inspired me to venture beyond the
familiar walls of the Linux world.

&lt;p&gt;It would be prudent to start with a hint of background. I first discovered
Linux via a cover disc on the APC magazine which included a live image for
Mandriva Linux 2006.0. Until then, I had been using Windows XP, Windows 98
and Windows 95 in reverse order. While I dabbled with it, the concept of
actually installing something that wasn&apos;t Windows on my computer never crossed
my mind.

&lt;p&gt;In 2009, I installed Ubuntu 9.04 cold turkey and continued using it for
nearly two years, at which point I distro-hopped to Debian. For the entirety
of 2012, I used Gentoo and I even had a small distcc cluster, but I eventually
became annoyed at the poor quality of Linux userland software. From then until
now, I had returned to Windows 7 with a sometimes painful drop of Cygwin where
I needed a Unixish environment.

&lt;h2&gt;Creating an installation medium&lt;/h2&gt;

&lt;p&gt;I&apos;m currently using an &lt;a
href=&quot;http://www.asus.com/Notebooks_Ultrabooks/U31SD/&quot;&gt;Asus U31SD&lt;/a&gt; which has
no optical disc drive, calling for the use of a USB flash drive. I wanted to
partition my large flash drive such that I could use it simultaneously as
installation media and a backup destination. This was not trivial to configure
in a way compatible with Windows.

&lt;p&gt;Windows makes an arbitrary, silly distinction whereby it tries very hard to
avoid partitioning &apos;removable disks&apos;, a category which includes USB flash
drives, but not USB portable hard drives. Using diskmgmt.msc, diskpart or any
stock GUI tool will create a partition table if the disk has no recognisable
volume or partition table, but will only allow one partition to be created.
Behold the idiocy:

&lt;pre&gt;
DISKPART&gt; clean

DiskPart succeeded in cleaning the disk.

DISKPART&gt; convert mbr

DiskPart successfully converted the selected disk to MBR format.

DISKPART&gt; create partition primary size=4096

DiskPart succeeded in creating the specified partition.

DISKPART&gt; create partition primary size=4096

No usable free extent could be found. It may be that there is insufficient
free space to create a partition at the specified size and offset. Specify
different size and offset values or don&apos;t specify either to create the
maximum sized partition. It may be that the disk is partitioned using the MBR disk
partitioning format and the disk contains either 4 primary partitions, (no
more partitions may be created), or 3 primary partitions and one extended
partition, (only logical drives may be created).

DISKPART&gt; clean

DiskPart succeeded in cleaning the disk.

DISKPART&gt; convert gpt

DiskPart successfully converted the selected disk to GPT format.

DISKPART&gt; create partition primary size=4096

DiskPart succeeded in creating the specified partition.

DISKPART&gt; create partition primary size=4096

Virtual Disk Service error:
The operation is not supported on a non-empty removable disk.
&lt;/pre&gt;

&lt;p&gt;If the disk has a direct volume with no partition table, Windows will roll
with that and pretend that there is one partition. If multiple partitions are
created on a disk elsewhere, its behaviour on Windows appears to be
unpredictable, but it looks like a drive letter can be assigned to the first
partition entry in the MBR, depending on the phase of the moon.

&lt;p&gt;There is allegedly a &apos;removable media bit&apos; in some drive controllers which
can be flipped by a tool for Lexar drives called BootIt. Sadly it appeared to
have no effect on my 64 GB Corsair drive. I tried creating many partition
configurations using OpenBSD&apos;s own fdisk in a virtual machine, as well as GNU
fdisk and parted on Debian. None of them played nicely with Windows. All I
wanted was an NTFS or FAT32 partition plus a BSD disklabel.

&lt;p&gt;What did work was dumping the contents of &lt;code&gt;install55.fs&lt;/code&gt; or
&lt;code&gt;miniroot55.fs&lt;/code&gt; onto the entire drive, then adding another partition
on entry 0 from there. These files contain not only a BSD disklabel with one
root FFS, but ostensibly also an MBR surrounding that.

&lt;pre&gt;
# ftp ftp://ftp.ii.net/pub/OpenBSD/5.5/amd64/install55.fs
# dd if=install55.fs of=/dev/rsd0c bs=65536
# echo &apos;e 0\n7\nn\n491520\n123043840\nw\np\nq&apos; | fdisk -e sd0 | sed &apos;:a;$q;N;6,$D;ba&apos;
 0: 07     30 151  58 -   7689 183  46 [      491520:   123043840 ] NTFS
 1: 00      0   0   0 -      0   0   0 [           0:           0 ] unused
 2: 00      0   0   0 -      0   0   0 [           0:           0 ] unused
 3: A6      0   1   2 -     30 151  57 [          64:      491456 ] OpenBSD
fdisk: 1&gt; #
# echo &apos;SCSI\nA\nw\np\nq&apos; | disklabel -E sd0 | sed &apos;:a;$q;N;5,$D;ba&apos;
  a:           491456               64  4.2BSD   2048 16384    1 # /
  c:        123535360                0  unused
  i:        123043840           491520    NTFS
&gt; No label changes.
&lt;/pre&gt;

&lt;p&gt;It&apos;s perhaps here that I should mention that I found OpenBSD&apos;s distinction
between block devices (for disks, partitions and slices) and their
corresponding raw character device files quite bizarre. I suppose I just need
to remember to prepend &lt;code&gt;r&lt;/code&gt; to any administrative task pertaining to
partitioning or filesystem creation.

&lt;h2&gt;The installation process proper&lt;/h2&gt;

&lt;p&gt;This was actually relatively straightforward, save for the wireless NIC
&lt;code&gt;iwn0&lt;/code&gt; not working until firmware files are automatically downloaded
after installation. Using the wired &lt;code&gt;alc0&lt;/code&gt; gigabit Ethernet
interface temporarily did the trick, but networking was not even necessary at
this stage, given that I had used the full &lt;code&gt;install55.fs&lt;/code&gt; image.

&lt;pre&gt;
iwn0: error, 2, could not read firmware iwn-6030
iwn0: could not read firmware
iwn0: no link ............. sleeping
&lt;/pre&gt;

&lt;p&gt;First boot was problematic, as the kernel hung on something ACPI:

&lt;pre&gt;
...
cpu3: smt 1, core 1, package 0
ioapic0 at mainbus0: apid 2 pa 0xfec00000, version 20, 24 pins
acpiec0 at acpi0
acpihpet0 at acpi0: 14318179 Hz
acpimcfg0 at acpi0 addr 0xe0000000, bus 0-63
&lt;/pre&gt;

&lt;p&gt;A quick and dirty solution I&apos;m using right now is to disable it:

&lt;pre&gt;
boot&gt; -c
UKC&gt; disable acpi
352 acpi0 disabled
UKC&gt; quit
&lt;/pre&gt;

&lt;p&gt;At this point I had to start over because I foolishly elected to start
&lt;code&gt;xdm(1)&lt;/code&gt; on boot, leaving me unable to use the keyboard or mouse.
Choosing the right option, I am now greeted with an odd monospaced serif
console. I then disabled ACPI permanently:

&lt;pre&gt;
# cp /bsd{,.old}
# echo quit | config -efu /bsd
&lt;/pre&gt;

&lt;p&gt;I missed the automatic firmware installation on first boot, so I had to
invoke the process manually:

&lt;pre&gt;
# echo dhcp &gt; /etc/hostname.alc0
# . /etc/netstart alc0
# fw_update
&lt;/pre&gt;

&lt;p&gt;Configuring a connection to my wireless network revealed the elegant nature
of &lt;code&gt;hostname.if(5)&lt;/code&gt;, which strongly reminded me of the file format
used with &lt;code&gt;iptables-{save,restore}(8)&lt;/code&gt;. It looks like connecting to
Curtin University&apos;s network might need &lt;code&gt;wpa_supplicant(8)&lt;/code&gt;, however.

&lt;pre&gt;
# echo &apos;nwid deLAN\nwpakey REDACTED\ndhcp\nrtsol&apos; &gt; /etc/hostname.iwn0
# . /etc/netstart iwn0
&lt;/pre&gt;

&lt;p&gt;Some &lt;code&gt;sysctl&lt;/code&gt; values prevent global IPv6 address configuration
from working automatically, but after my surprise upon seeing the default
values, they were quickly fixed:

&lt;pre&gt;
# ifconfig iwn0 | grep inet6
        inet6 fe80::b6b6:76ff:fe1e:7bfc%iwn0 prefixlen 64 scopeid 0x1
# rtsol iwn0
rtsol: kernel is configured not to accept RAs
rtsol: kernel is configured not to accept redirects
# sed -E &apos;s/^#(net.inet6.(icmp6.rediraccept|ip6.accept_rtadv)=)/\1/&apos; /etc/sysctl.conf &gt; /etc/sysctl.conf.
# mv /etc/sysctl.conf{.,}
# sysctl net.inet6.icmp6.rediraccept=1
# sysctl net.inet6.ip6.accept_rtadv=1
# rtsol iwn0
# ifconfig iwn0 | grep inet6
        inet6 fe80::b6b6:76ff:fe1e:7bfc%iwn0 prefixlen 64 scopeid 0x1
        inet6 2001:44b8:6116:1c00:b6b6:76ff:fe1e:7bfc prefixlen 64 autoconf pltime 3593 vltime 7194
        inet6 2001:44b8:6116:1c00:4487:8545:2941:c33c prefixlen 64 autoconf autoconfprivacy pltime 3593
vltime 7194
&lt;/pre&gt;

&lt;h2&gt;Getting X11 working&lt;/h2&gt;

&lt;p&gt;Upon trying to run &lt;code&gt;Xorg -configure&lt;/code&gt;, it turns out that the
problems I had been having with X11 seem to stem from the &lt;code&gt;pms&lt;/code&gt;
driver which is used for the Synaptics touchpad. For now, I&apos;ll simply disable
the driver and use an external USB mouse as a pointing device.

&lt;pre&gt;
boot&gt; -c
UKC&gt; disable pms
259 pms* disabled
UKC&gt; quit
# echo quit | config -efu /bsd
&lt;/pre&gt;

&lt;h2&gt;Finishing user configuration&lt;/h2&gt;

&lt;p&gt;All that&apos;s left is to download my dotfiles and install some packages.

&lt;pre&gt;
# sed -E &apos;s/^# (%wheel.+\) SETENV.+)/\1/&apos; /etc/sudoers &gt; /etc/sudoers.
# mv /etc/sudoers{.,}
# chmod 440 /etc/sudoers
$ export PKG_PATH=ftp://ftp.ii.net/pub/OpenBSD/5.5/packages/amd64/
$ sudo pkg_add -v zsh vim curl git irssi gnupg ...
$ git clone --bare https://github.com/delan/config .git
$ sed -E &apos;s/(bare = )true/\1false/&apos; .git/config &gt; .git/config.
$ mv .git/config{.,}
$ git checkout HEAD
$ chsh -s zsh
$ exec zsh
neptune%
&lt;/pre&gt;

&lt;h2&gt;Miscellaneous thoughts&lt;/h2&gt;

&lt;p&gt;I very much appreciated the clean separation between the base system, the
packages and the ports tree, and grew to rely on having an eloquent manual page
for every command, driver and configuration file in the base system.

&lt;p&gt;I don&apos;t know how I have managed to achieve this, but I can use vim key
bindings in GUI applications, including those that use GTK+. Perhaps it is
influenced by running &lt;code&gt;bindkey -v&lt;/code&gt; in my &lt;code&gt;.zshrc&lt;/code&gt;.

&lt;p&gt;&lt;a href=&quot;http://openports.se/&quot;&gt;OpenPorts.se&lt;/a&gt; was very useful for when I
couldn&apos;t recall the exact name of a package I was looking for. In the long run
however, it was much better to fetch and configure the ports tree locally as
recommended. This also buys one the ability to cleanly configure and compile
software from source, which is especially useful for &lt;code&gt;dwm&lt;/code&gt;, for
example.

&lt;p&gt;Using &lt;code&gt;pkg_info -t&lt;/code&gt; is a useful way of finding packages that one
has explicitly installed so far:

&lt;pre&gt;
neptune% pkg_info -t         
ImageMagick-6.7.7.7p6 image processing tools
chromium-32.0.1700.102-proprietary Chromium browser
dwm-6.0             dynamic window manager
git-1.8.5.3         GIT - Tree History Storage Tool
gnupg-2.0.22p0      gnu privacy guard - a free PGP replacement
irssi-0.8.15p5      modular IRC client with many features (ipv6,socks,proxy)
iwn-firmware-5.10p0 firmware binary images for iwn(4) driver
p7zip-9.20.1p0      file archiver with high compression ratio
povray-3.6.1p5      3D image rendering package
scrot-0.8p3         commandline screen capture util
texlive_texmf-full-2013 texlive texmf for extra macros
uvideo-firmware-1.2p1 firmware binary images for uvideo(4) driver
vim-7.4.135p0-no_x11 vi clone, many additional features
zip-3.0             create/update ZIP files compatible with PKZip(tm)
zsh-5.0.2           Z shell, Bourne shell-compatible
&lt;/pre&gt;

&lt;p&gt;In broad strokes, the tasks I have yet to complete are:

&lt;ul&gt;
	&lt;li&gt;Find out why ACPI causes the kernel to hang, and fix that;
	&lt;li&gt;Find out why &lt;code&gt;pms(4)&lt;/code&gt; with my touchpad breaks X11;
	&lt;li&gt;Create ports for software I miss such as the Proggy fonts; and
	&lt;li&gt;Create ports for software that I&apos;ve written, after cleaning up the code.
&lt;/ul&gt;</content><author><name></name></author><category term="home" /><category term="openbsd" /><summary type="html">Tired of the sluggishness of Windows on my laptop and interested in experimenting with a Unix-like that I haven&apos;t tried before, I gave OpenBSD a second shot after a brief stint on my netbook a couple of years back. Coming from exclusively using Gentoo, this seemed like a natural choice due to its minimalism and the shared roots between ports and portage. Reading Matthew D. Fuller&apos;s comparison of BSD and Linux inspired me to venture beyond the familiar walls of the Linux world.</summary></entry><entry><title type="html">Writing an IRC sedbot</title><link href="https://www.azabani.com/2014/02/08/writing-irc-sedbot.html" rel="alternate" type="text/html" title="Writing an IRC sedbot" /><published>2014-02-08T14:30:00+00:00</published><updated>2014-02-08T14:30:00+00:00</updated><id>https://www.azabani.com/2014/02/08/writing-irc-sedbot</id><content type="html" xml:base="https://www.azabani.com/2014/02/08/writing-irc-sedbot.html">&lt;p&gt;The ComSSA IRC has an official utility bot, &lt;a href=
&quot;https://github.com/ComSSA/KhlavKalash&quot;&gt;KhlavKalash&lt;/a&gt;, which currently does
some trivia like URL title fetching and server uptime. It uses Twisted for IRC
and has a good plugin system with Yapsy. Feeling a bit bored, I decided to try
my hand at implementing what I call a &quot;sedbot&quot;, which interprets messages that
are &lt;code&gt;sed&lt;/code&gt; replacement expressions, executing the replacement on the
last normal message.

&lt;p&gt;As I&apos;m writing this, KhlavKalash allows you to create plugins by simply
dropping Python files into a subdirectory with your own classes, each of which
represents essentially a simple bot. The current interface is limited to
callbacks on normal messages matching supplied regular expressions, and allows
you to return zero or one response message to be sent back. This could change
to become more flexible in the future.

&lt;p&gt;Initially I planned to make replacements operate on the requesting user&apos;s
last message, as opposed to the last message from anyone in the channel. This
would currently not be possible with the KhlavKalash API because requesting
nicks are not passed to the plugins. Thankfully, I discovered that most sedbots
operate on the last message in general anyway, as it allows users to &quot;correct&quot;
messages from other people, often for snarky effect.

&lt;p&gt;The most difficult, or rather, verbose, part of the implementation was
actually parsing the sed expressions. Not the regular expressions themselves,
but simply splitting &lt;code&gt;s/a/b/f&lt;/code&gt; into &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;b&lt;/code&gt;
and &lt;code&gt;f&lt;/code&gt; — you can&apos;t simply split the strong on &lt;code&gt;/&lt;/code&gt;
because patterns and replacements can have slashes in them via backslash
escapes. I wrote a simple finite state machine to parse the components of sed
expressions, and about 100 lines later, I was in business.

&lt;p&gt;There were a couple of things I couldn&apos;t implement in an obvious way. The
&lt;code&gt;regex&lt;/code&gt; module is much better than &lt;code&gt;re&lt;/code&gt; but uses the same
API. You can use the &lt;code&gt;count&lt;/code&gt; argument to implement the global
replacement flag, and the &lt;code&gt;regex.IGNORECASE&lt;/code&gt; bit in &lt;code
&gt;flags&lt;/code&gt; to implement &lt;code&gt;/i&lt;/code&gt;, but there isn&apos;t an easy way to
choose which occurrence to start replacing from, which is provided by &lt;code
&gt;sed&lt;/code&gt; via a numeric flag, e.g. &lt;code&gt;s/a/b/42&lt;/code&gt;.

&lt;p&gt;&lt;code&gt;sed&lt;/code&gt; allows you to specify multiple expressions by separating
them with semicolons. This is unambiguous, because the last component of a
sed expression are the flags, and a semicolon is not a valid flag. Some simple
additions to my parser&apos;s two flags states and support for this was implemented.
Unfortunately, this comes at the expense of the ability to allow the omission
of the final slash, something I hadn&apos;t yet done but was requested by a few
users, because a semicolon in the replacement section would be ambiguous.

&lt;p&gt;I tackled Unicode support by treating incoming byte strings as UTF-8, and
returning &lt;code&gt;unicode&lt;/code&gt; strings back to the core module. An interesting
thing to note is that the IRC protocol has no concept of text encoding at all;
it only sees bytes, leaving it up to clients to decide how to interpret them.
Ideally all clients would use UTF-8, but this is not always the case.
Regardless, the bot assumes UTF-8 everywhere, in the hope that anyone using
non-ASCII characters is using UTF-8, but there really is little other choice.

&lt;p&gt;Formal grammars are apparently covered in Theoretical Foundations of
Computer Science 300, which I&apos;m not due to study until some time next year.
Having written a couple of simple finite state machine parsers in the past as
well as today, I was keen to see how it fit into the grand scheme of things.

&lt;p&gt;I started looking for more information after writing the bot, and I found
out that the very fact that I used a state machine indicates that the language
of sed expressions (if you are not parsing the regular expressions inside) is a
&lt;i&gt;regular language&lt;/i&gt;, which is a subset of the &lt;i&gt;context-free languages&lt;/i&gt;
that can be parsed by, you guessed it, regular expressions.

&lt;p&gt;Accepting the implicit challenge that the realisation yielded, I managed to
create a regular expression that can be used to split a sed replacement
expression into its three parts. I decided against switching to it, as the
old parser is probably more readable and extensible, but it&apos;s worth sharing.
Imagine these two lines as one:

&lt;pre&gt;
^(?:s/((?:[^\\/]|\\.)*)/((?:[^\\/]|\\.)*)/((?:g|i|\d+)*))(?:;s/((?:[^\\/]|\\.)*
)/((?:[^\\/]|\\.)*)/((?:g|i|\d+)*))*$
&lt;/pre&gt;

&lt;p&gt;You can use the regular expression to parse sed replacement expressions,
even a semicolon delimited sequence of expressions, like the FSM parser. The
regex will yield three captured groups per sed expression, one each for the
needle, replacement and flags components respectively. All you need to do is
collapse backslash escapes, and split flags as necessary, both of which are
easy tasks. With verbose mode I can document the behemoth:

&lt;pre&gt;
^			# start of the message
(?:			# BEGIN first sed expression
  s/			#   sed replacement expression delimiter
  (			#   BEGIN needle component
    (?:			#     BEGIN single needle character
      [^\\/]		#       anything that isn&apos;t a slash or backslash...
      |\\.		#       ...or any backslash escape
    )*			#     END single needle character, zero or more
  )			#   END needle component
  /			#   slash between needle and replacement
  (			#   BEGIN replacement component
    (?:			#     BEGIN single replacement character
      [^\\/]|\\.	#       escape or non-slash-backslash, as above
    )*			#     END single replacement character, zero or more
  )			#   END replacement component
  /			#   slash between replacement and flags
  (			#   BEGIN flags component
    (?:			#     BEGIN single flag
      g|i|\d+		#       &quot;g&quot;, &quot;i&quot; or a sequence of digits
    )*			#     END single flag, zero or more
  )			#   END flags component
)			# END first sed expression
(?:			# BEGIN optional subsequent sed expressions
  ;			#   semicolon between sed expressions
  s/			#   sed replacement expression delimiter, as above
  ((?:[^\\/]|\\.)*)	#   needle component, as above
  /			#   slash between needle and replacement, as above
  ((?:[^\\/]|\\.)*)	#   replacement component, as above
  /			#   slash between replacement and flags, as above
  ((?:g|i|\d+)*)	#   flags component, as above
)*			# END optional subsequent sed expressions, zero or more
$			# end of the message
&lt;/pre&gt;</content><author><name></name></author><category term="home" /><summary type="html">The ComSSA IRC has an official utility bot, KhlavKalash, which currently does some trivia like URL title fetching and server uptime. It uses Twisted for IRC and has a good plugin system with Yapsy. Feeling a bit bored, I decided to try my hand at implementing what I call a &quot;sedbot&quot;, which interprets messages that are sed replacement expressions, executing the replacement on the last normal message.</summary></entry><entry><title type="html">Sidestepping NXDOMAIN hijacking</title><link href="https://www.azabani.com/2014/02/04/sidestepping-nxdomain-hijacking.html" rel="alternate" type="text/html" title="Sidestepping NXDOMAIN hijacking" /><published>2014-02-04T06:00:00+00:00</published><updated>2014-02-04T06:00:00+00:00</updated><id>https://www.azabani.com/2014/02/04/sidestepping-nxdomain-hijacking</id><content type="html" xml:base="https://www.azabani.com/2014/02/04/sidestepping-nxdomain-hijacking.html">&lt;p&gt;Until May last year, I was a customer of name.com for a domain name and DNS
services. Having just grown out of a comfortable walled garden of all-in-one
shared hosting and DNS perhaps about a year prior, I knew little other than
that I should avoid GoDaddy. I since discovered that name.com was pulling a
scummy trick that an increasing number of name service providers engaged in.

&lt;p&gt;NXDOMAIN hijacking is not news now. Many ISPs and even OpenDNS have started
doing it, and while it not only breaks the DNS specifications and poses a
security risk, name.com&apos;s flippant responses and actions in holding customers
liable for name.com&apos;s automatically generated content are clearly unacceptable.
Specifically, the actions of name.com have been
&lt;a href=&quot;http://www.destructuring.net/2013/02/28/name-com-is-doing-some-really-sketchy-stuff/&quot;&gt;covered&lt;/a&gt;
&lt;a href=&quot;http://nathanhammond.com/namedotcom-another-unscrupulous-registrar&quot;&gt;quite&lt;/a&gt;
&lt;a href=&quot;http://www.taborcg.com/2010/05/06/name-com-host-typo-hijacking/&quot;&gt;extensively&lt;/a&gt;.

&lt;p&gt;That&apos;s not what this post is about though. You can easily avoid all of this
by switching to a vendor that isn&apos;t bad, such as Namecheap, Hover or Gandi. If
you stay with a provider like name.com, consider using something like &lt;a href=
&quot;https://dns.he.net/&quot;&gt;Hurricane Electric&apos;s free DNS&lt;/a&gt; service, which supports
IPv6 and includes the ability to run primary or secondary nameservers.

&lt;p&gt;Time to explore some commonly suggested technical workarounds provided to
users of the broken name services, for some fun and no profit. To begin, take
this output of &lt;code&gt;nslookup&lt;/code&gt; which exhibits the issue:

&lt;pre&gt;
C:\Users\delan&gt;nslookup notexistent.azabani.com.
Server:  resolv.on.ii.net
Address:  2001:44b8:1::1

Non-authoritative answer:
Name:    notexistent.azabani.com
Address:  174.37.172.71
&lt;/pre&gt;

&lt;p&gt;A simplistic solution is to create wildcard A and AAAA records to IP
addresses guaranteed to never route nor reach a compliant networked device. RFC
3513 § 2.5.2 stipulates that the address &lt;code&gt;::&lt;/code&gt; &quot;must never be
assigned to any node&quot;. However, as you may expect, there are still addresses
returned by this method; any failure is in connection, not resolution.

&lt;pre&gt;
*	A	0.0.0.0
*	AAAA	::

C:\Users\delan&gt;nslookup notexistent.azabani.com.
Server:  resolv.on.ii.net
Address:  2001:44b8:1::1

Non-authoritative answer:
Name:    notexistent.azabani.com
Addresses:  ::
          0.0.0.0
&lt;/pre&gt;

&lt;p&gt;Interestingly, the presence of any type of record will disable address
hijacking, but the response will not actually be NXDOMAIN, but rather a NOERROR
with no answer records. You can see that by creating a TXT wildcard record.

&lt;pre&gt;
*	TXT	unused

C:\Users\delan&gt;nslookup notexistent.azabani.com.
Server:  resolv.on.ii.net
Address:  2001:44b8:1::1

*** No internal type for both IPv4 and IPv6 Addresses (A+AAAA) records
available for notexistent
&lt;/pre&gt;

&lt;p&gt;What does appear to do the trick is a wildcard CNAME to a target domain name
that will never resolve. RFC 2606 § 2 and the more recent RFC 6761 § 6.4
define &lt;code&gt;.invalid&lt;/code&gt; as a reserved TLD which is guaranteed to neither
have any public domain registrations, nor ever be resolved by a compliant
server.

&lt;pre&gt;
*	CNAME	working.around.dns.hijacking.invalid

C:\Users\delan&gt;nslookup notexistent.azabani.com.
Server:  resolv.on.ii.net
Address:  2001:44b8:1::1

*** resolv.on.ii.net can&apos;t find notexistent: Non-existent domain
&lt;/pre&gt;</content><author><name></name></author><category term="home" /><summary type="html">Until May last year, I was a customer of name.com for a domain name and DNS services. Having just grown out of a comfortable walled garden of all-in-one shared hosting and DNS perhaps about a year prior, I knew little other than that I should avoid GoDaddy. I since discovered that name.com was pulling a scummy trick that an increasing number of name service providers engaged in.</summary></entry><entry><title type="html">Network installing Windows XP</title><link href="https://www.azabani.com/2014/02/02/network-installing-windows-xp.html" rel="alternate" type="text/html" title="Network installing Windows XP" /><published>2014-02-02T04:00:00+00:00</published><updated>2014-02-02T04:00:00+00:00</updated><id>https://www.azabani.com/2014/02/02/network-installing-windows-xp</id><content type="html" xml:base="https://www.azabani.com/2014/02/02/network-installing-windows-xp.html">&lt;p&gt;This was originally going to be a quick guide to Windows Deployment Services
with Windows Server 2012. Using it to install Windows Vista and newer is
trivial though, with the introduction of the WIM format. However, upon finding
a laptop that needed Windows XP and could only externally boot via PXE, things
changed a little.

&lt;p&gt;It&apos;s obligatory for me to remind you that as this is published, Windows XP
will die in just over two months&apos; time, so you shouldn&apos;t use it unless
absolutely necessary. Nevertheless it&apos;s not completely terrible; it even has
decent IPv6 support up its sleeve, while striking the careful balance of good
support for programs from the 16-bit to modern eras.

&lt;p&gt;The trouble started when I acquired a Toshiba Tecra M2 with a faulty optical
drive. To make things worse, it&apos;s too old to support USB boot. To really rub in
the salt, it supports SD card boot, but only via floppy emulation of a file you
place in the FAT filesystem root named &lt;code&gt;$TOSFD00.VFD&lt;/code&gt;. I have no
idea what they were thinking.

&lt;p&gt;While from Vista onwards it&apos;s simply a case of having WDS and dropping in the
WIM file, installing Windows XP is full of pitfalls that took me a great deal of
poring over the Microsoft Knowledge Base and a variety of forums to decipher.
Hopefully this will help someone avoid the same adventure, to put it nicely.

&lt;p&gt;Essentially, the process involves creating a model installation of Windows
XP, which is then captured into a WIM file by booting the &lt;i&gt;capture image&lt;/i&gt;,
then deployed in a similar way to how Windows Vista or later would be deployed.

&lt;h2&gt;Configuration notes&lt;/h2&gt;

&lt;p&gt;The simplest configuration is to have Windows Server 2012 on a physical
machine while the model installation is generated in a virtual machine running
on the server. This is the layout that will be used for this guide, but others
may be useful if for instance, you don&apos;t have access to a spare computer or
network.

&lt;p&gt;One such alternative configuration is having both the server and the model
installation as virtual machines on one host; this may require the use of a
virtual network facility in your virtualisation software, so that both virtual
machines are connected to one another in one isolated network.

&lt;p&gt;This guide also assumes the use of VMware Workstation 10; Microsoft Virtual
PC and Oracle VM VirtualBox probably work as well, but to use the latter, make
sure the &lt;a href=&quot;https://www.virtualbox.org/wiki/Downloads&quot;&gt;Extension Pack&lt;/a&gt;
is installed or PXE booting WDS images will fail. Also, Windows Deployment
Services has been available since Windows Server 2003 with SP2, so you could
probably use an older version of Windows Server, although I haven&apos;t tested it.

&lt;h2&gt;Prerequisites&lt;/h2&gt;

&lt;ul&gt;
	&lt;li&gt;&lt;i&gt;Windows Server 2012&lt;/i&gt; as the WDS and DHCP/TFTP/PXE server
	&lt;li&gt;&lt;i&gt;VMware Workstation&lt;/i&gt; to generate an installation of Windows XP
	&lt;li&gt;&lt;i&gt;Windows 7 x86&lt;/i&gt; media for its &lt;code&gt;boot.wim&lt;/code&gt;&lt;br&gt;
	&lt;li&gt;&lt;i&gt;Windows XP Professional&lt;/i&gt; media
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;boot.wim&lt;/code&gt; serves the dual role of being the installer for
the completed XP image as well as being used to capture the model XP
installation. Beware of the Windows XP media; unlike newer version of Windows,
there are different media for OEM, Retail and Volume Licensing keys, and each
are mutually incompatible by only accepting their specific class of keys.

&lt;h2&gt;Preparing the server&lt;/h2&gt;

&lt;p&gt;&lt;i&gt;Estimated time: 20 minutes&lt;/i&gt;

&lt;p&gt;Choose a wired network interface for use with the target computer, and
assign it a static IP address, for example &lt;code&gt;192.168.123.1/24&lt;/code&gt;. This
is the address that will be used for the rest of this guide. Add the &lt;i&gt;DHCP
Server&lt;/i&gt; and &lt;i&gt;Windows Deployment Services&lt;/i&gt; roles.

&lt;p&gt;Ensure that the DHCP server is running and add two scopes, one listening for
the target on &lt;code&gt;192.168.123.0/24&lt;/code&gt;, and one listening for the VM on
the subnet chosen by VMware in its default host-only network interface. Of
course, leave out the first address from both ranges, as they&apos;re already taken.
Leave all other settings alone, and activate the scopes.

&lt;p&gt;Start the &lt;i&gt;Windows Deployment Services Configuration Wizard&lt;/i&gt; and select
&lt;i&gt;Standalone server&lt;/i&gt;. The wizard should detect that the DHCP server has
been configured and started, and should automatically check both the &lt;i&gt;Do not
listen&lt;/i&gt; and &lt;i&gt;Configure DHCP&lt;/i&gt; checkboxes. &lt;i&gt;If these are not both
checked, then the DHCP server is either not configured or is not running; if
so, cancel the wizard and fix that first.&lt;/i&gt;

&lt;p&gt;Later in the same wizard, select &lt;i&gt;Respond to all clients&lt;/i&gt;, and finish
the wizard. There is a chance the wizard will say that the service didn&apos;t
respond in a timely fashion. If this occurs, right click the server and start
the service manually.

&lt;p&gt;Finally, create an image group in the install images section, ready to later
receive the captured installation image.

&lt;h2&gt;Adding boot images for capture and setup&lt;/h2&gt;

&lt;p&gt;&lt;i&gt;Estimated time: 10 minutes&lt;/i&gt;

&lt;p&gt;Windows XP is unlike later versions in that the installation media has no
monolithic &lt;code&gt;.wim&lt;/code&gt; file that can be extracted during setup. While
RIS, essentially the predecessor to WDS, supports deploying Windows XP using
the original media, WDS only supports &lt;code&gt;.wim&lt;/code&gt; files. For Windows XP,
that means creating a model installation and then capturing it back to the
server as a deployable image.

&lt;p&gt;Two boot images must be added: the capture image, which the VM will boot
into after Windows XP is installed, and the setup image, which the target will
use to install Windows XP.

&lt;p&gt;Adding the setup image is simple: mount the Windows 7 media and add &lt;code
&gt;\sources\boot.wim&lt;/code&gt;. Once this is done, generate the capture image from
the setup image by right clicking it and clicking &lt;code&gt;Create Capture
Image&lt;/code&gt;.

&lt;p&gt;When prompted for a location, save the image anywhere you want, even &lt;code
&gt;%TEMP%&lt;/code&gt;, as you&apos;ll have an opportunity to use the same wizard to import
the image once it is completed. &lt;i&gt;If you choose to cleverly save the image in
the actual repository location, and then tell the wizard to import the image,
you will end up with two copies of the same capture image in WDS.&lt;/i&gt;

&lt;h2&gt;Creating a model installation in VMware&lt;/h2&gt;

&lt;p&gt;&lt;i&gt;Estimated time: 60 minutes&lt;/i&gt;

&lt;p&gt;Create a virtual machine with typical settings, but be sure to choose
&quot;install OS later&quot;, or VMware will try to do an &quot;easy install&quot; that automates
the installation of tools and drivers. While this isn&apos;t fatal, it is simply
unnecessary as the target computer isn&apos;t a VM.

&lt;p&gt;Open the virtual machine&apos;s settings after creation and increase the RAM to
1024 MB; without doing so, booting the WDS capture image may fail. Change the
network adapter type to &lt;i&gt;Host-only&lt;/i&gt; and attach the Windows XP media to the
virtual optical drive. &lt;i&gt;Disconnect the virtual network adapter &quot;at power on&quot;
to save a significant amount of time during the &quot;Installing Network&quot; stage of
installation.&lt;/i&gt;

&lt;p&gt;When indicating that the guest is Windows XP, VMware will default to
virtualising an AMD 10/100 NIC, which is not supported by the capture image.
You must change it to an Intel PRO/1000 by editing the VM&apos;s &lt;code&gt;.vmx&lt;/code&gt;
file and adding this line:

&lt;pre&gt;ethernet0.virtualDev = &quot;e1000&quot;&lt;/pre&gt;

&lt;p&gt;Launch the VM, enter the BIOS setup and change the boot order to:

&lt;pre&gt;LAN &gt; CD &gt; Removable Devices &gt; HDD&lt;/pre&gt;

&lt;p&gt;Now install Windows XP. Press F5 when the F6 prompt appears (that is not a
mistake), and choose the computer type that is compatible with your target. The
VMware virtual machine defaults to ACPI Uniprocessor PC, which works for many
computers, but you may need to choose an older type, such as ACPI PC for the
Tecra, or even Standard PC, if your target is that old.

&lt;p&gt;Although the &lt;a href=
&quot;http://technet.microsoft.com/en-us/library/cc781362%28v=ws.10%29.aspx&quot;
&gt;Microsoft documentation&lt;/a&gt; states that the Standard PC HAL is compatible with
all six computer types, you cannot easily take advantage of this to create a
&quot;universal&quot; image because WDS does very picky filtering of the available images
on the target based on the HAL, only allowing an exact match, but not other
compatible matches.

&lt;p&gt;Leave all of the regional and time settings at their defaults as they will
be discarded anyway later. If you are deploying an OEM installation to the
target, you can safely skip entering the product key, as you&apos;ll have to enter
it again further in the process regardless.

&lt;p&gt;During the OOBE wizard, set your username to &lt;code&gt;SYSTEM&lt;/code&gt;. This will
ensure that no user account is created, and you can create one for yourself
after deployment. Unfortunately, this trick doesn&apos;t make the OOBE wizard rerun
after deployment. What will happen is that you will be logged in as &lt;code
&gt;Administrator&lt;/code&gt; because &lt;code&gt;SYSTEM&lt;/code&gt; isn&apos;t for interactive use.
You can&apos;t, however, enter &lt;code&gt;Administrator&lt;/code&gt; itself in the wizard, as
that is explicitly rejected.

&lt;p&gt;It might appear that you can skip the OOBE and have it come back after
deployment by cancelling the screen resolution change dialog, but whlie that
will skip the OOBE (and leave you with a paltry 640x480 resolution) the wizard
will never return. In essence, this is thus equivalent to the &lt;code
&gt;SYSTEM&lt;/code&gt; trick, but without the resolution boost.

&lt;h2&gt;Preparing the completed installation for capture&lt;/h2&gt;

&lt;p&gt;&lt;i&gt;Estimated time: 40 minutes&lt;/i&gt;

&lt;p&gt;Inside the virtual machine now that Windows XP is installed, extract the
contents of &lt;code&gt;DEPLOY.CAB&lt;/code&gt; from the Windows XP media to &lt;code
&gt;C:\sysprep&lt;/code&gt;. &lt;i&gt;You must extract the files to this precise location. If
you extract elsewhere, Sysprep will ignore the changes you make to &lt;code
&gt;sysprep.inf&lt;/code&gt;, generate a default configuration in the correct location,
and the target will throw a &lt;code&gt;STOP 0x0000007B&lt;/code&gt; during deployment.&lt;/i&gt;

&lt;p&gt;&lt;i&gt;Note that if you are capturing and deploying to the same physical target,
and you are not using a virtual machine for the model installation, you should
skip to the part where you run  &lt;code&gt;sysprep.exe&lt;/code&gt;.&lt;/i&gt;

&lt;p&gt;Copy the entire &lt;code&gt;\i386\&lt;/code&gt; folder (including the folder itself, not
just the contents) from the Windows XP media to &lt;code&gt;C:\sysprep&lt;/code&gt;. This
ensures that all drivers and additional files are available to the target as
necessary during deployment.

&lt;p&gt;Run &lt;code&gt;setupmgr.exe&lt;/code&gt; and create a &lt;code&gt;sysprep.inf&lt;/code&gt; with all
settings left at their defaults. Now add the following lines to the &lt;code
&gt;[Unattended]&lt;/code&gt; section of &lt;code&gt;sysprep.inf&lt;/code&gt;:

&lt;pre&gt;
    DriverSigningPolicy=Ignore
    UpdateInstalledDrivers=Yes
&lt;/pre&gt;

Add the following to the end of &lt;code&gt;sysprep.inf&lt;/code&gt;:

&lt;pre&gt;
[Sysprep]
    BuildMassStorageSection=Yes

[SysprepMassStorage]
&lt;/pre&gt;

&lt;p&gt;Ensure that you leave at least one line break after &lt;code
&gt;[SysprepMassStorage]&lt;/code&gt;.

&lt;p&gt;Now run &lt;code&gt;sysprep.exe&lt;/code&gt;, selecting &lt;i&gt;Use Mini-Setup&lt;/i&gt; and &lt;i
&gt;Detect non-PnP hardware&lt;/i&gt;. Click &lt;i&gt;Reseal&lt;/i&gt; then &lt;i&gt;OK&lt;/i&gt;. This process
may take about half an hour, so grab a coffee while you wait.

&lt;h2&gt;Capturing the model installation&lt;/h2&gt;

&lt;p&gt;&lt;i&gt;Estimated time: 30 minutes&lt;/i&gt;

&lt;p&gt;Your virtual machine should be switched off now, as Sysprep shuts down after
it has finished working. Configure the virtual network adapter to connect at
power on, and boot the VM over the network.

&lt;p&gt;Press F12 to launch the WDS boot menu when prompted, and select the capture
image. Follow the wizard&apos;s instructions, which should be fairly
straightforward. When the capture completes, the image should be available on
the WDS repertoire, but you will have to close and reopen the WDS management
console if you wish to see it there.

&lt;p&gt;The capturing process can take quite a while, so another coffee break
opportunity is available.

&lt;h2&gt;Deploying the Windows XP image&lt;/h2&gt;

&lt;p&gt;&lt;i&gt;Estimated time: 20 minutes&lt;/i&gt;

&lt;p&gt;Connect the target computer to the interface you chose earlier. Some old
computers will not be able to negotiate a crossover connection using a normal
cable; for them you will have to use an actual crossover cable.

&lt;p&gt;Boot the target over the network, pressing F12 when necessary and this time
choosing &lt;i&gt;Microsoft Windows Setup (x86)&lt;/i&gt; from the boot image menu. The
first part of the deployment is much like a typical Windows 7 installation.
&lt;i&gt;When asked to authenticate to the WDS server, use the server&apos;s computer name
as the domain.&lt;/i&gt;

&lt;p&gt;If you do not see the captured installation image in the list, then the
computer type selected at the beginning of the VM installation does not match
the one for the target. In this case you will need to start again from &lt;i
&gt;Creating a model installation in VMware&lt;/i&gt;, choosing a different computer
type.

&lt;p&gt;If the target enters a reboot loop with the &lt;code&gt;0x0000007B&lt;/code&gt; BSOD,
then the mass storage drivers were not properly included in the Sysprep
process, either because you didn&apos;t modify &lt;code&gt;sysprep.inf&lt;/code&gt; correctly,
or because you extracted Sysprep to some location other than &lt;code
&gt;C:\sysprep&lt;/code&gt;.

&lt;h2&gt;After installation&lt;/h2&gt;

&lt;p&gt;Once Windows XP is deployed and running on the target, you will be logged in
as the &lt;code&gt;Administrator&lt;/code&gt; by default, with no password unless you set
one during the setup. You should probably set a password and then create
yourself a regular administrator account, for a marginal increase in security.

&lt;p&gt;After deployment, you are probably going to install some drivers for the
target computer. If you want, you can even sysprep again and capture the
installation from the target, just like you did from the virtual machine. This
will result in an image including drivers for the target computer.</content><author><name></name></author><category term="home" /><summary type="html">This was originally going to be a quick guide to Windows Deployment Services with Windows Server 2012. Using it to install Windows Vista and newer is trivial though, with the introduction of the WIM format. However, upon finding a laptop that needed Windows XP and could only externally boot via PXE, things changed a little.</summary></entry><entry><title type="html">Forwarding Curtin email</title><link href="https://www.azabani.com/2014/01/23/forwarding-curtin-email.html" rel="alternate" type="text/html" title="Forwarding Curtin email" /><published>2014-01-23T02:00:00+00:00</published><updated>2014-01-23T02:00:00+00:00</updated><id>https://www.azabani.com/2014/01/23/forwarding-curtin-email</id><content type="html" xml:base="https://www.azabani.com/2014/01/23/forwarding-curtin-email.html">&lt;p&gt;In an effort to avoid using any email UI other than the familiar embrace of
Gmail via Google Apps, one of the first things I did when I became a Curtin
student was to forward my email. I couldn&apos;t quite filter all of the incoming
emails properly though, until last night I had a sudden realisation.

&lt;p&gt;Most emails to my Curtin account look something like this:

&lt;pre&gt;
Delivered-To: delan@azabani.com
Return-Path: &amp;lt;delan.azabani@student.curtin.edu.au&gt;
Resent-From: &amp;lt;delan.azabani@student.curtin.edu.au&gt;
To: &amp;lt;delan.azabani@student.curtin.edu.au&gt;
&lt;/pre&gt;

&lt;p&gt;The obvious approach was to configure the Outlook Web App to forward email
to delan@azabani.com, and that seemed to work reasonably. On the Gmail side, I
simply created a filter that took all emails matching &lt;code
&gt;to:delan.azabani@student.curtin.edu.au&lt;/code&gt; and added the &lt;code&gt;curtin&lt;/code
&gt; label to them.

&lt;p&gt;Except that didn&apos;t work for the significant minority of messages where all
of the recipients were blind carbon copied, myself included. Why not filter on
the &lt;code&gt;Return-Path&lt;/code&gt; or &lt;code&gt;Resent-From&lt;/code&gt; headers? Gmail doesn&apos;t
allow users to search arbitrary headers, only those that are whitelisted. This
includes &lt;code&gt;From&lt;/code&gt;, &lt;code&gt;To&lt;/code&gt;, &lt;code&gt;Subject&lt;/code&gt;, &lt;code
&gt;Cc&lt;/code&gt;, &lt;code&gt;Bcc&lt;/code&gt; and &lt;code&gt;Message-ID&lt;/code&gt;.

&lt;p&gt;Gmail also allows you to search the &lt;code&gt;Delivered-To&lt;/code&gt; headers of
messages, and this seemed useless to me because the value would always be &lt;code
&gt;delan@azabani.com&lt;/code&gt;. There&apos;s where the realisation hit me: combined with
Gmail&apos;s automatic &quot;plus&quot; aliasing, the &lt;code&gt;Delivered-To&lt;/code&gt; header is in
fact the perfect solution.

&lt;p&gt;Now that I forward Curtin messages to &lt;code&gt;delan+curtin@azabani.com&lt;/code&gt;
and filter on &lt;code&gt;deliveredto:delan+curtin@azabani.com&lt;/code&gt;, no more
emails need to be manually labelled. In retrospect, that it took me over a year
as a Curtin student to forward my mail properly is somewhat disappointing.</content><author><name></name></author><category term="home" /><summary type="html">In an effort to avoid using any email UI other than the familiar embrace of Gmail via Google Apps, one of the first things I did when I became a Curtin student was to forward my email. I couldn&apos;t quite filter all of the incoming emails properly though, until last night I had a sudden realisation.</summary></entry><entry><title type="html">Network protocol checking tools</title><link href="https://www.azabani.com/2014/01/09/network-protocol-checking-tools.html" rel="alternate" type="text/html" title="Network protocol checking tools" /><published>2014-01-09T12:00:00+00:00</published><updated>2014-01-09T12:00:00+00:00</updated><id>https://www.azabani.com/2014/01/09/network-protocol-checking-tools</id><content type="html" xml:base="https://www.azabani.com/2014/01/09/network-protocol-checking-tools.html">&lt;p&gt;I&apos;ve seen several quick, web-based configuration testing tools for clients
and servers of various protocols, so it could potentially be helpful to someone
if I compiled a list of them here. These tools test not only that your servers
are working, but that they are configured in accordance with best practices, and
I use them all heavily. Let&apos;s start with TLS, DNS and SMTP, though I&apos;ll try to
find more in future and append them here.

&lt;h2&gt;DNS — authority&lt;/h2&gt;

&lt;ul&gt;
	&lt;li&gt;&lt;a href=&quot;http://intodns.com/&quot;&gt;intoDNS&lt;/a&gt; by &lt;a href=&quot;http://www.elvsoft.com/&quot;&gt;Elvsoft&lt;/a&gt;
	&lt;li&gt;&lt;a href=&quot;http://dnscheck.pingdom.com/&quot;&gt;DNS check tool&lt;/a&gt; by &lt;a href=&quot;https://www.pingdom.com/&quot;&gt;Pingdom&lt;/a&gt;
	&lt;li&gt;&lt;a href=&quot;http://dnsviz.net/&quot;&gt;DNSViz&lt;/a&gt; by &lt;a href=&quot;http://www.sandia.gov/&quot;&gt;Sandia National Laboratories&lt;/a&gt;
	&lt;li&gt;&lt;a href=&quot;http://dnssec-debugger.verisignlabs.com/&quot; &gt;DNSSEC Analyzer&lt;/a&gt; by &lt;a href=&quot;https://www.verisigninc.com/&quot;&gt;Verisign Labs&lt;/a&gt;
&lt;/ul&gt;

&lt;h2&gt;DNS — resolver&lt;/h2&gt;

&lt;ul&gt;
	&lt;li&gt;&lt;a href=&quot;https://www.dns-oarc.net/oarc/services/cmdns&quot;&gt;Check My DNS&lt;/a&gt; by &lt;a href=&quot;https://www.dns-oarc.net&quot;&gt;DNS-OARC&lt;/a&gt;
	&lt;li&gt;&lt;a href=&quot;https://www.dns-oarc.net/oarc/services/dnsentropy&quot;&gt;Web-based DNS Randomness Test&lt;/a&gt; by &lt;a href=&quot;https://www.dns-oarc.net&quot;&gt;DNS-OARC&lt;/a&gt;
&lt;/ul&gt;

&lt;h2&gt;SMTP&lt;/h2&gt;

&lt;ul&gt;
	&lt;li&gt;&lt;a href=&quot;https://www.port25.com/authentication-checker/&quot;&gt;Authentication Checker&lt;/a&gt; by &lt;a href=&quot;https://www.port25.com&quot;&gt;Port25&lt;/a&gt;
	&lt;li&gt;&lt;a href=&quot;http://www.brandonchecketts.com/emailtest.php&quot;&gt;DomainKeys, DKIM, SPF, SpamAssassin Email Validator&lt;/a&gt; by &lt;a href=&quot;http://www.brandonchecketts.com/&quot;&gt;Brandon Checketts&lt;/a&gt;
	&lt;li&gt;&lt;a href=&quot;https://www.had-pilot.com/py/had.html&quot; &gt;High Assurance Domain DMARC testing tool&lt;/a&gt; by &lt;a href=&quot;http://www.nist.gov/&quot;&gt;NIST&lt;/a&gt;
	&lt;li&gt;&lt;a href=&quot;http://kitterman.com/dmarc/assistant.html&quot; &gt;DMARC Record Assistant&lt;/a&gt; by &lt;a href=&quot;http://kitterman.com/&quot;&gt;Kitterman Technical Services&lt;/a&gt;
	&lt;li&gt;&lt;a href=&quot;https://dmarcian.com/dmarc-inspector/&quot; &gt;DMARC Inspector&lt;/a&gt; by &lt;a href=&quot;https://dmarcian.com/&quot;&gt;Jason Bela and Tim Draegen&lt;/a&gt;
&lt;/ul&gt;

&lt;h2&gt;TLS&lt;/h2&gt;

&lt;ul&gt;
	&lt;li&gt;&lt;a href=&quot;http://neverssl.com/&quot;&gt;NeverSSL&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/colmmacc/status/790598606000533504&quot;&gt;Colm MacCárthaigh&lt;/a&gt;
	&lt;li&gt;&lt;a href=&quot;https://badssl.com/&quot;&gt;&lt;del&gt;https&lt;/del&gt;://badssl.com&lt;/a&gt; by &lt;a href=&quot;https://pokeinthe.io/about/&quot;&gt;April King&lt;/a&gt; and &lt;a href=&quot;https://garron.net/&quot;&gt;Lucas Garron&lt;/a&gt;
&lt;/ul&gt;

&lt;h2&gt;TLS — server&lt;/h2&gt;

&lt;ul&gt;
	&lt;li&gt;&lt;a href=&quot;https://www.ssllabs.com/ssltest/&quot;&gt;SSL Server Test&lt;/a&gt; by &lt;a href=&quot;https://www.qualys.com/&quot;&gt;Qualys&lt;/a&gt;
	&lt;li&gt;&lt;a href=&quot;https://sslcheck.globalsign.com/&quot;&gt;SSL Check&lt;/a&gt; by &lt;a href=&quot;http://www.globalsign.com/&quot;&gt;GlobalSign&lt;/a&gt;
&lt;/ul&gt;

&lt;h2&gt;TLS — client&lt;/h2&gt;

&lt;ul&gt;
	&lt;li&gt;&lt;a href=&quot;https://www.howsmyssl.com/&quot;&gt;How&apos;s My SSL?&lt;/a&gt; by &lt;a href=&quot;http://www.somethingsimilar.com/&quot;&gt;Jeff Hodges&lt;/a&gt;
	&lt;li&gt;&lt;a href=&quot;https://www.ssllabs.com/ssltest/viewMyClient.html&quot; &gt;SSL Client Test&lt;/a&gt; by &lt;a href=&quot;https://www.qualys.com/&quot;&gt;Qualys&lt;/a&gt;
&lt;/ul&gt;

&lt;h2&gt;XMPP&lt;/h2&gt;

&lt;ul&gt;
	&lt;li&gt;&lt;a href=&quot;https://xmpp.net/&quot;&gt;IM Observatory&lt;/a&gt; by &lt;a href=&quot;https://blog.thijsalkema.de/about/&quot;&gt;Thijs Alkemade&lt;/a&gt;
&lt;/ul&gt;

&lt;h2&gt;CAA&lt;/h2&gt;

&lt;ul&gt;
	&lt;li&gt;&lt;a href=&quot;https://sslmate.com/caa/&quot;&gt;CAA Record Helper&lt;/a&gt; by &lt;a href=&quot;https://www.opsmate.com/&quot;&gt;Opsmate&lt;/a&gt;
&lt;/ul&gt;</content><author><name></name></author><category term="home" /><summary type="html">I&apos;ve seen several quick, web-based configuration testing tools for clients and servers of various protocols, so it could potentially be helpful to someone if I compiled a list of them here. These tools test not only that your servers are working, but that they are configured in accordance with best practices, and I use them all heavily. Let&apos;s start with TLS, DNS and SMTP, though I&apos;ll try to find more in future and append them here.</summary></entry><entry><title type="html">Forcing a single timezone in Jekyll</title><link href="https://www.azabani.com/2014/01/02/forcing-single-timezone-jekyll.html" rel="alternate" type="text/html" title="Forcing a single timezone in Jekyll" /><published>2014-01-02T23:00:00+00:00</published><updated>2014-01-02T23:00:00+00:00</updated><id>https://www.azabani.com/2014/01/02/forcing-single-timezone-jekyll</id><content type="html" xml:base="https://www.azabani.com/2014/01/02/forcing-single-timezone-jekyll.html">&lt;p&gt;Alternatively, &lt;i&gt;How I Learned to Stop Worrying and Love UTC&lt;/i&gt;. It was a
little more subtle than I had expected to ensure that Jekyll uses an arbitrary
timezone for dates in posts and their generated URLs, but is that even a Good
Thing™ to do?

&lt;p&gt;My posts were timestamped in their YAML front matter like so:

&lt;pre&gt;date: 2014-01-03 07:00:00 +0800&lt;/pre&gt;

&lt;p&gt;Then I tried adding this to &lt;code&gt;_config.yml&lt;/code&gt;:

&lt;pre&gt;timezone: Australia/Perth&lt;/pre&gt;

&lt;p&gt;It appeared that this had no effect at all. URLs were still being generated
based on the date in UTC, and displayed post dates were still:

&lt;pre&gt;2014-01-02 23:00 UTC&lt;/pre&gt;

&lt;p&gt;Technically the same date, but not quite what I wanted. After emailing
GitHub and receiving very quick and friendly responses, it turns out that both
the timezone offset and &lt;i&gt;the seconds&lt;/i&gt; must be omitted:

&lt;pre&gt;date: 2014-01-03 07:00&lt;/pre&gt;

&lt;p&gt;However, in the time that passed, I realised that doing this would actually
be short-sighted; it would lock this website into one timezone. Not permanently,
perhaps, but one would have to use the same timezone for all posts.

&lt;p&gt;What if, one day, I decide that Perth no longer has the same homely charm,
and I grow tired of living in a city where absolutely nothing happens? Heresy,
I know, but it&apos;s possible.

&lt;p&gt;As an aside, when doing the above, the displayed post dates would be like:

&lt;pre&gt;2014-01-03 07:00 Aus&lt;/pre&gt;

&lt;p&gt;Perhaps using &lt;code&gt;%Z&lt;/code&gt; in Jekyll simply takes the first three
characters of the tzdata name.

&lt;p&gt;Just stick with UTC.</content><author><name></name></author><category term="home" /><category term="jekyll" /><summary type="html">Alternatively, How I Learned to Stop Worrying and Love UTC. It was a little more subtle than I had expected to ensure that Jekyll uses an arbitrary timezone for dates in posts and their generated URLs, but is that even a Good Thing™ to do?</summary></entry><entry><title type="html">Hacking the Casio fx-82MS</title><link href="https://www.azabani.com/2014/01/02/hacking-casio-fx-82ms.html" rel="alternate" type="text/html" title="Hacking the Casio fx-82MS" /><published>2014-01-02T14:00:00+00:00</published><updated>2014-01-02T14:00:00+00:00</updated><id>https://www.azabani.com/2014/01/02/hacking-casio-fx-82ms</id><content type="html" xml:base="https://www.azabani.com/2014/01/02/hacking-casio-fx-82ms.html">&lt;p&gt;The fx-82MS is a cheap two-line scientific calculator that I fondly remember
using for countless hours since the beginning of high school. It&apos;s so reliable
that after using the same one for over six years, the worst that&apos;s happened is
a bit of corrosion on the rear screws. Here are a couple of useful tricks I&apos;ve
found after digging through forums and videos.

&lt;h2&gt;Upgrade your calculator to an fx-570MS&lt;/h2&gt;

&lt;p&gt;Casio&apos;s two-line scientific calculators all appear to use the same circuit
board and firmware. A combination of several soldered contacts determine which
features of the firmware to enable by default. A pencil can be used to
permanently change the effective model on older hardware revisions, but this
has since been prevented with a hard black obstacle injected onto that area of
the board.

&lt;p&gt;You can also &apos;upgrade&apos; your calculator to an fx-570MS, which is one of the
most feature-complete and expensive Casio scientific calculators by exploiting
an overflow bug, which lasts until the next reboot.

&lt;ol&gt;
	&lt;li&gt;Enter standard deviation mode, by pressing &lt;code&gt;[MODE] [2]&lt;/code&gt;.
	&lt;li&gt;Press &lt;code&gt;[0]&lt;/code&gt;, then press &lt;code&gt;[M+]&lt;/code&gt; until the words
	    &quot;Data full&quot; appear on the screen.
	&lt;li&gt;Press &lt;code&gt;[M+] [2] [UP]&lt;/code&gt;.
	&lt;li&gt;Press &lt;code&gt;[1] [3]&lt;/code&gt; repeatedly until no more can be entered.
	&lt;li&gt;Press &lt;code&gt;[=]&lt;/code&gt;. You will see the words &quot;Data full&quot;.
	&lt;li&gt;Press &lt;code&gt;[=] [0] [1] [AC]&lt;/code&gt;.
&lt;/ol&gt;

&lt;p&gt;With a few caveats, the available modes should be expanded to:

&lt;ul&gt;
	&lt;li&gt;COMP (general computation)
	&lt;li&gt;CMPLX (complex numbers)
	&lt;li&gt;SD (standard deviation)
	&lt;li&gt;REG (regressions)
	&lt;li&gt;BASE (base conversions)
	&lt;li&gt;EQN (solving equations)
	&lt;li&gt;MAT (matrices)
	&lt;li&gt;VCT (vectors)
&lt;/ul&gt;

&lt;p&gt;Of particular note is an &lt;a href=&quot;/images/fx-570ms.jpg&quot;&gt;an image of the
fx-570MS&lt;/a&gt;, which will become very useful for you when you need to find a
key, because although the key layout will be the same as on your calculator,
the labels will not be.

&lt;h2&gt;Diagnostic routine&lt;/h2&gt;

&lt;p&gt;This is a capability built into the firmware, presumably to allow easy,
initial in-factory testing of the screen and keypad. At any time during the
routine, pressing &lt;code&gt;[ON]&lt;/code&gt; will reboot the calculator, ending the test.

&lt;ol&gt;
	&lt;li&gt;While holding &lt;code&gt;[SHIFT]&lt;/code&gt; and &lt;code&gt;[7]&lt;/code&gt; down, press
	    &lt;code&gt;[ON]&lt;/code&gt;. The display will turn on all of its elements.
	&lt;li&gt;Press &lt;code&gt;[SHIFT]&lt;/code&gt;. The display will clear completely.
	&lt;li&gt;Press &lt;code&gt;[SHIFT]&lt;/code&gt;. The display will turn on half of its
	    elements in a pattern.
	&lt;li&gt;Press &lt;code&gt;[SHIFT]&lt;/code&gt;. The display will turn on the other half
	    of its elements.
	&lt;li&gt;Press &lt;code&gt;[SHIFT]&lt;/code&gt;. On the first line of the display, you
	    will see &quot;25&quot; on the left, and &quot;4&quot; on the right. On the second line
	    of the display, you will see all zeroes, including in the exponent.
	&lt;li&gt;Press &lt;code&gt;[SHIFT]&lt;/code&gt; nine times. The display will cycle its
	    second line through all ones, then all twos, and so on, until the
	    second line consists of all nines.
	&lt;li&gt;Press &lt;code&gt;[SHIFT]&lt;/code&gt;. The display will clear and the second
	    line will be set to &quot;0&quot;. This commences the keypad testing routine.
	&lt;li&gt;Press the keys of the keypad in the order,&lt;br&gt;
	    &lt;code&gt;[SHIFT] [ALPHA] [MODE] [LEFT] [UP] [RIGHT] [x&lt;sup&gt;-1&lt;/sup&gt;]
	    [nCr] [Pol(] [x&lt;sup&gt;3&lt;/sup&gt;] [DOWN]&lt;/code&gt;&lt;br&gt;
	    then the rest of the keys, as you would read them left to right,
	    until &lt;code&gt;[=]&lt;/code&gt;. On the first line of the display, you will
	    see &lt;code&gt;25&lt;/code&gt; on the left, and &lt;code&gt;OK&lt;/code&gt; on the right.
	    On the second line of the display, you will see &lt;code&gt;4&lt;/code&gt;.
	&lt;li&gt;Press &lt;code&gt;[ON]&lt;/code&gt; to reboot the calculator and exit the test.
&lt;/ol&gt;</content><author><name></name></author><category term="home" /><summary type="html">The fx-82MS is a cheap two-line scientific calculator that I fondly remember using for countless hours since the beginning of high school. It&apos;s so reliable that after using the same one for over six years, the worst that&apos;s happened is a bit of corrosion on the rear screws. Here are a couple of useful tricks I&apos;ve found after digging through forums and videos.</summary></entry><entry><title type="html">Web workers in a single script file</title><link href="https://www.azabani.com/2014/01/02/web-workers-single-script.html" rel="alternate" type="text/html" title="Web workers in a single script file" /><published>2014-01-02T12:00:00+00:00</published><updated>2014-01-02T12:00:00+00:00</updated><id>https://www.azabani.com/2014/01/02/web-workers-single-script</id><content type="html" xml:base="https://www.azabani.com/2014/01/02/web-workers-single-script.html">&lt;p&gt;Web workers are an immensely useful tool, enabling concurrency and perhaps
parallelism with JavaScript. Before their introduction, any non-trivial
computation would completely lock up the user interface of the current
document, or even the entire browser.

&lt;p&gt;One could work around this by breaking up a large job into very small parts and chain them with timers, but this is highly inefficient and choosing an ideal
amount of work for each part across all clients is almost impossible.

&lt;p&gt;However, the interface for creating workers is tricky; the &lt;code
&gt;Worker&lt;/code&gt; constructor takes an external script&apos;s filename to execute.
Suppose you are developing a JavaScript library that relies upon workers
heavily. Are you forced to have two separate script files? No!

&lt;p&gt;Interestingly, it is possible to have a single JavaScript file act as both a
parent and a worker, but the implementation is not completely intuitive.

&lt;p&gt;First of all, the single-file library must be aware of whether it is the
parent or a worker. This can be achieved by detecting the presence of the DOM
&lt;code&gt;document&lt;/code&gt;:

&lt;pre&gt;var is_worker = !this.document;&lt;/pre&gt;

&lt;p&gt;Of course, if you need to execute the above in a context where &lt;code
&gt;this&lt;/code&gt; isn&apos;t the global object, then you can reliably obtain the global
with something like

&lt;pre&gt;var global = (function(){ return this; })();&lt;/pre&gt;

&lt;p&gt;Surely, spawning a worker from the parent is now as simple as this, right?

&lt;pre&gt;var worker = new Worker(&apos;mylibrary.js&apos;);&lt;/pre&gt;

&lt;p&gt;Not quite. Worker paths are not resolved relative to the parent script file&apos;s
path, but instead, relative to the path of the parent page. If the script isn&apos;t
in the same directory as the page, the above will fail. Also, if the user has
renamed the library file, workers will break.

&lt;p&gt;The path of the script relative to the page must be used instead. This can be
obtained by appending a dummy element with &lt;code&gt;document.write&lt;/code&gt; and
getting the previous element&apos;s &lt;code&gt;src&lt;/code&gt;. The previous element, of
course, is the &lt;code&gt;script&lt;/code&gt; tag of the parent script, as conveniently for
this situation, scripts block the building of the DOM tree while running.

&lt;pre&gt;var script_path = is_worker ? null : (function() {
	var id = +new Date + Math.random();
	document.write(&apos;&amp;lt;script id=&quot;dummy&apos; + id + &apos;&quot;&gt;&amp;lt;\/script&gt;&apos;);
	return document.getElementById(&apos;dummy&apos; + id).
		previousSibling.src;
})();&lt;/pre&gt;

&lt;p&gt;The test for &lt;code&gt;is_worker&lt;/code&gt; is present because DOM manipulation can
only be done when the script isn&apos;t running as a worker. Thankfully, only the
parent script needs to know the path to start workers, unless you want to start
subworkers, but the method to do that already resolves paths relative to the
worker&apos;s location.

&lt;p&gt;A &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag is used as the dummy element because it&apos;s
guaranteed to be a valid child element, be it &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; or &lt;code
&gt;&amp;lt;body&amp;gt;&lt;/code&gt;, unlike context-sensitive elements such as &lt;code
&gt;&amp;lt;div&amp;gt;&lt;/code&gt;. It&apos;s also not an element that might have side-effects on
page display depending on CSS.

&lt;p&gt;Now, workers can be spawned in the script with

&lt;pre&gt;var worker = new Worker(script_path);&lt;/pre&gt;

&lt;p&gt;The library in a single file might look something like this:

&lt;pre&gt;(function(global) {
	var is_worker = !this.document;
	var script_path = is_worker ? null : (function() {
		var id = +new Date + Math.random();
		document.write(&apos;&amp;lt;script id=&quot;dummy&apos; + id + &apos;&quot;&gt;&amp;lt;\/script&gt;&apos;);
		return document.getElementById(&apos;dummy&apos; + id).
			previousSibling.src;
	})();
	function msg_from_parent(e) {
		// event handler for parent -&gt; worker messages
	}
	function msg_from_worker(e) {
		// event handler for worker -&gt; parent messages
	}
	function new_worker() {
		var w = new Worker(script_path);
		w.addEventListener(&apos;message&apos;, msg_from_worker, false);
		return w;
	}
	if (is_worker)
		global.addEventListener(&apos;message&apos;, msg_from_parent, false);
	// the rest of the library goes here
	// to spawn a worker, use new_worker()
})(this);&lt;/pre&gt;</content><author><name></name></author><category term="home" /><summary type="html">Web workers are an immensely useful tool, enabling concurrency and perhaps parallelism with JavaScript. Before their introduction, any non-trivial computation would completely lock up the user interface of the current document, or even the entire browser.</summary></entry></feed>