Skip to content

script: Populate document.fonts with web fonts from CSS - #46509

Merged
simonwuelker merged 2 commits into
servo:mainfrom
simonwuelker:populate-document-fonts
Jul 15, 2026
Merged

simonwuelker merged 2 commits into
servo:mainfrom
simonwuelker:populate-document-fonts

Conversation

@simonwuelker

Copy link
Copy Markdown
Member

Previously document.fonts only contained font faces explicitly added to it via javascript, but each valid @font-face rule should also have an entry here. This fixes intermittency in one test that reads document.fonts to wait for web fonts to load, but previously waited on nothing because document.fonts was empty.

Testing: New tests start to pass
Fixes #46434

@servo-highfive servo-highfive added the S-awaiting-review There is new code that needs to be reviewed. label Jul 14, 2026
@@ -1,9 +1,10 @@
[font-unicode-PUA.html]
expected: TIMEOUT

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This timeout represents progress: The test runs

promise_setup(_ => Promise.all([...document.fonts].map(f => f.load())));

which previously waited on nothing because document.fonts was always empty. That caused intermittent passes. Now it waits forever because we never mark FontFaces backed by @font-face rules as loaded. We can fix that in a later change.

@simonwuelker simonwuelker added the T-linux-wpt Do a try run of the WPT label Jul 14, 2026
@github-actions github-actions Bot removed the T-linux-wpt Do a try run of the WPT label Jul 14, 2026
@github-actions

Copy link
Copy Markdown

