Skip to content

paint: Add support for a blinking text caret#43128

Merged
mrobinson merged 1 commit into
servo:mainfrom
mrobinson:blinking-caret
Mar 12, 2026
Merged

paint: Add support for a blinking text caret#43128
mrobinson merged 1 commit into
servo:mainfrom
mrobinson:blinking-caret

Conversation

@mrobinson

Copy link
Copy Markdown
Member

This change adds support for a blinking text caret using WebRender
DynamicProperties updates. This ensures that text caret updates are as
lightweight as possible. As part of this change, the initial framework
for paint-side animations is added. Though this is driven by a timer for
now (in order to not have to run full speed animations for text carets),
the idea is that more animations can be driven by Paint in the future
(in order to avoid doing layouts during animations).

In addition, a preference is added which controls the caret blink speed.
This is used to disable caret blinking during WPT testing.

Testing: There are no tests for this change. We do not currently have a good
way to test caret blinking as the caret is render only and fully testing
blinking would require well timed screenshot creation.
Fixes: #33237.

@mrobinson mrobinson requested a review from Loirooriol as a code owner March 10, 2026 09:46
@servo-highfive servo-highfive added the S-awaiting-review There is new code that needs to be reviewed. label Mar 10, 2026
@mrobinson mrobinson added the T-linux-wpt Do a try run of the WPT label Mar 10, 2026
@github-actions github-actions Bot removed the T-linux-wpt Do a try run of the WPT label Mar 10, 2026
@github-actions

Copy link
Copy Markdown

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

@github-actions

Copy link
Copy Markdown

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

