diff --git a/README.md b/README.md
index ab1f1bf0e..be25c1eac 100644
--- a/README.md
+++ b/README.md
@@ -34,6 +34,8 @@ To copy the output to your clipboard for a specific calendar month only:
$ tools/list-commits-by-nightly.sh ~/code/servo 2>&1 | tee /dev/stderr | sed '/^>>> 2025-01-/,/^>>> 2025-02-/!d' | xclip -sel clip
```
+**NOTE:** this will display the squash commit message of each commit, which has historically been less useful than the pull request description. To display pull request descriptions instead, pass in a pulls.json as documented in [§ Triaging commits in nightlies for monthly updates](#triaging-commits-in-nightlies-for-monthly-updates).
+
## How to list this year’s pull request contributors
```sh
@@ -58,51 +60,46 @@ $ > tools/pull-requests.json tools/list-pull-requests.sh servo/servo 2023-03-16
```js
((data, ...dates) => {
- const stride = data.area_keys.length;
+ const focusAreaCount = data.focus_areas.length;
const rows = dates.map(expectedDate => data.scores.find(([date]) => date == expectedDate));
rows.forEach((row, i) => {
+ console.log(i, row);
if (row == null) throw `no results for ${dates[i]}!`;
- if (row.length != 3 + stride + 2 + stride) throw "schema change? bad length";
+ if (row.length != 3 + focusAreaCount) throw "schema change? bad length";
if (row[0] != dates[i]) throw "unreachable! incorrect date in [0]";
if (!/^[0-9a-f]{9}$/.test(row[1])) throw "schema change? expected commit hash in [1]";
- if (!/^0[.]0[.]1-[0-9a-f]{7}$/.test(row[2])) throw "schema change? expected version in [2]";
- if (!/^[0-9a-f]{9}$/.test(row[3+stride])) throw "schema change? expected commit hash in [3+stride]";
- if (!/^0[.]0[.]1-[0-9a-f]{7}$/.test(row[4+stride])) throw "schema change? expected version in [4+stride]";
+ if (!/^0[.]0[.]1-[0-9a-f]{7,9}$/.test(row[2])) throw "schema change? expected version in [2]";
});
- const areas = data.area_keys.map((key, i) => ({key, results: rows.map((row, j) => ({
+ const areas = data.focus_areas.map((focusAreaName, i) => ({focusAreaName, results: rows.map((row, j) => ({
date: dates[j],
- legacy: row[3+i],
- servo: row[5+stride+i],
+ score: row[3+i].total_score / row[3+i].total_tests,
+ subtests: row[3+i].total_subtests_passed / row[3+i].total_subtests,
}))}));
console.log(">>> areas", areas);
const analysis = areas
- .map(({key, results: [p, q]}) => ({
- key,
- regressionWas: p.legacy - p.servo,
- regressionNow: q.legacy - q.servo,
- legacyWas: p.legacy,
- legacyNow: q.legacy,
- servoWas: p.servo,
- servoNow: q.servo,
+ .map(({focusAreaName, results: [p, q]}) => ({
+ focusAreaName,
+ scoreWas: p.score,
+ scoreNow: q.score,
+ subtestsWas: p.subtests,
+ subtestsNow: q.subtests,
}))
- .map(({key, regressionWas, regressionNow, legacyWas, legacyNow, servoWas, servoNow}) => ({
- key, regressionWas, regressionNow, legacyWas, legacyNow, servoWas, servoNow,
- legacyDelta: delta(legacyWas, legacyNow),
- servoDelta: delta(servoWas, servoNow),
- regressionDelta: delta(regressionWas, regressionNow),
+ .map(({focusAreaName, scoreWas, scoreNow, subtestsWas, subtestsNow}) => ({
+ focusAreaName, scoreWas, scoreNow, subtestsWas, subtestsNow,
+ scoreDelta: delta(scoreWas, scoreNow),
+ subtestsDelta: delta(subtestsWas, subtestsNow),
}));
console.log(">>> analysis", analysis);
- const deltaAnalysisText = analysis
- .sort((p,q) => q.servoDelta.pp - p.servoDelta.pp)
- .map(({key, servoDelta, servoNow}) => `${key} (${servoDelta.pp.toFixed(1)}pp to ${(servoNow/10).toFixed(1)}%)\n`);
- console.log(`>>> top deltas (servo, pp):\n${deltaAnalysisText.join("")}`);
- const regressionAnalysisText = analysis
- .filter(({regressionWas}) => regressionWas >= 0)
- .sort((p,q) => p.regressionDelta.percent - q.regressionDelta.percent)
- .map(({key, regressionDelta, regressionWas, regressionNow}) => `${key} (${regressionDelta.percent.toFixed(1)}% from ${(regressionWas/10).toFixed(1)}pp to ${(regressionNow/10).toFixed(1)}pp)\n`);
- console.log(`>>> top cuts in legacy regression (%):\n${regressionAnalysisText.join("")}`);
- function delta(p,q) { return {pp: (q-p)/10, percent: 100*(q-p)/p}; }
-})(await (await fetch("scores.json")).json(), fromDate, toDate)
+ const scoreAnalysisText = analysis
+ .sort((p,q) => q.scoreDelta.pp - p.scoreDelta.pp)
+ .map(({focusAreaName, scoreDelta, scoreNow}) => `${focusAreaName} (${scoreDelta.pp.toFixed(1)}pp to ${(scoreNow*100).toFixed(1)}%)\n`);
+ console.log(`>>> top deltas (score, pp):\n${scoreAnalysisText.join("")}`);
+ const subtestsAnalysisText = analysis
+ .sort((p,q) => q.subtestsDelta.pp - p.subtestsDelta.pp)
+ .map(({focusAreaName, subtestsDelta, subtestsNow}) => `${focusAreaName} (${subtestsDelta.pp.toFixed(1)}pp to ${(subtestsNow*100).toFixed(1)}%)\n`);
+ console.log(`>>> top deltas (subtests, pp):\n${subtestsAnalysisText.join("")}`);
+ function delta(p,q) { return {pp: (q-p)*100, percent: 100*(q-p)/p}; }
+})(await (await fetch("https://wpt.servo.org/scores.json")).json(), fromDate, toDate)
```
## How to analyse [wpt.fyi](https://wpt.fyi) pass rate improvements
@@ -347,7 +344,7 @@ Generally we want to include...
And generally we want to exclude...
- dependabot updates (“build(deps)”)
-- WPT imports (“Update web-platform-tests”)
+- WPT imports (“Update web-platform-tests” or “Sync WPT with upstream”)
- lint and warning fixes
- other CI changes
- refactors (unless large-scale)
@@ -375,6 +372,8 @@ $ tools/list-commits-by-nightly.sh ~/code/servo tools/pulls-2025-01-2025-02.json
- To write some notes or additional context, append `; your notes` to that new tags line
- Generate the outline: `tools/generate-outline.sh commits.txt`
+**TIP:** if you’re faced with hundreds of commits and it’s a real slog, try triaging the commits of one author at a time. Each author probably only works on a few things each month, so it’s a lot easier to keep the context of their work in your head.
+
## Hints for writing about changes
**Always check the correct names of people and API features.** People like it when their names are spelled correctly, of course, but sometimes authors refer to API features by incorrect names. When in doubt, check the spec. For example, [servo#32642](https://github.com/servo/servo/pull/32642) says “ShaderCompilationInfo” in the title, but the interface is actually [GPUCompilationInfo](https://developer.mozilla.org/en-US/docs/Web/API/GPUCompilationInfo), returned by the [getCompilationInfo() method on GPUShaderModule](https://developer.mozilla.org/en-US/docs/Web/API/GPUShaderModule/getCompilationInfo) ([July 2024](https://servo.org/blog/2024/07/31/this-month-in-servo/)).
diff --git a/_posts/2025-05-09-this-month-in-servo.md b/_posts/2025-05-09-this-month-in-servo.md
new file mode 100644
index 000000000..158d2c7cb
--- /dev/null
+++ b/_posts/2025-05-09-this-month-in-servo.md
@@ -0,0 +1,205 @@
+---
+layout: post
+tags: blog
+title: "Two months in Servo: CSS nesting, Shadow DOM, Clipboard API, and more!"
+date: 2025-05-09
+summary: Try our new about:memory page and --enable-experimental-web-platform-features.
+categories:
+---
+
+Before we start, let’s address the elephant in the room.
+Last month, we proposed that we would change our [AI contributions policy](https://book.servo.org/contributing.html#ai-contributions) to allow the use of AI tools in some situations, including GitHub Copilot for code.
+[The feedback we received](https://github.com/servo/servo/discussions/36379) from the community was overwhelmingly clear, and we’ve listened.
+We will **keep the AI contributions ban in place**, and any future proposals regarding this policy will be discussed together, as a community.
+
+At the same time, we have other big news!
+Complex sites such as **Gmail** and **Google Chat** are **now usable in Servo**, with some caveats.
+This milestone is only possible through the continued hard work of many Servo contributors across the engine, and we're thankful for all of the efforts to reach this point.
+
+
+
+
+
+
+Servo now supports single-valued **<select> elements** ([@simonwuelker](https://github.com/simonwuelker), [#35684](https://github.com/servo/servo/pull/35684), [#36677](https://github.com/servo/servo/pull/36677)), disabling stylesheets with **<link disabled>** ([@Loirooriol](https://github.com/Loirooriol), [#36446](https://github.com/servo/servo/pull/36446)), and the **Refresh** header in HTTP responses and <meta> ([@sebsebmc](https://github.com/sebsebmc), [#36393](https://github.com/servo/servo/pull/36393)), plus several new CSS features:
+
+- the **&** selector for **CSS nesting** ([@Loirooriol](https://github.com/Loirooriol), [#36246](https://github.com/servo/servo/pull/36246), [#36254](https://github.com/servo/servo/pull/36254), [#36248](https://github.com/servo/servo/pull/36248), [#36249](https://github.com/servo/servo/pull/36249))
+- **‘scale’**, **‘rotate’**, and **‘translate’** ([@chocolate-pie](https://github.com/chocolate-pie), [@Loirooriol](https://github.com/Loirooriol), [#35926](https://github.com/servo/servo/pull/35926))
+- **‘will-change’** ([@yezhizhen](https://github.com/yezhizhen), [#35787](https://github.com/servo/servo/pull/35787))
+- the **‘fit-content()’** sizing function ([@Loirooriol](https://github.com/Loirooriol), [#36056](https://github.com/servo/servo/pull/36056))
+- the **‘image-set()’** notation ([@chocolate-pie](https://github.com/chocolate-pie), [#36210](https://github.com/servo/servo/pull/36210))
+
+We’ve also landed a bunch of new web API features:
+
+- the **Response.json()** static method ([@sebsebmc](https://github.com/sebsebmc), [#36589](https://github.com/servo/servo/pull/36589), [#36523](https://github.com/servo/servo/pull/36523))
+- the **CSSStyleSheet()** constructor ([@Loirooriol](https://github.com/Loirooriol), [#36521](https://github.com/servo/servo/pull/36521))
+- the **seekable** property on **HTMLMediaElement** ([@rayguo17](https://github.com/rayguo17), [#36541](https://github.com/servo/servo/pull/36541))
+- the **label** property on **HTMLOptGroupElement** ([@simonwuelker](https://github.com/simonwuelker), [#35970](https://github.com/servo/servo/pull/35970))
+- the **align** property on **HTMLParagraphElement** ([@stephenmuss](https://github.com/stephenmuss), [#36054](https://github.com/servo/servo/pull/36054))
+- **ClipboardItem** and **navigator.clipboard.writeText()** ([@Gae24](https://github.com/Gae24), [#36336](https://github.com/servo/servo/pull/36336), [#36498](https://github.com/servo/servo/pull/36498))
+- **addRule()**, **removeRules()**, **replaceSync()**, and the **rules** property on **CSSStyleSheet** ([@Loirooriol](https://github.com/Loirooriol), [@webbeef](https://github.com/webbeef), [#36313](https://github.com/servo/servo/pull/36313), [#36586](https://github.com/servo/servo/pull/36586))
+- **getLineDash()**, **setLineDash()**, and **lineDashOffset** on **CanvasRenderingContext2D** ([@stevennovaryo](https://github.com/stevennovaryo), [#36257](https://github.com/servo/servo/pull/36257))
+- **ReadableByteStreamController** and **pipeTo()** on **ReadableStream** ([@Taym95](https://github.com/Taym95), [@gterzian](https://github.com/gterzian), [#35410](https://github.com/servo/servo/pull/35410), [#35650](https://github.com/servo/servo/pull/35650))
+
+
+
+[The biggest engine improvements](https://servo.org/wpt/) we’ve made recently were in **Shadow DOM** (+70.0pp to 77.9%), the **Trusted Types API** (+57.8pp to 57.8%), **Content Security Policy** (+54.0pp to 54.8%), the **Streams API** (+31.9pp to 68.1%), and **CSS Text** (+20.4pp to 57.6%).
+
+We've **enabled Shadow DOM by default** after significantly improving support, allowing Servo to render sites like [wpt.fyi](https://wpt.fyi) correctly ([@simonwuelker](https://github.com/simonwuelker), [@longvatron111](https://github.com/longvatron111), [@elomscansio](https://github.com/elomscansio), [@jdm](https://github.com/jdm), [@sakupi01](https://github.com/sakupi01), [#35923](https://github.com/servo/servo/pull/35923), [#35899](https://github.com/servo/servo/pull/35899), [#35930](https://github.com/servo/servo/pull/35930), [#36104](https://github.com/servo/servo/pull/36104), [#34964](https://github.com/servo/servo/pull/34964), [#36024](https://github.com/servo/servo/pull/36024), [#36106](https://github.com/servo/servo/pull/36106), [#36173](https://github.com/servo/servo/pull/36173), [#36010](https://github.com/servo/servo/pull/36010), [#35769](https://github.com/servo/servo/pull/35769), [#36230](https://github.com/servo/servo/pull/36230), [#36620](https://github.com/servo/servo/pull/36620)).
+
+
+
+**ReadableStream**, **WritableStream**, **DOMPoint**, **DOMPointReadOnly**, and **DOMException** can now be sent over **postMessage()** and **structuredClone()** ([@gterzian](https://github.com/gterzian), [@kkoyung](https://github.com/kkoyung), [@jdm](https://github.com/jdm), [@mrobinson](https://github.com/mrobinson), [#36181](https://github.com/servo/servo/pull/36181), [#36588](https://github.com/servo/servo/pull/36588), [#36535](https://github.com/servo/servo/pull/36535), [#35989](https://github.com/servo/servo/pull/35989)).
+
+We’ve started working on support for **stream transforms** ([@Taym95](https://github.com/Taym95), [#36470](https://github.com/servo/servo/pull/36470)) and the [**trusted types API**](https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API) ([@TimvdLippe](https://github.com/TimvdLippe), [@jdm](https://github.com/jdm), [#36354](https://github.com/servo/servo/pull/36354), [#36355](https://github.com/servo/servo/pull/36355), [#36422](https://github.com/servo/servo/pull/36422), [#36454](https://github.com/servo/servo/pull/36454), [#36409](https://github.com/servo/servo/pull/36409), [#36363](https://github.com/servo/servo/pull/36363), [#36511](https://github.com/servo/servo/pull/36511), [#36596](https://github.com/servo/servo/pull/36596)).
+We’ve also laid the groundwork for supporting the **::marker pseudo element** ([@mrobinson](https://github.com/mrobinson), [#36202](https://github.com/servo/servo/pull/36202)), **animated images** in web content ([@rayguo17](https://github.com/rayguo17), [#36058](https://github.com/servo/servo/pull/36058), [#36141](https://github.com/servo/servo/pull/36141)), and **getClientRects()** and **getBoundingClientRect()** on **Range** ([@simonwuelker](https://github.com/simonwuelker), [#35993](https://github.com/servo/servo/pull/35993)).
+
+Servo can now render the **caret** and **text selection** in input fields ([@dklassic](https://github.com/dklassic), [@webbeef](https://github.com/webbeef), [#35830](https://github.com/servo/servo/pull/35830), [#36478](https://github.com/servo/servo/pull/36478)), and we’ve landed a few fixes to **radio buttons** ([@elomscansio](https://github.com/elomscansio), [#36252](https://github.com/servo/servo/pull/36252), [#36431](https://github.com/servo/servo/pull/36431)), **file inputs** ([@sebsebmc](https://github.com/sebsebmc), [#36458](https://github.com/servo/servo/pull/36458)), and **input validation** ([@MDCODE247](https://github.com/MDCODE247), [#36236](https://github.com/servo/servo/pull/36236)).
+
+Having disabled by default Servo's original, experimental layout implementation [back in November 2024]({{ '/blog/2024/12/09/this-month-in-servo/' | url }}), we’ve now taken the step of **deleting all of the disabled code** ([@Loirooriol](https://github.com/Loirooriol), [@TimvdLippe](https://github.com/TimvdLippe), [@mrobinson](https://github.com/mrobinson), [#35943](https://github.com/servo/servo/pull/35943), [#36281](https://github.com/servo/servo/pull/36281), [#36698](https://github.com/servo/servo/pull/36698)) and moving all of the remaining layout code to [`layout`](https://doc.servo.org/layout/) ([@mrobinson](https://github.com/mrobinson), [#36613](https://github.com/servo/servo/pull/36613)).
+Our new layout engine is improving significantly month over month!
+
+We’ve added a **new `--enable-experimental-web-platform-features` option** that enables all engine features, even those that may not be stable or complete.
+This works much like [Chromium’s option with the same name](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/RuntimeEnabledFeatures.md;drc=a4e3e1f59b6f4bcf64806cf40c1acbb043b0bddc), and it can be useful when a page is not functioning correctly, since it may allow the page to make further progress.
+Servo now uses this option when running the Web Platform Tests ([@Loirooriol](https://github.com/Loirooriol), [#36335](https://github.com/servo/servo/pull/36335), [#36519](https://github.com/servo/servo/pull/36519), [#36348](https://github.com/servo/servo/pull/36348), [#36475](https://github.com/servo/servo/pull/36475)), and the features enabled by this option are expected to change over time.
+
+## Servo-the-browser (servoshell)
+
+[Our devtools integration](https://book.servo.org/hacking/using-devtools.html) now **supports iframes** ([@simonwuelker](https://github.com/simonwuelker), [#35874](https://github.com/servo/servo/pull/35874)) and **color scheme simulation** ([@uthmaniv](https://github.com/uthmaniv), [#36253](https://github.com/servo/servo/pull/36253), [#36168](https://github.com/servo/servo/pull/36168), [#36297](https://github.com/servo/servo/pull/36297)), shows **computed display values** when inspecting elements ([@stephenmuss](https://github.com/stephenmuss), [#35870](https://github.com/servo/servo/pull/35870)), and supports **multiple tabs** open in the servoshell browser ([@atbrakhi](https://github.com/atbrakhi), [#35884](https://github.com/servo/servo/pull/35884)).
+We’ve also landed the beginnings of a **Sources panel** ([@delan](https://github.com/delan), [@atbrakhi](https://github.com/atbrakhi), [#36164](https://github.com/servo/servo/pull/36164), [#35971](https://github.com/servo/servo/pull/35971), [#36631](https://github.com/servo/servo/pull/36631), [#36632](https://github.com/servo/servo/pull/36632), [#36667](https://github.com/servo/servo/pull/36667)).
+To use devtools, we now **require Firefox 133 or newer** ([@atbrakhi](https://github.com/atbrakhi), [#35792](https://github.com/servo/servo/pull/35792)).
+
+Dialogs support **keyboard interaction** to close and cancel them ([@chickenleaf](https://github.com/chickenleaf), [#3567](https://github.com/servo/servo/pull/3567)), and the URL bar accepts any **domain-like input** ([@kafji](https://github.com/kafji), [#35756](https://github.com/servo/servo/pull/35756)).
+We’ve also enabled **sRGB colorspaces** on macOS for better colour fidelity ([@IsaacMarovitz](https://github.com/IsaacMarovitz), [#35683](https://github.com/servo/servo/pull/35683)).
+Using the `--userscripts` option without providing a path **defaults to `resources/user-agent-js`**.
+Finally, we’ve **renamed the OpenHarmony app bundle** ([@jschwe](https://github.com/jschwe), [#35790](https://github.com/servo/servo/pull/35790)).
+
+## Servo-the-engine (embedding)
+
+We’ve landed some big changes to our webview API:
+
+- **pinch zoom**, **page zoom**, and **HiDPI scaling** are now handled independently **for each webview** ([@mrobinson](https://github.com/mrobinson), [@shubhamg13](https://github.com/shubhamg13), [#36419](https://github.com/servo/servo/pull/36419), [#36312](https://github.com/servo/servo/pull/36312))
+- **mouse click events** no longer need to be generated by the embedder, only mouse button down and up events ([@yezhizhen](https://github.com/yezhizhen), [#36413](https://github.com/servo/servo/pull/36413))
+- webviews are now created with [**WebViewBuilder**](https://doc.servo.org/servo/struct.WebViewBuilder.html) ([@mrobinson](https://github.com/mrobinson), [#36483](https://github.com/servo/servo/pull/36483))
+- **EmbedderMethods** is now [**ServoBuilder**](https://doc.servo.org/servo/struct.ServoBuilder.html) ([@mrobinson](https://github.com/mrobinson), [#36276](https://github.com/servo/servo/pull/36276), [#36549](https://github.com/servo/servo/pull/36549))
+- **WindowMethods** have moved to [**WebViewDelegate**](https://doc.servo.org/servo/webview_delegate/trait.WebViewDelegate.html) and [**ServoDelegate**](https://doc.servo.org/servo/servo_delegate/trait.ServoDelegate.html) ([@mrobinson](https://github.com/mrobinson), [#36223](https://github.com/servo/servo/pull/36223), [#36400](https://github.com/servo/servo/pull/36400))
+
+Embedders can now **inject userscript sources** into all webviews ([@Legend-Master](https://github.com/Legend-Master), [#35388](https://github.com/servo/servo/pull/35388)).
+Links can be **opened in a new tab** by pressing the **Ctrl** or **⌘** modifier ([@webbeef](https://github.com/webbeef), [@mrobinson](https://github.com/mrobinson), [#35017](https://github.com/servo/servo/pull/35017)).
+Delegates will receive **send error notifications** for requests ([@delan](https://github.com/delan), [#35668](https://github.com/servo/servo/pull/35668)), and we made progress towards a per-webview renderer model ([@mrobinson](https://github.com/mrobinson), [@delan](https://github.com/delan), [#35701](https://github.com/servo/servo/pull/35701), [#35716](https://github.com/servo/servo/pull/35716)).
+
+We fixed a bug causing **flickering cursors** ([@DevGev](https://github.com/DevGev), [#35934](https://github.com/servo/servo/pull/35934)), and now **create the config directory** if it does not exist ([@yezhizhen](https://github.com/yezhizhen), [#35761](https://github.com/servo/servo/pull/35761)).
+We also fixed a number of bugs in the WebDriver server related to clicking on elements, opening and closing windows, and returning references to exotic objects ([@jdm](https://github.com/jdm), [#35737](https://github.com/servo/servo/pull/35737)).
+
+## Under the hood
+
+We’ve finally finished **splitting up our massive `script` crate** ([@jdm](https://github.com/jdm), [#35988](https://github.com/servo/servo/pull/35988), [#35987](https://github.com/servo/servo/pull/35987), [#36107](https://github.com/servo/servo/pull/36107), [#36216](https://github.com/servo/servo/pull/36216), [#36220](https://github.com/servo/servo/pull/36220), [#36095](https://github.com/servo/servo/pull/36095), [#36323](https://github.com/servo/servo/pull/36323)), which should **cut incremental build times** for that crate **by 60%**.
+This is something we’ve wanted to do for **over eleven years** ([@kmcallister](https://github.com/kmcallister), [#1799](https://github.com/servo/servo/issues/1799))!
+
+**`webgpu` rebuilds are now faster** as well, with changes to that crate no longer requiring a `script` rebuild ([@mrobinson](https://github.com/mrobinson), [#36332](https://github.com/servo/servo/pull/36332), [#36320](https://github.com/servo/servo/pull/36320)).
+
+**Stylo** has been upgraded to 2025-03-15 ([@nicoburns](https://github.com/nicoburns), [@Loirooriol](https://github.com/Loirooriol), [#35782](https://github.com/servo/servo/pull/35782), [#35925](https://github.com/servo/servo/pull/35925), [#35990](https://github.com/servo/servo/pull/35990)), and we upgraded to the **2024 Rust edition** ([@simonwuelker](https://github.com/simonwuelker), [#35755](https://github.com/servo/servo/pull/35755)).
+
+We added a memory usage view for Servo embedders: **visit about:memory** for a breakdown of identified allocations ([@webbeef](https://github.com/webbeef), [@jdm](https://github.com/jdm), [#35728](https://github.com/servo/servo/pull/35728), [#36557](https://github.com/servo/servo/pull/36557), [#36558](https://github.com/servo/servo/pull/36558), [#36556](https://github.com/servo/servo/pull/36556), [#36581](https://github.com/servo/servo/pull/36581), [#36553](https://github.com/servo/servo/pull/36553), [#36664](https://github.com/servo/servo/pull/36664)).
+
+
+
+## Perf and stability
+
+We’ve started building an **incremental layout** system in Servo ([@mrobinson](https://github.com/mrobinson), [@Loirooriol](https://github.com/Loirooriol), [#36404](https://github.com/servo/servo/pull/36404), [#36448](https://github.com/servo/servo/pull/36448), [#36447](https://github.com/servo/servo/pull/36447), [#36513](https://github.com/servo/servo/pull/36513)), with a huge speedup to **offsetWidth**, **offsetHeight**, **offsetLeft**, **offsetTop**, and **offsetParent** layout queries ([@mrobinson](https://github.com/mrobinson), [@Loirooriol](https://github.com/Loirooriol), [#36583](https://github.com/servo/servo/pull/36583), [#36629](https://github.com/servo/servo/pull/36629), [#36681](https://github.com/servo/servo/pull/36681), [#36663](https://github.com/servo/servo/pull/36663)).
+Incremental layout will allow Servo to respond to page updates and layout queries without repeating layout work, which is critical on today’s highly dynamic web.
+
+[OffscreenRenderingContext](https://doc.servo.org/servo/struct.OffscreenRenderingContext.html) is **no longer double buffered**, which can improve rendering performance in embeddings that rely on it.
+We also removed a source of **canvas rendering latency** ([@sagudev](https://github.com/sagudev), [#35719](https://github.com/servo/servo/pull/35719)), and fixed performance cliffs related to the Shadow DOM ([@simonwuelker](https://github.com/simonwuelker), [#35802](https://github.com/servo/servo/pull/35802), [#35725](https://github.com/servo/servo/pull/35725)).
+We improved layout performance by **reducing allocations** ([@jschwe](https://github.com/jschwe), [#35781](https://github.com/servo/servo/pull/35781)) and **caching layout results** ([@Loirooriol](https://github.com/Loirooriol), [@mrobinson](https://github.com/mrobinson), [#36082](https://github.com/servo/servo/pull/36082)), and reduced the **latency of touch events** when they are non-cancelable ([@kongbai1996](https://github.com/kongbai1996), [#35785](https://github.com/servo/servo/pull/35785)).
+
+We also fixed crashes involving **touch events** ([@kongbai1996](https://github.com/kongbai1996), [@jschwe](https://github.com/jschwe), [#35763](https://github.com/servo/servo/pull/35763), [#36531](https://github.com/servo/servo/pull/36531), [#36229](https://github.com/servo/servo/pull/36229)), **service workers** ([@jdm](https://github.com/jdm), [#36256](https://github.com/servo/servo/pull/36256)), **WritableStream** ([@Taym95](https://github.com/Taym95), [#36566](https://github.com/servo/servo/pull/36566)), **Location** ([@jdm](https://github.com/jdm), [#36494](https://github.com/servo/servo/pull/36494)), **<canvas>** ([@tharkum](https://github.com/tharkum), [@simonwuelker](https://github.com/simonwuelker), [#36569](https://github.com/servo/servo/pull/36569), [#36705](https://github.com/servo/servo/pull/36705)), **<input>** ([@dklassic](https://github.com/dklassic), [#36461](https://github.com/servo/servo/pull/36461)), **<iframe>** ([@leftmostcat](https://github.com/leftmostcat), [#35742](https://github.com/servo/servo/pull/35742)), **‘min-content’** and **‘max-content’** ([@Loirooriol](https://github.com/Loirooriol), [#36518](https://github.com/servo/servo/pull/36518), [#36571](https://github.com/servo/servo/pull/36571)), **flexbox** ([@mrobinson](https://github.com/mrobinson), [#36123](https://github.com/servo/servo/pull/36123)), global objects ([@jdm](https://github.com/jdm), [#36491](https://github.com/servo/servo/pull/36491)), resizing the viewport ([@sebsebmc](https://github.com/sebsebmc), [#35967](https://github.com/servo/servo/pull/35967)), and `--pref shell_background_color_rgba` ([@boluochoufeng](https://github.com/boluochoufeng), [#35865](https://github.com/servo/servo/pull/35865)).
+Additionally, we **removed undefined behaviour** from the Rust bindings to the SpiderMonkey engine ([@gmorenz](https://github.com/gmorenz), [#35892](https://github.com/servo/servo/pull/35892), [#36160](https://github.com/servo/servo/pull/36160), [#36161](https://github.com/servo/servo/pull/36161), [#36158](https://github.com/servo/servo/pull/36158)).
+
+The project to decrease the risk of [intermittent GC-related crashes](https://github.com/servo/servo/issues/33140) continues to make progress ([@jdm](https://github.com/jdm), [@Arya-A-Nair](https://github.com/Arya-A-Nair), [@Dericko681](https://github.com/Dericko681), [@yerke](https://github.com/yerke), [#35753](https://github.com/servo/servo/pull/35753), [#36014](https://github.com/servo/servo/pull/36014), [#36043](https://github.com/servo/servo/pull/36043), [#36156](https://github.com/servo/servo/pull/36156), [#36116](https://github.com/servo/servo/pull/36116), [#36180](https://github.com/servo/servo/pull/36180), [#36111](https://github.com/servo/servo/pull/36111), [#36375](https://github.com/servo/servo/pull/36375), [#36371](https://github.com/servo/servo/pull/36371), [#36395](https://github.com/servo/servo/pull/36395), [#36392](https://github.com/servo/servo/pull/36392), [#36464](https://github.com/servo/servo/pull/36464), [#36504](https://github.com/servo/servo/pull/36504), [#36495](https://github.com/servo/servo/pull/36495), [#36492](https://github.com/servo/servo/pull/36492)).
+
+## More changes
+
+Our flexbox implementation supports **min/max keyword sizes** for both cross and main axes ([@Loirooriol](https://github.com/Loirooriol), [#35860](https://github.com/servo/servo/pull/35860), [#35961](https://github.com/servo/servo/pull/35961)), as well as **keyword sizes** for non-replaced content ([@Loirooriol](https://github.com/Loirooriol), [#35826](https://github.com/servo/servo/pull/35826)) and **min and max sizing properties** ([@Loirooriol](https://github.com/Loirooriol), [#36015](https://github.com/servo/servo/pull/36015)).
+As a result, we now have full support for size keywords in flexbox!
+
+We made lots of progress on web API features:
+* added **partial support for IntersectionObserver** ([@stevennovaryo](https://github.com/stevennovaryo), [#35551](https://github.com/servo/servo/pull/35551))
+* initial work on implementing the URLPattern API ([@simonwuelker](https://github.com/simonwuelker), [#36144](https://github.com/servo/servo/pull/36144))
+* history.replaceState() can be called from file:// documents ([@kkoyung](https://github.com/kkoyung), [#35864](https://github.com/servo/servo/pull/35864))
+* members of radio input groups apply validity constraints more consistently ([@jerensl](https://github.com/jerensl), [@elomscansio](https://github.com/elomscansio), [@Barry-dE](https://github.com/Barry-dE), [#36197](https://github.com/servo/servo/pull/36197), [#36090](https://github.com/servo/servo/pull/36090), [#36103](https://github.com/servo/servo/pull/36103))
+* file inputs show the selected file ([@dklassic](https://github.com/dklassic), [#35789](https://github.com/servo/servo/pull/35789))
+
+On security and networking:
+* the `nonce` attribute is used in Content Security Policy checks ([@simonwuelker](https://github.com/simonwuelker), [#35876](https://github.com/servo/servo/pull/35876))
+* Request objects with FormData bodies use the correct Content-Type ([@andreubotella](https://github.com/andreubotella), [#36194](https://github.com/servo/servo/pull/36194))
+* text response bodies containing a BOM consume it ([@andreubotella](https://github.com/andreubotella), [#36192](https://github.com/servo/servo/pull/36192))
+* notifications fetch associated image resources ([@pewsheen](https://github.com/pewsheen), [#35878](https://github.com/servo/servo/pull/35878))
+
+On the DOM:
+* passive event listeners can be created ([@shanehandley](https://github.com/shanehandley), [#35877](https://github.com/servo/servo/pull/35877))
+* removing an event listener that has not run prevents it from running ([@tharkum](https://github.com/tharkum), [#36163](https://github.com/servo/servo/pull/36163))
+* we removed some cases where custom element callbacks fired incorrectly ([@xiaochengh](https://github.com/xiaochengh), [#35960](https://github.com/servo/servo/pull/35960), [#35883](https://github.com/servo/servo/pull/35883))
+* `touchmove` events are more reliable ([@kongbai1996](https://github.com/kongbai1996), [#36218](https://github.com/servo/servo/pull/36218) [#36200](https://github.com/servo/servo/pull/36200)) and support the `cancelable` property ([@kongbai1996](https://github.com/kongbai1996), [#35713](https://github.com/servo/servo/pull/35713))
+* ResizeObserver callbacks are only invoked when elements change size ([@simonwuelker](https://github.com/simonwuelker), [#36226](https://github.com/servo/servo/pull/36226))
+* cancelled enqueued animation frame callbacks no longer run ([@xiaochengh](https://github.com/xiaochengh), [#35849](https://github.com/servo/servo/pull/35849))
+* scripts are no longer executed in documents that should disable scripting ([@simonwuelker](https://github.com/simonwuelker), [#35871](https://github.com/servo/servo/pull/35871))
+* script elements adopted between documents use the original document to determine when to execute ([@xiaochengh](https://github.com/xiaochengh), [#35718](https://github.com/servo/servo/pull/35718))
+
+And on many other bugs:
+* **Backspace no longer removes entire lines** in <textarea> ([@elomscansio](https://github.com/elomscansio), [@jdm](https://github.com/jdm), [#36112](https://github.com/servo/servo/pull/36112))
+* improved **overflow handling** in some special cases ([@yezhizhen](https://github.com/yezhizhen), [#35670](https://github.com/servo/servo/pull/35670))
+* fixed incorrect **fallback font** caching ([@mrobinson](https://github.com/mrobinson), [#35705](https://github.com/servo/servo/pull/35705))
+* fixed the intrinsic block size of replaced elements with auto width ([@Loirooriol](https://github.com/Loirooriol), [#35275](https://github.com/servo/servo/pull/35275))
+* ‘table-layout: fixed’ is no longer ignored when ‘inline-size’ is ‘auto’ ([@Loirooriol](https://github.com/Loirooriol), [#35882](https://github.com/servo/servo/pull/35882))
+* margins of block-level box stretches are always zero, regardless of collapsing status ([@Loirooriol](https://github.com/Loirooriol), [#35904](https://github.com/servo/servo/pull/35904))
+* indefinite stretch contributes to intrinsic sizes ([@Loirooriol](https://github.com/Loirooriol), [#36030](https://github.com/servo/servo/pull/36030))
+* static positions include ancestor padding ([@Loirooriol](https://github.com/Loirooriol), [#36051](https://github.com/servo/servo/pull/36051))
+* table rows with a span of >1 are sized appropriately ([@PotatoCP](https://github.com/PotatoCP), [#36064](https://github.com/servo/servo/pull/36064))
+* input element contents ignore any outer display value ([@PotatoCP](https://github.com/PotatoCP), [#35908](https://github.com/servo/servo/pull/35908))
+* indexing properties with values near 2^32 resolves correctly ([@reesmichael1](https://github.com/reesmichael1), [#36136](https://github.com/servo/servo/pull/36136))
+
+## Donations
+
+Thanks again for your generous support!
+We are now receiving **4664 USD/month** (+6.8% over February) in recurring donations.
+This helps cover the cost of our [self-hosted](https://ci0.servo.org) [CI](https://ci1.servo.org) [runners](https://ci2.servo.org) and our [latest Outreachy interns](https://www.outreachy.org/alums/2025-06/#:~:text=Servo), **Usman Baba Yahaya** ([@uthmaniv](https://github.com/uthmaniv)) and **Jerens Lensun** ([@jerensl](https://github.com/jerensl))!
+
+Servo is also on [thanks.dev](https://thanks.dev), and already **24 GitHub users** (+3 over February) that depend on Servo are sponsoring us there.
+If you use Servo libraries like [url](https://crates.io/crates/url/reverse_dependencies), [html5ever](https://crates.io/crates/html5ever/reverse_dependencies), [selectors](https://crates.io/crates/selectors/reverse_dependencies), or [cssparser](https://crates.io/crates/cssparser/reverse_dependencies), signing up for [thanks.dev](https://thanks.dev) could be a good way for you (or your employer) to give back to the community.
+
+
+
+
4664 USD/month
+
+
+
10000
+
+
+
+
+As always, use of these funds will be decided transparently in the Technical Steering Committee.
+For more details, head to our [Sponsorship page]({{ '/sponsorship/' | url }}).
+
+## Conference talks
+
+**Josh Matthews** ([@jdm](https://github.com/jdm)) will be [speaking about Servo](https://rustweek.org/talks/josh/) at [**RustWeek 2025**](https://rustweek.org), on **Tuesday 13 May** at [17:05 local time](https://everytimezone.com/?t=68228b80,929) (15:05 UTC).
+See you there!
+
+
diff --git a/assets/img/blog/2025-04-about-memory.png b/assets/img/blog/2025-04-about-memory.png
new file mode 100644
index 000000000..93921ab11
Binary files /dev/null and b/assets/img/blog/2025-04-about-memory.png differ
diff --git a/assets/img/blog/2025-04-servo-aboutmemory.png b/assets/img/blog/2025-04-servo-aboutmemory.png
new file mode 100644
index 000000000..9513c0249
Binary files /dev/null and b/assets/img/blog/2025-04-servo-aboutmemory.png differ
diff --git a/assets/img/blog/2025-04-servo-gchat.png b/assets/img/blog/2025-04-servo-gchat.png
new file mode 100644
index 000000000..ee9ff4b8a
Binary files /dev/null and b/assets/img/blog/2025-04-servo-gchat.png differ
diff --git a/assets/img/blog/2025-04-servo-gmail.png b/assets/img/blog/2025-04-servo-gmail.png
new file mode 100644
index 000000000..55f46f3b4
Binary files /dev/null and b/assets/img/blog/2025-04-servo-gmail.png differ
diff --git a/assets/img/blog/2025-04-servo-wptfyi.png b/assets/img/blog/2025-04-servo-wptfyi.png
new file mode 100644
index 000000000..9bdfe290c
Binary files /dev/null and b/assets/img/blog/2025-04-servo-wptfyi.png differ
diff --git a/assets/img/blog/april-2025.png b/assets/img/blog/april-2025.png
new file mode 100644
index 000000000..612d50dcc
Binary files /dev/null and b/assets/img/blog/april-2025.png differ
diff --git a/tools/generate-outline.sh b/tools/generate-outline.sh
index 9e91112b1..ec203eb93 100755
--- a/tools/generate-outline.sh
+++ b/tools/generate-outline.sh
@@ -18,7 +18,7 @@ for tag in $tags; do
# The second line of the input is of the form ` one or more tags` or ` tags; notes`.
# Tags must not contain spaces or PCRE regex metacharacters.
< "$post_path" rg -v '^ # ' \
- | rg --pcre2 -B1 --no-context-separator '(?<=^ )(([^;]+ )?'"$tag"'( [^;]+)?)(;|$)' \
+ | rg --pcre2 -B1 --no-context-separator '(?<=^ )(([^;]+ )?'"$tag"'( [^;]+)?) *(;|$)' \
| while read -r list_commits_by_nightly_line; do
read -r tags_and_notes_line
printf ' - %s\n %s\n' "${list_commits_by_nightly_line#+}" "${tags_and_notes_line# }"
diff --git a/tools/list-commits-between.sh b/tools/list-commits-between.sh
index a66586d28..deac7e318 100755
--- a/tools/list-commits-between.sh
+++ b/tools/list-commits-between.sh
@@ -49,6 +49,6 @@ git -C "$1" log --pretty=$'tformat:%H\t%s\t%aE\t%(trailers:key=co-authored-by,va
# Print the commit message body, with a hard wrap and an indent.
# This doesn’t work too well, because our repo is configured to concatenate the PR commit
# messages, which often contain a subject only, rather than using the PR description.
- git -C "$1" log --pretty=$'tformat:%w(120,4,4)%b' | sed -E '/^ *$/d;/^ Signed-off-by: /d'
+ git -C "$1" log -n 1 --pretty=$'tformat:%w(120,4,4)%b' "$hash" | sed -E '/^ *$/d;/^ Signed-off-by: /d'
fi
done
diff --git a/tools/list-commits-by-nightly.sh b/tools/list-commits-by-nightly.sh
index ada0f667e..8b546001a 100755
--- a/tools/list-commits-by-nightly.sh
+++ b/tools/list-commits-by-nightly.sh
@@ -6,7 +6,7 @@ if [ $# -lt 1 ]; then >&2 sed '1d;2s/^# //;2q' "$0"; exit 1; fi
missing() { >&2 echo "fatal: $1 not found"; exit 1; }
> /dev/null command -v gh || missing gh
> /dev/null command -v jq || missing jq
-> /dev/null command -v tac || missing tac
+> /dev/null command -v tac && tac='tac' || tac='tail -r'
> /dev/null command -v rg || missing rg
> /dev/null command -v git || missing git
pulls_json_path=${${2-/dev/null}:a}
@@ -19,7 +19,7 @@ default_branch_head=$(cut -f 1 "$1/.git/FETCH_HEAD")
if ! [ -e runs.json ]; then
gh api '/repos/servo/servo/actions/workflows/nightly.yml/runs?status=success&per_page=62' > runs.json
fi
-< runs.json jq -r '.workflow_runs[] | "\(.head_sha)\t\(.updated_at)"' | tac > runs.tsv
+< runs.json jq -r '.workflow_runs[] | "\(.head_sha)\t\(.updated_at)"' | $tac > runs.tsv
< runs.tsv sed -En '1!{H;x;s/\n//;p;x;};s/\t.*//;s/$/\t/;h' \
| while read -r from to updated; do
printf '>>> %s\n' "$updated" | rg . # make it red if stdout is a tty