🔨 Triggering try run (#29372221445) for Linux (WPT)

@github-actions

Copy link
Copy Markdown

Test results for linux-wpt from try job (#29372221445):

Flaky unexpected result (31)
  • TIMEOUT /FileAPI/url/url-in-tags-revoke.window.html (#19978)
    • PASS [expected TIMEOUT] subtest: Fetching a blob URL immediately before revoking it works in <script> tags.
  • CRASH [expected OK] /_webgl/conformance/glsl/misc/shader-uniform-packing-restrictions.html
  • TIMEOUT [expected OK] /_webgl/conformance/textures/misc/texture-video-transparent.html
    • NOTRUN [expected PASS] subtest: Overall test
  • TIMEOUT [expected OK] /_webgl/conformance/uniforms/no-over-optimization-on-uniform-array-06.html
    • NOTRUN [expected PASS] subtest: Overall test
  • CRASH [expected OK] /_webgl/conformance/uniforms/no-over-optimization-on-uniform-array-14.html
  • TIMEOUT [expected OK] /_webgl/conformance/uniforms/no-over-optimization-on-uniform-array-17.html
    • NOTRUN [expected PASS] subtest: Overall test
  • TIMEOUT [expected OK] /_webgl/conformance2/state/gl-object-get-calls.html
    • NOTRUN [expected PASS] subtest: Overall test
  • TIMEOUT [expected OK] /_webgl/conformance2/transform_feedback/transform_feedback.html
  • OK /cookies/partitioned-cookies/partitioned-cookies-samesite-attribute.https.html (#45255)
    • FAIL [expected PASS] subtest: In embedded cross-site contexts, partitioned cookies can only be set with explicit SameSite=None

      assert_equals: expected 0 but got 3
      

  • CRASH [expected OK] /css/compositing/canvas-composite-modes.html
  • OK /css/css-cascade/layer-cssom-order-reverse.html (#36094)
    • FAIL [expected PASS] subtest: Delete layer invalidates @font-face

      assert_equals: expected "220px" but got "122.8px"
      

  • OK /css/css-fonts/variations/at-font-face-font-matching.html (#20684)
    • FAIL [expected PASS] subtest: Matching font-style: 'oblique -20deg' should prefer 'oblique -20deg' over 'oblique -60deg -40deg'

      assert_equals: Unexpected font on test element expected 487 but got 532
      

  • OK /css/css-grid/alignment/grid-alignment-implies-size-change-005.html
    • FAIL [expected PASS] subtest: .before 1

      assert_equals: 
      <div data-expected-width="100" id="item" class=" before" data-expected-height="100">XX X<br>X XXX<br>X<br>XX XXX</div>
      height expected 100 but got 80
      

  • FAIL [expected PASS] /css/css-sizing/dynamic-available-size-iframe.html (#45763)
  • TIMEOUT [expected OK] /fetch/content-encoding/br/big-br-body.https.any.worker.html
    • TIMEOUT [expected PASS] subtest: large br data should be decompressed successfully

      Test timed out
      

    • NOTRUN [expected FAIL] subtest: large br data should be decompressed successfully with byte stream
  • TIMEOUT [expected OK] /fetch/content-encoding/gzip/big-gzip-body.https.any.worker.html
    • TIMEOUT [expected PASS] subtest: large gzip data should be decompressed successfully

      Test timed out
      

    • NOTRUN [expected FAIL] subtest: large gzip data should be decompressed successfully with byte stream
  • TIMEOUT [expected OK] /fetch/content-encoding/zstd/big-zstd-body.https.any.html
  • OK /html/browsers/history/the-history-interface/traverse_the_history_5.html (#21383)
    • PASS [expected FAIL] subtest: Multiple history traversals, last would be aborted
  • TIMEOUT [expected OK] /html/browsers/origin/cross-origin-objects/cross-origin-objects.html
  • TIMEOUT [expected OK] /html/interaction/focus/the-autofocus-attribute/autofocus-dialog.html (#29087)
    • TIMEOUT [expected PASS] subtest: <dialog>-contained autofocus element gets focused when the dialog is shown

      Test timed out
      

  • TIMEOUT [expected OK] /html/interaction/focus/the-autofocus-attribute/update-the-rendering.html (#24145)
    • TIMEOUT [expected FAIL] subtest: "Flush autofocus candidates" should be happen before a scroll event and animation frame callbacks

      Test timed out
      

  • OK /html/semantics/embedded-content/media-elements/media_fragment_seek.html (#24114)
    • FAIL [expected PASS] subtest: Video should seek to time specified in media fragment syntax

      assert_equals: expected 1 but got 0
      

  • OK /html/semantics/embedded-content/media-elements/seeking/seek-to-currentTime.html (#46014)
    • FAIL [expected PASS] subtest: seek to currentTime

      assert_array_equals: fired events lengths differ, expected array ["seeking", "timeupdate", "seeked"] length 3, got ["seeking", "timeupdate", "timeupdate", "seeked"] length 4
      

  • OK /html/semantics/forms/form-submission-0/jsurl-form-submit.tentative.html (#36489)
    • PASS [expected FAIL] subtest: Verifies that form submissions scheduled inside javascript: urls take precedence over the javascript: url's return value.
  • TIMEOUT [expected OK] /html/syntax/parsing/html5lib_write_single.html?file=entities02
    • TIMEOUT [expected PASS] subtest: <div bar="ZZ>YY"></div>

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: <div bar="ZZ&"></div>

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: <div bar='ZZ&'></div>

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: <div bar=ZZ&></div>

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: <div bar="ZZ&gt=YY"></div>

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: <div bar="ZZ&gt0YY"></div>

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: <div bar="ZZ&gt9YY"></div>

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: <div bar="ZZ&gtaYY"></div>

      Test timed out
      

    • NOTRUN [expected PASS] subtest: <div bar="ZZ&gtZYY"></div>
    • NOTRUN [expected PASS] subtest: <div bar="ZZ&gt YY"></div>
    • And 16 more unexpected results...
  • TIMEOUT [expected OK] /html/user-activation/navigation-state-reset-sameorigin.html (#45151)
    • TIMEOUT [expected FAIL] subtest: Post-navigation state reset.

      Test timed out
      

  • TIMEOUT [expected OK] /pointerevents/compat/pointerevent_touch-action_two-finger_interaction.html (#44693)
    • NOTRUN [expected PASS] subtest: touch two-finger pan on 'touch-action: pan-x pan-y'
    • NOTRUN [expected FAIL] subtest: touch two-finger pan on 'touch-action: pinch-zoom'
  • OK [expected CRASH] /resource-timing/render-blocking-status-link.html (#41664)
    • FAIL [expected PASS] subtest: Validate render blocking status of link resources in PerformanceResourceTiming

      assert_equals: render blocking status for http://web-platform.test:8000/resource-timing/resources/empty_style.css?stylesheet-head-dynamic-docWrite should be blocking expected (string) "blocking" but got (undefined) undefined
      

  • CRASH [expected OK] /trusted-types/script-enforcement-016.html
  • OK [expected TIMEOUT] /trusted-types/trusted-types-navigation.html?01-05 (#38975)
    • PASS [expected TIMEOUT] subtest: Navigate a window via anchor with javascript:-urls in report-only mode.
    • PASS [expected NOTRUN] subtest: Navigate a window via anchor with javascript:-urls w/ default policy in report-only mode.
    • PASS [expected NOTRUN] subtest: Navigate a frame via anchor with javascript:-urls in enforcing mode.
  • CRASH [expected OK] /webstorage/storage_local_setitem_quotaexceedederr.window.html
Stable unexpected results that are known to be intermittent (18)
  • OK /_webgl/conformance/textures/misc/texture-upload-size.html (#21770)
    • FAIL [expected PASS] subtest: WebGL test #45

      assert_true: Texture was smaller than the expected size 2x2 expected true got false
      

    • FAIL [expected PASS] subtest: WebGL test #47

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : when calling texSubImage2D with the same texture upload with offset 1, 1 expected true got false
      

    • FAIL [expected PASS] subtest: WebGL test #49

      assert_true: Texture was smaller than the expected size 2x2 expected true got false
      

    • FAIL [expected PASS] subtest: WebGL test #51

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : when calling texSubImage2D with the same texture upload with offset 1, 1 expected true got false
      

    • PASS [expected FAIL] subtest: WebGL test #53
    • PASS [expected FAIL] subtest: WebGL test #55
    • PASS [expected FAIL] subtest: WebGL test #57
    • PASS [expected FAIL] subtest: WebGL test #59
    • FAIL [expected PASS] subtest: WebGL test #61

      assert_true: Texture was smaller than the expected size 2x2 expected true got false
      

    • FAIL [expected PASS] subtest: WebGL test #63

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : when calling texSubImage2D with the same texture upload with offset 1, 1 expected true got false
      

    • And 10 more unexpected results...
  • OK /beacon/beacon-basic.https.window.html (#41723)
    • FAIL [expected PASS] subtest: Payload size restriction should be accumulated: type = string

      assert_false: expected false got true
      

    • PASS [expected FAIL] subtest: Payload size restriction should be accumulated: type = arraybuffer
  • TIMEOUT /content-security-policy/inheritance/auxiliary-blank-document.html (#44821)
    • FAIL [expected PASS] subtest: For transient doc, violation event occurs in auxiliary, not opener window.

      assert_equals: expected Document node with 1 child but got Document node with 1 child
      

  • OK [expected TIMEOUT] /fetch/api/redirect/redirect-keepalive.https.any.html (#32153)
    • PASS [expected TIMEOUT] subtest: [keepalive][iframe][load] mixed content redirect; setting up
  • OK [expected ERROR] /fetch/fetch-later/quota/same-origin-iframe/multiple-iframes.https.window.html (#35176)
  • OK /html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-same-origin.window.html (#29049)
    • FAIL [expected PASS] subtest: Same-origin navigation started from unload handler must be ignored

      assert_equals: expected "?pass" but got "?fail"
      

  • TIMEOUT [expected OK] /html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-requestsubmit.html (#44098)
    • TIMEOUT [expected FAIL] subtest: Replace before load, triggered by formElement.requestSubmit()

      Test timed out
      

  • OK [expected TIMEOUT] /html/syntax/parsing/html5lib_write.html?file=processing-instructions (#46205)
  • OK /paint-timing/with-lcp/fcp-document-opacity-image.html (#43068)
    • PASS [expected FAIL] subtest: Test that FCP after opacity change is not a larger value than LCP
  • OK /pointerevents/pointerevent_pointercapture_in_frame.html?touch (#45445)
    • PASS [expected FAIL] subtest: Test touchpointer capture in same-origin frame: Pointer down at inner frame and set pointer capture.
    • PASS [expected FAIL] subtest: Test touchpointer capture in same-origin frame: Pointer down at inner frame and set pointer capture to outer frame should not capture.
  • OK /resource-timing/test_resource_timing.https.html (#25216)
    • FAIL [expected PASS] subtest: PerformanceEntry has correct name, initiatorType, startTime, and duration (xmlhttprequest)

      assert_equals: expected 47.160000000000004 but got 47.15
      

  • OK /sanitizer-api/sanitizer-inert-document.html (#45260)
    • FAIL [expected PASS] subtest: Test whether setHTML loads the image.

      promise_test: Unhandled rejection with value: "The server observed a request. It shouldn't have."
      

    • PASS [expected FAIL] subtest: Test whether setHTMLUnsafe loads the image.
  • OK /touch-events/single-tap-when-touchend-listener-use-sync-xhr.html (#41175)
    • PASS [expected FAIL] subtest: Click event should be fired when touchend opens synchronous XHR
  • TIMEOUT /trusted-types/trusted-types-navigation.html?06-10 (#37920)
    • TIMEOUT [expected PASS] subtest: Navigate a frame via anchor with javascript:-urls in report-only mode.

      Test timed out
      

    • NOTRUN [expected TIMEOUT] subtest: Navigate a frame via anchor with javascript:-urls w/ default policy in report-only mode.
  • TIMEOUT [expected OK] /trusted-types/trusted-types-navigation.html?16-20 (#44835)
    • TIMEOUT [expected PASS] subtest: Navigate a frame via area with javascript:-urls w/ default policy in enforcing mode.

      Test timed out
      

    • NOTRUN [expected PASS] subtest: Navigate a frame via area with javascript:-urls in report-only mode.
    • NOTRUN [expected PASS] subtest: Navigate a frame via area with javascript:-urls w/ default policy in report-only mode.
  • TIMEOUT [expected OK] /trusted-types/trusted-types-navigation.html?26-30 (#38807)
    • TIMEOUT [expected PASS] subtest: Navigate a window via form-submission with javascript:-urls in report-only mode.

      Test timed out
      

    • NOTRUN [expected PASS] subtest: Navigate a window via form-submission with javascript:-urls w/ default policy in report-only mode.
    • NOTRUN [expected PASS] subtest: Navigate a frame via form-submission with javascript:-urls in enforcing mode.
    • NOTRUN [expected PASS] subtest: Navigate a frame via form-submission with javascript:-urls w/ default policy in enforcing mode.
  • TIMEOUT /trusted-types/trusted-types-navigation.html?31-35 (#38034)
    • PASS [expected TIMEOUT] subtest: Navigate a frame via form-submission with javascript:-urls in report-only mode.
    • TIMEOUT [expected NOTRUN] subtest: Navigate a frame via form-submission with javascript:-urls w/ default policy in report-only mode.

      Test timed out
      

  • OK [expected TIMEOUT] /webstorage/localstorage-about-blank-3P-iframe-opens-3P-window.partitioned.html (#29053)
    • FAIL [expected TIMEOUT] subtest: StorageKey: test 3P about:blank window opened from a 3P iframe

      assert_true: about:blank window opened by 3P iframe does not inherit 3P iframe's StorageKey expected true got false
      

@github-actions

Copy link
Copy Markdown

✨ Try run (#29372221445) succeeded.

@TimvdLippe TimvdLippe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work. Appreciate all the spec comments to be able to follow the code. Only two clarifying questions

@@ -1092,17 +1089,23 @@ impl LayoutThread {

// Load new @font-face rules and remove old ones if necessary.
// TODO: Can we make the invalidation set tell us whether any @font-face rules changed?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this TODO still relevant? I am not intimately familiar with this code, but I think it does now.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its still relevant. Ideally stylo could tell us whether a stylesheet that was added to the page and then we might not have to call rebuild_font_face_set at all.

.iter()
.find(|entry| entry.is_connected_to_font_face_rule(removed_font_face_rule))
else {
if cfg!(debug_assertions) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a pattern we use? It's the first time I see it and usually we unconditionally call unreachable.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for bugs that are minor (don't affect soundness, and likely won't break any other assumptions related to soundess) this is preferable to avoid panics (DOS) in production.
The problem with always only logging a warning is that its easy to ignore, so adding a debug assertion is a middle ground that helps fix such minor bugs (and i guess unconditional debug_assert!(true, ..) might be linted against?)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for bugs that are minor (don't affect soundness, and likely won't break any other assumptions related to soundess) this is preferable to avoid panics (DOS) in production.

Yes, that is the intention.

and i guess unconditional debug_assert!(true, ..) might be linted against?

I've tried it just now and it doesn't trigger lints. I don't have a strong preference on what we should use (but I have used if cfg!(debug_assertions) { unreachable!() } in a couple of places before).

@servo-highfive servo-highfive removed the S-awaiting-review There is new code that needs to be reviewed. label Jul 15, 2026
@simonwuelker
simonwuelker added this pull request to the merge queue Jul 15, 2026
@servo-highfive servo-highfive added the S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. label Jul 15, 2026
Merged via the queue into servo:main with commit e4a1464 Jul 15, 2026
76 checks passed
@simonwuelker
simonwuelker deleted the populate-document-fonts branch July 15, 2026 13:41
@servo-highfive servo-highfive removed the S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. label Jul 15, 2026
mrobinson pushed a commit to mrobinson/servo that referenced this pull request Jul 16, 2026
servo#46537)

I wasn't sure how to approach this in
servo#46509 but it turns out the necessary
code already exists, so it is quite trivial. Changes to these
descriptors do not yet reflect in the actual @font-face rule.

Testing: This change adds a test

---------

Signed-off-by: Simon Wülker <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Intermittent FAIL in /css/css-fonts/matching/font-unicode-PUA.html

4 participants