Flaky unexpected result (25)
  • OK /_mozilla/css/offset_properties_inline.html (#40543)
    • FAIL [expected PASS] subtest: offsetTop

      assert_equals: offsetTop of #inline-1 should be 0. expected 0 but got -1
      

    • FAIL [expected PASS] subtest: offsetLeft

      assert_equals: offsetLeft of #inline-2 should be 40. expected 40 but got 25
      

  • ERROR [expected OK] /_mozilla/mozilla/img_find_non_sibling_map.html
  • CRASH [expected OK] /_webgl/conformance2/wasm/readpixels-2gb-in-4gb-wasm-memory.html
  • FAIL [expected PASS] /css/css-backgrounds/background-size-042.html
  • OK /css/css-fonts/variations/at-font-face-font-matching.html (#20684)
    • FAIL [expected PASS] subtest: Matching font-style: 'oblique 10deg' should prefer 'oblique 10deg' over 'oblique 5deg'

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

  • TIMEOUT [expected OK] /fetch/api/redirect/redirect-keepalive.https.any.html (#32153)
    • TIMEOUT [expected PASS] subtest: [keepalive][iframe][load] mixed content redirect; setting up

      Test timed out
      

  • OK [expected ERROR] /fetch/fetch-later/quota/same-origin-iframe/multiple-iframes.https.window.html (#35176)
  • OK /fetch/metadata/window-open.https.sub.html (#40339)
    • FAIL [expected PASS] subtest: Same-site window, forced, reloaded

      The operation is insecure.
      

  • OK /html/browsers/browsing-the-web/navigating-across-documents/005.html (#27062)
    • PASS [expected FAIL] subtest: Link with onclick navigation and href navigation
  • OK /html/browsers/history/the-history-interface/traverse_the_history_4.html (#21383)
    • FAIL [expected PASS] subtest: Multiple history traversals, last would be aborted

      assert_array_equals: Pages opened during history navigation expected property 1 to be 5 but got 3 (expected array [6, 5] got [6, 3])
      

  • OK /html/browsers/history/the-history-interface/traverse_the_history_5.html (#21383)
    • FAIL [expected PASS] subtest: Multiple history traversals, last would be aborted

      assert_array_equals: Pages opened during history navigation expected property 1 to be 5 but got 3 (expected array [6, 5] got [6, 3])
      

  • OK [expected TIMEOUT] /html/interaction/focus/the-autofocus-attribute/supported-elements.html (#24145)
    • FAIL [expected TIMEOUT] subtest: Host element with delegatesFocus should support autofocus

      assert_equals: expected Element node <div autofocus=""></div> but got Element node <body></body>
      

    • FAIL [expected NOTRUN] subtest: Host element with delegatesFocus including no focusable descendants should be skipped

      assert_equals: expected Element node <input autofocus=""></input> but got Element node <body><div autofocus=""></div><input autofocus=""></body>
      

    • FAIL [expected NOTRUN] subtest: Area element should support autofocus

      promise_test: Unhandled rejection with value: object "TypeError: can't access property "appendChild", w.document.querySelector(...) is null"
      

  • OK /html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/allow-scripts-flag-changing-1.html (#39694)
    • PASS [expected FAIL] subtest: Meta refresh is blocked by the allow-scripts sandbox flag at its creation time, not when refresh comes due
  • TIMEOUT /html/semantics/embedded-content/media-elements/autoplay-disabled-by-feature-policy.https.sub.html (#41221)
    • FAIL [expected TIMEOUT] subtest: Feature-Policy header: autoplay "none" disallows same-origin iframes.

      assert_false: autoplay expected false got true
      

  • 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 3 but got 0
      

  • TIMEOUT [expected OK] /html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_other_frame_popup.sub.html (#39702)
    • TIMEOUT [expected FAIL] subtest: Sandboxed iframe can not navigate other frame's popup

      Test timed out
      

  • 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.
  • OK /html/semantics/scripting-1/the-script-element/module/dynamic-import/blob-url.any.worker.html (#33909)
    • FAIL [expected PASS] subtest: Revoking a blob URL immediately after calling import will not fail

      promise_test: Unhandled rejection with value: object "TypeError: Module fetching failed"
      

  • CRASH [expected OK] /html/webappapis/scripting/processing-model-2/runtime-error-same-origin-with-hash.html
  • PASS [expected FAIL] /png/apng/acTL-plays-two.html (#41191)
  • FAIL [expected PASS] /png/apng/fcTL-dispose-background-final.html (#41581)
  • FAIL [expected PASS] /png/apng/fcTL-dispose-previous-first.html
  • OK /resource-timing/test_resource_timing.https.html (#25216)
    • PASS [expected FAIL] subtest: PerformanceEntry has correct name, initiatorType, startTime, and duration (xmlhttprequest)
  • OK [expected TIMEOUT] /trusted-types/trusted-types-navigation.html?06-10 (#37920)
    • PASS [expected TIMEOUT] subtest: Navigate a frame via anchor with javascript:-urls w/ default policy in report-only mode.
    • FAIL [expected NOTRUN] subtest: Navigate a window via anchor with javascript:-urls w/ a default policy throwing an exception in enforcing mode.

      promise_test: Unhandled rejection with value: "Unexpected message received: \"No securitypolicyviolation reported!\""
      

    • FAIL [expected NOTRUN] subtest: Navigate a window via anchor with javascript:-urls w/ a default policy throwing an exception in report-only mode.

      promise_test: Unhandled rejection with value: "Unexpected message received: \"No securitypolicyviolation reported!\""
      

  • OK [expected TIMEOUT] /webstorage/localstorage-about-blank-3P-iframe-opens-3P-window.partitioned.html (#29053)
    • PASS [expected TIMEOUT] subtest: StorageKey: test 3P about:blank window opened from a 3P iframe
Stable unexpected results that are known to be intermittent (20)
  • TIMEOUT /FileAPI/url/url-in-tags-revoke.window.html (#19978)
    • TIMEOUT [expected PASS] subtest: Fetching a blob URL immediately before revoking it works in <script> tags.

      Test timed out
      

  • FAIL [expected PASS] /_mozilla/mozilla/sslfail.html (#10760)
  • TIMEOUT [expected OK] /_mozilla/mozilla/window_resize_event.html (#36741)
    • TIMEOUT [expected PASS] subtest: Popup onresize event fires after resizeTo

      Test timed out
      

  • CRASH [expected PASS] /_mozilla/shadow-dom/move-element-with-ua-shadow-tree-crash.html (#39473)
  • OK /_webgl/conformance/textures/misc/texture-upload-size.html (#21770)
    • PASS [expected FAIL] subtest: WebGL test #45
    • PASS [expected FAIL] subtest: WebGL test #47
    • PASS [expected FAIL] subtest: WebGL test #49
    • PASS [expected FAIL] subtest: WebGL test #51
    • FAIL [expected PASS] subtest: WebGL test #53

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

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

      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 #57

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

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

      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 #61
    • PASS [expected FAIL] subtest: WebGL test #63
    • And 6 more unexpected results...
  • OK /content-security-policy/frame-ancestors/frame-ancestors-path-ignored.window.html (#36468)
    • FAIL [expected PASS] subtest: A 'frame-ancestors' CSP directive with a URL that includes a path should be ignored.

      assert_unreached: The IFrame should have been blocked (or cross-origin). It wasn't. Reached unreachable code
      

  • OK /css/css-cascade/layer-cssom-order-reverse.html (#36094)
    • PASS [expected FAIL] subtest: Delete layer invalidates @font-face
  • OK /css/css-cascade/layer-font-face-override.html (#35935)
    • PASS [expected FAIL] subtest: @font-face override update with appended sheet 1
    • PASS [expected FAIL] subtest: @font-face override update with appended sheet 2
  • OK /css/css-fonts/generic-family-keywords-001.html (#37467)
    • FAIL [expected PASS] subtest: @font-face matching for quoted and unquoted generic(fangsong)

      assert_equals: quoted generic(fangsong) matches  @font-face rule expected 30 but got 50
      

    • FAIL [expected PASS] subtest: @font-face matching for quoted and unquoted generic(kai)

      assert_equals: quoted generic(kai) matches  @font-face rule expected 30 but got 50
      

    • FAIL [expected PASS] subtest: @font-face matching for quoted and unquoted generic(khmer-mul)

      assert_equals: quoted generic(khmer-mul) matches  @font-face rule expected 30 but got 50
      

    • FAIL [expected PASS] subtest: @font-face matching for quoted and unquoted generic(nastaliq)

      assert_equals: quoted generic(nastaliq) matches  @font-face rule expected 30 but got 50
      

  • ERROR [expected OK] /fetch/fetch-later/quota/same-origin-iframe/accumulated-oversized-payload.https.window.html (#41705)
  • OK /fetch/metadata/generated/css-font-face.sub.tentative.html (#34624)
    • PASS [expected FAIL] subtest: sec-fetch-storage-access - Not sent to non-trustworthy same-origin destination
  • TIMEOUT /fetch/metadata/generated/css-images.https.sub.tentative.html (#42229)
    • FAIL [expected PASS] subtest: content sec-fetch-site - Cross-Site -> Same-Site

      assert_unreached: Reached unreachable code
      

  • OK [expected ERROR] /focus/focus-event-after-switching-iframes.sub.html (#40368)
  • OK [expected TIMEOUT] /html/anonymous-iframe/indexeddb.tentative.https.window.html (#39254)
    • FAIL [expected TIMEOUT] subtest: indexeddb

      assert_equals: expected (undefined) undefined but got (string) "7e56b70f-fd7f-4e6a-aabb-7e32709a4db6"
      

  • OK /html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-click.html (#28697)
    • PASS [expected FAIL] subtest: aElement.click() before the load event must NOT replace
  • OK /resource-timing/test_resource_timing.html (#25720)
    • FAIL [expected PASS] subtest: PerformanceEntry has correct name, initiatorType, startTime, and duration (link)

      assert_equals: expected 7.060000000000002 but got 7.06
      

  • 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
  • OK /visual-viewport/resize-event-order.html (#41981)
    • PASS [expected FAIL] subtest: Popup: DOMWindow resize fired before VisualViewport.
  • OK /webdriver/tests/classic/dismiss_alert/dismiss.py (#39098)
    • FAIL [expected PASS] subtest: test_dismiss_in_popup_window

      AssertionError: no such alert (404): No user prompt is currently active.
      

  • ERROR [expected OK] /webxr/render_state_update.https.html (#27535)
Stable unexpected results (1)
  • OK /dom/nodes/moveBefore/iframe-document-preserve.window.html
    • FAIL [expected PASS] subtest: moveBefore(): cross-origin iframe is preserved: remove self via innerHTML

      assert_equals: iframe does not fire a second load event expected 1 but got 0
      

@github-actions

Copy link
Copy Markdown

⚠️ Try run (#22908626710) failed!

@mrobinson mrobinson added the T-linux-wpt Do a try run of the WPT label Mar 10, 2026
@github-actions github-actions Bot removed the T-linux-wpt Do a try run of the WPT label Mar 10, 2026
@github-actions

Copy link
Copy Markdown

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

@github-actions

Copy link
Copy Markdown

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

Flaky unexpected result (26)
  • OK /_mozilla/css/offset_properties_inline.html (#40543)
    • FAIL [expected PASS] subtest: offsetTop

      assert_equals: offsetTop of #inline-1 should be 0. expected 0 but got -1
      

    • FAIL [expected PASS] subtest: offsetLeft

      assert_equals: offsetLeft of #inline-2 should be 40. expected 40 but got 25
      

  • ERROR [expected OK] /_mozilla/mozilla/img_load_more_than_cache.html
  • CRASH [expected OK] /_webgl/conformance2/wasm/readpixels-2gb-in-4gb-wasm-memory.html
  • OK /content-security-policy/frame-ancestors/frame-ancestors-path-ignored.window.html (#36468)
    • FAIL [expected PASS] subtest: A 'frame-ancestors' CSP directive with a URL that includes a path should be ignored.

      assert_unreached: The IFrame should have been blocked (or cross-origin). It wasn't. Reached unreachable code
      

  • FAIL [expected PASS] /css/css-backgrounds/border-image-repeat-space-9.html
  • TIMEOUT /fetch/metadata/generated/css-images.sub.tentative.html (#29047)
    • TIMEOUT [expected PASS] subtest: background-image sec-fetch-site - Not sent to non-trustworthy same-origin destination

      Test timed out
      

  • ERROR /fetch/metadata/generated/serviceworker.https.sub.html (#36247)
    • PASS [expected FAIL] subtest: sec-fetch-site - Same origin, no options - registration
  • OK [expected ERROR] /focus/focus-event-after-switching-iframes.sub.html (#40368)
  • OK /html/browsers/browsing-the-web/navigating-across-documents/008.html (#24456)
    • PASS [expected FAIL] subtest: Link with onclick form submit to javascript url and href navigation
  • OK /html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-click.html (#28697)
    • PASS [expected FAIL] subtest: aElement.click() before the load event must NOT replace
  • TIMEOUT [expected OK] /html/interaction/focus/the-autofocus-attribute/autofocus-dialog.html (#29087)
    • TIMEOUT [expected FAIL] subtest: <dialog>-contained autofocus element gets focused when the dialog is shown

      Test timed out
      

  • OK /html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/allow-scripts-flag-changing-1.html (#39694)
    • PASS [expected FAIL] subtest: Meta refresh is blocked by the allow-scripts sandbox flag at its creation time, not when refresh comes due
  • TIMEOUT /html/semantics/embedded-content/media-elements/autoplay-allowed-by-feature-policy.https.sub.html (#41404)
    • PASS [expected TIMEOUT] subtest: Feature-Policy header: autoplay * allows same-origin iframes.
  • CRASH [expected OK] /html/semantics/embedded-content/media-elements/offsets-into-the-media-resource/currentTime-move-within-document.html
  • TIMEOUT [expected OK] /html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_other_frame_popup.sub.html (#39702)
    • TIMEOUT [expected FAIL] subtest: Sandboxed iframe can not navigate other frame's popup

      Test timed out
      

  • CRASH [expected OK] /html/semantics/forms/form-submission-0/form-submit-iframe-then-location-navigate.html (#29634)
  • 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.
  • OK /html/semantics/scripting-1/the-script-element/execution-timing/077.html (#22139)
    • FAIL [expected PASS] subtest: adding several types of scripts through the DOM and removing some of them confuses scheduler

      assert_array_equals: expected property 1 to be "Script #1 ran" but got "Script #3 ran" (expected array ["Script #2 ran", "Script #1 ran", "Script #3 ran", "Script #4 ran"] got ["Script #2 ran", "Script #3 ran", "Script #4 ran", "Script #1 ran"])
      

  • CRASH [expected OK] /html/webappapis/system-state-and-capabilities/the-navigator-object/secure_context.html
  • CRASH [expected OK] /html/webappapis/timers/cleartimeout-clearinterval.any.worker.html
  • PASS [expected FAIL] /png/apng/fcTL-dispose-none.html (#41817)
  • 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?31-35 (#38034)
    • FAIL [expected PASS] subtest: Navigate a frame via form-submission with javascript:-urls in report-only mode.

      promise_test: Unhandled rejection with value: "Unexpected message received: \"No securitypolicyviolation reported!\""
      

  • OK /webdriver/tests/classic/element_send_keys/file_upload.py
    • FAIL [expected PASS] subtest: test_empty_text

      webdriver.error.NoSuchWindowException: no such window (404): No such window
      

  • OK /webdriver/tests/classic/execute_script/execute.py
    • ERROR [expected PASS] subtest: test_no_top_browsing_context

      setup error: webdriver.error.NoSuchElementException: no such element (404)
      

  • OK /webdriver/tests/classic/perform_actions/perform.py
    • FAIL [expected PASS] subtest: test_input_source_action_sequence_actions_pause_duration_valid[none]

      AssertionError: no such window (404): No such window
      

Stable unexpected results that are known to be intermittent (16)
  • FAIL [expected PASS] /_mozilla/mozilla/sslfail.html (#10760)
  • TIMEOUT [expected OK] /_mozilla/mozilla/window_resize_event.html (#36741)
    • TIMEOUT [expected PASS] subtest: Popup onresize event fires after resizeTo

      Test timed out
      

  • OK /_webgl/conformance/textures/misc/texture-upload-size.html (#21770)
    • PASS [expected FAIL] subtest: WebGL test #45
    • PASS [expected FAIL] subtest: WebGL test #47
    • PASS [expected FAIL] subtest: WebGL test #49
    • PASS [expected FAIL] subtest: WebGL test #51
    • FAIL [expected PASS] subtest: WebGL test #53

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

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

      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 #57

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

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

      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 #61
    • PASS [expected FAIL] subtest: WebGL test #63
    • And 14 more unexpected results...
  • OK /css/css-fonts/generic-family-keywords-003.html (#38994)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted cursive (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted fantasy (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted monospace (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted system-ui (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted math (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted generic(fangsong) (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted generic(kai) (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted generic(khmer-mul) (drawing text in a canvas)
  • ERROR [expected OK] /fetch/fetch-later/quota/same-origin-iframe/accumulated-oversized-payload.https.window.html (#41705)
  • OK [expected ERROR] /fetch/fetch-later/quota/same-origin-iframe/sandboxed-iframe.https.window.html (#41704)
  • OK /fetch/metadata/generated/css-font-face.https.sub.tentative.html (#32732)
    • PASS [expected FAIL] subtest: sec-fetch-mode
    • PASS [expected FAIL] subtest: sec-fetch-dest
    • PASS [expected FAIL] subtest: sec-fetch-user
    • PASS [expected FAIL] subtest: sec-fetch-storage-access - Cross-site
  • OK /fetch/metadata/generated/css-font-face.sub.tentative.html (#34624)
    • PASS [expected FAIL] subtest: sec-fetch-storage-access - Not sent to non-trustworthy cross-site destination
  • TIMEOUT /fetch/metadata/generated/css-images.https.sub.tentative.html (#42229)
    • FAIL [expected PASS] subtest: content sec-fetch-site - Same-Origin -> Same-Site

      assert_unreached: Reached unreachable code
      

  • OK [expected TIMEOUT] /html/anonymous-iframe/indexeddb.tentative.https.window.html (#39254)
    • FAIL [expected TIMEOUT] subtest: indexeddb

      assert_equals: expected (undefined) undefined but got (string) "f40fc17d-98b2-4dc0-a178-782902404d8f"
      

  • OK /html/browsers/history/the-history-interface/traverse_the_history_2.html (#21383)
    • FAIL [expected PASS] subtest: Multiple history traversals, last would be aborted

      assert_array_equals: Pages opened during history navigation lengths differ, expected array [6, 3] length 2, got [6, 2, 2] length 3
      

  • OK [expected TIMEOUT] /html/interaction/focus/the-autofocus-attribute/supported-elements.html (#24145)
    • FAIL [expected TIMEOUT] subtest: Host element with delegatesFocus should support autofocus

      assert_equals: expected Element node <div autofocus=""></div> but got Element node <body><div autofocus=""></div></body>
      

    • FAIL [expected NOTRUN] subtest: Host element with delegatesFocus including no focusable descendants should be skipped

      assert_equals: expected Element node <input autofocus=""></input> but got Element node <body></body>
      

    • FAIL [expected NOTRUN] subtest: Area element should support autofocus

      assert_equals: expected Element node <area autofocus="" href="/common/blank.html"></area> but got Element node <body>
      <img src="/media/poster.png" usemap="#map">
      <map n...
      

  • 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 4 but got 0
      

  • OK /navigation-timing/test-navigation-type-reload.html (#33334)
    • FAIL [expected PASS] subtest: Reload domComplete > Original domComplete

      assert_true: Reload domComplete > Original domComplete expected true got false
      

    • FAIL [expected PASS] subtest: Reload domContentLoadedEventEnd > Original domContentLoadedEventEnd

      assert_true: Reload domContentLoadedEventEnd > Original domContentLoadedEventEnd expected true got false
      

    • FAIL [expected PASS] subtest: Reload domContentLoadedEventStart > Original domContentLoadedEventStart

      assert_true: Reload domContentLoadedEventStart > Original domContentLoadedEventStart expected true got false
      

    • FAIL [expected PASS] subtest: Reload domInteractive > Original domInteractive

      assert_true: Reload domInteractive > Original domInteractive expected true got false
      

    • FAIL [expected PASS] subtest: Reload fetchStart > Original fetchStart

      assert_true: Reload fetchStart > Original fetchStart expected true got false
      

    • FAIL [expected PASS] subtest: Reload loadEventEnd > Original loadEventEnd

      assert_true: Reload loadEventEnd > Original loadEventEnd expected true got false
      

    • FAIL [expected PASS] subtest: Reload loadEventStart > Original loadEventStart

      assert_true: Reload loadEventStart > Original loadEventStart expected true got false
      

  • OK /resource-timing/test_resource_timing.html (#25720)
    • PASS [expected FAIL] subtest: PerformanceEntry has correct name, initiatorType, startTime, and duration (iframe)
    • FAIL [expected PASS] subtest: PerformanceEntry has correct name, initiatorType, startTime, and duration (link)

      assert_equals: expected 5.859999999999999 but got 5.86
      

  • ERROR [expected OK] /webxr/render_state_update.https.html (#27535)

@github-actions

Copy link
Copy Markdown

✨ Try run (#22910632629) succeeded.

Comment thread components/paint/painter.rs Outdated
WindowGLContext::initialize_image_handler(&mut external_image_handlers);

let embedder_to_constellation_sender = paint.embedder_to_constellation_sender.clone();
let timer_refresh_driver = LazyCell::new(Default::default);

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.

Suggested change
let timer_refresh_driver = LazyCell::new(Default::default);
let timer_refresh_driver = LazyCell::default();

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.

Done.

let value = if caret_visible || self.remaining_blink_count == 0 {
self.original_caret_color
} else {
ColorF::new(0., 0., 0., 0.)

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.

Suggested change
ColorF::new(0., 0., 0., 0.)
ColorF::TRANSPARENT

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.

Done.

@mrobinson mrobinson force-pushed the blinking-caret branch 2 times, most recently from 87e6cbb to 61c303d Compare March 11, 2026 14:45

@mukilan mukilan left a comment

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.

Nice! One question - when I load google.com, I see that the caret starts blinking only after a couple of seconds (approximately). Is that due to some other issue in our layout system?

}
}

/// This structure tracks the animations active for a given pipeline. Currently only caret

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.

Given that the struct called CaretAnimation which is very specific, I'm not sure if it make sense to imply that it tracks all animations in the pipeline. Was this doc comment meant to be for the PipelineAnimations struct?

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.

Oh, exactly. I've moved this to PipelineAnimations and have added relevant rustdoc comment here.

Comment thread components/layout/display_list/mod.rs Outdated
let insertion_point_common = builder.common_properties(insertion_point_rect, &parent_style);
builder.wr().push_rect(

let property_binding_key = PropertyBindingKey::new(1);

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'm not familar with the WebRender animation system. Why is it safe here to use a fixed key of 1?

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.

Hrm. This should likely be based on the pipeline. I'll make that change.

self.need_update.store(false, Ordering::Relaxed);

if colors.is_empty() {
self.caret_visible.set(true);

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.

It is not obvious to me why we need to set caret_visible to true here. Is it because all pipelines have reached the cursor timeout? Perhaps we can add a comment here?

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'll add a comment clarifying this. Thanks for pointing it out.

@servo-highfive servo-highfive removed the S-awaiting-review There is new code that needs to be reviewed. label Mar 12, 2026
@mrobinson

Copy link
Copy Markdown
Member Author

Nice! One question - when I load google.com, I see that the caret starts blinking only after a couple of seconds (approximately). Is that due to some other issue in our layout system?

Thanks for the review. I think the behavior you are seeing on the site is the result of lag and the fact that the cursor starts in the visible state. That said, this matches the behavior I see when clicking on things in Firefox as well (such as the URL bar), so I think it isn't so bad.

This change adds support for a blinking text caret using WebRender
`DynamicProperties` updates. This ensures that text caret updates are as
lightweight as possible. As part of this change, the initial framework
for paint-side animations is added. Though this is driven by a timer for
now (in order to not have to run full speed animations for text carets),
the idea is that more animations can be driven by `Paint` in the future
(in order to avoid doing layouts during animations).

In addition, a preference is added which controls the caret blink speed.
This is used to disable caret blinking during WPT testing.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
@servo-highfive servo-highfive added the S-awaiting-review There is new code that needs to be reviewed. label Mar 12, 2026
@mrobinson mrobinson enabled auto-merge March 12, 2026 11:39
@mrobinson mrobinson added this pull request to the merge queue Mar 12, 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 Mar 12, 2026
Merged via the queue into servo:main with commit ea99515 Mar 12, 2026
33 checks passed
@mrobinson mrobinson deleted the blinking-caret branch March 12, 2026 12:55
@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 Mar 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-awaiting-review There is new code that needs to be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No blinking cursor in editable elements

4 participants